You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by ea...@apache.org on 2018/06/25 21:26:51 UTC
[5/8] qpid-dispatch git commit: DISPATCH-1049 Add console tests
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/b5deb035/console/stand-alone/plugin/js/qdrChartService.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/qdrChartService.js b/console/stand-alone/plugin/js/qdrChartService.js
index 574ea07..fc53520 100644
--- a/console/stand-alone/plugin/js/qdrChartService.js
+++ b/console/stand-alone/plugin/js/qdrChartService.js
@@ -16,844 +16,825 @@ KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
-'use strict';
/* global angular d3 c3 */
-/**
- * @module QDR
- */
-var QDR = (function(QDR) {
-
- // The QDR chart service handles periodic gathering data for charts and displaying the charts
- QDR.module.factory('QDRChartService', ['QDRService',
- function(QDRService) {
-
- let instance = 0; // counter for chart instances
- let bases = [];
- var findBase = function(name, attr, request) {
- for (let i = 0; i < bases.length; ++i) {
- let base = bases[i];
- if (base.equals(name, attr, request))
- return base;
- }
- return null;
- };
-
- function ChartBase(name, attr, request) {
- // the base chart attributes
- this.name = name; // the record's "name" field
- this.attr = attr; // the record's attr field to chart
- this.request = request; // the associated request that fetches the data
-
- // copy the savable properties to an object
- this.copyProps = function(o) {
- o.name = this.name;
- o.attr = this.attr;
- this.request.copyProps(o);
- };
-
- this.equals = function(name, attr, request) {
- return (this.name == name && this.attr == attr && this.request.equals(request));
- };
+import { QDRLogger } from './qdrGlobals.js';
+
+let instance = 0,
+ bases = [];
+
+class ChartBase {
+ constructor(name, attr, request) {
+ // the base chart attributes
+ this.name = name; // the record's "name" field
+ this.attr = attr; // the record's attr field to chart
+ this.request = request; // the associated request that fetches the data
+ // copy the savable properties to an object
+ this.copyProps = function (o) {
+ o.name = this.name;
+ o.attr = this.attr;
+ this.request.copyProps(o);
+ };
+ this.equals = function (name, attr, request) {
+ return (this.name == name && this.attr == attr && this.request.equals(request));
+ };
+ }
+}
+
+// Object that represents a visible chart
+// There can be multiple of these per ChartBase (eg. one rate and one value chart)
+class Chart {
+ constructor(opts, request, QDRService, QDRChartService) {
+ var findBase = function (name, attr, request) {
+ for (let i = 0; i < bases.length; ++i) {
+ let base = bases[i];
+ if (base.equals(name, attr, request))
+ return base;
}
-
- // Object that represents a visible chart
- // There can be multiple of these per ChartBase (eg. one rate and one value chart)
- function Chart(opts, request) { //name, attr, cinstance, request) {
-
- let base = findBase(opts.name, opts.attr, request);
- if (!base) {
- base = new ChartBase(opts.name, opts.attr, request);
- bases.push(base);
+ return null;
+ };
+ let base = findBase(opts.name, opts.attr, request);
+ if (!base) {
+ base = new ChartBase(opts.name, opts.attr, request);
+ bases.push(base);
+ }
+ this.base = base;
+ this.QDRService = QDRService;
+ this.QDRChartService = QDRChartService;
+ this.instance = angular.isDefined(opts.instance) ? opts.instance : ++instance;
+ this.dashboard = false; // is this chart on the dashboard page
+ this.hdash = false; // is this chart on the hawtio dashboard page
+ this.hreq = false; // has this hdash chart been requested
+ this.type = opts.type ? opts.type : 'value'; // value or rate
+ this.rateWindow = opts.rateWindow ? opts.rateWindow : 1000; // calculate the rate of change over this time interval. higher == smother graph
+ this.areaColor = '#32b9f3'; // the chart's area color when not an empty string
+ this.lineColor = '#058dc7'; // the chart's line color when not an empty string
+ this.visibleDuration = opts.visibleDuration ? opts.visibleDuration : opts.type === 'rate' ? 0.25 : 1; // number of minutes of data to show (<= base.duration)
+ this.userTitle = null; // user title overrides title()
+ this.hideLabel = opts.hideLabel;
+ this.hideLegend = opts.hideLegend;
+ // generate a unique id for this chart
+ this.id = function () {
+ let name = this.name();
+ let nameparts = name.split('/');
+ if (nameparts.length == 2)
+ name = nameparts[1];
+ let key = this.QDRService.utilities.nameFromId(this.request().nodeId) + this.request().entity + name + this.attr() + '_' + this.instance + '_' + (this.request().aggregate ? '1' : '0');
+ // remove all characters except letters,numbers, and _
+ return key.replace(/[^\w]/gi, '');
+ };
+ // copy the savable properties to an object
+ this.copyProps = function (o) {
+ o.type = this.type;
+ o.rateWindow = this.rateWindow;
+ o.areaColor = this.areaColor;
+ o.lineColor = this.lineColor;
+ o.visibleDuration = this.visibleDuration;
+ o.userTitle = this.userTitle;
+ o.dashboard = this.dashboard;
+ o.hdash = this.hdash;
+ o.instance = this.instance;
+ this.base.copyProps(o);
+ };
+ this.name = function (_) {
+ if (!arguments.length)
+ return this.base.name;
+ this.base.name = _;
+ return this;
+ };
+ this.attr = function (_) {
+ if (!arguments.length)
+ return this.base.attr;
+ this.base.attr = _;
+ return this;
+ };
+ this.nodeId = function (_) {
+ if (!arguments.length)
+ return this.base.request.nodeId;
+ this.base.request.nodeId = _;
+ return this;
+ };
+ this.entity = function (_) {
+ if (!arguments.length)
+ return this.base.request.entity;
+ this.base.request.entity = _;
+ return this;
+ };
+ this.aggregate = function (_) {
+ if (!arguments.length)
+ return this.base.request.aggregate;
+ this.base.request.aggregate = _;
+ return this;
+ };
+ this.request = function (_) {
+ if (!arguments.length)
+ return this.base.request;
+ this.base.request = _;
+ return this;
+ };
+ this.data = function () {
+ return this.base.request.data(this.base.name, this.base.attr); // refernce to chart's data array
+ };
+ this.interval = function (_) {
+ if (!arguments.length)
+ return this.base.request.interval;
+ this.base.request.interval = _;
+ return this;
+ };
+ this.duration = function (_) {
+ if (!arguments.length)
+ return this.base.request.duration;
+ this.base.request.duration = _;
+ return this;
+ };
+ this.router = function () {
+ return this.QDRService.utilities.nameFromId(this.nodeId());
+ };
+ this.title = function (_) {
+ let name = this.request().aggregate ? 'Aggregate' : this.QDRService.utilities.nameFromId(this.nodeId());
+ let computed = name +
+ ' ' + this.QDRService.utilities.humanify(this.attr()) +
+ ' - ' + this.name();
+ if (!arguments.length)
+ return this.userTitle || computed;
+ // don't store computed title in userTitle
+ if (_ === computed)
+ _ = null;
+ this.userTitle = _;
+ return this;
+ };
+ this.title_short = function () {
+ if (!arguments.length)
+ return this.userTitle || this.name();
+ return this;
+ };
+ this.copy = function () {
+ let chart = this.QDRChartService.registerChart({
+ nodeId: this.nodeId(),
+ entity: this.entity(),
+ name: this.name(),
+ attr: this.attr(),
+ interval: this.interval(),
+ forceCreate: true,
+ aggregate: this.aggregate(),
+ hdash: this.hdash
+ });
+ chart.type = this.type;
+ chart.areaColor = this.areaColor;
+ chart.lineColor = this.lineColor;
+ chart.rateWindow = this.rateWindow;
+ chart.visibleDuration = this.visibleDuration;
+ chart.userTitle = this.userTitle;
+ return chart;
+ };
+ // compare to a chart
+ this.equals = function (c) {
+ return (c.instance == this.instance &&
+ c.base.equals(this.base.name, this.base.attr, this.base.request) &&
+ c.type == this.type &&
+ c.rateWindow == this.rateWindow &&
+ c.areaColor == this.areaColor &&
+ c.lineColor == this.lineColor);
+ };
+ }
+}
+
+
+// Object that represents the management request to fetch and store data for multiple charts
+class ChartRequest {
+ constructor(opts) {
+ this.duration = opts.duration || 10; // number of minutes to keep the data
+ this.nodeId = opts.nodeId; // eg amqp:/_topo/0/QDR.A/$management
+ this.entity = opts.entity; // eg .router.address
+ // sorted since the responses will always be sorted
+ this.aggregate = opts.aggregate; // list of nodeIds for aggregate charts
+ this.datum = {}; // object containing array of arrays for each attr
+ // like {attr1: [[date,value],[date,value]...], attr2: [[date,value]...]}
+ this.interval = opts.interval || 1000; // number of milliseconds between updates to data
+ this.setTimeoutHandle = null; // used to cancel the next request
+ // allow override of normal request's management call to get data
+ this.override = opts.override; // call this instead of internal function to retreive data
+ this.overrideAttrs = opts.overrideAttrs;
+ this.data = function (name, attr) {
+ if (this.datum[name] && this.datum[name][attr])
+ return this.datum[name][attr];
+ return null;
+ };
+ this.addAttrName = function (name, attr) {
+ if (Object.keys(this.datum).indexOf(name) == -1) {
+ this.datum[name] = {};
+ }
+ if (Object.keys(this.datum[name]).indexOf(attr) == -1) {
+ this.datum[name][attr] = [];
+ }
+ };
+ this.addAttrName(opts.name, opts.attr);
+ this.copyProps = function (o) {
+ o.nodeId = this.nodeId;
+ o.entity = this.entity;
+ o.interval = this.interval;
+ o.aggregate = this.aggregate;
+ o.duration = this.duration;
+ };
+ this.removeAttr = function (name, attr) {
+ if (this.datum[name]) {
+ if (this.datum[name][attr]) {
+ delete this.datum[name][attr];
}
- this.base = base;
- this.instance = angular.isDefined(opts.instance) ? opts.instance : ++instance;
- this.dashboard = false; // is this chart on the dashboard page
- this.hdash = false; // is this chart on the hawtio dashboard page
- this.hreq = false; // has this hdash chart been requested
- this.type = opts.type ? opts.type : 'value'; // value or rate
- this.rateWindow = opts.rateWindow ? opts.rateWindow : 1000; // calculate the rate of change over this time interval. higher == smother graph
- this.areaColor = '#32b9f3'; // the chart's area color when not an empty string
- this.lineColor = '#058dc7'; // the chart's line color when not an empty string
- this.visibleDuration = opts.visibleDuration ? opts.visibleDuration : opts.type === 'rate' ? 0.25 : 1; // number of minutes of data to show (<= base.duration)
- this.userTitle = null; // user title overrides title()
- this.hideLabel = opts.hideLabel;
- this.hideLegend = opts.hideLegend;
-
- // generate a unique id for this chart
- this.id = function() {
- let name = this.name();
- let nameparts = name.split('/');
- if (nameparts.length == 2)
- name = nameparts[1];
- let key = QDRService.management.topology.nameFromId(this.request().nodeId) + this.request().entity + name + this.attr() + '_' + this.instance + '_' + (this.request().aggregate ? '1' : '0');
- // remove all characters except letters,numbers, and _
- return key.replace(/[^\w]/gi, '');
- };
- // copy the savable properties to an object
- this.copyProps = function(o) {
- o.type = this.type;
- o.rateWindow = this.rateWindow;
- o.areaColor = this.areaColor;
- o.lineColor = this.lineColor;
- o.visibleDuration = this.visibleDuration;
- o.userTitle = this.userTitle;
- o.dashboard = this.dashboard;
- o.hdash = this.hdash;
- o.instance = this.instance;
- this.base.copyProps(o);
- };
- this.name = function(_) {
- if (!arguments.length) return this.base.name;
- this.base.name = _;
- return this;
- };
- this.attr = function(_) {
- if (!arguments.length) return this.base.attr;
- this.base.attr = _;
- return this;
- };
- this.nodeId = function(_) {
- if (!arguments.length) return this.base.request.nodeId;
- this.base.request.nodeId = _;
- return this;
- };
- this.entity = function(_) {
- if (!arguments.length) return this.base.request.entity;
- this.base.request.entity = _;
- return this;
- };
- this.aggregate = function(_) {
- if (!arguments.length) return this.base.request.aggregate;
- this.base.request.aggregate = _;
- return this;
- };
- this.request = function(_) {
- if (!arguments.length) return this.base.request;
- this.base.request = _;
- return this;
- };
- this.data = function() {
- return this.base.request.data(this.base.name, this.base.attr); // refernce to chart's data array
- };
- this.interval = function(_) {
- if (!arguments.length) return this.base.request.interval;
- this.base.request.interval = _;
- return this;
- };
- this.duration = function(_) {
- if (!arguments.length) return this.base.request.duration;
- this.base.request.duration = _;
- return this;
- };
- this.router = function () {
- return QDRService.management.topology.nameFromId(this.nodeId());
- };
- this.title = function(_) {
- let name = this.request().aggregate ? 'Aggregate' : QDRService.management.topology.nameFromId(this.nodeId());
- let computed = name +
- ' ' + QDRService.utilities.humanify(this.attr()) +
- ' - ' + this.name();
- if (!arguments.length) return this.userTitle || computed;
-
- // don't store computed title in userTitle
- if (_ === computed)
- _ = null;
- this.userTitle = _;
- return this;
- };
- this.title_short = function() {
- if (!arguments.length) return this.userTitle || this.name();
- return this;
- };
- this.copy = function() {
- let chart = self.registerChart({
- nodeId: this.nodeId(),
- entity: this.entity(),
- name: this.name(),
- attr: this.attr(),
- interval: this.interval(),
- forceCreate: true,
- aggregate: this.aggregate(),
- hdash: this.hdash
- });
- chart.type = this.type;
- chart.areaColor = this.areaColor;
- chart.lineColor = this.lineColor;
- chart.rateWindow = this.rateWindow;
- chart.visibleDuration = this.visibleDuration;
- chart.userTitle = this.userTitle;
- return chart;
- };
- // compare to a chart
- this.equals = function(c) {
- return (c.instance == this.instance &&
- c.base.equals(this.base.name, this.base.attr, this.base.request) &&
- c.type == this.type &&
- c.rateWindow == this.rateWindow &&
- c.areaColor == this.areaColor &&
- c.lineColor == this.lineColor);
- };
}
-
- // Object that represents the management request to fetch and store data for multiple charts
- function ChartRequest(opts) { //nodeId, entity, name, attr, interval, aggregate) {
- this.duration = opts.duration || 10; // number of minutes to keep the data
- this.nodeId = opts.nodeId; // eg amqp:/_topo/0/QDR.A/$management
- this.entity = opts.entity; // eg .router.address
- // sorted since the responses will always be sorted
- this.aggregate = opts.aggregate; // list of nodeIds for aggregate charts
- this.datum = {}; // object containing array of arrays for each attr
- // like {attr1: [[date,value],[date,value]...], attr2: [[date,value]...]}
-
- this.interval = opts.interval || 1000; // number of milliseconds between updates to data
- this.setTimeoutHandle = null; // used to cancel the next request
-
- // allow override of normal request's management call to get data
- this.override = opts.override; // call this instead of internal function to retreive data
- this.overrideAttrs = opts.overrideAttrs;
-
- this.data = function(name, attr) {
- if (this.datum[name] && this.datum[name][attr])
- return this.datum[name][attr];
- return null;
- };
- this.addAttrName = function(name, attr) {
- if (Object.keys(this.datum).indexOf(name) == -1) {
- this.datum[name] = {};
- }
- if (Object.keys(this.datum[name]).indexOf(attr) == -1) {
- this.datum[name][attr] = [];
- }
- };
- this.addAttrName(opts.name, opts.attr);
-
- this.copyProps = function(o) {
- o.nodeId = this.nodeId;
- o.entity = this.entity;
- o.interval = this.interval;
- o.aggregate = this.aggregate;
- o.duration = this.duration;
- };
-
- this.removeAttr = function(name, attr) {
- if (this.datum[name]) {
- if (this.datum[name][attr]) {
- delete this.datum[name][attr];
- }
- }
- return this.attrs().length;
- };
-
- this.equals = function(r, entity, aggregate) {
- if (arguments.length == 3) {
- let o = {
- nodeId: r,
- entity: entity,
- aggregate: aggregate
- };
- r = o;
- }
- return (this.nodeId === r.nodeId && this.entity === r.entity && this.aggregate == r.aggregate);
- };
- this.names = function() {
- return Object.keys(this.datum);
- };
- this.attrs = function() {
- let attrs = {};
- Object.keys(this.datum).forEach(function(name) {
- Object.keys(this.datum[name]).forEach(function(attr) {
- attrs[attr] = 1;
- });
- }, this);
- return Object.keys(attrs);
- };
+ return this.attrs().length;
+ };
+ this.equals = function (r, entity, aggregate) {
+ if (arguments.length == 3) {
+ let o = {
+ nodeId: r,
+ entity: entity,
+ aggregate: aggregate
+ };
+ r = o;
}
-
- // Below here are the properties and methods available on QDRChartService
- let self = {
- charts: [], // list of charts to gather data for
- chartRequests: [], // the management request info (multiple charts can be driven off of a single request
-
- init: function() {
- self.loadCharts();
- QDRService.management.connection.addDisconnectAction(function() {
- self.charts.forEach(function(chart) {
- self.unRegisterChart(chart, true);
- });
- QDRService.management.connection.addConnectAction(self.init);
- });
- },
-
- findChartRequest: function(nodeId, entity, aggregate) {
- let ret = null;
- self.chartRequests.some(function(request) {
- if (request.equals(nodeId, entity, aggregate)) {
- ret = request;
- return true;
- }
- });
- return ret;
- },
-
- findCharts: function(opts) { //name, attr, nodeId, entity, hdash) {
- if (!opts.hdash)
- opts.hdash = false; // rather than undefined
- return self.charts.filter(function(chart) {
- return (chart.name() == opts.name &&
- chart.attr() == opts.attr &&
- chart.nodeId() == opts.nodeId &&
- chart.entity() == opts.entity &&
- chart.hdash == opts.hdash);
- });
- },
-
- delChartRequest: function(request) {
- for (let i = 0; i < self.chartRequests.length; ++i) {
- let r = self.chartRequests[i];
- if (request.equals(r)) {
- QDR.log.debug('removed request: ' + request.nodeId + ' ' + request.entity);
- self.chartRequests.splice(i, 1);
- self.stopCollecting(request);
- return;
- }
- }
- },
-
- delChart: function(chart, skipSave) {
- let foundBases = 0;
- for (let i = 0; i < self.charts.length; ++i) {
- let c = self.charts[i];
- if (c.base === chart.base)
- ++foundBases;
- if (c.equals(chart)) {
- self.charts.splice(i, 1);
- if (chart.dashboard && !skipSave)
- self.saveCharts();
- }
- }
- if (foundBases == 1) {
- let baseIndex = bases.indexOf(chart.base);
- bases.splice(baseIndex, 1);
- }
- },
-
- createChart: function (opts, request) {
- return new Chart(opts, request);
- },
- createChartRequest: function (opts) {
- let request = new ChartRequest(opts); //nodeId, entity, name, attr, interval, aggregate);
- request.creationTimestamp = opts.now;
- self.chartRequests.push(request);
- self.startCollecting(request);
- self.sendChartRequest(request, true);
- return request;
- },
- destroyChartRequest: function (request) {
- self.stopCollecting(request);
- self.delChartRequest(request);
- },
-
- registerChart: function(opts) { //nodeId, entity, name, attr, interval, instance, forceCreate, aggregate, hdash) {
- let request = self.findChartRequest(opts.nodeId, opts.entity, opts.aggregate);
- if (request) {
- // add any new attr or name to the list
- request.addAttrName(opts.name, opts.attr);
- } else {
- // the nodeId/entity did not already exist, so add a new request and chart
- QDR.log.debug('added new request: ' + opts.nodeId + ' ' + opts.entity);
- request = self.createChartRequest(opts);
- }
- let charts = self.findCharts(opts); //name, attr, nodeId, entity, hdash);
- let chart;
- if (charts.length == 0 || opts.forceCreate) {
- if (!opts.use_instance && opts.instance)
- delete opts.instance;
- chart = new Chart(opts, request); //opts.name, opts.attr, opts.instance, request);
- self.charts.push(chart);
- } else {
- chart = charts[0];
- }
- return chart;
- },
-
- // remove the chart for name/attr
- // if all attrs are gone for this request, remove the request
- unRegisterChart: function(chart, skipSave) {
- // remove the chart
-
- // TODO: how do we remove charts that were added to the hawtio dashboard but then removed?
- // We don't get a notification that they were removed. Instead, we could just stop sending
- // the request in the background and only send the request when the chart's tick() event is triggered
- //if (chart.hdash) {
- // chart.dashboard = false;
- // self.saveCharts();
- // return;
- //}
-
- for (let i = 0; i < self.charts.length; ++i) {
- let c = self.charts[i];
- if (chart.equals(c)) {
- let request = chart.request();
- self.delChart(chart, skipSave);
- if (request) {
- // see if any other charts use this attr
- for (let j = 0; j < self.charts.length; ++j) {
- let ch = self.charts[j];
- if (ch.attr() == chart.attr() && ch.request().equals(chart.request()))
- return;
- }
- // no other charts use this attr, so remove it
- if (request.removeAttr(chart.name(), chart.attr()) == 0) {
- self.destroyChartRequest(request);
- }
- }
+ return (this.nodeId === r.nodeId && this.entity === r.entity && this.aggregate == r.aggregate);
+ };
+ this.names = function () {
+ return Object.keys(this.datum);
+ };
+ this.attrs = function () {
+ let attrs = {};
+ Object.keys(this.datum).forEach(function (name) {
+ Object.keys(this.datum[name]).forEach(function (attr) {
+ attrs[attr] = 1;
+ });
+ }, this);
+ return Object.keys(attrs);
+ };
+ }
+}
+
+class AreaChart {
+ constructor(chart, chartId, defer, width, QDRService) {
+ if (!chart)
+ return;
+ this.QDRService = QDRService;
+ // reference to underlying chart
+ this.chart = chart;
+ // if this is an aggregate chart, show it stacked
+ this.stacked = chart.request().aggregate;
+ // the id of the html element that is bound to the chart. The svg will be a child of this
+ this.htmlId = chartId;
+ this.colorMap = {};
+ if (!defer)
+ this.generate(width);
+ }
+
+ // create the svg and bind it to the given div.id
+ generate (width) {
+ let chart = this.chart; // for access during chart callbacks
+ let self = this;
+
+ // an array of 10 colors
+ let colors = d3.scale.category10().range();
+ // list of router names. used to get the color index
+ let nameList = this.QDRService.management.topology.nodeNameList();
+ for (let i=0; i<nameList.length; i++) {
+ this.colorMap[nameList[i]] = colors[i % 10];
+ }
+ let nodeName = this.QDRService.utilities.nameFromId(this.chart.base.request.nodeId);
+ this.colorMap[nodeName] = this.chart.areaColor;
+
+ let c3ChartDefaults = $().c3ChartDefaults();
+ let singleAreaChartConfig = c3ChartDefaults.getDefaultSingleAreaConfig();
+ singleAreaChartConfig.bindto = '#' + this.htmlId;
+ singleAreaChartConfig.size = {
+ width: width || 400,
+ height: 200
+ };
+ singleAreaChartConfig.data = {
+ x: 'x', // x-axis is named x
+ columns: [[]],
+ type: 'area-spline'
+ };
+ singleAreaChartConfig.axis = {
+ x: {
+ type: 'timeseries',
+ tick: {
+ format: (function (d) {
+ let data = this.singleAreaChart.data.shown();
+ let first = data[0]['values'][0].x;
+
+ if (d - first == 0) {
+ return d3.timeFormat('%I:%M:%S')(d);
}
- }
- if (!skipSave)
- self.saveCharts();
- },
-
- stopCollecting: function(request) {
- if (request.setTimeoutHandle) {
- clearInterval(request.setTimeoutHandle);
- request.setTimeoutHandle = null;
- }
- },
-
- startCollecting: function(request) {
- request.setTimeoutHandle = setInterval(self.sendChartRequest, request.interval, request);
- },
- shouldRequest: function() {
- // see if any of the charts associated with this request have either dialog, dashboard, or hreq
- return self.charts.some(function(chart) {
- return (chart.dashboard || chart.hreq) || (!chart.dashboard && !chart.hdash);
- });
- },
- // send the request
- sendChartRequest: function(request) {
- if (request.busy)
- return;
- if (self.charts.length > 0 && !self.shouldRequest(request)) {
- return;
- }
- // ensure the response has the name field so we can associate the response values with the correct chart
- let attrs = request.attrs();
- if (attrs.indexOf('name') == -1)
- attrs.push('name');
-
- // this is called when the response is received
- var saveResponse = function(nodeId, entity, response) {
- request.busy = false;
- if (!response || !response.attributeNames)
- return;
- //QDR.log.debug("got chart results for " + nodeId + " " + entity);
- // records is an array that has data for all names
- let records = response.results;
- if (!records)
- return;
+ return d3.timeFormat('%M:%S')(d);
+ }).bind(this),
+ culling: {max: 4}
+ }
+ },
+ y: {
+ tick: {
+ format: function (d) { return d<1 ? d3.format('.2f')(d) : d3.format('.2s')(d); },
+ count: 5
+ }
+ }
+ };
- let now = new Date();
- let cutOff = new Date(now.getTime() - request.duration * 60 * 1000);
- // index of the "name" attr in the response
- let nameIndex = response.attributeNames.indexOf('name');
- if (nameIndex < 0)
- return;
+ if (!chart.hideLabel) {
+ singleAreaChartConfig.axis.x.label = {
+ text: chart.name(),
+ position: 'outer-right'
+ };
- let names = request.names();
- // for each record returned, find the name/attr for this request and save the data with this timestamp
- for (let i = 0; i < records.length; ++i) {
- let name = records[i][nameIndex];
- // if we want to store the values for some attrs for this name
- if (names.indexOf(name) > -1) {
- attrs.forEach(function(attr) {
- let attrIndex = response.attributeNames.indexOf(attr);
- if (records[i][attrIndex] !== undefined) {
- let data = request.data(name, attr); // get a reference to the data array
- if (data) {
-
- if (request.aggregate) {
- data.push([now, response.aggregates[i][attrIndex].sum, response.aggregates[i][attrIndex].detail]);
- } else {
- data.push([now, records[i][attrIndex]]);
- }
- // expire the old data
- while (data[0][0] < cutOff) {
- data.shift();
- }
- }
- }
- });
- }
- }
- };
- request.busy = true;
- // check for override of request
- if (request.override) {
- request.override(request, saveResponse);
- } else {
- // send the appropriate request
- if (request.aggregate) {
- let nodeList = QDRService.management.topology.nodeIdList();
- QDRService.management.topology.getMultipleNodeInfo(nodeList, request.entity, attrs, saveResponse, request.nodeId);
- } else {
- QDRService.management.topology.fetchEntity(request.nodeId, request.entity, attrs, saveResponse);
- }
- }
- },
-
- numCharts: function() {
- return self.charts.filter(function(chart) {
- return chart.dashboard;
- }).length;
- //return self.charts.length;
- },
-
- isAttrCharted: function(nodeId, entity, name, attr, aggregate) {
- let charts = self.findCharts({
- name: name,
- attr: attr,
- nodeId: nodeId,
- entity: entity
- });
- // if any of the matching charts are on the dashboard page, return true
- return charts.some(function(chart) {
- return (chart.dashboard && (aggregate ? chart.aggregate() : !chart.aggregate()));
- });
- },
-
- addHDash: function(chart) {
- chart.hdash = true;
- self.saveCharts();
- },
- delHDash: function(chart) {
- chart.hdash = false;
- self.saveCharts();
- },
- addDashboard: function(chart) {
- chart.dashboard = true;
- self.saveCharts();
- },
- delDashboard: function(chart) {
- chart.dashboard = false;
- self.saveCharts();
- },
- // save the charts to local storage
- saveCharts: function() {
- let minCharts = [];
-
- self.charts.forEach(function(chart) {
- let minChart = {};
- // don't save chart unless it is on the dashboard
- if (chart.dashboard || chart.hdash) {
- chart.copyProps(minChart);
- minCharts.push(minChart);
- }
- });
- localStorage['QDRCharts'] = angular.toJson(minCharts);
- },
- loadCharts: function() {
- let charts = angular.fromJson(localStorage['QDRCharts']);
- if (charts) {
- // get array of known ids
- let nodeList = QDRService.management.topology.nodeIdList();
- charts.forEach(function(chart) {
- // if this chart is not in the current list of nodes, skip
- if (nodeList.indexOf(chart.nodeId) >= 0) {
- if (!angular.isDefined(chart.instance)) {
- chart.instance = ++instance;
- }
- if (chart.instance >= instance)
- instance = chart.instance + 1;
- if (!chart.duration)
- chart.duration = 1;
- if (chart.nodeList)
- chart.aggregate = true;
- if (!chart.hdash)
- chart.hdash = false;
- if (!chart.dashboard)
- chart.dashboard = false;
- if (!chart.hdash && !chart.dashboard)
- chart.dashboard = true;
- if (chart.hdash && chart.dashboard)
- chart.dashboard = false;
- chart.forceCreate = true;
- chart.use_instance = true;
- let newChart = self.registerChart(chart); //chart.nodeId, chart.entity, chart.name, chart.attr, chart.interval, true, chart.aggregate);
- newChart.dashboard = chart.dashboard;
- newChart.hdash = chart.hdash;
- newChart.hreq = false;
- newChart.type = chart.type;
- newChart.rateWindow = chart.rateWindow;
- newChart.areaColor = chart.areaColor ? chart.areaColor : '#32b9f3';
- newChart.lineColor = chart.lineColor ? chart.lineColor : '#058dc7';
- newChart.duration(chart.duration);
- newChart.visibleDuration = chart.visibleDuration ? chart.visibleDuration : newChart.type === 'rate' ? 0.25 : 1;
- if (chart.userTitle)
- newChart.title(chart.userTitle);
- }
- });
+ }
+ singleAreaChartConfig.transition = {
+ duration: 0
+ };
+
+ singleAreaChartConfig.area = {
+ zerobased: false
+ };
+
+ singleAreaChartConfig.tooltip = {
+ contents: function (d) {
+ let d3f = ',';
+ if (chart.type === 'rate')
+ d3f = ',.2f';
+ let zPre = function (i) {
+ if (i < 10) {
+ i = '0' + i;
}
- },
-
- // constructor for a c3 area chart
- pfAreaChart: function (chart, chartId, defer, width) {
- if (!chart)
- return;
-
- // reference to underlying chart
- this.chart = chart;
-
- // if this is an aggregate chart, show it stacked
- this.stacked = chart.request().aggregate;
-
- // the id of the html element that is bound to the chart. The svg will be a child of this
- this.htmlId = chartId;
-
- // an array of 20 colors
- this.colors = d3.scale.category10().range();
-
- if (!defer)
- this.generate(width);
- },
+ return i;
+ };
+ let h = zPre(d[0].x.getHours());
+ let m = zPre(d[0].x.getMinutes());
+ let s = zPre(d[0].x.getSeconds());
+ let table = '<table class=\'dispatch-c3-tooltip\'> <tr><th colspan=\'2\' class=\'text-center\'><strong>'+h+':'+m+':'+s+'</strong></th></tr> <tbody>';
+ for (let i=0; i<d.length; i++) {
+ let c = self.colorMap[d[i].id];
+ let span = `<span class='chart-tip-legend' style='background-color: ${c};'> </span>` + d[i].id;
+ table += ('<tr><td>'+span+'<td>'+d3.format(d3f)(d[i].value)+'</td></tr>');
+ }
+ table += '</tbody></table>';
+ return table;
+ }
+ };
- // aggregate chart is based on pfAreaChart
- pfAggChart: function (chart, chartId, defer) {
- // inherit pfChart's properties, but force a defer
- self.pfAreaChart.call(this, chart, chartId, true);
+ singleAreaChartConfig.title = {
+ text: this.QDRService.utilities.humanify(this.chart.attr())
+ };
- // the request is for aggregate data, but the chart is for the sum and not the detail
- // Explanation: When the chart.request is aggregate, each data point is composed of 3 parts:
- // 1. the datetime stamp
- // 2. the sum of the value for all routers
- // 3. an object with each router's name and value for this data point
- // Normally, an aggregate chart shows lines for each of the routers and ignores the sum
- // For this chart, we want to chart the sum (the 2nd value), so we set stacked to false
- this.stacked = false;
+ singleAreaChartConfig.data.color = (color, d) => {
+ let c = this.colorMap[d.id];
+ return c ? c : color;
+ };
+ singleAreaChartConfig.color.pattern[0] = this.chart.areaColor;
- // let chart legends and tooltips show 'Total' instead of a router name
- this.aggregate = true;
+ //singleAreaChartConfig.data.colors = {};
+ //nameList.forEach( (name, i) => singleAreaChartConfig.data.colors[name] = this.colorMap[name] );
+ singleAreaChartConfig.data.colors = this.colormap;
- if (!defer)
- this.generate();
- }
+ if (!chart.hideLegend) {
+ singleAreaChartConfig.legend = {
+ show: true,
};
- // allow pfAggChart to inherit prototyped methods
- self.pfAggChart.prototype = Object.create(self.pfAreaChart.prototype);
- // except for the constructor
- self.pfAggChart.prototype.constructor = self.pfAggChart;
-
- // create the svg and bind it to the given div.id
- self.pfAreaChart.prototype.generate = function (width) {
- let chart = this.chart; // for access during chart callbacks
- let self = this;
-
- // list of router names. used to get the color index
- let nameList = QDRService.management.topology.nodeNameList();
-
- let c3ChartDefaults = $().c3ChartDefaults();
- let singleAreaChartConfig = c3ChartDefaults.getDefaultSingleAreaConfig();
- singleAreaChartConfig.bindto = '#' + this.htmlId;
- singleAreaChartConfig.size = {
- width: width || 400,
- height: 200
- };
- singleAreaChartConfig.data = {
- x: 'x', // x-axis is named x
- columns: [[]],
- type: 'area-spline'
- };
- singleAreaChartConfig.axis = {
- x: {
- type: 'timeseries',
- tick: {
- format: (function (d) {
- let data = this.singleAreaChart.data.shown();
- let first = data[0]['values'][0].x;
-
- if (d - first == 0) {
- return d3.timeFormat('%I:%M:%S')(d);
- }
- return d3.timeFormat('%M:%S')(d);
- }).bind(this),
- culling: {max: 4}
- }
- },
- y: {
- tick: {
- format: function (d) { return d<1 ? d3.format('.2f')(d) : d3.format('.2s')(d); },
- count: 5
- }
- }
- };
+ }
- if (!chart.hideLabel) {
- singleAreaChartConfig.axis.x.label = {
- text: chart.name(),
- position: 'outer-right'
- };
+ if (this.stacked) {
+ // create a stacked area chart
+ singleAreaChartConfig.data.groups = [this.QDRService.management.topology.nodeNameList()];
+ singleAreaChartConfig.data.order = function (t1, t2) { return t1.id < t2.id; };
+ }
+ this.singleAreaChart = c3.generate(singleAreaChartConfig);
+ }
+
+ chartData() {
+ let data = this.chart.data();
+ let nodeList = this.QDRService.management.topology.nodeNameList();
+
+ // oldest data point that should be visible
+ let now = new Date();
+ let visibleDate = new Date(now.getTime() - this.chart.visibleDuration * 60 * 1000);
+
+ let accessorSingle = function (d, d1, elapsed) {
+ return this.chart.type === 'rate' ? (d1[1] - d[1]) / elapsed : d[1];
+ };
+ let accessorStacked = function (d, d1, elapsed, i) {
+ return this.chart.type === 'rate' ? (d1[2][i].val - d[2][i].val) / elapsed : d[2][i].val;
+ };
+ let accessor = this.stacked ? accessorStacked : accessorSingle;
+
+ let dx = ['x'];
+ let dlines = [];
+ if (this.stacked) {
+ // for stacked, there is a line per router
+ nodeList.forEach( function (node) {
+ dlines.push([node]);
+ });
+ } else {
+ // for non-stacked, there is only one line
+ dlines.push([this.aggregate ? 'Total' : this.chart.router()]);
+ }
+ for (let i=0; i<data.length; i++) {
+ let d = data[i], elapsed = 1, d1;
+ if (d[0] >= visibleDate) {
+ if (this.chart.type === 'rate' && i < data.length-1) {
+ d1 = data[i+1];
+ elapsed = Math.max((d1[0] - d[0]) / 1000, 0.001); // number of seconds that elapsed
}
- singleAreaChartConfig.transition = {
- duration: 0
- };
-
- singleAreaChartConfig.area = {
- zerobased: false
- };
-
- singleAreaChartConfig.tooltip = {
- contents: function (d) {
- let d3f = ',';
- if (chart.type === 'rate')
- d3f = ',.2f';
- let zPre = function (i) {
- if (i < 10) {
- i = '0' + i;
- }
- return i;
- };
- let h = zPre(d[0].x.getHours());
- let m = zPre(d[0].x.getMinutes());
- let s = zPre(d[0].x.getSeconds());
- let table = '<table class=\'dispatch-c3-tooltip\'> <tr><th colspan=\'2\' class=\'text-center\'><strong>'+h+':'+m+':'+s+'</strong></th></tr> <tbody>';
- for (let i=0; i<d.length; i++) {
- let colorIndex = nameList.indexOf(d[i].id) % 10;
- let span = '<span class=\'chart-tip-legend\' style=\'background-color: '+self.colors[colorIndex]+';\'> </span>' + d[i].id;
- table += ('<tr><td>'+span+'<td>'+d3.format(d3f)(d[i].value)+'</td></tr>');
+ // don't push the last data point for a rate chart
+ if (this.chart.type !== 'rate' || i < data.length-1) {
+ dx.push(d[0]);
+ if (this.stacked) {
+ for (let nodeIndex=0; nodeIndex<nodeList.length; nodeIndex++) {
+ dlines[nodeIndex].push(accessor.call(this, d, d1, elapsed, nodeIndex));
}
- table += '</tbody></table>';
- return table;
+ } else {
+ dlines[0].push(accessor.call(this, d, d1, elapsed));
}
- };
-
- singleAreaChartConfig.title = {
- text: QDRService.utilities.humanify(this.chart.attr())
- };
-
- singleAreaChartConfig.data.colors = {};
- nameList.forEach( (function (r, i) {
- singleAreaChartConfig.data.colors[r] = this.colors[i % 10];
- }).bind(this));
-
- singleAreaChartConfig.data.color = (function (color, d) {
- let i = nameList.indexOf(d);
- return i >= 0 ? this.colors[i % 10] : color;
- }).bind(this);
-
- if (!chart.hideLegend) {
- singleAreaChartConfig.legend = {
- show: true,
- };
}
-
- if (this.stacked) {
- // create a stacked area chart
- singleAreaChartConfig.data.groups = [QDRService.management.topology.nodeNameList()];
- singleAreaChartConfig.data.order = function (t1, t2) { return t1.id < t2.id; };
- }
-
- this.singleAreaChart = c3.generate(singleAreaChartConfig);
- };
-
- // filter/modify the chart.data into data points for the svg
- /* the collected data looks like:
- [[date, val, [v1,v2,...]], [date, val, [v1,v2,...]],...]
- with date being the timestamp of the sample
- val being the total value
- and the [v1,v2,...] array being the component values for each router for stacked charts
-
- for stacked charts, the returned data looks like:
- [['x', date, date,...},
- ['R1', v1, v1,...},
- ['R2', v2, v2,...],
- ...]
-
- for non-stacked charts, the returned data looks like:
- ['x', date, date,...],
- ['R1', val, val,...]]
-
- for rate charts, all the values returned are the change per second between adjacent values
- */
- self.pfAreaChart.prototype.chartData = function() {
- let data = this.chart.data();
- let nodeList = QDRService.management.topology.nodeNameList();
-
- // oldest data point that should be visible
- let now = new Date();
- let visibleDate = new Date(now.getTime() - this.chart.visibleDuration * 60 * 1000);
-
- let accessorSingle = function (d, d1, elapsed) {
- return this.chart.type === 'rate' ? (d1[1] - d[1]) / elapsed : d[1];
- };
- let accessorStacked = function (d, d1, elapsed, i) {
- return this.chart.type === 'rate' ? (d1[2][i].val - d[2][i].val) / elapsed : d[2][i].val;
- };
- let accessor = this.stacked ? accessorStacked : accessorSingle;
-
- let dx = ['x'];
- let dlines = [];
- if (this.stacked) {
- // for stacked, there is a line per router
- nodeList.forEach( function (node) {
- dlines.push([node]);
- });
- } else {
- // for non-stacked, there is only one line
- dlines.push([this.aggregate ? 'Total' : this.chart.router()]);
+ }
+ }
+ let columns = [dx];
+ dlines.forEach( function (line) {
+ columns.push(line);
+ });
+ return columns;
+ }
+
+ // get the data for the chart and update it
+ tick () {
+ // can't draw charts that don't have data yet
+ if (!this.chart.data() || this.chart.data().length == 0 || !this.singleAreaChart) {
+ return;
+ }
+ let nodeName = this.QDRService.utilities.nameFromId(this.chart.base.request.nodeId);
+ this.colorMap[nodeName] = this.chart.areaColor;
+ this.singleAreaChart.data.colors(this.colorMap);
+
+ // update the chart title
+ // since there is no c3 api to get or set the chart title, we change the title directly using d3
+ let rate = '';
+ if (this.chart.type === 'rate')
+ rate = ' per second';
+ d3.select('#'+this.htmlId+' svg text.c3-title').text(this.QDRService.utilities.humanify(this.chart.attr()) + rate);
+
+ let d = this.chartData();
+ // load the new data
+ // using the c3.flow api causes the x-axis labels to jump around
+ this.singleAreaChart.load({
+ columns: d
+ });
+ }
+
+}
+
+// aggregate chart is based on pfAreaChart
+class AggChart extends AreaChart {
+ constructor(chart, chartId, defer, QDRService) {
+ // inherit pfChart's properties, but force a defer
+ super(chart, chartId, true, undefined, QDRService);
+ // the request is for aggregate data, but the chart is for the sum and not the detail
+ // Explanation: When the chart.request is aggregate, each data point is composed of 3 parts:
+ // 1. the datetime stamp
+ // 2. the sum of the value for all routers
+ // 3. an object with each router's name and value for this data point
+ // Normally, an aggregate chart shows lines for each of the routers and ignores the sum
+ // For this chart, we want to chart the sum (the 2nd value), so we set stacked to false
+ this.stacked = false;
+ // let chart legends and tooltips show 'Total' instead of a router name
+ this.aggregate = true;
+ if (!defer)
+ this.generate();
+ }
+}
+
+
+export class QDRChartService {
+ constructor(QDRService, $log) {
+ this.charts = [];
+ this.chartRequests = [];
+ this.QDRService = QDRService;
+ this.QDRLog = new QDRLogger($log, 'QDRChartService');
+ }
+
+ // Example service function
+ init () {
+ let self = this;
+ this.loadCharts();
+ this.QDRService.management.connection.addDisconnectAction(function() {
+ self.charts.forEach(function(chart) {
+ self.unRegisterChart(chart, true);
+ });
+ self.QDRService.management.connection.addConnectAction(self.init);
+ });
+ }
+ findChartRequest (nodeId, entity, aggregate) {
+ let ret = null;
+ this.chartRequests.some(function(request) {
+ if (request.equals(nodeId, entity, aggregate)) {
+ ret = request;
+ return true;
+ }
+ });
+ return ret;
+ }
+ findCharts (opts) { //name, attr, nodeId, entity, hdash) {
+ if (!opts.hdash)
+ opts.hdash = false; // rather than undefined
+ return this.charts.filter(function(chart) {
+ return (chart.name() == opts.name &&
+ chart.attr() == opts.attr &&
+ chart.nodeId() == opts.nodeId &&
+ chart.entity() == opts.entity &&
+ chart.hdash == opts.hdash);
+ });
+ }
+
+ delChartRequest (request) {
+ for (let i = 0; i < this.chartRequests.length; ++i) {
+ let r = this.chartRequests[i];
+ if (request.equals(r)) {
+ this.QDRLog.debug('removed request: ' + request.nodeId + ' ' + request.entity);
+ this.chartRequests.splice(i, 1);
+ this.stopCollecting(request);
+ return;
+ }
+ }
+ }
+
+ delChart (chart, skipSave) {
+ let foundBases = 0;
+ for (let i = 0; i < this.charts.length; ++i) {
+ let c = this.charts[i];
+ if (c.base === chart.base)
+ ++foundBases;
+ if (c.equals(chart)) {
+ this.charts.splice(i, 1);
+ if (chart.dashboard && !skipSave)
+ this.saveCharts();
+ }
+ }
+ if (foundBases == 1) {
+ let baseIndex = bases.indexOf(chart.base);
+ bases.splice(baseIndex, 1);
+ }
+ }
+
+ createChart (opts, request) {
+ return new Chart(opts, request, this.QDRService, this);
+ }
+ createChartRequest (opts) {
+ let request = new ChartRequest(opts); //nodeId, entity, name, attr, interval, aggregate);
+ request.creationTimestamp = opts.now;
+ this.chartRequests.push(request);
+ this.startCollecting(request);
+ this.sendChartRequest(request);
+ return request;
+ }
+ destroyChartRequest (request) {
+ this.stopCollecting(request);
+ this.delChartRequest(request);
+ }
+
+ registerChart (opts) { //nodeId, entity, name, attr, interval, instance, forceCreate, aggregate, hdash) {
+ let request = this.findChartRequest(opts.nodeId, opts.entity, opts.aggregate);
+ if (request) {
+ // add any new attr or name to the list
+ request.addAttrName(opts.name, opts.attr);
+ } else {
+ // the nodeId/entity did not already exist, so add a new request and chart
+ this.QDRLog.debug('added new request: ' + opts.nodeId + ' ' + opts.entity);
+ request = this.createChartRequest(opts);
+ }
+ let charts = this.findCharts(opts); //name, attr, nodeId, entity, hdash);
+ let chart;
+ if (charts.length == 0 || opts.forceCreate) {
+ if (!opts.use_instance && opts.instance)
+ delete opts.instance;
+ chart = new Chart(opts, request, this.QDRService, this); //opts.name, opts.attr, opts.instance, request);
+ this.charts.push(chart);
+ } else {
+ chart = charts[0];
+ }
+ return chart;
+ }
+
+ // remove the chart for name/attr
+ // if all attrs are gone for this request, remove the request
+ unRegisterChart (chart, skipSave) {
+ // remove the chart
+ for (let i = 0; i < this.charts.length; ++i) {
+ let c = this.charts[i];
+ if (chart.equals(c)) {
+ let request = chart.request();
+ this.delChart(chart, skipSave);
+ if (request) {
+ // see if any other charts use this attr
+ for (let j = 0; j < this.charts.length; ++j) {
+ let ch = this.charts[j];
+ if (ch.attr() == chart.attr() && ch.request().equals(chart.request()))
+ return;
+ }
+ // no other charts use this attr, so remove it
+ if (request.removeAttr(chart.name(), chart.attr()) == 0) {
+ this.destroyChartRequest(request);
+ }
}
- for (let i=0; i<data.length; i++) {
- let d = data[i], elapsed = 1, d1;
- if (d[0] >= visibleDate) {
- if (this.chart.type === 'rate' && i < data.length-1) {
- d1 = data[i+1];
- elapsed = Math.max((d1[0] - d[0]) / 1000, 0.001); // number of seconds that elapsed
- }
- // don't push the last data point for a rate chart
- if (this.chart.type !== 'rate' || i < data.length-1) {
- dx.push(d[0]);
- if (this.stacked) {
- for (let nodeIndex=0; nodeIndex<nodeList.length; nodeIndex++) {
- dlines[nodeIndex].push(accessor.call(this, d, d1, elapsed, nodeIndex));
+ }
+ }
+ if (!skipSave)
+ this.saveCharts();
+ }
+
+ stopCollecting (request) {
+ if (request.setTimeoutHandle) {
+ clearInterval(request.setTimeoutHandle);
+ request.setTimeoutHandle = null;
+ }
+ }
+
+ startCollecting (request) {
+ request.setTimeoutHandle = setInterval(this.sendChartRequest.bind(this), request.interval, request);
+ }
+ shouldRequest () {
+ // see if any of the charts associated with this request have either dialog, dashboard, or hreq
+ return this.charts.some(function(chart) {
+ return (chart.dashboard || chart.hreq) || (!chart.dashboard && !chart.hdash);
+ });
+ }
+ // send the request
+ sendChartRequest (request) {
+ if (request.busy)
+ return;
+ if (this.charts.length > 0 && !this.shouldRequest(request)) {
+ return;
+ }
+ // ensure the response has the name field so we can associate the response values with the correct chart
+ let attrs = request.attrs();
+ if (attrs.indexOf('name') == -1)
+ attrs.push('name');
+
+ // this is called when the response is received
+ var saveResponse = function(nodeId, entity, response) {
+ request.busy = false;
+ if (!response || !response.attributeNames)
+ return;
+ // records is an array that has data for all names
+ let records = response.results;
+ if (!records)
+ return;
+
+ let now = new Date();
+ let cutOff = new Date(now.getTime() - request.duration * 60 * 1000);
+ // index of the "name" attr in the response
+ let nameIndex = response.attributeNames.indexOf('name');
+ if (nameIndex < 0)
+ return;
+
+ let names = request.names();
+ // for each record returned, find the name/attr for this request and save the data with this timestamp
+ for (let i = 0; i < records.length; ++i) {
+ let name = records[i][nameIndex];
+ // if we want to store the values for some attrs for this name
+ if (names.indexOf(name) > -1) {
+ attrs.forEach(function(attr) {
+ let attrIndex = response.attributeNames.indexOf(attr);
+ if (records[i][attrIndex] !== undefined) {
+ let data = request.data(name, attr); // get a reference to the data array
+ if (data) {
+
+ if (request.aggregate) {
+ data.push([now, response.aggregates[i][attrIndex].sum, response.aggregates[i][attrIndex].detail]);
+ } else {
+ data.push([now, records[i][attrIndex]]);
+ }
+ // expire the old data
+ while (data[0][0] < cutOff) {
+ data.shift();
}
- } else {
- dlines[0].push(accessor.call(this, d, d1, elapsed));
}
}
- }
+ });
}
- let columns = [dx];
- dlines.forEach( function (line) {
- columns.push(line);
- });
- return columns;
- };
-
- // get the data for the chart and update it
- self.pfAreaChart.prototype.tick = function() {
- // can't draw charts that don't have data yet
- if (!this.chart.data() || this.chart.data().length == 0 || !this.singleAreaChart) {
- return;
+ }
+ };
+ request.busy = true;
+ // check for override of request
+ if (request.override) {
+ request.override(request, saveResponse);
+ } else {
+ // send the appropriate request
+ if (request.aggregate) {
+ let nodeList = this.QDRService.management.topology.nodeIdList();
+ this.QDRService.management.topology.getMultipleNodeInfo(nodeList, request.entity, attrs, saveResponse, request.nodeId);
+ } else {
+ this.QDRService.management.topology.fetchEntity(request.nodeId, request.entity, attrs, saveResponse);
+ }
+ }
+ }
+
+ numCharts () {
+ return this.charts.filter(function(chart) {
+ return chart.dashboard;
+ }).length;
+ //return this.charts.length;
+ }
+
+ isAttrCharted (nodeId, entity, name, attr, aggregate) {
+ let charts = this.findCharts({
+ name: name,
+ attr: attr,
+ nodeId: nodeId,
+ entity: entity
+ });
+ // if any of the matching charts are on the dashboard page, return true
+ return charts.some(function(chart) {
+ return (chart.dashboard && (aggregate ? chart.aggregate() : !chart.aggregate()));
+ });
+ }
+
+ addHDash (chart) {
+ chart.hdash = true;
+ this.saveCharts();
+ }
+ delHDash (chart) {
+ chart.hdash = false;
+ this.saveCharts();
+ }
+ addDashboard (chart) {
+ chart.dashboard = true;
+ this.saveCharts();
+ }
+ delDashboard (chart) {
+ chart.dashboard = false;
+ this.saveCharts();
+ }
+ // save the charts to local storage
+ saveCharts () {
+ let minCharts = [];
+
+ this.charts.forEach(function(chart) {
+ let minChart = {};
+ // don't save chart unless it is on the dashboard
+ if (chart.dashboard || chart.hdash) {
+ chart.copyProps(minChart);
+ minCharts.push(minChart);
+ }
+ });
+ localStorage['QDRCharts'] = angular.toJson(minCharts);
+ }
+ loadCharts () {
+ let charts = angular.fromJson(localStorage['QDRCharts']);
+ if (charts) {
+ let self = this;
+ // get array of known ids
+ let nodeList = this.QDRService.management.topology.nodeIdList();
+ charts.forEach(function(chart) {
+ // if this chart is not in the current list of nodes, skip
+ if (nodeList.indexOf(chart.nodeId) >= 0) {
+ if (!angular.isDefined(chart.instance)) {
+ chart.instance = ++instance;
+ }
+ if (chart.instance >= instance)
+ instance = chart.instance + 1;
+ if (!chart.duration)
+ chart.duration = 1;
+ if (chart.nodeList)
+ chart.aggregate = true;
+ if (!chart.hdash)
+ chart.hdash = false;
+ if (!chart.dashboard)
+ chart.dashboard = false;
+ if (!chart.hdash && !chart.dashboard)
+ chart.dashboard = true;
+ if (chart.hdash && chart.dashboard)
+ chart.dashboard = false;
+ chart.forceCreate = true;
+ chart.use_instance = true;
+ let newChart = self.registerChart(chart); //chart.nodeId, chart.entity, chart.name, chart.attr, chart.interval, true, chart.aggregate);
+ newChart.dashboard = chart.dashboard;
+ newChart.hdash = chart.hdash;
+ newChart.hreq = false;
+ newChart.type = chart.type;
+ newChart.rateWindow = chart.rateWindow;
+ newChart.areaColor = chart.areaColor ? chart.areaColor : '#32b9f3';
+ newChart.lineColor = chart.lineColor ? chart.lineColor : '#058dc7';
+ newChart.duration(chart.duration);
+ newChart.visibleDuration = chart.visibleDuration ? chart.visibleDuration : newChart.type === 'rate' ? 0.25 : 1;
+ if (chart.userTitle)
+ newChart.title(chart.userTitle);
}
-
- // update the chart title
- // since there is no c3 api to get or set the chart title, we change the title directly using d3
- let rate = '';
- if (this.chart.type === 'rate')
- rate = ' per second';
- d3.select('#'+this.htmlId+' svg text.c3-title').text(QDRService.utilities.humanify(this.chart.attr()) + rate);
-
- let d = this.chartData();
- // load the new data
- // using the c3.flow api causes the x-axis labels to jump around
- this.singleAreaChart.load({
- columns: d
- });
- };
-
- return self;
+ });
}
- ]);
-
- return QDR;
-}(QDR || {}));
+ }
+ // constructor for a c3 area chart
+ pfAreaChart (chart, chartId, defer, width) {
+ return new AreaChart(chart, chartId, defer, width, this.QDRService);
+ }
+
+ // aggregate chart is based on pfAreaChart
+ pfAggChart (chart, chartId, defer) {
+ return new AggChart(chart, chartId, defer, this.QDRService);
+ }
+}
+
+QDRChartService.$inject = ['QDRService', '$log'];
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/b5deb035/console/stand-alone/plugin/js/qdrCharts.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/qdrCharts.js b/console/stand-alone/plugin/js/qdrCharts.js
index b296de7..0cffe4e 100644
--- a/console/stand-alone/plugin/js/qdrCharts.js
+++ b/console/stand-alone/plugin/js/qdrCharts.js
@@ -16,34 +16,27 @@ KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
-'use strict';
/* global angular */
-/**
- * @module QDR
- */
-var QDR = (function (QDR) {
+import { QDRTemplatePath, QDRRedirectWhenConnected } from './qdrGlobals.js';
- /**
- * @method ChartsController
- *
- * Controller that handles the QDR charts page
- */
- QDR.module.controller('QDR.ChartsController', function($scope, QDRService, QDRChartService, $uibModal, $location, $routeParams, $timeout) {
+export class ChartsController {
+ constructor(QDRService, QDRChartService, $scope, $location, $timeout, $routeParams, $uibModal) {
+ this.controllerName = 'QDR.ChartsController';
let updateTimer = null;
if (!QDRService.management.connection.is_connected()) {
// we are not connected. we probably got here from a bookmark or manual page reload
- QDR.redirectWhenConnected($location, 'charts');
+ QDRRedirectWhenConnected($location, 'charts');
return;
}
$scope.svgCharts = [];
// create an svg object for each chart
QDRChartService.charts.filter(function (chart) {return chart.dashboard;}).forEach(function (chart) {
- let svgChart = new QDRChartService.pfAreaChart(chart, chart.id(), true);
+ let svgChart = QDRChartService.pfAreaChart(chart, chart.id(), true);
svgChart.zoomed = false;
$scope.svgCharts.push(svgChart);
});
@@ -108,7 +101,7 @@ var QDR = (function (QDR) {
// called from dialog when we want to clone the dialog chart
// the chart argument here is a QDRChartService chart
$scope.addChart = function (chart) {
- let nchart = new QDRChartService.pfAreaChart(chart, chart.id(), true);
+ let nchart = QDRChartService.pfAreaChart(chart, chart.id(), true);
$scope.svgCharts.push(nchart);
$timeout( function () {
nchart.generate();
@@ -132,7 +125,7 @@ var QDR = (function (QDR) {
backdrop: true,
keyboard: true,
backdropClick: true,
- templateUrl: QDR.templatePath + template,
+ templateUrl: QDRTemplatePath + template,
controller: 'QDR.ChartDialogController',
resolve: {
chart: function() {
@@ -150,11 +143,7 @@ var QDR = (function (QDR) {
}
});
}
-
- });
-
-
- return QDR;
-
-}(QDR || {}));
+ }
+}
+ChartsController.$inject = ['QDRService', 'QDRChartService', '$scope', '$location', '$timeout', '$routeParams', '$uibModal'];
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/b5deb035/console/stand-alone/plugin/js/qdrGlobals.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/qdrGlobals.js b/console/stand-alone/plugin/js/qdrGlobals.js
index af06c55..6fadf14 100644
--- a/console/stand-alone/plugin/js/qdrGlobals.js
+++ b/console/stand-alone/plugin/js/qdrGlobals.js
@@ -16,31 +16,43 @@ KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
-'use strict';
-var QDR = (function(QDR) {
+export var QDRFolder = (function () {
+ function Folder(title) {
+ this.title = title;
+ this.children = [];
+ this.folder = true;
+ }
+ return Folder;
+})();
+export var QDRLeaf = (function () {
+ function Leaf(title) {
+ this.title = title;
+ }
+ return Leaf;
+})();
- QDR.Folder = (function () {
- function Folder(title) {
- this.title = title;
- this.children = [];
- this.folder = true;
- }
- return Folder;
- })();
- QDR.Leaf = (function () {
- function Leaf(title) {
- this.title = title;
- }
- return Leaf;
- })();
+export var QDRCore = {
+ notification: function (severity, msg) {
+ $.notify(msg, severity);
+ }
+};
- QDR.Core = {
- notification: function (severity, msg) {
- $.notify(msg, severity);
- }
- };
+export class QDRLogger {
+ constructor($log, source) {
+ this.log = function (msg) { $log.log(`QDR-${source}: ${msg}`); };
+ this.debug = function (msg) { $log.debug(`QDR-${source}: ${msg}`); };
+ this.error = function (msg) { $log.error(`QDR-${source}: ${msg}`); };
+ this.info = function (msg) { $log.info(`QDR-${source}: ${msg}`); };
+ this.warn = function (msg) { $log.warn(`QDR-${source}: ${msg}`); };
+ }
+}
- return QDR;
+export const QDRTemplatePath = 'html/';
+export const QDR_SETTINGS_KEY = 'QDRSettings';
+export const QDR_LAST_LOCATION = 'QDRLastLocation';
-} (QDR || {}));
\ No newline at end of file
+export var QDRRedirectWhenConnected = function ($location, org) {
+ $location.path('/connect');
+ $location.search('org', org);
+};
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org