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