diff --git a/CHANGELOG.md b/CHANGELOG.md index e7e6df626a5..090af032f30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,8 +3,36 @@ **UI Improvements* - [Issue #770](https://github.com/grafana/grafana/issues/770). UI: Panel dropdown menu replaced with a new panel menu +**Graph** - [Issue #877](https://github.com/grafana/grafana/issues/877). Graph: Smart auto decimal precision when using scaled unit formats - [Issue #850](https://github.com/grafana/grafana/issues/850). Graph: Shared tooltip that shows multiple series & crosshair line, thx @toni-moreno +- [Issue #940](https://github.com/grafana/grafana/issues/940). Graph: New series style override option "Fill below to", useful to visualize max & min as a shadow for the mean +- [Issue #1030](https://github.com/grafana/grafana/issues/1030). Graph: Legend table display/look changed, now includes column headers for min/max/avg, and full width (unless on right side) +- [Issue #861](https://github.com/grafana/grafana/issues/861). Graph: Export graph time series data as csv file + +**New Panels** +- [Issue #951](https://github.com/grafana/grafana/issues/951). SingleStat: New singlestat panel + +**Misc** +- [Issue #938](https://github.com/grafana/grafana/issues/938). Panel: Plugin panels now reside outside of app/panels directory +- [Issue #952](https://github.com/grafana/grafana/issues/952). Help: Shortcut "?" to open help modal with list of all shortcuts +- [Issue #991](https://github.com/grafana/grafana/issues/991). ScriptedDashboard: datasource services are now available in scripted dashboards, you can query datasource for metric keys, generate dashboards, and even save them in a scripted dashboard (see scripted_gen_and_save.js for example) +- [Issue #1041](https://github.com/grafana/grafana/issues/1041). Panel: All panels can now have links to other dashboards or absolute links, these links are available in the panel menu. + +**Changes** +- [Issue #1007](https://github.com/grafana/grafana/issues/1007). Graph: Series hide/show toggle changed to be default exclusive, so clicking on a series name will show only that series. (SHIFT or meta)+click will toggle hide/show. + +**OpenTSDB** +- [Issue #930](https://github.com/grafana/grafana/issues/930). OpenTSDB: Adding counter max and counter reset value to open tsdb query editor, thx @rsimiciuc +- [Issue #917](https://github.com/grafana/grafana/issues/917). OpenTSDB: Templating support for OpenTSDB series name and tags, thx @mchataigner + +**InfluxDB** +- [Issue #714](https://github.com/grafana/grafana/issues/714). InfluxDB: Support for sub second resolution graphs + +**Fixes** +- [Issue #925](https://github.com/grafana/grafana/issues/925). Graph: bar width calculation fix for some edge cases (bars would render on top of each other) +- [Issue #505](https://github.com/grafana/grafana/issues/505). Graph: fix for second y axis tick unit labels wrapping on the next line +- [Issue #987](https://github.com/grafana/grafana/issues/987). Dashboard: Collapsed rows became invisible when hide controls was enabled ======= # 1.8.1 (2014-09-30) diff --git a/src/app/app.js b/src/app/app.js index 91094ec38db..267d0081a90 100644 --- a/src/app/app.js +++ b/src/app/app.js @@ -56,7 +56,6 @@ function (angular, $, _, appLevelRequire, config) { register_fns.factory = $provide.factory; register_fns.service = $provide.service; register_fns.filter = $filterProvider.register; - }); var apps_deps = [ @@ -78,6 +77,8 @@ function (angular, $, _, appLevelRequire, config) { }); var preBootRequires = [ + 'services/all', + 'features/all', 'controllers/all', 'directives/all', 'filters/all', diff --git a/src/app/components/kbn.js b/src/app/components/kbn.js index cb084b1c166..ecef4bd164e 100644 --- a/src/app/components/kbn.js +++ b/src/app/components/kbn.js @@ -316,6 +316,10 @@ function($, _, moment) { kbn.formatFuncCreator = function(factor, extArray) { return function(size, decimals, scaledDecimals) { + if (size === null) { + return ""; + } + var steps = 0; while (Math.abs(size) >= factor) { @@ -331,6 +335,10 @@ function($, _, moment) { }; kbn.toFixed = function(value, decimals) { + if (value === null) { + return ""; + } + var factor = decimals ? Math.pow(10, decimals) : 1; var formatted = String(Math.round(value * factor) / factor); @@ -359,6 +367,8 @@ function($, _, moment) { kbn.valueFormats.none = kbn.toFixed; kbn.valueFormats.ms = function(size, decimals, scaledDecimals) { + if (size === null) { return ""; } + if (Math.abs(size) < 1000) { return kbn.toFixed(size, decimals) + " ms"; } @@ -383,6 +393,8 @@ function($, _, moment) { }; kbn.valueFormats.s = function(size, decimals, scaledDecimals) { + if (size === null) { return ""; } + if (Math.abs(size) < 600) { return kbn.toFixed(size, decimals) + " s"; } @@ -407,6 +419,8 @@ function($, _, moment) { }; kbn.valueFormats['µs'] = function(size, decimals, scaledDecimals) { + if (size === null) { return ""; } + if (Math.abs(size) < 1000) { return kbn.toFixed(size, decimals) + " µs"; } @@ -419,6 +433,8 @@ function($, _, moment) { }; kbn.valueFormats.ns = function(size, decimals, scaledDecimals) { + if (size === null) { return ""; } + if (Math.abs(size) < 1000) { return kbn.toFixed(size, decimals) + " ns"; } @@ -443,6 +459,17 @@ function($, _, moment) { .replace(/ +/g,'-'); }; + kbn.exportSeriesListToCsv = function(seriesList) { + var text = 'Series;Time;Value\n'; + _.each(seriesList, function(series) { + _.each(series.datapoints, function(dp) { + text += series.alias + ';' + new Date(dp[1]).toISOString() + ';' + dp[0] + '\n'; + }); + }); + var blob = new Blob([text], { type: "text/csv;charset=utf-8" }); + window.saveAs(blob, 'grafana_data_export.csv'); + }; + kbn.stringToJsRegex = function(str) { if (str[0] !== '/') { return new RegExp(str); diff --git a/src/app/components/panelmeta.js b/src/app/components/panelmeta.js new file mode 100644 index 00000000000..4fd97e9d02d --- /dev/null +++ b/src/app/components/panelmeta.js @@ -0,0 +1,45 @@ +define([ +], +function () { + "use strict"; + + function PanelMeta(options) { + this.description = options.description; + this.titlePos = options.titlePos; + this.fullscreen = options.fullscreen; + this.menu = []; + this.editorTabs = []; + this.extendedMenu = []; + + if (options.fullscreen) { + this.addMenuItem('view', 'icon-eye-open', 'toggleFullscreen(false)'); + } + + this.addMenuItem('edit', 'icon-cog', 'editPanel()'); + this.addMenuItem('duplicate', 'icon-copy', 'duplicatePanel()'); + this.addMenuItem('share', 'icon-share', 'sharePanel()'); + + this.addEditorTab('General', 'app/partials/panelgeneral.html'); + + if (options.metricsEditor) { + this.addEditorTab('Metrics', 'app/partials/metrics.html'); + } + + this.addExtendedMenuItem('Panel JSON', '', 'editPanelJson()'); + } + + PanelMeta.prototype.addMenuItem = function(text, icon, click) { + this.menu.push({text: text, icon: icon, click: click}); + }; + + PanelMeta.prototype.addExtendedMenuItem = function(text, icon, click) { + this.extendedMenu.push({text: text, icon: icon, click: click}); + }; + + PanelMeta.prototype.addEditorTab = function(title, src) { + this.editorTabs.push({title: title, src: src}); + }; + + return PanelMeta; + +}); diff --git a/src/app/components/require.config.js b/src/app/components/require.config.js index 9e0c271a40f..ff8540e5c40 100644 --- a/src/app/components/require.config.js +++ b/src/app/components/require.config.js @@ -30,7 +30,6 @@ require.config({ bootstrap: '../vendor/bootstrap/bootstrap', jquery: '../vendor/jquery/jquery-2.1.1.min', - 'jquery-ui': '../vendor/jquery/jquery-ui-1.10.3', 'extend-jquery': 'components/extend-jquery', @@ -42,6 +41,7 @@ require.config({ 'jquery.flot.stackpercent':'../vendor/jquery/jquery.flot.stackpercent', 'jquery.flot.time': '../vendor/jquery/jquery.flot.time', 'jquery.flot.crosshair': '../vendor/jquery/jquery.flot.crosshair', + 'jquery.flot.fillbelow': '../vendor/jquery/jquery.flot.fillbelow', modernizr: '../vendor/modernizr-2.6.1', @@ -77,7 +77,6 @@ require.config({ // simple dependency declaration // - 'jquery-ui': ['jquery'], 'jquery.flot': ['jquery'], 'jquery.flot.pie': ['jquery', 'jquery.flot'], 'jquery.flot.events': ['jquery', 'jquery.flot'], @@ -86,8 +85,9 @@ require.config({ 'jquery.flot.stackpercent':['jquery', 'jquery.flot'], 'jquery.flot.time': ['jquery', 'jquery.flot'], 'jquery.flot.crosshair':['jquery', 'jquery.flot'], + 'jquery.flot.fillbelow':['jquery', 'jquery.flot'], 'angular-cookies': ['angular'], - 'angular-dragdrop': ['jquery','jquery-ui','angular'], + 'angular-dragdrop': ['jquery', 'angular'], 'angular-loader': ['angular'], 'angular-mocks': ['angular'], 'angular-resource': ['angular'], diff --git a/src/app/components/settings.js b/src/app/components/settings.js index 6afba222b4b..532b2e2b133 100644 --- a/src/app/components/settings.js +++ b/src/app/components/settings.js @@ -15,12 +15,16 @@ function (_, crypto) { var defaults = { datasources : {}, window_title_prefix : 'Grafana - ', - panels : ['graph', 'text'], + panels : { + 'graph': { path: 'panels/graph' }, + 'singlestat': { path: 'panels/singlestat' }, + 'text': { path: 'panels/text' } + }, plugins : {}, default_route : '/dashboard/file/default.json', playlist_timespan : "1m", unsaved_changes_warning : true, - search : { max_results: 16 }, + search : { max_results: 100 }, admin : {} }; @@ -76,7 +80,7 @@ function (_, crypto) { }); if (settings.plugins.panels) { - settings.panels = _.union(settings.panels, settings.plugins.panels); + _.extend(settings.panels, settings.plugins.panels); } if (!settings.plugins.dependencies) { diff --git a/src/app/components/timeSeries.js b/src/app/components/timeSeries.js index a448a93649c..f9329141640 100644 --- a/src/app/components/timeSeries.js +++ b/src/app/components/timeSeries.js @@ -7,8 +7,12 @@ function (_, kbn) { function TimeSeries(opts) { this.datapoints = opts.datapoints; - this.info = opts.info; - this.label = opts.info.alias; + this.label = opts.alias; + this.id = opts.alias; + this.alias = opts.alias; + this.color = opts.color; + this.valueFormater = kbn.valueFormats.none; + this.stats = {}; } function matchSeriesOverride(aliasOrRegex, seriesAlias) { @@ -30,13 +34,13 @@ function (_, kbn) { this.lines = {}; this.points = {}; this.bars = {}; - this.info.yaxis = 1; + this.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)) { + if (!matchSeriesOverride(override.alias, this.alias)) { continue; } if (override.lines !== void 0) { this.lines.show = override.lines; } @@ -48,8 +52,10 @@ function (_, kbn) { 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.fillBelowTo !== void 0) { this.fillBelowTo = override.fillBelowTo; } + if (override.yaxis !== void 0) { - this.info.yaxis = override.yaxis; + this.yaxis = override.yaxis; } } }; @@ -57,12 +63,12 @@ function (_, kbn) { TimeSeries.prototype.getFlotPairs = function (fillStyle) { var result = []; - this.color = this.info.color; - this.yaxis = this.info.yaxis; - - this.info.total = 0; - this.info.max = -212312321312; - this.info.min = 212312321312; + this.stats.total = 0; + this.stats.max = Number.MIN_VALUE; + this.stats.min = Number.MAX_VALUE; + this.stats.avg = null; + this.stats.current = null; + this.allIsNull = true; var ignoreNulls = fillStyle === 'connected'; var nullAsZero = fillStyle === 'null as zero'; @@ -81,38 +87,47 @@ function (_, kbn) { } if (_.isNumber(currentValue)) { - this.info.total += currentValue; + this.stats.total += currentValue; + this.allIsNull = false; } - if (currentValue > this.info.max) { - this.info.max = currentValue; + if (currentValue > this.stats.max) { + this.stats.max = currentValue; } - if (currentValue < this.info.min) { - this.info.min = currentValue; + if (currentValue < this.stats.min) { + this.stats.min = currentValue; } - result.push([currentTime * 1000, currentValue]); + result.push([currentTime, currentValue]); } - if (result.length > 2) { - this.info.timeStep = result[1][0] - result[0][0]; + if (this.datapoints.length >= 2) { + this.stats.timeStep = this.datapoints[1][1] - this.datapoints[0][1]; } + if (this.stats.max === Number.MIN_VALUE) { this.stats.max = null; } + if (this.stats.min === Number.MAX_VALUE) { this.stats.min = null; } + if (result.length) { - this.info.avg = (this.info.total / result.length); - this.info.current = result[result.length-1][1]; + this.stats.avg = (this.stats.total / result.length); + this.stats.current = result[result.length-1][1]; + if (this.stats.current === null && result.length > 1) { + this.stats.current = result[result.length-2][1]; + } } return result; }; TimeSeries.prototype.updateLegendValues = function(formater, decimals, scaledDecimals) { - this.info.avg = this.info.avg != null ? formater(this.info.avg, decimals, scaledDecimals) : null; - this.info.current = this.info.current != null ? formater(this.info.current, decimals, scaledDecimals) : null; - this.info.min = this.info.min != null ? formater(this.info.min, decimals, scaledDecimals) : null; - this.info.max = this.info.max != null ? formater(this.info.max, decimals, scaledDecimals) : null; - this.info.total = this.info.total != null ? formater(this.info.total, decimals, scaledDecimals) : null; + this.valueFormater = formater; + this.decimals = decimals; + this.scaledDecimals = scaledDecimals; + }; + + TimeSeries.prototype.formatValue = function(value) { + return this.valueFormater(value, this.decimals, this.scaledDecimals); }; return TimeSeries; diff --git a/src/app/controllers/dashboardCtrl.js b/src/app/controllers/dashboardCtrl.js index dfe97682aa0..0aee19d82e4 100644 --- a/src/app/controllers/dashboardCtrl.js +++ b/src/app/controllers/dashboardCtrl.js @@ -3,7 +3,6 @@ define([ 'jquery', 'config', 'lodash', - 'services/all', ], function (angular, $, config, _) { "use strict"; @@ -18,11 +17,10 @@ function (angular, $, config, _) { templateValuesSrv, dashboardSrv, dashboardViewStateSrv, - panelMoveSrv, $timeout) { $scope.editor = { index: 0 }; - $scope.panelNames = config.panels; + $scope.panelNames = _.map(config.panels, function(value, key) { return key; }); var resizeEventTimeout; this.init = function(dashboardData) { @@ -51,7 +49,6 @@ function (angular, $, config, _) { // init services timeSrv.init($scope.dashboard); templateValuesSrv.init($scope.dashboard, $scope.dashboardViewState); - panelMoveSrv.init($scope.dashboard, $scope); $scope.checkFeatureToggles(); dashboardKeybindings.shortcuts($scope); @@ -92,21 +89,12 @@ function (angular, $, config, _) { }; }; - $scope.edit_path = function(type) { - var p = $scope.panel_path(type); - if(p) { - return p+'/editor.html'; - } else { - return false; - } + $scope.panelEditorPath = function(type) { + return 'app/' + config.panels[type].path + '/editor.html'; }; - $scope.panel_path =function(type) { - if(type) { - return 'app/panels/'+type.replace(".","/"); - } else { - return false; - } + $scope.pulldownEditorPath = function(type) { + return 'app/panels/'+type+'/editor.html'; }; $scope.showJsonEditor = function(evt, options) { @@ -120,12 +108,23 @@ function (angular, $, config, _) { $scope.submenuEnabled = $scope.dashboard.templating.enable || $scope.dashboard.annotations.enable; }; - $scope.setEditorTabs = function(panelMeta) { - $scope.editorTabs = ['General','Panel']; - if(!_.isUndefined(panelMeta.editorTabs)) { - $scope.editorTabs = _.union($scope.editorTabs,_.pluck(panelMeta.editorTabs,'title')); + $scope.onDrop = function(panelId, row, dropTarget) { + var info = $scope.dashboard.getPanelInfoById(panelId); + if (dropTarget) { + var dropInfo = $scope.dashboard.getPanelInfoById(dropTarget.id); + dropInfo.row.panels[dropInfo.index] = info.panel; + info.row.panels[info.index] = dropTarget; + var dragSpan = info.panel.span; + info.panel.span = dropTarget.span; + dropTarget.span = dragSpan; } - return $scope.editorTabs; + else { + info.row.panels.splice(info.index, 1); + info.panel.span = 12 - $scope.dashboard.rowSpan(row); + row.panels.push(info.panel); + } + + $rootScope.$broadcast('render'); }; }); diff --git a/src/app/controllers/dashboardNavCtrl.js b/src/app/controllers/dashboardNavCtrl.js index 084d1e215a6..3e3321b7d0d 100644 --- a/src/app/controllers/dashboardNavCtrl.js +++ b/src/app/controllers/dashboardNavCtrl.js @@ -93,12 +93,18 @@ function (angular, _, moment, config, store) { }; $scope.deleteDashboard = function(evt, options) { - if (!confirm('Do you want to delete dashboard ' + options.title + ' ?')) { - return; - } - if (!$scope.isAdmin()) { return false; } + $scope.appEvent('confirm-modal', { + title: 'Delete dashboard', + text: 'Do you want to delete dashboard ' + options.title + '?', + onConfirm: function() { + $scope.deleteDashboardConfirmed(options); + } + }); + }; + + $scope.deleteDashboardConfirmed = function(options) { var id = options.id; $scope.db.deleteDashboard(id).then(function(id) { $scope.appEvent('alert-success', ['Dashboard Deleted', id + ' has been deleted']); diff --git a/src/app/controllers/graphiteTarget.js b/src/app/controllers/graphiteTarget.js index 27299474bc0..3d899ac152f 100644 --- a/src/app/controllers/graphiteTarget.js +++ b/src/app/controllers/graphiteTarget.js @@ -201,7 +201,7 @@ function (angular, _, config, gfunc, Parser) { $scope.targetTextChanged = function() { parseTarget(); - $scope.$parent.get_data(); + $scope.get_data(); }; $scope.targetChanged = function() { @@ -275,6 +275,10 @@ function (angular, _, config, gfunc, Parser) { } }; + $scope.moveMetricQuery = function(fromIndex, toIndex) { + _.move($scope.panel.targets, fromIndex, toIndex); + }; + $scope.duplicate = function() { var clone = angular.copy($scope.target); $scope.panel.targets.push(clone); diff --git a/src/app/controllers/influxTargetCtrl.js b/src/app/controllers/influxTargetCtrl.js index b8101ab9577..e2b1a5234d4 100644 --- a/src/app/controllers/influxTargetCtrl.js +++ b/src/app/controllers/influxTargetCtrl.js @@ -1,7 +1,8 @@ define([ - 'angular' + 'angular', + 'lodash' ], -function (angular) { +function (angular, _) { 'use strict'; var module = angular.module('grafana.controllers'); @@ -83,10 +84,11 @@ function (angular) { }; $scope.listSeries = function(query, callback) { - if (!seriesList || query === '') { + if (query !== '') { seriesList = []; - $scope.datasource.listSeries().then(function(series) { + $scope.datasource.listSeries(query).then(function(series) { seriesList = series; + console.log(series); callback(seriesList); }); } @@ -95,6 +97,10 @@ function (angular) { } }; + $scope.moveMetricQuery = function(fromIndex, toIndex) { + _.move($scope.panel.targets, fromIndex, toIndex); + }; + $scope.duplicate = function() { var clone = angular.copy($scope.target); $scope.panel.targets.push(clone); diff --git a/src/app/controllers/row.js b/src/app/controllers/row.js index 55a838826bc..c92155d8d7e 100644 --- a/src/app/controllers/row.js +++ b/src/app/controllers/row.js @@ -47,9 +47,13 @@ function (angular, app, _) { }; $scope.delete_row = function() { - if (confirm("Are you sure you want to delete this row?")) { - $scope.dashboard.rows = _.without($scope.dashboard.rows, $scope.row); - } + $scope.appEvent('confirm-modal', { + title: 'Delete row', + text: 'Are you sure you want to delete this row?', + onConfirm: function() { + $scope.dashboard.rows = _.without($scope.dashboard.rows, $scope.row); + } + }); }; $scope.move_row = function(direction) { @@ -76,9 +80,13 @@ function (angular, app, _) { }; $scope.remove_panel_from_row = function(row, panel) { - if (confirm('Are you sure you want to remove this ' + panel.type + ' panel?')) { - row.panels = _.without(row.panels,panel); - } + $scope.appEvent('confirm-modal', { + title: 'Remove panel', + text: 'Are you sure you want to remove this panel?', + onConfirm: function() { + row.panels = _.without(row.panels, panel); + } + }); }; $scope.replacePanel = function(newPanel, oldPanel) { @@ -94,15 +102,12 @@ function (angular, app, _) { }); }; - $scope.duplicatePanel = function(panel, row) { - $scope.dashboard.duplicatePanel(panel, row || $scope.row); - }; - $scope.reset_panel = function(type) { var defaultSpan = 12; var _as = 12 - $scope.dashboard.rowSpan($scope.row); $scope.panel = { + title: 'no title [click here]', error : false, span : _as < defaultSpan && _as > 0 ? _as : defaultSpan, editable: true, @@ -144,13 +149,18 @@ function (angular, app, _) { module.directive('panelDropZone', function() { return function(scope, element) { - scope.$watch('dashboard.$$panelDragging', function(newVal) { - if (newVal && scope.dashboard.rowSpan(scope.row) < 10) { + scope.$on("ANGULAR_DRAG_START", function() { + var dropZoneSpan = 12 - scope.dashboard.rowSpan(scope.row); + + if (dropZoneSpan > 0) { + element.find('.panel-container').css('height', scope.row.height); + element[0].style.width = ((dropZoneSpan / 1.2) * 10) + '%'; element.show(); } - else { - element.hide(); - } + }); + + scope.$on("ANGULAR_DRAG_END", function() { + element.hide(); }); }; }); diff --git a/src/app/controllers/sharePanelCtrl.js b/src/app/controllers/sharePanelCtrl.js index e52dc297152..39909b20e19 100644 --- a/src/app/controllers/sharePanelCtrl.js +++ b/src/app/controllers/sharePanelCtrl.js @@ -27,19 +27,12 @@ function (angular, _) { } var panelId = $scope.panel.id; - var range = timeSrv.timeRange(false); var params = angular.copy($location.search()); - if (_.isString(range.to) && range.to.indexOf('now')) { - range = timeSrv.timeRange(); - } - + var range = timeSrv.timeRangeForUrl(); params.from = range.from; params.to = range.to; - if (_.isDate(params.from)) { params.from = params.from.getTime(); } - if (_.isDate(params.to)) { params.to = params.to.getTime(); } - if ($scope.includeTemplateVars) { _.each(templateSrv.variables, function(variable) { params['var-' + variable.name] = variable.current.text; @@ -66,11 +59,13 @@ function (angular, _) { var paramsArray = []; _.each(params, function(value, key) { - var str = key; - if (value !== true) { - str += '=' + encodeURIComponent(value); + if (value === null) { return; } + if (value === true) { + paramsArray.push(key); + } else { + key += '=' + encodeURIComponent(value); + paramsArray.push(key); } - paramsArray.push(str); }); $scope.shareUrl = baseUrl + "?" + paramsArray.join('&') ; diff --git a/src/app/dashboards/default.json b/src/app/dashboards/default.json index 241e663d5ea..931d4c6f802 100644 --- a/src/app/dashboards/default.json +++ b/src/app/dashboards/default.json @@ -101,7 +101,6 @@ "legend_counts": true, "timezone": "browser", "percentage": false, - "zerofill": true, "nullPointMode": "connected", "steppedLine": false, "tooltip": { diff --git a/src/app/dashboards/scripted.js b/src/app/dashboards/scripted.js index b8f428a9834..f4a438b7854 100644 --- a/src/app/dashboards/scripted.js +++ b/src/app/dashboards/scripted.js @@ -68,6 +68,17 @@ for (var i = 0; i < rows; i++) { 'target': "randomWalk('random walk2')" } ], + seriesOverrides: [ + { + alias: '/random/', + yaxis: 2, + fill: 0, + linewidth: 5 + } + ], + tooltip: { + shared: true + } } ] }); diff --git a/src/app/dashboards/scripted_gen_and_save.js b/src/app/dashboards/scripted_gen_and_save.js new file mode 100644 index 00000000000..d874b3fc28e --- /dev/null +++ b/src/app/dashboards/scripted_gen_and_save.js @@ -0,0 +1,95 @@ +/* global _ */ + +/* + * Complex scripted dashboard + * This script generates a dashboard object that Grafana can load. It also takes a number of user + * supplied URL parameters (int ARGS variable) + * + * Return a dashboard object, or a function + * + * For async scripts, return a function, this function must take a single callback function as argument, + * call this callback function with the dashboard object (look at scripted_async.js for an example) + */ + +'use strict'; + +// accessable variables in this scope +var window, document, ARGS, $, jQuery, moment, kbn, services, _; + +// default datasource +var datasource = services.datasourceSrv.default; +// get datasource used for saving dashboards +var dashboardDB = services.datasourceSrv.getGrafanaDB(); + +var targets = []; + +function getTargets(path) { + return datasource.metricFindQuery(path + '.*').then(function(result) { + if (!result) { + return null; + } + + if (targets.length === 10) { + return null; + } + + var promises = _.map(result, function(metric) { + if (metric.expandable) { + return getTargets(path + "." + metric.text); + } + else { + targets.push(path + '.' + metric.text); + } + return null; + }); + + return services.$q.all(promises); + }); +} + +function createDashboard(target, index) { + // Intialize a skeleton with nothing but a rows array and service object + var dashboard = { rows : [] }; + dashboard.title = 'Scripted dash ' + index; + dashboard.time = { + from: "now-6h", + to: "now" + }; + + dashboard.rows.push({ + title: 'Chart', + height: '300px', + panels: [ + { + title: 'Events', + type: 'graph', + span: 12, + targets: [ {target: target} ] + } + ] + }); + + return dashboard; +} + +function saveDashboard(dashboard) { + var model = services.dashboardSrv.create(dashboard); + dashboardDB.saveDashboard(model); +} + +return function(callback) { + + getTargets('apps').then(function() { + console.log('targets: ', targets); + _.each(targets, function(target, index) { + var dashboard = createDashboard(target, index); + saveDashboard(dashboard); + + if (index === targets.length - 1) { + callback(dashboard); + } + }); + }); + +}; + diff --git a/src/app/dashboards/template_vars.json b/src/app/dashboards/template_vars.json index affe7727ce2..43e9e37836c 100644 --- a/src/app/dashboards/template_vars.json +++ b/src/app/dashboards/template_vars.json @@ -65,7 +65,6 @@ "avg": false }, "percentage": false, - "zerofill": true, "nullPointMode": "connected", "steppedLine": false, "tooltip": { diff --git a/src/app/directives/addGraphiteFunc.js b/src/app/directives/addGraphiteFunc.js index e66689969ca..b585e7d003f 100644 --- a/src/app/directives/addGraphiteFunc.js +++ b/src/app/directives/addGraphiteFunc.js @@ -68,13 +68,12 @@ function (angular, app, _, $, gfunc) { }); $input.blur(function() { - $input.hide(); - $input.val(''); - $button.show(); - $button.focus(); // clicking the function dropdown menu wont // work if you remove class at once setTimeout(function() { + $input.val(''); + $input.hide(); + $button.show(); elem.removeClass('open'); }, 200); }); diff --git a/src/app/directives/all.js b/src/app/directives/all.js index 35d718fc942..d75410f3d33 100644 --- a/src/app/directives/all.js +++ b/src/app/directives/all.js @@ -10,7 +10,6 @@ define([ './confirmClick', './configModal', './spectrumPicker', - './grafanaGraph', './bootstrap-tagsinput', './bodyClass', './addGraphiteFunc', @@ -18,5 +17,6 @@ define([ './templateParamSelector', './graphiteSegment', './grafanaVersionCheck', + './dropdown.typeahead', './influxdbFuncEditor' ], function () {}); diff --git a/src/app/directives/dropdown.typeahead.js b/src/app/directives/dropdown.typeahead.js new file mode 100644 index 00000000000..f46de052c06 --- /dev/null +++ b/src/app/directives/dropdown.typeahead.js @@ -0,0 +1,105 @@ +define([ + 'angular', + 'app', + 'lodash', + 'jquery', +], +function (angular, app, _, $) { + 'use strict'; + + angular + .module('grafana.directives') + .directive('dropdownTypeahead', function($compile) { + + var inputTemplate = ''; + + var buttonTemplate = ''; + + return { + scope: { + "menuItems": "=dropdownTypeahead", + "dropdownTypeaheadOnSelect": "&dropdownTypeaheadOnSelect" + }, + link: function($scope, elem) { + var $input = $(inputTemplate); + var $button = $(buttonTemplate); + $input.appendTo(elem); + $button.appendTo(elem); + + var typeaheadValues = _.reduce($scope.menuItems, function(memo, value) { + _.each(value.submenu, function(item) { + memo.push(value.text + ' ' + item.text); + }); + return memo; + }, []); + + $scope.menuItemSelected = function(optionIndex, valueIndex) { + var option = $scope.menuItems[optionIndex]; + var result = { + $item: option.submenu[valueIndex], + $optionIndex: optionIndex, + $valueIndex: valueIndex + }; + + $scope.dropdownTypeaheadOnSelect(result); + }; + + $input.attr('data-provide', 'typeahead'); + $input.typeahead({ + source: typeaheadValues, + minLength: 1, + items: 10, + updater: function (value) { + var result = {}; + _.each($scope.menuItems, function(menuItem, optionIndex) { + _.each(menuItem.submenu, function(submenuItem, valueIndex) { + if (value === (menuItem.text + ' ' + submenuItem.text)) { + result.$item = submenuItem; + result.$optionIndex = optionIndex; + result.$valueIndex = valueIndex; + } + }); + }); + + if (result.$item) { + $scope.$apply(function() { + $scope.dropdownTypeaheadOnSelect(result); + }); + } + + $input.trigger('blur'); + return ''; + } + }); + + $button.click(function() { + $button.hide(); + $input.show(); + $input.focus(); + }); + + $input.keyup(function() { + elem.toggleClass('open', $input.val() === ''); + }); + + $input.blur(function() { + $input.hide(); + $input.val(''); + $button.show(); + $button.focus(); + // clicking the function dropdown menu wont + // work if you remove class at once + setTimeout(function() { + elem.removeClass('open'); + }, 200); + }); + + $compile(elem.contents())($scope); + } + }; + }); +}); diff --git a/src/app/directives/grafanaGraph.tooltip.js b/src/app/directives/grafanaGraph.tooltip.js deleted file mode 100644 index 9c98643a20e..00000000000 --- a/src/app/directives/grafanaGraph.tooltip.js +++ /dev/null @@ -1,132 +0,0 @@ -define([ - 'jquery', - 'kbn', -], -function ($, kbn) { - 'use strict'; - - function registerTooltipFeatures(elem, dashboard, scope) { - - var $tooltip = $('