http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/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..e52a39a --- /dev/null +++ b/console/stand-alone/plugin/html/tmplListChart.html @@ -0,0 +1,18 @@ +<!-- + 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"> + <div id="{{svgDivId}}" class="d3Chart"></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> \ No newline at end of file
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/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..9aa8be5 --- /dev/null +++ b/console/stand-alone/plugin/html/tmplListTree.html @@ -0,0 +1,17 @@ +<div class="qdr-attributes pane left" position="left" width="300"> + <div class="pane-wrapper"> + <div class="pane-header-wrapper"> + </div> + <div class="pane-viewport"> + <div class="pane-content"> + <div class="treeContainer"> + <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 id="entityTree" onSelect="onTreeSelected" onRoot="onRootReady" hideRoot="true"></div> + <div ng-init="treeReady()"></div> + </div> + </div> + </div> + <div class="pane-bar"></div> + </div> +</div> +<ng-include src="'listGrid.html'"></ng-include> http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/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..4346b9c --- /dev/null +++ b/console/stand-alone/plugin/html/tmplOverviewTree.html @@ -0,0 +1,15 @@ +<div class="qdr-overview pane left" position="left" width="300"> + <div class="pane-wrapper"> + <div class="pane-header-wrapper"> + </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> +<ng-include src="'overviewGrid.html'"></ng-include> http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/stand-alone/plugin/js/dispatchPlugin.js ---------------------------------------------------------------------- diff --git a/console/stand-alone/plugin/js/dispatchPlugin.js b/console/stand-alone/plugin/js/dispatchPlugin.js new file mode 100644 index 0000000..e8078e9 --- /dev/null +++ b/console/stand-alone/plugin/js/dispatchPlugin.js @@ -0,0 +1,254 @@ +/* +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +*/ +/** + * @module QDR + * @main QDR + * + * The main entry point for the QDR module + * + */ +var QDR = (function(QDR) { + + /** + * @property pluginName + * @type {string} + * + * The name of this plugin + */ + QDR.pluginName = "QDR"; + QDR.pluginRoot = ""; + QDR.isStandalone = true; + + /** + * @property log + * @type {Logging.Logger} + * + * This plugin's logger instance + */ + //HIO QDR.log = Logger.get(QDR.pluginName); + /** + * @property templatePath + * @type {string} + * + * The top level path to this plugin's partials + */ + QDR.srcBase = "plugin/"; + QDR.templatePath = QDR.srcBase + "html/"; + QDR.cssPath = QDR.srcBase + "css/"; + /** + * @property SETTINGS_KEY + * @type {string} + * + * The key used to fetch our settings from local storage + */ + QDR.SETTINGS_KEY = 'QDRSettings'; + QDR.LAST_LOCATION = "QDRLastLocation"; + + /** + * @property module + * @type {object} + * + * This plugin's angularjs module instance + */ + QDR.module = angular.module(QDR.pluginName, ['ngResource', 'ngGrid', 'ui.bootstrap', 'ui.slider'/*, 'minicolors' */]); + + // set up the routing for this plugin + QDR.module.config(function($routeProvider) { + $routeProvider + .when('/', { + templateUrl: QDR.templatePath + 'qdrConnect.html' + }) + .when('/overview', { + templateUrl: QDR.templatePath + 'qdrOverview.html' + }) + .when('/topology', { + templateUrl: QDR.templatePath + 'qdrTopology.html' + }) + .when('/list', { + templateUrl: QDR.templatePath + 'qdrList.html' + }) + .when('/schema', { + templateUrl: QDR.templatePath + 'qdrSchema.html' + }) + .when('/charts', { + templateUrl: QDR.templatePath + 'qdrCharts.html' + }) + .when('/connect', { + templateUrl: QDR.templatePath + 'qdrConnect.html' + }) + .otherwise({ + templateUrl: QDR.templatePath + 'qdrConnect.html' + }) + }); + + QDR.module.config(function ($compileProvider) { + var cur = $compileProvider.urlSanitizationWhitelist(); + $compileProvider.urlSanitizationWhitelist(/^\s*(https?|ftp|mailto|file|blob):/); + cur = $compileProvider.urlSanitizationWhitelist(); + }) + + QDR.module.filter('to_trusted', ['$sce', function($sce){ + return function(text) { + return $sce.trustAsHtml(text); + }; + }]); + + QDR.module.filter('humanify', function (QDRService) { + return function (input) { + return QDRService.humanify(input); + }; + }); + + QDR.module.filter('Pascalcase', function () { + return function (str) { + if (!str) + return ""; + return str.replace(/(\w)(\w*)/g, + function(g0,g1,g2){return g1.toUpperCase() + g2.toLowerCase();}); + } + }) + + QDR.module.filter('safePlural', function () { + return function (str) { + var es = ['x', 'ch', 'ss', 'sh'] + for (var i=0; i<es.length; ++i) { + if (str.endsWith(es[i])) + return str + 'es' + } + if (str.endsWith('y')) + return str.substr(0, str.length-2) + 'ies' + if (str.endsWith('s')) + return str; + return str + 's' + } + }) + + QDR.logger = function ($log) { + var log = $log; + + this.debug = function (msg) { msg = "QDR: " + msg; log.debug(msg)}; + this.error = function (msg) {msg = "QDR: " + msg; log.error(msg)} + this.info = function (msg) {msg = "QDR: " + msg; log.info(msg)} + this.warn = function (msg) {msg = "QDR: " + msg; log.warn(msg)} + + return this; + } + // one-time initialization happens in the run function + // of our module + QDR.module.run( ["$rootScope", '$route', '$timeout', "$location", "$log", "QDRService", "QDRChartService", function ($rootScope, $route, $timeout, $location, $log, QDRService, QDRChartService) { + QDR.log = new QDR.logger($log); + QDR.log.info("*************creating Dispatch Console************"); + var curPath = $location.path() + var org = curPath.substr(1) + if (org && org.length > 0 && org !== "connect") { + $location.search('org', org) + } else { + $location.search('org', null) + } + + QDRService.initProton(); + var settings = angular.fromJson(localStorage[QDR.SETTINGS_KEY]); + QDRService.addConnectAction(function() { + QDRChartService.init(); // initialize charting service after we are connected + }); + if (settings && settings.autostart) { + QDRService.addDisconnectAction( function () { + $timeout(function () { + var lastLocation = localStorage[QDR.LAST_LOCATION] || "/overview"; + org = lastLocation.substr(1) + $location.path("/connect"); + $location.search('org', org) + }) + }) + QDRService.addConnectAction(function() { + var searchObject = $location.search(); + // the redirect will be handled by QDRService when connected + if (searchObject.org) { + return; + } + // there was no org= parameter, so redirect to last known location + $timeout(function () { + var lastLocation = localStorage[QDR.LAST_LOCATION] || "/overview"; + $location.path(lastLocation); + }) + }); + QDRService.connect(settings); + } else { + $timeout(function () { + $location.path('/connect') + $location.search('org', org) + }) + } + + $rootScope.$on('$routeChangeSuccess', function() { + var path = $location.path(); + if (path !== "/connect") { + localStorage[QDR.LAST_LOCATION] = path; + } + }); + + }]); + + QDR.module.controller ("QDR.MainController", ['$scope', '$location', function ($scope, $location) { + QDR.log.debug("started QDR.MainController with location.url: " + $location.url()); + QDR.log.debug("started QDR.MainController with window.location.pathname : " + window.location.pathname); + $scope.topLevelTabs = []; + $scope.topLevelTabs.push({ + id: "qdr", + content: "Qpid Dispatch Router Console", + title: "Dispatch Router Console", + isValid: function() { return true; }, + href: function() { return "#connect"; }, + isActive: function() { return true; } + }); + }]) + + QDR.module.controller ("QDR.Core", function ($scope, $rootScope) { + $scope.alerts = []; + $scope.closeAlert = function(index) { + $scope.alerts.splice(index, 1); + }; + $scope.$on('newAlert', function(event, data) { + $scope.alerts.push(data); + $scope.$apply(); + }); + $scope.$on("clearAlerts", function () { + $scope.alerts = []; + $scope.$apply(); + }) + + }) + + return QDR; +}(QDR || {})); + +var Folder = (function () { + function Folder(title) { + this.title = title; + this.children = []; + this.folder = true; + } + return Folder; +})(); +var Leaf = (function () { + function Leaf(title) { + this.title = title; + } + return Leaf; +})(); http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/console/stand-alone/plugin/js/navbar.js ---------------------------------------------------------------------- diff --git a/console/stand-alone/plugin/js/navbar.js b/console/stand-alone/plugin/js/navbar.js index ebda4d4..337be52 100644 --- a/console/stand-alone/plugin/js/navbar.js +++ b/console/stand-alone/plugin/js/navbar.js @@ -34,39 +34,38 @@ var QDR = (function (QDR) { content: '<i class="icon-cogs"></i> Connect', title: "Connect to a router", isValid: function () { return true; }, - href: "#connect" + href: "#" + QDR.pluginRoot + "/connect" }, { - content: '<i class="fa fa-home"></i> Overview', + content: '<i class="icon-home"></i> Overview', title: "View router overview", isValid: function (QDRService) { return QDRService.isConnected(); }, - href: "#/overview" + href: "#" + QDR.pluginRoot + "/overview" }, { - content: '<i class="icon-star-empty"></i> Topology', - title: "View router network topology", + content: '<i class="icon-list "></i> Entities', + title: "View the attributes of the router entities", isValid: function (QDRService) { return QDRService.isConnected(); }, - href: "#/topology" + href: "#" + QDR.pluginRoot + "/list" }, { - content: '<i class="icon-list "></i> List', - title: "View router nodes as a list", + content: '<i class="icon-star-empty"></i> Topology', + title: "View router network topology", isValid: function (QDRService) { return QDRService.isConnected(); }, - href: "#/list" + href: "#" + QDR.pluginRoot + "/topology" }, { content: '<i class="icon-bar-chart"></i> Charts', title: "View charts", - isValid: function (QDRService, $location) { return QDRService.isConnected(); }, + isValid: function (QDRService, $location) { return QDRService.isConnected() && QDR.isStandalone; }, href: "#/charts" }, { content: '<i class="icon-align-left"></i> Schema', title: "View dispatch schema", isValid: function (QDRService) { return QDRService.isConnected(); }, - href: "#/schema", + href: "#" + QDR.pluginRoot + "/schema", right: true - } ]; /** @@ -78,28 +77,16 @@ var QDR = (function (QDR) { * The controller for this plugin's navigation bar * */ - QDR.module.controller("QDR.NavBarController", ['$scope', '$sce', 'QDRService', 'QDRChartService', '$location', function($scope, $sce, QDRService, QDRChartService, $location) { - - QDR.log.debug("navbar started with location.url: " + $location.url()); - QDR.log.debug("navbar started with window.location.pathname : " + window.location.pathname); - - if ($location.path().startsWith("/topology") - && !QDRService.isConnected()) { - $location.path("/connect"); - } - - if ($location.path().startsWith("/connect") - && QDRService.isConnected()) { - $location.path("/topology"); - } - + QDR.module.controller("QDR.NavBarController", ['$scope', 'QDRService', 'QDRChartService', '$routeParams', '$location', function($scope, QDRService, QDRChartService, $routeParams, $location) { $scope.breadcrumbs = QDR.breadcrumbs; - $scope.isValid = function(link) { return link.isValid(QDRService, $location); }; $scope.isActive = function(href) { + // highlight the connect tab if we are on the root page + if (($location.path() === QDR.pluginRoot) && (href.split("#")[1] === QDR.pluginRoot + "/connect")) + return true return href.split("#")[1] == $location.path(); }; @@ -112,8 +99,30 @@ var QDR = (function (QDR) { return QDRChartService.charts.some(function (c) { return c.dashboard }); } } + + $scope.isDashboardable = function () { + return ($location.path().indexOf("schema") < 0 && $location.path().indexOf("connect") < 0); + } + + $scope.addToDashboardLink = function () { + var href = "#" + $location.path(); + var size = angular.toJson({ + size_x: 2, + size_y: 2 + }); + + var routeParams = angular.toJson($routeParams); + var title = "Dispatch Router"; + return "/hawtio/#/dashboard/add?tab=dashboard" + + "&href=" + encodeURIComponent(href) + + "&routeParams=" + encodeURIComponent(routeParams) + + "&title=" + encodeURIComponent(title) + + "&size=" + encodeURIComponent(size); + }; + }]); + return QDR; } (QDR || {})); http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/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 efc607d..47cb47d 100644 --- a/console/stand-alone/plugin/js/qdrChartService.js +++ b/console/stand-alone/plugin/js/qdrChartService.js @@ -22,8 +22,8 @@ under the License. var QDR = (function(QDR) { // The QDR chart service handles periodic gathering data for charts and displaying the charts - QDR.module.factory("QDRChartService", ['$rootScope', 'QDRService', '$http', '$resource', - function($rootScope, QDRService, $http, $resource) { + QDR.module.factory("QDRChartService", ['$rootScope', 'QDRService', '$http', '$resource', '$location', + function($rootScope, QDRService, $http, $resource, $location) { var instance = 0; // counter for chart instances var bases = []; @@ -56,26 +56,32 @@ var QDR = (function(QDR) { // Object that represents a visible chart // There can be multiple of these per ChartBase (eg. one rate and one value chart) - function Chart(name, attr, request) { + function Chart(opts, request) { //name, attr, cinstance, request) { - var base = findBase(name, attr, request); + var base = findBase(opts.name, opts.attr, request); if (!base) { - base = new ChartBase(name, attr, request); + base = new ChartBase(opts.name, opts.attr, request); bases.push(base); } this.base = base; - this.instance = instance++; + this.instance = angular.isDefined(opts.instance) ? opts.instance : ++instance; this.dashboard = false; // is this chart on the dashboard page - this.type = "value"; // value or rate - this.rateWindow = 1000; // calculate the rate of change over this time interval. higher == smother graph - this.areaColor = "#c0e0ff"; // the chart's area color when not an empty string - this.lineColor = "#4682b4"; // the chart's line color when not an empty string - this.visibleDuration = 10; // number of minutes of data to show (<= base.duration) + 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 = "#cbe7f3"; // 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 : 10; // number of minutes of data to show (<= base.duration) this.userTitle = null; // user title overrides title() // generate a unique id for this chart this.id = function () { - var key = this.request().nodeId + this.request().entity + this.name() + this.attr() + "_" + this.instance; + var name = this.name() + var nameparts = name.split('/'); + if (nameparts.length == 2) + name = nameparts[1]; + var key = QDRService.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, '') } @@ -87,6 +93,9 @@ var QDR = (function(QDR) { 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 (_) { @@ -109,6 +118,11 @@ var QDR = (function(QDR) { 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 = _; @@ -145,8 +159,16 @@ var QDR = (function(QDR) { return this; } this.copy = function () { - var chart = self.registerChart(this.nodeId(), this.entity(), - this.name(), this.attr(), this.interval(), true, this.base.request.aggregate); + var 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; @@ -167,17 +189,17 @@ var QDR = (function(QDR) { } // Object that represents the management request to fetch and store data for multiple charts - function ChartRequest(nodeId, entity, name, attr, interval, aggregate) { - this.duration = 10; // number of minutes to keep the data - this.nodeId = nodeId; // eg amqp:/_topo/0/QDR.A/$management - this.entity = entity; // eg .router.address + 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 = 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.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 = interval; // number of milliseconds between updates to data - this.setTimeoutHandle = null; // used to cancel the next request + this.interval = opts.interval || 1000; // number of milliseconds between updates to data + this.setTimeoutHandle = null; // used to cancel the next request // copy the savable properties to an object this.data = function (name, attr) { @@ -193,7 +215,7 @@ var QDR = (function(QDR) { this.datum[name][attr] = []; } } - this.addAttrName(name, attr) + this.addAttrName(opts.name, opts.attr) this.copyProps = function (o) { o.nodeId = this.nodeId; @@ -232,6 +254,8 @@ var QDR = (function(QDR) { return Object.keys(attrs); } }; + + // Below here are the properties and methods available on QDRChartService var self = { charts: [], // list of charts to gather data for chartRequests: [], // the management request info (multiple charts can be driven off of a single request @@ -251,12 +275,15 @@ var QDR = (function(QDR) { return ret; }, - findCharts: function (name, attr, nodeId, entity) { + 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() == name && - chart.attr() == attr && - chart.nodeId() == nodeId && - chart.entity() == entity) + return (chart.name() == opts.name && + chart.attr() == opts.attr && + chart.nodeId() == opts.nodeId && + chart.entity() == opts.entity && + chart.hdash == opts.hdash) }); }, @@ -290,22 +317,24 @@ var QDR = (function(QDR) { } }, - registerChart: function (nodeId, entity, name, attr, interval, forceCreate, aggregate) { - var request = self.findChartRequest(nodeId, entity, aggregate); + registerChart: function (opts) { //nodeId, entity, name, attr, interval, instance, forceCreate, aggregate, hdash) { + var request = self.findChartRequest(opts.nodeId, opts.entity, opts.aggregate); if (request) { // add any new attr or name to the list - request.addAttrName(name, attr) + 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: " + nodeId + " " + entity); - request = new ChartRequest(nodeId, entity, name, attr, interval, aggregate); + QDR.log.debug("added new request: " + opts.nodeId + " " + opts.entity); + request = new ChartRequest(opts); //nodeId, entity, name, attr, interval, aggregate); self.chartRequests.push(request); self.startCollecting(request); } - var charts = self.findCharts(name, attr, nodeId, entity); + var charts = self.findCharts(opts); //name, attr, nodeId, entity, hdash); var chart; - if (charts.length == 0 || forceCreate) { - chart = new Chart(name, attr, request); + 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]; @@ -317,6 +346,16 @@ var QDR = (function(QDR) { // if all attrs are gone for this request, remove the request unRegisterChart: function (chart) { // 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 (var i=0; i<self.charts.length; ++i) { var c = self.charts[i]; if (chart.equals(c)) { @@ -337,6 +376,8 @@ var QDR = (function(QDR) { } } } + self.saveCharts(); + }, stopCollecting: function (request) { @@ -350,16 +391,29 @@ var QDR = (function(QDR) { // Using setTimeout instead of setInterval because the response may take longer than interval request.setTimeoutHandle = setTimeout(self.sendChartRequest, request.interval, request); }, + shouldRequest: function (request) { + // 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) { + sendChartRequest: function (request, once) { + if (!once && !self.shouldRequest(request)) { + request.setTimeoutHandle = setTimeout(self.sendChartRequest, request.interval, request) + return; + } + // ensure the response has the name field so we can associate the response values with the correct chart var attrs = request.attrs(); attrs.push("name"); // this is called when the response is received var saveResponse = function (nodeId, entity, response) { - QDR.log.debug("got chart results for " + nodeId + " " + entity); - // records an array that has data for all names + if (!response || !response.attributeNames) + return; + //QDR.log.debug("got chart results for " + nodeId + " " + entity); + // records is an array that has data for all names var records = response.results; if (!records) return; @@ -397,27 +451,52 @@ var QDR = (function(QDR) { } if (request.aggregate) { var nodeList = QDRService.nodeIdList() - QDR.log.debug("sent multiple chart request for " + nodeList + " " + request.entity, + " " + attrs + " " + request.nodeId); QDRService.getMultipleNodeInfo(nodeList, request.entity, attrs, saveResponse, request.nodeId); } else { - QDR.log.debug("sent chart request for " + request.nodeId + " " + request.entity + " " + attrs); QDRService.getNodeInfo(request.nodeId, request.entity, attrs, saveResponse); } - // it is now safe to send another request + // it is now safe to schedule another request + if (once) + return; request.setTimeoutHandle = setTimeout(self.sendChartRequest, request.interval, request) }, numCharts: function () { - return self.charts.length; + return self.charts.filter( function (chart) { return chart.dashboard }).length; + //return self.charts.length; }, isAttrCharted: function (nodeId, entity, name, attr) { - var charts = self.findCharts(name, attr, nodeId, entity); + var 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) }); }, + addHDash: function (chart) { + chart.hdash = true; + self.saveCharts(); + /* + if (!chart.hdash) { + var dashChart = self.registerChart(chart.nodeId(), chart.entity(), + chart.name(), chart.attr(), chart.interval(), true, chart.aggregate(), true); + dashChart.dashboard = true; + dashChart.hdash = false; + chart.dashboard = false; + chart.hdash = true; + self.saveCharts(); + } + */ + }, + delHDash: function (chart) { + chart.hdash = false; + self.saveCharts(); + }, addDashboard: function (chart) { chart.dashboard = true; self.saveCharts(); @@ -434,7 +513,7 @@ var QDR = (function(QDR) { self.charts.forEach(function (chart) { var minChart = {}; // don't save chart unless it is on the dashboard - if (chart.dashboard) { + if (chart.dashboard || chart.hdash) { chart.copyProps(minChart); minCharts.push(minChart); } @@ -469,8 +548,53 @@ var QDR = (function(QDR) { }) } }, + loadCharts: function () { + var charts = angular.fromJson(localStorage["QDRCharts"]); + if (charts) { + // get array of known ids + var nodeList = QDRService.nodeList().map( function (node) { + return node.id; + }) + 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 = 10; + 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; + var 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 : "#cbe7f3"; + newChart.lineColor = chart.lineColor ? chart.lineColor : "#058dc7"; + newChart.duration(chart.duration); + newChart.visibleDuration = chart.visibleDuration ? chart.visibleDuration : 10; + if (chart.userTitle) + newChart.title(chart.userTitle); + } + }) + } + }, - AreaChart: function (chart, url) { + AreaChart: function (chart) { if (!chart) return; @@ -478,11 +602,7 @@ var QDR = (function(QDR) { var stacked = chart.request().aggregate; this.chart = chart; // reference to underlying chart this.svgchart = null; - if (url) - url = "/dispatch" + url; - else - url = ""; - this.url = url; + this.url = $location.absUrl(); // callback function. called by svgchart when binding data // the variable 'this' refers to the svg and not the AreaChart, @@ -600,7 +720,8 @@ var QDR = (function(QDR) { this.chart.name(), QDRService.nameFromId(this.chart.nodeId()), this.chart.entity(), - stacked) + stacked, + this.chart.visibleDuration) .tooltipGenerator(tooltipGenerator); } @@ -619,7 +740,7 @@ var QDR = (function(QDR) { } }, - timeSeriesStackedChart: function (id, width, height, attrName, name, node, entity, stacked) { + timeSeriesStackedChart: function (id, width, height, attrName, name, node, entity, stacked, visibleDuration) { var margin = {top: 20, right: 18, bottom: 10, left: 15} // attrs that can be changed after the chart is created by using // chart.attr(<attrname>, <attrvalue>); @@ -633,18 +754,19 @@ var QDR = (function(QDR) { type: "value", // value or rate areaColor: "", // can be set for non-stacked charts lineColor: "", // can be set for non-stacked charts - zoom: false // should the y-axis range start at 0 or the min data value + zoom: false, // should the y-axis range start at 0 or the min data value + visibleDuration: visibleDuration } var width = width - margin.left - margin.right, - height = height - margin.top - margin.bottom + height = height - margin.top - margin.bottom, yAxisTransitionDuration = 0 var x = d3.time.scale() var y = d3.scale.linear() .rangeRound([height, 0]); - // The x-accessor for the path generator; xScale â xValue. + // The x-accessor for the path generator; xScale * xValue. var X = function (d) { return x(d[0]) } - // The x-accessor for the path generator; yScale â yValue. + // The x-accessor for the path generator; yScale * yValue. var Y = function Y(d) { return y(d[1]) } var xAxis = d3.svg.axis().scale(x).orient("bottom") @@ -672,7 +794,6 @@ var QDR = (function(QDR) { .y(function (d) { return d.value; }); var area = d3.svg.area() - if (stacked) { area.interpolate("cardinal") .x(function (d) { return x(d.date); }) @@ -691,7 +812,22 @@ var QDR = (function(QDR) { .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); - var clip = svg.append("defs").append("svg:clipPath") + sv.append("linearGradient") + .attr("id", id) //"temperature-gradient") + .attr("gradientUnits", "userSpaceOnUse") + .attr("x1", 0).attr("y1", height *.5 ) + .attr("x2", 0).attr("y2", height * 1.2) + .selectAll("stop") + .data([ + {offset: "0%", opacity: 1}, + {offset: "100%", opacity: 0} + ]) + .enter().append("stop") + .attr("offset", function(d) { return d.offset; }) + .attr("stop-opacity", function(d) { return d.opacity; }) + .attr("stop-color", function(d) { return "#cbe7f3" }); +/* + var clip = svg.append("defs").append("svg:clipPath") .attr("id", "clip") .append("svg:rect") .attr("id", "clip-rect") @@ -699,7 +835,7 @@ var QDR = (function(QDR) { .attr("y", "0") .attr("width", width) .attr("height", height); - +*/ // we want all our areas to appear before the axiis svg.append("g") .attr("class", "section-container") @@ -741,8 +877,11 @@ var QDR = (function(QDR) { focus.append("foreignObject") .attr('class', 'svg-tooltip') .append("xhtml:span"); - - +/* + var transition = d3.select({}).transition() + .duration(2000) + .ease("linear"); +*/ function chart(selection) { selection.each(function(data) { @@ -769,12 +908,15 @@ var QDR = (function(QDR) { } var extent = d3.extent(data, function(d) {return d[0];}); + //var points = data.length; + //var futureDate = new Date(data[points-1][0].getTime() - attrs.visibleDuration * 60 * 1000); + //extent = [futureDate, data[points-1][0]] x.domain(extent) .range([0, width - margin.left - margin.right]); // Update the y-scale. - var min = attrs.zoom ? 0 : d3.min(data, function(d) {return d[1]}); - var max = d3.max(data, function(d) {return d[1]}); + var min = attrs.zoom ? 0 : d3.min(data, function(d) {return d[1]}) *.99; + var max = d3.max(data, function(d) {return d[1]}) * 1.01; var mean = d3.mean(data, function(d) {return d[1]}); //max = max * 1.01; var diff = (max - min); @@ -839,7 +981,6 @@ var QDR = (function(QDR) { series.exit().remove() - //series.exit().remove() // each time the data is updated, update each section container.selectAll(".series .streamPath").data(seriesArr) .attr("d", function (d) { return area(d.values); }) @@ -847,23 +988,30 @@ var QDR = (function(QDR) { var series = container.selectAll(".series") .data([data], function(d) { return d; }) - series.enter().append("g") - .append("path") + var g = series.enter().append("g") + .attr("class", "series") + + g.append("path") .attr("class", "area") - series.enter().append("path") - .attr("class", "line") + .style("fill", "url(" + attrs.url + "#" + id + ") " + attrs.areaColor) //temperature-gradient)") + .attr("d", area.y0(y.range()[0])) + .attr("transform", null); + g.append("path") + .attr("class", "line") + .style("stroke", attrs.lineColor) + .attr("d", line) +/* +debugger; + g.transition() + .duration(2000) + .attr("transform", "translate(-4)"); +*/ series.exit().remove() - // Update the area path. - container.select(".area").data([data]) - .attr("d", area.y0(y.range()[0])) - .style("fill", attrs.areaColor); + sv.selectAll("stop") + .attr("stop-color", attrs.areaColor) - //Update the line path. - container.select(".line").data([data]) - .attr("d", line) - .style("stroke", attrs.lineColor) } // Update the x-axis. svg.select(".x.axis") http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/d98ae37e/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 e484b07..8bddca5 100644 --- a/console/stand-alone/plugin/js/qdrCharts.js +++ b/console/stand-alone/plugin/js/qdrCharts.js @@ -19,44 +19,58 @@ under the License. /** * @module QDR */ -/** - * @module QDR - */ var QDR = (function (QDR) { /** * @method ChartsController - * @param $scope - * @param QDRServer - * @param QDRChartServer * * Controller that handles the QDR charts page */ - QDR.module.controller("QDR.ChartsController", ['$scope', 'QDRService', 'QDRChartService', '$uibModal', '$location', - function($scope, QDRService, QDRChartService, $uibModal, $location) { - var updateTimer = null; + QDR.module.controller("QDR.ChartsController", function($scope, QDRService, QDRChartService, $dialog, $location, $routeParams) { + + var updateTimer = null; + + if (!QDRService.connected) { + // we are not connected. we probably got here from a bookmark or manual page reload + QDRService.redirectWhenConnected("charts"); + return; + } + // we are currently connected. setup a handler to get notified if we are ever disconnected + QDRService.addDisconnectAction( function () { + QDRService.redirectWhenConnected("charts") + $scope.$apply(); + }) - QDR.log.debug("started Charts controller"); - if (!angular.isDefined(QDRService.schema)) - return; $scope.svgCharts = []; // create an svg object for each chart QDRChartService.charts.filter(function (chart) {return chart.dashboard}).forEach(function (chart) { - var svgChart = new QDRChartService.AreaChart(chart, $location.$$path) + var svgChart = new QDRChartService.AreaChart(chart) svgChart.zoomed = false; $scope.svgCharts.push(svgChart); }) - // redraw the charts every second + // redraw the chart every update period + // this is a $scope function because it is called from the dialog 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 }) - updateHandle = setTimeout(updateCharts, 1100); + var updateRate = localStorage['updateRate'] ? localStorage['updateRate'] : 5000; + if (updateTimer) { + clearTimeout(updateTimer) + } + updateTimer = setTimeout(updateCharts, updateRate); } + + // called by ng-init in the html when the page is loaded $scope.chartsLoaded = function () { - setTimeout(updateCharts, 0); + $scope.svgCharts.forEach(function (svgChart) { + QDRChartService.sendChartRequest(svgChart.chart.request(), true); + }) + if (updateTimer) + clearTimeout(updateTimer) + setTimeout(updateCharts, 0); } $scope.zoomChart = function (chart) { @@ -88,12 +102,12 @@ 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) { - $scope.svgCharts.push(new QDRChartService.AreaChart(chart, $location.$$path)); + $scope.svgCharts.push(new QDRChartService.AreaChart(chart)); }; $scope.$on("$destroy", function( event ) { if (updateTimer) { - cancelTimer(updateTimer); + clearTimeout(updateTimer); updateTimer = null; } for (var i=$scope.svgCharts.length-1; i>=0; --i) { @@ -103,28 +117,33 @@ var QDR = (function (QDR) { function doDialog(template, chart) { - var modalInstance = $uibModal.open({ - animation: true, - templateUrl: template, - controller: 'QDR.ChartDialogController', - resolve: { - chart: function () { - return chart; - }, - dashboard: function () { - return $scope; - } - } - }); + $dialog.dialog({ + backdrop: true, + keyboard: true, + backdropClick: true, + templateUrl: template, + controller: "QDR.ChartDialogController", + resolve: { + chart: function() { + return chart; + }, + updateTick: function () { + return updateCharts; + }, + dashboard: function () { + return $scope; + } + } + }).open(); }; - }]); + }); - QDR.module.controller("QDR.ChartDialogController", function($scope, QDRChartService, $location, $uibModalInstance, $rootScope, chart, dashboard) { + QDR.module.controller("QDR.ChartDialogController", function($scope, QDRChartService, $location, dialog, $rootScope, chart, updateTick, dashboard) { var dialogSvgChart = null; $scope.svgDivId = "dialogChart"; // the div id for the svg chart - $scope.updateTimer = null; + var 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(); @@ -132,51 +151,85 @@ var QDR = (function (QDR) { $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.tick($scope.svgDivId); + } + }) + // the stored rateWindow is in milliseconds, but the slider is in seconds $scope.rateWindow = $scope.chart.rateWindow / 1000; var cleanup = function () { - if ($scope.updateTimer) { - clearTimeout($scope.updateTimer); - $scope.updateTimer = null; + if (updateTimer) { + clearTimeout(updateTimer); + updateTimer = null; } QDRChartService.unRegisterChart($scope.dialogChart); // remove the chart } $scope.okClick = function () { - cleanup(); - $uibModalInstance.close(); + cleanup(); + dialog.close(); }; - // initialize the rateWindow slider - $scope.slider = { - 'options': { - min: 1, - max: 10, - step: 1, - tick: true, - stop: function (event, ui) { - $scope.dialogChart.rateWindow = ui.value * 1000; - if (dialogSvgChart) - dialogSvgChart.tick($scope.svgDivId); - } - } - }; - - $scope.visibleDuration = - $scope.duration = { - 'options': { - min: 1, - max: 10, - step: 1, - tick: true, - stop: function (event, ui) { - if (dialogSvgChart) - dialogSvgChart.tick($scope.svgDivId); - } - } - }; + 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: 1, + max: 10, + step: 1, + slide: function( event, ui ) { + $scope.visibleDuration = $scope.dialogChart.visibleDuration = ui.value; + $scope.$apply(); + if (dialogSvgChart) + dialogSvgChart.tick($scope.svgDivId); + } + }); + + } else { + setTimeout(initDurationSlider, 100) + } + } + initDurationSlider(); // handle the Apply button click // update the dashboard chart's properties @@ -184,10 +237,14 @@ var QDR = (function (QDR) { $scope.chart.areaColor = $scope.dialogChart.areaColor; $scope.chart.lineColor = $scope.dialogChart.lineColor; $scope.chart.type = $scope.dialogChart.type; - $scope.chart.rateWindow = $scope.dialogChart.rateWindow; + $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(); + if (typeof updateTick === "function") + updateTick(); } // add a new chart to the dashboard based on the current dialog settings @@ -206,7 +263,10 @@ var QDR = (function (QDR) { dialogSvgChart.tick($scope.svgDivId); // draw the chart again in 1 second - $scope.updateTimer = setTimeout(updateDialogChart, 1000); + var updateRate = localStorage['updateRate'] ? localStorage['updateRate'] : 5000; + if (updateTimer) + clearTimeout(updateTimer); + updateTimer = setTimeout(updateDialogChart, updateRate); } var showChart = function () { @@ -216,7 +276,19 @@ var QDR = (function (QDR) { setTimeout(showChart, 100); return; } - dialogSvgChart = new QDRChartService.AreaChart($scope.dialogChart, $location.$$path); + dialogSvgChart = new QDRChartService.AreaChart($scope.dialogChart); + $('input[name=lineColor]').val($scope.dialogChart.lineColor); + $('input[name=areaColor]').val($scope.dialogChart.areaColor); + $('input[name=areaColor]').on('input', function (e) { + $scope.dialogChart.areaColor = $(this).val(); + updateDialogChart() + }) + $('input[name=lineColor]').on('input', function (e) { + $scope.dialogChart.lineColor = $(this).val(); + updateDialogChart() + }) + if (updateTimer) + clearTimeout(updateTimer); updateDialogChart(); } showChart(); --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@qpid.apache.org For additional commands, e-mail: commits-h...@qpid.apache.org