Browse Source

feat(plugins): made panels loaded via plugin-componet directive

Torkel Ödegaard 10 years ago
parent
commit
14cc771cbe

+ 14 - 10
public/app/core/directives/plugin_component.ts

@@ -5,6 +5,7 @@ import _ from 'lodash';
 
 import config from 'app/core/config';
 import coreModule from 'app/core/core_module';
+import {UnknownPanelCtrl} from 'app/plugins/panel/unknown/module';
 
 /** @ngInject */
 function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $templateCache) {
@@ -45,20 +46,23 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $
   }
 
   function loadPanelComponentInfo(scope, attrs) {
+    var componentInfo: any = {
+      name: 'panel-plugin-' + scope.panel.type,
+      bindings: {dashboard: "=", panel: "=", row: "="},
+      attrs: {dashboard: "dashboard", panel: "panel", row: "row"},
+    };
+
     var panelElemName = 'panel-' + scope.panel.type;
     let panelInfo = config.panels[scope.panel.type];
-    if (!panelInfo) {
-      // unknown
+    var panelCtrlPromise = Promise.resolve(UnknownPanelCtrl);
+    if (panelInfo) {
+      panelCtrlPromise = System.import(panelInfo.module).then(function(panelModule) {
+        return panelModule.PanelCtrl;
+      });
     }
 
-    return System.import(panelInfo.module).then(function(panelModule): any {
-      var PanelCtrl = panelModule.PanelCtrl;
-      var componentInfo = {
-        name: 'panel-plugin-' + panelInfo.id,
-        bindings: {dashboard: "=", panel: "=", row: "="},
-        attrs: {dashboard: "dashboard", panel: "panel", row: "row"},
-        Component: PanelCtrl,
-      };
+    return panelCtrlPromise.then(function(PanelCtrl: any) {
+      componentInfo.Component = PanelCtrl;
 
       if (!PanelCtrl || PanelCtrl.registered) {
         return componentInfo;

+ 0 - 1
public/app/features/panel/all.js

@@ -2,7 +2,6 @@ define([
   './panel_menu',
   './panel_directive',
   './solo_panel_ctrl',
-  './panel_loader',
   './query_ctrl',
   './panel_editor_tab',
   './query_editor_row',

+ 8 - 2
public/app/features/panel/panel.ts

@@ -4,12 +4,18 @@ import config from 'app/core/config';
 
 import {PanelCtrl} from './panel_ctrl';
 import {MetricsPanelCtrl} from './metrics_panel_ctrl';
-import {PanelDirective} from './panel_directive';
 import {QueryCtrl} from './query_ctrl';
 
+class DefaultPanelCtrl extends PanelCtrl {
+  /** @ngInject */
+  constructor($scope, $injector) {
+    super($scope, $injector);
+  }
+}
+
 export {
   PanelCtrl,
+  DefaultPanelCtrl,
   MetricsPanelCtrl,
-  PanelDirective,
   QueryCtrl,
 }

+ 0 - 42
public/app/features/panel/panel_directive.ts

@@ -3,48 +3,6 @@
 import angular from 'angular';
 import $ from 'jquery';
 
-import {PanelCtrl} from './panel_ctrl';
-
-export class DefaultPanelCtrl extends PanelCtrl {
-  /** @ngInject */
-  constructor($scope, $injector) {
-    super($scope, $injector);
-  }
-}
-
-export class PanelDirective {
-  template: string;
-  templateUrl: string;
-  bindToController: boolean;
-  scope: any;
-  controller: any;
-  controllerAs: string;
-
-  getDirective() {
-    if (!this.controller) {
-      this.controller = DefaultPanelCtrl;
-    }
-
-    return {
-      template: this.template,
-      templateUrl: this.templateUrl,
-      controller: this.controller,
-      controllerAs: 'ctrl',
-      bindToController: true,
-      scope: {dashboard: "=", panel: "=", row: "="},
-      link: (scope, elem, attrs, ctrl) => {
-        ctrl.init();
-        this.link(scope, elem, attrs, ctrl);
-      }
-    };
-  }
-
-  link(scope, elem, attrs, ctrl) {
-    return null;
-  }
-}
-
-
 var module = angular.module('grafana.directives');
 
 module.directive('grafanaPanel', function() {

+ 0 - 88
public/app/features/panel/panel_loader.ts

@@ -1,88 +0,0 @@
-///<reference path="../../headers/common.d.ts" />
-
-import angular from 'angular';
-import config from 'app/core/config';
-
-import {UnknownPanel} from '../../plugins/panel/unknown/module';
-
-var directiveModule = angular.module('grafana.directives');
-
-/** @ngInject */
-function panelLoader($compile, $http, $q, $injector, $templateCache) {
-  return {
-    restrict: 'E',
-    scope: {
-      dashboard: "=",
-      row: "=",
-      panel: "="
-    },
-    link: function(scope, elem, attrs) {
-
-      function getTemplate(directive) {
-        if (directive.template) {
-          return $q.when(directive.template);
-        }
-        var cached = $templateCache.get(directive.templateUrl);
-        if (cached) {
-          return $q.when(cached);
-        }
-        return $http.get(directive.templateUrl).then(res => {
-          return res.data;
-        });
-      }
-
-      function addPanelAndCompile(name) {
-        var child = angular.element(document.createElement(name));
-        child.attr('dashboard', 'dashboard');
-        child.attr('panel', 'panel');
-        child.attr('row', 'row');
-        $compile(child)(scope);
-
-        elem.empty();
-        elem.append(child);
-      }
-
-      function addPanel(name, Panel) {
-        if (Panel.registered) {
-          addPanelAndCompile(name);
-          return;
-        }
-
-        if (Panel.promise) {
-          Panel.promise.then(() => {
-            addPanelAndCompile(name);
-          });
-          return;
-        }
-
-        var panelInstance = $injector.instantiate(Panel);
-        var directive = panelInstance.getDirective();
-
-        Panel.promise = getTemplate(directive).then(template => {
-          directive.templateUrl = null;
-          directive.template = `<grafana-panel ctrl="ctrl">${template}</grafana-panel>`;
-          directiveModule.directive(attrs.$normalize(name), function() {
-            return directive;
-          });
-          Panel.registered = true;
-          addPanelAndCompile(name);
-        });
-      }
-
-      var panelElemName = 'panel-directive-' + scope.panel.type;
-      let panelInfo = config.panels[scope.panel.type];
-      if (!panelInfo) {
-        addPanel(panelElemName, UnknownPanel);
-        return;
-      }
-
-      System.import(panelInfo.module).then(function(panelModule) {
-        addPanel(panelElemName, panelModule.Panel);
-      }).catch(err => {
-        console.log('Panel err: ', err);
-      });
-    }
-  };
-}
-
-directiveModule.directive('panelLoader', panelLoader);

+ 2 - 9
public/app/plugins/panel/dashlist/module.ts

@@ -2,7 +2,7 @@
 
 import _ from 'lodash';
 import config from 'app/core/config';
-import {PanelDirective, PanelCtrl} from '../../../features/panel/panel';
+import {PanelCtrl} from '../../../features/panel/panel';
 
  // Set and populate defaults
 var panelDefaults = {
@@ -55,11 +55,4 @@ class DashListCtrl extends PanelCtrl {
   }
 }
 
-class DashListPanel extends PanelDirective {
-  controller = DashListCtrl;
-}
-
-export {
-  DashListCtrl as DashListCtrl,
-  DashListCtrl as PanelCtrl,
-}
+export {DashListCtrl, DashListCtrl as PanelCtrl}

+ 0 - 232
public/app/plugins/panel/singlestat/controller.ts

@@ -1,232 +0,0 @@
-///<reference path="../../../headers/common.d.ts" />
-
-import angular from 'angular';
-import _ from 'lodash';
-import kbn from 'app/core/utils/kbn';
-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 extends MetricsPanelCtrl {
-  series: any[];
-  data: any[];
-  fontSizes: any[];
-  unitFormats: any[];
-
-  /** @ngInject */
-  constructor($scope, $injector) {
-    super($scope, $injector);
-    _.defaults(this.panel, panelDefaults);
-  }
-
-  initEditMode() {
-    super.initEditMode();
-    this.icon =  "fa fa-dashboard";
-    this.fontSizes = ['20%', '30%','50%','70%','80%','100%', '110%', '120%', '150%', '170%', '200%'];
-    this.addEditorTab('Options', 'app/plugins/panel/singlestat/editor.html', 2);
-    this.unitFormats = kbn.getUnitFormats();
-  }
-
-  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();
-  }
-
-  seriesHandler(seriesData) {
-    var series = new TimeSeries({
-      datapoints: seriesData.datapoints,
-      alias: seriesData.target,
-    });
-
-    series.flotpairs = series.getFlotPairs(this.panel.nullPointMode);
-    return series;
-  }
-
-  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();
-  }
-
-  invertColorOrder() {
-    var tmp = this.panel.colors[0];
-    this.panel.colors[0] = this.panel.colors[2];
-    this.panel.colors[2] = tmp;
-    this.render();
-  }
-
-  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;
-    }
-
-    size *= magn;
-
-    // reduce starting decimals if not needed
-    if (Math.floor(value) === value) { dec = 0; }
-
-    var result: any = {};
-    result.decimals = Math.max(0, dec);
-    result.scaledDecimals = result.decimals - Math.floor(Math.log(size) / Math.LN10) + 2;
-
-    return result;
-  }
-
-  render() {
-    var data: any = {};
-    this.setValues(data);
-
-    data.thresholds = this.panel.thresholds.split(',').map(function(strVale) {
-      return Number(strVale.trim());
-    });
-
-    data.colorMap = this.panel.colors;
-
-    this.data = data;
-    this.broadcastRender();
-  }
-
-  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;
-          return;
-        }
-        continue;
-      }
-
-      // value/number to text mapping
-      var value = parseFloat(map.value);
-      if (value === data.value) {
-        data.valueFormated = map.text;
-        return;
-      }
-    }
-
-    if (data.value === null || data.value === void 0) {
-      data.valueFormated = "no value";
-    }
-  };
-
-  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: '' });
-  }
-}
-

