Преглед изворни кода

Merge branch 'master' into 10630_folder_api

Marcus Efraimsson пре 7 година
родитељ
комит
3c6bc263dc
37 измењених фајлова са 186 додато и 122 уклоњено
  1. 8 0
      CHANGELOG.md
  2. 1 4
      conf/provisioning/dashboards/sample.yaml
  3. 1 4
      conf/provisioning/datasources/sample.yaml
  4. 25 0
      docker/blocks/prometheus2/docker-compose.yaml
  5. 3 3
      docs/sources/installation/debian.md
  6. 2 2
      docs/sources/installation/rpm.md
  7. 1 1
      docs/sources/installation/windows.md
  8. 1 1
      package.json
  9. 2 2
      packaging/publish/publish_testing.sh
  10. 16 15
      pkg/models/datasource.go
  11. 1 1
      pkg/plugins/dashboard_importer.go
  12. 1 0
      pkg/services/dashboards/dashboard_service.go
  13. 1 1
      pkg/services/provisioning/dashboards/config_reader.go
  14. 8 8
      pkg/services/provisioning/dashboards/test-configs/dashboards-from-disk/sample.yaml
  15. 1 1
      pkg/services/provisioning/datasources/config_reader.go
  16. 12 1
      pkg/services/provisioning/datasources/config_reader_test.go
  17. 27 27
      pkg/services/provisioning/datasources/test-configs/all-properties/sample.yaml
  18. 1 1
      pkg/social/github_oauth.go
  19. 1 1
      pkg/tsdb/influxdb/query.go
  20. 6 0
      pkg/tsdb/influxdb/query_test.go
  21. 1 2
      public/app/core/components/org_switcher.ts
  22. 1 1
      public/app/core/components/search/search_results.html
  23. 1 1
      public/app/core/controllers/invited_ctrl.ts
  24. 7 0
      public/app/core/controllers/signup_ctrl.ts
  25. 1 1
      public/app/features/alerting/alert_tab_ctrl.ts
  26. 6 0
      public/app/features/dashboard/dashboard_migration.ts
  27. 10 28
      public/app/features/org/partials/select_org.html
  28. 8 0
      public/app/features/org/select_org_ctrl.ts
  29. 1 3
      public/app/features/plugins/import_list/import_list.html
  30. 5 1
      public/app/plugins/panel/graph/legend.ts
  31. 1 1
      public/app/plugins/panel/graph/tab_display.html
  32. 9 4
      public/app/plugins/panel/heatmap/color_legend.ts
  33. 1 2
      public/app/plugins/panel/heatmap/rendering.ts
  34. 3 0
      public/app/plugins/panel/heatmap/specs/renderer_specs.ts
  35. 5 0
      public/sass/components/_modals.scss
  36. 6 4
      public/sass/components/_panel_heatmap.scss
  37. 1 1
      public/sass/components/_scrollbar.scss

+ 8 - 0
CHANGELOG.md

