diff --git a/CHANGELOG.md b/CHANGELOG.md index 55cde63197f..0c6d39929f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,12 +3,27 @@ **New features and improvements** - [Issue #578](https://github.com/grafana/grafana/issues/578). Dashboard: Row option to display row title even when the row is visible +- [Issue #672](https://github.com/grafana/grafana/issues/672). Dashboard: panel fullscreen & edit state is present in url, can now link to graph in edit & fullscreen mode. +- [Issue #709](https://github.com/grafana/grafana/issues/709). Dashboard: Small UI look polish to search results, made dashboard title link are larger +- [Issue #425](https://github.com/grafana/grafana/issues/425). Graph: New section in 'Display Styles' tab to override any display setting on per series bases (mix and match lines, bars, points, fill, stack, line width etc) + +**Fixes** +- [Issue #696](https://github.com/grafana/grafana/issues/696). Graph: Fix for y-axis format 'none' when values are in scientific notation (ex 2.3e-13) +- [Issue #697](https://github.com/grafana/grafana/issues/697). Graphite: Fix for Glob syntax in graphite queries ([1-9] and ?) that made the query editor / parser bail and fallback to a text box. +- [Issue #277](https://github.com/grafana/grafana/issues/277). Dashboard: Fix for timepicker date & tooltip when UTC timezone selected. Closes #277 **Tech** - Upgraded from angularjs 1.1.5 to 1.3 beta 17; - Switch from underscore to lodash - helpers to easily unit test angularjs controllers and services - Test coverage through coveralls +- Upgrade from jquery 1.8.0 to 2.1.1 (**Removes support for IE7 & IE8**) + +# 1.7.1 (unreleased) + +**Fixes** +- [Issue #691](https://github.com/grafana/grafana/issues/691). Dashboard: tooltip fixes, sometimes they would not show, and sometimes they would get stuck. +- [Issue #695](https://github.com/grafana/grafana/issues/695). Dashboard: Tooltip on goto home menu icon would get stuck after clicking on it # 1.7.0 (2014-08-11) diff --git a/README.md b/README.md index 5b74f23ac8b..fd4fef2f364 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ Graphite, InfluxDB & OpenTSDB. - Import dashboard from Graphite - Templating - [Scripted dashboards](http://grafana.org/docs/features/scripted_dashboards) -- [Dashboard playlists](http://grafana.org/docs/docs/features/playlist) +- [Dashboard playlists](http://grafana.org/docs/features/playlist) - [Time range controls](http://grafana.org/docs/features/time_range) ### InfluxDB @@ -49,40 +49,38 @@ Graphite, InfluxDB & OpenTSDB. - Use as metric data source - Query editor with metric name typeahead and tag filtering -# Requirements +## Requirements There are no dependencies, Grafana is a client side application that runs in your browser. It only needs a time series store where it can fetch metrics. If you use InfluxDB Grafana can use it to store dashboards. If you use Graphite or OpenTSDB you can use Elasticsearch to store dashboards or just use json files stored on disk. -# Installation +## Installation Head to [grafana.org](http://grafana.org) and [download](http://grafana.org/download/) the latest release. Then follow the quick [setup & config guide](http://grafana.org/docs/). If you have any problems please read the [troubleshooting guide](http://grafana.org/docs/troubleshooting). -# Documentation & Support +## Documentation & Support Be sure to read the [getting started guide](http://grafana.org/docs/features/intro) and the other feature guides. -# Roadmap -- Improve graphite query editor to handle all types of queries -- Refine and simplify common tasks -- More panel types (not just graphs) -- Improve templating support -- Alerting -- Optional backend component -- Much much more! (what ever gets votes on github issues!) +## Run from master +Grafana uses nodejs and grunt for asset management (css & javascript), unit test runner and javascript syntax verification. +- clone repository +- install nodejs +- npm install (in project root) +- npm install -g grunt-cli +- grunt (runt default task that will generate css files) +- grunt build (creates optimized & minified release) +- grunt release (same as grunt build but will also create tar & zip package) +- grunt test (executes jshint and unit tests) -# Contribute -If you have any idea for an improvement or found a bug do not hesitate to open an issue. And if you have time clone this repo and submit a pull request and help me make Grafana the kickass metrics & devops dashboard we all dream about! +## Contribute +If you have any idea for an improvement or found a bug do not hesitate to open an issue. +And if you have time clone this repo and submit a pull request and help me make Grafana +the kickass metrics & devops dashboard we all dream about! -Clone repository: -- npm install -- grunt server (starts development web server in src folder) -- grunt (runs jshint and less -> css compilation) -- npm test runs jshint, and unit tests +Before creating a pull request be sure that "grunt test" runs without any style or unit test errors, also +please [sign the CLA](http://grafana.org/docs/contributing/cla.html) -Before submitting a PR be sure that there are no jshint or unit test failures. -And [sign the CLA](http://grafana.org/docs/contributing/cla.html) - -# License +## License Grafana is distributed under Apache 2.0 License. diff --git a/package.json b/package.json index 31c552fe6c2..eecf826776e 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "grunt-string-replace": "~0.2.4", "grunt-usemin": "^2.1.1", "jshint-stylish": "~0.1.5", - "karma": "~0.12.16", + "karma": "~0.12.21", "karma-chrome-launcher": "~0.1.4", "karma-coffee-preprocessor": "~0.1.2", "karma-coverage": "^0.2.5", diff --git a/src/app/app.js b/src/app/app.js index 973a74ab9e0..ab5c8fd6355 100644 --- a/src/app/app.js +++ b/src/app/app.js @@ -78,14 +78,23 @@ function (angular, $, _, appLevelRequire, config) { apps_deps.push(module_name); }); + var preBootRequires = [ + 'controllers/all', + 'directives/all', + 'filters/all', + 'components/partials', + 'routes/all', + ]; + + _.each(config.plugins.dependencies, function(dep) { + preBootRequires.push('../plugins/' + dep); + }); + app.boot = function() { - require([ - 'controllers/all', - 'directives/all', - 'filters/all', - 'components/partials', - 'routes/all', - ], function () { + require(preBootRequires, function () { + + // disable tool tip animation + $.fn.tooltip.defaults.animation = false; // bootstrap the app angular diff --git a/src/app/components/kbn.js b/src/app/components/kbn.js index 07d0f21c090..46a1b8dfa79 100644 --- a/src/app/components/kbn.js +++ b/src/app/components/kbn.js @@ -1,4 +1,8 @@ -define(['jquery','lodash','moment'], +define([ + 'jquery', + 'lodash', + 'moment' +], function($, _, moment) { 'use strict'; @@ -227,36 +231,36 @@ function($, _, moment) { if (type === 0) { roundUp ? dateTime.endOf('year') : dateTime.startOf('year'); } else if (type === 1) { - dateTime.add('years',num); + dateTime.add(num, 'years'); } else if (type === 2) { - dateTime.subtract('years',num); + dateTime.subtract(num, 'years'); } break; case 'M': if (type === 0) { roundUp ? dateTime.endOf('month') : dateTime.startOf('month'); } else if (type === 1) { - dateTime.add('months',num); + dateTime.add(num, 'months'); } else if (type === 2) { - dateTime.subtract('months',num); + dateTime.subtract(num, 'months'); } break; case 'w': if (type === 0) { roundUp ? dateTime.endOf('week') : dateTime.startOf('week'); } else if (type === 1) { - dateTime.add('weeks',num); + dateTime.add(num, 'weeks'); } else if (type === 2) { - dateTime.subtract('weeks',num); + dateTime.subtract(num, 'weeks'); } break; case 'd': if (type === 0) { roundUp ? dateTime.endOf('day') : dateTime.startOf('day'); } else if (type === 1) { - dateTime.add('days',num); + dateTime.add(num, 'days'); } else if (type === 2) { - dateTime.subtract('days',num); + dateTime.subtract(num, 'days'); } break; case 'h': @@ -264,27 +268,27 @@ function($, _, moment) { if (type === 0) { roundUp ? dateTime.endOf('hour') : dateTime.startOf('hour'); } else if (type === 1) { - dateTime.add('hours',num); + dateTime.add(num, 'hours'); } else if (type === 2) { - dateTime.subtract('hours',num); + dateTime.subtract(num,'hours'); } break; case 'm': if (type === 0) { roundUp ? dateTime.endOf('minute') : dateTime.startOf('minute'); } else if (type === 1) { - dateTime.add('minutes',num); + dateTime.add(num, 'minutes'); } else if (type === 2) { - dateTime.subtract('minutes',num); + dateTime.subtract(num, 'minutes'); } break; case 's': if (type === 0) { roundUp ? dateTime.endOf('second') : dateTime.startOf('second'); } else if (type === 1) { - dateTime.add('seconds',num); + dateTime.add(num, 'seconds'); } else if (type === 2) { - dateTime.subtract('seconds',num); + dateTime.subtract(num, 'seconds'); } break; default: @@ -525,12 +529,35 @@ function($, _, moment) { return kbn.nanosFormat(val, decimals); }; default: - return function(val) { - return val % 1 === 0 ? val : val.toFixed(decimals); + return function(val, axis) { + return kbn.noneFormat(val, axis ? axis.tickDecimals : null); }; } }; + kbn.noneFormat = function(value, decimals) { + var factor = decimals ? Math.pow(10, decimals) : 1; + var formatted = String(Math.round(value * factor) / factor); + + // if exponent return directly + if (formatted.indexOf('e') !== -1 || value === 0) { + return formatted; + } + + // If tickDecimals was specified, ensure that we have exactly that + // much precision; otherwise default to the value's own precision. + + if (decimals != null) { + var decimalPos = formatted.indexOf("."); + var precision = decimalPos === -1 ? 0 : formatted.length - decimalPos - 1; + if (precision < decimals) { + return (precision ? formatted : formatted + ".") + (String(factor)).substr(1, decimals - precision); + } + } + + return formatted; + }; + kbn.msFormat = function(size, decimals) { // Less than 1 milli, downscale to micro if (Math.abs(size) < 1) { diff --git a/src/app/components/lodash.extended.js b/src/app/components/lodash.extended.js index 3554eef6e51..40372239672 100644 --- a/src/app/components/lodash.extended.js +++ b/src/app/components/lodash.extended.js @@ -33,4 +33,4 @@ function () { }); return _; -}); \ No newline at end of file +}); diff --git a/src/app/components/require.config.js b/src/app/components/require.config.js index 7c488565525..8fdc7156441 100644 --- a/src/app/components/require.config.js +++ b/src/app/components/require.config.js @@ -8,6 +8,7 @@ require.config({ config: ['../config', '../config.sample'], settings: 'components/settings', kbn: 'components/kbn', + store: 'components/store', css: '../vendor/require/css', text: '../vendor/require/text', @@ -27,7 +28,7 @@ require.config({ 'lodash-src': '../vendor/lodash', bootstrap: '../vendor/bootstrap/bootstrap', - jquery: '../vendor/jquery/jquery-1.8.0', + jquery: '../vendor/jquery/jquery-2.1.1.min', 'jquery-ui': '../vendor/jquery/jquery-ui-1.10.3', 'extend-jquery': 'components/extend-jquery', diff --git a/src/app/components/settings.js b/src/app/components/settings.js index 165bbc6c476..b43237224b4 100644 --- a/src/app/components/settings.js +++ b/src/app/components/settings.js @@ -19,7 +19,7 @@ function (_, crypto) { default_route : '/dashboard/file/default.json', playlist_timespan : "1m", unsaved_changes_warning : true, - search : { max_results: 20 }, + search : { max_results: 16 }, admin : {} }; @@ -70,7 +70,7 @@ function (_, crypto) { _.each(settings.datasources, function(datasource, key) { datasource.name = key; - parseBasicAuth(datasource); + if (datasource.url) { parseBasicAuth(datasource); } if (datasource.type === 'influxdb') { parseMultipleHosts(datasource); } }); @@ -78,6 +78,10 @@ function (_, crypto) { settings.panels = _.union(settings.panels, settings.plugins.panels); } + if (!settings.plugins.dependencies) { + settings.plugins.dependencies = []; + } + return settings; }; }); diff --git a/src/app/components/store.js b/src/app/components/store.js new file mode 100644 index 00000000000..b9006f6e37d --- /dev/null +++ b/src/app/components/store.js @@ -0,0 +1,20 @@ +define([], function() { + 'use strict'; + + return { + get: function(key) { + return window.localStorage[key]; + }, + set: function(key, value) { + window.localStorage[key] = value; + }, + getBool: function(key) { + return window.localStorage[key] === 'true' ? true : false; + }, + delete: function(key) { + window.localStorage.removeItem(key); + } + + }; + +}); diff --git a/src/app/panels/graph/timeSeries.js b/src/app/components/timeSeries.js similarity index 51% rename from src/app/panels/graph/timeSeries.js rename to src/app/components/timeSeries.js index f1794cd6a30..85c27f1da88 100644 --- a/src/app/panels/graph/timeSeries.js +++ b/src/app/components/timeSeries.js @@ -5,15 +5,57 @@ define([ function (_, kbn) { 'use strict'; - var ts = {}; - - ts.ZeroFilled = function (opts) { + function TimeSeries(opts) { this.datapoints = opts.datapoints; this.info = opts.info; this.label = opts.info.alias; + } + + function matchSeriesOverride(aliasOrRegex, seriesAlias) { + if (!aliasOrRegex) { return false; } + + if (aliasOrRegex[0] === '/') { + var match = aliasOrRegex.match(new RegExp('^/(.*?)/(g?i?m?y?)$')); + var regex = new RegExp(match[1], match[2]); + return seriesAlias.match(regex) != null; + } + + return aliasOrRegex === seriesAlias; + } + + function translateFillOption(fill) { + return fill === 0 ? 0.001 : fill/10; + } + + TimeSeries.prototype.applySeriesOverrides = function(overrides) { + this.lines = {}; + this.points = {}; + this.bars = {}; + this.info.yaxis = 1; + this.zindex = 0; + delete this.stack; + + for (var i = 0; i < overrides.length; i++) { + var override = overrides[i]; + if (!matchSeriesOverride(override.alias, this.info.alias)) { + continue; + } + if (override.lines !== void 0) { this.lines.show = override.lines; } + if (override.points !== void 0) { this.points.show = override.points; } + if (override.bars !== void 0) { this.bars.show = override.bars; } + if (override.fill !== void 0) { this.lines.fill = translateFillOption(override.fill); } + if (override.stack !== void 0) { this.stack = override.stack; } + if (override.linewidth !== void 0) { this.lines.lineWidth = override.linewidth; } + if (override.pointradius !== void 0) { this.points.radius = override.pointradius; } + if (override.steppedLine !== void 0) { this.lines.steps = override.steppedLine; } + if (override.zindex !== void 0) { this.zindex = override.zindex; } + if (override.yaxis !== void 0) { + this.info.yaxis = override.yaxis; + } + } }; - ts.ZeroFilled.prototype.getFlotPairs = function (fillStyle, yFormats) { + TimeSeries.prototype.getFlotPairs = function (fillStyle, yFormats) { var result = []; this.color = this.info.color; @@ -74,5 +116,6 @@ function (_, kbn) { return result; }; - return ts; -}); \ No newline at end of file + return TimeSeries; + +}); diff --git a/src/app/controllers/all.js b/src/app/controllers/all.js index d3de182589d..3565f9fa844 100644 --- a/src/app/controllers/all.js +++ b/src/app/controllers/all.js @@ -1,7 +1,7 @@ define([ './grafanaCtrl', - './dash', - './dashLoader', + './dashboardCtrl', + './dashboardNavCtrl', './row', './submenuCtrl', './pulldown', diff --git a/src/app/controllers/console-ctrl.js b/src/app/controllers/console-ctrl.js index e2194b8eca9..8a673d99476 100644 --- a/src/app/controllers/console-ctrl.js +++ b/src/app/controllers/console-ctrl.js @@ -2,12 +2,13 @@ define([ 'angular', 'lodash', 'moment', + 'store' ], -function (angular, _, moment) { +function (angular, _, moment, store) { 'use strict'; var module = angular.module('grafana.controllers'); - var consoleEnabled = window.localStorage && window.localStorage.grafanaConsole === 'true'; + var consoleEnabled = store.getBool('grafanaConsole'); if (!consoleEnabled) { return; @@ -15,20 +16,6 @@ function (angular, _, moment) { var events = []; - var oldLog = console.log; - console.log = function (message) { - try { - if (_.isObject(message)) { - message = angular.toJson(message); - if (message.length > 50) { - message = message.substring(0, 50); - } - } - events.push(new ConsoleEvent('log', message, {})); - oldLog.apply(console, arguments); - } catch (e) { } - }; - function ConsoleEvent(type, title, data) { this.type = type; this.title = title; diff --git a/src/app/controllers/dash.js b/src/app/controllers/dashboardCtrl.js similarity index 81% rename from src/app/controllers/dash.js rename to src/app/controllers/dashboardCtrl.js index e40db93fd7e..7132ca8a571 100644 --- a/src/app/controllers/dash.js +++ b/src/app/controllers/dashboardCtrl.js @@ -10,8 +10,10 @@ function (angular, $, config, _) { var module = angular.module('grafana.controllers'); - module.controller('DashCtrl', function( - $scope, $rootScope, dashboardKeybindings, filterSrv, dashboardSrv, panelMoveSrv, timer) { + module.controller('DashboardCtrl', function( + $scope, $rootScope, dashboardKeybindings, + filterSrv, dashboardSrv, dashboardViewStateSrv, + panelMoveSrv, timer, $timeout) { $scope.editor = { index: 0 }; $scope.panelNames = config.panels; @@ -19,14 +21,25 @@ function (angular, $, config, _) { $scope.init = function() { $scope.availablePanels = config.panels; $scope.onAppEvent('setup-dashboard', $scope.setupDashboard); + + angular.element(window).bind('resize', function() { + $timeout(function() { + $scope.$broadcast('render'); + }); + }); + }; $scope.setupDashboard = function(event, dashboardData) { timer.cancel_all(); - $rootScope.fullscreen = false; + $rootScope.performance.dashboardLoadStart = new Date().getTime(); + $rootScope.performance.panelsInitialized = 0; + $rootScope.performance.panelsRendered= 0; $scope.dashboard = dashboardSrv.create(dashboardData); + $scope.dashboardViewState = dashboardViewStateSrv.create($scope); + $scope.grafana.style = $scope.dashboard.style; $scope.filter = filterSrv; @@ -78,10 +91,6 @@ function (angular, $, config, _) { }; }; - $scope.row_style = function(row) { - return { 'min-height': row.collapse ? '5px' : row.height }; - }; - $scope.panel_path =function(type) { if(type) { return 'app/panels/'+type.replace(".","/"); diff --git a/src/app/controllers/dashLoader.js b/src/app/controllers/dashboardNavCtrl.js similarity index 90% rename from src/app/controllers/dashLoader.js rename to src/app/controllers/dashboardNavCtrl.js index 27071213d8f..a58a2647945 100644 --- a/src/app/controllers/dashLoader.js +++ b/src/app/controllers/dashboardNavCtrl.js @@ -3,17 +3,19 @@ define([ 'lodash', 'moment', 'config', + 'store', 'filesaver' ], -function (angular, _, moment, config) { +function (angular, _, moment, config, store) { 'use strict'; var module = angular.module('grafana.controllers'); - module.controller('dashLoader', function($scope, $rootScope, $http, alertSrv, $location, playlistSrv, datasourceSrv) { + module.controller('DashboardNavCtrl', function($scope, $rootScope, alertSrv, $location, playlistSrv, datasourceSrv) { $scope.init = function() { $scope.db = datasourceSrv.getGrafanaDB(); + $scope.onAppEvent('save-dashboard', function() { $scope.saveDashboard(); }); @@ -21,19 +23,16 @@ function (angular, _, moment, config) { $scope.onAppEvent('zoom-out', function() { $scope.zoom(2); }); - }; - $scope.exitFullscreen = function() { - $scope.emitAppEvent('panel-fullscreen-exit'); }; $scope.set_default = function() { - window.localStorage.grafanaDashboardDefault = $location.path(); + store.set('grafanaDashboardDefault', $location.path()); alertSrv.set('Home Set','This page has been set as your default dashboard','success',5000); }; $scope.purge_default = function() { - delete window.localStorage.grafanaDashboardDefault; + store.delete('grafanaDashboardDefault'); alertSrv.set('Local Default Clear','Your default dashboard has been reset to the default','success', 5000); }; @@ -78,6 +77,7 @@ function (angular, _, moment, config) { .then(function(result) { alertSrv.set('Dashboard Saved', 'Dashboard has been saved as "' + result.title + '"','success', 5000); + $location.search({}); $location.path(result.url); $rootScope.$emit('dashboard-saved', $scope.dashboard); @@ -87,7 +87,9 @@ function (angular, _, moment, config) { }); }; - $scope.deleteDashboard = function(id) { + $scope.deleteDashboard = function(id, $event) { + $event.stopPropagation(); + if (!confirm('Are you sure you want to delete dashboard?')) { return; } @@ -135,6 +137,7 @@ function (angular, _, moment, config) { $scope.openSaveDropdown = function() { $scope.isFavorite = playlistSrv.isCurrentFavorite($scope.dashboard); + $scope.saveDropdownOpened = true; }; $scope.markAsFavorite = function() { diff --git a/src/app/controllers/grafanaCtrl.js b/src/app/controllers/grafanaCtrl.js index eaf3dfd0422..060c0bc0803 100644 --- a/src/app/controllers/grafanaCtrl.js +++ b/src/app/controllers/grafanaCtrl.js @@ -2,8 +2,10 @@ define([ 'angular', 'config', 'lodash', + 'jquery', + 'store', ], -function (angular, config, _) { +function (angular, config, _, $, store) { "use strict"; var module = angular.module('grafana.controllers'); @@ -11,20 +13,23 @@ function (angular, config, _) { module.controller('GrafanaCtrl', function($scope, alertSrv, grafanaVersion, $rootScope) { $scope.grafanaVersion = grafanaVersion[0] === '@' ? 'master' : grafanaVersion; + $scope.consoleEnabled = store.getBool('grafanaConsole'); + + $rootScope.profilingEnabled = store.getBool('profilingEnabled'); + $rootScope.performance = { loadStart: new Date().getTime() }; $scope.init = function() { $scope._ = _; - $scope.dashAlerts = alertSrv; - $scope.grafana = { - style: 'dark' - }; - $scope.consoleEnabled = (window.localStorage && window.localStorage.grafanaConsole === 'true'); + if ($rootScope.profilingEnabled) { $scope.initProfiling(); } + + $scope.dashAlerts = alertSrv; + $scope.grafana = { style: 'dark' }; }; $scope.toggleConsole = function() { $scope.consoleEnabled = !$scope.consoleEnabled; - window.localStorage.grafanaConsole = $scope.consoleEnabled ? 'true' : 'false'; + store.set('grafanaConsole', $scope.consoleEnabled); }; $rootScope.onAppEvent = function(name, callback) { @@ -46,6 +51,61 @@ function (angular, config, _) { "#E0F9D7","#FCEACA","#CFFAFF","#F9E2D2","#FCE2DE","#BADFF4","#F9D9F9","#DEDAF7" //7 ]; + $scope.getTotalWatcherCount = function() { + var count = 0; + var scopes = 0; + var root = $(document.getElementsByTagName('body')); + + var f = function (element) { + if (element.data().hasOwnProperty('$scope')) { + scopes++; + angular.forEach(element.data().$scope.$$watchers, function () { + count++; + }); + } + + angular.forEach(element.children(), function (childElement) { + f($(childElement)); + }); + }; + + f(root); + $rootScope.performance.scopeCount = scopes; + return count; + }; + + $scope.initProfiling = function() { + var count = 0; + + $scope.$watch(function digestCounter() { + count++; + }, function() { + }); + + $scope.onAppEvent('setup-dashboard', function() { + count = 0; + + setTimeout(function() { + console.log("Dashboard::Performance Total Digests: " + count); + console.log("Dashboard::Performance Total Watchers: " + $scope.getTotalWatcherCount()); + console.log("Dashboard::Performance Total ScopeCount: " + $rootScope.performance.scopeCount); + + var timeTaken = $rootScope.performance.allPanelsInitialized - $rootScope.performance.dashboardLoadStart; + console.log("Dashboard::Performance - All panels initialized in " + timeTaken + " ms"); + + // measure digest performance + var rootDigestStart = window.performance.now(); + for (var i = 0; i < 30; i++) { + $rootScope.$apply(); + } + console.log("Dashboard::Performance Root Digest " + ((window.performance.now() - rootDigestStart) / 30)); + + }, 3000); + + }); + + }; + $scope.init(); }); diff --git a/src/app/controllers/graphiteTarget.js b/src/app/controllers/graphiteTarget.js index 6b74455f59c..935ee1aeef0 100644 --- a/src/app/controllers/graphiteTarget.js +++ b/src/app/controllers/graphiteTarget.js @@ -289,7 +289,7 @@ function (angular, _, config, gfunc, Parser) { this.expandable = options.expandable; if (options.type === 'template') { - this.html = $sce.trustAsHtml("" + options.value + ""); + this.html = $sce.trustAsHtml(options.value); } else { this.html = $sce.trustAsHtml(this.value); diff --git a/src/app/controllers/row.js b/src/app/controllers/row.js index 364cf051dac..314559405ea 100644 --- a/src/app/controllers/row.js +++ b/src/app/controllers/row.js @@ -32,36 +32,13 @@ function (angular, app, _) { } }; - $scope.rowSpan = function(row) { - return _.reduce(row.panels, function(p,v) { - return p + v.span; - },0); - }; - // This can be overridden by individual panels $scope.close_edit = function() { $scope.$broadcast('render'); }; $scope.add_panel = function(panel) { - var rowSpan = $scope.rowSpan($scope.row); - var panelCount = $scope.row.panels.length; - var space = (12 - rowSpan) - panel.span; - - // try to make room of there is no space left - if (space <= 0) { - if (panelCount === 1) { - $scope.row.panels[0].span = 6; - panel.span = 6; - } - else if (panelCount === 2) { - $scope.row.panels[0].span = 4; - $scope.row.panels[1].span = 4; - panel.span = 4; - } - } - - $scope.row.panels.push(panel); + $scope.dashboard.add_panel(panel, $scope.row); }; $scope.delete_row = function() { @@ -100,45 +77,17 @@ function (angular, app, _) { }; $scope.duplicatePanel = function(panel, row) { - row = row || $scope.row; - var currentRowSpan = $scope.rowSpan(row); - if (currentRowSpan <= 9) { - row.panels.push(angular.copy(panel)); - } - else { - var rowsList = $scope.dashboard.rows; - var rowIndex = _.indexOf(rowsList, row); - if (rowIndex === rowsList.length - 1) { - var newRow = angular.copy($scope.row); - newRow.panels = []; - $scope.dashboard.rows.push(newRow); - $scope.duplicatePanel(panel, newRow); - } - else { - $scope.duplicatePanel(panel, rowsList[rowIndex+1]); - } - } + $scope.dashboard.duplicatePanel(panel, row || $scope.row); }; $scope.reset_panel = function(type) { - var - defaultSpan = 12, - _as = 12-$scope.rowSpan($scope.row); + var defaultSpan = 12; + var _as = 12 - $scope.dashboard.rowSpan($scope.row); $scope.panel = { error : false, - /** @scratch /panels/1 - * span:: A number, 1-12, that describes the width of the panel. - */ span : _as < defaultSpan && _as > 0 ? _as : defaultSpan, - /** @scratch /panels/1 - * editable:: Enable or disable the edit button the the panel - */ editable: true, - /** @scratch /panels/1 - * type:: The type of panel this object contains. Each panel type will require additional - * properties. See the panel types list to the right. - */ type : type }; @@ -155,12 +104,37 @@ function (angular, app, _) { $scope.row.height = fixRowHeight($scope.row.height); }; - /** @scratch /panels/2 - * -- - */ - $scope.init(); }); + module.directive('rowHeight', function() { + return function(scope, element) { + scope.$watchGroup(['row.collapse', 'row.height'], function() { + element[0].style.minHeight = scope.row.collapse ? '5px' : scope.row.height; + }); + }; + }); + + module.directive('panelWidth', function() { + return function(scope, element) { + scope.$watch('panel.span', function() { + element[0].style.width = ((scope.panel.span / 1.2) * 10) + '%'; + }); + }; + }); + + module.directive('panelDropZone', function() { + return function(scope, element) { + scope.$watch('dashboard.$$panelDragging', function(newVal) { + if (newVal && scope.dashboard.rowSpan(scope.row) < 10) { + element.show(); + } + else { + element.hide(); + } + }); + }; + }); + }); diff --git a/src/app/controllers/search.js b/src/app/controllers/search.js index 6c504e1b925..de0ec44045a 100644 --- a/src/app/controllers/search.js +++ b/src/app/controllers/search.js @@ -41,6 +41,7 @@ function (angular, _, config, $) { var selectedDash = $scope.results.dashboards[$scope.selectedIndex]; if (selectedDash) { + $location.search({}); $location.path("/dashboard/db/" + selectedDash.id); setTimeout(function() { $('body').click(); // hack to force dropdown to close; @@ -49,7 +50,12 @@ function (angular, _, config, $) { } }; - $scope.shareDashboard = function(title, id) { + $scope.goToDashboard = function(id) { + $location.path("/dashboard/db/" + id); + }; + + $scope.shareDashboard = function(title, id, $event) { + $event.stopPropagation(); var baseUrl = window.location.href.replace(window.location.hash,''); $scope.share = { @@ -98,6 +104,7 @@ function (angular, _, config, $) { $element.next().find('.dropdown-toggle').dropdown('toggle'); } + $scope.searchOpened = true; $scope.giveSearchFocus = $scope.giveSearchFocus + 1; $scope.query.query = 'title:'; $scope.search(); diff --git a/src/app/directives/addGraphiteFunc.js b/src/app/directives/addGraphiteFunc.js index ca5943da508..6898b838845 100644 --- a/src/app/directives/addGraphiteFunc.js +++ b/src/app/directives/addGraphiteFunc.js @@ -97,4 +97,4 @@ function (angular, app, _, $, gfunc) { }; }); } -}); \ No newline at end of file +}); diff --git a/src/app/directives/addPanel.js b/src/app/directives/addPanel.js deleted file mode 100644 index e3f0fe7fc3d..00000000000 --- a/src/app/directives/addPanel.js +++ /dev/null @@ -1,35 +0,0 @@ -define([ - 'angular', - 'app', - 'lodash' -], -function (angular, app, _) { - 'use strict'; - - angular - .module('grafana.directives') - .directive('addPanel', function($compile) { - return { - restrict: 'A', - link: function($scope, elem) { - - $scope.$on("$destroy",function() { - elem.remove(); - }); - - $scope.$watch('panel.type', function() { - var _type = $scope.panel.type; - $scope.reset_panel(_type); - if(!_.isUndefined($scope.panel.type)) { - $scope.panel.loadingEditor = true; - $scope.require(['panels/'+$scope.panel.type.replace(".","/") +'/module'], function () { - var template = '
'; - elem.html($compile(angular.element(template))($scope)); - $scope.panel.loadingEditor = false; - }); - } - }); - } - }; - }); -}); \ No newline at end of file diff --git a/src/app/directives/all.js b/src/app/directives/all.js index ed97de93780..5236a1a619d 100644 --- a/src/app/directives/all.js +++ b/src/app/directives/all.js @@ -1,5 +1,4 @@ define([ - './addPanel', './arrayJoin', './dashUpload', './grafanaPanel', @@ -17,4 +16,4 @@ define([ './graphiteFuncEditor', './grafanaVersionCheck', './influxdbFuncEditor' -], function () {}); \ No newline at end of file +], function () {}); diff --git a/src/app/directives/bodyClass.js b/src/app/directives/bodyClass.js index dc3e08053e9..6d3c6d32e15 100644 --- a/src/app/directives/bodyClass.js +++ b/src/app/directives/bodyClass.js @@ -15,7 +15,7 @@ function (angular, app, _) { var lastPulldownVal; var lastHideControlsVal; - $scope.$watch('dashboard.pulldowns', function() { + $scope.$watchCollection('dashboard.pulldowns', function() { if (!$scope.dashboard) { return; } @@ -26,7 +26,7 @@ function (angular, app, _) { elem.toggleClass('submenu-controls-visible', panelEnabled); lastPulldownVal = panelEnabled; } - }, true); + }); $scope.$watch('dashboard.hideControls', function() { if (!$scope.dashboard) { @@ -49,4 +49,4 @@ function (angular, app, _) { }; }); -}); \ No newline at end of file +}); diff --git a/src/app/directives/configModal.js b/src/app/directives/configModal.js index e3d21253260..b7e579e7026 100644 --- a/src/app/directives/configModal.js +++ b/src/app/directives/configModal.js @@ -45,4 +45,4 @@ function (angular, _, $) { } }; }); -}); \ No newline at end of file +}); diff --git a/src/app/directives/grafanaGraph.js b/src/app/directives/grafanaGraph.js index 323db2141bd..222bde78b26 100755 --- a/src/app/directives/grafanaGraph.js +++ b/src/app/directives/grafanaGraph.js @@ -21,7 +21,6 @@ function (angular, $, kbn, moment, _) { var legendSideLastValue = null; scope.$on('refresh',function() { - if (scope.otherPanelInFullscreenMode()) { return; } scope.get_data(); }); @@ -39,15 +38,14 @@ function (angular, $, kbn, moment, _) { // Receive render events scope.$on('render',function(event, renderData) { data = renderData || data; + if (!data) { + scope.get_data(); + return; + } annotations = data.annotations || annotations; render_panel(); }); - // Re-render if the window is resized - angular.element(window).bind('resize', function() { - render_panel(); - }); - function setElementHeight() { try { var height = scope.height || scope.panel.height || scope.row.height; @@ -115,7 +113,7 @@ function (angular, $, kbn, moment, _) { lines: { show: panel.lines, zero: false, - fill: panel.fill === 0 ? 0.001 : panel.fill/10, + fill: translateFillOption(panel.fill), lineWidth: panel.linewidth, steps: panel.steppedLine }, @@ -151,11 +149,12 @@ function (angular, $, kbn, moment, _) { }; for (var i = 0; i < data.length; i++) { - var _d = data[i].getFlotPairs(panel.nullPointMode, panel.y_formats); - data[i].data = _d; + var series = data[i]; + series.applySeriesOverrides(panel.seriesOverrides); + series.data = series.getFlotPairs(panel.nullPointMode, panel.y_formats); } - if (panel.bars && data.length && data[0].info.timeStep) { + if (data.length && data[0].info.timeStep) { options.series.bars.barWidth = data[0].info.timeStep / 1.5; } @@ -164,21 +163,27 @@ function (angular, $, kbn, moment, _) { addAnnotations(options); configureAxisOptions(data, options); + var sortedSeries = _.sortBy(data, function(series) { return series.zindex; }); + // if legend is to the right delay plot draw a few milliseconds // so the legend width calculation can be done if (shouldDelayDraw(panel)) { legendSideLastValue = panel.legend.rightSide; setTimeout(function() { - plot = $.plot(elem, data, options); + plot = $.plot(elem, sortedSeries, options); addAxisLabels(); }, 50); } else { - plot = $.plot(elem, data, options); + plot = $.plot(elem, sortedSeries, options); addAxisLabels(); } } + function translateFillOption(fill) { + return fill === 0 ? 0.001 : fill/10; + } + function shouldDelayDraw(panel) { if (panel.legend.rightSide) { return true; @@ -300,9 +305,7 @@ function (angular, $, kbn, moment, _) { } function configureAxisMode(axis, format) { - if (format !== 'none') { - axis.tickFormatter = kbn.getFormatFunction(format, 1); - } + axis.tickFormatter = kbn.getFormatFunction(format, 1); } function time_format(interval, ticks, min, max) { @@ -353,10 +356,7 @@ function (angular, $, kbn, moment, _) { } value = kbn.getFormatFunction(format, 2)(value); - - timestamp = dashboard.timezone === 'browser' ? - moment(item.datapoint[0]).format('YYYY-MM-DD HH:mm:ss') : - moment.utc(item.datapoint[0]).format('YYYY-MM-DD HH:mm:ss'); + timestamp = dashboard.formatDate(item.datapoint[0]); $tooltip.html(group + value + " @ " + timestamp).place_tt(pos.pageX, pos.pageY); } else { diff --git a/src/app/directives/grafanaPanel.js b/src/app/directives/grafanaPanel.js index 32817c9b161..0229277b038 100644 --- a/src/app/directives/grafanaPanel.js +++ b/src/app/directives/grafanaPanel.js @@ -8,7 +8,7 @@ function (angular, $) { angular .module('grafana.directives') - .directive('grafanaPanel', function($compile) { + .directive('grafanaPanel', function($compile, $parse) { var container = ''; var content = ''; @@ -18,11 +18,13 @@ function (angular, $) { '