+ 228 - 9
public/app/plugins/panel/singlestat/module.ts

@@ -1,18 +1,237 @@
 ///<reference path="../../../headers/common.d.ts" />
 
+import angular from 'angular';
 import _ from 'lodash';
 import $ from 'jquery';
 import 'jquery.flot';
-import {SingleStatCtrl} from './controller';
-import {PanelDirective} from '../../../features/panel/panel';
 
-class SingleStatPanel extends PanelDirective {
-  templateUrl = 'public/app/plugins/panel/singlestat/module.html';
-  controller = SingleStatCtrl;
+import kbn from 'app/core/utils/kbn';
+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)',
+  }
+};
+
+class SingleStatCtrl extends MetricsPanelCtrl {
+  static templateUrl = 'public/app/plugins/panel/singlestat/module.html';
+
+  series: any[];
+  data: any[];
+  fontSizes: any[];
+  unitFormats: any[];
 
   /** @ngInject */
-  constructor(private $location, private linkSrv, private $timeout, private templateSrv) {
-    super();
+  constructor($scope, $injector, private $location, private linkSrv, private templateSrv) {
+    super($scope, $injector);
+    _.defaults(this.panel, panelDefaults);
+  }
+
+  initEditMode() {
+    super.initEditMode();
+    this.icon =  "fa fa-dashboard";
+    this.fontSizes = ['20%', '30%','50%','70%','80%','100%', '110%', '120%', '150%', '170%', '200%'];
+    this.addEditorTab('Options', 'app/plugins/panel/singlestat/editor.html', 2);
+    this.unitFormats = kbn.getUnitFormats();
+  }
+
+  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();
+  }
+
+  seriesHandler(seriesData) {
+    var series = new TimeSeries({
+      datapoints: seriesData.datapoints,
+      alias: seriesData.target,
+    });
+
+    series.flotpairs = series.getFlotPairs(this.panel.nullPointMode);
+    return series;
+  }
+
+  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();
+  }
+
+  invertColorOrder() {
+    var tmp = this.panel.colors[0];
+    this.panel.colors[0] = this.panel.colors[2];
+    this.panel.colors[2] = tmp;
+    this.render();
+  }
+
+  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;
+    }
+
+    size *= magn;
+
+    // reduce starting decimals if not needed
+    if (Math.floor(value) === value) { dec = 0; }
+
+    var result: any = {};
+    result.decimals = Math.max(0, dec);
+    result.scaledDecimals = result.decimals - Math.floor(Math.log(size) / Math.LN10) + 2;
+
+    return result;
+  }
+
+  render() {
+    var data: any = {};
+    this.setValues(data);
+
+    data.thresholds = this.panel.thresholds.split(',').map(function(strVale) {
+      return Number(strVale.trim());
+    });
+
+    data.colorMap = this.panel.colors;
+
+    this.data = data;
+    this.broadcastRender();
+  }
+
+  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;
+          return;
+        }
+        continue;
+      }
+
+      // value/number to text mapping
+      var value = parseFloat(map.value);
+      if (value === data.value) {
+        data.valueFormated = map.text;
+        return;
+      }
+    }
+
+    if (data.value === null || data.value === void 0) {
+      data.valueFormated = "no value";
+    }
+  };
+
+  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: '' });
   }
 
   link(scope, elem, attrs, ctrl) {
@@ -235,7 +454,7 @@ function getColorForValue(data, value) {
 }
 
 export {
-  SingleStatPanel,
-  SingleStatPanel as Panel,
+  SingleStatCtrl,
+  SingleStatCtrl as PanelCtrl,
   getColorForValue
 };

+ 1 - 1
public/app/plugins/panel/singlestat/specs/singlestat-specs.ts

@@ -4,7 +4,7 @@ import {describe, beforeEach, it, sinon, expect, angularMocks} from '../../../..
 
 import angular from 'angular';
 import helpers from '../../../../../test/specs/helpers';
-import {SingleStatCtrl} from '../controller';
+import {SingleStatCtrl} from '../module';
 
 describe('SingleStatCtrl', function() {
   var ctx = new helpers.ControllerTestContext();

+ 0 - 133
public/app/plugins/panel/table/controller.ts

@@ -1,133 +0,0 @@
-///<reference path="../../../headers/common.d.ts" />
-
-import angular from 'angular';
-import _ from 'lodash';
-import moment from 'moment';
-import * as FileExport from 'app/core/utils/file_export';
-import {MetricsPanelCtrl} from '../../../features/panel/panel';
-import {transformDataToTable} from './transformers';
-import {tablePanelEditor} from './editor';
-
-var panelDefaults = {
-  targets: [{}],
-  transform: 'timeseries_to_columns',
-  pageSize: null,
-  showHeader: true,
-  styles: [
-    {
-      type: 'date',
-      pattern: 'Time',
-      dateFormat: 'YYYY-MM-DD HH:mm:ss',
-    },
-    {
-      unit: 'short',
-      type: 'number',
-      decimals: 2,
-      colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"],
-      colorMode: null,
-      pattern: '/.*/',
-      thresholds: [],
-    }
-  ],
-  columns: [],
-  scroll: true,
-  fontSize: '100%',
-  sort: {col: 0, desc: true},
-};
-
-export class TablePanelCtrl extends MetricsPanelCtrl {
-  pageIndex: number;
-  dataRaw: any;
-  table: any;
-
-  /** @ngInject */
-  constructor($scope, $injector, private annotationsSrv) {
-    super($scope, $injector);
-    this.pageIndex = 0;
-
-    if (this.panel.styles === void 0) {
-      this.panel.styles = this.panel.columns;
-      this.panel.columns = this.panel.fields;
-      delete this.panel.columns;
-      delete this.panel.fields;
-    }
-
-    _.defaults(this.panel, panelDefaults);
-  }
-
-  initEditMode() {
-    super.initEditMode();
-    this.addEditorTab('Options', tablePanelEditor, 2);
-  }
-
-  getExtendedMenu() {
-    var menu = super.getExtendedMenu();
-    menu.push({text: 'Export CSV', click: 'ctrl.exportCsv()'});
-    return menu;
-  }
-
-  refreshData(datasource) {
-    this.pageIndex = 0;
-
-    if (this.panel.transform === 'annotations') {
-      return this.annotationsSrv.getAnnotations(this.dashboard).then(annotations => {
-        this.dataRaw = annotations;
-        this.render();
-      });
-    }
-
-    return this.issueQueries(datasource)
-    .then(this.dataHandler.bind(this))
-    .catch(err => {
-      this.render();
-      throw err;
-    });
-  }
-
-  toggleColumnSort(col, colIndex) {
-    if (this.panel.sort.col === colIndex) {
-      if (this.panel.sort.desc) {
-        this.panel.sort.desc = false;
-      } else {
-        this.panel.sort.col = null;
-      }
-    } else {
-      this.panel.sort.col = colIndex;
-      this.panel.sort.desc = true;
-    }
-
-    this.render();
-  }
-
-  dataHandler(results) {
-    this.dataRaw = results.data;
-    this.pageIndex = 0;
-    this.render();
-  }
-
-  render() {
-    // automatically correct transform mode
-    // based on data
-    if (this.dataRaw && this.dataRaw.length) {
-      if (this.dataRaw[0].type === 'table') {
-        this.panel.transform = 'table';
-      } else {
-        if (this.dataRaw[0].type === 'docs') {
-          this.panel.transform = 'json';
-        } else {
-          if (this.panel.transform === 'table' || this.panel.transform === 'json') {
-            this.panel.transform = 'timeseries_to_rows';
-          }
-        }
-      }
-    }
-
-    this.table = transformDataToTable(this.dataRaw, this.panel);
-    this.table.sort(this.panel.sort);
-    this.broadcastRender(this.table);
-  }
-
-  exportCsv() {
-    FileExport.exportTableDataToCsv(this.table);
-  }
-}

+ 131 - 9
public/app/plugins/panel/table/module.ts

@@ -1,17 +1,139 @@
 ///<reference path="../../../headers/common.d.ts" />
 
-import kbn = require('app/core/utils/kbn');
-
+import angular from 'angular';
 import _ from 'lodash';
 import $ from 'jquery';
 import moment from 'moment';
-import {PanelDirective} from '../../../features/panel/panel';
-import {TablePanelCtrl} from './controller';
+import * as FileExport from 'app/core/utils/file_export';
+import {MetricsPanelCtrl} from '../../../features/panel/panel';
+import {transformDataToTable} from './transformers';
+import {tablePanelEditor} from './editor';
 import {TableRenderer} from './renderer';
 
-class TablePanel extends PanelDirective {
-  templateUrl = 'public/app/plugins/panel/table/module.html';
-  controller = TablePanelCtrl;
+var panelDefaults = {
+  targets: [{}],
+  transform: 'timeseries_to_columns',
+  pageSize: null,
+  showHeader: true,
+  styles: [
+    {
+      type: 'date',
+      pattern: 'Time',
+      dateFormat: 'YYYY-MM-DD HH:mm:ss',
+    },
+    {
+      unit: 'short',
+      type: 'number',
+      decimals: 2,
+      colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"],
+      colorMode: null,
+      pattern: '/.*/',
+      thresholds: [],
+    }
+  ],
+  columns: [],
+  scroll: true,
+  fontSize: '100%',
+  sort: {col: 0, desc: true},
+};
+
+class TablePanelCtrl extends MetricsPanelCtrl {
+  static templateUrl = 'public/app/plugins/panel/table/module.html';
+
+  pageIndex: number;
+  dataRaw: any;
+  table: any;
+
+  /** @ngInject */
+  constructor($scope, $injector, private annotationsSrv) {
+    super($scope, $injector);
+    this.pageIndex = 0;
+
+    if (this.panel.styles === void 0) {
+      this.panel.styles = this.panel.columns;
+      this.panel.columns = this.panel.fields;
+      delete this.panel.columns;
+      delete this.panel.fields;
+    }
+
+    _.defaults(this.panel, panelDefaults);
+  }
+
+  initEditMode() {
+    super.initEditMode();
+    this.addEditorTab('Options', tablePanelEditor, 2);
+  }
+
+  getExtendedMenu() {
+    var menu = super.getExtendedMenu();
+    menu.push({text: 'Export CSV', click: 'ctrl.exportCsv()'});
+    return menu;
+  }
+
+  refreshData(datasource) {
+    this.pageIndex = 0;
+
+    if (this.panel.transform === 'annotations') {
+      return this.annotationsSrv.getAnnotations(this.dashboard).then(annotations => {
+        this.dataRaw = annotations;
+        this.render();
+      });
+    }
+
+    return this.issueQueries(datasource)
+    .then(this.dataHandler.bind(this))
+    .catch(err => {
+      this.render();
+      throw err;
+    });
+  }
+
+  toggleColumnSort(col, colIndex) {
+    if (this.panel.sort.col === colIndex) {
+      if (this.panel.sort.desc) {
+        this.panel.sort.desc = false;
+      } else {
+        this.panel.sort.col = null;
+      }
+    } else {
+      this.panel.sort.col = colIndex;
+      this.panel.sort.desc = true;
+    }
+
+    this.render();
+  }
+
+  dataHandler(results) {
+    this.dataRaw = results.data;
+    this.pageIndex = 0;
+    this.render();
+  }
+
+  render() {
+    // automatically correct transform mode
+    // based on data
+    if (this.dataRaw && this.dataRaw.length) {
+      if (this.dataRaw[0].type === 'table') {
+        this.panel.transform = 'table';
+      } else {
+        if (this.dataRaw[0].type === 'docs') {
+          this.panel.transform = 'json';
+        } else {
+          if (this.panel.transform === 'table' || this.panel.transform === 'json') {
+            this.panel.transform = 'timeseries_to_rows';
+          }
+        }
+      }
+    }
+
+    this.table = transformDataToTable(this.dataRaw, this.panel);
+    this.table.sort(this.panel.sort);
+    this.broadcastRender(this.table);
+  }
+
+  exportCsv() {
+    FileExport.exportTableDataToCsv(this.table);
+  }
 
   link(scope, elem, attrs, ctrl) {
     var data;
@@ -97,6 +219,6 @@ class TablePanel extends PanelDirective {
 }
 
 export {
-  TablePanel,
-  TablePanel as Panel
+  TablePanelCtrl,
+  TablePanelCtrl as PanelCtrl
 };

+ 1 - 1
public/app/plugins/panel/text/module.ts

@@ -1,7 +1,7 @@
 ///<reference path="../../../headers/common.d.ts" />
 
 import _ from 'lodash';
-import {PanelDirective, PanelCtrl} from '../../../features/panel/panel';
+import {PanelCtrl} from '../../../features/panel/panel';
 
  // Set and populate defaults
 var panelDefaults = {

+ 7 - 7
public/app/plugins/panel/unknown/module.ts

@@ -1,14 +1,14 @@
 ///<reference path="../../../headers/common.d.ts" />
 
-import {PanelDirective} from '../../../features/panel/panel';
+import {PanelCtrl} from '../../../features/panel/panel';
 
-class UnknownPanel extends PanelDirective {
-  templateUrl = 'public/app/plugins/panel/unknown/module.html';
+export class UnknownPanelCtrl extends PanelCtrl {
+  static templateUrl = 'public/app/plugins/panel/unknown/module.html';
+
+  constructor($scope, $injector) {
+    super($scope, $injector);
+  }
 }
 
 
-export {
-  UnknownPanel,
-  UnknownPanel as Panel
-}