Repository: qpid-dispatch Updated Branches: refs/heads/master 43b3b2e03 -> 0310ac224
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0310ac22/console/stand-alone/package.json ---------------------------------------------------------------------- diff --git a/console/stand-alone/package.json b/console/stand-alone/package.json index 23731e3..aa59f68 100644 --- a/console/stand-alone/package.json +++ b/console/stand-alone/package.json @@ -45,6 +45,26 @@ "jquery.fancytree": "^2.26.0", "notifyjs-browser": "^0.4.2", "patternfly": "^3.30.0", - "rhea": "^0.2.10" + "rhea": "^0.2.13" + }, + "devDependencies": { + "babel-core": "^6.26.3", + "babel-preset-env": "^1.7.0", + "del": "^3.0.0", + "fs": "0.0.1-security", + "gulp": "github:gulpjs/gulp#4.0", + "gulp-babel": "^7.0.1", + "gulp-clean-css": "^3.9.4", + "gulp-concat": "^2.6.1", + "gulp-eslint": "^4.0.2", + "gulp-insert": "^0.5.0", + "gulp-ng-annotate": "^2.1.0", + "gulp-rename": "^1.2.3", + "gulp-sourcemaps": "^2.6.4", + "gulp-tslint": "^8.1.3", + "gulp-typescript": "^4.0.2", + "gulp-uglify": "^3.0.0", + "tslint": "^5.10.0", + "typescript": "^2.9.1" } } http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0310ac22/console/stand-alone/plugin/css/dispatch.css ---------------------------------------------------------------------- diff --git a/console/stand-alone/plugin/css/dispatch.css b/console/stand-alone/plugin/css/dispatch.css index 6478620..38495ad 100644 --- a/console/stand-alone/plugin/css/dispatch.css +++ b/console/stand-alone/plugin/css/dispatch.css @@ -22,7 +22,7 @@ main-display > .span8 { } ul.qdrListNodes > li > span { - padding: 6px 20px; 6px; 6px; + padding: 6px 20px 6px 6px; display: block; } http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0310ac22/console/stand-alone/plugin/js/dispatchPlugin.js ---------------------------------------------------------------------- diff --git a/console/stand-alone/plugin/js/dispatchPlugin.js b/console/stand-alone/plugin/js/dispatchPlugin.js index c39cb9b..47c8a60 100644 --- a/console/stand-alone/plugin/js/dispatchPlugin.js +++ b/console/stand-alone/plugin/js/dispatchPlugin.js @@ -39,20 +39,12 @@ var QDR = (function(QDR) { QDR.isStandalone = true; /** - * @property log - * @type {Logging.Logger} - * - * This plugin's logger instance - */ - //HIO QDR.log = Logger.get(QDR.pluginName); - /** * @property templatePath * @type {string} * * The top level path to this plugin's partials */ - QDR.srcBase = 'plugin/'; - QDR.templatePath = QDR.srcBase + 'html/'; + QDR.templatePath = 'html/'; /** * @property SETTINGS_KEY * @type {string} http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0310ac22/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 74ad417..15e73c4 100644 --- a/console/stand-alone/plugin/js/topology/qdrTopology.js +++ b/console/stand-alone/plugin/js/topology/qdrTopology.js @@ -1509,7 +1509,7 @@ var QDR = (function(QDR) { // add rows to the table for each link for (let l=0; l<links.length; l++) { if (l>=max_links) { - HTMLHeading = '<h4>Top ' + max_links + ' Links</h4>'; + HTMLHeading = `<h4>Top ${max_links} Links</h4>`; break; } let link = links[l]; @@ -1523,7 +1523,7 @@ var QDR = (function(QDR) { let joinedVals = fnJoin(vals, function (v1) { return ['</td><td' + (isNaN(+v1) ? '': ' align="right"') + '>', QDRService.utilities.pretty(v1 || '0')]; }); - HTML += ('<tr><td>' + joinedVals + '</td></tr>'); + HTML += `<tr><td> ${joinedVals} </td></tr>`; } HTML += '</table>'; return HTMLHeading + HTML; http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0310ac22/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 deleted file mode 100644 index e52df67..0000000 --- a/console/stand-alone/plugin/js/topology/traffic.js +++ /dev/null @@ -1,440 +0,0 @@ -/* -Licensed to the Apache Software Foundation (ASF) under one -or more contributor license agreements. See the NOTICE file -distributed with this work for additional information -regarding copyright ownership. The ASF licenses this file -to you under the Apache License, Version 2.0 (the -"License"); you may not use this file except in compliance -with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, -software distributed under the License is distributed on an -"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -KIND, either express or implied. See the License for the -specific language governing permissions and limitations -under the License. -*/ -'use strict'; - -/* global d3 ChordData MIN_CHORD_THRESHOLD Promise */ - -const transitionDuration = 1000; -const CHORDFILTERKEY = 'chordFilter'; - -function Traffic ($scope, $timeout, QDRService, converter, radius, topology, nextHop, type, prefix) { - $scope.addressColors = {}; - this.QDRService = QDRService; - this.type = type; // moving dots or colored path - this.prefix = prefix; // url prefix used in svg url()s - this.topology = topology; // contains the list of router nodes - this.nextHop = nextHop; // fn that returns the route through the network between two routers - this.$scope = $scope; - this.$timeout = $timeout; - // internal variables - this.interval = null; // setInterval handle - this.setAnimationType(type, converter, radius); -} - -// stop updating the traffic data -Traffic.prototype.stop = function () { - if (this.interval) { - clearInterval(this.interval); - this.interval = null; - } -}; -// start updating the traffic data -Traffic.prototype.start = function () { - this.doUpdate(); - this.interval = setInterval(this.doUpdate.bind(this), transitionDuration); -}; -// remove any animations that are in progress -Traffic.prototype.remove = function() { - if (this.vis) - this.vis.remove(); -}; -// called when one of the address checkboxes is toggled -Traffic.prototype.setAnimationType = function (type, converter, radius) { - this.stop(); - this.remove(); - this.type = type; - this.vis = type === 'dots' ? new Dots(this, converter, radius) : - new Congestion(this); -}; -// called periodically to refresh the traffic flow -Traffic.prototype.doUpdate = function () { - this.vis.doUpdate(); -}; - -/* Base class for congestion and dots visualizations */ -function TrafficAnimation (traffic) { - this.traffic = traffic; -} -TrafficAnimation.prototype.nodeIndexFor = function (nodes, name) { - for (let i=0; i<nodes.length; i++) { - let node = nodes[i]; - if (node.container === name) - return i; - } - return -1; -}; - -/* Color the links between router to show how heavily used the links are. */ -function Congestion (traffic) { - TrafficAnimation.call(this, traffic); - this.init_markerDef(); -} -Congestion.prototype = Object.create(TrafficAnimation.prototype); -Congestion.prototype.constructor = Congestion; - -Congestion.prototype.init_markerDef = function () { - 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'); - } -}; -Congestion.prototype.findResult = function (node, entity, attribute, value) { - let attrIndex = node[entity].attributeNames.indexOf(attribute); - 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 null; -}; -Congestion.prototype.doUpdate = function () { - 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(); - // 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']; - for (let n=0; n<nodeLinks.results.length; n++) { - let link = self.traffic.QDRService.utilities.flatten(nodeLinks.attributeNames, nodeLinks.results[n]); - if (link.linkType !== 'router-control') { - let f = self.nodeIndexFor(self.traffic.topology.nodes, - self.traffic.QDRService.management.topology.nameFromId(nodeId)); - let connection = self.findResult(node, 'connection', 'identity', link.connectionId); - if (connection) { - let t = self.nodeIndexFor(self.traffic.topology.nodes, connection.container); - let little = Math.min(f, t); - let big = Math.max(f, t); - let key = ['#path', little, big].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 path = d3.select(key); - 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 - .attr('stroke', congestion) - .classed('traffic', true) - .attr('marker-start', function(d) { - return d.left ? 'url(' + self.traffic.prefix + '#' + id + ')' : ''; - }) - .attr('marker-end', function(d) { - return d.right ? 'url(' + self.traffic.prefix + '#' + id + ')' : ''; - }); - } - } - - // 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', 4) - .attr('markerHeight', 4) - .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.prototype.congestion = function (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); - } - return this.fillColor(v); -}; -Congestion.prototype.fillColor = function (v) { - let color = d3.scale.linear().domain([0, 1, 2, 3]) - .interpolate(d3.interpolateHcl) - .range([d3.rgb('#CCCCCC'), d3.rgb('#00FF00'), d3.rgb('#FFA500'), d3.rgb('#FF0000')]); - return color(v); -}; -Congestion.prototype.remove = function () { - d3.select('#SVG_ID').selectAll('path.traffic') - .classed('traffic', false); - d3.select('#SVG_ID').select('defs.custom-markers') - .selectAll('marker').remove(); -}; - -/* Create animated dots moving along the links between routers - to show message flow */ -function Dots (traffic, converter, radius) { - TrafficAnimation.call(this, traffic); - 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.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)); - // colors - this.colorGen = d3.scale.category10(); - 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(); - }); - }; - // called by angular when mouse enters one of the address legends - 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 () { - self.unFadeAll(); - }; - // clicked on the address name. toggle the address checkbox - traffic.$scope.addressClick = function (address) { - self.toggleAddress(address) - .then(function () { - self.updateAddresses(); - }); - }; -} -Dots.prototype = Object.create(TrafficAnimation.prototype); -Dots.prototype.constructor = Dots; - -Dots.prototype.remove = function () { - for (let id in this.lastFlows) { - d3.select('#SVG_ID').selectAll('circle.flow' + id).remove(); - } - this.lastFlows = {}; -}; -Dots.prototype.updateAddresses = function () { - this.excludedAddresses = []; - for (let address in this.traffic.$scope.addresses) { - if (!this.traffic.$scope.addresses[address]) - this.excludedAddresses.push(address); - } - localStorage[CHORDFILTERKEY] = JSON.stringify(this.excludedAddresses); - if (this.chordData) - this.chordData.setFilter(this.excludedAddresses); - return new Promise(function (resolve) { - return resolve(); - }); -}; -Dots.prototype.toggleAddress = function (address) { - this.traffic.$scope.addresses[address] = !this.traffic.$scope.addresses[address]; - return new Promise(function (resolve) { - return resolve(); - }); -}; -Dots.prototype.fadeOtherAddresses = function (address) { - d3.selectAll('circle.flow').classed('fade', function (d) { - return d.address !== address; - }); -}; -Dots.prototype.unFadeAll = function () { - d3.selectAll('circle.flow').classed('fade', false); -}; -Dots.prototype.doUpdate = function () { - let self = this; - // 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); - }); - }); -}; -Dots.prototype.render = function (matrix) { - 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.75]); - // 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, matrix.rows[r].egress); - let t = this.nodeIndexFor(this.traffic.topology.nodes, matrix.rows[r].ingress); - let address = matrix.getAddress(r, c); - if (r !== c) { - // accumulate the hops between the ingress and egress routers - this.traffic.nextHop(this.traffic.topology.nodes[f], this.traffic.topology.nodes[t], 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, f, val, true, address); - this.addClients(hops, this.traffic.topology.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 flowId = id + '-' + this.addressIndex(this, ahop.address) + (ahop.back ? 'b' : ''); - let path = d3.select('#path' + id); - // 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; - } - } - // remove any existing animations that we don't have data for anymore - 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(); - } - } -}; -// animate the d3 selection (flow) along the given path -Dots.prototype.animateFlow = function (flow, path, count, back, rate) { - let self = this; - let l = path.node().getTotalLength(); - flow.transition() - .ease('easeLinear') - .duration(l * 10 / rate) - .attrTween('transform', self.translateDots(self.radius, path, count, back)) - .each('end', function () { self.animateFlow(flow, path, count, back, rate); }); -}; -// create dots along the path between routers -Dots.prototype.startAnimation = function (path, id, hop, rate) { - if (!path.node()) - return; - this.animateDots(path, id, hop, rate); -}; -Dots.prototype.animateDots = function (path, id, hop, rate) { - 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 = []; - for (let i = 0, offset = this.addressIndex(this, address); i < len; ++i) { - dots[i] = { i: i + 10 * offset, address: address }; - } - // 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; - else { - if (this.lastFlows[id] !== len) { - this.lastFlows[id] = len; - 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 circles = flow - .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(); -}; -Dots.prototype.fillColor = function (n) { - if (!(n in this.traffic.$scope.addressColors)) { - let ci = Object.keys(this.traffic.$scope.addressColors).length; - this.traffic.$scope.addressColors[n] = this.colorGen(ci); - } - return this.traffic.$scope.addressColors[n]; -}; -Dots.prototype.addClients = function (hops, nodes, f, val, sender, address) { - let cdir = sender ? 'out' : 'in'; - for (let n=0; n<nodes.length; n++) { - let node = nodes[n]; - if (node.normals && node.key === nodes[f].key && node.cdir === cdir) { - let key = ['',f,n].join('-'); - if (!hops[key]) - hops[key] = []; - hops[key].push({val: val, back: node.cdir === 'in', address: address}); - return; - } - } -}; -Dots.prototype.addressIndex = function (vis, address) { - return Object.keys(vis.traffic.$scope.addresses).indexOf(address); -}; -// calculate the translation for each dot along the path -Dots.prototype.translateDots = function (radius, path, count, back) { - let pnode = path.node(); - // will be called for each element in the flow selection (for each dot) - return function(d) { - // will be called with t going from 0 to 1 for each dot - 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; - // 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 + ')'; - }; - }; -}; http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0310ac22/console/stand-alone/plugin/js/topology/traffic.ts ---------------------------------------------------------------------- diff --git a/console/stand-alone/plugin/js/topology/traffic.ts b/console/stand-alone/plugin/js/topology/traffic.ts new file mode 100644 index 0000000..3f1e5c2 --- /dev/null +++ b/console/stand-alone/plugin/js/topology/traffic.ts @@ -0,0 +1,443 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +*/ +'use strict'; + +/* global d3 ChordData MIN_CHORD_THRESHOLD Promise */ +declare var d3: any; +declare var ChordData: any; +declare var MIN_CHORD_THRESHOLD: number; + +const transitionDuration = 1000; +const CHORDFILTERKEY = 'chordFilter'; + +class Traffic { // eslint-disable-line no-unused-vars + [x: string]: any; + constructor($scope, $timeout, QDRService, converter, radius, topology, nextHop, type, prefix) { + $scope.addressColors = {}; + this.QDRService = QDRService; + this.type = type; // moving dots or colored path + this.prefix = prefix; // url prefix used in svg url()s + this.topology = topology; // contains the list of router nodes + this.nextHop = nextHop; // fn that returns the route through the network between two routers + this.$scope = $scope; + this.$timeout = $timeout; + // internal variables + this.interval = null; // setInterval handle + this.setAnimationType(type, converter, radius); + } + // stop updating the traffic data + stop() { + if (this.interval) { + clearInterval(this.interval); + this.interval = null; + } + } + // start updating the traffic data + start() { + this.doUpdate(); + this.interval = setInterval(this.doUpdate.bind(this), transitionDuration); + } + // remove any animations that are in progress + 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); + } + // called periodically to refresh the traffic flow + doUpdate() { + this.vis.doUpdate(); + } +} + + +/* Base class for congestion and dots visualizations */ +class TrafficAnimation { + [x: string]: any; + constructor(traffic) { + this.traffic = traffic; + } + nodeIndexFor(nodes, name) { + for (let i = 0; i < nodes.length; i++) { + let node = nodes[i]; + if (node.container === name) { + return i; + } + } + return -1; + } +} + +/* Color the links between router to show how heavily used the links are. */ +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'); + if (this.custom_markers_def.empty()) { + this.custom_markers_def = d3.select('#SVG_ID').append('svg:defs').attr('class', 'custom-markers'); + } + } + findResult(node, entity, attribute, value) { + let attrIndex = node[entity].attributeNames.indexOf(attribute); + 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 null; + } + 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(); + // accumulate all the inter-router links in an object + // keyed by the svgs path id + for (const nodeId of Object.keys(nodeInfo)) { + let node = nodeInfo[nodeId]; + let nodeLinks = node['router.link']; + for (let n = 0; n < nodeLinks.results.length; n++) { + let link = self.traffic.QDRService.utilities.flatten(nodeLinks.attributeNames, nodeLinks.results[n]); + if (link.linkType !== 'router-control') { + let f = self.nodeIndexFor(self.traffic.topology.nodes, self.traffic.QDRService.management.topology.nameFromId(nodeId)); + let connection = self.findResult(node, 'connection', 'identity', link.connectionId); + if (connection) { + let t = self.nodeIndexFor(self.traffic.topology.nodes, connection.container); + let little = Math.min(f, t); + let big = Math.max(f, t); + let key = ['#path', little, big].join('-'); + if (!links[key]) { + links[key] = []; + } + links[key].push(link); + } + } + } + } + // accumulate the colors/directions to be used + let colors = {}; + for (const key of Object.keys(links)) { + let congestion = self.congestion(links[key]); + let path = d3.select(key); + 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 + .attr('stroke', congestion) + .classed('traffic', true) + .attr('marker-start', function (d) { + return d.left ? 'url(' + self.traffic.prefix + '#' + id + ')' : ''; + }) + .attr('marker-end', function (d) { + return d.right ? 'url(' + self.traffic.prefix + '#' + id + ')' : ''; + }); + } + } + // 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', 4) + .attr('markerHeight', 4) + .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); + } + return this.fillColor(v); + } + fillColor(v) { + let color = d3.scale.linear().domain([0, 1, 2, 3]) + .interpolate(d3.interpolateHcl) + .range([d3.rgb('#CCCCCC'), d3.rgb('#00FF00'), d3.rgb('#FFA500'), d3.rgb('#FF0000')]); + return color(v); + } + remove() { + d3.select('#SVG_ID').selectAll('path.traffic') + .classed('traffic', false); + d3.select('#SVG_ID').select('defs.custom-markers') + .selectAll('marker').remove(); + } +} + +/* Create animated dots moving along the links between routers + to show message flow */ +class Dots extends TrafficAnimation { + constructor(traffic, converter, radius) { + super(traffic); + 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.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 (const address of Object.keys(this.traffic.$scope.addresses)) { + this.fillColor(address); + } + }.bind(this)); + }.bind(this)); + // colors + this.colorGen = d3.scale.category10(); + let self = this; + // event notification that an address checkbox has changed + traffic.$scope.addressFilterChanged = function () { + self.updateAddresses(); + // 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) { + // 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 () { + self.unFadeAll(); + }; + // clicked on the address name. toggle the address checkbox + traffic.$scope.addressClick = function (address) { + self.toggleAddress(address); + self.updateAddresses(); + }; + } + remove() { + for (const id of Object.keys(this.lastFlows)) { + d3.select('#SVG_ID').selectAll('circle.flow' + id).remove(); + } + this.lastFlows = {}; + } + updateAddresses() { + this.excludedAddresses = []; + for (let address in this.traffic.$scope.addresses) { + if (!this.traffic.$scope.addresses[address]) { + this.excludedAddresses.push(address); + } + } + localStorage[CHORDFILTERKEY] = JSON.stringify(this.excludedAddresses); + if (this.chordData) { + this.chordData.setFilter(this.excludedAddresses); + } + } + toggleAddress(address) { + this.traffic.$scope.addresses[address] = !this.traffic.$scope.addresses[address]; + } + fadeOtherAddresses(address) { + d3.selectAll('circle.flow').classed('fade', function (d) { + return d.address !== address; + }); + } + unFadeAll() { + d3.selectAll('circle.flow').classed('fade', false); + } + doUpdate() { + let self = this; + // 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); + }); + }); + } + render(matrix) { + 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.75]); + // 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, matrix.rows[r].egress); + let t = this.nodeIndexFor(this.traffic.topology.nodes, matrix.rows[r].ingress); + let address = matrix.getAddress(r, c); + if (r !== c) { + // accumulate the hops between the ingress and egress routers + this.traffic.nextHop(this.traffic.topology.nodes[f], this.traffic.topology.nodes[t], 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, f, val, true, address); + this.addClients(hops, this.traffic.topology.nodes, t, val, false, address); + } + }.bind(this)); + }.bind(this)); + // for each link between routers that has traffic, start an animation + let keep = {}; + for (const id of Object.keys(hops)) { + let hop = hops[id]; + for (let h = 0; h < hop.length; h++) { + let ahop = hop[h]; + let flowId = id + '-' + this.addressIndex(this, ahop.address) + (ahop.back ? 'b' : ''); + let path = d3.select('#path' + id); + // 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; + } + } + // remove any existing animations that we don't have data for anymore + 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(); + } + } + } + // animate the d3 selection (flow) along the given path + animateFlow(flow, path, count, back, rate) { + let self = this; + let l = path.node().getTotalLength(); + flow.transition() + .ease('easeLinear') + .duration(l * 10 / rate) + .attrTween('transform', self.translateDots(self.radius, path, count, back)) + .each('end', function () { self.animateFlow(flow, path, count, back, rate); }); + } + // create dots along the path between routers + startAnimation(path, id, hop, rate) { + if (!path.node()) { + return; + } + this.animateDots(path, id, hop, rate); + } + animateDots(path, id, hop, rate) { + 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 = []; + for (let i = 0, offset = this.addressIndex(this, address); i < len; ++i) { + dots[i] = { i: i + 10 * offset, address: address }; + } + // 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; + } else { + if (this.lastFlows[id] !== len) { + this.lastFlows[id] = len; + 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 circles = flow + .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(); + } + fillColor(n) { + if (!(n in this.traffic.$scope.addressColors)) { + let ci = Object.keys(this.traffic.$scope.addressColors).length; + this.traffic.$scope.addressColors[n] = this.colorGen(ci); + } + return this.traffic.$scope.addressColors[n]; + } + addClients(hops, nodes, f, val, sender, address) { + let cdir = sender ? 'out' : 'in'; + for (let n = 0; n < nodes.length; n++) { + let node = nodes[n]; + if (node.normals && node.key === nodes[f].key && node.cdir === cdir) { + let key = ['', f, n].join('-'); + if (!hops[key]) { + hops[key] = []; + } + hops[key].push({ val: val, back: node.cdir === 'in', address: address }); + return; + } + } + } + addressIndex(vis, address) { + return Object.keys(vis.traffic.$scope.addresses).indexOf(address); + } + // calculate the translation for each dot along the path + 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) { + // will be called with t going from 0 to 1 for each dot + 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; + } + // 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 + ')'; + }; + }; + } +} + + http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0310ac22/console/stand-alone/tsconfig.json ---------------------------------------------------------------------- diff --git a/console/stand-alone/tsconfig.json b/console/stand-alone/tsconfig.json new file mode 100644 index 0000000..056e773 --- /dev/null +++ b/console/stand-alone/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "lib": [ + "dom", + "dom.iterable", + "es5", + "es6", + "es7", + "esnext", + "esnext.asynciterable", + "es2015.iterable", + "es2017" + ] + }, + "compileOnSave": true, + "include": [ + "plugin/*" + ], + "exclude": [ + "node_modules/*" + ], + "typeAcquisition": { + "enable": true + }, + "files": [ + "plugin/**/*.ts" + ] + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0310ac22/console/stand-alone/tslint.json ---------------------------------------------------------------------- diff --git a/console/stand-alone/tslint.json b/console/stand-alone/tslint.json new file mode 100644 index 0000000..ad645d3 --- /dev/null +++ b/console/stand-alone/tslint.json @@ -0,0 +1,64 @@ +/* +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. +*/ +{ + "rules": { + "class-name": true, + "curly": true, + "eofline": false, + "forin": true, + "indent": [true, 4], + "label-position": true, + "max-line-length": [true, 140], + "no-arg": true, + "no-bitwise": true, + "no-console": [true, + "debug", + "info", + "time", + "timeEnd", + "trace" + ], + "no-construct": true, + "no-debugger": true, + "no-duplicate-variable": true, + "no-empty": true, + "no-eval": true, + "no-string-literal": false, + "no-trailing-whitespace": true, + "no-unused-variable": false, + //"no-use-before-declare": true, + "one-line": [true, + "check-open-brace", + "check-catch", + "check-else", + "check-whitespace" + ], + "quotemark": [true, "single"], + "radix": true, + "semicolon": true, + "triple-equals": [true, "allow-null-check"], + "variable-name": false, + "whitespace": [true, + "check-branch", + "check-decl", + "check-operator", + "check-separator" + ] + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0310ac22/console/stand-alone/vendor-css.txt ---------------------------------------------------------------------- diff --git a/console/stand-alone/vendor-css.txt b/console/stand-alone/vendor-css.txt new file mode 100644 index 0000000..333e38c --- /dev/null +++ b/console/stand-alone/vendor-css.txt @@ -0,0 +1,25 @@ +- 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. + +-- The following files get packaged into a single vendor.min.css + +node_modules/jquery-ui-dist/jquery-ui.css +node_modules/patternfly/dist/css/patternfly.min.css +node_modules/patternfly/dist/css/patternfly-additions.min.css +node_modules/jquery.fancytree/dist/skin-bootstrap-n/ui.fancytree.css +node_modules/c3/c3.css +node_modules/angular-ui-grid/ui-grid.css http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/0310ac22/console/stand-alone/vendor-js.txt ---------------------------------------------------------------------- diff --git a/console/stand-alone/vendor-js.txt b/console/stand-alone/vendor-js.txt new file mode 100644 index 0000000..5af21f4 --- /dev/null +++ b/console/stand-alone/vendor-js.txt @@ -0,0 +1,43 @@ +- 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. + +-- The following files get packaged into a single vendor.min.js + +node_modules/bluebird/js/browser/bluebird.min.js +node_modules/jquery/dist/jquery.min.js +node_modules/jquery-ui-dist/jquery-ui.min.js +node_modules/jquery.fancytree/dist/jquery.fancytree-all.min.js +node_modules/angular/angular.min.js +node_modules/angular-animate/angular-animate.min.js +node_modules/angular-sanitize/angular-sanitize.min.js +node_modules/angular-route/angular-route.min.js +node_modules/angular-resource/angular-resource.min.js +node_modules/bootstrap/dist/js/bootstrap.min.js +node_modules/angular-ui-bootstrap/dist/ui-bootstrap.js +node_modules/angular-ui-bootstrap/dist/ui-bootstrap-tpls.js +node_modules/d3/d3.min.js +node_modules/d3-queue/build/d3-queue.min.js +node_modules/d3-time/build/d3-time.min.js +node_modules/d3-time-format/build/d3-time-format.min.js +node_modules/d3-path/build/d3-path.min.js +node_modules/c3/c3.min.js +node_modules/angular-ui-slider/src/slider.js +node_modules/angular-ui-grid/ui-grid.min.js +node_modules/angular-bootstrap-checkbox/angular-bootstrap-checkbox.js +node_modules/notifyjs-browser/dist/notify.js +node_modules/patternfly/dist/js/patternfly.min.js +node_modules/dispatch-management/dist/dispatch-management.min.js --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