@@ -1,3 +1,11 @@
+# 5.0.0-beta4 (2018-02-19)
+
+### Fixes
+
+- **Dashboard** Fixed dashboard overwrite permission issue [#10814](https://github.com/grafana/grafana/issues/10814)
+- **Keyboard shortcuts** Fixed Esc key when in panel edit/view mode [#10945](https://github.com/grafana/grafana/issues/10945)
+- **Save dashboard** Fixed issue with time range & variable reset after saving [#10946](https://github.com/grafana/grafana/issues/10946)
+
 # 5.0.0-beta3 (2018-02-16)
 
 ### Fixes

+ 1 - 4
conf/provisioning/dashboards/sample.yaml

@@ -1,8 +1,5 @@
-# This file is only an example.
-# Grafana will never read sample.yaml files
-
 # # config file version
-# apiVersion: 1
+apiVersion: 1
 
 #providers:
 # - name: 'default'

+ 1 - 4
conf/provisioning/datasources/sample.yaml

@@ -1,8 +1,5 @@
-# This file is only an example.
-# Grafana will never read sample.yaml files
-
 # # config file version
-# apiVersion: 1
+apiVersion: 1
 
 # # list of datasources that should be deleted from the database
 #deleteDatasources:

+ 25 - 0
docker/blocks/prometheus2/docker-compose.yaml

@@ -0,0 +1,25 @@
+  prometheus:
+    build: blocks/prometheus2
+    network_mode: host
+    ports:
+      - "9090:9090"
+
+  node_exporter:
+    image: prom/node-exporter
+    network_mode: host
+    ports:
+      - "9100:9100"
+
+  fake-prometheus-data:
+    image: grafana/fake-data-gen
+    network_mode: host
+    ports:
+      - "9091:9091"
+    environment:
+      FD_DATASOURCE: prom
+
+  alertmanager:
+    image: quay.io/prometheus/alertmanager
+    network_mode: host
+    ports:
+      - "9093:9093"

+ 3 - 3
docs/sources/installation/debian.md

@@ -16,7 +16,7 @@ weight = 1
 Description | Download
 ------------ | -------------
 Stable for Debian-based Linux | [grafana_4.6.3_amd64.deb](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_4.6.3_amd64.deb)
-Beta for Debian-based Linux | [grafana_5.0.0-beta3_amd64.deb](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_5.0.0-beta3_amd64.deb)
+Beta for Debian-based Linux | [grafana_5.0.0-beta4_amd64.deb](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_5.0.0-beta4_amd64.deb)
 
 Read [Upgrading Grafana]({{< relref "installation/upgrading.md" >}}) for tips and guidance on updating an existing
 installation.
@@ -33,9 +33,9 @@ sudo dpkg -i grafana_4.6.3_amd64.deb
 ## Install Latest Beta
 
 ```bash
-wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_5.0.0-beta3_amd64.deb
+wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_5.0.0-beta4_amd64.deb
 sudo apt-get install -y adduser libfontconfig
-sudo dpkg -i grafana_5.0.0-beta3_amd64.deb
+sudo dpkg -i grafana_5.0.0-beta4_amd64.deb
 
 ```
 ## APT Repository

+ 2 - 2
docs/sources/installation/rpm.md

@@ -16,7 +16,7 @@ weight = 2
 Description | Download
 ------------ | -------------
 Stable for CentOS / Fedora / OpenSuse / Redhat Linux | [4.6.3 (x86-64 rpm)](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-4.6.3-1.x86_64.rpm)
-Latest Beta for CentOS / Fedora / OpenSuse / Redhat Linux | [5.0.0-beta3 (x86-64 rpm)](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.0.0-beta3.x86_64.rpm)
+Latest Beta for CentOS / Fedora / OpenSuse / Redhat Linux | [5.0.0-beta4 (x86-64 rpm)](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.0.0-beta4.x86_64.rpm)
 
 Read [Upgrading Grafana]({{< relref "installation/upgrading.md" >}}) for tips and guidance on updating an existing
 installation.
@@ -32,7 +32,7 @@ $ sudo yum install https://s3-us-west-2.amazonaws.com/grafana-releases/release/g
 ## Install Beta
 
 ```bash
-$ sudo yum install https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.0.0-beta3.x86_64.rpm
+$ sudo yum install https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.0.0-beta4.x86_64.rpm
 ```
 
 Or install manually using `rpm`.

+ 1 - 1
docs/sources/installation/windows.md

@@ -14,7 +14,7 @@ weight = 3
 Description | Download
 ------------ | -------------
 Latest stable package for Windows | [grafana.4.6.3.windows-x64.zip](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-4.6.3.windows-x64.zip)
-Latest beta package for Windows | [grafana.5.0.0-beta3.windows-x64.zip](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.0.0-beta3.windows-x64.zip)
+Latest beta package for Windows | [grafana.5.0.0-beta4.windows-x64.zip](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.0.0-beta4.windows-x64.zip)
 
 Read [Upgrading Grafana]({{< relref "installation/upgrading.md" >}}) for tips and guidance on updating an existing
 installation.

+ 1 - 1
package.json

@@ -4,7 +4,7 @@
     "company": "Grafana Labs"
   },
   "name": "grafana",
-  "version": "5.0.0-beta3",
+  "version": "5.0.0-beta4",
   "repository": {
     "type": "git",
     "url": "http://github.com/grafana/grafana.git"

+ 2 - 2
packaging/publish/publish_testing.sh

@@ -1,6 +1,6 @@
 #! /usr/bin/env bash
-deb_ver=5.0.0-beta3
-rpm_ver=5.0.0-beta3
+deb_ver=5.0.0-beta4
+rpm_ver=5.0.0-beta4
 
 wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_${deb_ver}_amd64.deb
 

+ 16 - 15
pkg/models/datasource.go

@@ -58,21 +58,22 @@ type DataSource struct {
 }
 
 var knownDatasourcePlugins map[string]bool = map[string]bool{
-	DS_ES:                                 true,
-	DS_GRAPHITE:                           true,
-	DS_INFLUXDB:                           true,
-	DS_INFLUXDB_08:                        true,
-	DS_KAIROSDB:                           true,
-	DS_CLOUDWATCH:                         true,
-	DS_PROMETHEUS:                         true,
-	DS_OPENTSDB:                           true,
-	DS_POSTGRES:                           true,
-	DS_MYSQL:                              true,
-	"opennms":                             true,
-	"druid":                               true,
-	"dalmatinerdb":                        true,
-	"gnocci":                              true,
-	"zabbix":                              true,
+	DS_ES:                       true,
+	DS_GRAPHITE:                 true,
+	DS_INFLUXDB:                 true,
+	DS_INFLUXDB_08:              true,
+	DS_KAIROSDB:                 true,
+	DS_CLOUDWATCH:               true,
+	DS_PROMETHEUS:               true,
+	DS_OPENTSDB:                 true,
+	DS_POSTGRES:                 true,
+	DS_MYSQL:                    true,
+	"opennms":                   true,
+	"abhisant-druid-datasource": true,
+	"dalmatinerdb-datasource":   true,
+	"gnocci":                    true,
+	"zabbix":                    true,
+	"alexanderzobnin-zabbix-datasource":   true,
 	"newrelic-app":                        true,
 	"grafana-datadog-datasource":          true,
 	"grafana-simple-json":                 true,

+ 1 - 1
pkg/plugins/dashboard_importer.go

@@ -35,7 +35,7 @@ type DashboardInputMissingError struct {
 }
 
 func (e DashboardInputMissingError) Error() string {
-	return fmt.Sprintf("Dashbord input variable: %v missing from import command", e.VariableName)
+	return fmt.Sprintf("Dashboard input variable: %v missing from import command", e.VariableName)
 }
 
 func init() {

+ 1 - 0
pkg/services/dashboards/dashboard_service.go

@@ -118,6 +118,7 @@ func (dr *dashboardServiceImpl) buildSaveDashboardCommand(dto *SaveDashboardDTO,
 		UserId:    dto.User.UserId,
 		FolderId:  dash.FolderId,
 		IsFolder:  dash.IsFolder,
+		PluginId:  dash.PluginId,
 	}
 
 	if !dto.UpdatedAt.IsZero() {

+ 1 - 1
pkg/services/provisioning/dashboards/config_reader.go

@@ -63,7 +63,7 @@ func (cr *configReader) readConfig() ([]*DashboardsAsConfig, error) {
 	}
 
 	for _, file := range files {
-		if (!strings.HasSuffix(file.Name(), ".yaml") && !strings.HasSuffix(file.Name(), ".yml")) || file.Name() == "sample.yaml" {
+		if !strings.HasSuffix(file.Name(), ".yaml") && !strings.HasSuffix(file.Name(), ".yml") {
 			continue
 		}
 

+ 8 - 8
pkg/services/provisioning/dashboards/test-configs/dashboards-from-disk/sample.yaml

@@ -1,10 +1,10 @@
 apiVersion: 1
 
-providers:
-- name: 'gasdf'
-  orgId: 2
-  folder: 'developers'
-  editable: true
-  type: file
-  options:
-    path: /var/lib/grafana/dashboards
+#providers:
+#- name: 'gasdf'
+#  orgId: 2
+#  folder: 'developers'
+#  editable: true
+#  type: file
+#  options:
+#    path: /var/lib/grafana/dashboards

+ 1 - 1
pkg/services/provisioning/datasources/config_reader.go

@@ -24,7 +24,7 @@ func (cr *configReader) readConfig(path string) ([]*DatasourcesAsConfig, error)
 	}
 
 	for _, file := range files {
-		if (strings.HasSuffix(file.Name(), ".yaml") || strings.HasSuffix(file.Name(), ".yml")) && file.Name() != "sample.yaml" {
+		if strings.HasSuffix(file.Name(), ".yaml") || strings.HasSuffix(file.Name(), ".yml") {
 			datasource, err := cr.parseDatasourceConfig(path, file)
 			if err != nil {
 				return nil, err

+ 12 - 1
pkg/services/provisioning/datasources/config_reader_test.go

@@ -138,7 +138,7 @@ func TestDatasourceAsConfig(t *testing.T) {
 				t.Fatalf("readConfig return an error %v", err)
 			}
 
-			So(len(cfg), ShouldEqual, 2)
+			So(len(cfg), ShouldEqual, 3)
 
 			dsCfg := cfg[0]
 
@@ -146,6 +146,17 @@ func TestDatasourceAsConfig(t *testing.T) {
 
 			validateDatasource(dsCfg)
 			validateDeleteDatasources(dsCfg)
+
+			dsCount := 0
+			delDsCount := 0
+
+			for _, c := range cfg {
+				dsCount += len(c.Datasources)
+				delDsCount += len(c.DeleteDatasources)
+			}
+
+			So(dsCount, ShouldEqual, 2)
+			So(delDsCount, ShouldEqual, 1)
 		})
 
 		Convey("can read all properties from version 0", func() {

+ 27 - 27
pkg/services/provisioning/datasources/test-configs/all-properties/sample.yaml

@@ -3,30 +3,30 @@
 
 apiVersion: 1
 
-datasources:
-  - name: name
-    type: type
-    access: proxy
-    orgId: 2
-    url: url
-    password: password
-    user: user
-    database: database
-    basicAuth: true
-    basicAuthUser: basic_auth_user
-    basicAuthPassword: basic_auth_password
-    withCredentials: true
-    jsonData:
-      graphiteVersion: "1.1"
-      tlsAuth: true
-      tlsAuthWithCACert: true
-    secureJsonData:
-      tlsCACert: "MjNOcW9RdkbUDHZmpco2HCYzVq9dE+i6Yi+gmUJotq5CDA=="
-      tlsClientCert: "ckN0dGlyMXN503YNfjTcf9CV+GGQneN+xmAclQ=="
-      tlsClientKey: "ZkN4aG1aNkja/gKAB1wlnKFIsy2SRDq4slrM0A=="
-    editable: true
-    version: 10
-
-deleteDatasources:
-  - name: old-graphite3
-    orgId: 2
+#datasources:
+#  - name: name
+#    type: type
+#    access: proxy
+#    orgId: 2
+#    url: url
+#    password: password
+#    user: user
+#    database: database
+#    basicAuth: true
+#    basicAuthUser: basic_auth_user
+#    basicAuthPassword: basic_auth_password
+#    withCredentials: true
+#    jsonData:
+#      graphiteVersion: "1.1"
+#      tlsAuth: true
+#      tlsAuthWithCACert: true
+#    secureJsonData:
+#      tlsCACert: "MjNOcW9RdkbUDHZmpco2HCYzVq9dE+i6Yi+gmUJotq5CDA=="
+#      tlsClientCert: "ckN0dGlyMXN503YNfjTcf9CV+GGQneN+xmAclQ=="
+#      tlsClientKey: "ZkN4aG1aNkja/gKAB1wlnKFIsy2SRDq4slrM0A=="
+#    editable: true
+#    version: 10
+#
+#deleteDatasources:
+#  - name: old-graphite3
+#    orgId: 2

+ 1 - 1
pkg/social/github_oauth.go

@@ -210,7 +210,7 @@ func (s *SocialGithub) UserInfo(client *http.Client, token *oauth2.Token) (*Basi
 	if err != nil {
 		return nil, fmt.Errorf("Error getting user info: %s", err)
 	}
-	data.OrganizationsUrl = s.apiUrl + "/user/orgs"
+
 	userInfo := &BasicUserInfo{
 		Name:  data.Login,
 		Login: data.Login,

+ 1 - 1
pkg/tsdb/influxdb/query.go

@@ -70,7 +70,7 @@ func (query *Query) renderTags() []string {
 		} else if tag.Operator == "<" || tag.Operator == ">" {
 			textValue = tag.Value
 		} else {
-			textValue = fmt.Sprintf("'%s'", tag.Value)
+			textValue = fmt.Sprintf("'%s'", strings.Replace(tag.Value, `\`, `\\`, -1))
 		}
 
 		res = append(res, fmt.Sprintf(`%s"%s" %s %s`, str, tag.Key, tag.Operator, textValue))

+ 6 - 0
pkg/tsdb/influxdb/query_test.go

@@ -170,6 +170,12 @@ func TestInfluxdbQueryBuilder(t *testing.T) {
 			So(strings.Join(query.renderTags(), ""), ShouldEqual, `"key" = 'value'`)
 		})
 
+		Convey("can escape backslashes when rendering string tags", func() {
+			query := &Query{Tags: []*Tag{{Operator: "=", Value: `C:\test\`, Key: "key"}}}
+
+			So(strings.Join(query.renderTags(), ""), ShouldEqual, `"key" = 'C:\\test\\'`)
+		})
+
 		Convey("can render regular measurement", func() {
 			query := &Query{Measurement: `apa`, Policy: "policy"}
 

+ 1 - 2
public/app/core/components/org_switcher.ts

@@ -15,8 +15,7 @@ const template = `
 		</a>
 	</div>
 
-	<div class="modal-content">
-    <div class="gf-form-group">
+  <div class="modal-content modal-content--has-scroll" grafana-scrollbar>
     <table class="filter-table form-inline">
 			<thead>
 				<tr>

+ 1 - 1
public/app/core/components/search/search_results.html

@@ -20,7 +20,7 @@
   <div class="search-section__header" ng-show="section.hideHeader"></div>
 
   <div ng-if="section.expanded">
-    <a ng-repeat="item in section.items" class="search-item" ng-class="{'selected': item.selected}" ng-href="{{::item.url}}">
+    <a ng-repeat="item in section.items" class="search-item" ng-class="{'selected': item.selected}" ng-href="{{::item.url}}" >
       <div ng-click="ctrl.toggleSelection(item, $event)">
         <gf-form-switch
            ng-show="ctrl.editable"

+ 1 - 1
public/app/core/controllers/invited_ctrl.ts

@@ -12,7 +12,7 @@ export class InvitedCtrl {
         icon: 'gicon gicon-branding',
         text: 'Invite',
         subTitle: 'Register your Grafana account',
-        breadcrumbs: [{ title: 'Login', url: '/login' }],
+        breadcrumbs: [{ title: 'Login', url: 'login' }],
       },
     };
 

+ 7 - 0
public/app/core/controllers/signup_ctrl.ts

@@ -10,6 +10,13 @@ export class SignUpCtrl {
     $scope.formModel = {};
 
     var params = $location.search();
+
+    // validate email is semi ok
+    if (params.email && !params.email.match(/^\S+@\S+$/)) {
+      console.log('invalid email');
+      return;
+    }
+
     $scope.formModel.orgName = params.email;
     $scope.formModel.email = params.email;
     $scope.formModel.username = params.email;

+ 1 - 1
public/app/features/alerting/alert_tab_ctrl.ts

@@ -75,7 +75,7 @@ export class AlertTabCtrl {
 
   getAlertHistory() {
     this.backendSrv
-      .get(`/api/annotations?dashboardId=${this.panelCtrl.dashboard.id}&panelId=${this.panel.id}&limit=50`)
+      .get(`/api/annotations?dashboardId=${this.panelCtrl.dashboard.id}&panelId=${this.panel.id}&limit=50&type=alert`)
       .then(res => {
         this.alertHistory = _.map(res, ah => {
           ah.time = this.dashboardSrv.getCurrent().formatDate(ah.time, 'MMM D, YYYY HH:mm:ss');

+ 6 - 0
public/app/features/dashboard/dashboard_migration.ts

@@ -63,11 +63,17 @@ export class DashboardMigrator {
         }
 
         if (panel.y_format) {
+          if (!panel.y_formats) {
+            panel.y_formats = [];
+          }
           panel.y_formats[0] = panel.y_format;
           delete panel.y_format;
         }
 
         if (panel.y2_format) {
+          if (!panel.y_formats) {
+            panel.y_formats = [];
+          }
           panel.y_formats[1] = panel.y2_format;
           delete panel.y2_format;
         }

+ 10 - 28
public/app/features/org/partials/select_org.html

@@ -1,44 +1,26 @@
-<div class="container">
+<page-header model="navModel"></page-header>
 
-	<div class="signup-page-background">
-	</div>
-
-	<div class="login-content">
-
-		<div class="login-branding">
-			<img src="img/logo_transparent_200x75.png">
-		</div>
+<div class="page-container page-body">
 
-    <div class="invite-box">
-			<h3>
-				<i class="fa fa-users"></i>&nbsp;
-				Change active organization
-			</h3>
+	<div class="signup">
+		<div class="login-form">
 
 			<div class="modal-tagline">
-				You have been added to another Organization <br>
-				due to an open invitation!
-				<br><br>
+				You have been added to another Organization due to an open invitation!
 
 				Please select which organization you want to <br>
 				use right now (you can change this later at any time).
 			</div>
 
 			<div style="display: inline-block; width: 400px; margin: 30px 0">
-				<table class="filter-table">
-					<tr ng-repeat="org in orgs">
-						<td class="nobg max-width-btns">
-							<a ng-click="setUsingOrg(org)" class="btn btn-inverse">
-								{{org.name}} ({{org.role}})
-							</a>
-						</td>
-					</tr>
-				</table>
+				<div ng-repeat="org in orgs">
+					<a ng-click="setUsingOrg(org)" class="btn btn-success">
+						{{org.name}} ({{org.role}})
+					</a>
+				</div>
 			</div>
 		</div>
-
 	</div>
-
 </div>
 
 

+ 8 - 0
public/app/features/org/select_org_ctrl.ts

@@ -6,6 +6,14 @@ export class SelectOrgCtrl {
   constructor($scope, backendSrv, contextSrv) {
     contextSrv.sidemenu = false;
 
+    $scope.navModel = {
+      main: {
+        icon: 'gicon gicon-branding',
+        subTitle: 'Preferences',
+        text: 'Select active organization',
+      },
+    };
+
     $scope.init = function() {
       $scope.getUserOrgs();
     };

+ 1 - 3
public/app/features/plugins/import_list/import_list.html

@@ -9,9 +9,7 @@
 					<a href="{{dash.importedUrl}}" ng-show="dash.imported">
 						{{dash.title}}
 					</a>
-					<span ng-show="!dash.imported">
-						{{dash.title}}
-					</span>
+					<span ng-show="!dash.imported">{{dash.title}}</span>
 				</td>
 				<td style="text-align: right">
 					<button class="btn btn-secondary btn-small" ng-click="ctrl.import(dash, false)" ng-show="!dash.imported">

+ 5 - 1
public/app/plugins/panel/graph/legend.ts

@@ -151,7 +151,11 @@ module.directive('graphLegend', function(popoverSrv, $timeout) {
 
         if (panel.legend.sort) {
           seriesList = _.sortBy(seriesList, function(series) {
-            return series.stats[panel.legend.sort];
+            let sort = series.stats[panel.legend.sort];
+            if (sort === null) {
+              sort = -Infinity;
+            }
+            return sort;
           });
           if (panel.legend.sortDesc) {
             seriesList = seriesList.reverse();

+ 1 - 1
public/app/plugins/panel/graph/tab_display.html

@@ -43,7 +43,7 @@
 			<div class="gf-form">
 				<label class="gf-form-label width-8">Point Radius</label>
 				<div class="gf-form-select-wrapper max-width-5">
-					<select class="gf-form-input" ng-model="ctrl.panel.pointradius" ng-options="f for f in [1,2,3,4,5,6,7,8,9,10]" ng-change="ctrl.render()" ng-disabled="!ctrl.panel.points"></select>
+					<select class="gf-form-input" ng-model="ctrl.panel.pointradius" ng-options="f for f in [0.5,1,2,3,4,5,6,7,8,9,10]" ng-change="ctrl.render()" ng-disabled="!ctrl.panel.points"></select>
 				</div>
 			</div>
 		</div>

+ 9 - 4
public/app/plugins/panel/heatmap/color_legend.ts

@@ -8,13 +8,18 @@ import { getColorScale, getOpacityScale } from './color_scale';
 
 let module = angular.module('grafana.directives');
 
+const LEGEND_HEIGHT_PX = 6;
+const LEGEND_WIDTH_PX = 100;
+const LEGEND_TICK_SIZE = 0;
+const LEGEND_VALUE_MARGIN = 0;
+
 /**
  * Color legend for heatmap editor.
  */
 module.directive('colorLegend', function() {
   return {
     restrict: 'E',
-    template: '<div class="heatmap-color-legend"><svg width="16.8rem" height="24px"></svg></div>',
+    template: '<div class="heatmap-color-legend"><svg width="16.5rem" height="24px"></svg></div>',
     link: function(scope, elem, attrs) {
       let ctrl = scope.ctrl;
       let panel = scope.ctrl.panel;
@@ -50,7 +55,7 @@ module.directive('colorLegend', function() {
 module.directive('heatmapLegend', function() {
   return {
     restrict: 'E',
-    template: '<div class="heatmap-color-legend"><svg width="100px" height="14px"></svg></div>',
+    template: `<div class="heatmap-color-legend"><svg width="${LEGEND_WIDTH_PX}px" height="${LEGEND_HEIGHT_PX}px"></svg></div>`,
     link: function(scope, elem, attrs) {
       let ctrl = scope.ctrl;
       let panel = scope.ctrl.panel;
@@ -163,10 +168,10 @@ function drawLegendValues(elem, colorScale, rangeFrom, rangeTo, maxValue, minVal
   let xAxis = d3
     .axisBottom(legendValueScale)
     .tickValues(ticks)
-    .tickSize(2);
+    .tickSize(LEGEND_TICK_SIZE);
 
   let colorRect = legendElem.find(':first-child');
-  let posY = getSvgElemHeight(legendElem) + 2;
+  let posY = getSvgElemHeight(legendElem) + LEGEND_VALUE_MARGIN;
   let posX = getSvgElemX(colorRect);
 
   d3

+ 1 - 2
public/app/plugins/panel/heatmap/rendering.ts

@@ -66,8 +66,7 @@ export default function link(scope, elem, attrs, ctrl) {
         height = parseInt(height.replace('px', ''), 10);
       }
 
-      height -= 5; // padding
-      height -= panel.title ? 24 : 9; // subtract panel title bar
+      height -= panel.legend.show ? 28 : 11; // bottom padding and space for legend
 
       $heatmap.css('height', height + 'px');
 

+ 3 - 0
public/app/plugins/panel/heatmap/specs/renderer_specs.ts

@@ -51,6 +51,9 @@ describe('grafanaHeatmap', function() {
                   colorScheme: 'interpolateOranges',
                   fillBackground: false,
                 },
+                legend: {
+                  show: false,
+                },
                 xBucketSize: 1000,
                 xBucketNumber: null,
                 yBucketSize: 1,

+ 5 - 0
public/sass/components/_modals.scss

@@ -67,6 +67,11 @@
 
 .modal-content {
   padding: $spacer*2;
+
+  &--has-scroll {
+    max-height: calc(100vh - 400px);
+    position: relative;
+  }
 }
 
 // Remove bottom margin if need be

+ 6 - 4
public/sass/components/_panel_heatmap.scss

@@ -1,3 +1,5 @@
+$font-size-heatmap-tick: 11px;
+
 .heatmap-canvas-wrapper {
   // position: relative;
   cursor: crosshair;
@@ -10,7 +12,7 @@
     text {
       fill: $text-color;
       color: $text-color;
-      font-size: $font-size-sm;
+      font-size: $font-size-heatmap-tick;
     }
 
     line {
@@ -56,12 +58,12 @@
 .heatmap-legend-wrapper {
   @include clearfix();
   margin: 0 $spacer;
-  padding-top: 10px;
+  padding-top: 4px;
 
   svg {
     width: 100%;
     max-width: 300px;
-    height: 33px;
+    height: 18px;
     float: left;
     white-space: nowrap;
     padding-left: 10px;
@@ -75,7 +77,7 @@
     text {
       fill: $text-color;
       color: $text-color;
-      font-size: $font-size-sm;
+      font-size: $font-size-heatmap-tick;
     }
 
     line {

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

@@ -75,7 +75,7 @@
   border-radius: 6px;
   width: 6px;
   /* there must be 'right' for ps__thumb-y */
-  right: 2px;
+  right: 0px;
   /* please don't change 'position' */
   position: absolute;
 }