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/11/29 19:58:31 UTC

[3/4] qpid-dispatch git commit: DISPATCH-886 Account for / in router name and prevent script injections

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/55d7bd34/console/hawtio/src/main/webapp/plugin/js/qdrService.js
----------------------------------------------------------------------
diff --git a/console/hawtio/src/main/webapp/plugin/js/qdrService.js b/console/hawtio/src/main/webapp/plugin/js/qdrService.js
deleted file mode 120000
index 2d2a672..0000000
--- a/console/hawtio/src/main/webapp/plugin/js/qdrService.js
+++ /dev/null
@@ -1 +0,0 @@
-../../../../../../stand-alone/plugin/js/qdrService.js
\ No newline at end of file
diff --git a/console/hawtio/src/main/webapp/plugin/js/qdrService.js b/console/hawtio/src/main/webapp/plugin/js/qdrService.js
new file mode 100644
index 0000000..73102a7
--- /dev/null
+++ b/console/hawtio/src/main/webapp/plugin/js/qdrService.js
@@ -0,0 +1,1232 @@
+/*
+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) {
+
+  // The QDR service handles the connection to
+  // the server in the background
+  QDR.module.factory("QDRService", ['$rootScope', '$http', '$timeout', '$resource', '$location', function($rootScope, $http, $timeout, $resource, $location) {
+    var self = {
+
+      rhea: require("rhea"),
+      timeout: 10,             // seconds to wait before assuming a request has failed
+      updateInterval: 2000,   // milliseconds between background updates
+      connectActions: [],
+      disconnectActions: [],
+      updatedActions: {},
+      updating: false,        // are we updating the node list in the background
+      maxCorrelatorDepth: 10, // max number of outstanding requests to allow
+
+      /*
+       * @property message
+       * The proton message that is used to send commands
+       * and receive responses
+       */
+      sender: undefined,
+      receiver: undefined,
+      version: undefined,
+      sendable: false,
+
+      schema: undefined,
+
+      connected: false,
+      gotTopology: false,
+      errorText: undefined,
+      connectionError: undefined,
+
+      addConnectAction: function(action) {
+        if (angular.isFunction(action)) {
+          self.connectActions.push(action);
+        }
+      },
+      addDisconnectAction: function(action) {
+        if (angular.isFunction(action)) {
+          self.disconnectActions.push(action);
+        }
+      },
+      delDisconnectAction: function(action) {
+        if (angular.isFunction(action)) {
+          var index = self.disconnectActions.indexOf(action)
+          if (index >= 0)
+            self.disconnectActions.splice(index, 1)
+        }
+      },
+      addUpdatedAction: function(key, action) {
+        if (angular.isFunction(action)) {
+          self.updatedActions[key] = action;
+        }
+      },
+      delUpdatedAction: function(key) {
+        if (key in self.updatedActions)
+          delete self.updatedActions[key];
+      },
+
+      executeConnectActions: function() {
+        self.connectActions.forEach(function(action) {
+          try {
+            action.apply();
+          } catch (e) {
+            // in case the page that registered the handler has been unloaded
+            QDR.log.info(e.message)
+          }
+        });
+        self.connectActions = [];
+      },
+      executeDisconnectActions: function() {
+        self.disconnectActions.forEach(function(action) {
+          try {
+            action.apply();
+          } catch (e) {
+            // in case the page that registered the handler has been unloaded
+          }
+        });
+        self.disconnectActions = [];
+      },
+      executeUpdatedActions: function() {
+        for (action in self.updatedActions) {
+//          try {
+            self.updatedActions[action].apply();
+/*          } catch (e) {
+QDR.log.debug("caught error executing updated actions")
+console.dump(e)
+            delete self.updatedActions[action]
+          }
+          */
+        }
+      },
+      redirectWhenConnected: function(org) {
+        $location.path(QDR.pluginRoot + "/connect")
+        $location.search('org', org);
+      },
+
+      notifyTopologyDone: function() {
+        if (!self.gotTopology) {
+          QDR.log.debug("topology was just initialized");
+          //console.dump(self.topology._nodeInfo)
+          self.gotTopology = true;
+          //$rootScope.$apply();
+        } else {
+          //QDR.log.debug("topology model was just updated");
+        }
+        self.executeUpdatedActions();
+
+      },
+
+      isConnected: function() {
+        return self.connected;
+      },
+
+      versionCheck: function (minVer) {
+        var verparts = self.version.split('.')
+        var minparts = minVer.split('.')
+        try {
+          for (var i=0; i<minparts.length; ++i) {
+            if (parseInt(minVer[i] > parseInt(verparts[i])))
+              return false
+          }
+        } catch (e) {
+          QDR.log.debug("error doing version check between: " + self.version + " and " + minVer + " " + e.message)
+          return false
+        }
+        return true
+      },
+
+      correlator: {
+        _objects: {},
+        _correlationID: 0,
+
+        corr: function() {
+          var id = ++this._correlationID + "";
+          this._objects[id] = {
+            resolver: null
+          }
+          return id;
+        },
+        request: function() {
+          //QDR.log.debug("correlator:request");
+          return this;
+        },
+        then: function(id, resolver, error) {
+          //QDR.log.debug("registered then resolver for correlationID: " + id);
+          if (error) {
+            //QDR.log.debug("then received an error. deleting correlator")
+            delete this._objects[id];
+            return;
+          }
+          this._objects[id].resolver = resolver;
+        },
+        // called by receiver's on('message') handler when a response arrives
+        resolve: function(context) {
+          var correlationID = context.message.correlation_id;
+          this._objects[correlationID].resolver(context.message.body, context);
+          delete this._objects[correlationID];
+        },
+        depth: function () {
+          return Object.keys(this._objects).length
+        }
+      },
+
+      onSubscription: function() {
+        self.executeConnectActions();
+        var org = $location.search()
+        if (org)
+          org = org.org
+        if (org && org.length > 0 && org !== "connect") {
+          self.getSchema(function () {
+            self.setUpdateEntities([])
+            self.topology.get()
+            self.addUpdatedAction('onSub', function () {
+              self.delUpdatedAction('onSub')
+              $timeout( function () {
+                $location.path(QDR.pluginRoot + '/' + org)
+                $location.search('org', null)
+                $location.replace()
+              })
+            })
+          });
+        }
+      },
+
+      startUpdating: function() {
+        self.stopUpdating(true);
+        QDR.log.info("startUpdating called")
+        self.updating = true;
+        self.topology.get();
+      },
+      stopUpdating: function(silent) {
+        self.updating = false;
+        if (self.topology._getTimer) {
+          clearTimeout(self.topology._getTimer)
+          self.topology._getTimer = null;
+        }
+        if (self.topology._waitTimer) {
+          clearTimeout(self.topology._waitTimer)
+          self.topology._waitTimer = null;
+        }
+        if (self.topology._gettingTopo) {
+          if (self.topology.q)
+            self.topology.q.abort()
+        }
+        if (!silent)
+          QDR.log.info("stopUpdating called")
+      },
+
+      cleanUp: function() {},
+      error: function(line) {
+        if (line.num) {
+          QDR.log.debug("error - num: ", line.num, " message: ", line.message);
+        } else {
+          QDR.log.debug("error - message: ", line.message);
+        }
+      },
+      disconnected: function(line) {
+        QDR.log.debug("Disconnected from QDR server");
+        self.executeDisconnectActions();
+      },
+
+      nameFromId: function(id) {
+        // the router id looks like 'amqp:/topo/0/routerName/$managemrnt'
+        var parts = id.split('/')
+        // handle cases where the router name contains a /
+        parts.splice(0, 3)  // remove amqp, topo, 0
+        parts.pop()         // remove $management
+        return parts.join('/')
+      },
+
+      humanify: function(s) {
+        if (!s || s.length === 0)
+          return s;
+        var t = s.charAt(0).toUpperCase() + s.substr(1).replace(/[A-Z]/g, ' $&');
+        return t.replace(".", " ");
+      },
+      pretty: function(v) {
+        var formatComma = d3.format(",");
+        if (!isNaN(parseFloat(v)) && isFinite(v))
+          return formatComma(v);
+        return v;
+      },
+
+      nodeNameList: function() {
+        var nl = [];
+        for (var id in self.topology._nodeInfo) {
+          nl.push(self.nameFromId(id));
+        }
+        return nl.sort();
+      },
+
+      nodeIdList: function() {
+        var nl = [];
+        for (var id in self.topology._nodeInfo) {
+          nl.push(id);
+        }
+        return nl.sort();
+      },
+
+      nodeList: function() {
+        var nl = [];
+        for (var id in self.topology._nodeInfo) {
+          nl.push({
+            name: self.nameFromId(id),
+            id: id
+          });
+        }
+        return nl;
+      },
+
+      isLargeNetwork: function () {
+        return Object.keys(self.topology._nodeInfo).length >= 12
+      },
+      isMSIE: function () {
+        return (document.documentMode || /Edge/.test(navigator.userAgent))
+      },
+
+      // given an attribute name array, find the value at the same index in the values array
+      valFor: function(aAr, vAr, key) {
+        var idx = aAr.indexOf(key);
+        if ((idx > -1) && (idx < vAr.length)) {
+          return vAr[idx];
+        }
+        return null;
+      },
+
+      isArtemis: function(d) {
+        return (d.nodeType === 'route-container' || d.nodeType === 'on-demand') && (d.properties && d.properties.product === 'apache-activemq-artemis');
+      },
+
+      isQpid: function(d) {
+        return (d.nodeType === 'route-container' || d.nodeType === 'on-demand') && (d.properties && d.properties.product === 'qpid-cpp');
+      },
+
+      isAConsole: function(properties, connectionId, nodeType, key) {
+        return self.isConsole({
+          properties: properties,
+          connectionId: connectionId,
+          nodeType: nodeType,
+          key: key
+        })
+      },
+      isConsole: function(d) {
+        // use connection properties if available
+        return (d && d['properties'] && d['properties']['console_identifier'] === 'Dispatch console')
+      },
+
+      flatten: function(attributes, result) {
+        var flat = {}
+        attributes.forEach(function(attr, i) {
+          if (result && result.length > i)
+            flat[attr] = result[i]
+        })
+        return flat;
+      },
+      isConsoleLink: function(link) {
+        // find the connection for this link
+        var conns = self.topology.nodeInfo()[link.nodeId]['.connection']
+        var connIndex = conns.attributeNames.indexOf("identity")
+        var linkCons = conns.results.filter(function(conn) {
+          return conn[connIndex] === link.connectionId;
+        })
+        var conn = self.flatten(conns.attributeNames, linkCons[0]);
+
+        return self.isConsole(conn)
+      },
+
+      quiesceLink: function(nodeId, name) {
+        function gotMethodResponse(nodeName, entity, response, context) {
+          var statusCode = context.message.application_properties.statusCode;
+          if (statusCode < 200 || statusCode >= 300) {
+            Core.notification('error', context.message.statusDescription);
+            QDR.log.info('Error ' + context.message.statusDescription)
+          }
+        }
+        var attributes = {
+          adminStatus: 'disabled',
+          name: name
+        };
+        self.sendMethod(nodeId, "router.link", attributes, "UPDATE", undefined, gotMethodResponse)
+      },
+      addr_text: function(addr) {
+        if (!addr)
+          return "-"
+        if (addr[0] == 'M')
+          return addr.substring(2)
+        else
+          return addr.substring(1)
+      },
+      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] == 'E') return "link-incoming"
+        if (addr[0] == 'D') return "link-outgoing"
+        if (addr[0] == 'F') return "link-outgoing"
+        if (addr[0] == 'T') return "topo"
+        return "unknown: " + addr[0]
+      },
+      identity_clean: function(identity) {
+        if (!identity)
+          return "-"
+        var pos = identity.indexOf('/')
+        if (pos >= 0)
+          return identity.substring(pos + 1)
+        return identity
+      },
+
+      queueDepth: function () {
+        var qdepth = self.maxCorrelatorDepth - self.correlator.depth()
+        if (qdepth <= 0)
+          qdepth = 1;
+//QDR.log.debug("queueDepth requested " + qdepth + "(" + self.correlator.depth() + ")")
+        return qdepth;
+      },
+      // check if all nodes have this entity. if not, get them
+      initEntity: function (entity, callback) {
+        var callNeeded = Object.keys(self.topology._nodeInfo).some( function (node) {
+          return !angular.isDefined(self.topology._nodeInfo[node][entity])
+        })
+        if (callNeeded) {
+          self.loadEntity(entity, callback)
+        } else
+          callback()
+      },
+
+      // get/refresh entities for all nodes
+      loadEntity: function (entities, callback) {
+        if (Object.prototype.toString.call(entities) !== '[object Array]') {
+          entities = [entities]
+        }
+        var q = QDR.queue(self.queueDepth())
+        for (node in self.topology._nodeInfo) {
+          for (var i=0; i<entities.length; ++i) {
+            var entity = entities[i]
+            q.defer(self.ensureNodeInfo, node, entity, [], q)
+          }
+        }
+        q.await(function (error) {
+          clearTimeout(self.topology._waitTimer)
+          callback();
+        })
+      },
+
+      // enusre all the topology nones have all these entities
+      ensureAllEntities: function (entityAttribs, callback, extra) {
+        self.ensureEntities(Object.keys(self.topology._nodeInfo), entityAttribs, callback, extra)
+      },
+
+      // ensure these nodes have all these entities. don't fetch unless forced to
+      ensureEntities: function (nodes, entityAttribs, callback, extra) {
+        if (Object.prototype.toString.call(entityAttribs) !== '[object Array]') {
+          entityAttribs = [entityAttribs]
+        }
+        if (Object.prototype.toString.call(nodes) !== '[object Array]') {
+          nodes = [nodes]
+        }
+        var q = QDR.queue(self.queueDepth())
+        for (var n=0; n<nodes.length; ++n) {
+          for (var i=0; i<entityAttribs.length; ++i) {
+            var ea = entityAttribs[i]
+            // if we don'e already have the entity or we want to force a refresh
+            if (!self.topology._nodeInfo[nodes[n]][ea.entity] || ea.force)
+              q.defer(self.ensureNodeInfo, nodes[n], ea.entity, ea.attrs || [], q)
+          }
+        }
+        q.await(function (error) {
+          clearTimeout(self.topology._waitTimer)
+          callback(extra);
+        })
+      },
+
+      // queue up a request to get certain attributes for one entity for a node and return the results
+      fetchEntity: function (node, entity, attrs, callback) {
+        var results = {}
+        var gotResponse = function (nodeName, dotentity, response) {
+          results = response
+        }
+        var q = QDR.queue(self.queueDepth())
+        q.defer(self.fetchNodeInfo, node, entity, attrs, q, gotResponse)
+        q.await(function (error) {
+          callback(node, entity, results)
+        })
+      },
+
+      // get/refreshes entities for all topology.nodes
+      // call doneCallback when all data is available
+      // optionally supply a resultCallBack that will be called as each result is avaialble
+      // if a resultCallBack is supplied, the calling function is responsible for accumulating the responses
+      //   otherwise the responses will be returned to the doneCallback as an object
+      fetchAllEntities: function (entityAttribs, doneCallback, resultCallback) {
+        var q = QDR.queue(self.queueDepth())
+        var results = {}
+        if (!resultCallback) {
+          resultCallback = function (nodeName, dotentity, response) {
+            if (!results[nodeName])
+              results[nodeName] = {}
+            results[nodeName][dotentity] = angular.copy(response);
+          }
+        }
+        var gotAResponse = function (nodeName, dotentity, response) {
+          resultCallback(nodeName, dotentity, response)
+        }
+        if (Object.prototype.toString.call(entityAttribs) !== '[object Array]') {
+          entityAttribs = [entityAttribs]
+        }
+        var nodes = Object.keys(self.topology._nodeInfo)
+        for (var n=0; n<nodes.length; ++n) {
+          for (var i=0; i<entityAttribs.length; ++i) {
+            var ea = entityAttribs[i]
+            q.defer(self.fetchNodeInfo, nodes[n], ea.entity, ea.attrs || [], q, gotAResponse)
+          }
+        }
+        q.await(function (error) {
+          doneCallback(results);
+        })
+      },
+
+      setUpdateEntities: function (entities) {
+        self.topology._autoUpdatedEntities = entities
+      },
+      addUpdateEntity: function (entity) {
+        if (self.topology._autoUpdatedEntities.indexOf(entity) == -1)
+          self.topology._autoUpdatedEntities.push(entity)
+      },
+      delUpdateEntity: function (entity) {
+        var index = self.topology._autoUpdatedEntities.indexOf(entity)
+        if (index != -1)
+          self.topology._autoUpdatedEntities.splice(index, 1)
+      },
+
+      /*
+       * send the management messages that build up the topology
+       *
+       *
+       */
+      topology: {
+        _gettingTopo: false,
+        _nodeInfo: {},
+        _lastNodeInfo: {},
+        _waitTimer: null,
+        _getTimer: null,
+        _autoUpdatedEntities: [],
+        q: null,
+
+        nodeInfo: function() {
+          return self.topology._nodeInfo
+        },
+
+        get: function() {
+          if (self.topology._gettingTopo) {
+            QDR.log.debug("asked to get topology but was already getting it")
+            if (self.topology.q)
+              self.topology.q.abort()
+          }
+          self.topology.q = null
+          if (!self.connected) {
+            QDR.log.debug("topology get failed because !self.connected")
+            return;
+          }
+          if (self.topology._getTimer) {
+            clearTimeout(self.topology._getTimer)
+            self.topology._getTimer = null
+          }
+
+          //QDR.log.info("starting get topology with correlator.depth of " + self.correlator.depth())
+          self.topology._gettingTopo = true;
+          self.errorText = undefined;
+
+          // get the list of nodes to query.
+          // once this completes, we will get the info for each node returned
+          self.getRemoteNodeInfo(function(response, context) {
+            if (Object.prototype.toString.call(response) === '[object Array]') {
+              // remove dropped nodes
+              var keys = Object.keys(self.topology._nodeInfo)
+              for (var i=0; i<keys.length; ++i) {
+                if (response.indexOf(keys[i]) < 0) {
+                  delete self.topology._nodeInfo[keys[i]]
+                }
+              }
+              // add any new nodes
+              // if there is only one node, it will not be returned
+              if (response.length === 0) {
+                var parts = self.receiver.remote.attach.source.address.split('/')
+                parts[parts.length-1] = '$management'
+                response.push(parts.join('/'))
+                //QDR.log.info("GET-MGMT-NODES returned an empty list. Using ")
+                //console.dump(response)
+              }
+              for (var i=0; i<response.length; ++i) {
+                if (!angular.isDefined(self.topology._nodeInfo[response[i]])) {
+                  self.topology._nodeInfo[angular.copy(response[i])] = {};
+                }
+              }
+              // also refresh any entities that were requested
+              self.topology.q = QDR.queue(self.queueDepth())
+              for (var i=0; i<self.topology._autoUpdatedEntities.length; ++i) {
+                var entity = self.topology._autoUpdatedEntities[i]
+                //QDR.log.debug("queuing requests for all nodes for " + entity)
+                for (node in self.topology._nodeInfo) {
+                  self.topology.q.defer(self.ensureNodeInfo, node, entity, [], self.topology.q)
+                }
+              }
+              self.topology.q.await(function (error) {
+                self.topology._gettingTopo = false;
+                self.topology.q = null
+                self.topology.ondone(error)
+              })
+            };
+          });
+        },
+
+        cleanUp: function(obj) {
+          if (obj)
+            delete obj;
+        },
+        timedOut: function(q) {
+          // a node dropped out. this happens when the get-mgmt-nodex
+          // results contains more nodes than actually respond within
+          // the timeout
+          QDR.log.debug("timed out waiting for management responses");
+          // note: can't use 'this' in a timeout handler
+          self.topology.miniDump("state at timeout");
+          q.abort()
+          //self.topology.onerror(Error("management responses are not consistent"));
+        },
+
+        addNodeInfo: function(id, entity, values, q) {
+          clearTimeout(self.topology._waitTimer)
+          // save the results in the nodeInfo object
+          if (id) {
+            if (!(id in self.topology._nodeInfo)) {
+              self.topology._nodeInfo[id] = {};
+            }
+            // copy the values to allow garbage collector to reclaim their memory
+            self.topology._nodeInfo[id][entity] = angular.copy(values)
+          }
+          self.topology.cleanUp(values);
+        },
+        ondone: function(waserror) {
+          clearTimeout(self.topology._getTimer);
+          clearTimeout(self.topology._waitTimer);
+          self.topology._waitTimer = null;
+          if (self.updating)
+            self.topology._getTimer = setTimeout(self.topology.get, self.updateInterval);
+          //if (!waserror)
+            self.notifyTopologyDone();
+        },
+        dump: function(prefix) {
+          if (prefix)
+            QDR.log.info(prefix);
+          QDR.log.info("---");
+          for (var key in self.topology._nodeInfo) {
+            QDR.log.info(key);
+            console.dump(self.topology._nodeInfo[key]);
+            QDR.log.info("---");
+          }
+        },
+        miniDump: function(prefix) {
+          if (prefix)
+            QDR.log.info(prefix);
+          QDR.log.info("---");
+          console.dump(Object.keys(self.topology._nodeInfo));
+          QDR.log.info("---");
+        },
+        onerror: function(err) {
+          self.topology._gettingTopo = false;
+          QDR.log.debug("Err:" + err);
+          //self.executeDisconnectActions();
+        }
+
+      },
+      getRemoteNodeInfo: function(callback) {
+        //QDR.log.debug("getRemoteNodeInfo called");
+
+        setTimeout(function () {
+          var ret;
+          // first get the list of remote node names
+          self.correlator.request(
+            ret = self.sendMgmtQuery('GET-MGMT-NODES')
+          ).then(ret.id, function(response, context) {
+            callback(response, context);
+            self.topology.cleanUp(response);
+          }, ret.error);
+        }, 1)
+      },
+
+      // sends a request and updates the topology.nodeInfo object with the response
+      // should only be called from a q.defer() statement
+      ensureNodeInfo: function (nodeId, entity, attrs, q, callback) {
+        //QDR.log.debug("queuing request for " + nodeId + " " + entity)
+        self.getNodeInfo(nodeId, entity, attrs, q, function (nodeName, dotentity, response) {
+          //QDR.log.debug("got response for " + nodeId + " " + entity)
+          self.topology.addNodeInfo(nodeName, dotentity, response, q)
+          callback(null)
+        })
+        return {
+          abort: function() {
+            delete self.topology._nodeInfo[nodeId]
+            //self.topology._nodeInfo[nodeId][entity] = {attributeNames: [], results: [[]]};
+          }
+        }
+      },
+
+      // sends request and returns the response
+      // should only be called from a q.defer() statement
+      fetchNodeInfo: function (nodeId, entity, attrs, q, heartbeat, callback) {
+        self.getNodeInfo(nodeId, entity, attrs, q, function (nodeName, dotentity, response) {
+          heartbeat(nodeName, dotentity, response)
+          callback(null)
+        })
+      },
+
+      getMultipleNodeInfo: function(nodeNames, entity, attrs, callback, selectedNodeId, aggregate) {
+        if (!angular.isDefined(aggregate))
+          aggregate = true;
+        var responses = {};
+        var gotNodesResult = function(nodeName, dotentity, response) {
+          responses[nodeName] = response;
+        }
+
+        var q = QDR.queue(self.queueDepth())
+        nodeNames.forEach(function(id) {
+            q.defer(self.fetchNodeInfo, id, '.' + entity, attrs, q, gotNodesResult)
+        })
+        q.await(function (error) {
+          if (aggregate)
+            self.aggregateNodeInfo(nodeNames, entity, selectedNodeId, responses, callback);
+          else {
+            callback(nodeNames, entity, responses)
+          }
+        })
+      },
+
+      aggregateNodeInfo: function(nodeNames, entity, selectedNodeId, responses, callback) {
+        //QDR.log.debug("got all results for  " + entity);
+        // aggregate the responses
+        var newResponse = {};
+        var thisNode = responses[selectedNodeId];
+        newResponse['attributeNames'] = thisNode.attributeNames;
+        newResponse['results'] = thisNode.results;
+        newResponse['aggregates'] = [];
+        for (var i = 0; i < thisNode.results.length; ++i) {
+          var result = thisNode.results[i];
+          var vals = [];
+          result.forEach(function(val) {
+            vals.push({
+              sum: val,
+              detail: []
+            })
+          })
+          newResponse.aggregates.push(vals);
+        }
+        var nameIndex = thisNode.attributeNames.indexOf("name");
+        var ent = self.schema.entityTypes[entity];
+        var ids = Object.keys(responses);
+        ids.sort();
+        ids.forEach(function(id) {
+          var response = responses[id];
+          var results = response.results;
+          results.forEach(function(result) {
+            // find the matching result in the aggregates
+            var found = newResponse.aggregates.some(function(aggregate, j) {
+              if (aggregate[nameIndex].sum === result[nameIndex]) {
+                // result and aggregate are now the same record, add the graphable values
+                newResponse.attributeNames.forEach(function(key, i) {
+                  if (ent.attributes[key] && ent.attributes[key].graph) {
+                    if (id != selectedNodeId)
+                      aggregate[i].sum += result[i];
+                  }
+                  aggregate[i].detail.push({
+                    node: self.nameFromId(id) + ':',
+                    val: result[i]
+                  })
+                })
+                return true; // stop looping
+              }
+              return false; // continute looking for the aggregate record
+            })
+            if (!found) {
+              // this attribute was not found in the aggregates yet
+              // because it was not in the selectedNodeId's results
+              var vals = [];
+              result.forEach(function(val) {
+                vals.push({
+                  sum: val,
+                  detail: [{
+                    node: self.nameFromId(id),
+                    val: val
+                  }]
+                })
+              })
+              newResponse.aggregates.push(vals)
+            }
+          })
+        })
+        callback(nodeNames, entity, newResponse);
+      },
+
+
+      getSchema: function(callback) {
+        //QDR.log.info("getting schema");
+        var ret;
+        self.correlator.request(
+          ret = self.sendMgmtQuery('GET-SCHEMA')
+        ).then(ret.id, function(response) {
+          //QDR.log.info("Got schema response");
+          // remove deprecated
+          for (var entityName in response.entityTypes) {
+            var entity = response.entityTypes[entityName]
+            if (entity.deprecated) {
+              // deprecated entity
+              delete response.entityTypes[entityName]
+            } else {
+              for (var attributeName in entity.attributes) {
+                var attribute = entity.attributes[attributeName]
+                if (attribute.deprecated) {
+                  // deprecated attribute
+                  delete response.entityTypes[entityName].attributes[attributeName]
+                }
+              }
+            }
+          }
+          self.schema = response;
+          callback()
+        }, ret.error);
+      },
+
+      getNodeInfo: function(nodeName, entity, attrs, q, callback) {
+        //QDR.log.debug("getNodeInfo called with nodeName: " + nodeName + " and entity " + entity);
+        var timedOut = function (q) {
+          q.abort()
+        }
+        var atimer = setTimeout(timedOut, self.timeout * 1000, q);
+        var ret;
+        self.correlator.request(
+          ret = self.sendQuery(nodeName, entity, attrs)
+        ).then(ret.id, function(response) {
+          clearTimeout(atimer)
+          callback(nodeName, entity, response);
+        }, ret.error);
+      },
+
+      sendMethod: function(nodeId, entity, attrs, operation, props, callback) {
+        setTimeout(function () {
+          var ret;
+          self.correlator.request(
+            ret = self._sendMethod(nodeId, entity, attrs, operation, props)
+          ).then(ret.id, function(response, context) {
+            callback(nodeId, entity, response, context);
+          }, ret.error);
+        }, 1)
+      },
+
+      _fullAddr: function(toAddr) {
+        var toAddrParts = toAddr.split('/');
+        if (toAddrParts.shift() != "amqp:") {
+          self.topology.error(Error("unexpected format for router address: " + toAddr));
+          return;
+        }
+        var fullAddr = toAddrParts.join('/');
+        return fullAddr;
+      },
+
+      _sendMethod: function(toAddr, entity, attrs, operation, props) {
+        var ret = {
+          id: self.correlator.corr()
+        };
+        var fullAddr = self._fullAddr(toAddr);
+        if (!self.sender || !self.sendable) {
+          ret.error = "no sender"
+          return ret;
+        }
+        try {
+          var application_properties = {
+            operation: operation
+          }
+          if (entity) {
+            var ent = self.schema.entityTypes[entity];
+            var fullyQualifiedType = ent ? ent.fullyQualifiedType : entity;
+            application_properties.type = fullyQualifiedType || entity;
+          }
+          if (attrs.name)
+            application_properties.name = attrs.name;
+          if (props) {
+            jQuery.extend(application_properties, props);
+          }
+          var msg = {
+            body: attrs,
+            to: fullAddr,
+            reply_to: self.receiver.remote.attach.source.address,
+            correlation_id: ret.id,
+            application_properties: application_properties
+          }
+          self.sender.send(msg);
+          //console.dump("------- method called -------")
+          //console.dump(msg)
+        } catch (e) {
+          error = "error sending: " + e;
+          QDR.log.error(error)
+          ret.error = error;
+        }
+        return ret;
+      },
+
+      sendQuery: function(toAddr, entity, attrs, operation) {
+        operation = operation || "QUERY"
+        var fullAddr = self._fullAddr(toAddr);
+
+        var body;
+        if (attrs) {
+          body = {
+            "attributeNames": attrs,
+          }
+        } else {
+          body = {
+            "attributeNames": [],
+          }
+        }
+        if (entity[0] === '.')
+          entity = entity.substr(1, entity.length - 1)
+        var prefix = "org.apache.qpid.dispatch."
+        var configs = ["address", "autoLink", "linkRoute"]
+        if (configs.indexOf(entity) > -1)
+          prefix += "router.config."
+        return self._send(body, fullAddr, operation, prefix + entity);
+      },
+
+      sendMgmtQuery: function(operation) {
+        return self._send([], "/$management", operation);
+      },
+
+      _send: function(body, to, operation, entityType) {
+        var ret = {
+          id: self.correlator.corr()
+        };
+        if (!self.sender || !self.sendable) {
+          ret.error = "no sender"
+          return ret;
+        }
+        try {
+          var application_properties = {
+            operation: operation,
+            type: "org.amqp.management",
+            name: "self"
+          };
+          if (entityType)
+            application_properties.entityType = entityType;
+
+          self.sender.send({
+            body: body,
+            to: to,
+            reply_to: self.receiver.remote.attach.source.address,
+            correlation_id: ret.id,
+            application_properties: application_properties
+          })
+        } catch (e) {
+          error = "error sending: " + e;
+          QDR.log.error(error)
+          ret.error = error;
+        }
+        return ret;
+      },
+
+      disconnect: function() {
+        self.connection.close();
+        self.connected = false
+        self.errorText = "Disconnected."
+      },
+
+      connectionTimer: null,
+
+      testConnect: function (options, timeout, callback) {
+        var connection;
+        var allowDelete = true;
+        var reconnect = angular.isDefined(options.reconnect) ? options.reconnect : false
+        var baseAddress = options.address + ':' + options.port;
+        var protocol = "ws"
+        if ($location.protocol() === "https")
+          protocol = "wss"
+        QDR.log.info("testConnect called with reconnect " + reconnect + " using " + protocol + " protocol")
+        try {
+          var ws = self.rhea.websocket_connect(WebSocket);
+          connection = self.rhea.connect({
+            connection_details: ws(protocol + "://" + baseAddress, ["binary"]),
+            reconnect: reconnect,
+              properties: {
+                console_identifier: 'Dispatch console'
+              }
+            }
+          );
+        } catch (e) {
+          QDR.log.debug("exception caught on test connect " + e)
+          self.errorText = "Connection failed "
+          callback({error: e})
+          return
+        }
+        // called when initial connecting fails, and when connection is dropped after connecting
+        connection.on('disconnected', function(context) {
+          if (allowDelete) {
+            delete connection
+            connection.options.reconnect = false
+            //QDR.log.info("connection.on(disconnected) called")
+            callback({error: "failed to connect"})
+          }
+        })
+        connection.on("connection_open", function (context) {
+          allowDelete = false;
+          callback({connection: connection, context: context})
+        })
+      },
+
+      connect: function(options) {
+        var connection;
+        self.topologyInitialized = false;
+        if (!self.connected) {
+          var okay = {
+            connection: false,
+            sender: false,
+            receiver: false
+          }
+          var sender, receiver
+          self.connectionError = undefined;
+
+          var stop = function(context) {
+            //self.stopUpdating();
+            okay.sender = false;
+            okay.receiver = false;
+            okay.connected = false;
+            self.connected = false;
+            self.sender = null;
+            self.receiver = null;
+            self.sendable = false;
+            self.gotTopology = false;
+          }
+          var maybeStart = function() {
+            if (okay.connection && okay.sender && okay.receiver && self.sendable && !self.connected) {
+              //QDR.log.info("okay to start")
+              self.connected = true;
+              self.connection = connection;
+              self.sender = sender;
+              self.receiver = receiver;
+              self.gotTopology = false;
+              self.onSubscription();
+            }
+          }
+          var onDisconnect = function() {
+            //QDR.log.warn("Disconnected");
+            self.connectionError = true;
+            stop();
+            self.executeDisconnectActions();
+          }
+
+          // called after connection.open event is fired or connection error has happened
+          var connectionCallback = function (options) {
+            //QDR.log.info('connectionCallback called')
+            if (!options.error) {
+              //QDR.log.info('there was no error')
+              connection = options.connection
+              self.version = options.context.connection.properties.version
+              QDR.log.debug("connection_opened")
+              okay.connection = true;
+              okay.receiver = false;
+              okay.sender = false;
+
+              connection.on('disconnected', function(context) {
+                //QDR.log.info("connection.on(disconnected) called")
+                self.errorText = "Unable to connect"
+                onDisconnect();
+              })
+              connection.on('connection_close', function(context) {
+                //QDR.log.info("connection closed")
+                self.errorText = "Disconnected"
+                onDisconnect();
+              })
+
+              sender = connection.open_sender();
+              sender.on('sender_open', function(context) {
+                //QDR.log.info("sender_opened")
+                okay.sender = true
+                maybeStart()
+              })
+              sender.on('sendable', function(context) {
+                //QDR.log.debug("sendable")
+                self.sendable = true;
+                maybeStart();
+              })
+
+              receiver = connection.open_receiver({
+                source: {
+                  dynamic: true
+                }
+              });
+              receiver.on('receiver_open', function(context) {
+                //QDR.log.info("receiver_opened")
+                if (receiver.remote && receiver.remote.attach && receiver.remote.attach.source) {
+                  okay.receiver = true;
+                  maybeStart()
+                }
+              })
+              receiver.on("message", function(context) {
+                self.correlator.resolve(context);
+              });
+            } else {
+              //QDR.log.info("there was an error " + options.error)
+              self.errorText = "Unable to connect"
+              onDisconnect();
+            }
+          }
+
+          QDR.log.debug("****** calling rhea.connect ********")
+          if (!options.connection) {
+            QDR.log.debug("rhea.connect was not passed an existing connection")
+            options.reconnect = true
+            self.testConnect(options, 5000, connectionCallback)
+          } else {
+            QDR.log.debug("rhea.connect WAS passed an existing connection")
+            connectionCallback(options)
+          }
+        }
+      }
+    }
+    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);
+  };
+})();
+
+function ngGridFlexibleHeightPlugin (opts) {
+    var self = this;
+    self.grid = null;
+    self.scope = null;
+    self.init = function (scope, grid, services) {
+        self.domUtilityService = services.DomUtilityService;
+        self.grid = grid;
+        self.scope = scope;
+        var recalcHeightForData = function () { setTimeout(innerRecalcForData, 1); };
+        var innerRecalcForData = function () {
+            var gridId = self.grid.gridId;
+            var footerPanelSel = '.' + gridId + ' .ngFooterPanel';
+            if (!self.grid.$topPanel || !self.grid.$canvas)
+              return;
+            var extraHeight = self.grid.$topPanel.height() + $(footerPanelSel).height();
+            var naturalHeight = self.grid.$canvas.height() + 1;
+            if (opts != null) {
+                if (opts.minHeight != null && (naturalHeight + extraHeight) < opts.minHeight) {
+                    naturalHeight = opts.minHeight - extraHeight - 2;
+                }
+                if (opts.maxHeight != null && (naturalHeight + extraHeight) > opts.maxHeight) {
+                    naturalHeight = opts.maxHeight;
+                }
+            }
+
+            var newViewportHeight = naturalHeight + 3;
+            if (!self.scope.baseViewportHeight || self.scope.baseViewportHeight !== newViewportHeight) {
+                self.grid.$viewport.css('height', newViewportHeight + 'px');
+                self.grid.$root.css('height', (newViewportHeight + extraHeight) + 'px');
+                self.scope.baseViewportHeight = newViewportHeight;
+                self.domUtilityService.RebuildGrid(self.scope, self.grid);
+            }
+        };
+        self.scope.catHashKeys = function () {
+            var hash = '',
+                idx;
+            for (idx in self.scope.renderedRows) {
+                hash += self.scope.renderedRows[idx].$$hashKey;
+            }
+            return hash;
+        };
+        self.scope.$watch('catHashKeys()', innerRecalcForData);
+        self.scope.$watch(self.grid.config.data, recalcHeightForData);
+    };
+}
+
+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) {
+      var subjectString = this.toString();
+      if (typeof position !== 'number' || !isFinite(position) || Math.floor(position) !== position || position > subjectString.length) {
+        position = subjectString.length;
+      }
+      position -= searchString.length;
+      var 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');
+      }
+
+      var o = Object(this);
+
+      // 2. Let len be ? ToLength(? Get(O, "length")).
+      var 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.
+      var thisArg = arguments[1];
+
+      // 5. Let k be 0.
+      var 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.
+        var kValue = o[k];
+        if (predicate.call(thisArg, kValue, k, o)) {
+          return k;
+        }
+        // e. Increase k by 1.
+        k++;
+      }
+
+      // 7. Return -1.
+      return -1;
+    }
+  });
+}

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/55d7bd34/console/hawtio/src/main/webapp/plugin/js/qdrSettings.js
----------------------------------------------------------------------
diff --git a/console/hawtio/src/main/webapp/plugin/js/qdrSettings.js b/console/hawtio/src/main/webapp/plugin/js/qdrSettings.js
deleted file mode 120000
index 6861eb2..0000000
--- a/console/hawtio/src/main/webapp/plugin/js/qdrSettings.js
+++ /dev/null
@@ -1 +0,0 @@
-../../../../../../stand-alone/plugin/js/qdrSettings.js
\ No newline at end of file
diff --git a/console/hawtio/src/main/webapp/plugin/js/qdrSettings.js b/console/hawtio/src/main/webapp/plugin/js/qdrSettings.js
new file mode 100644
index 0000000..3f0c109
--- /dev/null
+++ b/console/hawtio/src/main/webapp/plugin/js/qdrSettings.js
@@ -0,0 +1,188 @@
+/*
+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 SettingsController
+   * @param $scope
+   * @param QDRServer
+   *
+   * Controller that handles the QDR settings page
+   */
+
+  QDR.module.controller("QDR.SettingsController", ['$scope', 'QDRService', '$timeout', '$location', function($scope, QDRService, $timeout, $location) {
+
+    $scope.connecting = false;
+    $scope.connectionError = false;
+    $scope.connectionErrorText = undefined;
+    $scope.forms = {};
+
+    $scope.formEntity = angular.fromJson(localStorage[QDR.SETTINGS_KEY]) || {
+      address: '',
+      port: '',
+      username: '',
+      password: '',
+      autostart: false
+    };
+
+    $scope.$watch('formEntity', function(newValue, oldValue) {
+      if (newValue !== oldValue) {
+        localStorage[QDR.SETTINGS_KEY] = angular.toJson(newValue);
+      }
+    }, true);
+
+    $scope.buttonText = function() {
+      if (QDRService.isConnected()) {
+        return "Disconnect";
+      } else {
+        return "Connect";
+      }
+    };
+
+    $scope.connect = function() {
+      if (QDRService.connected) {
+        $timeout( function () {
+          QDRService.disconnect();
+        })
+        return;
+      }
+
+      if ($scope.settings.$valid) {
+        $scope.connectionError = false;
+        $scope.connecting = true;
+        // timeout so connecting animation can display
+        $timeout(function () {
+          doConnect()
+        })
+      }
+    }
+
+    var doConnect = function(opts) {
+      if (!$scope.formEntity.address)
+        $scope.formEntity.address = "localhost"
+      if (!$scope.formEntity.port)
+        $scope.formEntity.port = 5673
+
+      var failed = function() {
+        $timeout(function() {
+          QDR.log.debug("disconnect action called");
+          $scope.connecting = false;
+          $scope.connectionErrorText = QDRService.errorText;
+          $scope.connectionError = true;
+        })
+      }
+      QDRService.addDisconnectAction(failed);
+      QDRService.addConnectAction(function() {
+        QDRService.delDisconnectAction(failed)
+        QDRService.getSchema(function () {
+          QDR.log.info("got schema after connection")
+          QDRService.addUpdatedAction("initialized", function () {
+            QDRService.delUpdatedAction("initialized")
+            QDR.log.info("got initial topology")
+            $timeout(function() {
+              $scope.connecting = false;
+              if ($location.path().startsWith(QDR.pluginRoot)) {
+                  var searchObject = $location.search();
+                  var goto = "overview";
+                  if (searchObject.org && searchObject.org !== "connect") {
+                    goto = searchObject.org;
+                  }
+                  $location.search('org', null)
+                  $location.path(QDR.pluginRoot + "/" + goto);
+              }
+            })
+          })
+          QDR.log.info("requesting a topology")
+          QDRService.setUpdateEntities([])
+          QDRService.topology.get()
+        })
+      });
+      var options = {address: $scope.formEntity.address, port: $scope.formEntity.port}
+      // if we have already successfully connected (the test connections succeeded)
+      if (opts && opts.connection) {
+        options.connection = opts.connection
+        options.context = opts.context
+      }
+      QDRService.connect(options);
+    }
+  }]);
+
+
+  QDR.module.directive('posint', function() {
+    return {
+      require: 'ngModel',
+
+      link: function(scope, elem, attr, ctrl) {
+        // input type number allows + and - but we don't want them so filter them out
+        elem.bind('keypress', function(event) {
+          var nkey = !event.charCode ? event.which : event.charCode;
+          var skey = String.fromCharCode(nkey);
+          var nono = "-+.,"
+          if (nono.indexOf(skey) >= 0) {
+            event.preventDefault();
+            return false;
+          }
+          // firefox doesn't filter out non-numeric input. it just sets the ctrl to invalid
+          if (/[\!\@\#\$\%^&*\(\)]/.test(skey) && event.shiftKey || // prevent shift numbers
+            !( // prevent all but the following
+              nkey <= 0 || // arrows
+              nkey == 8 || // delete|backspace
+              nkey == 13 || // enter
+              (nkey >= 37 && nkey <= 40) || // arrows
+              event.ctrlKey || event.altKey || // ctrl-v, etc.
+              /[0-9]/.test(skey)) // numbers
+          ) {
+            event.preventDefault();
+            return false;
+          }
+        })
+          // check the current value of input
+        var _isPortInvalid = function(value) {
+          var port = value + ''
+          var isErrRange = false;
+          // empty string is valid
+          if (port.length !== 0) {
+            var n = ~~Number(port);
+            if (n < 1 || n > 65535) {
+              isErrRange = true;
+            }
+          }
+          ctrl.$setValidity('range', !isErrRange)
+          return isErrRange;
+        }
+
+        //For DOM -> model validation
+        ctrl.$parsers.unshift(function(value) {
+          return _isPortInvalid(value) ? undefined : value;
+        });
+
+        //For model -> DOM validation
+        ctrl.$formatters.unshift(function(value) {
+          _isPortInvalid(value);
+          return value;
+        });
+      }
+    };
+  });
+
+  return QDR;
+}(QDR || {}));


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