From c260c319eede99513e234f0e16391706e51f1552 Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Thu, 21 Jan 2016 01:48:29 -0800 Subject: [PATCH 01/42] Started Grafana stats poc --- .../app/core/components/sidemenu/sidemenu.ts | 7 +++ public/app/core/routes/all.js | 4 ++ public/app/features/admin/adminStatsCtrl.js | 24 +++++++++ public/app/features/admin/all.js | 1 + public/app/features/admin/partials/stats.html | 53 +++++++++++++++++++ 5 files changed, 89 insertions(+) create mode 100644 public/app/features/admin/adminStatsCtrl.js create mode 100644 public/app/features/admin/partials/stats.html diff --git a/public/app/core/components/sidemenu/sidemenu.ts b/public/app/core/components/sidemenu/sidemenu.ts index d2a640c1345..8f0c57bfade 100644 --- a/public/app/core/components/sidemenu/sidemenu.ts +++ b/public/app/core/components/sidemenu/sidemenu.ts @@ -107,6 +107,12 @@ export class SideMenuCtrl { url: this.getUrl("/admin/settings"), }); + this.mainLinks.push({ + text: "Grafana stats", + icon: "fa fa-fw fa-bar-chart", + url: this.getUrl("/admin/stats"), + }); + this.mainLinks.push({ text: "Global Users", icon: "fa fa-fw fa-user", @@ -118,6 +124,7 @@ export class SideMenuCtrl { icon: "fa fa-fw fa-users", url: this.getUrl("/admin/orgs"), }); + } updateMenu() { diff --git a/public/app/core/routes/all.js b/public/app/core/routes/all.js index cc4d73ef708..bc71a63a094 100644 --- a/public/app/core/routes/all.js +++ b/public/app/core/routes/all.js @@ -112,6 +112,10 @@ define([ templateUrl: 'app/features/admin/partials/edit_org.html', controller : 'AdminEditOrgCtrl', }) + .when('/admin/stats', { + templateUrl: 'app/features/admin/partials/stats.html', + controller : 'AdminStatsCtrl', + }) .when('/login', { templateUrl: 'app/partials/login.html', controller : 'LoginCtrl', diff --git a/public/app/features/admin/adminStatsCtrl.js b/public/app/features/admin/adminStatsCtrl.js new file mode 100644 index 00000000000..94b40b59591 --- /dev/null +++ b/public/app/features/admin/adminStatsCtrl.js @@ -0,0 +1,24 @@ +define([ + 'angular', +], +function (angular) { + 'use strict'; + + var module = angular.module('grafana.controllers'); + + module.controller('AdminStatsCtrl', function($scope) { + + $scope.init = function() { + $scope.getStats(); + }; + + $scope.getStats = function() { +// backendSrv.get('/api/admin/stats').then(function(stats) { +// $scope.stats = stats; +// }); + }; + + $scope.init(); + + }); +}); diff --git a/public/app/features/admin/all.js b/public/app/features/admin/all.js index 14bff249b0e..786210f064f 100644 --- a/public/app/features/admin/all.js +++ b/public/app/features/admin/all.js @@ -4,4 +4,5 @@ define([ './adminEditOrgCtrl', './adminEditUserCtrl', './adminSettingsCtrl', + './adminStatsCtrl', ], function () {}); diff --git a/public/app/features/admin/partials/stats.html b/public/app/features/admin/partials/stats.html new file mode 100644 index 00000000000..048be83c83e --- /dev/null +++ b/public/app/features/admin/partials/stats.html @@ -0,0 +1,53 @@ + + + +
+
+

+ Stats +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameValue
Total dashboards213
Total users97
Total organizations4
Total playlists12
Total snapshots64
Total dashboard tags15
Total starred dashboards131
Total panels2739
+
+
From bbfdbaf95212edecb18738ba660279b121d52ff9 Mon Sep 17 00:00:00 2001 From: Ivan Babrou Date: Thu, 21 Jan 2016 16:38:39 +0000 Subject: [PATCH 02/42] Support OpenTSDB 2.2 fill policies, closes #3802 --- .../plugins/datasource/opentsdb/datasource.js | 4 ++++ .../opentsdb/partials/query.editor.html | 16 ++++++++++++++-- .../app/plugins/datasource/opentsdb/queryCtrl.js | 5 +++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/public/app/plugins/datasource/opentsdb/datasource.js b/public/app/plugins/datasource/opentsdb/datasource.js index b9593c013a1..d7cc354861f 100644 --- a/public/app/plugins/datasource/opentsdb/datasource.js +++ b/public/app/plugins/datasource/opentsdb/datasource.js @@ -272,6 +272,10 @@ function (angular, _, dateMath) { } query.downsample = interval + "-" + target.downsampleAggregator; + + if (target.downsampleFillPolicy !== "none") { + query.downsample += "-" + target.downsampleFillPolicy; + } } query.tags = angular.copy(target.tags); diff --git a/public/app/plugins/datasource/opentsdb/partials/query.editor.html b/public/app/plugins/datasource/opentsdb/partials/query.editor.html index 8a3c0c0f753..fffa7194228 100644 --- a/public/app/plugins/datasource/opentsdb/partials/query.editor.html +++ b/public/app/plugins/datasource/opentsdb/partials/query.editor.html @@ -104,8 +104,20 @@ -
  • - Disable downsampling +
  • + Fill policy + Available since OpenTSDB 2.2 +
  • + +
  • + +
  • + +
  • + Disable downsampling
  • diff --git a/public/app/plugins/datasource/opentsdb/queryCtrl.js b/public/app/plugins/datasource/opentsdb/queryCtrl.js index 8af3af206b6..14d28c310c2 100644 --- a/public/app/plugins/datasource/opentsdb/queryCtrl.js +++ b/public/app/plugins/datasource/opentsdb/queryCtrl.js @@ -13,6 +13,7 @@ function (angular, _, kbn) { $scope.init = function() { $scope.target.errors = validateTarget($scope.target); $scope.aggregators = ['avg', 'sum', 'min', 'max', 'dev', 'zimsum', 'mimmin', 'mimmax']; + $scope.fillPolicies = ['none', 'nan', 'null', 'zero']; if (!$scope.target.aggregator) { $scope.target.aggregator = 'sum'; @@ -22,6 +23,10 @@ function (angular, _, kbn) { $scope.target.downsampleAggregator = 'avg'; } + if (!$scope.target.downsampleFillPolicy) { + $scope.target.downsampleFillPolicy = 'none'; + } + $scope.datasource.getAggregators().then(function(aggs) { $scope.aggregators = aggs; }); From a85bda13db2df04175889662883f3c2ded474661 Mon Sep 17 00:00:00 2001 From: bergquist Date: Sun, 24 Jan 2016 18:07:42 +0100 Subject: [PATCH 03/42] tech(docker-dev): update dev docker for graphite --- docker/blocks/graphite/Dockerfile | 72 +++++++------------ docker/blocks/graphite/fig | 8 ++- docker/blocks/graphite/files/my_htpasswd | 1 + docker/blocks/graphite/files/supervisord.conf | 7 -- 4 files changed, 35 insertions(+), 53 deletions(-) create mode 100644 docker/blocks/graphite/files/my_htpasswd diff --git a/docker/blocks/graphite/Dockerfile b/docker/blocks/graphite/Dockerfile index 0c85798dd8a..aeac91b7a58 100644 --- a/docker/blocks/graphite/Dockerfile +++ b/docker/blocks/graphite/Dockerfile @@ -1,68 +1,50 @@ -from ubuntu:14.10 +from ubuntu:14.04 -run apt-get -y update +run apt-get -y update -run apt-get -y install software-properties-common - -run apt-get -y install python-software-properties &&\ - add-apt-repository ppa:chris-lea/node.js &&\ - apt-get -y update - -run apt-get -y install python-django-tagging python-simplejson python-memcache \ - python-ldap python-cairo python-django python-twisted \ - python-pysqlite2 python-support python-pip gunicorn \ - supervisor nginx-light nodejs git wget curl - -# Install statsd -run mkdir /src && git clone https://github.com/etsy/statsd.git /src/statsd +run apt-get -y install libcairo2-dev libffi-dev pkg-config python-dev python-pip fontconfig apache2 libapache2-mod-wsgi git-core collectd memcached gcc g++ make supervisor nginx-light gunicorn run cd /usr/local/src && git clone https://github.com/graphite-project/graphite-web.git run cd /usr/local/src && git clone https://github.com/graphite-project/carbon.git run cd /usr/local/src && git clone https://github.com/graphite-project/whisper.git run cd /usr/local/src/whisper && git checkout master && python setup.py install -run cd /usr/local/src/carbon && git checkout 0.9.x && python setup.py install -run cd /usr/local/src/graphite-web && git checkout 0.9.x && python check-dependencies.py; python setup.py install - -# statsd -add ./files/statsd_config.js /src/statsd/config.js +run cd /usr/local/src/carbon && git checkout 0.9.x && pip install -r requirements.txt; python setup.py install +run cd /usr/local/src/graphite-web && git checkout 0.9.x && pip install -r requirements.txt; python check-dependencies.py; python setup.py install # Add graphite config -add ./files/initial_data.json /opt/graphite/webapp/graphite/initial_data.json -add ./files/local_settings.py /opt/graphite/webapp/graphite/local_settings.py -add ./files/carbon.conf /opt/graphite/conf/carbon.conf -add ./files/storage-schemas.conf /opt/graphite/conf/storage-schemas.conf -add ./files/storage-aggregation.conf /opt/graphite/conf/storage-aggregation.conf -add ./files/events_views.py /opt/graphite/webapp/graphite/events/views.py +add ./files/initial_data.json /opt/graphite/webapp/graphite/initial_data.json +add ./files/local_settings.py /opt/graphite/webapp/graphite/local_settings.py +add ./files/carbon.conf /opt/graphite/conf/carbon.conf +add ./files/storage-schemas.conf /opt/graphite/conf/storage-schemas.conf +add ./files/storage-aggregation.conf /opt/graphite/conf/storage-aggregation.conf +add ./files/events_views.py /opt/graphite/webapp/graphite/events/views.py -run mkdir -p /opt/graphite/storage/whisper -run touch /opt/graphite/storage/graphite.db /opt/graphite/storage/index -run chown -R www-data /opt/graphite/storage -run chmod 0775 /opt/graphite/storage /opt/graphite/storage/whisper -run chmod 0664 /opt/graphite/storage/graphite.db -run cd /opt/graphite/webapp/graphite && python manage.py syncdb --noinput +run mkdir -p /opt/graphite/storage/whisper +run touch /opt/graphite/storage/graphite.db /opt/graphite/storage/index +run chown -R www-data /opt/graphite/storage +run chmod 0775 /opt/graphite/storage /opt/graphite/storage/whisper +run chmod 0664 /opt/graphite/storage/graphite.db +run cd /opt/graphite/webapp/graphite && python manage.py syncdb --noinput + +add ./files/my_htpasswd /etc/nginx/.htpasswd # Add system service config -add ./files/nginx.conf /etc/nginx/nginx.conf -add ./files/supervisord.conf /etc/supervisor/conf.d/supervisord.conf - +add ./files/nginx.conf /etc/nginx/nginx.conf +add ./files/supervisord.conf /etc/supervisor/conf.d/supervisord.conf +# Nginx +# # graphite -expose 80 +expose 80 # Carbon line receiver port -expose 2003 +expose 2003 # Carbon cache query port -expose 7002 +expose 7002 -# Statsd UDP port -expose 8125/udp -# Statsd Management port -expose 8126 - -VOLUME ["/var/lib/elasticsearch"] VOLUME ["/opt/graphite/storage/whisper"] VOLUME ["/var/lib/log/supervisor"] -cmd ["/usr/bin/supervisord"] +cmd ["/usr/bin/supervisord"] # vim:ts=8:noet: diff --git a/docker/blocks/graphite/fig b/docker/blocks/graphite/fig index 28e7d3c53a2..84da45341e1 100644 --- a/docker/blocks/graphite/fig +++ b/docker/blocks/graphite/fig @@ -1,4 +1,10 @@ graphite: build: blocks/graphite ports: - - "8776:80" + - "8080:80" + - "2003:2003" + volumes: + - /var/docker/gfdev/graphite:/opt/graphite/storage/whisper + - /etc/localtime:/etc/localtime:ro + - /etc/timezone:/etc/timezone:ro + diff --git a/docker/blocks/graphite/files/my_htpasswd b/docker/blocks/graphite/files/my_htpasswd new file mode 100644 index 00000000000..52a72d01b4c --- /dev/null +++ b/docker/blocks/graphite/files/my_htpasswd @@ -0,0 +1 @@ +grafana:$apr1$4R/20xhC$8t37jPP5dbcLr48btdkU// diff --git a/docker/blocks/graphite/files/supervisord.conf b/docker/blocks/graphite/files/supervisord.conf index 25ba39c8819..c9812bb16dc 100644 --- a/docker/blocks/graphite/files/supervisord.conf +++ b/docker/blocks/graphite/files/supervisord.conf @@ -24,10 +24,3 @@ stdout_logfile = /var/log/supervisor/%(program_name)s.log stderr_logfile = /var/log/supervisor/%(program_name)s.log autorestart = true -[program:statsd] -;user = www-data -command = /usr/bin/node /src/statsd/stats.js /src/statsd/config.js -stdout_logfile = /var/log/supervisor/%(program_name)s.log -stderr_logfile = /var/log/supervisor/%(program_name)s.log -autorestart = true - From 42802ac7102668a4b7b4c35eb2d3094d08171e80 Mon Sep 17 00:00:00 2001 From: bergquist Date: Sun, 24 Jan 2016 17:10:26 +0100 Subject: [PATCH 04/42] tech(singlestat): convert singlestat panel to typescript --- public/app/core/time_series2.ts | 1 + .../{controller.js => controller.ts} | 39 ++- public/app/plugins/panel/singlestat/module.js | 235 ------------------ public/app/plugins/panel/singlestat/module.ts | 231 +++++++++++++++++ .../panel/singlestat/singleStatPanel.js | 221 ---------------- .../singlestat/specs/singlestat_panel_spec.ts | 0 6 files changed, 250 insertions(+), 477 deletions(-) rename public/app/plugins/panel/singlestat/{controller.js => controller.ts} (92%) delete mode 100644 public/app/plugins/panel/singlestat/module.js create mode 100644 public/app/plugins/panel/singlestat/module.ts delete mode 100644 public/app/plugins/panel/singlestat/singleStatPanel.js create mode 100644 public/app/plugins/panel/singlestat/specs/singlestat_panel_spec.ts diff --git a/public/app/core/time_series2.ts b/public/app/core/time_series2.ts index c8384b5b40a..ef2e6b9c8b2 100644 --- a/public/app/core/time_series2.ts +++ b/public/app/core/time_series2.ts @@ -41,6 +41,7 @@ export default class TimeSeries { nullPointMode: any; fillBelowTo: any; transform: any; + flotpairs: any; constructor(opts) { this.datapoints = opts.datapoints; diff --git a/public/app/plugins/panel/singlestat/controller.js b/public/app/plugins/panel/singlestat/controller.ts similarity index 92% rename from public/app/plugins/panel/singlestat/controller.js rename to public/app/plugins/panel/singlestat/controller.ts index 62ae13d0f5f..4b55a4568be 100644 --- a/public/app/plugins/panel/singlestat/controller.js +++ b/public/app/plugins/panel/singlestat/controller.ts @@ -1,17 +1,15 @@ -define([ - 'angular', - 'app/app', - 'lodash', - 'app/core/utils/kbn', - 'app/core/time_series', - 'app/features/panel/panel_meta', -], -function (angular, app, _, kbn, TimeSeries, PanelMeta) { - 'use strict'; +/// + +import angular from 'angular'; +import _ from 'lodash'; +import kbn from 'app/core/utils/kbn'; +import PanelMeta from 'app/features/panel/panel_meta2'; +import TimeSeries from '../../../core/time_series2'; + +export class SingleStatCtrl { /** @ngInject */ - function SingleStatCtrl($scope, panelSrv, panelHelper) { - + constructor($scope, panelSrv, panelHelper) { $scope.panelMeta = new PanelMeta({ panelName: 'Singlestat', editIcon: "fa fa-dashboard", @@ -57,6 +55,7 @@ function (angular, app, _, kbn, TimeSeries, PanelMeta) { }; _.defaults($scope.panel, _d); + $scope.unitFormats = kbn.getUnitFormats(); $scope.setUnitFormat = function(subItem) { @@ -104,8 +103,7 @@ function (angular, app, _, kbn, TimeSeries, PanelMeta) { if (options.background) { $scope.panel.colorValue = false; $scope.panel.colors = ['rgba(71, 212, 59, 0.4)', 'rgba(245, 150, 40, 0.73)', 'rgba(225, 40, 40, 0.59)']; - } - else { + } else { $scope.panel.colorBackground = false; $scope.panel.colors = ['rgba(50, 172, 45, 0.97)', 'rgba(237, 129, 40, 0.89)', 'rgba(245, 54, 54, 0.9)']; } @@ -151,7 +149,7 @@ function (angular, app, _, kbn, TimeSeries, PanelMeta) { // reduce starting decimals if not needed if (Math.floor(value) === value) { dec = 0; } - var result = {}; + var result: any = {}; result.decimals = Math.max(0, dec); result.scaledDecimals = result.decimals - Math.floor(Math.log(size) / Math.LN10) + 2; @@ -159,7 +157,7 @@ function (angular, app, _, kbn, TimeSeries, PanelMeta) { }; $scope.render = function() { - var data = {}; + var data: any = {}; $scope.setValues(data); @@ -176,7 +174,7 @@ function (angular, app, _, kbn, TimeSeries, PanelMeta) { $scope.setValues = function(data) { data.flotpairs = []; - if($scope.series.length > 1) { + if ($scope.series.length > 1) { $scope.inspector.error = new Error(); $scope.inspector.error.message = 'Multiple Series Error'; $scope.inspector.error.data = 'Metric query returns ' + $scope.series.length + @@ -204,7 +202,7 @@ function (angular, app, _, kbn, TimeSeries, PanelMeta) { } // check value to text mappings - for(var i = 0; i < $scope.panel.valueMaps.length; i++) { + for (var i = 0; i < $scope.panel.valueMaps.length; i++) { var map = $scope.panel.valueMaps[i]; // special null case if (map.value === 'null') { @@ -239,7 +237,6 @@ function (angular, app, _, kbn, TimeSeries, PanelMeta) { }; $scope.init(); - } - return SingleStatCtrl; -}); + } +} diff --git a/public/app/plugins/panel/singlestat/module.js b/public/app/plugins/panel/singlestat/module.js deleted file mode 100644 index 8ce441818e6..00000000000 --- a/public/app/plugins/panel/singlestat/module.js +++ /dev/null @@ -1,235 +0,0 @@ -define([ - './controller', - 'lodash', - 'jquery', - 'jquery.flot', -], -function (SingleStatCtrl, _, $) { - 'use strict'; - - /** @ngInject */ - function singleStatPanel($location, linkSrv, $timeout, templateSrv) { - return { - controller: SingleStatCtrl, - templateUrl: 'app/plugins/panel/singlestat/module.html', - link: function(scope, elem) { - var data, panel, linkInfo, $panelContainer; - var firstRender = true; - - scope.$on('render', function() { - if (firstRender) { - var inner = elem.find('.singlestat-panel'); - if (inner.length) { - elem = inner; - $panelContainer = elem.parents('.panel-container'); - firstRender = false; - hookupDrilldownLinkTooltip(); - } - } - - render(); - scope.panelRenderingComplete(); - }); - - function setElementHeight() { - try { - var height = scope.height || panel.height || scope.row.height; - if (_.isString(height)) { - height = parseInt(height.replace('px', ''), 10); - } - - height -= 5; // padding - height -= panel.title ? 24 : 9; // subtract panel title bar - - elem.css('height', height + 'px'); - - return true; - } catch(e) { // IE throws errors sometimes - return false; - } - } - - function applyColoringThresholds(value, valueString) { - if (!panel.colorValue) { - return valueString; - } - - var color = getColorForValue(value); - if (color) { - return ''+ valueString + ''; - } - - return valueString; - } - - function getColorForValue(value) { - for (var i = data.thresholds.length - 1; i >= 0 ; i--) { - if (value >= data.thresholds[i]) { - return data.colorMap[i]; - } - } - return null; - } - - function getSpan(className, fontSize, value) { - value = templateSrv.replace(value); - return '' + - value + ''; - } - - function getBigValueHtml() { - var body = '
    '; - - if (panel.prefix) { body += getSpan('singlestat-panel-prefix', panel.prefixFontSize, scope.panel.prefix); } - - var value = applyColoringThresholds(data.valueRounded, data.valueFormated); - body += getSpan('singlestat-panel-value', panel.valueFontSize, value); - - if (panel.postfix) { body += getSpan('singlestat-panel-postfix', panel.postfixFontSize, panel.postfix); } - - body += '
    '; - - return body; - } - - function addSparkline() { - var panel = scope.panel; - var width = elem.width() + 20; - var height = elem.height() || 100; - - var plotCanvas = $('
    '); - var plotCss = {}; - plotCss.position = 'absolute'; - - if (panel.sparkline.full) { - plotCss.bottom = '5px'; - plotCss.left = '-5px'; - plotCss.width = (width - 10) + 'px'; - var dynamicHeightMargin = height <= 100 ? 5 : (Math.round((height/100)) * 15) + 5; - plotCss.height = (height - dynamicHeightMargin) + 'px'; - } - else { - plotCss.bottom = "0px"; - plotCss.left = "-5px"; - plotCss.width = (width - 10) + 'px'; - plotCss.height = Math.floor(height * 0.25) + "px"; - } - - plotCanvas.css(plotCss); - - var options = { - legend: { show: false }, - series: { - lines: { - show: true, - fill: 1, - lineWidth: 1, - fillColor: panel.sparkline.fillColor, - }, - }, - yaxes: { show: false }, - xaxis: { - show: false, - mode: "time", - min: scope.range.from.valueOf(), - max: scope.range.to.valueOf(), - }, - grid: { hoverable: false, show: false }, - }; - - elem.append(plotCanvas); - - var plotSeries = { - data: data.flotpairs, - color: panel.sparkline.lineColor - }; - - $.plot(plotCanvas, [plotSeries], options); - } - - function render() { - if (!scope.data) { return; } - - data = scope.data; - panel = scope.panel; - - setElementHeight(); - - var body = getBigValueHtml(); - - if (panel.colorBackground && !isNaN(data.valueRounded)) { - var color = getColorForValue(data.valueRounded); - if (color) { - $panelContainer.css('background-color', color); - if (scope.fullscreen) { - elem.css('background-color', color); - } else { - elem.css('background-color', ''); - } - } - } else { - $panelContainer.css('background-color', ''); - elem.css('background-color', ''); - } - - elem.html(body); - - if (panel.sparkline.show) { - addSparkline(); - } - - elem.toggleClass('pointer', panel.links.length > 0); - - if (panel.links.length > 0) { - linkInfo = linkSrv.getPanelLinkAnchorInfo(panel.links[0], scope.panel.scopedVars); - } else { - linkInfo = null; - } - } - - function hookupDrilldownLinkTooltip() { - // drilldown link tooltip - var drilldownTooltip = $('
    hello
    "'); - - elem.mouseleave(function() { - if (panel.links.length === 0) { return;} - drilldownTooltip.detach(); - }); - - elem.click(function(evt) { - if (!linkInfo) { return; } - // ignore title clicks in title - if ($(evt).parents('.panel-header').length > 0) { return; } - - if (linkInfo.target === '_blank') { - var redirectWindow = window.open(linkInfo.href, '_blank'); - redirectWindow.location; - return; - } - - if (linkInfo.href.indexOf('http') === 0) { - window.location.href = linkInfo.href; - } else { - $timeout(function() { - $location.url(linkInfo.href); - }); - } - - drilldownTooltip.detach(); - }); - - elem.mousemove(function(e) { - if (!linkInfo) { return;} - - drilldownTooltip.text('click to go to: ' + linkInfo.title); - drilldownTooltip.place_tt(e.pageX+20, e.pageY-15); - }); - } - } - }; - } - - return { - panel: singleStatPanel - }; -}); diff --git a/public/app/plugins/panel/singlestat/module.ts b/public/app/plugins/panel/singlestat/module.ts new file mode 100644 index 00000000000..c6d10afb567 --- /dev/null +++ b/public/app/plugins/panel/singlestat/module.ts @@ -0,0 +1,231 @@ +/// + +import _ from 'lodash'; +import $ from 'jquery'; +import angular from 'angular'; +import {SingleStatCtrl} from './controller'; + +angular.module('grafana.directives').directive('singleStatPanel', singleStatPanel); + +function singleStatPanel($location, linkSrv, $timeout, templateSrv) { + 'use strict'; + return { + controller: SingleStatCtrl, + templateUrl: 'app/plugins/panel/singlestat/module.html', + link: function(scope, elem) { + var data, panel, linkInfo, $panelContainer; + var firstRender = true; + + scope.$on('render', function() { + if (firstRender) { + var inner = elem.find('.singlestat-panel'); + if (inner.length) { + elem = inner; + $panelContainer = elem.parents('.panel-container'); + firstRender = false; + hookupDrilldownLinkTooltip(); + } + } + + render(); + scope.panelRenderingComplete(); + }); + + function setElementHeight() { + try { + var height = scope.height || panel.height || scope.row.height; + if (_.isString(height)) { + height = parseInt(height.replace('px', ''), 10); + } + + height -= 5; // padding + height -= panel.title ? 24 : 9; // subtract panel title bar + + elem.css('height', height + 'px'); + + return true; + } catch (e) { // IE throws errors sometimes + return false; + } + } + + function applyColoringThresholds(value, valueString) { + if (!panel.colorValue) { + return valueString; + } + + var color = getColorForValue(value); + if (color) { + return ''+ valueString + ''; + } + + return valueString; + } + + function getColorForValue(value) { + for (var i = data.thresholds.length - 1; i >= 0 ; i--) { + if (value >= data.thresholds[i]) { + return data.colorMap[i]; + } + } + return null; + } + + function getSpan(className, fontSize, value) { + value = templateSrv.replace(value); + return '' + + value + ''; + } + + function getBigValueHtml() { + var body = '
    '; + + if (panel.prefix) { body += getSpan('singlestat-panel-prefix', panel.prefixFontSize, scope.panel.prefix); } + + var value = applyColoringThresholds(data.valueRounded, data.valueFormated); + body += getSpan('singlestat-panel-value', panel.valueFontSize, value); + + if (panel.postfix) { body += getSpan('singlestat-panel-postfix', panel.postfixFontSize, panel.postfix); } + + body += '
    '; + + return body; + } + + function addSparkline() { + var panel = scope.panel; + var width = elem.width() + 20; + var height = elem.height() || 100; + + var plotCanvas = $('
    '); + var plotCss: any = {}; + plotCss.position = 'absolute'; + + if (panel.sparkline.full) { + plotCss.bottom = '5px'; + plotCss.left = '-5px'; + plotCss.width = (width - 10) + 'px'; + var dynamicHeightMargin = height <= 100 ? 5 : (Math.round((height/100)) * 15) + 5; + plotCss.height = (height - dynamicHeightMargin) + 'px'; + } else { + plotCss.bottom = "0px"; + plotCss.left = "-5px"; + plotCss.width = (width - 10) + 'px'; + plotCss.height = Math.floor(height * 0.25) + "px"; + } + + plotCanvas.css(plotCss); + + var options = { + legend: { show: false }, + series: { + lines: { + show: true, + fill: 1, + lineWidth: 1, + fillColor: panel.sparkline.fillColor, + }, + }, + yaxes: { show: false }, + xaxis: { + show: false, + mode: "time", + min: scope.range.from.valueOf(), + max: scope.range.to.valueOf(), + }, + grid: { hoverable: false, show: false }, + }; + + elem.append(plotCanvas); + + var plotSeries = { + data: data.flotpairs, + color: panel.sparkline.lineColor + }; + + $.plot(plotCanvas, [plotSeries], options); + } + + function render() { + if (!scope.data) { return; } + + data = scope.data; + panel = scope.panel; + + setElementHeight(); + + var body = getBigValueHtml(); + + if (panel.colorBackground && !isNaN(data.valueRounded)) { + var color = getColorForValue(data.valueRounded); + if (color) { + $panelContainer.css('background-color', color); + if (scope.fullscreen) { + elem.css('background-color', color); + } else { + elem.css('background-color', ''); + } + } + } else { + $panelContainer.css('background-color', ''); + elem.css('background-color', ''); + } + + elem.html(body); + + if (panel.sparkline.show) { + addSparkline(); + } + + elem.toggleClass('pointer', panel.links.length > 0); + + if (panel.links.length > 0) { + linkInfo = linkSrv.getPanelLinkAnchorInfo(panel.links[0], scope.panel.scopedVars); + } else { + linkInfo = null; + } + } + + function hookupDrilldownLinkTooltip() { + // drilldown link tooltip + var drilldownTooltip = $('
    hello
    "'); + + elem.mouseleave(function() { + if (panel.links.length === 0) { return;} + drilldownTooltip.detach(); + }); + + elem.click(function(evt) { + if (!linkInfo) { return; } + // ignore title clicks in title + if ($(evt).parents('.panel-header').length > 0) { return; } + + if (linkInfo.target === '_blank') { + var redirectWindow = window.open(linkInfo.href, '_blank'); + redirectWindow.location; + return; + } + + if (linkInfo.href.indexOf('http') === 0) { + window.location.href = linkInfo.href; + } else { + $timeout(function() { + $location.url(linkInfo.href); + }); + } + + drilldownTooltip.detach(); + }); + + elem.mousemove(function(e) { + if (!linkInfo) { return;} + + drilldownTooltip.text('click to go to: ' + linkInfo.title); + drilldownTooltip.place_tt(e.pageX+20, e.pageY-15); + }); + } + } + }; +} + +export {singleStatPanel as panel}; diff --git a/public/app/plugins/panel/singlestat/singleStatPanel.js b/public/app/plugins/panel/singlestat/singleStatPanel.js deleted file mode 100644 index a176ea974ae..00000000000 --- a/public/app/plugins/panel/singlestat/singleStatPanel.js +++ /dev/null @@ -1,221 +0,0 @@ -define([ - 'angular', - 'app/app', - 'lodash', - 'jquery', - 'jquery.flot', -], -function (angular, app, _, $) { - 'use strict'; - - var module = angular.module('grafana.panels.singlestat', []); - app.useModule(module); - - module.directive('singlestatPanel', function($location, linkSrv, $timeout, templateSrv) { - - return { - link: function(scope, elem) { - var data, panel, linkInfo; - var $panelContainer = elem.parents('.panel-container'); - - scope.$on('render', function() { - render(); - scope.panelRenderingComplete(); - }); - - function setElementHeight() { - try { - var height = scope.height || panel.height || scope.row.height; - if (_.isString(height)) { - height = parseInt(height.replace('px', ''), 10); - } - - height -= 5; // padding - height -= panel.title ? 24 : 9; // subtract panel title bar - - elem.css('height', height + 'px'); - - return true; - } catch(e) { // IE throws errors sometimes - return false; - } - } - - function applyColoringThresholds(value, valueString) { - if (!panel.colorValue) { - return valueString; - } - - var color = getColorForValue(value); - if (color) { - return ''+ valueString + ''; - } - - return valueString; - } - - function getColorForValue(value) { - for (var i = data.thresholds.length - 1; i >= 0 ; i--) { - if (value >= data.thresholds[i]) { - return data.colorMap[i]; - } - } - return null; - } - - function getSpan(className, fontSize, value) { - value = templateSrv.replace(value); - return '' + - value + ''; - } - - function getBigValueHtml() { - var body = '
    '; - - if (panel.prefix) { body += getSpan('singlestat-panel-prefix', panel.prefixFontSize, scope.panel.prefix); } - - var value = applyColoringThresholds(data.valueRounded, data.valueFormated); - body += getSpan('singlestat-panel-value', panel.valueFontSize, value); - - if (panel.postfix) { body += getSpan('singlestat-panel-postfix', panel.postfixFontSize, panel.postfix); } - - body += '
    '; - - return body; - } - - function addSparkline() { - var panel = scope.panel; - var width = elem.width() + 20; - var height = elem.height() || 100; - - var plotCanvas = $('
    '); - var plotCss = {}; - plotCss.position = 'absolute'; - - if (panel.sparkline.full) { - plotCss.bottom = '5px'; - plotCss.left = '-5px'; - plotCss.width = (width - 10) + 'px'; - var dynamicHeightMargin = height <= 100 ? 5 : (Math.round((height/100)) * 15) + 5; - plotCss.height = (height - dynamicHeightMargin) + 'px'; - } - else { - plotCss.bottom = "0px"; - plotCss.left = "-5px"; - plotCss.width = (width - 10) + 'px'; - plotCss.height = Math.floor(height * 0.25) + "px"; - } - - plotCanvas.css(plotCss); - - var options = { - legend: { show: false }, - series: { - lines: { - show: true, - fill: 1, - lineWidth: 1, - fillColor: panel.sparkline.fillColor, - }, - }, - yaxes: { show: false }, - xaxis: { - show: false, - mode: "time", - min: scope.range.from.valueOf(), - max: scope.range.to.valueOf(), - }, - grid: { hoverable: false, show: false }, - }; - - elem.append(plotCanvas); - - var plotSeries = { - data: data.flotpairs, - color: panel.sparkline.lineColor - }; - - $.plot(plotCanvas, [plotSeries], options); - } - - function render() { - if (!scope.data) { return; } - - data = scope.data; - panel = scope.panel; - - setElementHeight(); - - var body = getBigValueHtml(); - - if (panel.colorBackground && !isNaN(data.valueRounded)) { - var color = getColorForValue(data.valueRounded); - if (color) { - $panelContainer.css('background-color', color); - if (scope.fullscreen) { - elem.css('background-color', color); - } else { - elem.css('background-color', ''); - } - } - } else { - $panelContainer.css('background-color', ''); - elem.css('background-color', ''); - } - - elem.html(body); - - if (panel.sparkline.show) { - addSparkline(); - } - - elem.toggleClass('pointer', panel.links.length > 0); - - if (panel.links.length > 0) { - linkInfo = linkSrv.getPanelLinkAnchorInfo(panel.links[0], scope.panel.scopedVars); - } else { - linkInfo = null; - } - } - - // drilldown link tooltip - var drilldownTooltip = $('
    hello
    "'); - - elem.mouseleave(function() { - if (panel.links.length === 0) { return;} - drilldownTooltip.detach(); - }); - - elem.click(function() { - if (!linkInfo) { return; } - - if (linkInfo.target === '_blank') { - var redirectWindow = window.open(linkInfo.href, '_blank'); - redirectWindow.location; - return; - } - - if (linkInfo.href.indexOf('http') === 0) { - window.location.href = linkInfo.href; - } else { - $timeout(function() { - $location.url(linkInfo.href); - }); - } - - drilldownTooltip.detach(); - }); - - elem.mousemove(function(e) { - if (!linkInfo) { return;} - - drilldownTooltip.text('click to go to: ' + linkInfo.title); - - drilldownTooltip.place_tt(e.pageX+20, e.pageY-15); - }); - } - }; - }); - -}); diff --git a/public/app/plugins/panel/singlestat/specs/singlestat_panel_spec.ts b/public/app/plugins/panel/singlestat/specs/singlestat_panel_spec.ts new file mode 100644 index 00000000000..e69de29bb2d From c7fae5386daf060d848bc840a33cbe60aef1457d Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Sun, 24 Jan 2016 11:01:33 -0800 Subject: [PATCH 05/42] Added backend API for stats --- pkg/api/{admin_settings.go => admin.go} | 14 ++++++++ pkg/api/api.go | 2 ++ pkg/models/stats.go | 15 ++++++++ pkg/services/sqlstore/stats.go | 47 +++++++++++++++++++++++++ 4 files changed, 78 insertions(+) rename pkg/api/{admin_settings.go => admin.go} (65%) diff --git a/pkg/api/admin_settings.go b/pkg/api/admin.go similarity index 65% rename from pkg/api/admin_settings.go rename to pkg/api/admin.go index 1f800cfe558..1264cfa12eb 100644 --- a/pkg/api/admin_settings.go +++ b/pkg/api/admin.go @@ -5,6 +5,8 @@ import ( "github.com/grafana/grafana/pkg/middleware" "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/bus" + m "github.com/grafana/grafana/pkg/models" ) func AdminGetSettings(c *middleware.Context) { @@ -27,3 +29,15 @@ func AdminGetSettings(c *middleware.Context) { c.JSON(200, settings) } + +func AdminGetStats(c *middleware.Context) { + + statsQuery := m.GetAdminStatsQuery{} + + if err := bus.Dispatch(&statsQuery); err != nil { + c.JsonApiErr(500, "Failed to get admin stats from database", err) + return + } + + c.JSON(200, statsQuery.Result) +} diff --git a/pkg/api/api.go b/pkg/api/api.go index ea434ed71da..7a4fc684497 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -40,6 +40,7 @@ func Register(r *macaron.Macaron) { r.Get("/admin/users/edit/:id", reqGrafanaAdmin, Index) r.Get("/admin/orgs", reqGrafanaAdmin, Index) r.Get("/admin/orgs/edit/:id", reqGrafanaAdmin, Index) + r.Get("/admin/stats", reqGrafanaAdmin, Index) r.Get("/apps", reqSignedIn, Index) r.Get("/apps/edit/*", reqSignedIn, Index) @@ -210,6 +211,7 @@ func Register(r *macaron.Macaron) { r.Delete("/users/:id", AdminDeleteUser) r.Get("/users/:id/quotas", wrap(GetUserQuotas)) r.Put("/users/:id/quotas/:target", bind(m.UpdateUserQuotaCmd{}), wrap(UpdateUserQuota)) + r.Get("/stats", AdminGetStats) }, reqGrafanaAdmin) // rendering diff --git a/pkg/models/stats.go b/pkg/models/stats.go index 6a060137ac7..8fd7614ffd2 100644 --- a/pkg/models/stats.go +++ b/pkg/models/stats.go @@ -18,3 +18,18 @@ type GetSystemStatsQuery struct { type GetDataSourceStatsQuery struct { Result []*DataSourceStats } + +type AdminStats struct { + UserCount int + OrgCount int + DashboardCount int + DBSnapshotCount int + DBTagCount int + DataSourceCount int + PlaylistCount int + StarredDBCount int +} + +type GetAdminStatsQuery struct { + Result *AdminStats +} diff --git a/pkg/services/sqlstore/stats.go b/pkg/services/sqlstore/stats.go index 044aa185f19..c57128bc76a 100644 --- a/pkg/services/sqlstore/stats.go +++ b/pkg/services/sqlstore/stats.go @@ -8,6 +8,7 @@ import ( func init() { bus.AddHandler("sql", GetSystemStats) bus.AddHandler("sql", GetDataSourceStats) + bus.AddHandler("sql", GetAdminStats) } func GetDataSourceStats(query *m.GetDataSourceStatsQuery) error { @@ -46,3 +47,49 @@ func GetSystemStats(query *m.GetSystemStatsQuery) error { query.Result = &stats return err } + +func GetAdminStats(query *m.GetAdminStatsQuery) error { + var rawSql = `SELECT + ( + SELECT COUNT(*) + FROM ` + dialect.Quote("user") + ` + ) AS user_count, + ( + SELECT COUNT(*) + FROM ` + dialect.Quote("org") + ` + ) AS org_count, + ( + SELECT COUNT(*) + FROM ` + dialect.Quote("dashboard") + ` + ) AS dashboard_count, + ( + SELECT COUNT(*) + FROM ` + dialect.Quote("dashboard_snapshot") + ` + ) AS db_snapshot_count, + ( + SELECT COUNT(*) + FROM ` + dialect.Quote("dashboard_tag") + ` + ) AS db_tag_count, + ( + SELECT COUNT(*) + FROM ` + dialect.Quote("data_source") + ` + ) AS datasource_count, + ( + SELECT COUNT(*) + FROM ` + dialect.Quote("playlist") + ` + ) AS playlist_count, + ( + SELECT DISTINCT(dashboard_id) + FROM ` + dialect.Quote("star") + ` + ) AS starred_db_count + ` + + var stats m.AdminStats + _, err := x.Sql(rawSql).Get(&stats) + if err != nil { + return err + } + + query.Result = &stats + return err +} From da67afa51ede6e72ac61f7e499d0a704390c11cb Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Sun, 24 Jan 2016 21:18:17 -0800 Subject: [PATCH 06/42] Fixed api bugs, stats endpoint working --- pkg/api/admin.go | 18 +++++++++--------- pkg/api/api.go | 4 ++-- pkg/models/stats.go | 18 +++++++++--------- pkg/services/sqlstore/stats.go | 22 +++++++++++----------- 4 files changed, 31 insertions(+), 31 deletions(-) diff --git a/pkg/api/admin.go b/pkg/api/admin.go index 1264cfa12eb..d7f5a240416 100644 --- a/pkg/api/admin.go +++ b/pkg/api/admin.go @@ -3,10 +3,10 @@ package api import ( "strings" + "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/middleware" + m "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/setting" - "github.com/grafana/grafana/pkg/bus" - m "github.com/grafana/grafana/pkg/models" ) func AdminGetSettings(c *middleware.Context) { @@ -32,12 +32,12 @@ func AdminGetSettings(c *middleware.Context) { func AdminGetStats(c *middleware.Context) { - statsQuery := m.GetAdminStatsQuery{} - - if err := bus.Dispatch(&statsQuery); err != nil { - c.JsonApiErr(500, "Failed to get admin stats from database", err) - return - } + statsQuery := m.GetAdminStatsQuery{} - c.JSON(200, statsQuery.Result) + if err := bus.Dispatch(&statsQuery); err != nil { + c.JsonApiErr(500, "Failed to get admin stats from database", err) + return + } + + c.JSON(200, statsQuery.Result) } diff --git a/pkg/api/api.go b/pkg/api/api.go index 7a4fc684497..32ccd5dc793 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -40,7 +40,7 @@ func Register(r *macaron.Macaron) { r.Get("/admin/users/edit/:id", reqGrafanaAdmin, Index) r.Get("/admin/orgs", reqGrafanaAdmin, Index) r.Get("/admin/orgs/edit/:id", reqGrafanaAdmin, Index) - r.Get("/admin/stats", reqGrafanaAdmin, Index) + r.Get("/admin/stats", reqGrafanaAdmin, Index) r.Get("/apps", reqSignedIn, Index) r.Get("/apps/edit/*", reqSignedIn, Index) @@ -211,7 +211,7 @@ func Register(r *macaron.Macaron) { r.Delete("/users/:id", AdminDeleteUser) r.Get("/users/:id/quotas", wrap(GetUserQuotas)) r.Put("/users/:id/quotas/:target", bind(m.UpdateUserQuotaCmd{}), wrap(UpdateUserQuota)) - r.Get("/stats", AdminGetStats) + r.Get("/stats", AdminGetStats) }, reqGrafanaAdmin) // rendering diff --git a/pkg/models/stats.go b/pkg/models/stats.go index 8fd7614ffd2..6d8618d6ddf 100644 --- a/pkg/models/stats.go +++ b/pkg/models/stats.go @@ -20,16 +20,16 @@ type GetDataSourceStatsQuery struct { } type AdminStats struct { - UserCount int - OrgCount int - DashboardCount int - DBSnapshotCount int - DBTagCount int - DataSourceCount int - PlaylistCount int - StarredDBCount int + UserCount int `json:"user_count"` + OrgCount int `json:"org_count"` + DashboardCount int `json:"dashboard_count"` + DbSnapshotCount int `json:"db_snapshot_count"` + DbTagCount int `json:"db_tag_count"` + DataSourceCount int `json:"data_source_count"` + PlaylistCount int `json:"playlist_count"` + StarredDbCount int `json:"starred_db_count"` } type GetAdminStatsQuery struct { - Result *AdminStats + Result *AdminStats } diff --git a/pkg/services/sqlstore/stats.go b/pkg/services/sqlstore/stats.go index c57128bc76a..3c9325fa149 100644 --- a/pkg/services/sqlstore/stats.go +++ b/pkg/services/sqlstore/stats.go @@ -8,7 +8,7 @@ import ( func init() { bus.AddHandler("sql", GetSystemStats) bus.AddHandler("sql", GetDataSourceStats) - bus.AddHandler("sql", GetAdminStats) + bus.AddHandler("sql", GetAdminStats) } func GetDataSourceStats(query *m.GetDataSourceStatsQuery) error { @@ -49,7 +49,7 @@ func GetSystemStats(query *m.GetSystemStatsQuery) error { } func GetAdminStats(query *m.GetAdminStatsQuery) error { - var rawSql = `SELECT + var rawSql = `SELECT ( SELECT COUNT(*) FROM ` + dialect.Quote("user") + ` @@ -73,23 +73,23 @@ func GetAdminStats(query *m.GetAdminStatsQuery) error { ( SELECT COUNT(*) FROM ` + dialect.Quote("data_source") + ` - ) AS datasource_count, + ) AS data_source_count, ( SELECT COUNT(*) FROM ` + dialect.Quote("playlist") + ` ) AS playlist_count, ( - SELECT DISTINCT(dashboard_id) + SELECT COUNT (DISTINCT ` + dialect.Quote("dashboard_id") + ` ) FROM ` + dialect.Quote("star") + ` ) AS starred_db_count ` - var stats m.AdminStats - _, err := x.Sql(rawSql).Get(&stats) - if err != nil { - return err - } + var stats m.AdminStats + _, err := x.Sql(rawSql).Get(&stats) + if err != nil { + return err + } - query.Result = &stats - return err + query.Result = &stats + return err } From 4c12703e0c756af1b9ec00ac8da1f4d23f11e11c Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Sun, 24 Jan 2016 21:39:30 -0800 Subject: [PATCH 07/42] Integrated angularjs with go api --- public/app/features/admin/adminStatsCtrl.js | 8 +++---- public/app/features/admin/partials/stats.html | 22 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/public/app/features/admin/adminStatsCtrl.js b/public/app/features/admin/adminStatsCtrl.js index 94b40b59591..217429d1253 100644 --- a/public/app/features/admin/adminStatsCtrl.js +++ b/public/app/features/admin/adminStatsCtrl.js @@ -6,16 +6,16 @@ function (angular) { var module = angular.module('grafana.controllers'); - module.controller('AdminStatsCtrl', function($scope) { + module.controller('AdminStatsCtrl', function($scope, backendSrv) { $scope.init = function() { $scope.getStats(); }; $scope.getStats = function() { -// backendSrv.get('/api/admin/stats').then(function(stats) { -// $scope.stats = stats; -// }); + backendSrv.get('/api/admin/stats').then(function(stats) { + $scope.stats = stats; + }); }; $scope.init(); diff --git a/public/app/features/admin/partials/stats.html b/public/app/features/admin/partials/stats.html index 048be83c83e..0df6d251f5c 100644 --- a/public/app/features/admin/partials/stats.html +++ b/public/app/features/admin/partials/stats.html @@ -17,35 +17,35 @@ Total dashboards - 213 + {{stats.dashboard_count}} Total users - 97 + {{stats.user_count}} Total organizations - 4 + {{stats.org_count}} + + + Total datasources + {{stats.data_source_count}} Total playlists - 12 + {{stats.playlist_count}} Total snapshots - 64 + {{stats.db_snapshot_count}} Total dashboard tags - 15 + {{stats.db_tag_count}} Total starred dashboards - 131 - - - Total panels - 2739 + {{stats.starred_db_count}} From a621c0d27356321da6e71d766a24aa583dfc1320 Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Mon, 25 Jan 2016 00:02:05 -0800 Subject: [PATCH 08/42] Added docs for stats api --- docs/sources/reference/http_api.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/docs/sources/reference/http_api.md b/docs/sources/reference/http_api.md index 7cdd8a872d5..a7ae066a8db 100644 --- a/docs/sources/reference/http_api.md +++ b/docs/sources/reference/http_api.md @@ -1422,6 +1422,33 @@ Keys: } } +### Grafana Stats + +`GET /api/admin/stats` + +**Example Request**: + + GET /api/admin/stats + Accept: application/json + Content-Type: application/json + Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + +**Example Response**: + + HTTP/1.1 200 + Content-Type: application/json + + { + "user_count":2, + "org_count":1, + "dashboard_count":4, + "db_snapshot_count":2, + "db_tag_count":6, + "data_source_count":1, + "playlist_count":1, + "starred_db_count":2 + } + ### Global Users `POST /api/admin/users` From 2190392e052221905eba1b6f164ddc9574d92c9a Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Mon, 25 Jan 2016 00:39:31 -0800 Subject: [PATCH 09/42] Added grafana_admins count --- docs/sources/reference/http_api.md | 3 ++- pkg/models/stats.go | 17 +++++++++-------- pkg/services/sqlstore/stats.go | 6 +++++- public/app/features/admin/partials/stats.html | 11 +++++++++-- 4 files changed, 25 insertions(+), 12 deletions(-) diff --git a/docs/sources/reference/http_api.md b/docs/sources/reference/http_api.md index a7ae066a8db..4b0dff6c4b6 100644 --- a/docs/sources/reference/http_api.md +++ b/docs/sources/reference/http_api.md @@ -1446,7 +1446,8 @@ Keys: "db_tag_count":6, "data_source_count":1, "playlist_count":1, - "starred_db_count":2 + "starred_db_count":2, + "grafana_admin_count":2 } ### Global Users diff --git a/pkg/models/stats.go b/pkg/models/stats.go index 6d8618d6ddf..4b1d863d1d2 100644 --- a/pkg/models/stats.go +++ b/pkg/models/stats.go @@ -20,14 +20,15 @@ type GetDataSourceStatsQuery struct { } type AdminStats struct { - UserCount int `json:"user_count"` - OrgCount int `json:"org_count"` - DashboardCount int `json:"dashboard_count"` - DbSnapshotCount int `json:"db_snapshot_count"` - DbTagCount int `json:"db_tag_count"` - DataSourceCount int `json:"data_source_count"` - PlaylistCount int `json:"playlist_count"` - StarredDbCount int `json:"starred_db_count"` + UserCount int `json:"user_count"` + OrgCount int `json:"org_count"` + DashboardCount int `json:"dashboard_count"` + DbSnapshotCount int `json:"db_snapshot_count"` + DbTagCount int `json:"db_tag_count"` + DataSourceCount int `json:"data_source_count"` + PlaylistCount int `json:"playlist_count"` + StarredDbCount int `json:"starred_db_count"` + GrafanaAdminCount int `json:"grafana_admin_count"` } type GetAdminStatsQuery struct { diff --git a/pkg/services/sqlstore/stats.go b/pkg/services/sqlstore/stats.go index 3c9325fa149..ad1d87299b1 100644 --- a/pkg/services/sqlstore/stats.go +++ b/pkg/services/sqlstore/stats.go @@ -81,7 +81,11 @@ func GetAdminStats(query *m.GetAdminStatsQuery) error { ( SELECT COUNT (DISTINCT ` + dialect.Quote("dashboard_id") + ` ) FROM ` + dialect.Quote("star") + ` - ) AS starred_db_count + ) AS starred_db_count, + ( + SELECT SUM ( ` + dialect.Quote("is_admin") + ` ) + FROM ` + dialect.Quote("user") + ` + ) AS grafana_admin_count ` var stats m.AdminStats diff --git a/public/app/features/admin/partials/stats.html b/public/app/features/admin/partials/stats.html index 0df6d251f5c..3743b0c81b8 100644 --- a/public/app/features/admin/partials/stats.html +++ b/public/app/features/admin/partials/stats.html @@ -1,10 +1,13 @@ - + +

    - Stats + Overview

    @@ -23,6 +26,10 @@ + + + + From b0a24ae4aadc3067b55f1f8cb140f125c77652cb Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 25 Jan 2016 14:48:23 +0100 Subject: [PATCH 10/42] tech(singlestat): move singlestat test to plugin and change to typescript --- .../singlestat/specs/singlestat-specs.ts | 88 +++++++++++++++++++ .../singlestat/specs/singlestat_panel_spec.ts | 58 ++++++++++++ public/test/specs/singlestat-specs.js | 88 ------------------- 3 files changed, 146 insertions(+), 88 deletions(-) create mode 100644 public/app/plugins/panel/singlestat/specs/singlestat-specs.ts delete mode 100644 public/test/specs/singlestat-specs.js diff --git a/public/app/plugins/panel/singlestat/specs/singlestat-specs.ts b/public/app/plugins/panel/singlestat/specs/singlestat-specs.ts new file mode 100644 index 00000000000..8d532d17d80 --- /dev/null +++ b/public/app/plugins/panel/singlestat/specs/singlestat-specs.ts @@ -0,0 +1,88 @@ +/// + +import {describe, beforeEach, it, sinon, expect, angularMocks} from '../../../../../test/lib/common'; + +import 'app/features/panel/panel_srv'; +import 'app/features/panel/panel_helper'; + +import angular from 'angular'; +import helpers from '../../../../../test/specs/helpers'; +import {SingleStatCtrl} from '../controller'; + + +angular.module('grafana.controllers').controller('SingleStatCtrl', SingleStatCtrl); + +describe('SingleStatCtrl', function() { + var ctx = new helpers.ControllerTestContext(); + + function singleStatScenario(desc, func) { + + describe(desc, function() { + + ctx.setup = function (setupFunc) { + + beforeEach(angularMocks.module('grafana.services')); + beforeEach(angularMocks.module('grafana.controllers')); + + beforeEach(ctx.providePhase()); + beforeEach(ctx.createControllerPhase('SingleStatCtrl')); + + beforeEach(function() { + setupFunc(); + ctx.datasource.query = sinon.stub().returns(ctx.$q.when({ + data: [{target: 'test.cpu1', datapoints: ctx.datapoints}] + })); + + ctx.scope.refreshData(ctx.datasource); + ctx.scope.$digest(); + ctx.data = ctx.scope.data; + }); + }; + + func(ctx); + }); + } + + singleStatScenario('with defaults', function(ctx) { + ctx.setup(function() { + ctx.datapoints = [[10,1], [20,2]]; + }); + + it('Should use series avg as default main value', function() { + expect(ctx.data.value).to.be(15); + expect(ctx.data.valueRounded).to.be(15); + }); + + it('should set formated falue', function() { + expect(ctx.data.valueFormated).to.be('15'); + }); + }); + + singleStatScenario('MainValue should use same number for decimals as displayed when checking thresholds', function(ctx) { + ctx.setup(function() { + ctx.datapoints = [[99.999,1], [99.99999,2]]; + }); + + it('Should be rounded', function() { + expect(ctx.data.value).to.be(99.999495); + expect(ctx.data.valueRounded).to.be(100); + }); + + it('should set formated falue', function() { + expect(ctx.data.valueFormated).to.be('100'); + }); + }); + + singleStatScenario('When value to text mapping is specified', function(ctx) { + ctx.setup(function() { + ctx.datapoints = [[10,1]]; + ctx.scope.panel.valueMaps = [{value: '10', text: 'OK'}]; + }); + + it('Should replace value with text', function() { + expect(ctx.data.value).to.be(10); + expect(ctx.data.valueFormated).to.be('OK'); + }); + + }); +}); diff --git a/public/app/plugins/panel/singlestat/specs/singlestat_panel_spec.ts b/public/app/plugins/panel/singlestat/specs/singlestat_panel_spec.ts index e69de29bb2d..17b34f6bcef 100644 --- a/public/app/plugins/panel/singlestat/specs/singlestat_panel_spec.ts +++ b/public/app/plugins/panel/singlestat/specs/singlestat_panel_spec.ts @@ -0,0 +1,58 @@ +import {describe, beforeEach, it, sinon, expect} from 'test/lib/common'; + +import {getColorForValue} from '../module'; + +describe('grafanaSingleStat', function() { + describe('legacy thresholds', () => { + describe('positive thresholds', () => { + var data: any = { + colorMap: ['green', 'yellow', 'red'], + thresholds: [0, 20, 50] + }; + + it('5 should return green', () => { + expect(getColorForValue(data, 5)).to.be('green'); + }); + + it('25 should return green', () => { + expect(getColorForValue(data, 25)).to.be('yellow'); + }); + + it('55 should return green', () => { + expect(getColorForValue(data, 55)).to.be('red'); + }); + }); + }); + + + + describe('negative thresholds', () => { + var data: any = { + colorMap: ['green', 'yellow', 'red'], + thresholds: [ -20, 0, 20] + }; + + it('-30 should return green', () => { + expect(getColorForValue(data, -30)).to.be('green'); + }); + + it('1 should return green', () => { + expect(getColorForValue(data, 1)).to.be('yellow'); + }); + + it('22 should return green', () => { + expect(getColorForValue(data, 22)).to.be('red'); + }); + }); + + describe('negative thresholds', () => { + var data: any = { + colorMap: ['green', 'yellow', 'red'], + thresholds: [ -40, -27, 20] + }; + + it('-30 should return green', () => { + expect(getColorForValue(data, -26)).to.be('yellow'); + }); + }); +}); diff --git a/public/test/specs/singlestat-specs.js b/public/test/specs/singlestat-specs.js deleted file mode 100644 index 60953345f2d..00000000000 --- a/public/test/specs/singlestat-specs.js +++ /dev/null @@ -1,88 +0,0 @@ -define([ - 'angular', - './helpers', - 'app/plugins/panel/singlestat/controller', - 'app/features/panel/panel_srv', - 'app/features/panel/panel_helper', -], function(angular, helpers, SingleStatCtrl) { - 'use strict'; - - angular.module('grafana.controllers').controller('SingleStatCtrl', SingleStatCtrl); - - describe('SingleStatCtrl', function() { - var ctx = new helpers.ControllerTestContext(); - - function singleStatScenario(desc, func) { - - describe(desc, function() { - - ctx.setup = function (setupFunc) { - - beforeEach(module('grafana.services')); - beforeEach(module('grafana.controllers')); - - beforeEach(ctx.providePhase()); - beforeEach(ctx.createControllerPhase('SingleStatCtrl')); - - beforeEach(function() { - setupFunc(); - ctx.datasource.query = sinon.stub().returns(ctx.$q.when({ - data: [{target: 'test.cpu1', datapoints: ctx.datapoints}] - })); - - ctx.scope.refreshData(ctx.datasource); - ctx.scope.$digest(); - ctx.data = ctx.scope.data; - }); - }; - - func(ctx); - }); - } - - singleStatScenario('with defaults', function(ctx) { - ctx.setup(function() { - ctx.datapoints = [[10,1], [20,2]]; - }); - - it('Should use series avg as default main value', function() { - expect(ctx.data.value).to.be(15); - expect(ctx.data.valueRounded).to.be(15); - }); - - it('should set formated falue', function() { - expect(ctx.data.valueFormated).to.be('15'); - }); - }); - - singleStatScenario('MainValue should use same number for decimals as displayed when checking thresholds', function(ctx) { - ctx.setup(function() { - ctx.datapoints = [[99.999,1], [99.99999,2]]; - }); - - it('Should be rounded', function() { - expect(ctx.data.value).to.be(99.999495); - expect(ctx.data.valueRounded).to.be(100); - }); - - it('should set formated falue', function() { - expect(ctx.data.valueFormated).to.be('100'); - }); - }); - - singleStatScenario('When value to text mapping is specified', function(ctx) { - ctx.setup(function() { - ctx.datapoints = [[10,1]]; - ctx.scope.panel.valueMaps = [{value: '10', text: 'OK'}]; - }); - - it('Should replace value with text', function() { - expect(ctx.data.value).to.be(10); - expect(ctx.data.valueFormated).to.be('OK'); - }); - - }); - - }); -}); - From cd1b2e28417eed741f32f839a09ba5c9a92ead14 Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 25 Jan 2016 16:27:00 +0100 Subject: [PATCH 11/42] feat(singlestat): reduce max thresholds to two. closes #3248 --- public/app/features/dashboard/dashboardSrv.js | 21 ++++++++++++++-- .../app/plugins/panel/singlestat/editor.html | 4 +-- public/app/plugins/panel/singlestat/module.ts | 25 ++++++++++--------- public/test/specs/dashboardSrv-specs.js | 12 ++++++++- 4 files changed, 45 insertions(+), 17 deletions(-) diff --git a/public/app/features/dashboard/dashboardSrv.js b/public/app/features/dashboard/dashboardSrv.js index 559adff5c54..e4d74c985ba 100644 --- a/public/app/features/dashboard/dashboardSrv.js +++ b/public/app/features/dashboard/dashboardSrv.js @@ -234,9 +234,9 @@ function (angular, $, _, moment) { var i, j, k; var oldVersion = this.schemaVersion; var panelUpgrades = []; - this.schemaVersion = 8; + this.schemaVersion = 9; - if (oldVersion === 8) { + if (oldVersion === this.schemaVersion) { return; } @@ -390,6 +390,23 @@ function (angular, $, _, moment) { }); } + // schema version 9 changes + if (oldVersion < 9) { + // move aliasYAxis changes + panelUpgrades.push(function(panel) { + if (panel.type !== 'singlestat' && panel.thresholds !== "") { return; } + + if (panel.thresholds) { + var k = panel.thresholds.split(","); + + if (k.length >= 3) { + k.shift(); + panel.thresholds = k.join(","); + } + } + }); + } + if (panelUpgrades.length === 0) { return; } diff --git a/public/app/plugins/panel/singlestat/editor.html b/public/app/plugins/panel/singlestat/editor.html index f8df6d8adc9..76c264e7b3a 100644 --- a/public/app/plugins/panel/singlestat/editor.html +++ b/public/app/plugins/panel/singlestat/editor.html @@ -97,10 +97,10 @@
  • - ThresholdsComma seperated values + ThresholdsDefine two threshold values<br /> 50,80 will produce: <50 = Green, 50:80 = Yellow, >80 = Red
  • - +
  • Colors diff --git a/public/app/plugins/panel/singlestat/module.ts b/public/app/plugins/panel/singlestat/module.ts index c6d10afb567..0a171207bcf 100644 --- a/public/app/plugins/panel/singlestat/module.ts +++ b/public/app/plugins/panel/singlestat/module.ts @@ -54,7 +54,7 @@ function singleStatPanel($location, linkSrv, $timeout, templateSrv) { return valueString; } - var color = getColorForValue(value); + var color = getColorForValue(data, value); if (color) { return ''+ valueString + ''; } @@ -62,15 +62,6 @@ function singleStatPanel($location, linkSrv, $timeout, templateSrv) { return valueString; } - function getColorForValue(value) { - for (var i = data.thresholds.length - 1; i >= 0 ; i--) { - if (value >= data.thresholds[i]) { - return data.colorMap[i]; - } - } - return null; - } - function getSpan(className, fontSize, value) { value = templateSrv.replace(value); return '' + @@ -157,7 +148,7 @@ function singleStatPanel($location, linkSrv, $timeout, templateSrv) { var body = getBigValueHtml(); if (panel.colorBackground && !isNaN(data.valueRounded)) { - var color = getColorForValue(data.valueRounded); + var color = getColorForValue(data, data.valueRounded); if (color) { $panelContainer.css('background-color', color); if (scope.fullscreen) { @@ -228,4 +219,14 @@ function singleStatPanel($location, linkSrv, $timeout, templateSrv) { }; } -export {singleStatPanel as panel}; +function getColorForValue(data, value) { + for (var i = data.thresholds.length; i > 0; i--) { + if (value >= data.thresholds[i]) { + return data.colorMap[i]; + } + } + + return _.first(data.colorMap); +} + +export {singleStatPanel as panel, getColorForValue}; diff --git a/public/test/specs/dashboardSrv-specs.js b/public/test/specs/dashboardSrv-specs.js index 5b2fefd384d..4a6c01bdc3b 100644 --- a/public/test/specs/dashboardSrv-specs.js +++ b/public/test/specs/dashboardSrv-specs.js @@ -141,6 +141,7 @@ define([ describe('when creating dashboard with old schema', function() { var model; var graph; + var singlestat; beforeEach(function() { model = _dashboardSrv.create({ @@ -155,6 +156,10 @@ define([ { type: 'graphite', legend: true, aliasYAxis: { test: 2 }, grid: { min: 1, max: 10 }, targets: [{refId: 'A'}, {}], + }, + { + type: 'singlestat', legend: true, thresholds: '10,20,30', aliasYAxis: { test: 2 }, grid: { min: 1, max: 10 }, + targets: [{refId: 'A'}, {}], } ] } @@ -162,6 +167,7 @@ define([ }); graph = model.rows[0].panels[0]; + singlestat = model.rows[0].panels[1]; }); it('should have title', function() { @@ -181,6 +187,10 @@ define([ expect(graph.type).to.be('graph'); }); + it('single stat panel should have two thresholds', function() { + expect(singlestat.thresholds).to.be('20,30'); + }); + it('queries without refId should get it', function() { expect(graph.targets[1].refId).to.be('B'); }); @@ -204,7 +214,7 @@ define([ }); it('dashboard schema version should be set to latest', function() { - expect(model.schemaVersion).to.be(8); + expect(model.schemaVersion).to.be(9); }); }); From b939a27a8dcdbd6a405a270784692ad226a84d8b Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 25 Jan 2016 18:40:28 +0100 Subject: [PATCH 12/42] revert f3bc726001bfbc707597bce5fa169b46e992eb7ao This change have caused alot of questions. So we revert --- public/app/plugins/panel/graph/graph_tooltip.js | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/public/app/plugins/panel/graph/graph_tooltip.js b/public/app/plugins/panel/graph/graph_tooltip.js index 99fb20b2ed5..1eabbd0f7af 100644 --- a/public/app/plugins/panel/graph/graph_tooltip.js +++ b/public/app/plugins/panel/graph/graph_tooltip.js @@ -79,9 +79,9 @@ function ($) { // Stacked series can increase its length on each new stacked serie if null points found, // to speed the index search we begin always on the last found hoverIndex. var newhoverIndex = this.findHoverIndexFromDataPoints(pos.x, series, hoverIndex); - results.push({ value: value, hoverIndex: newhoverIndex, color: series.color, label: series.label }); + results.push({ value: value, hoverIndex: newhoverIndex }); } else { - results.push({ value: value, hoverIndex: hoverIndex, color: series.color, label: series.label }); + results.push({ value: value, hoverIndex: hoverIndex }); } } @@ -126,8 +126,6 @@ function ($) { relativeTime = dashboard.getRelativeTime(seriesHoverInfo.time); absoluteTime = dashboard.formatDate(seriesHoverInfo.time); - seriesHoverInfo.sort(byToolTipValue); - for (i = 0; i < seriesHoverInfo.length; i++) { hoverInfo = seriesHoverInfo[i]; @@ -140,7 +138,7 @@ function ($) { value = series.formatValue(hoverInfo.value); seriesHtml += '
    '; - seriesHtml += ' ' + hoverInfo.label + ':
    '; + seriesHtml += ' ' + series.label + ':
    '; seriesHtml += '
    ' + value + '
    '; plot.highlight(i, hoverInfo.hoverIndex); } @@ -176,9 +174,5 @@ function ($) { }); } - function byToolTipValue(a, b) { - return parseFloat(b.value) - parseFloat(a.value); - } - return GraphTooltip; }); From 40088cd4fe31a7a6fa61ffa4493c101cbd6ebf86 Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 25 Jan 2016 20:30:25 +0100 Subject: [PATCH 13/42] tech(docker): add elastic fig --- docker/blocks/elastic/elasticsearch/config/.placeholder | 1 + docker/blocks/elastic/fig | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 docker/blocks/elastic/elasticsearch/config/.placeholder create mode 100644 docker/blocks/elastic/fig diff --git a/docker/blocks/elastic/elasticsearch/config/.placeholder b/docker/blocks/elastic/elasticsearch/config/.placeholder new file mode 100644 index 00000000000..9ad266259c2 --- /dev/null +++ b/docker/blocks/elastic/elasticsearch/config/.placeholder @@ -0,0 +1 @@ +Ensure the existence of the parent folder. diff --git a/docker/blocks/elastic/fig b/docker/blocks/elastic/fig new file mode 100644 index 00000000000..498402ac7b0 --- /dev/null +++ b/docker/blocks/elastic/fig @@ -0,0 +1,6 @@ +elasticsearch: + image: elasticsearch:latest + command: elasticsearch -Des.network.host=0.0.0.0 + ports: + - "9200:9200" + - "9300:9300" From 1cac6ecedbda59067966ff9b8354836fae3cabe6 Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 25 Jan 2016 20:46:15 +0100 Subject: [PATCH 14/42] tech(docker): update influxdb to use latest --- docker/blocks/influxdb/Dockerfile | 16 ---------------- docker/blocks/influxdb/fig | 2 +- 2 files changed, 1 insertion(+), 17 deletions(-) delete mode 100644 docker/blocks/influxdb/Dockerfile diff --git a/docker/blocks/influxdb/Dockerfile b/docker/blocks/influxdb/Dockerfile deleted file mode 100644 index 69d10992464..00000000000 --- a/docker/blocks/influxdb/Dockerfile +++ /dev/null @@ -1,16 +0,0 @@ -# influxdb - -FROM ubuntu - -RUN mkdir -p /opt/influxdb/shared/data - -ADD http://s3.amazonaws.com/influxdb/influxdb_0.8.8_amd64.deb /influx88.deb -RUN dpkg -i /influx88.deb -RUN rm -rf /opt/influxdb/shared/data - -ADD config.toml /opt/influxdb/shared/config.toml - -EXPOSE 8083 8086 2004 - -ENTRYPOINT ["/usr/bin/influxdb"] -CMD ["-config=/opt/influxdb/shared/config.toml"] diff --git a/docker/blocks/influxdb/fig b/docker/blocks/influxdb/fig index 3f247f756e2..931f8a2640a 100644 --- a/docker/blocks/influxdb/fig +++ b/docker/blocks/influxdb/fig @@ -1,5 +1,5 @@ influxdb: - build: blocks/influxdb + image: tutum/influxdb:latest ports: - "2004:2004" - "8083:8083" From d8bb7c3094831d7847527692b96d25ea6d52ac3c Mon Sep 17 00:00:00 2001 From: jimmyR Date: Mon, 25 Jan 2016 20:57:55 +0100 Subject: [PATCH 15/42] resolves #3741 merge conflict After a refactoring the sidemenu-canvas css class disappeared so changed the code from #3741 to use the main-view class instead. --- vendor/phantomjs/render.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/vendor/phantomjs/render.js b/vendor/phantomjs/render.js index 95885a17886..02acabebc33 100644 --- a/vendor/phantomjs/render.js +++ b/vendor/phantomjs/render.js @@ -49,6 +49,15 @@ }); if (canvas || tries === 1000) { + var bb = page.evaluate(function () { + return document.getElementsByClassName("main-view")[0].getBoundingClientRect(); + }); + page.clipRect = { + top: bb.top, + left: bb.left, + width: bb.width, + height: bb.height + }; page.render(params.png); phantom.exit(); } From 52403ca17eaf2e469c5978a962195c2f886fb22e Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 25 Jan 2016 21:39:04 +0100 Subject: [PATCH 16/42] feat(playlist): add usage statisics --- pkg/metrics/report_usage.go | 1 + pkg/models/stats.go | 1 + pkg/services/sqlstore/stats.go | 8 +++++++- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/pkg/metrics/report_usage.go b/pkg/metrics/report_usage.go index abda18d12b7..c5c2afed7f7 100644 --- a/pkg/metrics/report_usage.go +++ b/pkg/metrics/report_usage.go @@ -55,6 +55,7 @@ func sendUsageStats() { metrics["stats.dashboards.count"] = statsQuery.Result.DashboardCount metrics["stats.users.count"] = statsQuery.Result.UserCount metrics["stats.orgs.count"] = statsQuery.Result.OrgCount + metrics["stats.playlist.count"] = statsQuery.Result.PlaylistCount dsStats := m.GetDataSourceStatsQuery{} if err := bus.Dispatch(&dsStats); err != nil { diff --git a/pkg/models/stats.go b/pkg/models/stats.go index 6a060137ac7..30c09deb768 100644 --- a/pkg/models/stats.go +++ b/pkg/models/stats.go @@ -4,6 +4,7 @@ type SystemStats struct { DashboardCount int UserCount int OrgCount int + PlaylistCount int } type DataSourceStats struct { diff --git a/pkg/services/sqlstore/stats.go b/pkg/services/sqlstore/stats.go index 044aa185f19..203534b3b93 100644 --- a/pkg/services/sqlstore/stats.go +++ b/pkg/services/sqlstore/stats.go @@ -1,6 +1,8 @@ package sqlstore import ( + "fmt" + "github.com/grafana/grafana/pkg/bus" m "github.com/grafana/grafana/pkg/models" ) @@ -34,7 +36,11 @@ func GetSystemStats(query *m.GetSystemStatsQuery) error { ( SELECT COUNT(*) FROM ` + dialect.Quote("dashboard") + ` - ) AS dashboard_count + ) AS dashboard_count, + ( + SELECT COUNT(*) + FROM ` + dialect.Quote("playlist") + ` + ) AS playlist_count ` var stats m.SystemStats From 92cba9403181ad1094722ddf5440e71cc34ba3cf Mon Sep 17 00:00:00 2001 From: bergquist Date: Mon, 25 Jan 2016 22:11:24 +0100 Subject: [PATCH 17/42] tech(fmt): remove unused code --- pkg/services/sqlstore/stats.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/services/sqlstore/stats.go b/pkg/services/sqlstore/stats.go index 203534b3b93..6c5dfaea906 100644 --- a/pkg/services/sqlstore/stats.go +++ b/pkg/services/sqlstore/stats.go @@ -1,8 +1,6 @@ package sqlstore import ( - "fmt" - "github.com/grafana/grafana/pkg/bus" m "github.com/grafana/grafana/pkg/models" ) From be2e2577b82d7570abb6d6330eb7516d2c856763 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Mon, 25 Jan 2016 22:59:48 +0100 Subject: [PATCH 18/42] fixes #3839 Uses width set to 800px for invite div instead of position fixed. It looks the same for desktop and makes the button clickable on smaller resolutions and mobile. Although it is not really responsive so the text will be small on mobiles. Better that though than a non-clickable button. --- public/less/login.less | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/public/less/login.less b/public/less/login.less index 924659d9e16..e599bca1927 100644 --- a/public/less/login.less +++ b/public/less/login.less @@ -110,13 +110,9 @@ text-align: center; border: 1px solid @grafanaTargetFuncBackground; background-color: @grafanaPanelBackground; - position: fixed; - max-width: 800px; - left: 0; - right: 0; + width: 800px; margin-left: auto; margin-right: auto; - top: 20%; .tight-form { text-align: left; From 442db7fee1cc87a04fd95477b8a432784311f448 Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Mon, 25 Jan 2016 14:30:36 -0800 Subject: [PATCH 19/42] Changed sql query for grafana_admin_count --- pkg/services/sqlstore/stats.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/services/sqlstore/stats.go b/pkg/services/sqlstore/stats.go index ad1d87299b1..a2e61aa0c3e 100644 --- a/pkg/services/sqlstore/stats.go +++ b/pkg/services/sqlstore/stats.go @@ -83,8 +83,9 @@ func GetAdminStats(query *m.GetAdminStatsQuery) error { FROM ` + dialect.Quote("star") + ` ) AS starred_db_count, ( - SELECT SUM ( ` + dialect.Quote("is_admin") + ` ) + SELECT COUNT(*) FROM ` + dialect.Quote("user") + ` + WHERE ` + dialect.Quote("is_admin") + ` = 1 ) AS grafana_admin_count ` From 29185eeef78f54e59cf2a91b6bed0017a9c87edd Mon Sep 17 00:00:00 2001 From: Pablo Fischer Date: Tue, 26 Jan 2016 00:12:56 +0000 Subject: [PATCH 20/42] If OpenTSDB is 3rd-party hosted (or by another team) and does not support OPTIONS, send the request as POST (urlencoded) --- public/app/plugins/datasource/opentsdb/datasource.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/public/app/plugins/datasource/opentsdb/datasource.js b/public/app/plugins/datasource/opentsdb/datasource.js index d7cc354861f..f4680038e8d 100644 --- a/public/app/plugins/datasource/opentsdb/datasource.js +++ b/public/app/plugins/datasource/opentsdb/datasource.js @@ -72,6 +72,9 @@ function (angular, _, dateMath) { data: reqBody }; + // In case the backend is 3rd-party hosted and does not suport OPTIONS, urlencoded requests + // go as POST rather than OPTIONS+POST + options.headers = { 'Content-Type': 'application/x-www-form-urlencoded' }; return backendSrv.datasourceRequest(options); }; From 07fee0a810f0e0a2f6058ec6be8df5e1d9863508 Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Mon, 25 Jan 2016 17:49:39 -0800 Subject: [PATCH 21/42] Converted adminStatsCtrl to typescript --- public/app/core/routes/all.js | 1 + public/app/features/admin/adminStatsCtrl.js | 24 ------------------- public/app/features/admin/adminStatsCtrl.ts | 18 ++++++++++++++ public/app/features/admin/partials/stats.html | 20 ++++++++-------- 4 files changed, 29 insertions(+), 34 deletions(-) delete mode 100644 public/app/features/admin/adminStatsCtrl.js create mode 100644 public/app/features/admin/adminStatsCtrl.ts diff --git a/public/app/core/routes/all.js b/public/app/core/routes/all.js index bc71a63a094..d9726ee782c 100644 --- a/public/app/core/routes/all.js +++ b/public/app/core/routes/all.js @@ -115,6 +115,7 @@ define([ .when('/admin/stats', { templateUrl: 'app/features/admin/partials/stats.html', controller : 'AdminStatsCtrl', + controllerAs: 'ctrl', }) .when('/login', { templateUrl: 'app/partials/login.html', diff --git a/public/app/features/admin/adminStatsCtrl.js b/public/app/features/admin/adminStatsCtrl.js deleted file mode 100644 index 217429d1253..00000000000 --- a/public/app/features/admin/adminStatsCtrl.js +++ /dev/null @@ -1,24 +0,0 @@ -define([ - 'angular', -], -function (angular) { - 'use strict'; - - var module = angular.module('grafana.controllers'); - - module.controller('AdminStatsCtrl', function($scope, backendSrv) { - - $scope.init = function() { - $scope.getStats(); - }; - - $scope.getStats = function() { - backendSrv.get('/api/admin/stats').then(function(stats) { - $scope.stats = stats; - }); - }; - - $scope.init(); - - }); -}); diff --git a/public/app/features/admin/adminStatsCtrl.ts b/public/app/features/admin/adminStatsCtrl.ts new file mode 100644 index 00000000000..0331524dcc7 --- /dev/null +++ b/public/app/features/admin/adminStatsCtrl.ts @@ -0,0 +1,18 @@ +// + +import angular from 'angular'; + +export class AdminStatsCtrl { + stats: any; + + /** @ngInject */ + constructor(private backendSrv: any) {} + + init() { + this.backendSrv.get('/api/admin/stats').then(stats => { + this.stats = stats; + }); + } +} + +angular.module('grafana.controllers').controller('AdminStatsCtrl', AdminStatsCtrl); diff --git a/public/app/features/admin/partials/stats.html b/public/app/features/admin/partials/stats.html index 3743b0c81b8..4949e71e441 100644 --- a/public/app/features/admin/partials/stats.html +++ b/public/app/features/admin/partials/stats.html @@ -5,7 +5,7 @@
    -
    +

    Overview

    @@ -20,39 +20,39 @@
  • - + - + - + - + - + - + - + - + - +
    Total users {{stats.user_count}}
    Total grafana admins{{stats.grafana_admin_count}}
    Total organizations {{stats.org_count}}
    Total dashboards{{stats.dashboard_count}}{{ctrl.stats.dashboard_count}}
    Total users{{stats.user_count}}{{ctrl.stats.user_count}}
    Total grafana admins{{stats.grafana_admin_count}}{{ctrl.stats.grafana_admin_count}}
    Total organizations{{stats.org_count}}{{ctrl.stats.org_count}}
    Total datasources{{stats.data_source_count}}{{ctrl.stats.data_source_count}}
    Total playlists{{stats.playlist_count}}{{ctrl.stats.playlist_count}}
    Total snapshots{{stats.db_snapshot_count}}{{ctrl.stats.db_snapshot_count}}
    Total dashboard tags{{stats.db_tag_count}}{{ctrl.stats.db_tag_count}}
    Total starred dashboards{{stats.starred_db_count}}{{ctrl.stats.starred_db_count}}
    From e59b0c0694106e2fb131c54ebd03c950756b447d Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Mon, 25 Jan 2016 18:10:48 -0800 Subject: [PATCH 22/42] Fixed ts file comment --- public/app/features/admin/adminStatsCtrl.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/features/admin/adminStatsCtrl.ts b/public/app/features/admin/adminStatsCtrl.ts index 0331524dcc7..aa3ed6de343 100644 --- a/public/app/features/admin/adminStatsCtrl.ts +++ b/public/app/features/admin/adminStatsCtrl.ts @@ -1,4 +1,4 @@ -// +/// import angular from 'angular'; From 09de46e5ac1fdfb4cce18f0d374037a0c84bceb2 Mon Sep 17 00:00:00 2001 From: bergquist Date: Tue, 26 Jan 2016 17:54:19 +0100 Subject: [PATCH 23/42] tech(search): convert search to typescript --- .../components/search}/search.html | 31 ++-- public/app/core/components/search/search.ts | 144 ++++++++++++++++++ public/app/core/core.ts | 3 +- .../dashboard/directives/dashSearchView.js | 2 +- 4 files changed, 161 insertions(+), 19 deletions(-) rename public/app/{partials => core/components/search}/search.html (61%) create mode 100644 public/app/core/components/search/search.ts diff --git a/public/app/partials/search.html b/public/app/core/components/search/search.html similarity index 61% rename from public/app/partials/search.html rename to public/app/core/components/search/search.html index fab8fd9291a..1f51c8e0650 100644 --- a/public/app/partials/search.html +++ b/public/app/core/components/search/search.html @@ -1,24 +1,22 @@ -