Преглед на файлове

feat(panels): upgradede singlestat panel

Torkel Ödegaard преди 10 години
родител
ревизия
97ac81aa9c

+ 1 - 0
pkg/api/frontendsettings.go

@@ -123,6 +123,7 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro
 		panels[panel.Id] = map[string]interface{}{
 		panels[panel.Id] = map[string]interface{}{
 			"module": panel.Module,
 			"module": panel.Module,
 			"name":   panel.Name,
 			"name":   panel.Name,
+			"id":     panel.Id,
 			"info":   panel.Info,
 			"info":   panel.Info,
 		}
 		}
 	}
 	}

+ 1 - 0
public/app/features/panel/panel_editor_tab.ts

@@ -16,6 +16,7 @@ function panelEditorTab(dynamicDirectiveSrv) {
     directive: scope => {
     directive: scope => {
       var pluginId = scope.ctrl.pluginId;
       var pluginId = scope.ctrl.pluginId;
       var tabIndex = scope.index;
       var tabIndex = scope.index;
+      console.log('tab plugnId:', pluginId);
 
 
       return Promise.resolve({
       return Promise.resolve({
         name: `panel-editor-tab-${pluginId}${tabIndex}`,
         name: `panel-editor-tab-${pluginId}${tabIndex}`,

+ 196 - 203
public/app/plugins/panel/singlestat/controller.ts

@@ -3,240 +3,233 @@
 import angular from 'angular';
 import angular from 'angular';
 import _ from 'lodash';
 import _ from 'lodash';
 import kbn from 'app/core/utils/kbn';
 import kbn from 'app/core/utils/kbn';
-import PanelMeta from 'app/features/panel/panel_meta2';
 import TimeSeries from '../../../core/time_series2';
 import TimeSeries from '../../../core/time_series2';
+import {MetricsPanelCtrl} from '../../../features/panel/panel';
+
+// Set and populate defaults
+var panelDefaults = {
+  links: [],
+  datasource: null,
+  maxDataPoints: 100,
+  interval: null,
+  targets: [{}],
+  cacheTimeout: null,
+  format: 'none',
+  prefix: '',
+  postfix: '',
+  nullText: null,
+  valueMaps: [
+    { value: 'null', op: '=', text: 'N/A' }
+  ],
+  nullPointMode: 'connected',
+  valueName: 'avg',
+  prefixFontSize: '50%',
+  valueFontSize: '80%',
+  postfixFontSize: '50%',
+  thresholds: '',
+  colorBackground: false,
+  colorValue: false,
+  colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"],
+  sparkline: {
+    show: false,
+    full: false,
+    lineColor: 'rgb(31, 120, 193)',
+    fillColor: 'rgba(31, 118, 189, 0.18)',
+  }
+};
 
 
-export class SingleStatCtrl {
+export class SingleStatCtrl extends MetricsPanelCtrl {
+  series: any[];
+  data: any[];
+  fontSizes: any[];
+  unitFormats: any[];
 
 
   /** @ngInject */
   /** @ngInject */
-  constructor($scope, panelSrv, panelHelper) {
-    $scope.panelMeta = new PanelMeta({
-      panelName: 'Singlestat',
-      editIcon:  "fa fa-dashboard",
-      fullscreen: true,
-      metricsEditor: true
-    });
+  constructor($scope, $injector) {
+    super($scope, $injector);
+    _.defaults(this.panel, panelDefaults);
+  }
 
 
-    $scope.fontSizes = ['20%', '30%','50%','70%','80%','100%', '110%', '120%', '150%', '170%', '200%'];
-
-    $scope.panelMeta.addEditorTab('Options', 'app/plugins/panel/singlestat/editor.html');
-    $scope.panelMeta.addEditorTab('Time range', 'app/features/panel/partials/panelTime.html');
-
-    // Set and populate defaults
-    var _d = {
-      links: [],
-      datasource: null,
-      maxDataPoints: 100,
-      interval: null,
-      targets: [{}],
-      cacheTimeout: null,
-      format: 'none',
-      prefix: '',
-      postfix: '',
-      nullText: null,
-      valueMaps: [
-        { value: 'null', op: '=', text: 'N/A' }
-      ],
-      nullPointMode: 'connected',
-      valueName: 'avg',
-      prefixFontSize: '50%',
-      valueFontSize: '80%',
-      postfixFontSize: '50%',
-      thresholds: '',
-      colorBackground: false,
-      colorValue: false,
-      colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"],
-      sparkline: {
-        show: false,
-        full: false,
-        lineColor: 'rgb(31, 120, 193)',
-        fillColor: 'rgba(31, 118, 189, 0.18)',
-      }
-    };
-
-    _.defaults($scope.panel, _d);
-
-    $scope.unitFormats = kbn.getUnitFormats();
-
-    $scope.setUnitFormat = function(subItem) {
-      $scope.panel.format = subItem.value;
-      $scope.render();
-    };
-
-    $scope.init = function() {
-      panelSrv.init($scope);
-    };
-
-    $scope.refreshData = function(datasource) {
-      panelHelper.updateTimeRange($scope);
-
-      return panelHelper.issueMetricQuery($scope, datasource)
-        .then($scope.dataHandler, function(err) {
-          $scope.series = [];
-          $scope.render();
-          throw err;
-        });
-    };
-
-    $scope.loadSnapshot = function(snapshotData) {
-      panelHelper.updateTimeRange($scope);
-      $scope.dataHandler(snapshotData);
-    };
-
-    $scope.dataHandler = function(results) {
-      $scope.series = _.map(results.data, $scope.seriesHandler);
-      $scope.render();
-    };
-
-    $scope.seriesHandler = function(seriesData) {
-      var series = new TimeSeries({
-        datapoints: seriesData.datapoints,
-        alias: seriesData.target,
-      });
 
 
-      series.flotpairs = series.getFlotPairs($scope.panel.nullPointMode);
+  initEditMode() {
+    this.icon =  "fa fa-dashboard";
+    this.fontSizes = ['20%', '30%','50%','70%','80%','100%', '110%', '120%', '150%', '170%', '200%'];
 
 
-      return series;
-    };
+    this.addEditorTab('Options', 'app/plugins/panel/singlestat/editor.html');
+    this.addEditorTab('Time range', 'app/features/panel/partials/panelTime.html');
 
 
-    $scope.setColoring = function(options) {
-      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 {
-        $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)'];
-      }
-      $scope.render();
-    };
-
-    $scope.invertColorOrder = function() {
-      var tmp = $scope.panel.colors[0];
-      $scope.panel.colors[0] = $scope.panel.colors[2];
-      $scope.panel.colors[2] = tmp;
-      $scope.render();
-    };
-
-    $scope.getDecimalsForValue = function(value) {
-      if (_.isNumber($scope.panel.decimals)) {
-        return { decimals: $scope.panel.decimals, scaledDecimals: null };
-      }
+    this.unitFormats = kbn.getUnitFormats();
+  }
 
 
-      var delta = value / 2;
-      var dec = -Math.floor(Math.log(delta) / Math.LN10);
-
-      var magn = Math.pow(10, -dec),
-          norm = delta / magn, // norm is between 1.0 and 10.0
-          size;
-
-      if (norm < 1.5) {
-        size = 1;
-      } else if (norm < 3) {
-        size = 2;
-        // special case for 2.5, requires an extra decimal
-        if (norm > 2.25) {
-          size = 2.5;
-          ++dec;
-        }
-      } else if (norm < 7.5) {
-        size = 5;
-      } else {
-        size = 10;
-      }
+  setUnitFormat(subItem) {
+    this.panel.format = subItem.value;
+    this.render();
+  }
+
+  refreshData(datasource) {
+    return this.issueQueries(datasource)
+      .then(this.dataHandler.bind(this))
+      .catch(err => {
+        this.series = [];
+        this.render();
+        throw err;
+      });
+  }
+
+  loadSnapshot(snapshotData) {
+    this.updateTimeRange();
+    this.dataHandler(snapshotData);
+  }
+
+  dataHandler(results) {
+    this.series = _.map(results.data, this.seriesHandler.bind(this));
+    this.render();
+  }
 
 
-      size *= magn;
+  seriesHandler(seriesData) {
+    var series = new TimeSeries({
+      datapoints: seriesData.datapoints,
+      alias: seriesData.target,
+    });
 
 
-      // reduce starting decimals if not needed
-      if (Math.floor(value) === value) { dec = 0; }
+    series.flotpairs = series.getFlotPairs(this.panel.nullPointMode);
+    return series;
+  }
 
 
-      var result: any = {};
-      result.decimals = Math.max(0, dec);
-      result.scaledDecimals = result.decimals - Math.floor(Math.log(size) / Math.LN10) + 2;
+  setColoring(options) {
+    if (options.background) {
+      this.panel.colorValue = false;
+      this.panel.colors = ['rgba(71, 212, 59, 0.4)', 'rgba(245, 150, 40, 0.73)', 'rgba(225, 40, 40, 0.59)'];
+    } else {
+      this.panel.colorBackground = false;
+      this.panel.colors = ['rgba(50, 172, 45, 0.97)', 'rgba(237, 129, 40, 0.89)', 'rgba(245, 54, 54, 0.9)'];
+    }
+    this.render();
+  }
 
 
-      return result;
-    };
+  invertColorOrder() {
+    var tmp = this.panel.colors[0];
+    this.panel.colors[0] = this.panel.colors[2];
+    this.panel.colors[2] = tmp;
+    this.render();
+  }
 
 
-    $scope.render = function() {
-      var data: any = {};
+  getDecimalsForValue(value) {
+    if (_.isNumber(this.panel.decimals)) {
+      return {decimals: this.panel.decimals, scaledDecimals: null};
+    }
+
+    var delta = value / 2;
+    var dec = -Math.floor(Math.log(delta) / Math.LN10);
+
+    var magn = Math.pow(10, -dec),
+      norm = delta / magn, // norm is between 1.0 and 10.0
+      size;
+
+    if (norm < 1.5) {
+      size = 1;
+    } else if (norm < 3) {
+      size = 2;
+      // special case for 2.5, requires an extra decimal
+      if (norm > 2.25) {
+        size = 2.5;
+        ++dec;
+      }
+    } else if (norm < 7.5) {
+      size = 5;
+    } else {
+      size = 10;
+    }
 
 
-      $scope.setValues(data);
+    size *= magn;
 
 
-      data.thresholds = $scope.panel.thresholds.split(',').map(function(strVale) {
-        return Number(strVale.trim());
-      });
+    // reduce starting decimals if not needed
+    if (Math.floor(value) === value) { dec = 0; }
 
 
-      data.colorMap = $scope.panel.colors;
+    var result: any = {};
+    result.decimals = Math.max(0, dec);
+    result.scaledDecimals = result.decimals - Math.floor(Math.log(size) / Math.LN10) + 2;
 
 
-      $scope.data = data;
-      $scope.$broadcast('render');
-    };
+    return result;
+  }
 
 
-    $scope.setValues = function(data) {
-      data.flotpairs = [];
+  render() {
+    var data: any = {};
+    this.setValues(data);
 
 
-      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 +
-        ' series. Single Stat Panel expects a single series.\n\nResponse:\n'+JSON.stringify($scope.series);
-        throw $scope.inspector.error;
-      }
+    data.thresholds = this.panel.thresholds.split(',').map(function(strVale) {
+      return Number(strVale.trim());
+    });
 
 
-      if ($scope.series && $scope.series.length > 0) {
-        var lastPoint = _.last($scope.series[0].datapoints);
-        var lastValue = _.isArray(lastPoint) ? lastPoint[0] : null;
-
-        if (_.isString(lastValue)) {
-          data.value = 0;
-          data.valueFormated = lastValue;
-          data.valueRounded = 0;
-        } else {
-          data.value = $scope.series[0].stats[$scope.panel.valueName];
-          data.flotpairs = $scope.series[0].flotpairs;
-
-          var decimalInfo = $scope.getDecimalsForValue(data.value);
-          var formatFunc = kbn.valueFormats[$scope.panel.format];
-          data.valueFormated = formatFunc(data.value, decimalInfo.decimals, decimalInfo.scaledDecimals);
-          data.valueRounded = kbn.roundValue(data.value, decimalInfo.decimals);
-        }
-      }
+    data.colorMap = this.panel.colors;
 
 
-      // check value to text mappings
-      for (var i = 0; i < $scope.panel.valueMaps.length; i++) {
-        var map = $scope.panel.valueMaps[i];
-        // special null case
-        if (map.value === 'null') {
-          if (data.value === null || data.value === void 0) {
-            data.valueFormated = map.text;
-            return;
-          }
-          continue;
-        }
+    this.data = data;
+    this.broadcastRender();
+  }
 
 
-        // value/number to text mapping
-        var value = parseFloat(map.value);
-        if (value === data.value) {
+  setValues(data) {
+    data.flotpairs = [];
+
+    if (this.series.length > 1) {
+      this.inspector.error = new Error();
+      this.inspector.error.message = 'Multiple Series Error';
+      this.inspector.error.data = 'Metric query returns ' + this.series.length +
+        ' series. Single Stat Panel expects a single series.\n\nResponse:\n'+JSON.stringify(this.series);
+      throw this.inspector.error;
+    }
+
+    if (this.series && this.series.length > 0) {
+      var lastPoint = _.last(this.series[0].datapoints);
+      var lastValue = _.isArray(lastPoint) ? lastPoint[0] : null;
+
+      if (_.isString(lastValue)) {
+        data.value = 0;
+        data.valueFormated = lastValue;
+        data.valueRounded = 0;
+      } else {
+        data.value = this.series[0].stats[this.panel.valueName];
+        data.flotpairs = this.series[0].flotpairs;
+
+        var decimalInfo = this.getDecimalsForValue(data.value);
+        var formatFunc = kbn.valueFormats[this.panel.format];
+        data.valueFormated = formatFunc(data.value, decimalInfo.decimals, decimalInfo.scaledDecimals);
+        data.valueRounded = kbn.roundValue(data.value, decimalInfo.decimals);
+      }
+    }
+
+    // check value to text mappings
+    for (var i = 0; i < this.panel.valueMaps.length; i++) {
+      var map = this.panel.valueMaps[i];
+      // special null case
+      if (map.value === 'null') {
+        if (data.value === null || data.value === void 0) {
           data.valueFormated = map.text;
           data.valueFormated = map.text;
           return;
           return;
         }
         }
+        continue;
       }
       }
 
 
-      if (data.value === null || data.value === void 0) {
-        data.valueFormated = "no value";
+      // value/number to text mapping
+      var value = parseFloat(map.value);
+      if (value === data.value) {
+        data.valueFormated = map.text;
+        return;
       }
       }
-    };
+    }
 
 
-    $scope.removeValueMap = function(map) {
-      var index = _.indexOf($scope.panel.valueMaps, map);
-      $scope.panel.valueMaps.splice(index, 1);
-      $scope.render();
-    };
+    if (data.value === null || data.value === void 0) {
+      data.valueFormated = "no value";
+    }
+  };
 
 
-    $scope.addValueMap = function() {
-      $scope.panel.valueMaps.push({value: '', op: '=', text: '' });
-    };
-
-    $scope.init();
+  removeValueMap(map) {
+    var index = _.indexOf(this.panel.valueMaps, map);
+    this.panel.valueMaps.splice(index, 1);
+    this.render();
+  };
 
 
+  addValueMap() {
+    this.panel.valueMaps.push({value: '', op: '=', text: '' });
   }
   }
 }
 }
+

+ 34 - 34
public/app/plugins/panel/singlestat/editor.html

@@ -10,20 +10,20 @@
 				</li>
 				</li>
 				<li>
 				<li>
 					<input type="text" class="input-small tight-form-input"
 					<input type="text" class="input-small tight-form-input"
-					ng-model="panel.prefix" ng-change="render()" ng-model-onblur>
+					ng-model="ctrl.panel.prefix" ng-change="ctrl.render()" ng-model-onblur>
 				</li>
 				</li>
 				<li class="tight-form-item">
 				<li class="tight-form-item">
 					Value
 					Value
 				</li>
 				</li>
 				<li>
 				<li>
-					<select class="input-small tight-form-input" ng-model="panel.valueName" ng-options="f for f in ['min','max','avg', 'current', 'total']" ng-change="render()"></select>
+					<select class="input-small tight-form-input" ng-model="ctrl.panel.valueName" ng-options="f for f in ['min','max','avg', 'current', 'total']" ng-change="ctrl.render()"></select>
 				</li>
 				</li>
 				<li class="tight-form-item">
 				<li class="tight-form-item">
 					Postfix
 					Postfix
 				</li>
 				</li>
 				<li>
 				<li>
 					<input type="text" class="input-small tight-form-input last"
 					<input type="text" class="input-small tight-form-input last"
-					ng-model="panel.postfix" ng-change="render()" ng-model-onblur>
+					ng-model="ctrl.panel.postfix" ng-change="ctrl.render()" ng-model-onblur>
 				</li>
 				</li>
 			</ul>
 			</ul>
 			<div class="clearfix"></div>
 			<div class="clearfix"></div>
@@ -37,19 +37,19 @@
 					Prefix
 					Prefix
 				</li>
 				</li>
 				<li>
 				<li>
-					<select class="input-small tight-form-input" ng-model="panel.prefixFontSize" ng-options="f for f in fontSizes" ng-change="render()"></select>
+					<select class="input-small tight-form-input" ng-model="ctrl.panel.prefixFontSize" ng-options="f for f in ctrl.fontSizes" ng-change="ctrl.render()"></select>
 				</li>
 				</li>
 				<li class="tight-form-item">
 				<li class="tight-form-item">
 					Value
 					Value
 				</li>
 				</li>
 				<li>
 				<li>
-					<select class="input-small tight-form-input" ng-model="panel.valueFontSize" ng-options="f for f in fontSizes" ng-change="render()"></select>
+					<select class="input-small tight-form-input" ng-model="ctrl.panel.valueFontSize" ng-options="f for f in ctrl.fontSizes" ng-change="ctrl.render()"></select>
 				</li>
 				</li>
 				<li class="tight-form-item">
 				<li class="tight-form-item">
 					Postfix
 					Postfix
 				</li>
 				</li>
 				<li>
 				<li>
-					<select class="input-small tight-form-input last" ng-model="panel.postfixFontSize" ng-options="f for f in fontSizes" ng-change="render()"></select>
+					<select class="input-small tight-form-input last" ng-model="ctrl.panel.postfixFontSize" ng-options="f for f in ctrl.fontSizes" ng-change="ctrl.render()"></select>
 				</li>
 				</li>
 			</ul>
 			</ul>
 			<div class="clearfix"></div>
 			<div class="clearfix"></div>
@@ -60,16 +60,16 @@
 					<strong>Unit</strong>
 					<strong>Unit</strong>
 				</li>
 				</li>
 				<li class="dropdown" style="width: 266px;"
 				<li class="dropdown" style="width: 266px;"
-					ng-model="panel.format"
-					dropdown-typeahead="unitFormats"
-					dropdown-typeahead-on-select="setUnitFormat($subItem)">
+					ng-model="ctrl.panel.format"
+					dropdown-typeahead="ctrl.unitFormats"
+					dropdown-typeahead-on-select="ctrl.setUnitFormat($subItem)">
 				</li>
 				</li>
 				<li class="tight-form-item">
 				<li class="tight-form-item">
 					Decimals
 					Decimals
 				</li>
 				</li>
 				<li>
 				<li>
 					<input type="number" class="input-small tight-form-input last" placeholder="auto" bs-tooltip="'Override automatic decimal precision for legend and tooltips'" data-placement="right"
 					<input type="number" class="input-small tight-form-input last" placeholder="auto" bs-tooltip="'Override automatic decimal precision for legend and tooltips'" data-placement="right"
-					ng-model="panel.decimals" ng-change="render()" ng-model-onblur>
+					ng-model="ctrl.panel.decimals" ng-change="ctrl.render()" ng-model-onblur>
 				</li>
 				</li>
 			</ul>
 			</ul>
 			<div class="clearfix"></div>
 			<div class="clearfix"></div>
@@ -86,32 +86,32 @@
 				</li>
 				</li>
 				<li class="tight-form-item">
 				<li class="tight-form-item">
 					Background&nbsp;
 					Background&nbsp;
-					<input class="cr1" id="panel.colorBackground" type="checkbox"
-					ng-model="panel.colorBackground" ng-checked="panel.colorBackground" ng-change="render()">
-					<label for="panel.colorBackground" class="cr1"></label>
+					<input class="cr1" id="ctrl.panel.colorBackground" type="checkbox"
+					ng-model="ctrl.panel.colorBackground" ng-checked="ctrl.panel.colorBackground" ng-change="ctrl.render()">
+					<label for="ctrl.panel.colorBackground" class="cr1"></label>
 				</li>
 				</li>
 				<li class="tight-form-item">
 				<li class="tight-form-item">
 					Value&nbsp;
 					Value&nbsp;
-					<input class="cr1" id="panel.colorValue" type="checkbox"
-					ng-model="panel.colorValue" ng-checked="panel.colorValue" ng-change="render()">
-					<label for="panel.colorValue" class="cr1"></label>
+					<input class="cr1" id="ctrl.panel.colorValue" type="checkbox"
+					ng-model="ctrl.panel.colorValue" ng-checked="ctrl.panel.colorValue" ng-change="ctrl.render()">
+					<label for="ctrl.panel.colorValue" class="cr1"></label>
 				</li>
 				</li>
 				<li class="tight-form-item">
 				<li class="tight-form-item">
 					Thresholds<tip>Define two threshold values&lt;br /&gt; 50,80 will produce: &lt;50 = Green, 50:80 = Yellow, &gt;80 = Red</tip>
 					Thresholds<tip>Define two threshold values&lt;br /&gt; 50,80 will produce: &lt;50 = Green, 50:80 = Yellow, &gt;80 = Red</tip>
 				</li>
 				</li>
 				<li>
 				<li>
-					<input type="text" class="input-large tight-form-input" ng-model="panel.thresholds" ng-blur="render()" placeholder="50,80"></input>
+					<input type="text" class="input-large tight-form-input" ng-model="ctrl.panel.thresholds" ng-blur="ctrl.render()" placeholder="50,80"></input>
 				</li>
 				</li>
 				<li class="tight-form-item">
 				<li class="tight-form-item">
 					Colors
 					Colors
 				</li>
 				</li>
 				<li class="tight-form-item">
 				<li class="tight-form-item">
-					<spectrum-picker ng-model="panel.colors[0]" ng-change="render()" ></spectrum-picker>
-					<spectrum-picker ng-model="panel.colors[1]" ng-change="render()" ></spectrum-picker>
-					<spectrum-picker ng-model="panel.colors[2]" ng-change="render()" ></spectrum-picker>
+					<spectrum-picker ng-model="ctrl.panel.colors[0]" ng-change="ctrl.render()" ></spectrum-picker>
+					<spectrum-picker ng-model="ctrl.panel.colors[1]" ng-change="ctrl.render()" ></spectrum-picker>
+					<spectrum-picker ng-model="ctrl.panel.colors[2]" ng-change="ctrl.render()" ></spectrum-picker>
 				</li>
 				</li>
 				<li class="tight-form-item last">
 				<li class="tight-form-item last">
-					<a class="pointer" ng-click="invertColorOrder()">invert order</a>
+					<a class="pointer" ng-click="ctrl.invertColorOrder()">invert order</a>
 				</li>
 				</li>
 			</ul>
 			</ul>
 			<div class="clearfix"></div>
 			<div class="clearfix"></div>
@@ -128,27 +128,27 @@
 				</li>
 				</li>
 				<li class="tight-form-item">
 				<li class="tight-form-item">
 					Show&nbsp;
 					Show&nbsp;
-					<input class="cr1" id="panel.sparkline.show" type="checkbox"
-					ng-model="panel.sparkline.show" ng-checked="panel.sparkline.show" ng-change="render()">
-					<label for="panel.sparkline.show" class="cr1"></label>
+					<input class="cr1" id="ctrl.panel.sparkline.show" type="checkbox"
+					ng-model="ctrl.panel.sparkline.show" ng-checked="ctrl.panel.sparkline.show" ng-change="ctrl.render()">
+					<label for="ctrl.panel.sparkline.show" class="cr1"></label>
 				</li>
 				</li>
 				<li class="tight-form-item">
 				<li class="tight-form-item">
 					Background mode&nbsp;
 					Background mode&nbsp;
-					<input class="cr1" id="panel.sparkline.full" type="checkbox"
-					ng-model="panel.sparkline.full" ng-checked="panel.sparkline.full" ng-change="render()">
-					<label for="panel.sparkline.full" class="cr1"></label>
+					<input class="cr1" id="ctrl.panel.sparkline.full" type="checkbox"
+					ng-model="ctrl.panel.sparkline.full" ng-checked="ctrl.panel.sparkline.full" ng-change="ctrl.render()">
+					<label for="ctrl.panel.sparkline.full" class="cr1"></label>
 				</li>
 				</li>
 				<li class="tight-form-item">
 				<li class="tight-form-item">
 					Line Color
 					Line Color
 				</li>
 				</li>
 				<li class="tight-form-item">
 				<li class="tight-form-item">
-					<spectrum-picker ng-model="panel.sparkline.lineColor" ng-change="render()" ></spectrum-picker>
+					<spectrum-picker ng-model="ctrl.panel.sparkline.lineColor" ng-change="ctrl.render()" ></spectrum-picker>
 				</li>
 				</li>
 				<li class="tight-form-item">
 				<li class="tight-form-item">
 					Fill Color
 					Fill Color
 				</li>
 				</li>
 				<li class="tight-form-item last">
 				<li class="tight-form-item last">
-					<spectrum-picker ng-model="panel.sparkline.fillColor" ng-change="render()" ></spectrum-picker>
+					<spectrum-picker ng-model="ctrl.panel.sparkline.fillColor" ng-change="ctrl.render()" ></spectrum-picker>
 				</li>
 				</li>
 			</ul>
 			</ul>
 			<div class="clearfix"></div>
 			<div class="clearfix"></div>
@@ -163,21 +163,21 @@
 				<li class="tight-form-item">
 				<li class="tight-form-item">
 					<strong>Value to text mapping</strong>
 					<strong>Value to text mapping</strong>
 				</li>
 				</li>
-				<li class="tight-form-item"  ng-repeat-start="map in panel.valueMaps">
-					<i class="fa fa-remove pointer" ng-click="removeValueMap(map)"></i>
+				<li class="tight-form-item"  ng-repeat-start="map in ctrl.panel.valueMaps">
+					<i class="fa fa-remove pointer" ng-click="ctrl.removeValueMap(map)"></i>
 				</li>
 				</li>
 				<li>
 				<li>
-					<input type="text" ng-model="map.value" placeholder="value" class="input-mini tight-form-input" ng-blur="render()">
+					<input type="text" ng-model="ctrl.map.value" placeholder="value" class="input-mini tight-form-input" ng-blur="ctrl.render()">
 				</li>
 				</li>
 				<li class="tight-form-item">
 				<li class="tight-form-item">
 					<i class="fa fa-arrow-right"></i>
 					<i class="fa fa-arrow-right"></i>
 				</li>
 				</li>
 				<li ng-repeat-end>
 				<li ng-repeat-end>
-					<input type="text" placeholder="text" ng-model="map.text" class="input-mini tight-form-input" ng-blur="render()">
+					<input type="text" placeholder="text" ng-model="ctrl.map.text" class="input-mini tight-form-input" ng-blur="ctrl.render()">
 				</li>
 				</li>
 
 
 				<li>
 				<li>
-					<a class="pointer tight-form-item last" ng-click="addValueMap();">
+					<a class="pointer tight-form-item last" ng-click="ctrl.addValueMap();">
 						<i class="fa fa-plus"></i>
 						<i class="fa fa-plus"></i>
 					</a>
 					</a>
 				</li>
 				</li>

+ 4 - 4
public/app/plugins/panel/singlestat/module.html

@@ -1,4 +1,4 @@
-<grafana-panel>
-	<div class="singlestat-panel"></div>
-	<div class="clearfix"></div>
-</grafana-panel>
+<div class="singlestat-panel">
+
+</div>
+<div class="clearfix"></div>

+ 180 - 170
public/app/plugins/panel/singlestat/module.ts

@@ -2,221 +2,226 @@
 
 
 import _ from 'lodash';
 import _ from 'lodash';
 import $ from 'jquery';
 import $ from 'jquery';
-import angular from 'angular';
+import 'jquery.flot';
 import {SingleStatCtrl} from './controller';
 import {SingleStatCtrl} from './controller';
+import {PanelDirective} from '../../../features/panel/panel';
 
 
-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
+class SingleStatPanel extends PanelDirective {
+  templateUrl = 'app/plugins/panel/singlestat/module.html';
+  controller = SingleStatCtrl;
 
 
-          elem.css('height', height + 'px');
+  /** @ngInject */
+  constructor(private $location, private linkSrv, private $timeout, private templateSrv) {
+    super();
+  }
 
 
-          return true;
-        } catch (e) { // IE throws errors sometimes
-          return false;
+  link(scope, elem, attrs, ctrl) {
+    var $location = this.$location;
+    var linkSrv = this.linkSrv;
+    var $timeout = this.$timeout;
+    var panel = ctrl.panel;
+    var templateSrv = this.templateSrv;
+    var data, 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();
         }
         }
       }
       }
 
 
-      function applyColoringThresholds(value, valueString) {
-        if (!panel.colorValue) {
-          return valueString;
-        }
+      render();
+      ctrl.renderingCompleted();
+    });
 
 
-        var color = getColorForValue(data, value);
-        if (color) {
-          return '<span style="color:' + color + '">'+ valueString + '</span>';
+    function setElementHeight() {
+      try {
+        var height = scope.height || panel.height || ctrl.row.height;
+        if (_.isString(height)) {
+          height = parseInt(height.replace('px', ''), 10);
         }
         }
 
 
-        return valueString;
-      }
+        height -= 5; // padding
+        height -= panel.title ? 24 : 9; // subtract panel title bar
+
+        elem.css('height', height + 'px');
 
 
-      function getSpan(className, fontSize, value)  {
-        value = templateSrv.replace(value);
-        return '<span class="' + className + '" style="font-size:' + fontSize + '">' +
-          value + '</span>';
+        return true;
+      } catch (e) { // IE throws errors sometimes
+        return false;
       }
       }
+    }
 
 
-      function getBigValueHtml() {
-        var body = '<div class="singlestat-panel-value-container">';
+    function applyColoringThresholds(value, valueString) {
+      if (!panel.colorValue) {
+        return valueString;
+      }
 
 
-        if (panel.prefix) { body += getSpan('singlestat-panel-prefix', panel.prefixFontSize, scope.panel.prefix); }
+      var color = getColorForValue(data, value);
+      if (color) {
+        return '<span style="color:' + color + '">'+ valueString + '</span>';
+      }
 
 
-        var value = applyColoringThresholds(data.valueRounded, data.valueFormated);
-        body += getSpan('singlestat-panel-value', panel.valueFontSize, value);
+      return valueString;
+    }
 
 
-        if (panel.postfix) { body += getSpan('singlestat-panel-postfix', panel.postfixFontSize, panel.postfix); }
+    function getSpan(className, fontSize, value)  {
+      value = templateSrv.replace(value);
+      return '<span class="' + className + '" style="font-size:' + fontSize + '">' +
+        value + '</span>';
+    }
 
 
-        body += '</div>';
+    function getBigValueHtml() {
+      var body = '<div class="singlestat-panel-value-container">';
 
 
-        return body;
-      }
+      if (panel.prefix) { body += getSpan('singlestat-panel-prefix', panel.prefixFontSize, panel.prefix); }
 
 
-      function addSparkline() {
-        var panel = scope.panel;
-        var width = elem.width() + 20;
-        var height = elem.height() || 100;
-
-        var plotCanvas = $('<div></div>');
-        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";
-        }
+      var value = applyColoringThresholds(data.valueRounded, data.valueFormated);
+      body += getSpan('singlestat-panel-value', panel.valueFontSize, value);
 
 
-        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 },
-        };
+      if (panel.postfix) { body += getSpan('singlestat-panel-postfix', panel.postfixFontSize, panel.postfix); }
 
 
-        elem.append(plotCanvas);
+      body += '</div>';
 
 
-        var plotSeries = {
-          data: data.flotpairs,
-          color: panel.sparkline.lineColor
-        };
+      return body;
+    }
 
 
-        $.plot(plotCanvas, [plotSeries], options);
+    function addSparkline() {
+      var width = elem.width() + 20;
+      var height = elem.height() || 100;
+
+      var plotCanvas = $('<div></div>');
+      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";
       }
       }
 
 
