浏览代码

Merge branch 'alerting' into alerting_notifications

bergquist 9 年之前
父节点
当前提交
96e5ad3f5c
共有 37 个文件被更改,包括 184 次插入202 次删除
  1. 4 1
      CHANGELOG.md
  2. 1 1
      circle.yml
  3. 1 1
      package.json
  4. 1 0
      pkg/api/api.go
  5. 0 2
      pkg/api/dataproxy.go
  6. 0 1
      pkg/services/search/handlers.go
  7. 0 1
      pkg/services/search/models.go
  8. 1 7
      pkg/services/sqlstore/dashboard.go
  9. 4 0
      pkg/services/sqlstore/migrations/dashboard_mig.go
  10. 3 1
      public/app/core/profiler.ts
  11. 1 3
      public/app/features/dashboard/import/dash_import.ts
  12. 1 1
      public/app/features/dashboard/shareModalCtrl.js
  13. 12 2
      public/app/features/plugins/ds_edit_ctrl.ts
  14. 5 2
      public/app/features/plugins/partials/ds_http_settings.html
  15. 1 1
      public/app/features/styleguide/styleguide.ts
  16. 1 1
      public/app/plugins/datasource/elasticsearch/partials/config.html
  17. 3 1
      public/app/plugins/datasource/graphite/partials/config.html
  18. 1 1
      public/app/plugins/datasource/influxdb/partials/config.html
  19. 16 10
      public/app/plugins/datasource/opentsdb/datasource.js
  20. 2 2
      public/app/plugins/datasource/opentsdb/partials/config.html
  21. 1 1
      public/app/plugins/datasource/prometheus/partials/config.html
  22. 8 88
      public/app/plugins/panel/graph/graph.js
  23. 4 4
      public/app/plugins/panel/graph/module.ts
  24. 71 25
      public/app/plugins/panel/graph/specs/graph_specs.ts
  25. 2 2
      public/sass/components/_dropdown.scss
  26. 2 2
      public/sass/components/_footer.scss
  27. 4 4
      public/sass/components/_gf-form.scss
  28. 1 1
      public/sass/components/_infobox.scss
  29. 7 7
      public/sass/components/_panel_graph.scss
  30. 2 2
      public/sass/components/_search.scss
  31. 1 1
      public/sass/components/_sidemenu.scss
  32. 7 7
      public/sass/components/_switch.scss
  33. 1 1
      public/sass/components/_tagsinput.scss
  34. 9 10
      public/sass/components/_tightform.scss
  35. 2 2
      public/sass/components/_timepicker.scss
  36. 1 1
      tasks/options/postcss.js
  37. 3 5
      vendor/phantomjs/render.js

+ 4 - 1
CHANGELOG.md

