浏览代码

ux(dashboard): edit mode fixes

Torkel Ödegaard 9 年之前
父节点
当前提交
786afda4c3

+ 2 - 1
public/app/features/dashboard/all.js

@@ -22,5 +22,6 @@ define([
   './export/export_modal',
   './dash_list_ctrl',
   './ad_hoc_filters',
-  './row/row',
+  './row/row_ctrl',
+  './repeat_option/repeat_option',
 ], function () {});

+ 5 - 2
public/app/features/dashboard/dynamic_dashboard_srv.ts

@@ -5,6 +5,7 @@ import angular from 'angular';
 import _ from 'lodash';
 
 import coreModule from 'app/core/core_module';
+import {DashboardRow} from './row/row_model';
 
 export class DynamicDashboardSrv {
   iteration: number;
@@ -45,7 +46,7 @@ export class DynamicDashboardSrv {
         }
       } else if (row.repeatRowId && row.repeatIteration !== this.iteration) {
         // clean up old left overs
-        this.dashboard.rows.splice(i, 1);
+        this.dashboard.removeRow(row, true);
         i = i - 1;
         continue;
       }
@@ -80,12 +81,14 @@ export class DynamicDashboardSrv {
       row = this.dashboard.rows[i];
       if (row.repeatRowId === sourceRowId && row.repeatIteration !== this.iteration) {
         copy = row;
+        copy.copyPropertiesFromRowSource(sourceRow);
         break;
       }
     }
 
     if (!copy) {
-      copy = angular.copy(sourceRow);
+      var modelCopy = angular.copy(sourceRow.getSaveModel());
+      copy = new DashboardRow(modelCopy);
       this.dashboard.rows.splice(sourceRowIndex + repeatIndex, 0, copy);
 
       // set new panel ids

+ 23 - 2
public/app/features/dashboard/model.ts

@@ -6,7 +6,7 @@ import moment from 'moment';
 import _ from 'lodash';
 import $ from 'jquery';
 
-import {Emitter, contextSrv} from 'app/core/core';
+import {Emitter, contextSrv, appEvents} from 'app/core/core';
 import {DashboardRow} from './row/row_model';
 
 export class DashboardModel {
@@ -169,6 +169,27 @@ export class DashboardModel {
     row.addPanel(panel);
   }
 
+  removeRow(row, force?) {
+    var index = _.indexOf(this.rows, row);
+
+    if (!row.panels.length || force) {
+      this.rows.splice(index, 1);
+      row.destroy();
+      return;
+    }
+
+    appEvents.emit('confirm-modal', {
+      title: 'Delete',
+      text: 'Are you sure you want to delete this row?',
+      icon: 'fa-trash',
+      yesText: 'Delete',
+      onConfirm: () => {
+        this.rows.splice(index, 1);
+        row.destroy();
+      }
+    });
+  }
+
   toggleEditMode() {
     this.editMode = !this.editMode;
     this.updateSubmenuVisibility();
@@ -234,7 +255,7 @@ export class DashboardModel {
   destroy() {
     this.events.removeAllListeners();
     for (let row of this.rows) {
-      row.events.removeAllListeners();
+      row.destroy();
     }
   }
 

+ 34 - 0
public/app/features/dashboard/repeat_option/repeat_option.ts

@@ -0,0 +1,34 @@
+///<reference path="../../../headers/common.d.ts" />
+
+import {coreModule} from 'app/core/core';
+
+var template = `
+<div class="gf-form-select-wrapper max-width-13">
+<select class="gf-form-input" ng-model="model.repeat" ng-options="f.value as f.text for f in variables">
+<option value=""></option>
+</div>
+`;
+
+coreModule.directive('dashRepeatOption', function(variableSrv) {
+  return {
+    restrict: 'E',
+    template: template,
+    scope: {
+      model: "=",
+    },
+    link: function(scope, element) {
+      element.css({display: 'block', width: '100%'});
+
+      scope.variables = variableSrv.variables.map(item => {
+        return {text: item.name, value: item.name};
+      });
+
+      if (scope.variables.length === 0) {
+        scope.variables.unshift({text: 'No template variables found', value: null});
+      }
+
+      scope.variables.unshift({text: 'Disabled', value: null});
+    }
+  };
+});
+

+ 13 - 7
public/app/features/dashboard/row/options.html

@@ -1,6 +1,5 @@
 <div class="dash-row-options">
 	<div class="gf-form section">
-		<!-- <h5 class="section&#45;heading">Options</h5> -->
 		<div class="gf-form-inline">
 			<div class="gf-form">
 				<span class="gf-form-label width-6">Row Title</span>
@@ -24,15 +23,22 @@
 	</div>
 
 	<div class="gf-form section">
-		<!-- <h5 class="section&#45;heading">Row Templating</h5> -->
-
 		<div class="gf-form">
 			<span class="gf-form-label">Repeat Row</span>
-			<div class="gf-form-select-wrapper max-width-10">
-				<select class="gf-form-input" ng-model="row.repeat" ng-options="f.name as f.name for f in dashboard.templating.list">
-					<option value=""></option>
-			</div>
+      <dash-repeat-option model="ctrl.row"></dash-repeat-option>
 		</div>
 	</div>
+
+  <div class="clearfix"></div>
+
+  <div class="pull-right">
+    <button class="btn btn-danger btn-small" ng-click="ctrl.removeRow()">
+      <i class="fa fa-trash"></i>
+      Delete row
+    </button>
+  </div>
+
+  <div class="clearfix"></div>
+
 </div>
 

+ 3 - 17
public/app/features/dashboard/row/options.ts

@@ -3,7 +3,7 @@
 import _ from 'lodash';
 
 import config from 'app/core/config';
-import {coreModule, appEvents} from 'app/core/core';
+import {coreModule} from 'app/core/core';
 // import VirtualScroll from 'virtual-scroll';
 // console.log(VirtualScroll);
 
@@ -20,23 +20,9 @@ export class RowOptionsCtrl {
     this.row.titleSize = this.row.titleSize || 'h6';
   }
 
-  deleteRow() {
-    if (!this.row.panels.length) {
-      this.dashboard.rows = _.without(this.dashboard.rows, this.row);
-      return;
-    }
-
-    appEvents.emit('confirm-modal', {
-      title: 'Delete',
-      text: 'Are you sure you want to delete this row?',
-      icon: 'fa-trash',
-      yesText: 'Delete',
-      onConfirm: () => {
-        this.dashboard.rows = _.without(this.dashboard.rows, this.row);
-      }
-    });
+  removeRow() {
+    this.dashboard.removeRow(this.row);
   }
-
 }
 
 export function rowOptionsDirective() {

+ 4 - 4
public/app/features/dashboard/row/row.html

@@ -5,7 +5,7 @@
         <i class="fa fa-chevron-down" ng-show="!ctrl.row.collapse"></i>
         <i class="fa fa-chevron-right" ng-show="ctrl.row.collapse"></i>
       </span>
-      <span ng-class="ctrl.row.titleSize">{{ctrl.row.title}}</span>
+      <span ng-class="ctrl.row.titleSize">{{ctrl.row.title | interpolateTemplateVars:this}}</span>
     </a>
 
     <div class="dash-row-header-spacer">
@@ -22,10 +22,10 @@
         <i class="fa fa-cog" ng-hide="ctrl.dropView===2"></i>
         <i class="fa fa-remove" ng-show="ctrl.dropView===2"></i>
       </a>
-      <a class="pointer dash-row-header-actions--tight" bs-tooltip="'Move row up'">
+      <a class="pointer dash-row-header-actions--tight" bs-tooltip="'Move row up'" ng-click="ctrl.moveRow(-1)">
         <i class="fa fa-arrow-up"></i>
       </a>
-      <a class="pointer dash-row-header-actions--tight" bs-tooltip="'Move row down'">
+      <a class="pointer dash-row-header-actions--tight" bs-tooltip="'Move row down'" ng-click="ctrl.moveRow(1)">
         <i class="fa fa-arrow-down"></i>
       </a>
     </div>
@@ -47,7 +47,7 @@
         <i class="fa fa-chevron-down" ng-show="!ctrl.row.collapse"></i>
         <i class="fa fa-chevron-right" ng-show="ctrl.row.collapse"></i>
       </span>
-      <span ng-class="ctrl.row.titleSize">{{ctrl.row.title}}</span>
+      <span ng-class="ctrl.row.titleSize">{{ctrl.row.title | interpolateTemplateVars:this}}</span>
     </a>
   </div>
 </div>

+ 0 - 267
public/app/features/dashboard/row/row.ts

@@ -1,267 +0,0 @@
-///<reference path="../../../headers/common.d.ts" />
-
-import _ from 'lodash';
-import $ from 'jquery';
-import angular from 'angular';
-
-import config from 'app/core/config';
-import {coreModule} from 'app/core/core';
-
-import './options';
-import './add_panel';
-
-export class DashRowCtrl {
-  dashboard: any;
-  row: any;
-  dropView: number;
-
-  /** @ngInject */
-  constructor(private $scope, private $rootScope, private $timeout, private uiSegmentSrv, private $q) {
-    this.row.title = this.row.title || 'Row title';
-
-    if (this.row.isNew) {
-      this.dropView = 1;
-      delete this.row.isNew;
-    }
-  }
-
-  onDrop(panelId, dropTarget) {
-    var dragObject;
-
-    // if string it's a panel type
-    if (_.isString(panelId)) {
-      // setup new panel
-      dragObject = {
-        row: this.row,
-        panel: {
-          title: config.new_panel_title,
-          type: panelId,
-          id: this.dashboard.getNextPanelId(),
-        },
-        isNew: true,
-      };
-    } else {
-      dragObject = this.dashboard.getPanelInfoById(panelId);
-    }
-
-    if (dropTarget) {
-      dropTarget = this.dashboard.getPanelInfoById(dropTarget.id);
-      // if draging new panel onto existing panel split it
-      if (dragObject.isNew) {
-        dragObject.panel.span = dropTarget.panel.span = dropTarget.panel.span/2;
-        // insert after
-        dropTarget.row.panels.splice(dropTarget.index+1, 0, dragObject.panel);
-      } else if (this.row === dragObject.row) {
-        // just move element
-        this.row.movePanel(dropTarget.index, dragObject.index);
-      } else {
-        // split drop target space
-        dragObject.panel.span = dropTarget.panel.span = dropTarget.panel.span/2;
-        // insert after
-        dropTarget.row.panels.splice(dropTarget.index+1, 0, dragObject.panel);
-        // remove from source row
-        dragObject.row.removePanel(dragObject.panel);
-      }
-      // dropInfo.row.panels[dropInfo.index] = info.panel;
-      // info.row.panels[info.index] = dropTarget;
-      // var dragSpan = info.panel.span;
-      // info.panel.span = dropTarget.span;
-      // dropTarget.span = dragSpan;
-    } else {
-      dragObject.panel.span = 12 - this.row.span;
-      this.row.panels.push(dragObject.panel);
-
-      // if not new remove from source row
-      if (!dragObject.isNew) {
-        dragObject.row.removePanel(dragObject.panel);
-      }
-    }
-
-    this.row.panelSpanChanged();
-    this.$timeout(() => {
-      this.$rootScope.$broadcast('render');
-    });
-  }
-
-  setHeight(height) {
-    this.row.height = height;
-    this.$scope.$broadcast('render');
-  }
-
-  moveRow(direction) {
-    var rowsList = this.dashboard.rows;
-    var rowIndex = _.indexOf(rowsList, this.row);
-    var newIndex = rowIndex;
-    switch (direction) {
-      case 'up': {
-        newIndex = rowIndex - 1;
-        break;
-      }
-      case 'down': {
-        newIndex = rowIndex + 1;
-        break;
-      }
-      case 'top': {
-        newIndex = 0;
-        break;
-      }
-      case 'bottom': {
-        newIndex = rowsList.length - 1;
-        break;
-      }
-      default: {
-        newIndex = rowIndex;
-      }
-    }
-    if (newIndex >= 0 && newIndex <= (rowsList.length - 1)) {
-      _.move(rowsList, rowIndex, newIndex);
-    }
-  }
-
-  toggleCollapse() {
-    this.dropView = 0;
-    this.row.collapse = !this.row.collapse;
-  }
-
-  showAddPanel() {
-    this.row.collapse = false;
-    this.dropView = this.dropView === 1 ? 0 : 1;
-  }
-
-  showRowOptions() {
-    this.dropView = this.dropView === 2 ? 0 : 2;
-  }
-}
-
-export function rowDirective($rootScope) {
-  return {
-    restrict: 'E',
-    templateUrl: 'public/app/features/dashboard/row/row.html',
-    controller: DashRowCtrl,
-    bindToController: true,
-    controllerAs: 'ctrl',
-    scope: {
-      dashboard: "=",
-      row: "=",
-    },
-    link: function(scope, element) {
-      scope.$watchGroup(['ctrl.row.collapse', 'ctrl.row.height'], function() {
-        element.find('.panels-wrapper').css({minHeight: scope.ctrl.row.collapse ? '5px' : scope.ctrl.row.height});
-      });
-
-      $rootScope.onAppEvent('panel-fullscreen-enter', function(evt, info) {
-        var hasPanel = _.find(scope.ctrl.row.panels, {id: info.panelId});
-        if (!hasPanel) {
-          element.hide();
-        }
-      }, scope);
-
-      $rootScope.onAppEvent('panel-fullscreen-exit', function() {
-        element.show();
-      }, scope);
-    }
-  };
-}
-
-coreModule.directive('dashRow', rowDirective);
-
-
-coreModule.directive('panelWidth', function($rootScope) {
-  return function(scope, element) {
-    var fullscreen = false;
-
-    function updateWidth() {
-      if (!fullscreen) {
-        element[0].style.width = ((scope.panel.span / 1.2) * 10) + '%';
-      }
-    }
-
-    $rootScope.onAppEvent('panel-fullscreen-enter', function(evt, info) {
-      fullscreen = true;
-
-      if (scope.panel.id !== info.panelId) {
-        element.hide();
-      } else {
-        element[0].style.width = '100%';
-      }
-    }, scope);
-
-    $rootScope.onAppEvent('panel-fullscreen-exit', function(evt, info) {
-      fullscreen = false;
-
-      if (scope.panel.id !== info.panelId) {
-        element.show();
-      }
-
-      updateWidth();
-    }, scope);
-
-    scope.$watch('panel.span', updateWidth);
-
-    if (fullscreen) {
-      element.hide();
-    }
-  };
-});
-
-
-coreModule.directive('panelDropZone', function($timeout) {
-  return function(scope, element) {
-    var row = scope.ctrl.row;
-    var indrag = false;
-    var textEl = element.find('.panel-drop-zone-text');
-
-    function showPanel(span, text) {
-      element.find('.panel-container').css('height', row.height);
-      element[0].style.width = ((span / 1.2) * 10) + '%';
-      textEl.text(text);
-      element.show();
-    }
-
-    function hidePanel() {
-      element.hide();
-      // element.removeClass('panel-drop-zone--empty');
-    }
-
-    function updateState() {
-      if (scope.ctrl.dashboard.editMode) {
-        if (row.panels.length === 0 && indrag === false) {
-          return showPanel(12, 'Empty Space');
-        }
-
-        var dropZoneSpan = 12 - scope.ctrl.dashboard.rowSpan(scope.ctrl.row);
-        if (dropZoneSpan > 0) {
-          if (indrag)  {
-            return showPanel(dropZoneSpan, 'Drop Here');
-          } else {
-            return showPanel(dropZoneSpan, 'Empty Space');
-          }
-        }
-      }
-
-      if (indrag === true) {
-        var dropZoneSpan = 12 - scope.ctrl.dashboard.rowSpan(scope.ctrl.row);
-        if (dropZoneSpan > 1) {
-          return showPanel(dropZoneSpan, 'Drop Here');
-        }
-      }
-
-      hidePanel();
-    }
-
-    row.events.on('span-changed', updateState, scope);
-
-    scope.$watchGroup(['ctrl.dashboard.editMode'], updateState);
-
-    scope.$on("ANGULAR_DRAG_START", function() {
-      indrag = true;
-      updateState();
-    });
-
-    scope.$on("ANGULAR_DRAG_END", function() {
-      indrag = false;
-      updateState();
-    });
-  };
-});
-

+ 59 - 54
public/app/features/dashboard/row/row_ctrl.ts

@@ -1,11 +1,9 @@
 ///<reference path="../../../headers/common.d.ts" />
 
 import _ from 'lodash';
-import $ from 'jquery';
-import angular from 'angular';
 
 import config from 'app/core/config';
-import {coreModule} from 'app/core/core';
+import {coreModule, appEvents} from 'app/core/core';
 
 import './options';
 import './add_panel';
@@ -19,28 +17,63 @@ export class DashRowCtrl {
   constructor(private $scope, private $rootScope, private $timeout, private uiSegmentSrv, private $q) {
     this.row.title = this.row.title || 'Row title';
 
-    if (this.dashboard.meta.isNew) {
+    if (this.row.isNew) {
       this.dropView = 1;
       delete this.row.isNew;
     }
   }
 
   onDrop(panelId, dropTarget) {
-    var info = this.dashboard.getPanelInfoById(panelId);
+    var dragObject;
+
+    // if string it's a panel type
+    if (_.isString(panelId)) {
+      // setup new panel
+      dragObject = {
+        row: this.row,
+        panel: {
+          title: config.new_panel_title,
+          type: panelId,
+          id: this.dashboard.getNextPanelId(),
+        },
+        isNew: true,
+      };
+    } else {
+      dragObject = this.dashboard.getPanelInfoById(panelId);
+    }
+
     if (dropTarget) {
-      var dropInfo = this.dashboard.getPanelInfoById(dropTarget.id);
-      dropInfo.row.panels[dropInfo.index] = info.panel;
-      info.row.panels[info.index] = dropTarget;
-      var dragSpan = info.panel.span;
-      info.panel.span = dropTarget.span;
-      dropTarget.span = dragSpan;
+      dropTarget = this.dashboard.getPanelInfoById(dropTarget.id);
+      // if draging new panel onto existing panel split it
+      if (dragObject.isNew) {
+        dragObject.panel.span = dropTarget.panel.span = dropTarget.panel.span/2;
+        // insert after
+        dropTarget.row.panels.splice(dropTarget.index+1, 0, dragObject.panel);
+      } else if (this.row === dragObject.row) {
+        // just move element
+        this.row.movePanel(dropTarget.index, dragObject.index);
+      } else {
+        // split drop target space
+        dragObject.panel.span = dropTarget.panel.span = dropTarget.panel.span/2;
+        // insert after
+        dropTarget.row.panels.splice(dropTarget.index+1, 0, dragObject.panel);
+        // remove from source row
+        dragObject.row.removePanel(dragObject.panel);
+      }
     } else {
-      info.row.panels.splice(info.index, 1);
-      info.panel.span = 12 - this.dashboard.rowSpan(this.row);
-      this.row.panels.push(info.panel);
+      dragObject.panel.span = 12 - this.row.span;
+      this.row.panels.push(dragObject.panel);
+
+      // if not new remove from source row
+      if (!dragObject.isNew) {
+        dragObject.row.removePanel(dragObject.panel);
+      }
     }
 
-    this.$rootScope.$broadcast('render');
+    this.row.panelSpanChanged();
+    this.$timeout(() => {
+      this.$rootScope.$broadcast('render');
+    });
   }
 
   setHeight(height) {
@@ -51,28 +84,8 @@ export class DashRowCtrl {
   moveRow(direction) {
     var rowsList = this.dashboard.rows;
     var rowIndex = _.indexOf(rowsList, this.row);
-    var newIndex = rowIndex;
-    switch (direction) {
-      case 'up': {
-        newIndex = rowIndex - 1;
-        break;
-      }
-      case 'down': {
-        newIndex = rowIndex + 1;
-        break;
-      }
-      case 'top': {
-        newIndex = 0;
-        break;
-      }
-      case 'bottom': {
-        newIndex = rowsList.length - 1;
-        break;
-      }
-      default: {
-        newIndex = rowIndex;
-      }
-    }
+    var newIndex = rowIndex + direction;
+
     if (newIndex >= 0 && newIndex <= (rowsList.length - 1)) {
       _.move(rowsList, rowIndex, newIndex);
     }
@@ -93,7 +106,7 @@ export class DashRowCtrl {
   }
 }
 
-export function rowDirective($rootScope) {
+coreModule.directive('dashRow', function($rootScope) {
   return {
     restrict: 'E',
     templateUrl: 'public/app/features/dashboard/row/row.html',
@@ -121,10 +134,7 @@ export function rowDirective($rootScope) {
       }, scope);
     }
   };
-}
-
-coreModule.directive('dashRow', rowDirective);
-
+});
 
 coreModule.directive('panelWidth', function($rootScope) {
   return function(scope, element) {
@@ -180,7 +190,6 @@ coreModule.directive('panelDropZone', function($timeout) {
 
     function hidePanel() {
       element.hide();
-      // element.removeClass('panel-drop-zone--empty');
     }
 
     function updateState() {
@@ -190,7 +199,7 @@ coreModule.directive('panelDropZone', function($timeout) {
         }
 
         var dropZoneSpan = 12 - scope.ctrl.dashboard.rowSpan(scope.ctrl.row);
-        if (dropZoneSpan > 1) {
+        if (dropZoneSpan > 0) {
           if (indrag)  {
             return showPanel(dropZoneSpan, 'Drop Here');
           } else {
@@ -200,26 +209,22 @@ coreModule.directive('panelDropZone', function($timeout) {
       }
 
       if (indrag === true) {
-        return showPanel(dropZoneSpan, 'Drop Here');
+        var dropZoneSpan = 12 - scope.ctrl.dashboard.rowSpan(scope.ctrl.row);
+        if (dropZoneSpan > 1) {
+          return showPanel(dropZoneSpan, 'Drop Here');
+        }
       }
 
       hidePanel();
     }
 
-    scope.row.events.on('panel-added', updateState);
-    scope.row.events.on('span-changed', updateState);
+    row.events.on('span-changed', updateState, scope);
 
-    scope.$watchGroup(['ctrl.row.panels.length', 'ctrl.dashboard.editMode', 'ctrl.row.span'], updateState);
+    scope.$watchGroup(['ctrl.dashboard.editMode'], updateState);
 
     scope.$on("ANGULAR_DRAG_START", function() {
       indrag = true;
       updateState();
-      // $timeout(function() {
-      //   var dropZoneSpan = 12 - scope.ctrl.dashboard.rowSpan(scope.ctrl.row);
-      //   if (dropZoneSpan > 0) {
-      //     showPanel(dropZoneSpan, 'Panel Drop Zone');
-      //   }
-      // });
     });
 
     scope.$on("ANGULAR_DRAG_END", function() {

+ 15 - 0
public/app/features/dashboard/row/row_model.ts

@@ -11,6 +11,7 @@ export class DashboardRow {
   titleSize: any;
   events: Emitter;
   span: number;
+  height: number;
 
   defaults = {
     title: 'Dashboard Row',
@@ -19,6 +20,9 @@ export class DashboardRow {
     titleSize: 'h6',
     height: 250,
     isNew: false,
+    repeat: null,
+    repeatRowId: null,
+    repeatIteration: null,
   };
 
   constructor(private model) {
@@ -86,5 +90,16 @@ export class DashboardRow {
   movePanel(fromIndex, toIndex) {
     this.panels.splice(toIndex, 0, this.panels.splice(fromIndex, 1)[0]);
   }
+
+  destroy() {
+    this.events.removeAllListeners();
+  }
+
+  copyPropertiesFromRowSource(source) {
+    this.height = source.height;
+    this.title = source.title;
+    this.showTitle = source.showTitle;
+    this.titleSize = source.titleSize;
+  }
 }
 

+ 0 - 51
public/app/features/dashboard/rowCtrl.js

@@ -1,51 +0,0 @@
-// define([
-//   'angular',
-//   'lodash',
-//   'app/core/config'
-// ],
-// function (angular, _, config) {
-//   'use strict';
-//
-//   var module = angular.module('grafana.controllers');
-//
-//   module.controller('RowCtrl', function($scope, $rootScope, $timeout) {
-//
-//     $scope.moveRow = function(direction) {
-//       var rowsList = $scope.dashboard.rows;
-//       var rowIndex = _.indexOf(rowsList, $scope.row);
-//       var newIndex = rowIndex;
-//       switch(direction) {
-//         case 'up': {
-//           newIndex = rowIndex - 1;
-//           break;
-//         }
-//         case 'down': {
-//           newIndex = rowIndex + 1;
-//           break;
-//         }
-//         case 'top': {
-//           newIndex = 0;
-//           break;
-//         }
-//         case 'bottom': {
-//           newIndex = rowsList.length - 1;
-//           break;
-//         }
-//         default: {
-//           newIndex = rowIndex;
-//         }
-//       }
-//       if (newIndex >= 0 && newIndex <= (rowsList.length - 1)) {
-//         _.move(rowsList, rowIndex, newIndex);
-//       }
-//     };
-//
-//     $scope.setHeight = function(height) {
-//       $scope.row.height = height;
-//       $scope.$broadcast('render');
-//     };
-//
-//     $scope.init();
-//   });
-//
-// });

+ 1 - 1
public/app/features/panel/panel_directive.ts

@@ -139,7 +139,7 @@ module.directive('panelResizer', function($rootScope) {
       }
 
       function moveHandler(e) {
-        ctrl.row.height = originalHeight + (e.pageY - handleOffset.top);
+        ctrl.row.height = Math.round(originalHeight + (e.pageY - handleOffset.top));
         ctrl.panel.span = originalWidth + (((e.pageX - handleOffset.left) / maxWidth) * 12);
         ctrl.panel.span = Math.min(Math.max(ctrl.panel.span, 1), 12);
 

+ 1 - 3
public/app/partials/panelgeneral.html

@@ -18,9 +18,7 @@
 	<div class="gf-form-inline">
 		<div class="gf-form max-width-21">
 			<span class="gf-form-label width-8">Repeat Panel</span>
-			<select class="gf-form-input" ng-model="ctrl.panel.repeat" ng-options="f.name as f.name for f in ctrl.dashboard.templating.list">
-				<option value=""></option>
-			</select>
+      <dash-repeat-option model="ctrl.panel"></dash-repeat-option>
 		</div>
 		<div class="gf-form">
 			<span class="gf-form-label width-6">Min span</span>

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

@@ -104,7 +104,7 @@ a.dash-row-header-actions--tight {
     position: relative;
   }
   .dash-row {
-    margin-bottom: $spacer*2;
+    margin-bottom: $spacer;
   }
   .dash-row-header-title {
     border-left: 1px solid $dark-4;