Repository: qpid-dispatch Updated Branches: refs/heads/master 3adde8e5d -> 55b7ae55e
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/55b7ae55/console/stand-alone/plugin/js/topology/nodes.js ---------------------------------------------------------------------- diff --git a/console/stand-alone/plugin/js/topology/nodes.js b/console/stand-alone/plugin/js/topology/nodes.js index 5d003c6..50c9a37 100644 --- a/console/stand-alone/plugin/js/topology/nodes.js +++ b/console/stand-alone/plugin/js/topology/nodes.js @@ -17,70 +17,74 @@ specific language governing permissions and limitations under the License. */ -import { utils } from '../amqp/utilities.js'; +import { utils } from "../amqp/utilities.js"; /* global d3 Promise */ export class Node { - constructor(id, name, nodeType, properties, routerId, x, y, nodeIndex, resultIndex, fixed, connectionContainer) { - this.key = id; // the router uri for this node (or group of clients) like: amqp:/_topo/0/<router id>/$management - this.name = name; // the router id portion of the key - this.nodeType = nodeType; // router.role + constructor( + id, + name, + nodeType, + properties, + routerId, + x, + y, + nodeIndex, + resultIndex, + fixed, + connectionContainer + ) { + this.key = id; // the router uri for this node (or group of clients) like: amqp:/_topo/0/<router id>/$management + this.name = name; // the router id portion of the key + this.nodeType = nodeType; // router.role this.properties = properties; - this.routerId = routerId; // the router uri of the router we are connected to (for groups) + this.routerId = routerId; // the router uri of the router we are connected to (for groups) this.x = x; this.y = y; this.id = nodeIndex; this.resultIndex = resultIndex; this.fixed = !!+fixed; - this.cls = ''; + this.cls = ""; this.container = connectionContainer; this.isConsole = utils.isConsole(this); this.isArtemis = utils.isArtemis(this); } - title (hide) { - let x = ''; + title(hide) { + let x = ""; if (this.normals && this.normals.length > 1 && !hide) - x = ' x ' + this.normals.length; - if (this.isConsole) - return 'Dispatch console' + x; - else if (this.isArtemis) - return 'Broker - Artemis' + x; - else if (this.properties.product == 'qpid-cpp') - return 'Broker - qpid-cpp' + x; - else if (this.nodeType === 'edge') - return 'Edge Router'; - else if (this.cdir === 'in') - return 'Sender' + x; - else if (this.cdir === 'out') - return 'Receiver' + x; - else if (this.cdir === 'both') - return 'Sender/Receiver' + x; - else if (this.nodeType === 'normal') - return 'client' + x; - else if (this.nodeType === 'on-demand') - return 'broker'; + x = " x " + this.normals.length; + if (this.isConsole) return "Dispatch console" + x; + else if (this.isArtemis) return "Broker - Artemis" + x; + else if (this.properties.product == "qpid-cpp") + return "Broker - qpid-cpp" + x; + else if (this.nodeType === "edge") return "Edge Router"; + else if (this.cdir === "in") return "Sender" + x; + else if (this.cdir === "out") return "Receiver" + x; + else if (this.cdir === "both") return "Sender/Receiver" + x; + else if (this.nodeType === "normal") return "client" + x; + else if (this.nodeType === "on-demand") return "broker"; else if (this.properties.product) { return this.properties.product; - } - else { - return ''; + } else { + return ""; } } - toolTip (topology) { - return new Promise( (function (resolve) { - if (this.nodeType === 'normal' || this.nodeType === 'edge') { - resolve(this.clientTooltip()); - } else - this.routerTooltip(topology) - .then( function (toolTip) { + toolTip(topology) { + return new Promise( + function(resolve) { + if (this.nodeType === "normal" || this.nodeType === "edge") { + resolve(this.clientTooltip()); + } else + this.routerTooltip(topology).then(function(toolTip) { resolve(toolTip); }); - }.bind(this))); + }.bind(this) + ); } - clientTooltip () { + clientTooltip() { let type = this.title(true); - let title = ''; + let title = ""; title += `<table class="popupTable"><tr><td>Type</td><td>${type}</td></tr>`; if (!this.normals || this.normals.length < 2) title += `<tr><td>Host</td><td>${this.host}</td></tr>`; @@ -88,77 +92,103 @@ export class Node { title += `<tr><td>Count</td><td>${this.normals.length}</td></tr>`; } if (!this.isConsole && !this.isArtemis) - title += '<tr><td colspan=2 class="more-info">Click circle for more info</td></tr></table>'; + title += + '<tr><td colspan=2 class="more-info">Click circle for more info</td></tr></table>'; return title; } - routerTooltip (topology) { - return new Promise( (function (resolve) { - topology.ensureEntities(this.key, [ - {entity: 'listener', attrs: ['role', 'port', 'http']}, - {entity: 'router', attrs: ['name', 'version', 'hostName']} - ], function (foo, nodes) { - // update all the router title text - let node = nodes[this.key]; - const err = `<table class="popupTable"><tr><td>Error</td><td>Unable to get router info for ${this.key}</td></tr></table>`; - if (!node) { - resolve(err); - return; - } - let listeners = node['listener']; - let router = node['router']; - if (!listeners || !router) { - resolve(err); - return; - } - let r = utils.flatten(router.attributeNames, router.results[0]); - let title = '<table class="popupTable">'; - title += ('<tr><td>Router</td><td>' + r.name + '</td></tr>'); - if (r.hostName) - title += ('<tr><td>Host Name</td><td>' + r.hostHame + '</td></tr>'); - title += ('<tr><td>Version</td><td>' + r.version + '</td></tr>'); - let ports = []; - for (let l=0; l<listeners.results.length; l++) { - let listener = utils.flatten(listeners.attributeNames, listeners.results[l]); - if (listener.role === 'normal') { - ports.push(listener.port+''); - } - } - if (ports.length > 0) { - title += ('<tr><td>Ports</td><td>' + ports.join(', ') + '</td></tr>'); - } - title += '</table>'; - resolve(title); - return title; - }.bind(this)); - }.bind(this))); + routerTooltip(topology) { + return new Promise( + function(resolve) { + topology.ensureEntities( + this.key, + [ + { entity: "listener", attrs: ["role", "port", "http"] }, + { entity: "router", attrs: ["name", "version", "hostName"] } + ], + function(foo, nodes) { + // update all the router title text + let node = nodes[this.key]; + const err = `<table class="popupTable"><tr><td>Error</td><td>Unable to get router info for ${ + this.key + }</td></tr></table>`; + if (!node) { + resolve(err); + return; + } + let listeners = node["listener"]; + let router = node["router"]; + if (!listeners || !router) { + resolve(err); + return; + } + let r = utils.flatten(router.attributeNames, router.results[0]); + let title = '<table class="popupTable">'; + title += "<tr><td>Router</td><td>" + r.name + "</td></tr>"; + if (r.hostName) + title += "<tr><td>Host Name</td><td>" + r.hostHame + "</td></tr>"; + title += "<tr><td>Version</td><td>" + r.version + "</td></tr>"; + let ports = []; + for (let l = 0; l < listeners.results.length; l++) { + let listener = utils.flatten( + listeners.attributeNames, + listeners.results[l] + ); + if (listener.role === "normal") { + ports.push(listener.port + ""); + } + } + if (ports.length > 0) { + title += + "<tr><td>Ports</td><td>" + ports.join(", ") + "</td></tr>"; + } + title += "</table>"; + resolve(title); + return title; + }.bind(this) + ); + }.bind(this) + ); } radius() { return nodeProperties[this.nodeType].radius; } uid() { - if (!this.uuid) - this.uuid = this.container; + if (!this.uuid) this.uuid = this.container; return this.normals ? `${this.uuid}-${this.normals.length}` : this.uuid; } setFixed(fixed) { - if (!fixed) - this.lat = this.lon = null; + if (!fixed) this.lat = this.lon = null; this.fixed = fixed; } } const nodeProperties = { // router types - 'inter-router': {radius: 28, refX: {end: 32, start: -19}, linkDistance: [150, 70], charge: [-1800, -900]}, - 'edge': {radius: 20, refX: {end: 24, start: -12}, linkDistance: [110, 55], charge: [-1350, -900]}, + "inter-router": { + radius: 28, + refX: { end: 32, start: -19 }, + linkDistance: [150, 70], + charge: [-1800, -900] + }, + edge: { + radius: 20, + refX: { end: 24, start: -12 }, + linkDistance: [110, 55], + charge: [-1350, -900] + }, // generated nodes from connections. key is from connection.role - 'normal': {radius: 15, refX: {end: 20, start: -7}, linkDistance: [75, 40], charge: [-900, -900]}, + normal: { + radius: 15, + refX: { end: 20, start: -7 }, + linkDistance: [75, 40], + charge: [-900, -900] + } }; // aliases -nodeProperties._topo = nodeProperties['inter-router']; -nodeProperties._edge = nodeProperties['edge']; -nodeProperties['on-demand'] = nodeProperties['normal']; -nodeProperties['route-container'] = nodeProperties['normal']; +nodeProperties._topo = nodeProperties["inter-router"]; +nodeProperties._edge = nodeProperties["edge"]; +nodeProperties["on-demand"] = nodeProperties["normal"]; +nodeProperties["route-container"] = nodeProperties["normal"]; export class Nodes { constructor(logger) { @@ -166,8 +196,7 @@ export class Nodes { this.logger = logger; } static radius(type) { - if (nodeProperties[type].radius) - return nodeProperties[type].radius; + if (nodeProperties[type].radius) return nodeProperties[type].radius; return 15; } static maxRadius() { @@ -179,8 +208,7 @@ export class Nodes { } static refX(end, r) { for (let key in nodeProperties) { - if (nodeProperties[key].radius == r) - return nodeProperties[key].refX[end]; + if (nodeProperties[key].radius == r) return nodeProperties[key].refX[end]; } return 0; } @@ -193,97 +221,98 @@ export class Nodes { return Object.keys(values); } // vary the following force graph attributes based on nodeCount - static forceScale (nodeCount, minmax) { + static forceScale(nodeCount, minmax) { let count = Math.max(Math.min(nodeCount, 80), 6); - let x = d3.scale.linear() - .domain([6,80]) + let x = d3.scale + .linear() + .domain([6, 80]) .range(minmax); return x(count); } - linkDistance (d, nodeCount) { + linkDistance(d, nodeCount) { let range = nodeProperties[d.target.nodeType].linkDistance; return Nodes.forceScale(nodeCount, range); } - charge (d, nodeCount) { + charge(d, nodeCount) { let charge = nodeProperties[d.nodeType].charge; return Nodes.forceScale(nodeCount, charge); } - gravity (d, nodeCount) { + gravity(d, nodeCount) { return Nodes.forceScale(nodeCount, [0.0001, 0.1]); } - getLength () { + getLength() { return this.nodes.length; } - get (index) { + get(index) { if (index < this.getLength()) { return this.nodes[index]; } - this.logger.error(`Attempted to get node[${index}] but there were only ${this.getLength()} nodes`); + this.logger.error( + `Attempted to get node[${index}] but there were only ${this.getLength()} nodes` + ); return undefined; } - setNodesFixed (name, b) { - this.nodes.some(function (n) { + setNodesFixed(name, b) { + this.nodes.some(function(n) { if (n.name === name) { n.fixed(b); return true; } }); } - nodeFor (name) { + nodeFor(name) { for (let i = 0; i < this.nodes.length; ++i) { - if (this.nodes[i].name == name) - return this.nodes[i]; + if (this.nodes[i].name == name) return this.nodes[i]; } return null; } - nodeExists (connectionContainer) { - return this.nodes.findIndex( function (node) { + nodeExists(connectionContainer) { + return this.nodes.findIndex(function(node) { return node.container === connectionContainer; }); } - normalExists (connectionContainer) { + normalExists(connectionContainer) { let normalInfo = {}; - for (let i=0; i<this.nodes.length; ++i) { + for (let i = 0; i < this.nodes.length; ++i) { if (this.nodes[i].normals) { - if (this.nodes[i].normals.some(function (normal, j) { - if (normal.container === connectionContainer && i !== j) { - normalInfo = {nodesIndex: i, normalsIndex: j}; - return true; - } - return false; - })) + if ( + this.nodes[i].normals.some(function(normal, j) { + if (normal.container === connectionContainer && i !== j) { + normalInfo = { nodesIndex: i, normalsIndex: j }; + return true; + } + return false; + }) + ) break; } } return normalInfo; } - savePositions (nodes) { - if (!nodes) - nodes = this.nodes; - if (Object.prototype.toString.call(nodes) !== '[object Array]') { + savePositions(nodes) { + if (!nodes) nodes = this.nodes; + if (Object.prototype.toString.call(nodes) !== "[object Array]") { nodes = [nodes]; } - this.nodes.forEach( function (d) { + this.nodes.forEach(function(d) { localStorage[d.name] = JSON.stringify({ x: Math.round(d.x), y: Math.round(d.y), - fixed: (d.fixed & 1) ? 1 : 0, + fixed: d.fixed & 1 ? 1 : 0 }); }); } // Convert node's x,y coordinates to longitude, lattitude - saveLonLat (backgroundMap, nodes) { - if (!backgroundMap || !backgroundMap.initialized) - return; + saveLonLat(backgroundMap, nodes) { + if (!backgroundMap || !backgroundMap.initialized) return; // didn't pass nodes, use all nodes - if (!nodes) - nodes = this.nodes; + if (!nodes) nodes = this.nodes; // passed a single node, wrap it in an array - if (Object.prototype.toString.call(nodes) !== '[object Array]') { + if (Object.prototype.toString.call(nodes) !== "[object Array]") { nodes = [nodes]; } - for (let i=0; i<nodes.length; i++) { + for (let i = 0; i < nodes.length; i++) { let n = nodes[i]; if (n.fixed) { let lonlat = backgroundMap.getLonLat(n.x, n.y); @@ -297,10 +326,9 @@ export class Nodes { } } // convert all nodes' longitude,lattitude to x,y coordinates - setXY (backgroundMap) { - if (!backgroundMap) - return; - for (let i=0; i<this.nodes.length; i++) { + setXY(backgroundMap) { + if (!backgroundMap) return; + for (let i = 0; i < this.nodes.length; i++) { let n = this.nodes[i]; if (n.lon && n.lat) { let xy = backgroundMap.getXY(n.lon, n.lat); @@ -312,68 +340,128 @@ export class Nodes { } } - find (connectionContainer, properties, name) { + find(connectionContainer, properties, name) { properties = properties || {}; - for (let i=0; i<this.nodes.length; ++i) { - if (this.nodes[i].name === name || this.nodes[i].container === connectionContainer) { - if (properties.product) - this.nodes[i].properties = properties; + for (let i = 0; i < this.nodes.length; ++i) { + if ( + this.nodes[i].name === name || + this.nodes[i].container === connectionContainer + ) { + if (properties.product) this.nodes[i].properties = properties; return this.nodes[i]; } } return undefined; } - getOrCreateNode (id, name, nodeType, nodeIndex, x, y, - connectionContainer, resultIndex, fixed, properties) { + getOrCreateNode( + id, + name, + nodeType, + nodeIndex, + x, + y, + connectionContainer, + resultIndex, + fixed, + properties + ) { properties = properties || {}; let gotNode = this.find(connectionContainer, properties, name); if (gotNode) { return gotNode; } let routerId = utils.nameFromId(id); - return new Node(id, name, nodeType, properties, routerId, x, y, - nodeIndex, resultIndex, fixed, connectionContainer); - } - add (obj) { + return new Node( + id, + name, + nodeType, + properties, + routerId, + x, + y, + nodeIndex, + resultIndex, + fixed, + connectionContainer + ); + } + add(obj) { this.nodes.push(obj); return obj; } - addUsing (id, name, nodeType, nodeIndex, x, y, - connectContainer, resultIndex, fixed, properties) { - let obj = this.getOrCreateNode(id, name, nodeType, nodeIndex, x, y, - connectContainer, resultIndex, fixed, properties); + addUsing( + id, + name, + nodeType, + nodeIndex, + x, + y, + connectContainer, + resultIndex, + fixed, + properties + ) { + let obj = this.getOrCreateNode( + id, + name, + nodeType, + nodeIndex, + x, + y, + connectContainer, + resultIndex, + fixed, + properties + ); this.nodes.push(obj); return obj; } - clearHighlighted () { - for (let i = 0; i<this.nodes.length; ++i) { + clearHighlighted() { + for (let i = 0; i < this.nodes.length; ++i) { this.nodes[i].highlighted = false; } } - initialize (nodeInfo, localStorage, width, height) { + initialize(nodeInfo, localStorage, width, height) { let nodeCount = Object.keys(nodeInfo).length; let yInit = 50; let animate = false; for (let id in nodeInfo) { let name = utils.nameFromId(id); // if we have any new nodes, animate the force graph to position them - let position = localStorage[name] ? JSON.parse(localStorage[name]) : undefined; + let position = localStorage[name] + ? JSON.parse(localStorage[name]) + : undefined; if (!position) { animate = true; position = { - x: Math.round(width / 4 + ((width / 2) / nodeCount) * this.nodes.length), - y: Math.round(height / 2 + Math.sin(this.nodes.length / (Math.PI*2.0)) * height / 4), - fixed: false, + x: Math.round( + width / 4 + (width / 2 / nodeCount) * this.nodes.length + ), + y: Math.round( + height / 2 + + (Math.sin(this.nodes.length / (Math.PI * 2.0)) * height) / 4 + ), + fixed: false }; } if (position.y > height) { position.y = 200 - yInit; yInit *= -1; } - let parts = id.split('/'); - this.addUsing(id, name, parts[1], this.nodes.length, position.x, position.y, name, undefined, position.fixed, {}); + let parts = id.split("/"); + this.addUsing( + id, + name, + parts[1], + this.nodes.length, + position.x, + position.y, + name, + undefined, + position.fixed, + {} + ); } return animate; } } - http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/55b7ae55/console/stand-alone/plugin/js/topology/qdrTopology.js ---------------------------------------------------------------------- diff --git a/console/stand-alone/plugin/js/topology/qdrTopology.js b/console/stand-alone/plugin/js/topology/qdrTopology.js index e5cb783..b20e69e 100644 --- a/console/stand-alone/plugin/js/topology/qdrTopology.js +++ b/console/stand-alone/plugin/js/topology/qdrTopology.js @@ -146,20 +146,26 @@ export class TopologyController { }); } + // called from the html page's popup menu $scope.setFixed = function(b) { if ($scope.contextNode) { $scope.contextNode.setFixed(b); nodes.savePositions(); nodes.saveLonLat(backgroundMap, $scope.contextNode); } - if (!b) - animate = true; + // redraw the circles/links restart(); + + if (!b) { + // let the nodes move to a new position + animate = true; + force.start(); + } }; $scope.isFixed = function() { if (!$scope.contextNode) return false; - return ($scope.contextNode.fixed & 1); + return ($scope.contextNode.fixed); }; let mouseX, mouseY; @@ -255,13 +261,15 @@ export class TopologyController { }); // the legend - d3.select('#topo_svg_legend svg').remove(); - lsvg = d3.select('#topo_svg_legend') - .append('svg') - .attr('id', 'svglegend'); - lsvg = lsvg.append('svg:g') - .attr('transform', `translate(${Nodes.maxRadius()}, ${Nodes.maxRadius()})`) - .selectAll('g'); + //d3.select('#topo_svg_legend svg').remove(); + if (d3.select('#svglegend').empty()) { + lsvg = d3.select('#topo_svg_legend') + .append('svg') + .attr('id', 'svglegend'); + lsvg = lsvg.append('svg:g') + .attr('transform', `translate(${Nodes.maxRadius()}, ${Nodes.maxRadius()})`) + .selectAll('g'); + } // mouse event vars $scope.mousedown_node = null; @@ -342,8 +350,8 @@ export class TopologyController { circle = svg.append('svg:g').attr('class', 'nodes').selectAll('g'); // app starts here - if (unknowns.length === 0) - restart(); + //if (unknowns.length === 0) + restart(); if (oldSelectedNode) { d3.selectAll('circle.inter-router').classed('selected', function (d) { if (d.key === oldSelectedNode.key) { @@ -510,11 +518,12 @@ export class TopologyController { }; // update the contents of the popup tooltip each time the data is polled QDRService.management.topology.addUpdatedAction('connectionPopupHTML', updateTooltip); + // request the data and update the tooltip as soon as it arrives QDRService.management.topology.ensureAllEntities( [{ entity: 'router.link', force: true},{entity: 'connection'}], function () { updateTooltip(); }); - // show the tooltip + // just show the tooltip with whatever data we have updateTooltip(); restart(); @@ -572,7 +581,7 @@ export class TopologyController { return (d === selected_node); }) .classed('fixed', function(d) { - return d.fixed & 1; + return d.fixed; }); circle .classed('multiple', function (d) { @@ -648,9 +657,8 @@ export class TopologyController { if (cur_mouse[0] != initial_mouse_down_position[0] || cur_mouse[1] != initial_mouse_down_position[1]) { d.setFixed(true); - nodes.savePositions(d); - nodes.saveLonLat(backgroundMap, d); - console.log('savedLonLat for fixed node'); + nodes.savePositions(); + nodes.saveLonLat(backgroundMap); resetMouseVars(); restart(); return; @@ -670,17 +678,17 @@ export class TopologyController { $scope.mousedown_node = null; if (!$scope.$$phase) $scope.$apply(); // handle clicking on nodes that represent multiple sub-nodes - if (d.normals && !d.isConsole && !d.isArtemis) { + if (d.normals && !d.isArtemis && !d.isQpid) { doDialog(d); } + // apply any data changes to the interface restart(); }) .on('dblclick', function(d) { // circle d3.event.preventDefault(); if (d.fixed) { - d.fixed = false; - nodes.setNodesFixed(d.name, false); + d.setFixed(false); restart(); // redraw the node without a dashed line force.start(); // let the nodes move to a new position } @@ -841,7 +849,7 @@ export class TopologyController { return null; }) .classed('fixed', function(d) { - return d.fixed & 1; + return d.fixed; }) .classed('normal', function(d) { return d.nodeType == 'normal' || QDRService.utilities.isConsole(d); @@ -997,11 +1005,8 @@ export class TopologyController { if (!savedKeys.hasOwnProperty(key)) return 1; // if the number of connections for this node chaanged - if (!nodeInfo[key]['connection']) - return -1; - if (nodeInfo[key]['connection'].results.length != savedKeys[key]) { - return -1; - } + if (nodeInfo[key]['connection'].results.length !== savedKeys[key]) + return nodeInfo[key]['connection'].results.length - savedKeys[key]; } return 0; } @@ -1061,25 +1066,9 @@ export class TopologyController { setupInitialUpdate(); } else if (changed === -1) { // we lost a node (or a client), we can draw the new svg immediately - animate = false; + QDRService.management.topology.purge(); + initForceGraph(); saveChanged(); - let nodeInfo = QDRService.management.topology.nodeInfo(); - forceData.nodes = nodes = new Nodes(QDRLog); - animate = nodes.initialize(nodeInfo, localStorage, width, height); - - let unknowns = []; - forceData.links = links = new Links(QDRLog); - if (links.initialize(nodeInfo, nodes, unknowns, localStorage, height)) { - animate = true; - } - if (unknowns.length > 0) { - resolveUnknowns(nodeInfo, unknowns); - } - else { - force.nodes(nodes.nodes).links(links.links).start(); - restart(); - } - //initForceGraph(); } else { //QDRLog.debug("topology didn't change") } @@ -1102,7 +1091,7 @@ export class TopologyController { animate = true; setupInitialUpdate(); - QDRService.management.topology.startUpdating(false); + QDRService.management.topology.startUpdating(true); } } http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/55b7ae55/console/stand-alone/plugin/js/topology/topoUtils.js ---------------------------------------------------------------------- diff --git a/console/stand-alone/plugin/js/topology/topoUtils.js b/console/stand-alone/plugin/js/topology/topoUtils.js index cbed6c0..71e5e4c 100644 --- a/console/stand-alone/plugin/js/topology/topoUtils.js +++ b/console/stand-alone/plugin/js/topology/topoUtils.js @@ -16,6 +16,9 @@ KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + +/* global Set */ + // highlight the paths between the selected node and the hovered node function findNextHopNode(from, d, QDRService, selected_node, nodes) { // d is the node that the mouse is over @@ -35,7 +38,7 @@ function findNextHopNode(from, d, QDRService, selected_node, nodes) { let vAr = sInfo['router.node'].results; for (let hIdx = 0; hIdx < vAr.length; ++hIdx) { let addrT = QDRService.utilities.valFor(aAr, vAr[hIdx], 'id'); - if (addrT == d.name) { + if (d.name && (addrT == d.name)) { let next = QDRService.utilities.valFor(aAr, vAr[hIdx], 'nextHop'); return (next == null) ? nodes.nodeFor(addrT) : nodes.nodeFor(next); } @@ -67,23 +70,44 @@ export function connectionPopupHTML (d, QDRService) { return; } let utils = QDRService.utilities; - let getConnsArray = function (d, conn) { - let conns = [conn]; - if (d.cls === 'small') { - conns = []; - let normals = d.target.normals ? d.target.normals : d.source.normals; - for (let n=0; n<normals.length; n++) { - if (normals[n].resultIndex !== undefined) { - conns.push(utils.flatten(onode['connection'].attributeNames, - onode['connection'].results[normals[n].resultIndex])); + // return all of onode's connections that connecto to right + let getConnsArray = function (onode, key, right) { + if (right.normals) { + // if we want connections between a router and a client[s] + let connIds = new Set(); + let connIndex = onode.connection.attributeNames.indexOf('identity'); + for (let n=0; n<right.normals.length; n++) { + let normal = right.normals[n]; + if (normal.key === key) { + connIds.add(normal.connectionId); + } else if (normal.alsoConnectsTo) { + normal.alsoConnectsTo.forEach( function (ac2) { + if (ac2.key === key) + connIds.add(ac2.connectionId); + }); } } + return onode.connection.results.filter( function (result) { + return connIds.has(result[connIndex]); + }).map( function (c) { + return utils.flatten(onode.connection.attributeNames, c); + }); + } + else { + // we want the connection between two routers + let container = utils.nameFromId(right.key); + let containerIndex = onode.connection.attributeNames.indexOf('container'); + let roleIndex = onode.connection.attributeNames.indexOf('role'); + return onode.connection.results.filter( function (conn) { + return conn[containerIndex] === container && conn[roleIndex] === 'inter-router'; + }).map( function (c) { + return utils.flatten(onode.connection.attributeNames, c); + }); } - return conns; }; // construct HTML to be used in a popup when the mouse is moved over a link. // The HTML is sanitized elsewhere before it is displayed - let linksHTML = function (onode, conn, d) { + let linksHTML = function (onode, conns) { const max_links = 10; const fields = ['deliveryCount', 'undeliveredCount', 'unsettledCount', 'rejectedCount', 'releasedCount', 'modifiedCount']; // local function to determine if a link's connectionId is in any of the connections @@ -103,7 +127,6 @@ export function connectionPopupHTML (d, QDRService) { } return out; }; - let conns = getConnsArray(d, conn); // if the data for the line is from a client (small circle), we may have multiple connections // loop through all links for this router and accumulate those belonging to the connection(s) let nodeLinks = onode['router.link']; @@ -189,50 +212,39 @@ export function connectionPopupHTML (d, QDRService) { }); HTML += `<tr><td> ${joinedVals} </td></tr>`; } - // no rows were added - if (links.length === 0) { - HTML += `<tr><td align="center" colspan="${th.length}">Calculating rates, or rates were all zero</td></tr>`; - } - HTML += '</table>'; - return HTMLHeading + HTML; + return links.length > 0 ? `${HTMLHeading}${HTML}</table>` : ''; }; - let left = d.left ? d.source : d.target; - // left is the connection with dir 'in' - let right = d.left ? d.target : d.source; - let onode = QDRService.management.topology.nodeInfo()[left.key]; - // loop through all the connections for left, and find the one for right - let rightIndex = onode['connection'].results.findIndex( function (conn) { - return utils.valFor(onode['connection'].attributeNames, conn, 'container') === right.routerId; - }); - if (rightIndex < 0) { - // we have a connection to a client/service - rightIndex = +left.resultIndex; + let left, right; + if (d.left) { + left = d.source; + right = d.target; + } else { + left = d.target; + right = d.source; } - if (isNaN(rightIndex)) { - // we have a connection to a console - rightIndex = +right.resultIndex; + if (left.normals) { + // swap left and right + [left, right] = [right, left]; } + // left is a router. right is either a router or a client[s] + let onode = QDRService.management.topology.nodeInfo()[left.key]; + // find all the connections for left that go to right + let conns = getConnsArray(onode, left.key, right); + let HTML = ''; - if (rightIndex >= 0) { - let conn = onode['connection'].results[rightIndex]; - conn = utils.flatten(onode['connection'].attributeNames, conn); - let conns = getConnsArray(d, conn); - if (conns.length === 1) { - HTML += '<h5>Connection'+(conns.length > 1 ? 's' : '')+'</h5>'; - HTML += '<table class="popupTable"><tr class="header"><td>Security</td><td>Authentication</td><td>Tenant</td><td>Host</td>'; + HTML += '<h5>Connection'+(conns.length > 1 ? 's' : '')+'</h5>'; + HTML += '<table class="popupTable"><tr class="header"><td>Security</td><td>Authentication</td><td>Tenant</td><td>Host</td>'; - for (let c=0; c<conns.length; c++) { - HTML += ('<tr><td>' + utils.connSecurity(conns[c]) + '</td>'); - HTML += ('<td>' + utils.connAuth(conns[c]) + '</td>'); - HTML += ('<td>' + (utils.connTenant(conns[c]) || '--') + '</td>'); - HTML += ('<td>' + conns[c].host + '</td>'); - HTML += '</tr>'; - } - HTML += '</table>'; - } - HTML += linksHTML(onode, conn, d); + for (let c=0; c<Math.min(conns.length, 10); c++) { + HTML += ('<tr><td>' + utils.connSecurity(conns[c]) + '</td>'); + HTML += ('<td>' + utils.connAuth(conns[c]) + '</td>'); + HTML += ('<td>' + (utils.connTenant(conns[c]) || '--') + '</td>'); + HTML += ('<td>' + conns[c].host + '</td>'); + HTML += '</tr>'; } + HTML += '</table>'; + HTML += linksHTML(onode, conns); return HTML; } http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/55b7ae55/console/stand-alone/plugin/js/topology/traffic.js ---------------------------------------------------------------------- diff --git a/console/stand-alone/plugin/js/topology/traffic.js b/console/stand-alone/plugin/js/topology/traffic.js index a6079fc..ff46d63 100644 --- a/console/stand-alone/plugin/js/topology/traffic.js +++ b/console/stand-alone/plugin/js/topology/traffic.js @@ -19,15 +19,25 @@ under the License. /* global d3 Promise */ -import { ChordData } from '../chord/data.js'; -import { MIN_CHORD_THRESHOLD } from '../chord/matrix.js'; -import { nextHop } from './topoUtils.js'; +import { ChordData } from "../chord/data.js"; +import { MIN_CHORD_THRESHOLD } from "../chord/matrix.js"; +import { nextHop } from "./topoUtils.js"; const transitionDuration = 1000; -const CHORDFILTERKEY = 'chordFilter'; +const CHORDFILTERKEY = "chordFilter"; -export class Traffic { // eslint-disable-line no-unused-vars - constructor($scope, $timeout, QDRService, converter, radius, topology, type, prefix) { +export class Traffic { + // eslint-disable-line no-unused-vars + constructor( + $scope, + $timeout, + QDRService, + converter, + radius, + topology, + type, + prefix + ) { $scope.addressColors = {}; this.QDRService = QDRService; this.type = type; // moving dots or colored path @@ -53,16 +63,17 @@ export class Traffic { // eslint-disable-line no-unused-vars } // remove any animations that are in progress remove() { - if (this.vis) - this.vis.remove(); + if (this.vis) this.vis.remove(); } // called when one of the address checkboxes is toggled setAnimationType(type, converter, radius) { this.stop(); this.remove(); this.type = type; - this.vis = type === 'dots' ? new Dots(this, converter, radius) : - new Congestion(this); + this.vis = + type === "dots" + ? new Dots(this, converter, radius) + : new Congestion(this); } // called periodically to refresh the traffic flow doUpdate() { @@ -70,7 +81,6 @@ export class Traffic { // eslint-disable-line no-unused-vars } } - /* Base class for congestion and dots visualizations */ class TrafficAnimation { constructor(traffic) { @@ -79,18 +89,16 @@ class TrafficAnimation { nodeIndexFor(nodes, name) { for (let i = 0; i < nodes.length; i++) { let node = nodes[i]; - if (node.container === name) - return i; + if (node.container === name) return i; } // not found. loop through normals for (let i = 0; i < nodes.length; i++) { let node = nodes[i]; if (node.normals) { - let normalIndex = node.normals.findIndex( function (normal) { + let normalIndex = node.normals.findIndex(function(normal) { return normal.container === name; }); - if (normalIndex >= 0) - return i; + if (normalIndex >= 0) return i; } } return -1; @@ -98,15 +106,20 @@ class TrafficAnimation { } /* Color the links between router to show how heavily used the links are. */ -class Congestion extends TrafficAnimation{ +class Congestion extends TrafficAnimation { constructor(traffic) { super(traffic); this.init_markerDef(); } init_markerDef() { - this.custom_markers_def = d3.select('#SVG_ID').select('defs.custom-markers'); + this.custom_markers_def = d3 + .select("#SVG_ID") + .select("defs.custom-markers"); if (this.custom_markers_def.empty()) { - this.custom_markers_def = d3.select('#SVG_ID').append('svg:defs').attr('class', 'custom-markers'); + this.custom_markers_def = d3 + .select("#SVG_ID") + .append("svg:defs") + .attr("class", "custom-markers"); } } findResult(node, entity, attribute, value) { @@ -114,7 +127,10 @@ class Congestion extends TrafficAnimation{ if (attrIndex >= 0) { for (let i = 0; i < node[entity].results.length; i++) { if (node[entity].results[i][attrIndex] === value) { - return this.traffic.QDRService.utilities.flatten(node[entity].attributeNames, node[entity].results[i]); + return this.traffic.QDRService.utilities.flatten( + node[entity].attributeNames, + node[entity].results[i] + ); } } } @@ -122,107 +138,146 @@ class Congestion extends TrafficAnimation{ } doUpdate() { let self = this; - this.traffic.QDRService.management.topology.ensureAllEntities([{ entity: 'router.link', force: true }, { entity: 'connection' }], function () { - let links = {}; - let nodeInfo = self.traffic.QDRService.management.topology.nodeInfo(); - const nodes = self.traffic.topology.nodes.nodes; - const srv = self.traffic.QDRService; - // accumulate all the inter-router links in an object - // keyed by the svgs path id - for (let nodeId in nodeInfo) { - let node = nodeInfo[nodeId]; - let nodeLinks = node['router.link']; - if (!nodeLinks) - continue; - for (let n = 0; n < nodeLinks.results.length; n++) { - let link = srv.utilities.flatten(nodeLinks.attributeNames, nodeLinks.results[n]); - if (link.linkType !== 'router-control') { - let f = self.nodeIndexFor(nodes, srv.utilities.nameFromId(nodeId)); - let connection = self.findResult(node, 'connection', 'identity', link.connectionId); - if (connection) { - let t = self.nodeIndexFor(nodes, connection.container); - let little = Math.min(f, t); - let big = Math.max(f, t); - if (little >= 0) { - let key = ['#path', nodes[little].uid(srv), - nodes[big].uid(srv)].join('-'); - if (!links[key]) - links[key] = []; - links[key].push(link); + this.traffic.QDRService.management.topology.ensureAllEntities( + [{ entity: "router.link", force: true }, { entity: "connection" }], + function() { + let links = {}; + let nodeInfo = self.traffic.QDRService.management.topology.nodeInfo(); + const nodes = self.traffic.topology.nodes.nodes; + const srv = self.traffic.QDRService; + // accumulate all the inter-router links in an object + // keyed by the svgs path id + for (let nodeId in nodeInfo) { + let node = nodeInfo[nodeId]; + let nodeLinks = node["router.link"]; + if (!nodeLinks) continue; + for (let n = 0; n < nodeLinks.results.length; n++) { + let link = srv.utilities.flatten( + nodeLinks.attributeNames, + nodeLinks.results[n] + ); + if (link.linkType !== "router-control") { + let f = self.nodeIndexFor( + nodes, + srv.utilities.nameFromId(nodeId) + ); + let connection = self.findResult( + node, + "connection", + "identity", + link.connectionId + ); + if (connection) { + let t = self.nodeIndexFor(nodes, connection.container); + let little = Math.min(f, t); + let big = Math.max(f, t); + if (little >= 0) { + let key = [ + "#path", + nodes[little].uid(srv), + nodes[big].uid(srv) + ].join("-"); + if (!links[key]) links[key] = []; + links[key].push(link); + } } } } } - } - // accumulate the colors/directions to be used - let colors = {}; - for (let key in links) { - let congestion = self.congestion(links[key]); - let pathId = key.replace(/\./g, '\\.').replace(/ /g, '\\ '); - let path = d3.select(pathId); - if (path && !path.empty()) { - let dir = path.attr('marker-end') === '' ? 'start' : 'end'; - let small = path.attr('class').indexOf('small') > -1; - let id = dir + '-' + congestion.substr(1) + (small ? '-s' : ''); - colors[id] = { dir: dir, color: congestion, small: small }; - path - .classed('traffic', true) - .attr('marker-start', function (d) { - return null; - //return d.left ? 'url(' + self.traffic.prefix + '#' + id + ')' : null; - }) - .attr('marker-end', function (d) { - return null; - //return d.right ? 'url(' + self.traffic.prefix + '#' + id + ')' : null; - }); - path - .transition() - .duration(1000) - .attr('stroke', congestion); + // accumulate the colors/directions to be used + let colors = {}; + for (let key in links) { + let congestion = self.congestion(links[key]); + let pathId = key.replace(/\./g, "\\.").replace(/ /g, "\\ "); + let path = d3.select(pathId); + if (path && !path.empty()) { + let dir = path.attr("marker-end") === "" ? "start" : "end"; + let small = path.attr("class").indexOf("small") > -1; + let id = dir + "-" + congestion.substr(1) + (small ? "-s" : ""); + colors[id] = { dir: dir, color: congestion, small: small }; + path + .classed("traffic", true) + .attr("marker-start", function() { + return null; + //return d.left ? 'url(' + self.traffic.prefix + '#' + id + ')' : null; + }) + .attr("marker-end", function() { + return null; + //return d.right ? 'url(' + self.traffic.prefix + '#' + id + ')' : null; + }); + path + .transition() + .duration(1000) + .attr("stroke", congestion); + } } + // create the svg:def that holds the custom markers + self.init_markerDef(); + let colorKeys = Object.keys(colors); + let custom_markers = self.custom_markers_def + .selectAll("marker") + .data(colorKeys, function(d) { + return d; + }); + custom_markers + .enter() + .append("svg:marker") + .attr("id", function(d) { + return d; + }) + .attr("viewBox", "0 -5 10 10") + .attr("refX", function(d) { + return colors[d].dir === "end" ? 24 : colors[d].small ? -24 : -14; + }) + .attr("markerWidth", 14) + .attr("markerHeight", 14) + .attr("markerUnits", "userSpaceOnUse") + .attr("orient", "auto") + .style("fill", function(d) { + return colors[d].color; + }) + .append("svg:path") + .attr("d", function(d) { + return colors[d].dir === "end" + ? "M 0 -5 L 10 0 L 0 5 z" + : "M 10 -5 L 0 0 L 10 5 z"; + }); + custom_markers.exit().remove(); } - // create the svg:def that holds the custom markers - self.init_markerDef(); - let colorKeys = Object.keys(colors); - let custom_markers = self.custom_markers_def.selectAll('marker') - .data(colorKeys, function (d) { return d; }); - custom_markers.enter().append('svg:marker') - .attr('id', function (d) { return d; }) - .attr('viewBox', '0 -5 10 10') - .attr('refX', function (d) { - return colors[d].dir === 'end' ? 24 : (colors[d].small) ? -24 : -14; - }) - .attr('markerWidth', 14) - .attr('markerHeight', 14) - .attr('markerUnits', 'userSpaceOnUse') - .attr('orient', 'auto') - .style('fill', function (d) { return colors[d].color; }) - .append('svg:path') - .attr('d', function (d) { - return colors[d].dir === 'end' ? 'M 0 -5 L 10 0 L 0 5 z' : 'M 10 -5 L 0 0 L 10 5 z'; - }); - custom_markers.exit().remove(); - }); + ); } congestion(links) { let v = 0; for (let l = 0; l < links.length; l++) { let link = links[l]; - v = Math.max(v, (link.undeliveredCount + link.unsettledCount) / link.capacity); + v = Math.max( + v, + (link.undeliveredCount + link.unsettledCount) / link.capacity + ); } return this.fillColor(v); } fillColor(v) { - let color = d3.scale.linear().domain([0, 1, 2, 3]) + let color = d3.scale + .linear() + .domain([0, 1, 2, 3]) .interpolate(d3.interpolateHcl) - .range([d3.rgb('#000000'), d3.rgb('#00FF00'), d3.rgb('#FFA500'), d3.rgb('#FF0000')]); + .range([ + d3.rgb("#000000"), + d3.rgb("#00FF00"), + d3.rgb("#FFA500"), + d3.rgb("#FF0000") + ]); return color(Math.max(0, Math.min(3, v))); } remove() { - d3.select('#SVG_ID').selectAll('path.traffic') - .classed('traffic', false); - d3.select('#SVG_ID').select('defs.custom-markers') - .selectAll('marker').remove(); + d3.select("#SVG_ID") + .selectAll("path.traffic") + .classed("traffic", false); + d3.select("#SVG_ID") + .select("defs.custom-markers") + .selectAll("marker") + .remove(); } } @@ -231,55 +286,61 @@ class Congestion extends TrafficAnimation{ class Dots extends TrafficAnimation { constructor(traffic, converter, radius) { super(traffic); - this.excludedAddresses = localStorage[CHORDFILTERKEY] ? JSON.parse(localStorage[CHORDFILTERKEY]) : []; + this.excludedAddresses = localStorage[CHORDFILTERKEY] + ? JSON.parse(localStorage[CHORDFILTERKEY]) + : []; this.radius = radius; // the radius of a router circle this.lastFlows = {}; // the number of dots animated between routers this.stopped = false; this.chordData = new ChordData(this.traffic.QDRService, true, converter); // gets ingressHistogram data this.chordData.setFilter(this.excludedAddresses); traffic.$scope.addresses = {}; - this.chordData.getMatrix().then(function () { - this.traffic.$timeout(function () { - this.traffic.$scope.addresses = this.chordData.getAddresses(); - for (let address in this.traffic.$scope.addresses) { - this.fillColor(address); - } - }.bind(this)); - }.bind(this)); + this.chordData.getMatrix().then( + function() { + this.traffic.$timeout( + function() { + this.traffic.$scope.addresses = this.chordData.getAddresses(); + for (let address in this.traffic.$scope.addresses) { + this.fillColor(address); + } + }.bind(this) + ); + }.bind(this) + ); // colors this.colorGen = d3.scale.category10(); - for (let i=0; i<10; i++) { + for (let i = 0; i < 10; i++) { this.colorGen(i); } let self = this; // event notification that an address checkbox has changed - traffic.$scope.addressFilterChanged = function () { - self.updateAddresses() - .then(function () { - // don't wait for the next polling cycle. update now - self.traffic.stop(); - self.traffic.start(); - }); + traffic.$scope.addressFilterChanged = function() { + self.updateAddresses().then(function() { + // don't wait for the next polling cycle. update now + self.traffic.stop(); + self.traffic.start(); + }); }; // called by angular when mouse enters one of the address legends - traffic.$scope.enterLegend = function (address) { + traffic.$scope.enterLegend = function(address) { // fade all flows that aren't for this address self.fadeOtherAddresses(address); }; // called when the mouse leaves one of the address legends - traffic.$scope.leaveLegend = function () { + traffic.$scope.leaveLegend = function() { self.unFadeAll(); }; // clicked on the address name. toggle the address checkbox - traffic.$scope.addressClick = function (address) { - self.toggleAddress(address) - .then(function () { - self.updateAddresses(); - }); + traffic.$scope.addressClick = function(address) { + self.toggleAddress(address).then(function() { + self.updateAddresses(); + }); }; } remove() { - d3.select('#SVG_ID').selectAll('circle.flow').remove(); + d3.select("#SVG_ID") + .selectAll("circle.flow") + .remove(); this.lastFlows = {}; this.stopped = true; } @@ -290,88 +351,130 @@ class Dots extends TrafficAnimation { this.excludedAddresses.push(address); } localStorage[CHORDFILTERKEY] = JSON.stringify(this.excludedAddresses); - if (this.chordData) - this.chordData.setFilter(this.excludedAddresses); - return new Promise(function (resolve) { + if (this.chordData) this.chordData.setFilter(this.excludedAddresses); + return new Promise(function(resolve) { return resolve(); }); } toggleAddress(address) { - this.traffic.$scope.addresses[address] = !this.traffic.$scope.addresses[address]; - return new Promise(function (resolve) { + this.traffic.$scope.addresses[address] = !this.traffic.$scope.addresses[ + address + ]; + return new Promise(function(resolve) { return resolve(); }); } fadeOtherAddresses(address) { - d3.selectAll('circle.flow').classed('fade', function (d) { + d3.selectAll("circle.flow").classed("fade", function(d) { return d.address !== address; }); } unFadeAll() { - d3.selectAll('circle.flow').classed('fade', false); + d3.selectAll("circle.flow").classed("fade", false); } doUpdate() { let self = this; this.stopped = false; // we need the nextHop data to show traffic between routers that are connected by intermediaries - this.traffic.QDRService.management.topology.ensureAllEntities([{ entity: 'router.node', attrs: ['id', 'nextHop'] }], function () { - // get the ingressHistogram data for all routers - self.chordData.getMatrix().then(self.render.bind(self), function (e) { - console.log('Could not get message histogram' + e); - }); - }); + this.traffic.QDRService.management.topology.ensureAllEntities( + [{ entity: "router.node", attrs: ["id", "nextHop"] }], + function() { + // get the ingressHistogram data for all routers + self.chordData.getMatrix().then(self.render.bind(self), function(e) { + console.log("Could not get message histogram" + e); + }); + } + ); } render(matrix) { if (this.stopped === false) { - this.traffic.$timeout(function () { - this.traffic.$scope.addresses = this.chordData.getAddresses(); - }.bind(this)); + this.traffic.$timeout( + function() { + this.traffic.$scope.addresses = this.chordData.getAddresses(); + }.bind(this) + ); // get the rate of message flow between routers let hops = {}; // every hop between routers that is involved in message flow let matrixMessages = matrix.matrixMessages(); // the fastest traffic rate gets 3 times as many dots as the slowest let minmax = matrix.getMinMax(); - let flowScale = d3.scale.linear().domain(minmax).range([1, 1.1]); + let flowScale = d3.scale + .linear() + .domain(minmax) + .range([1, 1.1]); // row is ingress router, col is egress router. Value at [row][col] is the rate - matrixMessages.forEach(function (row, r) { - row.forEach(function (val, c) { - if (val > MIN_CHORD_THRESHOLD) { - // translate between matrix row/col and node index - let f = this.nodeIndexFor(this.traffic.topology.nodes.nodes, matrix.rows[r].egress); - let t = this.nodeIndexFor(this.traffic.topology.nodes.nodes, matrix.rows[r].ingress); - let address = matrix.getAddress(r, c); - if (r !== c) { - // accumulate the hops between the ingress and egress routers - nextHop(this.traffic.topology.nodes.nodes[f], - this.traffic.topology.nodes.nodes[t], - this.traffic.topology.nodes, - this.traffic.topology.links, - this.traffic.QDRService, - this.traffic.topology.nodes.nodes[f], - function (link, fnode, tnode) { - let key = '-' + link.uid; - let back = fnode.index < tnode.index; - if (!hops[key]) - hops[key] = []; - hops[key].push({ val: val, back: back, address: address }); - }); - } - // Find the senders connected to nodes[f] and the receivers connected to nodes[t] - // and add their links to the animation - this.addClients(hops, this.traffic.topology.nodes.nodes, f, val, true, address); - this.addClients(hops, this.traffic.topology.nodes.nodes, t, val, false, address); - } - }.bind(this)); - }.bind(this)); + matrixMessages.forEach( + function(row, r) { + row.forEach( + function(val, c) { + if (val > MIN_CHORD_THRESHOLD) { + // translate between matrix row/col and node index + let f = this.nodeIndexFor( + this.traffic.topology.nodes.nodes, + matrix.rows[r].egress + ); + let t = this.nodeIndexFor( + this.traffic.topology.nodes.nodes, + matrix.rows[r].ingress + ); + let address = matrix.getAddress(r, c); + if (r !== c) { + // accumulate the hops between the ingress and egress routers + nextHop( + this.traffic.topology.nodes.nodes[f], + this.traffic.topology.nodes.nodes[t], + this.traffic.topology.nodes, + this.traffic.topology.links, + this.traffic.QDRService, + this.traffic.topology.nodes.nodes[f], + function(link, fnode, tnode) { + let key = "-" + link.uid; + let back = fnode.index < tnode.index; + if (!hops[key]) hops[key] = []; + hops[key].push({ + val: val, + back: back, + address: address + }); + } + ); + } + // Find the senders connected to nodes[f] and the receivers connected to nodes[t] + // and add their links to the animation + this.addClients( + hops, + this.traffic.topology.nodes.nodes, + f, + val, + true, + address + ); + this.addClients( + hops, + this.traffic.topology.nodes.nodes, + t, + val, + false, + address + ); + } + }.bind(this) + ); + }.bind(this) + ); // for each link between routers that has traffic, start an animation let keep = {}; for (let id in hops) { let hop = hops[id]; for (let h = 0; h < hop.length; h++) { let ahop = hop[h]; - let pathId = id.replace(/\./g, '\\.').replace(/ /g, '\\ '); - let flowId = id.replace(/\./g, '').replace(/ /g, '') + '-' + this.addressIndex(this, ahop.address) + (ahop.back ? 'b' : ''); - let path = d3.select('#path' + pathId); + let pathId = id.replace(/\./g, "\\.").replace(/ /g, "\\ "); + let flowId = + id.replace(/\./g, "").replace(/ /g, "") + + "-" + + this.addressIndex(this, ahop.address) + + (ahop.back ? "b" : ""); + let path = d3.select("#path" + pathId); // start the animation. If the animation is already running, this will have no effect this.startAnimation(path, flowId, ahop, flowScale(ahop.val)); keep[flowId] = true; @@ -381,7 +484,9 @@ class Dots extends TrafficAnimation { for (let id in this.lastFlows) { if (this.lastFlows[id] && !keep[id]) { this.lastFlows[id] = 0; - d3.select('#SVG_ID').selectAll('circle.flow' + id).remove(); + d3.select("#SVG_ID") + .selectAll("circle.flow" + id) + .remove(); } } } @@ -389,11 +494,15 @@ class Dots extends TrafficAnimation { // animate the d3 selection (flow) along the given path animateFlow(flow, path, count, back, rate) { let l = path.node().getTotalLength(); - flow.transition() - .ease('easeLinear') - .duration(l * 10 / rate) - .attrTween('transform', this.translateDots(this.radius, path, count, back)) - .each('end', () => { + flow + .transition() + .ease("easeLinear") + .duration((l * 10) / rate) + .attrTween( + "transform", + this.translateDots(this.radius, path, count, back) + ) + .each("end", () => { if (this.stopped === false) { this.animateFlow(flow, path, count, back, rate); } @@ -401,12 +510,12 @@ class Dots extends TrafficAnimation { } // create dots along the path between routers startAnimation(selection, id, hop, rate) { - if (selection.empty()) - return; + if (selection.empty()) return; this.animateDots(selection, id, hop, rate); } animateDots(path, id, hop, rate) { - let back = hop.back, address = hop.address; + let back = hop.back, + address = hop.address; // the density of dots is determined by the rate of this traffic relative to the other traffic let len = Math.max(Math.floor(path.node().getTotalLength() / 50), 1); let dots = []; @@ -415,24 +524,29 @@ class Dots extends TrafficAnimation { } // keep track of the number of dots for each link. If the length of the link is changed, // re-create the animation - if (!this.lastFlows[id]) - this.lastFlows[id] = len; + if (!this.lastFlows[id]) this.lastFlows[id] = len; else { if (this.lastFlows[id] !== len) { this.lastFlows[id] = len; - d3.select('#SVG_ID').selectAll('circle.flow' + id).remove(); + d3.select("#SVG_ID") + .selectAll("circle.flow" + id) + .remove(); } } - let flow = d3.select('#SVG_ID').selectAll('circle.flow' + id) - .data(dots, function (d) { return d.i + d.address; }); + let flow = d3 + .select("#SVG_ID") + .selectAll("circle.flow" + id) + .data(dots, function(d) { + return d.i + d.address; + }); let circles = flow - .enter().append('circle') - .attr('class', 'flow flow' + id) - .attr('fill', this.fillColor(address)) - .attr('r', 5); + .enter() + .append("circle") + .attr("class", "flow flow" + id) + .attr("fill", this.fillColor(address)) + .attr("r", 5); this.animateFlow(circles, path, dots.length, back, rate); - flow.exit() - .remove(); + flow.exit().remove(); } fillColor(n) { if (!(n in this.traffic.$scope.addressColors)) { @@ -441,51 +555,53 @@ class Dots extends TrafficAnimation { } return this.traffic.$scope.addressColors[n]; } - // find the link that carries traffic for this address + // find the link that carries traffic for this address // going to nodes[f] if sender is true // coming from nodes[f] if sender if false. // Add the link's id to the hops array addClients(hops, nodes, f, val, sender, address) { - const cdir = sender ? 'out' : 'in'; - const uuid = nodes[f].uid(this.traffic.QDRService); + if (!nodes[f]) + return; + const cdir = sender ? "out" : "in"; + const uuid = nodes[f].uid(); const key = nodes[f].key; - const links = this.traffic.QDRService.management.topology._nodeInfo[key]['router.link']; + const links = this.traffic.QDRService.management.topology._nodeInfo[key][ + "router.link" + ]; if (links) { - const ilt = links.attributeNames.indexOf('linkType'); - const ioa = links.attributeNames.indexOf('owningAddr'); - const ici = links.attributeNames.indexOf('connectionId'); - const ild = links.attributeNames.indexOf('linkDir'); - let foundLinks = links.results.filter( function (l) { - return (l[ilt] === 'endpoint' || l[ilt] === 'edge-downlink') && + const ilt = links.attributeNames.indexOf("linkType"); + const ioa = links.attributeNames.indexOf("owningAddr"); + const ici = links.attributeNames.indexOf("connectionId"); + const ild = links.attributeNames.indexOf("linkDir"); + let foundLinks = links.results.filter(function(l) { + return ( + (l[ilt] === "endpoint" || l[ilt] === "edge-downlink") && address === this.traffic.QDRService.utilities.addr_text(l[ioa]) && - l[ild] === cdir; + l[ild] === cdir + ); }, this); // we now have the links involved in traffic for this address that // ingress/egress to/from this router (f). - // Now find the created node that the link is to - for (let linkIndex=0; linkIndex<foundLinks.length; linkIndex++) { - let nodeIndex = nodes.findIndex( function (node) { - if (node.normals && node.key === key && (node.cdir === cdir || node.cdir === 'both')) { - return node.normals.some( function (normal) { - return normal.connectionId == foundLinks[linkIndex][ici]; - }); - } else if (node.alsoConnectsTo) { - return node.alsoConnectsTo.some( function (ac2) { - return ac2.key === key && ac2.connectionId === foundLinks[linkIndex][ici] && - (ac2.dir === cdir || ac2.dir === 'both'); - }); + // Now find the created node that each link is associated with + for (let linkIndex = 0; linkIndex < foundLinks.length; linkIndex++) { + // use .some so the loop stops at the 1st match + nodes.some( function (node) { + if ( + node.normals && + node.normals.some(function(normal) { + return testNode(normal, key, cdir, foundLinks[linkIndex][ici]); + }) + ) { + // one of the normals for this node has the traffic + const uuid2 = node.uid(); + const key = ["", uuid, uuid2].join("-"); + if (!hops[key]) + hops[key] = []; + hops[key].push({ val: val, back: !sender, address: address }); + return true; } - else - return false; + return false; }); - if (nodeIndex >= 0) { - // one of the normals for this node has the traffic - const uuid2 = nodes[nodeIndex].uid(this.traffic.QDRService); - const key = ['', uuid, uuid2].join('-'); - if (!hops[key]) - hops[key] = []; - hops[key].push({ val: val, back: !sender, address: address }); - } } } } @@ -496,20 +612,36 @@ class Dots extends TrafficAnimation { translateDots(radius, path, count, back) { let pnode = path.node(); // will be called for each element in the flow selection (for each dot) - return function (d) { + return function(d) { // will be called with t going from 0 to 1 for each dot - return function (t) { + return function(t) { // start the points at different positions depending on their value (d) let tt = t * 1000; - let f = ((tt + (d.i * 1000 / count)) % 1000) / 1000; - if (back) - f = 1 - f; + let f = ((tt + (d.i * 1000) / count) % 1000) / 1000; + if (back) f = 1 - f; // l needs to be calculated each tick because the path's length might be changed during the animation let l = pnode.getTotalLength(); let p = pnode.getPointAtLength(f * l); - return 'translate(' + p.x + ',' + p.y + ')'; + return "translate(" + p.x + "," + p.y + ")"; }; }; } } +// see if this node, or any of the nodes it also connects to +// match the key, dir, and connectionId +let testNode = function(node, key, dir, connectionId) { + // does the node match + if ( + node.key === key && + node.connectionId == connectionId && + (node.cdir === dir || node.cdir === "both") + ) + return true; + if (!node.alsoConnectsTo) + return false; + // do any of the alsoConnectsTo nodes match + return node.alsoConnectsTo.some(function(ac2) { + return testNode(ac2, key, dir, connectionId); + }); +}; http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/55b7ae55/console/stand-alone/test/links.js ---------------------------------------------------------------------- diff --git a/console/stand-alone/test/links.js b/console/stand-alone/test/links.js index f6aff44..c3aad88 100644 --- a/console/stand-alone/test/links.js +++ b/console/stand-alone/test/links.js @@ -27,11 +27,11 @@ import { QDRService } from '../plugin/js/qdrService.js'; class Log { constructor() { } - log (msg) {} - debug (msg) {} - error (msg) {} - info (msg) {} - warn (msg) {} + log (msg) {console.log(msg);} + debug (msg) {console.log(msg);} + error (msg) {console.log(msg);} + info (msg) {console.log(msg);} + warn (msg) {console.log(msg);} } var log = new Log(); var loc = {protocol: function () { return 'http://';}}; @@ -85,7 +85,7 @@ describe('Links', function() { }); describe('#initializes', function() { it('should initialize', function() { - links.initializeLinks(nodeInfo, nodes, unknowns, {}, width); + links.initialize(nodeInfo, nodes, unknowns, {}, width); assert.equal(links.links.length, 10); }); }); http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/55b7ae55/console/stand-alone/tslint.json ---------------------------------------------------------------------- diff --git a/console/stand-alone/tslint.json b/console/stand-alone/tslint.json index ad645d3..06ee45b 100644 --- a/console/stand-alone/tslint.json +++ b/console/stand-alone/tslint.json @@ -27,13 +27,7 @@ under the License. "max-line-length": [true, 140], "no-arg": true, "no-bitwise": true, - "no-console": [true, - "debug", - "info", - "time", - "timeEnd", - "trace" - ], + "no-console": [true, "debug", "info", "time", "timeEnd", "trace"], "no-construct": true, "no-debugger": true, "no-duplicate-variable": true, @@ -43,22 +37,24 @@ under the License. "no-trailing-whitespace": true, "no-unused-variable": false, //"no-use-before-declare": true, - "one-line": [true, + "one-line": [ + true, "check-open-brace", "check-catch", "check-else", "check-whitespace" ], - "quotemark": [true, "single"], + "quotemark": [true, "double"], "radix": true, "semicolon": true, "triple-equals": [true, "allow-null-check"], "variable-name": false, - "whitespace": [true, + "whitespace": [ + true, "check-branch", "check-decl", "check-operator", "check-separator" ] } -} \ No newline at end of file +} --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@qpid.apache.org For additional commands, e-mail: commits-h...@qpid.apache.org