-      function render() {
-        if (!scope.data) { return; }
+      plotCanvas.css(plotCss);
 
 
-        data = scope.data;
-        panel = scope.panel;
+      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: ctrl.range.from.valueOf(),
+          max: ctrl.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 (!ctrl.data) { return; }
 
 
-        setElementHeight();
+      data = ctrl.data;
+      setElementHeight();
 
 
-        var body = getBigValueHtml();
+      var body = getBigValueHtml();
 
 
-        if (panel.colorBackground && !isNaN(data.valueRounded)) {
-          var color = getColorForValue(data, data.valueRounded);
-          if (color) {
-            $panelContainer.css('background-color', color);
-            if (scope.fullscreen) {
-              elem.css('background-color', color);
-            } else {
-              elem.css('background-color', '');
-            }
+      if (panel.colorBackground && !isNaN(data.valueRounded)) {
+        var color = getColorForValue(data, 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', '');
         }
         }
+      } else {
+        $panelContainer.css('background-color', '');
+        elem.css('background-color', '');
+      }
 
 
-        elem.html(body);
+      elem.html(body);
 
 
-        if (panel.sparkline.show) {
-          addSparkline();
-        }
+      if (panel.sparkline.show) {
+        addSparkline();
+      }
 
 
-        elem.toggleClass('pointer', panel.links.length > 0);
+      elem.toggleClass('pointer', panel.links.length > 0);
 
 
-        if (panel.links.length > 0) {
-          linkInfo = linkSrv.getPanelLinkAnchorInfo(panel.links[0], scope.panel.scopedVars);
-        } else {
-          linkInfo = null;
-        }
+      if (panel.links.length > 0) {
+        linkInfo = linkSrv.getPanelLinkAnchorInfo(panel.links[0], panel.scopedVars);
+      } else {
+        linkInfo = null;
       }
       }
+    }
 
 
-      function hookupDrilldownLinkTooltip() {
-        // drilldown link tooltip
-        var drilldownTooltip = $('<div id="tooltip" class="">hello</div>"');
+    function hookupDrilldownLinkTooltip() {
+      // drilldown link tooltip
+      var drilldownTooltip = $('<div id="tooltip" class="">hello</div>"');
 
 
-        elem.mouseleave(function() {
-          if (panel.links.length === 0) { return;}
-          drilldownTooltip.detach();
-        });
+      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; }
+      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.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);
-            });
-          }
+        if (linkInfo.href.indexOf('http') === 0) {
+          window.location.href = linkInfo.href;
+        } else {
+          $timeout(function() {
+            $location.url(linkInfo.href);
+          });
+        }
 
 
-          drilldownTooltip.detach();
-        });
+        drilldownTooltip.detach();
+      });
 
 
-        elem.mousemove(function(e) {
-          if (!linkInfo) { return;}
+      elem.mousemove(function(e) {
+        if (!linkInfo) { return;}
 
 
-          drilldownTooltip.text('click to go to: ' + linkInfo.title);
-          drilldownTooltip.place_tt(e.pageX+20, e.pageY-15);
-        });
-      }
+        drilldownTooltip.text('click to go to: ' + linkInfo.title);
+        drilldownTooltip.place_tt(e.pageX+20, e.pageY-15);
+      });
     }
     }
-  };
+  }
 }
 }
 
 
 function getColorForValue(data, value) {
 function getColorForValue(data, value) {
@@ -226,7 +231,12 @@ function getColorForValue(data, value) {
     }
     }
   }
   }
 
 
+  console.log('first');
   return _.first(data.colorMap);
   return _.first(data.colorMap);
 }
 }
 
 
-export {singleStatPanel as panel, getColorForValue};
+export {
+  SingleStatPanel,
+  SingleStatPanel as Panel,
+  getColorForValue
+};