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/05/17 16:09:30 UTC

[5/6] qpid-dispatch git commit: DISPATCH-1000 Restore the html/css/js files that were in the npm repository

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfd3cf7c/console/stand-alone/plugin/html/tmplChartConfig.html
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/html/tmplChartConfig.html b/console/stand-alone/plugin/html/tmplChartConfig.html
new file mode 100644
index 0000000..4c9c727
--- /dev/null
+++ b/console/stand-alone/plugin/html/tmplChartConfig.html
@@ -0,0 +1,85 @@
+<!--
+ 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
+-->
+
+<!--
+    This is the template for the graph dialog that is displayed. It uses the
+    dialogCtrl controller in qdrCharts.js.
+-->
+<div class="chartOptions">
+    <div class="modal-header">
+        <h3 class="modal-title">Chart {{chart.attr() | humanify}}</h3>
+    </div>
+    <div class="modal-body">
+        <div id="{{svgDivId}}" class="line-chart-pf"></div>
+
+        <uib-tabset>
+            <uib-tab heading="Type">
+                <legend>Chart type</legend>
+                <div>
+                    <label><input type="radio" ng-model="dialogChart.type" value="value" /> Value Chart</label>
+                    <label><input type="radio" ng-model="dialogChart.type" value="rate" /> Rate Chart</label>
+<!--
+                    <div class="dlg-slider" ng-show="dialogChart.type=='rate'">
+                        <span>Rate Window: {{rateWindow}} second{{rateWindow > 1 ? "s" : ""}}</span>
+                        <div id="rateSlider"></div>
+                    </div>
+-->
+                </div>
+                <div style="clear:both;"> </div>
+            </uib-tab>
+<!--
+            <uib-tab ng-hide="$parent.chart.aggregate()" heading="Colors">
+                <legend>Chart colors</legend>
+                <div>
+                    <div class="colorPicker">
+                        <label>Area: <input id="areaColor" name="areaColor" type="color" /></label>
+                    </div>
+                </div>
+                <div style="clear:both;"> </div>
+            </uib-tab>
+-->
+            <uib-tab heading="Duration">
+                <legend>Chart duration</legend>
+                <div class="clearfix">
+                    <div class="dlg-slider duration">
+                        <span>Show data for past {{dialogChart.visibleDuration}} minute{{dialogChart.visibleDuration > 1 ? "s" : ""}}</span>
+                        <div id="durationSlider"></div>
+                    </div>
+                </div>
+            </uib-tab>
+        </uib-tabset>
+    </div>
+    <div class="modal-footer">
+        <div ng-hide="adding()">
+            <button class="btn btn-success" type="button" ng-click="apply()">Apply to existing chart</button>
+            <button class="btn btn-info" type="button" ng-click="copyToDashboard()">Create new chart</button>
+            <button class="btn btn-primary" type="button" ng-click="okClick()">Close</button>
+        </div>
+        <div ng-show="adding()">
+            <span ng-hide="isOnChartsPage()">
+                <button class="btn btn-success" type="button" ng-click="addChartsPage()"><i class="icon-bar-chart"></i> Add</button> this chart to the Charts page.
+            </span>
+            <span ng-show="isOnChartsPage()">
+                View the <button class="btn btn-success" type="button" ng-click="showChartsPage()"><i class="icon-bar-chart"></i> Charts</button> page.
+            </span>
+            <button class="btn btn-primary" type="button" ng-click="okClick()">Close</button>
+        </div>
+    </div>
+</div>
+

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfd3cf7c/console/stand-alone/plugin/html/tmplListChart.html
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/html/tmplListChart.html b/console/stand-alone/plugin/html/tmplListChart.html
new file mode 100644
index 0000000..3214187
--- /dev/null
+++ b/console/stand-alone/plugin/html/tmplListChart.html
@@ -0,0 +1,40 @@
+<!--
+ 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
+-->
+
+<!--
+    This is the template for the graph dialog that is displayed.
+-->
+<div class="modal-header">
+    <h3 class="modal-title">Chart {{chart.attr() | humanify}}</h3>
+</div>
+<div class="modal-body">
+    <p class="newChart">
+        <button ng-click="editChart()" title="Configure"><span class="fa-edit"></span></button>
+    </p><div style="clear:both"></div>
+    <div id="pfDialogChart" class="line-chart-pf"></div>
+</div>
+<div class="modal-footer">
+        <span ng-hide="isOnChartsPage()">
+            <button class="btn btn-success" type="button" ng-click="addChartsPage()"><i class="icon-bar-chart"></i> Add</button> this chart to the Charts page.
+        </span>
+        <span ng-show="isOnChartsPage()">
+            <button class="btn btn-danger" type="button" ng-click="delChartsPage()">Remove</button> this chart from the <button class="btn btn-success" type="button" ng-click="showChartsPage()"><i class="icon-bar-chart"></i> Charts</button> page.
+        </span>
+    <button class="btn btn-primary" type="button" ng-click="ok()">Close</button>
+</div>

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfd3cf7c/console/stand-alone/plugin/html/tmplListTree.html
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/html/tmplListTree.html b/console/stand-alone/plugin/html/tmplListTree.html
new file mode 100644
index 0000000..64797fa
--- /dev/null
+++ b/console/stand-alone/plugin/html/tmplListTree.html
@@ -0,0 +1,42 @@
+<!--
+ 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
+-->
+
+<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
+    <ng-include src="'listGrid.html'"></ng-include>
+</div>
+<div class="qdr-attributes col-sm-3 col-md-2 col-sm-pull-9 col-md-pull-10 sidebar-pf sidebar-pf-left">
+    <div class="pane-wrapper">
+        <div class="pane-header-wrapper">
+            <div class="tree-header"><select ng-options="node as node.name for node in nodes" ng-model="currentNode" ng-change="selectNode(currentNode)"></select></div>
+            <div ng-hide="largeNetwork" class="expand-collapse">
+                <i class="icon-chevron-down clickable" title="Expand all nodes" ng-click="expandAll()"></i>
+                <i class="icon-chevron-up clickable" title="Unexpand all nodes" ng-click="contractAll()"></i>
+            </div>
+        </div>
+        <div class="pane-viewport">
+            <div class="pane-content">
+                <div class="treeContainer">
+                    <div id="entityTree" onSelect="onTreeSelected" onRoot="onRootReady" hideRoot="true"></div>
+                    <div ng-init="treeReady()"></div>
+                </div>
+            </div>
+        </div>
+        <div class="pane-bar"></div>
+    </div>
+</div>

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfd3cf7c/console/stand-alone/plugin/html/tmplOverviewTree.html
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/html/tmplOverviewTree.html b/console/stand-alone/plugin/html/tmplOverviewTree.html
new file mode 100644
index 0000000..d3f6e80
--- /dev/null
+++ b/console/stand-alone/plugin/html/tmplOverviewTree.html
@@ -0,0 +1,48 @@
+<!--
+ 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
+-->
+<div class="col-xs-9">
+    <ng-include src="'overviewGrid.html'"></ng-include>
+</div>
+
+<button type='button' class='navbar-toggle tree-menu-button' data-toggle="collapse" data-target=".overview-tree">
+    <span class="sr-only">Toggle navigation</span>
+    <span class="icon-bar"></span>
+    <span class="icon-bar"></span>
+    <span class="icon-bar"></span>
+ </button>
+
+<div class="overview-tree navbar-collapse collapse col-xs-3">
+    <div class="pane-wrapper">
+        <div class="pane-header-wrapper">
+            <div ng-hide="largeNetwork" class="expand-collapse">
+                <i class="icon-chevron-down clickable" title="Expand all nodes" ng-click="expandAll()"></i>
+                <i class="icon-chevron-up clickable" title="Unexpand all nodes" ng-click="contractAll()"></i>
+            </div>
+        </div>
+        <div class="pane-viewport">
+            <div class="pane-content">
+                <div class="treeContainer ng-scope">
+                    <div id="overtree"></div>
+                </div>
+            </div>
+        </div>
+        <div class="pane-bar" ng-mousedown="startMoving($event)" ng-click="toggle()"></div>
+    </div>
+</div>
+<div ng-init="overviewLoaded()"></div>

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfd3cf7c/console/stand-alone/plugin/js/dlgChartController.js
----------------------------------------------------------------------
diff --git a/console/stand-alone/plugin/js/dlgChartController.js b/console/stand-alone/plugin/js/dlgChartController.js
new file mode 100644
index 0000000..e8c240f
--- /dev/null
+++ b/console/stand-alone/plugin/js/dlgChartController.js
@@ -0,0 +1,204 @@
+/*
+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 angular */
+/**
+ * @module QDR
+ */
+var QDR = (function(QDR) {
+
+  // controller for the edit/configure chart dialog
+  QDR.module.controller('QDR.ChartDialogController', function($scope, QDRChartService, $location, $uibModalInstance, chart, updateTick, dashboard, adding) {
+    let dialogSvgChart = null;
+    $scope.svgDivId = 'dialogEditChart';    // the div id for the svg chart
+
+    let updateTimer = null;
+    $scope.chart = chart;  // the underlying chart object from the dashboard
+    $scope.dialogChart = $scope.chart.copy(); // the chart object for this dialog
+    $scope.userTitle = $scope.chart.title();
+
+    $scope.$watch('userTitle', function(newValue, oldValue) {
+      if (newValue !== oldValue) {
+        $scope.dialogChart.title(newValue);
+        dialogSvgChart.tick($scope.svgDivId);
+      }
+    });
+    $scope.$watch('dialogChart.areaColor', function (newValue, oldValue) {
+      if (newValue !== oldValue) {
+        if (dialogSvgChart)
+          dialogSvgChart.tick($scope.svgDivId);
+      }
+    });
+    $scope.$watch('dialogChart.lineColor', function (newValue, oldValue) {
+      if (newValue !== oldValue) {
+        if (dialogSvgChart)
+          dialogSvgChart.tick($scope.svgDivId);
+      }
+    });
+    $scope.$watch('dialogChart.type', function (newValue, oldValue) {
+      if (newValue !== oldValue) {
+        if (dialogSvgChart) {
+          dialogSvgChart.chart.visibleDuration = newValue === 'rate' ? 0.25 : 1;
+          dialogSvgChart.tick($scope.svgDivId);
+        }
+      }
+    });
+
+    // the stored rateWindow is in milliseconds, but the slider is in seconds
+    $scope.rateWindow = $scope.chart.rateWindow / 1000;
+
+    $scope.addChartsPage = function () {
+      QDRChartService.addDashboard(dialogSvgChart.chart);
+    };
+    $scope.delChartsPage = function () {
+      QDRChartService.delDashboard($scope.chart);
+    };
+
+    $scope.showChartsPage = function () {
+      cleanup();
+      $uibModalInstance.close(true);
+      $location.path(QDR.pluginRoot + '/charts');
+    };
+
+    var cleanup = function () {
+      if (updateTimer) {
+        clearTimeout(updateTimer);
+        updateTimer = null;
+      }
+      if (!$scope.isOnChartsPage())
+        QDRChartService.unRegisterChart($scope.dialogChart);     // remove the chart
+    };
+    $scope.okClick = function () {
+      cleanup();
+      $uibModalInstance.close(true);
+    };
+
+    var initRateSlider = function () {
+      if (document.getElementById('rateSlider')) {
+        $( '#rateSlider' ).slider({
+          value: $scope.rateWindow,
+          min: 1,
+          max: 10,
+          step: 1,
+          slide: function( event, ui ) {
+            $scope.rateWindow = ui.value;
+            $scope.dialogChart.rateWindow = ui.value * 1000;
+            $scope.$apply();
+            if (dialogSvgChart)
+              dialogSvgChart.tick($scope.svgDivId);
+          }
+        });
+      } else {
+        setTimeout(initRateSlider, 100);
+      }
+    };
+    //initRateSlider();
+
+    var initDurationSlider = function () {
+      if (document.getElementById('durationSlider')) {
+        $( '#durationSlider' ).slider({
+          value: $scope.dialogChart.visibleDuration,
+          min: 0.25,
+          max: 10,
+          step: 0.25,
+          slide: function( event, ui ) {
+            $scope.visibleDuration = $scope.dialogChart.visibleDuration = ui.value;
+            $scope.$apply();
+            if (dialogSvgChart)
+              dialogSvgChart.tick($scope.svgDivId);
+          }
+        });
+      } else {
+        setTimeout(initDurationSlider, 100);
+      }
+    };
+    initDurationSlider();
+
+    $scope.adding = function () {
+      return adding;
+    };
+
+    $scope.isOnChartsPage = function () {
+      let chart = $scope.chart;
+      if (adding)
+        return QDRChartService.isAttrCharted(chart.nodeId(), chart.entity(), chart.name(), chart.attr(), chart.aggregate());
+      else
+        return $scope.chart.dashboard;
+    };
+
+    // handle the Apply button click
+    // update the dashboard chart's properties
+    $scope.apply = function () {
+      $scope.chart.areaColor = $scope.dialogChart.areaColor;
+      $scope.chart.lineColor = $scope.dialogChart.lineColor;
+      $scope.chart.type = $scope.dialogChart.type;
+      $scope.chart.rateWindow = $scope.rateWindow * 1000;
+      $scope.chart.title($scope.dialogChart.title());
+      $scope.chart.visibleDuration = $scope.dialogChart.visibleDuration;
+      QDRChartService.saveCharts();
+      if (typeof updateTick === 'function')
+        updateTick();
+    };
+
+    // add a new chart to the dashboard based on the current dialog settings
+    $scope.copyToDashboard = function () {
+      let chart = $scope.dialogChart.copy();
+      // set the new chart's dashboard state
+      QDRChartService.addDashboard(chart);
+      // notify the chart controller that it needs to display a new chart
+      dashboard.addChart(chart);
+    };
+
+    // update the chart on the popup dialog
+    var updateDialogChart = function () {
+      // draw the chart using the current data
+      if (dialogSvgChart)
+        dialogSvgChart.tick($scope.svgDivId);
+
+      // draw the chart again in 1 second
+      const updateRate = localStorage['updateRate'] ? localStorage['updateRate'] : 1000;
+      if (updateTimer)
+        clearTimeout(updateTimer);
+      updateTimer = setTimeout(updateDialogChart, updateRate);
+    };
+
+    var showChart = function () {
+      // ensure the div for our chart is loaded in the dom
+      let div = angular.element('#' + $scope.svgDivId);
+      if (!div.width()) {
+        setTimeout(showChart, 100);
+        return;
+      }
+      dialogSvgChart = new QDRChartService.pfAreaChart($scope.dialogChart, $scope.svgDivId);
+      /*
+      $('input[name=areaColor]').val($scope.dialogChart.areaColor);
+      $('input[name=areaColor]').on('input', function (e) {
+        $scope.dialogChart.areaColor = $(this).val();
+        updateDialogChart()
+      })
+*/
+      if (updateTimer)
+        clearTimeout(updateTimer);
+      updateDialogChart();
+    };
+    showChart();
+  });
+  return QDR;
+
+} (QDR || {}));

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfd3cf7c/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
new file mode 100644
index 0000000..574ea07
--- /dev/null
+++ b/console/stand-alone/plugin/js/qdrChartService.js
@@ -0,0 +1,859 @@
+/*
+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 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));
+        };
+      }
+
+      // 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);
+        }
+        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);
+        };
+      }
+
+      // 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);
+                }
+              }
+            }
+          }
+          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;
+
+            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();
+                      }
+                    }
+                  }
+                });
+              }
+            }
+          };
+          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);
+              }
+            });
+          }
+        },
+
+        // 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);
+        },
+
+        // 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);
+
+          // 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();
+        }
+      };
+      // 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'
+          };
+
+        }
+        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>');
+            }
+            table += '</tbody></table>';
+            return table;
+          }
+        };
+
+        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()]);
+        }
+        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));
+                }
+              } 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;
+        }
+
+        // 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 || {}));

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfd3cf7c/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
new file mode 100644
index 0000000..b296de7
--- /dev/null
+++ b/console/stand-alone/plugin/js/qdrCharts.js
@@ -0,0 +1,160 @@
+/*
+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 angular */
+
+/**
+ * @module QDR
+ */
+var QDR = (function (QDR) {
+
+  /**
+   * @method ChartsController
+   *
+   * Controller that handles the QDR charts page
+   */
+  QDR.module.controller('QDR.ChartsController', function($scope, QDRService, QDRChartService, $uibModal, $location, $routeParams, $timeout) {
+
+    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');
+      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);
+      svgChart.zoomed = false;
+      $scope.svgCharts.push(svgChart);
+    });
+
+
+    // redraw the chart every update period
+    var updateCharts = function () {
+      $scope.svgCharts.forEach(function (svgChart) {
+        svgChart.tick(svgChart.chart.id()); // on this page we are using the chart.id() as the div id in which to render the chart
+      });
+      const updateRate = localStorage['updateRate'] ?  localStorage['updateRate'] : 1000;
+      if (updateTimer) {
+        clearTimeout(updateTimer);
+      }
+      updateTimer = setTimeout(updateCharts, updateRate);
+    };
+
+    // called by ng-init in the html when the page is loaded
+    $scope.chartsLoaded = function () {
+      // ensure the div for our chart is loaded in the dom
+      let div = angular.element('.chartContainer');
+      if (!div.width()) {
+        setTimeout($scope.chartsLoaded, 100);
+        return;
+      }
+      // create an svg object for each chart
+      $scope.svgCharts.forEach ( function (c) {
+        c.generate(380);
+        QDRChartService.sendChartRequest(c.chart.request(), true);
+      });
+      if (updateTimer)
+        clearTimeout(updateTimer);
+      setTimeout(updateCharts);
+    };
+
+    $scope.zoomChart = function (chart) {
+      chart.zoomed = !chart.zoomed;
+      chart.zoom(chart.chart.id(), chart.zoomed);
+    };
+    $scope.showListPage = function () {
+      $location.path('/list');
+    };
+
+    $scope.hasCharts = function () {
+      return QDRChartService.numCharts() > 0;
+    };
+
+    $scope.editChart = function (chart) {
+      doDialog('tmplChartConfig.html', chart.chart);
+    };
+
+    $scope.delChart = function (chart) {
+      QDRChartService.unRegisterChart(chart.chart);
+      // remove from svgCharts
+      $scope.svgCharts.forEach(function (svgChart, i) {
+        if (svgChart === chart) {
+          delete $scope.svgCharts.splice(i, 1);
+        }
+      });
+    };
+
+    // 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);
+      $scope.svgCharts.push(nchart);
+      $timeout( function () {
+        nchart.generate();
+        QDRChartService.sendChartRequest(chart.request(), true);
+      });
+    };
+
+    $scope.$on('$destroy', function() {
+      if (updateTimer) {
+        clearTimeout(updateTimer);
+        updateTimer = null;
+      }
+      for (let i=$scope.svgCharts.length-1; i>=0; --i) {
+        delete $scope.svgCharts.splice(i, 1);
+      }
+    });
+
+    function doDialog(template, chart) {
+
+      $uibModal.open({
+        backdrop: true,
+        keyboard: true,
+        backdropClick: true,
+        templateUrl: QDR.templatePath + template,
+        controller: 'QDR.ChartDialogController',
+        resolve: {
+          chart: function() {
+            return chart;
+          },
+          updateTick: function () {
+            return function () { return updateCharts; };
+          },
+          dashboard: function () {
+            return $scope;
+          },
+          adding: function () {
+            return false;
+          }
+        }
+      });
+    }
+
+  });
+
+
+  return QDR;
+
+}(QDR || {}));
+

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/dfd3cf7c/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
new file mode 100644
index 0000000..af06c55
--- /dev/null
+++ b/console/stand-alone/plugin/js/qdrGlobals.js
@@ -0,0 +1,46 @@
+/*
+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';
+
+var QDR = (function(QDR) {
+
+  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;
+  })();
+
+  QDR.Core = {
+    notification: function (severity, msg) {
+      $.notify(msg, severity);
+    }
+  };
+
+  return QDR;
+
+} (QDR || {}));
\ No newline at end of file


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