@@ -15,10 +15,13 @@
 * **OpenTSDB**: Support nested template variables in tag_values function, closes [4398](https://github.com/grafana/grafana/issues/4398)
 * **Datasource**: Pending data source requests are cancelled before new ones are issues (Graphite & Prometheus), closes [5321](https://github.com/grafana/grafana/issues/5321)
 
-## Breaking changes
+### Breaking changes
 * **Logging** : Changed default logging output format (now structured into message, and key value pairs, with logger key acting as component). You can also no change in config to json log ouput.
 * **Graphite** : The Graph panel no longer have a Graphite PNG option. closes #[5367](https://github.com/grafana/grafana/issues/5367)
 
+### Bug fixes
+* **PNG rendering**: Fixed phantomjs rendering and y-axis label rotation. fixes #[5220](https://github.com/grafana/grafana/issues/5220)
+
 # 3.0.4 Patch release (2016-05-25)
 * **Panel**: Fixed blank dashboard issue when switching to other dashboard while in fullscreen edit mode, fixes [#5163](https://github.com/grafana/grafana/pull/5163)
 * **Templating**: Fixed issue with nested multi select variables and cascading and updating child variable selection state, fixes [#4861](https://github.com/grafana/grafana/pull/4861)

+ 1 - 1
circle.yml

@@ -1,6 +1,6 @@
 machine:
   node:
-    version: 4.0
+    version: 5.11.1
   environment:
     GOPATH: "/home/ubuntu/.go_workspace"
     ORG_PATH: "github.com/grafana"

+ 1 - 1
package.json

@@ -50,7 +50,7 @@
     "karma-phantomjs-launcher": "1.0.0",
     "load-grunt-tasks": "3.4.0",
     "mocha": "2.3.4",
-    "phantomjs-prebuilt": "^2.1.3",
+    "phantomjs-prebuilt": "^2.1.7",
     "reflect-metadata": "0.1.2",
     "rxjs": "5.0.0-beta.4",
     "sass-lint": "^1.6.0",

+ 1 - 0
pkg/api/api.go

@@ -34,6 +34,7 @@ func Register(r *macaron.Macaron) {
 	r.Get("/org/", reqSignedIn, Index)
 	r.Get("/org/new", reqSignedIn, Index)
 	r.Get("/datasources/", reqSignedIn, Index)
+	r.Get("/datasources/new", reqSignedIn, Index)
 	r.Get("/datasources/edit/*", reqSignedIn, Index)
 	r.Get("/org/users/", reqSignedIn, Index)
 	r.Get("/org/apikeys/", reqSignedIn, Index)

+ 0 - 2
pkg/api/dataproxy.go

@@ -63,8 +63,6 @@ func NewReverseProxy(ds *m.DataSource, proxyPath string, targetUrl *url.URL) *ht
 			req.Header.Add("Authorization", dsAuth)
 		}
 
-		time.Sleep(time.Second * 5)
-
 		// clear cookie headers
 		req.Header.Del("Cookie")
 		req.Header.Del("Set-Cookie")

+ 0 - 1
pkg/services/search/handlers.go

@@ -44,7 +44,6 @@ func searchHandler(query *Query) error {
 		IsStarred:    query.IsStarred,
 		OrgId:        query.OrgId,
 		DashboardIds: query.DashboardIds,
-		Limit:        query.Limit,
 	}
 
 	if err := bus.Dispatch(&dashQuery); err != nil {

+ 0 - 1
pkg/services/search/models.go

@@ -42,7 +42,6 @@ type FindPersistedDashboardsQuery struct {
 	UserId       int64
 	IsStarred    bool
 	DashboardIds []int
-	Limit        int
 
 	Result HitList
 }

+ 1 - 7
pkg/services/sqlstore/dashboard.go

@@ -123,11 +123,6 @@ type DashboardSearchProjection struct {
 }
 
 func SearchDashboards(query *search.FindPersistedDashboardsQuery) error {
-	limit := query.Limit
-	if limit == 0 {
-		limit = 1000
-	}
-
 	var sql bytes.Buffer
 	params := make([]interface{}, 0)
 
@@ -170,8 +165,7 @@ func SearchDashboards(query *search.FindPersistedDashboardsQuery) error {
 		params = append(params, "%"+query.Title+"%")
 	}
 
-	sql.WriteString(fmt.Sprintf(" ORDER BY dashboard.title ASC LIMIT ?"))
-	params = append(params, limit)
+	sql.WriteString(fmt.Sprintf(" ORDER BY dashboard.title ASC LIMIT 1000"))
 
 	var res []DashboardSearchProjection
 

+ 4 - 0
pkg/services/sqlstore/migrations/dashboard_mig.go

@@ -107,4 +107,8 @@ func addDashboardMigration(mg *Migrator) {
 	mg.AddMigration("Add column gnetId in dashboard", NewAddColumnMigration(dashboardV2, &Column{
 		Name: "gnet_id", Type: DB_BigInt, Nullable: true,
 	}))
+
+	mg.AddMigration("Add index for gnetId in dashboard", NewAddIndexMigration(dashboardV2, &Index{
+		Cols: []string{"gnet_id"}, Type: IndexType,
+	}))
 }

+ 3 - 1
public/app/core/profiler.ts

@@ -99,7 +99,9 @@ export class Profiler {
   }
 
   renderingCompleted(panelId, panelTimings) {
-    this.panelsRendered++;
+    // add render counter to root scope
+    // used by phantomjs render.js to know when panel has rendered
+    this.$rootScope.panelsRendered = this.panelsRendered++;
 
     if (this.enabled) {
       panelTimings.renderEnd = new Date().getTime();

+ 1 - 3
public/app/features/dashboard/import/dash_import.ts

@@ -69,9 +69,7 @@ export class DashImportCtrl {
 
     if (sources.length === 0) {
       inputModel.info = "No data sources of type " + input.pluginName + " found";
-    } else if (inputModel.description) {
-      inputModel.info = inputModel.description;
-    } else {
+    } else if (!inputModel.info) {
       inputModel.info = "Select a " + input.pluginName + " data source";
     }
 

+ 1 - 1
public/app/features/dashboard/shareModalCtrl.js

@@ -75,7 +75,7 @@ function (angular, _, require, config) {
 
       var soloUrl = $scope.shareUrl;
       soloUrl = soloUrl.replace(config.appSubUrl + '/dashboard/', config.appSubUrl + '/dashboard-solo/');
-      soloUrl = soloUrl.replace("&fullscreen", "");
+      soloUrl = soloUrl.replace("&fullscreen", "").replace("&edit", "");
 
       $scope.iframeHtml = '<iframe src="' + soloUrl + '" width="450" height="200" frameborder="0"></iframe>';
 

+ 12 - 2
public/app/features/plugins/ds_edit_ctrl.ts

@@ -166,7 +166,17 @@ coreModule.controller('DataSourceEditCtrl', DataSourceEditCtrl);
 
 coreModule.directive('datasourceHttpSettings', function() {
   return {
-    scope: {current: "="},
-    templateUrl: 'public/app/features/plugins/partials/ds_http_settings.html'
+    scope: {
+      current: "=",
+      suggestUrl: "@",
+    },
+    templateUrl: 'public/app/features/plugins/partials/ds_http_settings.html',
+    link: {
+      pre: function($scope, elem, attrs) {
+        $scope.getSuggestUrls = function() {
+          return [$scope.suggestUrl];
+        };
+      }
+    }
   };
 });

+ 5 - 2
public/app/features/plugins/partials/ds_http_settings.html

@@ -6,7 +6,10 @@
 	<div class="gf-form-inline">
 		<div class="gf-form max-width-30">
 			<span class="gf-form-label width-7">Url</span>
-			<input class="gf-form-input" type="text" ng-model='current.url' placeholder="for example: http://localhost:8081" ng-pattern="/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/" required></input>
+			<input class="gf-form-input" type="text"
+						 ng-model='current.url' placeholder="{{suggestUrl}}"
+						 bs-typeahead="getSuggestUrls"  min-length="0"
+						 ng-pattern="/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/" required></input>
 			<info-popover mode="right-absolute">
 				<p>Specify a complete HTTP url (for example http://your_server:8080)</p>
 				<span ng-show="current.access === 'direct'">
@@ -57,7 +60,7 @@
 
 	<div class="gf-form" ng-if="current.basicAuth">
 		<span class="gf-form-label width-7">
-			Passord
+			Password
 		</span>
 		<input class="gf-form-input max-width-21" type="password" ng-model='current.basicAuthPassword' placeholder="password" required></input>
 	</div>

+ 1 - 1
public/app/features/styleguide/styleguide.ts

@@ -10,7 +10,7 @@ class StyleGuideCtrl {
   buttonSizes = ['btn-small', '', 'btn-large'];
   buttonVariants = ['-', '-outline-'];
   page: any;
-  pages = ['colors', 'buttons', 'forms', 'dashboard', 'query-editors'];
+  pages = ['colors', 'buttons'];
 
   /** @ngInject **/
   constructor(private $http, $routeParams) {

+ 1 - 1
public/app/plugins/datasource/elasticsearch/partials/config.html

@@ -1,4 +1,4 @@
-<datasource-http-settings current="ctrl.current">
+<datasource-http-settings current="ctrl.current" suggest-url="http://localhost:9200">
 </datasource-http-settings>
 
 <h3 class="page-heading">Elasticsearch details</h3>

+ 3 - 1
public/app/plugins/datasource/graphite/partials/config.html

@@ -1,3 +1,5 @@
-<datasource-http-settings current="ctrl.current">
+<datasource-http-settings
+	current="ctrl.current"
+	suggest-url="http://localhost:8080">
 </datasource-http-settings>
 

+ 1 - 1
public/app/plugins/datasource/influxdb/partials/config.html

@@ -1,4 +1,4 @@
-<datasource-http-settings current="ctrl.current">
+<datasource-http-settings current="ctrl.current" suggest-url="http://localhost:8086">
 </datasource-http-settings>
 
 <h3 class="page-heading">InfluxDB Details</h3>

+ 16 - 10
public/app/plugins/datasource/opentsdb/datasource.js

@@ -128,13 +128,7 @@ function (angular, _, dateMath) {
         data: reqBody
       };
 
-      if (this.basicAuth || this.withCredentials) {
-        options.withCredentials = true;
-      }
-
-      if (this.basicAuth) {
-        options.headers = {"Authorization": this.basicAuth};
-      }
+      this._addCredentialOptions(options);
 
       // In case the backend is 3rd-party hosted and does not suport OPTIONS, urlencoded requests
       // go as POST rather than OPTIONS+POST
@@ -210,11 +204,24 @@ function (angular, _, dateMath) {
     };
 
     this._get = function(relativeUrl, params) {
-      return backendSrv.datasourceRequest({
+      var options = {
         method: 'GET',
         url: this.url + relativeUrl,
         params: params,
-      });
+      };
+
+      this._addCredentialOptions(options);
+
+      return backendSrv.datasourceRequest(options);
+    };
+
+    this._addCredentialOptions = function(options) {
+      if (this.basicAuth || this.withCredentials) {
+        options.withCredentials = true;
+      }
+      if (this.basicAuth) {
+        options.headers = {"Authorization": this.basicAuth};
+      }
     };
 
     this.metricFindQuery = function(query) {
@@ -435,7 +442,6 @@ function (angular, _, dateMath) {
       date = dateMath.parse(date, roundUp);
       return date.valueOf();
     }
-
   }
 
   return {

+ 2 - 2
public/app/plugins/datasource/opentsdb/partials/config.html

@@ -1,6 +1,6 @@
-<datasource-http-settings current="ctrl.current"></datasource-http-settings>
+<datasource-http-settings current="ctrl.current" suggest-url="http://localhost:4242"></datasource-http-settings>
 
-<h5>Opentsdb settings</h5>
+<h5>OpenTSDB settings</h5>
 <div class="gf-form">
   <span class="gf-form-label width-7">
     Version

+ 1 - 1
public/app/plugins/datasource/prometheus/partials/config.html

@@ -1,3 +1,3 @@
-<datasource-http-settings current="ctrl.current">
+<datasource-http-settings current="ctrl.current" suggest-url="http://localhost:9090">
 </datasource-http-settings>
 

+ 8 - 88
public/app/plugins/panel/graph/graph.js

@@ -20,7 +20,6 @@ function (angular, $, moment, _, kbn, GraphTooltip, thresholds) {
 
   var module = angular.module('grafana.directives');
   var labelWidthCache = {};
-  var panelWidthCache = {};
 
   // systemjs export
   var ThresholdControls = thresholds.ThresholdControls;
@@ -107,11 +106,6 @@ function (angular, $, moment, _, kbn, GraphTooltip, thresholds) {
 
           if (!setElementHeight()) { return true; }
 
-          if(_.isString(data)) {
-            render_panel_as_graphite_png(data);
-            return true;
-          }
-
           if (panelWidth === 0) {
             return true;
           }
@@ -181,10 +175,7 @@ function (angular, $, moment, _, kbn, GraphTooltip, thresholds) {
 
         // Function for rendering panel
         function render_panel() {
-          panelWidth = panelWidthCache[panel.span];
-          if (!panelWidth) {
-            panelWidth = panelWidthCache[panel.span] = elem.width();
-          }
+          panelWidth =  elem.width();
 
           if (shouldAbortRender()) {
             return;
@@ -340,7 +331,6 @@ function (angular, $, moment, _, kbn, GraphTooltip, thresholds) {
           var crit = panel.alert.crit;
           var warn = panel.alert.warn;
           var critEdge = Infinity;
-          var warnEdge = crit.value;
 
           if (_.isNumber(crit.value)) {
             if (crit.op === '<') {
@@ -361,8 +351,13 @@ function (angular, $, moment, _, kbn, GraphTooltip, thresholds) {
           }
 
           if (_.isNumber(warn.value)) {
-            // if (warn.op === '<') {
-            // }
+            //var warnEdge = crit.value || Infinity;
+            var warnEdge;
+            if (crit.value) {
+              warnEdge = crit.value;
+            } else {
+              warnEdge = warn.op === '<' ? -Infinity : Infinity;
+            }
 
             // fill
             options.grid.markings.push({
@@ -527,80 +522,6 @@ function (angular, $, moment, _, kbn, GraphTooltip, thresholds) {
           return "%H:%M";
         }
 
-        function render_panel_as_graphite_png(url) {
-          url += '&width=' + panelWidth;
-          url += '&height=' + elem.css('height').replace('px', '');
-          url += '&bgcolor=1f1f1f'; // @grayDarker & @grafanaPanelBackground
-          url += '&fgcolor=BBBFC2'; // @textColor & @grayLighter
-          url += panel.stack ? '&areaMode=stacked' : '';
-          url += panel.fill !== 0 ? ('&areaAlpha=' + (panel.fill/10).toFixed(1)) : '';
-          url += panel.linewidth !== 0 ? '&lineWidth=' + panel.linewidth : '';
-          url += panel.legend.show ? '&hideLegend=false' : '&hideLegend=true';
-
-          if (panel.yaxes && panel.yaxes.length > 0) {
-            var showYaxis = false;
-            for(var i = 0; panel.yaxes.length > i; i++) {
-              if (panel.yaxes[i].show) {
-                url += (panel.yaxes[i].min !== null && panel.yaxes[i].min !== undefined) ? '&yMin=' + panel.yaxes[i].min : '';
-                url += (panel.yaxes[i].max !== null && panel.yaxes[i].max !== undefined) ? '&yMax=' + panel.yaxes[i].max : '';
-                showYaxis = true;
-                break;
-              }
-            }
-            url += showYaxis ? '' : '&hideYAxis=true';
-          }
-
-          url += panel.xaxis.show ? '' : '&hideAxes=true';
-
-          switch(panel.yaxes[0].format) {
-            case 'bytes':
-              url += '&yUnitSystem=binary';
-              break;
-            case 'bits':
-              url += '&yUnitSystem=binary';
-              break;
-            case 'bps':
-              url += '&yUnitSystem=si';
-              break;
-            case 'pps':
-              url += '&yUnitSystem=si';
-              break;
-            case 'Bps':
-              url += '&yUnitSystem=si';
-              break;
-            case 'short':
-              url += '&yUnitSystem=si';
-              break;
-            case 'joule':
-              url += '&yUnitSystem=si';
-              break;
-            case 'watt':
-              url += '&yUnitSystem=si';
-              break;
-            case 'ev':
-              url += '&yUnitSystem=si';
-              break;
-            case 'none':
-              url += '&yUnitSystem=none';
-              break;
-          }
-
-          switch(panel.nullPointMode) {
-            case 'connected':
-              url += '&lineMode=connected';
-              break;
-            case 'null':
-              break; // graphite default lineMode
-            case 'null as zero':
-              url += "&drawNullAsZero=true";
-              break;
-          }
-
-          url += panel.steppedLine ? '&lineMode=staircase' : '';
-
-          elem.html('<img src="' + url + '"></img>');
-        }
-
         new GraphTooltip(elem, dashboard, scope, function() {
           return sortedSeries;
         });
@@ -616,5 +537,4 @@ function (angular, $, moment, _, kbn, GraphTooltip, thresholds) {
       }
     };
   });
-
 });

+ 4 - 4
public/app/plugins/panel/graph/module.ts

@@ -54,9 +54,9 @@ class GraphCtrl extends MetricsPanelCtrl {
     xaxis: {
       show: true
     },
-    thresholds: {
-      warn: {op: '>', level: undefined},
-      crit: {op: '>', level: undefined},
+    alert: {
+      warn: {op: '>', value: undefined},
+      crit: {op: '>', value: undefined},
     },
     // show/hide lines
     lines         : true,
@@ -113,7 +113,7 @@ class GraphCtrl extends MetricsPanelCtrl {
 
     _.defaults(this.panel, this.panelDefaults);
     _.defaults(this.panel.tooltip, this.panelDefaults.tooltip);
-    _.defaults(this.panel.thresholds, this.panelDefaults.thresholds);
+    _.defaults(this.panel.alert, this.panelDefaults.alert);
     _.defaults(this.panel.legend, this.panelDefaults.legend);
 
     this.colors = $scope.$root.colors;

+ 71 - 25
public/app/plugins/panel/graph/specs/graph_specs.ts

@@ -113,55 +113,101 @@ describe('grafanaGraph', function() {
 
   graphScenario('grid thresholds 100, 200', function(ctx) {
     ctx.setup(function(ctrl) {
-      ctrl.panel.grid = {
-        threshold1: 100,
-        threshold1Color: "#111",
-        threshold2: 200,
-        threshold2Color: "#222",
+      ctrl.panel.alert = {
+        warn: { op: ">", value: 100},
+        crit: { op: ">", value: 200}
       };
     });
 
-    it('should add grid markings', function() {
+    it('should add crit fill', function() {
       var markings = ctx.plotOptions.grid.markings;
-      expect(markings[0].yaxis.from).to.be(100);
-      expect(markings[0].yaxis.to).to.be(200);
-      expect(markings[0].color).to.be('#111');
+
+      expect(markings[0].yaxis.from).to.be(200);
+      expect(markings[0].yaxis.to).to.be(Infinity);
+      expect(markings[0].color).to.be('rgba(234, 112, 112, 0.10)');
+    });
+
+    it('should add crit line', function() {
+      var markings = ctx.plotOptions.grid.markings;
+
       expect(markings[1].yaxis.from).to.be(200);
-      expect(markings[1].yaxis.to).to.be(Infinity);
+      expect(markings[1].yaxis.to).to.be(200);
+      expect(markings[1].color).to.be('#ed2e18');
+    });
+
+    it('should add warn fill', function() {
+      var markings = ctx.plotOptions.grid.markings;
+
+      expect(markings[2].yaxis.from).to.be(100);
+      expect(markings[2].yaxis.to).to.be(200);
+      expect(markings[2].color).to.be('rgba(216, 200, 27, 0.10)');
+    });
+
+    it('should add warn line', function() {
+      var markings = ctx.plotOptions.grid.markings;
+      expect(markings[3].yaxis.from).to.be(100);
+      expect(markings[3].yaxis.to).to.be(100);
+      expect(markings[3].color).to.be('#F79520');
     });
   });
 
   graphScenario('inverted grid thresholds 200, 100', function(ctx) {
     ctx.setup(function(ctrl) {
-      ctrl.panel.grid = {
-        threshold1: 200,
-        threshold1Color: "#111",
-        threshold2: 100,
-        threshold2Color: "#222",
+      ctrl.panel.alert = {
+        warn: { op: "<", value: 200},
+        crit: { op: "<", value: 100}
       };
     });
 
-    it('should add grid markings', function() {
+    it('should add crit fill', function() {
+      var markings = ctx.plotOptions.grid.markings;
+      expect(markings[0].yaxis.from).to.be(100);
+      expect(markings[0].yaxis.to).to.be(-Infinity);
+      expect(markings[0].color).to.be('rgba(234, 112, 112, 0.10)');
+    });
+
+    it('should add crit line', function() {
       var markings = ctx.plotOptions.grid.markings;
-      expect(markings[0].yaxis.from).to.be(200);
-      expect(markings[0].yaxis.to).to.be(100);
-      expect(markings[0].color).to.be('#111');
       expect(markings[1].yaxis.from).to.be(100);
-      expect(markings[1].yaxis.to).to.be(-Infinity);
+      expect(markings[1].yaxis.to).to.be(100);
+      expect(markings[1].color).to.be('#ed2e18');
+    });
+
+    it('should add warn fill', function() {
+      var markings = ctx.plotOptions.grid.markings;
+      expect(markings[2].yaxis.from).to.be(200);
+      expect(markings[2].yaxis.to).to.be(100);
+      expect(markings[2].color).to.be('rgba(216, 200, 27, 0.10)');
+    });
+
+    it('should add warn line', function() {
+      var markings = ctx.plotOptions.grid.markings;
+      expect(markings[3].yaxis.from).to.be(200);
+      expect(markings[3].yaxis.to).to.be(200);
+      expect(markings[3].color).to.be('#F79520');
     });
   });
 
-  graphScenario('grid thresholds from zero', function(ctx) {
+  graphScenario('grid warn thresholds from zero', function(ctx) {
     ctx.setup(function(ctrl) {
-      ctrl.panel.grid = {
-        threshold1: 0,
-        threshold1Color: "#111",
+      ctrl.panel.alert = {
+        warn: { op: ">", value: 0},
+        crit: { op: ">", value: undefined}
       };
     });
 
-    it('should add grid markings', function() {
+    it('should add warn fill', function() {
       var markings = ctx.plotOptions.grid.markings;
       expect(markings[0].yaxis.from).to.be(0);
+      expect(markings[0].yaxis.to).to.be(Infinity);
+      expect(markings[0].color).to.be('rgba(216, 200, 27, 0.10)');
+    });
+
+    it('should add warn line', function() {
+      var markings = ctx.plotOptions.grid.markings;
+      expect(markings[1].yaxis.from).to.be(0);
+      expect(markings[1].yaxis.to).to.be(0);
+      expect(markings[1].color).to.be('#F79520');
     });
   });
 

+ 2 - 2
public/sass/components/_dropdown.scss

@@ -207,7 +207,7 @@
 }
 
 // Caret to indicate there is a submenu
-.dropdown-submenu > a:after {
+.dropdown-submenu > a::after {
   display: block;
   content: " ";
   float: right;
@@ -220,7 +220,7 @@
   margin-top: 5px;
   margin-right: -10px;
 }
-.dropdown-submenu:hover > a:after {
+.dropdown-submenu:hover > a::after {
   border-left-color: $dropdownLinkColorHover;
 }
 

+ 2 - 2
public/sass/components/_footer.scss

@@ -23,14 +23,14 @@
   li {
     display: inline-block;
     padding-right: 2px;
-    &:after {
+    &::after {
       content: ' | ';
       padding-left: 2px;
     }
   }
 
   li:last-child {
-    &:after {
+    &::after {
       padding-left: 0;
       content: '';
     }

+ 4 - 4
public/sass/components/_gf-form.scss

@@ -127,7 +127,7 @@ $gf-form-margin: 0.25rem;
   &--dropdown {
     padding-right: $input-padding-x*2;
 
-    &:after {
+    &::after {
       position: absolute;
       top: 35%;
       right: $input-padding-x/2;
@@ -164,7 +164,7 @@ $gf-form-margin: 0.25rem;
     }
   }
 
-  &:after {
+  &::after {
     position: absolute;
     top: 35%;
     right: $input-padding-x/2;
@@ -176,7 +176,7 @@ $gf-form-margin: 0.25rem;
   }
 
   &--has-help-icon {
-    &:after {
+    &::after {
       right: $input-padding-x*3;
     }
   }
@@ -215,7 +215,7 @@ $gf-form-margin: 0.25rem;
   background-color: $input-bg;
   padding-right: $input-padding-x;
 
-  &:after {
+  &::after {
     position: absolute;
     top: 35%;
     right: $input-padding-x/2;

+ 1 - 1
public/sass/components/_infobox.scss

@@ -1,4 +1,4 @@
-.grafana-info-box:before {
+.grafana-info-box::before {
   content: "\f05a";
   font-family:'FontAwesome';
   position: absolute;

+ 7 - 7
public/sass/components/_panel_graph.scss

@@ -47,19 +47,19 @@
   white-space: nowrap;
   font-size: 85%;
   text-align: left;
-  &.current:before {
+  &.current::before {
     content: "Current: "
   }
-  &.max:before {
+  &.max::before {
     content: "Max: "
   }
-  &.min:before {
+  &.min::before {
     content: "Min: "
   }
-  &.total:before {
+  &.total::before {
     content: "Total: "
   }
-  &.avg:before {
+  &.avg::before {
     content: "Avg: "
   }
 }
@@ -95,7 +95,7 @@
     &--right-y {
       float: none;
 
-      .graph-legend-alias:after {
+      .graph-legend-alias::after {
         content: '(right-y)';
         padding: 0 5px;
         color: $text-color-weak;
@@ -137,7 +137,7 @@
 
   .graph-legend-value {
     &.current, &.max, &.min, &.total, &.avg {
-      &:before {
+      &::before {
         content: '';
       }
     }

+ 2 - 2
public/sass/components/_search.scss

@@ -74,11 +74,11 @@
     background-color: $grafanaListBackground;
     margin-bottom: 4px;
 
-    .search-result-icon:before {
+    .search-result-icon::before {
       content: "\f009";
     }
 
-    &.search-item-dash-home .search-result-icon:before {
+    &.search-item-dash-home .search-result-icon::before {
       content: "\f015";
     }
   }

+ 1 - 1
public/sass/components/_sidemenu.scss

@@ -151,7 +151,7 @@
   display: inline-block;
   position: relative;
 
-  &:after {
+  &::after {
     display: block;
     position: absolute;
     top: 50%;

+ 7 - 7
public/sass/components/_switch.scss

@@ -30,7 +30,7 @@ $switch-height: 1.5rem;
     background-color: $page-bg;
   }
 
-  input + label:before, input + label:after {
+  input + label::before, input + label::after {
     @include buttonBackground($input-bg, $input-bg);
 
     display: block;
@@ -47,13 +47,13 @@ $switch-height: 1.5rem;
   }
 
   &:hover {
-    input + label:before {
+    input + label::before {
       @include buttonBackground($input-bg, lighten($input-bg, 5%));
       color: $text-color;
       text-shadow: $text-shadow-faint;
     }
 
-    input + label:after {
+    input + label::after {
       @include buttonBackground($input-bg, lighten($input-bg, 5%));
       color: lighten($orange, 10%);
       text-shadow: $text-shadow-strong;
@@ -61,7 +61,7 @@ $switch-height: 1.5rem;
 
   }
 
-  input + label:before {
+  input + label::before {
     font-family: 'FontAwesome';
     content: "\f096"; // square-o
     color: $text-color-faint;
@@ -70,7 +70,7 @@ $switch-height: 1.5rem;
     text-shadow: $text-shadow-faint;
   }
 
-  input + label:after {
+  input + label::after {
     content: "\f046"; // check-square-o
     color: $orange;
     text-shadow: $text-shadow-strong;
@@ -81,11 +81,11 @@ $switch-height: 1.5rem;
     backface-visibility: hidden;
   }
 
-  input:checked + label:before {
+  input:checked + label::before {
     transform: rotateY(180deg);
   }
 
-  input:checked + label:after {
+  input:checked + label::after {
     transform: rotateY(0);
   }
 }

+ 1 - 1
public/sass/components/_tagsinput.scss

@@ -25,7 +25,7 @@
     [data-role="remove"] {
       margin-left:8px;
       cursor:pointer;
-      &:after{
+      &::after{
         content: "x";
         padding:0px 2px;
       }

+ 9 - 10
public/sass/components/_tightform.scss

@@ -25,12 +25,11 @@
   border: 1px solid $tight-form-border;
   border-bottom: none;
 
-  .tight-form, .tight-form-item, [type=text].tight-form-input, [type=text].tight-form-clear-input  {
+  .tight-form, .tight-form-item, [type="text"].tight-form-input, [type="text"].tight-form-clear-input  {
     border: none;
   }
 }
 
-
 .spaced-form {
   .tight-form {
     margin: 7px 0;
@@ -122,14 +121,14 @@
   }
 }
 
-input[type=text].tight-form-func-param {
+input[type="text"].tight-form-func-param {
   background: transparent;
   border: none;
   margin: 0;
   padding: 0;
 }
 
-input[type=text].tight-form-clear-input {
+input[type="text"].tight-form-clear-input {
   padding: 8px 7px;
   border: none;
   margin: 0px;
@@ -138,10 +137,10 @@ input[type=text].tight-form-clear-input {
   border-right: 1px solid $tight-form-border;
 }
 
-[type=text],
-[type=email],
-[type=number],
-[type=password] {
+[type="text"],
+[type="email"],
+[type="number"],
+[type="password"] {
   &.tight-form-input {
     background-color: $input-bg;
     border: none;
@@ -157,7 +156,7 @@ input[type=text].tight-form-clear-input {
   }
 }
 
-input[type=checkbox].tight-form-checkbox {
+input[type="checkbox"].tight-form-checkbox {
   margin: 0;
 }
 
@@ -200,7 +199,7 @@ select.tight-form-input {
 }
 
 .tight-form-radio {
-  input[type=radio] {
+  input[type="radio"] {
     margin: 0;
   }
   label {

+ 2 - 2
public/sass/components/_timepicker.scss

@@ -99,10 +99,10 @@
   -moz-osx-font-smoothing: grayscale;
 }
 
-.fa-chevron-left:before {
+.fa-chevron-left::before {
   content: "\f053";
 }
-.fa-chevron-right:before {
+.fa-chevron-right::before {
   content: "\f054";
 }
 

+ 1 - 1
tasks/options/postcss.js

@@ -11,7 +11,7 @@ module.exports = function(config) {
       },
 
       processors: [
-        require('autoprefixer')({browsers: 'last 2 versions'}), // add vendor prefixes
+        require('autoprefixer')({browsers: 'last 3 versions'}), // add vendor prefixes
       ]
     },
     dist: {

+ 3 - 5
vendor/phantomjs/render.js

@@ -36,7 +36,7 @@
     // console.log('Loading a web page: ' + params.url + ' status: ' + status);
 
     function checkIsReady() {
-      var canvas = page.evaluate(function() {
+      var panelsRendered = page.evaluate(function() {
         if (!window.angular) { return false; }
         var body = window.angular.element(document.body);
         if (!body.injector) { return false; }
@@ -44,12 +44,10 @@
 
         var rootScope = body.injector().get('$rootScope');
         if (!rootScope) {return false;}
-        if (!rootScope.performance) { return false; }
-        var panelsToLoad = window.angular.element('div.panel').length;
-        return rootScope.performance.panelsRendered >= panelsToLoad;
+        return rootScope.panelsRendered;
       });
 
-      if (canvas || tries === 1000) {
+      if (panelsRendered || tries === 1000) {
         var bb = page.evaluate(function () {
           return document.getElementsByClassName("main-view")[0].getBoundingClientRect();
         });