From 3e3b99696309981740703641e5995a0b863628c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 19 Jan 2016 21:57:58 +0100 Subject: [PATCH 01/91] feat(plugin): experimental work on plugin architecture --- public/app/plugins/panel/test/module.ts | 43 +++++++++++++++++++++++ public/app/plugins/panel/test/plugin.json | 5 +++ 2 files changed, 48 insertions(+) create mode 100644 public/app/plugins/panel/test/module.ts create mode 100644 public/app/plugins/panel/test/plugin.json diff --git a/public/app/plugins/panel/test/module.ts b/public/app/plugins/panel/test/module.ts new file mode 100644 index 00000000000..4fb2c4a66eb --- /dev/null +++ b/public/app/plugins/panel/test/module.ts @@ -0,0 +1,43 @@ +/// + +import PanelMeta from 'app/features/panel/panel_meta2'; + +class PanelBaseCtrl { + constructor(private $scope) { + $scope.panelMeta = new PanelMeta({ + panelName: 'Table', + editIcon: "fa fa-table", + fullscreen: true, + metricsEditor: true, + }); + $scope.testProp = "hello"; + } +} + +class TestPanelCtrl extends PanelBaseCtrl { + + constructor($scope) { + super($scope); + $scope.panelMeta.panelName = "Test"; + } +} + +function testPanelDirective() { + return { + restrict: 'E', + template: ` + +
+

Test Panel, {{testProp}}

+
+
+ `, + controller: TestPanelCtrl + }; +} + +export { + PanelBaseCtrl, + TestPanelCtrl, + testPanelDirective as panel +} diff --git a/public/app/plugins/panel/test/plugin.json b/public/app/plugins/panel/test/plugin.json new file mode 100644 index 00000000000..b2d7b9b42c6 --- /dev/null +++ b/public/app/plugins/panel/test/plugin.json @@ -0,0 +1,5 @@ +{ + "type": "panel", + "name": "Test", + "id": "test" +} From 8dd118bd8659f178326e95afa5e64fbb97ddf02d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 20 Jan 2016 09:45:24 +0100 Subject: [PATCH 02/91] updated --- public/app/features/panel/panel_meta2.ts | 1 - public/app/plugins/panel/test/module.ts | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/public/app/features/panel/panel_meta2.ts b/public/app/features/panel/panel_meta2.ts index ca276f9377b..c3f9a25eb31 100644 --- a/public/app/features/panel/panel_meta2.ts +++ b/public/app/features/panel/panel_meta2.ts @@ -23,7 +23,6 @@ export default class PanelMeta { this.addMenuItem('Edit', 'icon-cog', 'editPanel(); dismiss();', 'Editor'); this.addMenuItem('Duplicate', 'icon-copy', 'duplicatePanel()', 'Editor'); this.addMenuItem('Share', 'icon-share', 'sharePanel(); dismiss();'); - this.addEditorTab('General', 'app/partials/panelgeneral.html'); if (options.metricsEditor) { diff --git a/public/app/plugins/panel/test/module.ts b/public/app/plugins/panel/test/module.ts index 4fb2c4a66eb..74272cd21e3 100644 --- a/public/app/plugins/panel/test/module.ts +++ b/public/app/plugins/panel/test/module.ts @@ -15,7 +15,6 @@ class PanelBaseCtrl { } class TestPanelCtrl extends PanelBaseCtrl { - constructor($scope) { super($scope); $scope.panelMeta.panelName = "Test"; @@ -32,7 +31,8 @@ function testPanelDirective() { `, - controller: TestPanelCtrl + controller: TestPanelCtrl, + controllerAs: 'ctrl', }; } From 0796b2c0e366099ef3363cba2dfb280d0448b5e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 21 Jan 2016 17:46:57 +0100 Subject: [PATCH 03/91] poc(panel as isolated compoennts): experimental panel stuff --- public/app/features/panel/panel_directive.js | 2 +- public/app/features/panel/panel_loader.ts | 48 +++++++++++++------- public/app/features/panel/panel_menu.js | 14 +++--- public/app/plugins/panel/test/module.ts | 30 ++++++------ 4 files changed, 57 insertions(+), 37 deletions(-) diff --git a/public/app/features/panel/panel_directive.js b/public/app/features/panel/panel_directive.js index b4c1a2d6a0f..9d1b6b54b76 100644 --- a/public/app/features/panel/panel_directive.js +++ b/public/app/features/panel/panel_directive.js @@ -12,9 +12,9 @@ function (angular, $) { restrict: 'E', templateUrl: 'app/features/panel/partials/panel.html', transclude: true, + scope: { ctrl: "=" }, link: function(scope, elem) { var panelContainer = elem.find('.panel-container'); - scope.$watchGroup(['fullscreen', 'height', 'panel.height', 'row.height'], function() { panelContainer.css({ minHeight: scope.height || scope.panel.height || scope.row.height, display: 'block' }); elem.toggleClass('panel-fullscreen', scope.fullscreen ? true : false); diff --git a/public/app/features/panel/panel_loader.ts b/public/app/features/panel/panel_loader.ts index 8dc46f494a1..612cacd3b98 100644 --- a/public/app/features/panel/panel_loader.ts +++ b/public/app/features/panel/panel_loader.ts @@ -5,26 +5,42 @@ import config from 'app/core/config'; import {unknownPanelDirective} from '../../plugins/panel/unknown/module'; +var directiveModule = angular.module('grafana.directives'); + /** @ngInject */ -function panelLoader($parse, dynamicDirectiveSrv) { - return dynamicDirectiveSrv.create({ - directive: scope => { - let panelInfo = config.panels[scope.panel.type]; - if (!panelInfo) { - return Promise.resolve({ - name: 'panel-directive-' + scope.panel.type, - fn: unknownPanelDirective - }); +function panelLoader($compile, dynamicDirectiveSrv) { + return { + restrict: 'E', + link: function(scope, elem, attrs) { + + function addDirective(name, component) { + if (!component.registered) { + directiveModule.component(attrs.$normalize(name), component); + component.registered = true; + } + + var child = angular.element(document.createElement(name)); + child.attr('dashboard', 'dashboard'); + child.attr('panel', 'panel'); + $compile(child)(scope); + + elem.empty(); + elem.append(child); } - return System.import(panelInfo.module).then(function(panelModule) { - return { - name: 'panel-directive-' + scope.panel.type, - fn: panelModule.panel, - }; + var panelElemName = 'panel-directive-' + scope.panel.type; + let panelInfo = config.panels[scope.panel.type]; + if (!panelInfo) { + addDirective(panelElemName, unknownPanelDirective); + } + + System.import(panelInfo.module).then(function(panelModule) { + addDirective(panelElemName, panelModule.component); + }).catch(err => { + console.log('Panel err: ', err); }); - }, - }); + } + }; } angular.module('grafana.directives').directive('panelLoader', panelLoader); diff --git a/public/app/features/panel/panel_menu.js b/public/app/features/panel/panel_menu.js index 30b61418d72..ec0f2301675 100644 --- a/public/app/features/panel/panel_menu.js +++ b/public/app/features/panel/panel_menu.js @@ -11,16 +11,16 @@ function (angular, $, _) { .directive('panelMenu', function($compile, linkSrv) { var linkTemplate = '' + - '{{panel.title | interpolateTemplateVars:this}}' + + '{{ctrl.panel.title}}' + '' + - ' {{panelMeta.timeInfo}}' + + ' {{panelMeta.timeInfo}}' + ''; function createExternalLinkMenu($scope) { var template = '
'; template += '
'; - if ($scope.panel.links) { + if ($scope.ctrl.panel.links) { _.each($scope.panel.links, function(link) { var info = linkSrv.getPanelLinkAnchorInfo(link, $scope.panel.scopedVars); template += '' + info.title + ''; @@ -31,7 +31,7 @@ function (angular, $, _) { function createMenuTemplate($scope) { var template = '
'; - if ($scope.dashboardMeta.canEdit) { + if ($scope.ctrl.dashboard.meta.canEdit) { template += '
'; template += '
'; template += ''; @@ -44,9 +44,9 @@ function (angular, $, _) { template += '
'; template += ''; - _.each($scope.panelMeta.menu, function(item) { + _.each($scope.ctrl.panelMeta.menu, function(item) { // skip edit actions if not editor - if (item.role === 'Editor' && !$scope.dashboardMeta.canEdit) { + if (item.role === 'Editor' && !$scope.ctrl.dashboard.meta.canEdit) { return; } @@ -64,7 +64,7 @@ function (angular, $, _) { } function getExtendedMenu($scope) { - return angular.copy($scope.panelMeta.extendedMenu); + return angular.copy($scope.ctrl.panelMeta.extendedMenu); } return { diff --git a/public/app/plugins/panel/test/module.ts b/public/app/plugins/panel/test/module.ts index 4fb2c4a66eb..5c4413ea144 100644 --- a/public/app/plugins/panel/test/module.ts +++ b/public/app/plugins/panel/test/module.ts @@ -3,14 +3,17 @@ import PanelMeta from 'app/features/panel/panel_meta2'; class PanelBaseCtrl { + panelMeta: any; + panel: any; + dashboard: any; + constructor(private $scope) { - $scope.panelMeta = new PanelMeta({ + this.panelMeta = new PanelMeta({ panelName: 'Table', editIcon: "fa fa-table", fullscreen: true, metricsEditor: true, }); - $scope.testProp = "hello"; } } @@ -18,26 +21,27 @@ class TestPanelCtrl extends PanelBaseCtrl { constructor($scope) { super($scope); - $scope.panelMeta.panelName = "Test"; } } -function testPanelDirective() { - return { - restrict: 'E', - template: ` - +var testPanelComponent = { + template: ` +
-

Test Panel, {{testProp}}

+

Test Panel

`, - controller: TestPanelCtrl - }; -} + controller: TestPanelCtrl, + controllerAs: 'ctrl', + bindings: { + dashboard: "=", + panel: "=", + } +}; export { PanelBaseCtrl, TestPanelCtrl, - testPanelDirective as panel + testPanelComponent as component, } From 94a7e9b18576f0d047ffeeb50fc48ce31b6bd3e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 22 Jan 2016 19:59:36 +0100 Subject: [PATCH 04/91] poc(panel): experimental panel stuff --- public/app/features/panel/panel_ctrl.ts | 21 +++++++++ public/app/features/panel/panel_directive.js | 7 +-- public/app/features/panel/panel_loader.ts | 47 +++++++++++++++++--- public/app/partials/dashboard.html | 7 +-- public/app/plugins/panel/test/module.html | 4 ++ public/app/plugins/panel/test/module.ts | 39 +++------------- 6 files changed, 80 insertions(+), 45 deletions(-) create mode 100644 public/app/features/panel/panel_ctrl.ts create mode 100644 public/app/plugins/panel/test/module.html diff --git a/public/app/features/panel/panel_ctrl.ts b/public/app/features/panel/panel_ctrl.ts new file mode 100644 index 00000000000..db8788ab306 --- /dev/null +++ b/public/app/features/panel/panel_ctrl.ts @@ -0,0 +1,21 @@ +/// + +import PanelMeta from './panel_meta2'; + +export class PanelCtrl { + panelMeta: any; + panel: any; + row: any; + dashboard: any; + + constructor(private $scope) { + this.panelMeta = new PanelMeta({ + panelName: 'Table', + editIcon: "fa fa-table", + fullscreen: true, + metricsEditor: true, + }); + } +} + + diff --git a/public/app/features/panel/panel_directive.js b/public/app/features/panel/panel_directive.js index 9d1b6b54b76..f8b9e724b32 100644 --- a/public/app/features/panel/panel_directive.js +++ b/public/app/features/panel/panel_directive.js @@ -15,9 +15,10 @@ function (angular, $) { scope: { ctrl: "=" }, link: function(scope, elem) { var panelContainer = elem.find('.panel-container'); - scope.$watchGroup(['fullscreen', 'height', 'panel.height', 'row.height'], function() { - panelContainer.css({ minHeight: scope.height || scope.panel.height || scope.row.height, display: 'block' }); - elem.toggleClass('panel-fullscreen', scope.fullscreen ? true : false); + var ctrl = scope.ctrl; + scope.$watchGroup(['ctrl.fullscreen', 'ctrl.height', 'ctrl.panel.height', 'ctrl.row.height'], function() { + panelContainer.css({ minHeight: ctrl.height || ctrl.panel.height || ctrl.row.height, display: 'block' }); + elem.toggleClass('panel-fullscreen', ctrl.fullscreen ? true : false); }); } }; diff --git a/public/app/features/panel/panel_loader.ts b/public/app/features/panel/panel_loader.ts index 612cacd3b98..dd0284138ab 100644 --- a/public/app/features/panel/panel_loader.ts +++ b/public/app/features/panel/panel_loader.ts @@ -8,34 +8,67 @@ import {unknownPanelDirective} from '../../plugins/panel/unknown/module'; var directiveModule = angular.module('grafana.directives'); /** @ngInject */ -function panelLoader($compile, dynamicDirectiveSrv) { +function panelLoader($compile, dynamicDirectiveSrv, $http, $q) { return { restrict: 'E', + scope: { + dashboard: "=", + row: "=", + panel: "=" + }, link: function(scope, elem, attrs) { - function addDirective(name, component) { - if (!component.registered) { - directiveModule.component(attrs.$normalize(name), component); - component.registered = true; + function getTemplate(component) { + if (component.template) { + return $q.when(component.template); } + return $http.get(component.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, directive) { + if (!directive.registered) { + getTemplate(directive).then(template => { + directive.templateUrl = null; + directive.template = `${template}`; + directive.controllerAs = 'ctrl'; + directive.bindToController = true; + directive.scope = { + dashboard: "=", + panel: "=", + row: "=" + }; + + directiveModule.directive(attrs.$normalize(name), function() { + return directive; + }); + directive.registered = true; + addPanelAndCompile(name); + }); + } + addPanelAndCompile(name); + } + var panelElemName = 'panel-directive-' + scope.panel.type; let panelInfo = config.panels[scope.panel.type]; if (!panelInfo) { - addDirective(panelElemName, unknownPanelDirective); + addPanel(panelElemName, unknownPanelDirective); } System.import(panelInfo.module).then(function(panelModule) { - addDirective(panelElemName, panelModule.component); + addPanel(panelElemName, panelModule.panel); }).catch(err => { console.log('Panel err: ', err); }); diff --git a/public/app/partials/dashboard.html b/public/app/partials/dashboard.html index 9d20da3d7b4..7aeefb20730 100644 --- a/public/app/partials/dashboard.html +++ b/public/app/partials/dashboard.html @@ -79,9 +79,10 @@
-
- +
+ +
diff --git a/public/app/plugins/panel/test/module.html b/public/app/plugins/panel/test/module.html new file mode 100644 index 00000000000..47b59289210 --- /dev/null +++ b/public/app/plugins/panel/test/module.html @@ -0,0 +1,4 @@ +

+ Test panel! + panel.id: {{ctrl.panel.id}} +

diff --git a/public/app/plugins/panel/test/module.ts b/public/app/plugins/panel/test/module.ts index 5c4413ea144..ae9af416ed0 100644 --- a/public/app/plugins/panel/test/module.ts +++ b/public/app/plugins/panel/test/module.ts @@ -1,47 +1,22 @@ /// -import PanelMeta from 'app/features/panel/panel_meta2'; - -class PanelBaseCtrl { - panelMeta: any; - panel: any; - dashboard: any; - - constructor(private $scope) { - this.panelMeta = new PanelMeta({ - panelName: 'Table', - editIcon: "fa fa-table", - fullscreen: true, - metricsEditor: true, - }); - } -} - -class TestPanelCtrl extends PanelBaseCtrl { +import {PanelCtrl} from '../../../features/panel/panel_ctrl'; +class TestPanelCtrl extends PanelCtrl { constructor($scope) { super($scope); } } -var testPanelComponent = { - template: ` - -
-

Test Panel

-
-
- `, +var panel = { + templateUrl: `app/plugins/panel/test/module.html`, controller: TestPanelCtrl, - controllerAs: 'ctrl', - bindings: { - dashboard: "=", - panel: "=", + link: function(scope, elem) { + console.log('panel link'); } }; export { - PanelBaseCtrl, TestPanelCtrl, - testPanelComponent as component, + panel, } From ab3b586838578d84a0c3495c84d9dd8efe28f776 Mon Sep 17 00:00:00 2001 From: Anthony Woods Date: Sat, 23 Jan 2016 03:15:39 +0800 Subject: [PATCH 05/91] add encryption util functions --- pkg/util/encryption.go | 70 +++++++++++++++++++++++++++++++++++++ pkg/util/encryption_test.go | 28 +++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 pkg/util/encryption.go create mode 100644 pkg/util/encryption_test.go diff --git a/pkg/util/encryption.go b/pkg/util/encryption.go new file mode 100644 index 00000000000..f24a9b29397 --- /dev/null +++ b/pkg/util/encryption.go @@ -0,0 +1,70 @@ +package util + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "io" + + "github.com/grafana/grafana/pkg/log" +) + +func Decrypt(payload []byte, secret string) []byte { + key := encryptionKeyToBytes(secret) + + block, err := aes.NewCipher(key) + if err != nil { + log.Fatal(4, err.Error()) + } + + // The IV needs to be unique, but not secure. Therefore it's common to + // include it at the beginning of the ciphertext. + if len(payload) < aes.BlockSize { + log.Fatal(4, "payload too short") + } + iv := payload[:aes.BlockSize] + payload = payload[aes.BlockSize:] + + stream := cipher.NewCFBDecrypter(block, iv) + + // XORKeyStream can work in-place if the two arguments are the same. + stream.XORKeyStream(payload, payload) + return payload +} + +func Encrypt(payload []byte, secret string) []byte { + key := encryptionKeyToBytes(secret) + + block, err := aes.NewCipher(key) + if err != nil { + log.Fatal(4, err.Error()) + } + + // The IV needs to be unique, but not secure. Therefore it's common to + // include it at the beginning of the ciphertext. + ciphertext := make([]byte, aes.BlockSize+len(payload)) + iv := ciphertext[:aes.BlockSize] + if _, err := io.ReadFull(rand.Reader, iv); err != nil { + log.Fatal(4, err.Error()) + } + + stream := cipher.NewCFBEncrypter(block, iv) + stream.XORKeyStream(ciphertext[aes.BlockSize:], payload) + + return ciphertext +} + +// Key needs to be 32bytes +func encryptionKeyToBytes(secret string) []byte { + key := make([]byte, 32, 32) + keyBytes := []byte(secret) + secretLength := len(keyBytes) + for i := 0; i < 32; i++ { + if secretLength > i { + key[i] = keyBytes[i] + } else { + key[i] = 0 + } + } + return key +} diff --git a/pkg/util/encryption_test.go b/pkg/util/encryption_test.go new file mode 100644 index 00000000000..254f0f178c0 --- /dev/null +++ b/pkg/util/encryption_test.go @@ -0,0 +1,28 @@ +package util + +import ( + "testing" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestEncryption(t *testing.T) { + + Convey("When getting encryption key", t, func() { + + key := encryptionKeyToBytes("secret") + So(len(key), ShouldEqual, 32) + + key = encryptionKeyToBytes("a very long secret key that is larger then 32bytes") + So(len(key), ShouldEqual, 32) + + }) + + Convey("When decrypting basic payload", t, func() { + encrypted := Encrypt([]byte("grafana"), "1234") + decrypted := Decrypt(encrypted, "1234") + + So(string(decrypted), ShouldEqual, "grafana") + }) + +} From 32f78d465bb1bd8d393db4bf2624cf8845345a7d Mon Sep 17 00:00:00 2001 From: Anthony Woods Date: Sat, 23 Jan 2016 06:17:22 +0800 Subject: [PATCH 06/91] add secureJsonData to appSettings model. - adds the new column to the DB table. - data stored in the DB is encrypted - update appRouteHeaders templates to use the jsonData and decrypted secureJsonData --- pkg/api/app_routes.go | 11 +++++-- pkg/models/app_settings.go | 33 ++++++++++++++----- pkg/services/sqlstore/app_settings.go | 25 ++++++++++---- .../sqlstore/migrations/app_settings.go | 1 + public/app/features/apps/edit_ctrl.ts | 1 + 5 files changed, 53 insertions(+), 18 deletions(-) diff --git a/pkg/api/app_routes.go b/pkg/api/app_routes.go index 6ad41f79b53..169c5c6d15c 100644 --- a/pkg/api/app_routes.go +++ b/pkg/api/app_routes.go @@ -94,8 +94,15 @@ func NewApiPluginProxy(ctx *middleware.Context, proxyPath string, route *plugins ctx.JsonApiErr(500, "failed to get AppSettings.", err) return } - - err = t.Execute(&contentBuf, query.Result.JsonData) + type templateData struct { + JsonData map[string]interface{} + SecureJsonData map[string]string + } + data := templateData{ + JsonData: query.Result.JsonData, + SecureJsonData: query.Result.SecureJsonData.Decrypt(), + } + err = t.Execute(&contentBuf, data) if err != nil { ctx.JsonApiErr(500, fmt.Sprintf("failed to execute header content template for header %s.", header.Name), err) return diff --git a/pkg/models/app_settings.go b/pkg/models/app_settings.go index f3b60502cb0..78d4c483f2b 100644 --- a/pkg/models/app_settings.go +++ b/pkg/models/app_settings.go @@ -3,6 +3,9 @@ package models import ( "errors" "time" + + "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/util" ) var ( @@ -10,25 +13,37 @@ var ( ) type AppSettings struct { - Id int64 - AppId string - OrgId int64 - Enabled bool - Pinned bool - JsonData map[string]interface{} + Id int64 + AppId string + OrgId int64 + Enabled bool + Pinned bool + JsonData map[string]interface{} + SecureJsonData SecureJsonData Created time.Time Updated time.Time } +type SecureJsonData map[string][]byte + +func (s SecureJsonData) Decrypt() map[string]string { + decrypted := make(map[string]string) + for key, data := range s { + decrypted[key] = string(util.Decrypt(data, setting.SecretKey)) + } + return decrypted +} + // ---------------------- // COMMANDS // Also acts as api DTO type UpdateAppSettingsCmd struct { - Enabled bool `json:"enabled"` - Pinned bool `json:"pinned"` - JsonData map[string]interface{} `json:"jsonData"` + Enabled bool `json:"enabled"` + Pinned bool `json:"pinned"` + JsonData map[string]interface{} `json:"jsonData"` + SecureJsonData map[string]string `json:"secureJsonData"` AppId string `json:"-"` OrgId int64 `json:"-"` diff --git a/pkg/services/sqlstore/app_settings.go b/pkg/services/sqlstore/app_settings.go index 7d9482e7b22..f454d2cc5ff 100644 --- a/pkg/services/sqlstore/app_settings.go +++ b/pkg/services/sqlstore/app_settings.go @@ -5,6 +5,8 @@ import ( "github.com/grafana/grafana/pkg/bus" m "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/util" ) func init() { @@ -40,18 +42,27 @@ func UpdateAppSettings(cmd *m.UpdateAppSettingsCmd) error { sess.UseBool("enabled") sess.UseBool("pinned") if !exists { + // encrypt secureJsonData + secureJsonData := make(map[string][]byte) + for key, data := range cmd.SecureJsonData { + secureJsonData[key] = util.Encrypt([]byte(data), setting.SecretKey) + } app = m.AppSettings{ - AppId: cmd.AppId, - OrgId: cmd.OrgId, - Enabled: cmd.Enabled, - Pinned: cmd.Pinned, - JsonData: cmd.JsonData, - Created: time.Now(), - Updated: time.Now(), + AppId: cmd.AppId, + OrgId: cmd.OrgId, + Enabled: cmd.Enabled, + Pinned: cmd.Pinned, + JsonData: cmd.JsonData, + SecureJsonData: secureJsonData, + Created: time.Now(), + Updated: time.Now(), } _, err = sess.Insert(&app) return err } else { + for key, data := range cmd.SecureJsonData { + app.SecureJsonData[key] = util.Encrypt([]byte(data), setting.SecretKey) + } app.Updated = time.Now() app.Enabled = cmd.Enabled app.JsonData = cmd.JsonData diff --git a/pkg/services/sqlstore/migrations/app_settings.go b/pkg/services/sqlstore/migrations/app_settings.go index 437debbe95b..8b970a5062a 100644 --- a/pkg/services/sqlstore/migrations/app_settings.go +++ b/pkg/services/sqlstore/migrations/app_settings.go @@ -13,6 +13,7 @@ func addAppSettingsMigration(mg *Migrator) { {Name: "enabled", Type: DB_Bool, Nullable: false}, {Name: "pinned", Type: DB_Bool, Nullable: false}, {Name: "json_data", Type: DB_Text, Nullable: true}, + {Name: "secure_json_data", Type: DB_Text, Nullable: true}, {Name: "created", Type: DB_DateTime, Nullable: false}, {Name: "updated", Type: DB_DateTime, Nullable: false}, }, diff --git a/public/app/features/apps/edit_ctrl.ts b/public/app/features/apps/edit_ctrl.ts index dfbce64df5e..ccdaf529b5e 100644 --- a/public/app/features/apps/edit_ctrl.ts +++ b/public/app/features/apps/edit_ctrl.ts @@ -24,6 +24,7 @@ export class AppEditCtrl { enabled: this.appModel.enabled, pinned: this.appModel.pinned, jsonData: this.appModel.jsonData, + secureJsonData: this.appModel.secureJsonData, }, options); this.backendSrv.post(`/api/org/apps/${this.$routeParams.appId}/settings`, updateCmd).then(function() { From 892704242102d30ff5d20e77fbd6a876f1e28b01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Sat, 23 Jan 2016 01:44:38 +0100 Subject: [PATCH 07/91] more new panel stuff --- public/app/features/dashboard/viewStateSrv.js | 32 ++++++++++++------- public/app/features/panel/panel_ctrl.ts | 14 +++++++- public/app/features/panel/panel_directive.js | 22 +++++++------ public/app/features/panel/panel_menu.js | 31 +++++++++--------- public/app/features/panel/panel_meta2.ts | 10 +++--- public/app/features/panel/partials/panel.html | 4 +-- 6 files changed, 69 insertions(+), 44 deletions(-) diff --git a/public/app/features/dashboard/viewStateSrv.js b/public/app/features/dashboard/viewStateSrv.js index 58403766a31..63bb999e3ca 100644 --- a/public/app/features/dashboard/viewStateSrv.js +++ b/public/app/features/dashboard/viewStateSrv.js @@ -31,6 +31,14 @@ function (angular, _, $) { } }); + $scope.onAppEvent('panel-change-view', function(evt, payload) { + self.update(payload); + }); + + $scope.onAppEvent('panel-instantiated', function(evt, payload) { + self.registerPanel(payload.scope); + }); + this.update(this.getQueryStringState(), true); this.expandRowForPanel(); } @@ -105,23 +113,24 @@ function (angular, _, $) { DashboardViewState.prototype.getPanelScope = function(id) { return _.find(this.panelScopes, function(panelScope) { - return panelScope.panel.id === id; + return panelScope.ctrl.panel.id === id; }); }; DashboardViewState.prototype.leaveFullscreen = function(render) { var self = this; + var ctrl = self.fullscreenPanel.ctrl; - self.fullscreenPanel.editMode = false; - self.fullscreenPanel.fullscreen = false; - delete self.fullscreenPanel.height; + ctrl.editMode = false; + ctrl.fullscreen = false; + delete ctrl.height; - this.$scope.appEvent('panel-fullscreen-exit', {panelId: this.fullscreenPanel.panel.id}); + this.$scope.appEvent('panel-fullscreen-exit', {panelId: ctrl.panel.id}); if (!render) { return false;} $timeout(function() { - if (self.oldTimeRange !== self.fullscreenPanel.range) { + if (self.oldTimeRange !== ctrl.range) { self.$scope.broadcastRefresh(); } else { @@ -135,17 +144,18 @@ function (angular, _, $) { var docHeight = $(window).height(); var editHeight = Math.floor(docHeight * 0.3); var fullscreenHeight = Math.floor(docHeight * 0.7); + var ctrl = panelScope.ctrl; - panelScope.editMode = this.state.edit && this.$scope.dashboardMeta.canEdit; - panelScope.height = panelScope.editMode ? editHeight : fullscreenHeight; + ctrl.editMode = this.state.edit && this.$scope.dashboardMeta.canEdit; + ctrl.height = ctrl.editMode ? editHeight : fullscreenHeight; - this.oldTimeRange = panelScope.range; + this.oldTimeRange = ctrl.range; this.fullscreenPanel = panelScope; $(window).scrollTop(0); panelScope.fullscreen = true; - this.$scope.appEvent('panel-fullscreen-enter', {panelId: panelScope.panel.id}); + this.$scope.appEvent('panel-fullscreen-enter', {panelId: ctrl.panel.id}); $timeout(function() { panelScope.$broadcast('render'); @@ -156,7 +166,7 @@ function (angular, _, $) { var self = this; self.panelScopes.push(panelScope); - if (self.state.panelId === panelScope.panel.id) { + if (self.state.panelId === panelScope.ctrl.panel.id) { self.enterFullscreen(panelScope); } diff --git a/public/app/features/panel/panel_ctrl.ts b/public/app/features/panel/panel_ctrl.ts index db8788ab306..19c1ed2bc8c 100644 --- a/public/app/features/panel/panel_ctrl.ts +++ b/public/app/features/panel/panel_ctrl.ts @@ -8,13 +8,25 @@ export class PanelCtrl { row: any; dashboard: any; - constructor(private $scope) { + constructor(private scope) { this.panelMeta = new PanelMeta({ panelName: 'Table', editIcon: "fa fa-table", fullscreen: true, metricsEditor: true, }); + + this.publishAppEvent('panel-instantiated', {scope: scope}); + } + + publishAppEvent(evtName, evt) { + this.scope.$root.appEvent(evtName, evt); + } + + editPanel() { + this.publishAppEvent('panel-change-view', { + fullscreen: true, edit: true, panelId: this.panel.id + }); } } diff --git a/public/app/features/panel/panel_directive.js b/public/app/features/panel/panel_directive.js index f8b9e724b32..50cc4b57b26 100644 --- a/public/app/features/panel/panel_directive.js +++ b/public/app/features/panel/panel_directive.js @@ -17,6 +17,7 @@ function (angular, $) { var panelContainer = elem.find('.panel-container'); var ctrl = scope.ctrl; scope.$watchGroup(['ctrl.fullscreen', 'ctrl.height', 'ctrl.panel.height', 'ctrl.row.height'], function() { + console.log('height: ', ctrl.height); panelContainer.css({ minHeight: ctrl.height || ctrl.panel.height || ctrl.row.height, display: 'block' }); elem.toggleClass('panel-fullscreen', ctrl.fullscreen ? true : false); }); @@ -31,6 +32,7 @@ function (angular, $) { link: function(scope, elem) { var resizing = false; var lastPanel = false; + var ctrl = scope.ctrl; var handleOffset; var originalHeight; var originalWidth; @@ -41,31 +43,31 @@ function (angular, $) { resizing = true; handleOffset = $(e.target).offset(); - originalHeight = parseInt(scope.row.height); - originalWidth = scope.panel.span; + originalHeight = parseInt(ctrl.row.height); + originalWidth = ctrl.panel.span; maxWidth = $(document).width(); - lastPanel = scope.row.panels[scope.row.panels.length - 1]; + lastPanel = ctrl.row.panels[ctrl.row.panels.length - 1]; $('body').on('mousemove', moveHandler); $('body').on('mouseup', dragEndHandler); } function moveHandler(e) { - scope.row.height = originalHeight + (e.pageY - handleOffset.top); - scope.panel.span = originalWidth + (((e.pageX - handleOffset.left) / maxWidth) * 12); - scope.panel.span = Math.min(Math.max(scope.panel.span, 1), 12); + ctrl.row.height = 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); - var rowSpan = scope.dashboard.rowSpan(scope.row); + var rowSpan = ctrl.dashboard.rowSpan(ctrl.row); // auto adjust other panels if (Math.floor(rowSpan) < 14) { // last panel should not push row down - if (lastPanel === scope.panel && rowSpan > 12) { + if (lastPanel === ctrl.panel && rowSpan > 12) { lastPanel.span -= rowSpan - 12; } // reduce width of last panel so total in row is 12 - else if (lastPanel !== scope.panel) { + else if (lastPanel !== ctrl.panel) { lastPanel.span = lastPanel.span - (rowSpan - 12); lastPanel.span = Math.min(Math.max(lastPanel.span, 1), 12); } @@ -78,7 +80,7 @@ function (angular, $) { function dragEndHandler() { // if close to 12 - var rowSpan = scope.dashboard.rowSpan(scope.row); + var rowSpan = ctrl.dashboard.rowSpan(scope.row); if (rowSpan < 12 && rowSpan > 11) { lastPanel.span += 12 - rowSpan; } diff --git a/public/app/features/panel/panel_menu.js b/public/app/features/panel/panel_menu.js index ec0f2301675..bea8d27d2fa 100644 --- a/public/app/features/panel/panel_menu.js +++ b/public/app/features/panel/panel_menu.js @@ -13,25 +13,25 @@ function (angular, $, _) { '' + '{{ctrl.panel.title}}' + '' + - ' {{panelMeta.timeInfo}}' + + ' {{ctrl.panelMeta.timeInfo}}' + ''; - function createExternalLinkMenu($scope) { + function createExternalLinkMenu(ctrl) { var template = '
'; template += '
'; - if ($scope.ctrl.panel.links) { - _.each($scope.panel.links, function(link) { - var info = linkSrv.getPanelLinkAnchorInfo(link, $scope.panel.scopedVars); + if (ctrl.panel.links) { + _.each(ctrl.panel.links, function(link) { + var info = linkSrv.getPanelLinkAnchorInfo(link, ctrl.panel.scopedVars); template += '' + info.title + ''; }); } return template; } - function createMenuTemplate($scope) { + function createMenuTemplate(ctrl) { var template = '
'; - if ($scope.ctrl.dashboard.meta.canEdit) { + if (ctrl.dashboard.meta.canEdit) { template += '
'; template += '
'; template += ''; @@ -44,9 +44,9 @@ function (angular, $, _) { template += '
'; template += ''; - _.each($scope.ctrl.panelMeta.menu, function(item) { + _.each(ctrl.panelMeta.menu, function(item) { // skip edit actions if not editor - if (item.role === 'Editor' && !$scope.ctrl.dashboard.meta.canEdit) { + if (item.role === 'Editor' && !ctrl.dashboard.meta.canEdit) { return; } @@ -63,8 +63,8 @@ function (angular, $, _) { return template; } - function getExtendedMenu($scope) { - return angular.copy($scope.ctrl.panelMeta.extendedMenu); + function getExtendedMenu(ctrl) { + return angular.copy(ctrl.panelMeta.extendedMenu); } return { @@ -74,13 +74,14 @@ function (angular, $, _) { var $panelLinksBtn = $link.find(".panel-links-btn"); var $panelContainer = elem.parents(".panel-container"); var menuScope = null; + var ctrl = $scope.ctrl; var timeout = null; var $menu = null; elem.append($link); $scope.$watchCollection('panel.links', function(newValue) { - var showIcon = (newValue ? newValue.length > 0 : false) && $scope.panel.title !== ''; + var showIcon = (newValue ? newValue.length > 0 : false) && ctrl.panel.title !== ''; $panelLinksBtn.toggle(showIcon); }); @@ -124,9 +125,9 @@ function (angular, $, _) { var menuTemplate; if ($(e.target).hasClass('fa-external-link')) { - menuTemplate = createExternalLinkMenu($scope); + menuTemplate = createExternalLinkMenu(ctrl); } else { - menuTemplate = createMenuTemplate($scope); + menuTemplate = createMenuTemplate(ctrl); } $menu = $(menuTemplate); @@ -135,7 +136,7 @@ function (angular, $, _) { }); menuScope = $scope.$new(); - menuScope.extendedMenu = getExtendedMenu($scope); + menuScope.extendedMenu = getExtendedMenu(ctrl); menuScope.dismiss = function() { dismiss(null, true); }; diff --git a/public/app/features/panel/panel_meta2.ts b/public/app/features/panel/panel_meta2.ts index ca276f9377b..fe72b9f4fa2 100644 --- a/public/app/features/panel/panel_meta2.ts +++ b/public/app/features/panel/panel_meta2.ts @@ -17,12 +17,12 @@ export default class PanelMeta { this.extendedMenu = []; if (options.fullscreen) { - this.addMenuItem('View', 'icon-eye-open', 'toggleFullscreen(false); dismiss();'); + this.addMenuItem('View', 'icon-eye-open', 'ctrl.viewPanel(); dismiss();'); } - this.addMenuItem('Edit', 'icon-cog', 'editPanel(); dismiss();', 'Editor'); - this.addMenuItem('Duplicate', 'icon-copy', 'duplicatePanel()', 'Editor'); - this.addMenuItem('Share', 'icon-share', 'sharePanel(); dismiss();'); + this.addMenuItem('Edit', 'icon-cog', 'ctrl.editPanel(); dismiss();', 'Editor'); + this.addMenuItem('Duplicate', 'icon-copy', 'ctrl.duplicate()', 'Editor'); + this.addMenuItem('Share', 'icon-share', 'ctrl.share(); dismiss();'); this.addEditorTab('General', 'app/partials/panelgeneral.html'); @@ -30,7 +30,7 @@ export default class PanelMeta { this.addEditorTab('Metrics', 'app/partials/metrics.html'); } - this.addExtendedMenuItem('Panel JSON', '', 'editPanelJson(); dismiss();'); + this.addExtendedMenuItem('Panel JSON', '', 'ctrl.editPanelJson(); dismiss();'); } addMenuItem (text, icon, click, role?) { diff --git a/public/app/features/panel/partials/panel.html b/public/app/features/panel/partials/panel.html index a7fef0f5d67..f3eb19df424 100644 --- a/public/app/features/panel/partials/panel.html +++ b/public/app/features/panel/partials/panel.html @@ -19,12 +19,12 @@
-
+
- {{panelMeta.panelName}} + {{ctrl.panelMeta.panelName}}
From ecdc730de7b93c04d7673267a10da29c87b59e3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Sat, 23 Jan 2016 15:40:30 -0500 Subject: [PATCH 08/91] poc(): new panel system --- public/app/features/panel/panel_ctrl.ts | 26 ++++++++++++------- public/app/features/panel/partials/panel.html | 2 +- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/public/app/features/panel/panel_ctrl.ts b/public/app/features/panel/panel_ctrl.ts index 19c1ed2bc8c..cc020c23423 100644 --- a/public/app/features/panel/panel_ctrl.ts +++ b/public/app/features/panel/panel_ctrl.ts @@ -3,19 +3,13 @@ import PanelMeta from './panel_meta2'; export class PanelCtrl { - panelMeta: any; + meta: any; panel: any; row: any; dashboard: any; constructor(private scope) { - this.panelMeta = new PanelMeta({ - panelName: 'Table', - editIcon: "fa fa-table", - fullscreen: true, - metricsEditor: true, - }); - + this.meta = new PanelMeta(this.panel); this.publishAppEvent('panel-instantiated', {scope: scope}); } @@ -23,11 +17,23 @@ export class PanelCtrl { this.scope.$root.appEvent(evtName, evt); } - editPanel() { + changeView(fullscreen, edit) { this.publishAppEvent('panel-change-view', { - fullscreen: true, edit: true, panelId: this.panel.id + fullscreen: fullscreen, edit: edit, panelId: this.panel.id }); } + + viewPanel() { + this.changeView(true, false); + } + + editPanel() { + this.changeView(true, true); + } + + exitFullscreen() { + this.changeView(false, false); + } } diff --git a/public/app/features/panel/partials/panel.html b/public/app/features/panel/partials/panel.html index f3eb19df424..3b325366826 100644 --- a/public/app/features/panel/partials/panel.html +++ b/public/app/features/panel/partials/panel.html @@ -32,7 +32,7 @@
-
From 4f7fb40d9b542f696238c33fa049f45632adffa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Sun, 24 Jan 2016 16:39:25 -0500 Subject: [PATCH 09/91] feat(panel plugin): improving panel plugin model --- pkg/api/frontendsettings.go | 1 + public/app/core/filters/filters.ts | 9 ++- public/app/features/panel/all.js | 1 + .../panel/{panel_ctrl.ts => panel.ts} | 28 ++++++++- public/app/features/panel/panel_editor_tab.ts | 24 ++++++++ public/app/features/panel/panel_loader.ts | 57 ++++++++++--------- public/app/features/panel/panel_menu.js | 10 ++-- public/app/features/panel/panel_meta3.ts | 56 ++++++++++++++++++ public/app/features/panel/partials/panel.html | 12 ++-- public/app/partials/panelgeneral.html | 14 ++--- public/app/plugins/panel/test/module.ts | 24 +++++--- public/app/plugins/panel/test/plugin.json | 15 ++++- public/app/plugins/panel/unknown/module.ts | 18 +++--- 13 files changed, 201 insertions(+), 68 deletions(-) rename public/app/features/panel/{panel_ctrl.ts => panel.ts} (55%) create mode 100644 public/app/features/panel/panel_editor_tab.ts create mode 100644 public/app/features/panel/panel_meta3.ts diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go index 7bea0b6338c..256ff15e881 100644 --- a/pkg/api/frontendsettings.go +++ b/pkg/api/frontendsettings.go @@ -123,6 +123,7 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro panels[panel.Id] = map[string]interface{}{ "module": panel.Module, "name": panel.Name, + "info": panel.Info, } } diff --git a/public/app/core/filters/filters.ts b/public/app/core/filters/filters.ts index 57b2b1bd9ba..6122a010182 100644 --- a/public/app/core/filters/filters.ts +++ b/public/app/core/filters/filters.ts @@ -59,11 +59,14 @@ coreModule.filter('noXml', function() { coreModule.filter('interpolateTemplateVars', function (templateSrv) { var filterFunc: any = function(text, scope) { - if (scope.panel) { - return templateSrv.replaceWithText(text, scope.panel.scopedVars); + var scopedVars; + if (scope.ctrl && scope.ctrl.panel) { + scopedVars = scope.ctrl.panel.scopedVars; } else { - return templateSrv.replaceWithText(text, scope.row.scopedVars); + scopedVars = scope.row.scopedVars; } + + return templateSrv.replaceWithText(text, scopedVars); }; filterFunc.$stateful = true; diff --git a/public/app/features/panel/all.js b/public/app/features/panel/all.js index ef3ea9b9b0f..ad635d83f07 100644 --- a/public/app/features/panel/all.js +++ b/public/app/features/panel/all.js @@ -6,4 +6,5 @@ define([ './solo_panel_ctrl', './panel_loader', './query_editor', + './panel_editor_tab', ], function () {}); diff --git a/public/app/features/panel/panel_ctrl.ts b/public/app/features/panel/panel.ts similarity index 55% rename from public/app/features/panel/panel_ctrl.ts rename to public/app/features/panel/panel.ts index cc020c23423..a3bf35db9a4 100644 --- a/public/app/features/panel/panel_ctrl.ts +++ b/public/app/features/panel/panel.ts @@ -1,15 +1,17 @@ /// -import PanelMeta from './panel_meta2'; +import PanelMeta from './panel_meta3'; export class PanelCtrl { meta: any; panel: any; row: any; dashboard: any; + tabIndex: number; constructor(private scope) { this.meta = new PanelMeta(this.panel); + this.tabIndex = 0; this.publishAppEvent('panel-instantiated', {scope: scope}); } @@ -36,4 +38,28 @@ export class PanelCtrl { } } +export class PanelDirective { + template: string; + templateUrl: string; + bindToController: boolean; + scope: any; + controller: any; + controllerAs: string; + + getDirective() { + return { + template: this.template, + templateUrl: this.templateUrl, + controller: this.controller, + controllerAs: 'ctrl', + bindToController: true, + scope: {dashboard: "=", panel: "=", row: "="}, + link: this.link + }; + } + + link(scope) { + return null; + } +} diff --git a/public/app/features/panel/panel_editor_tab.ts b/public/app/features/panel/panel_editor_tab.ts new file mode 100644 index 00000000000..56e3078962e --- /dev/null +++ b/public/app/features/panel/panel_editor_tab.ts @@ -0,0 +1,24 @@ +/// + +import angular from 'angular'; +import config from 'app/core/config'; + +var directiveModule = angular.module('grafana.directives'); + +/** @ngInject */ +function panelEditorTab(dynamicDirectiveSrv) { + return dynamicDirectiveSrv.create({ + scope: { + panelCtrl: "=", + editorTab: "=", + }, + directive: scope => { + return Promise.resolve({ + name: 'panel-editor-tab-' + scope.editorTab.title, + fn: scope.editorTab.directiveFn, + }); + } + }); +} + +directiveModule.directive('panelEditorTab', panelEditorTab); diff --git a/public/app/features/panel/panel_loader.ts b/public/app/features/panel/panel_loader.ts index dd0284138ab..cf42458a9a7 100644 --- a/public/app/features/panel/panel_loader.ts +++ b/public/app/features/panel/panel_loader.ts @@ -3,12 +3,12 @@ import angular from 'angular'; import config from 'app/core/config'; -import {unknownPanelDirective} from '../../plugins/panel/unknown/module'; +import {UnknownPanel} from '../../plugins/panel/unknown/module'; var directiveModule = angular.module('grafana.directives'); /** @ngInject */ -function panelLoader($compile, dynamicDirectiveSrv, $http, $q) { +function panelLoader($compile, dynamicDirectiveSrv, $http, $q, $injector) { return { restrict: 'E', scope: { @@ -18,11 +18,11 @@ function panelLoader($compile, dynamicDirectiveSrv, $http, $q) { }, link: function(scope, elem, attrs) { - function getTemplate(component) { - if (component.template) { - return $q.when(component.template); + function getTemplate(directive) { + if (directive.template) { + return $q.when(directive.template); } - return $http.get(component.templateUrl).then(res => { + return $http.get(directive.templateUrl).then(res => { return res.data; }); } @@ -38,37 +38,42 @@ function panelLoader($compile, dynamicDirectiveSrv, $http, $q) { elem.append(child); } - function addPanel(name, directive) { - if (!directive.registered) { - getTemplate(directive).then(template => { - directive.templateUrl = null; - directive.template = `${template}`; - directive.controllerAs = 'ctrl'; - directive.bindToController = true; - directive.scope = { - dashboard: "=", - panel: "=", - row: "=" - }; + function addPanel(name, Panel) { + if (Panel.registered) { + addPanelAndCompile(name); + } - directiveModule.directive(attrs.$normalize(name), function() { - return directive; - }); - directive.registered = true; + if (Panel.promise) { + Panel.promise.then(() => { addPanelAndCompile(name); }); + return; } - addPanelAndCompile(name); + + var panelInstance = $injector.instantiate(Panel); + var directive = panelInstance.getDirective(); + + Panel.promise = getTemplate(directive).then(template => { + directive.templateUrl = null; + directive.template = `${template}`; + directiveModule.directive(attrs.$normalize(name), function() { + return directive; + }); + Panel.registered = true; + addPanelAndCompile(name); + }); + + return; } var panelElemName = 'panel-directive-' + scope.panel.type; let panelInfo = config.panels[scope.panel.type]; if (!panelInfo) { - addPanel(panelElemName, unknownPanelDirective); + addPanel(panelElemName, UnknownPanel); } System.import(panelInfo.module).then(function(panelModule) { - addPanel(panelElemName, panelModule.panel); + addPanel(panelElemName, panelModule.Panel); }).catch(err => { console.log('Panel err: ', err); }); @@ -76,4 +81,4 @@ function panelLoader($compile, dynamicDirectiveSrv, $http, $q) { }; } -angular.module('grafana.directives').directive('panelLoader', panelLoader); +directiveModule.directive('panelLoader', panelLoader); diff --git a/public/app/features/panel/panel_menu.js b/public/app/features/panel/panel_menu.js index bea8d27d2fa..16ee311ff52 100644 --- a/public/app/features/panel/panel_menu.js +++ b/public/app/features/panel/panel_menu.js @@ -11,9 +11,9 @@ function (angular, $, _) { .directive('panelMenu', function($compile, linkSrv) { var linkTemplate = '' + - '{{ctrl.panel.title}}' + + '{{ctrl.panel.title | interpolateTemplateVars:this}}' + '' + - ' {{ctrl.panelMeta.timeInfo}}' + + ' {{ctrl.panelMeta.timeInfo}}' + ''; function createExternalLinkMenu(ctrl) { @@ -44,7 +44,7 @@ function (angular, $, _) { template += '
'; template += ''; - _.each(ctrl.panelMeta.menu, function(item) { + _.each(ctrl.meta.menu, function(item) { // skip edit actions if not editor if (item.role === 'Editor' && !ctrl.dashboard.meta.canEdit) { return; @@ -64,7 +64,7 @@ function (angular, $, _) { } function getExtendedMenu(ctrl) { - return angular.copy(ctrl.panelMeta.extendedMenu); + return angular.copy(ctrl.meta.extendedMenu); } return { @@ -80,7 +80,7 @@ function (angular, $, _) { elem.append($link); - $scope.$watchCollection('panel.links', function(newValue) { + $scope.$watchCollection('ctrl.panel.links', function(newValue) { var showIcon = (newValue ? newValue.length > 0 : false) && ctrl.panel.title !== ''; $panelLinksBtn.toggle(showIcon); }); diff --git a/public/app/features/panel/panel_meta3.ts b/public/app/features/panel/panel_meta3.ts new file mode 100644 index 00000000000..056a75d5ee3 --- /dev/null +++ b/public/app/features/panel/panel_meta3.ts @@ -0,0 +1,56 @@ +/// + +import config from 'app/core/config'; + +function panelOptionsTab() { + return {templateUrl: 'app/partials/panelgeneral.html'}; +} + +export default class PanelMeta { + description: any; + icon: any; + name: any; + menu: any; + editorTabs: any; + extendedMenu: any; + + constructor(panel) { + let panelInfo = config.panels[panel.type]; + console.log(panelInfo); + + this.icon = panelInfo.icon; + this.name = panelInfo.name; + this.menu = []; + this.editorTabs = []; + this.extendedMenu = []; + + if (panelInfo.fullscreen) { + this.addMenuItem('View', 'icon-eye-open', 'ctrl.viewPanel(); dismiss();'); + } + + this.addMenuItem('Edit', 'icon-cog', 'ctrl.editPanel(); dismiss();', 'Editor'); + this.addMenuItem('Duplicate', 'icon-copy', 'ctrl.duplicate()', 'Editor'); + this.addMenuItem('Share', 'icon-share', 'ctrl.share(); dismiss();'); + + this.addEditorTab('General', panelOptionsTab); + + if (panelInfo.metricsEditor) { + this.addEditorTab('Metrics', 'app/partials/metrics.html'); + } + + this.addExtendedMenuItem('Panel JSON', '', 'ctrl.editPanelJson(); dismiss();'); + } + + addMenuItem (text, icon, click, role?) { + this.menu.push({text: text, icon: icon, click: click, role: role}); + } + + addExtendedMenuItem (text, icon, click, role?) { + this.extendedMenu.push({text: text, icon: icon, click: click, role: role}); + } + + addEditorTab(title, directiveFn) { + this.editorTabs.push({title: title, directiveFn: directiveFn}); + } +} + diff --git a/public/app/features/panel/partials/panel.html b/public/app/features/panel/partials/panel.html index 3b325366826..c3fb0090241 100644 --- a/public/app/features/panel/partials/panel.html +++ b/public/app/features/panel/partials/panel.html @@ -23,12 +23,12 @@
- - {{ctrl.panelMeta.panelName}} + + {{ctrl.meta.name}}
-
-
+
+
@@ -38,8 +38,8 @@
-
-
+
+
diff --git a/public/app/partials/panelgeneral.html b/public/app/partials/panelgeneral.html index 09ccbe86dfa..d0e41e5180f 100644 --- a/public/app/partials/panelgeneral.html +++ b/public/app/partials/panelgeneral.html @@ -7,23 +7,23 @@ Title
  • - +
  • Span
  • - +
  • Height
  • - +
  • - +
  • @@ -38,7 +38,7 @@ Repeat Panel
  • -
  • @@ -46,7 +46,7 @@ Min span
  • -
  • @@ -56,6 +56,6 @@
    - + diff --git a/public/app/plugins/panel/test/module.ts b/public/app/plugins/panel/test/module.ts index ae9af416ed0..da1682544a4 100644 --- a/public/app/plugins/panel/test/module.ts +++ b/public/app/plugins/panel/test/module.ts @@ -1,6 +1,6 @@ /// -import {PanelCtrl} from '../../../features/panel/panel_ctrl'; +import {PanelDirective, PanelCtrl} from '../../../features/panel/panel'; class TestPanelCtrl extends PanelCtrl { constructor($scope) { @@ -8,15 +8,23 @@ class TestPanelCtrl extends PanelCtrl { } } -var panel = { - templateUrl: `app/plugins/panel/test/module.html`, - controller: TestPanelCtrl, - link: function(scope, elem) { - console.log('panel link'); + +class TestPanel extends PanelDirective { + templateUrl = `app/plugins/panel/test/module.html`; + controller = TestPanelCtrl; + + constructor($http) { + super(); + console.log('panel ctor: ', $http); } -}; + + link(scope) { + console.log('panel link: ', scope.ctrl.panel.id); + } +} export { TestPanelCtrl, - panel, + // testPanelDirective as panel, + TestPanel as Panel, } diff --git a/public/app/plugins/panel/test/plugin.json b/public/app/plugins/panel/test/plugin.json index b2d7b9b42c6..08fb883af76 100644 --- a/public/app/plugins/panel/test/plugin.json +++ b/public/app/plugins/panel/test/plugin.json @@ -1,5 +1,18 @@ { "type": "panel", "name": "Test", - "id": "test" + "id": "test", + + "info": { + "description": "Test panel", + "author": { + "name": "Core Grafana Team.", + "url": "http://grafana.org" + }, + "logos": { + "icon": "fa fa-fw th-large", + "small": "img/logo_small.png", + "large": "img/logo_large.png" + } + } } diff --git a/public/app/plugins/panel/unknown/module.ts b/public/app/plugins/panel/unknown/module.ts index 8d1ce6c1f9a..4f729649395 100644 --- a/public/app/plugins/panel/unknown/module.ts +++ b/public/app/plugins/panel/unknown/module.ts @@ -1,15 +1,11 @@ /// -export function unknownPanelDirective() { - return { - restrict: 'E', - template: ` - -
    - Unknown panel type: {{panel.type}} -
    -
    - `, - }; +import {PanelDirective} from '../../../features/panel/panel'; + +export class UnknownPanel extends PanelDirective { + template = `
    + Unknown panel type: {{ctrl.panel.type}} +
    `; } + From 4132cf12e3c8743e9df41dbe1dc5c8db70802097 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Sun, 24 Jan 2016 17:30:29 -0500 Subject: [PATCH 10/91] feat(panels): more panel refactoring --- public/app/features/dashboard/viewStateSrv.js | 6 +- public/app/features/panel/panel.ts | 37 ++++++++++-- public/app/features/panel/panel_menu.js | 6 +- public/app/features/panel/panel_meta3.ts | 56 ------------------- public/app/features/panel/partials/panel.html | 16 +++--- public/app/plugins/panel/test/module.ts | 9 --- 6 files changed, 48 insertions(+), 82 deletions(-) delete mode 100644 public/app/features/panel/panel_meta3.ts diff --git a/public/app/features/dashboard/viewStateSrv.js b/public/app/features/dashboard/viewStateSrv.js index 63bb999e3ca..c523cd709d7 100644 --- a/public/app/features/dashboard/viewStateSrv.js +++ b/public/app/features/dashboard/viewStateSrv.js @@ -167,7 +167,11 @@ function (angular, _, $) { self.panelScopes.push(panelScope); if (self.state.panelId === panelScope.ctrl.panel.id) { - self.enterFullscreen(panelScope); + if (self.state.edit) { + panelScope.ctrl.editPanel(); + } else { + panelScope.ctrl.viewPanel(); + } } panelScope.$on('$destroy', function() { diff --git a/public/app/features/panel/panel.ts b/public/app/features/panel/panel.ts index a3bf35db9a4..550f453260b 100644 --- a/public/app/features/panel/panel.ts +++ b/public/app/features/panel/panel.ts @@ -1,17 +1,26 @@ /// -import PanelMeta from './panel_meta3'; +import config from 'app/core/config'; + +function generalOptionsTabEditorTab() { + return {templateUrl: 'public/app/partials/panelgeneral.html'}; +} export class PanelCtrl { - meta: any; panel: any; row: any; dashboard: any; - tabIndex: number; + editorTabIndex: number; + name: string; + icon: string; + editorTabs: any; constructor(private scope) { - this.meta = new PanelMeta(this.panel); - this.tabIndex = 0; + var plugin = config.panels[this.panel.type]; + + this.name = plugin.name; + this.icon = plugin.info.icon; + this.editorTabIndex = 0; this.publishAppEvent('panel-instantiated', {scope: scope}); } @@ -30,12 +39,30 @@ export class PanelCtrl { } editPanel() { + if (!this.editorTabs) { + this.initEditorTabs(); + } + this.changeView(true, true); } exitFullscreen() { this.changeView(false, false); } + + initEditorTabs() { + this.editorTabs = []; + this.editorTabs.push({title: 'General', directiveFn: generalOptionsTabEditorTab}); + } + + getMenu() { + let menu = []; + menu.push({text: 'View', click: 'ctrl.viewPanel(); dismiss();'}); + menu.push({text: 'Edit', click: 'ctrl.editPanel(); dismiss();', role: 'Editor'}); + menu.push({text: 'Duplicate', click: 'ctrl.duplicate()', role: 'Editor' }); + menu.push({text: 'Share', click: 'ctrl.share(); dismiss();'}); + return menu; + } } export class PanelDirective { diff --git a/public/app/features/panel/panel_menu.js b/public/app/features/panel/panel_menu.js index 16ee311ff52..5ec5129dc38 100644 --- a/public/app/features/panel/panel_menu.js +++ b/public/app/features/panel/panel_menu.js @@ -13,7 +13,7 @@ function (angular, $, _) { '' + '{{ctrl.panel.title | interpolateTemplateVars:this}}' + '' + - ' {{ctrl.panelMeta.timeInfo}}' + + ' {{ctrl.timeInfo}}' + ''; function createExternalLinkMenu(ctrl) { @@ -44,7 +44,7 @@ function (angular, $, _) { template += '
    '; template += ''; - _.each(ctrl.meta.menu, function(item) { + _.each(ctrl.getMenu(), function(item) { // skip edit actions if not editor if (item.role === 'Editor' && !ctrl.dashboard.meta.canEdit) { return; @@ -64,7 +64,7 @@ function (angular, $, _) { } function getExtendedMenu(ctrl) { - return angular.copy(ctrl.meta.extendedMenu); + return angular.copy(ctrl.extendedMenu); } return { diff --git a/public/app/features/panel/panel_meta3.ts b/public/app/features/panel/panel_meta3.ts deleted file mode 100644 index 056a75d5ee3..00000000000 --- a/public/app/features/panel/panel_meta3.ts +++ /dev/null @@ -1,56 +0,0 @@ -/// - -import config from 'app/core/config'; - -function panelOptionsTab() { - return {templateUrl: 'app/partials/panelgeneral.html'}; -} - -export default class PanelMeta { - description: any; - icon: any; - name: any; - menu: any; - editorTabs: any; - extendedMenu: any; - - constructor(panel) { - let panelInfo = config.panels[panel.type]; - console.log(panelInfo); - - this.icon = panelInfo.icon; - this.name = panelInfo.name; - this.menu = []; - this.editorTabs = []; - this.extendedMenu = []; - - if (panelInfo.fullscreen) { - this.addMenuItem('View', 'icon-eye-open', 'ctrl.viewPanel(); dismiss();'); - } - - this.addMenuItem('Edit', 'icon-cog', 'ctrl.editPanel(); dismiss();', 'Editor'); - this.addMenuItem('Duplicate', 'icon-copy', 'ctrl.duplicate()', 'Editor'); - this.addMenuItem('Share', 'icon-share', 'ctrl.share(); dismiss();'); - - this.addEditorTab('General', panelOptionsTab); - - if (panelInfo.metricsEditor) { - this.addEditorTab('Metrics', 'app/partials/metrics.html'); - } - - this.addExtendedMenuItem('Panel JSON', '', 'ctrl.editPanelJson(); dismiss();'); - } - - addMenuItem (text, icon, click, role?) { - this.menu.push({text: text, icon: icon, click: click, role: role}); - } - - addExtendedMenuItem (text, icon, click, role?) { - this.extendedMenu.push({text: text, icon: icon, click: click, role: role}); - } - - addEditorTab(title, directiveFn) { - this.editorTabs.push({title: title, directiveFn: directiveFn}); - } -} - diff --git a/public/app/features/panel/partials/panel.html b/public/app/features/panel/partials/panel.html index c3fb0090241..bf946d16fc3 100644 --- a/public/app/features/panel/partials/panel.html +++ b/public/app/features/panel/partials/panel.html @@ -1,12 +1,12 @@
    - - + + - + @@ -23,12 +23,12 @@
    - - {{ctrl.meta.name}} + + {{ctrl.name}}
    -
    -
    +
    +
    @@ -38,7 +38,7 @@
    -
    +
    diff --git a/public/app/plugins/panel/test/module.ts b/public/app/plugins/panel/test/module.ts index da1682544a4..47eab73f936 100644 --- a/public/app/plugins/panel/test/module.ts +++ b/public/app/plugins/panel/test/module.ts @@ -12,15 +12,6 @@ class TestPanelCtrl extends PanelCtrl { class TestPanel extends PanelDirective { templateUrl = `app/plugins/panel/test/module.html`; controller = TestPanelCtrl; - - constructor($http) { - super(); - console.log('panel ctor: ', $http); - } - - link(scope) { - console.log('panel link: ', scope.ctrl.panel.id); - } } export { From a950ff9795482683f805127bd8e9c7d06da73e0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Sun, 24 Jan 2016 17:58:08 -0500 Subject: [PATCH 11/91] feat(panel): more panel base infrastructure --- public/app/features/panel/panel.ts | 15 ++++++++++++--- public/app/plugins/panel/test/module.ts | 19 ++++++++++++------- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/public/app/features/panel/panel.ts b/public/app/features/panel/panel.ts index 550f453260b..bff0d68955b 100644 --- a/public/app/features/panel/panel.ts +++ b/public/app/features/panel/panel.ts @@ -14,18 +14,20 @@ export class PanelCtrl { name: string; icon: string; editorTabs: any; + $scope: any; - constructor(private scope) { + constructor($scope) { var plugin = config.panels[this.panel.type]; + this.$scope = $scope; this.name = plugin.name; this.icon = plugin.info.icon; this.editorTabIndex = 0; - this.publishAppEvent('panel-instantiated', {scope: scope}); + this.publishAppEvent('panel-instantiated', {scope: $scope}); } publishAppEvent(evtName, evt) { - this.scope.$root.appEvent(evtName, evt); + this.$scope.$root.appEvent(evtName, evt); } changeView(fullscreen, edit) { @@ -53,8 +55,11 @@ export class PanelCtrl { initEditorTabs() { this.editorTabs = []; this.editorTabs.push({title: 'General', directiveFn: generalOptionsTabEditorTab}); + this.editorTabs = this.editorTabs.concat(this.getEditorTabs()); } + getEditorTabs() { return [];} + getMenu() { let menu = []; menu.push({text: 'View', click: 'ctrl.viewPanel(); dismiss();'}); @@ -74,6 +79,10 @@ export class PanelDirective { controllerAs: string; getDirective() { + if (!this.controller) { + this.controller = PanelCtrl; + } + return { template: this.template, templateUrl: this.templateUrl, diff --git a/public/app/plugins/panel/test/module.ts b/public/app/plugins/panel/test/module.ts index 47eab73f936..eed9fdc021d 100644 --- a/public/app/plugins/panel/test/module.ts +++ b/public/app/plugins/panel/test/module.ts @@ -2,20 +2,25 @@ import {PanelDirective, PanelCtrl} from '../../../features/panel/panel'; -class TestPanelCtrl extends PanelCtrl { +function optionsTab() { + return { + template: '

    options!

    ' + }; +} + +export class TestPanelCtrl extends PanelCtrl { constructor($scope) { super($scope); } -} + getEditorTabs() { + return [{title: 'Options', directiveFn: optionsTab}]; + } +} class TestPanel extends PanelDirective { templateUrl = `app/plugins/panel/test/module.html`; controller = TestPanelCtrl; } -export { - TestPanelCtrl, - // testPanelDirective as panel, - TestPanel as Panel, -} +export {TestPanel as Panel} From 3e14f8a0e54b942fde51af9cf3387bd814572834 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Sun, 24 Jan 2016 18:44:21 -0500 Subject: [PATCH 12/91] feat(): panel refactoring --- .../app/features/panel/metrics_panel_ctrl.ts | 22 ++++++ public/app/features/panel/panel.ts | 76 ++----------------- public/app/features/panel/panel_ctrl.ts | 69 +++++++++++++++++ public/app/plugins/panel/test/module.ts | 13 ++-- 4 files changed, 105 insertions(+), 75 deletions(-) create mode 100644 public/app/features/panel/metrics_panel_ctrl.ts create mode 100644 public/app/features/panel/panel_ctrl.ts diff --git a/public/app/features/panel/metrics_panel_ctrl.ts b/public/app/features/panel/metrics_panel_ctrl.ts new file mode 100644 index 00000000000..968bcea52b6 --- /dev/null +++ b/public/app/features/panel/metrics_panel_ctrl.ts @@ -0,0 +1,22 @@ +/// + +import config from 'app/core/config'; +import {PanelCtrl} from './panel_ctrl'; + +function metricsEditorTab() { + return {templateUrl: 'public/app/partials/metrics.html'}; +} + +class MetricsPanelCtrl extends PanelCtrl { + constructor($scope) { + super($scope); + } + + initEditorTabs() { + super.initEditorTabs(); + this.editorTabs.push({title: 'Metrics', directiveFn: metricsEditorTab}); + } +} + +export {MetricsPanelCtrl}; + diff --git a/public/app/features/panel/panel.ts b/public/app/features/panel/panel.ts index bff0d68955b..08ede83eb95 100644 --- a/public/app/features/panel/panel.ts +++ b/public/app/features/panel/panel.ts @@ -2,75 +2,10 @@ import config from 'app/core/config'; -function generalOptionsTabEditorTab() { - return {templateUrl: 'public/app/partials/panelgeneral.html'}; -} +import {PanelCtrl} from './panel_ctrl'; +import {MetricsPanelCtrl} from './metrics_panel_ctrl'; -export class PanelCtrl { - panel: any; - row: any; - dashboard: any; - editorTabIndex: number; - name: string; - icon: string; - editorTabs: any; - $scope: any; - - constructor($scope) { - var plugin = config.panels[this.panel.type]; - - this.$scope = $scope; - this.name = plugin.name; - this.icon = plugin.info.icon; - this.editorTabIndex = 0; - this.publishAppEvent('panel-instantiated', {scope: $scope}); - } - - publishAppEvent(evtName, evt) { - this.$scope.$root.appEvent(evtName, evt); - } - - changeView(fullscreen, edit) { - this.publishAppEvent('panel-change-view', { - fullscreen: fullscreen, edit: edit, panelId: this.panel.id - }); - } - - viewPanel() { - this.changeView(true, false); - } - - editPanel() { - if (!this.editorTabs) { - this.initEditorTabs(); - } - - this.changeView(true, true); - } - - exitFullscreen() { - this.changeView(false, false); - } - - initEditorTabs() { - this.editorTabs = []; - this.editorTabs.push({title: 'General', directiveFn: generalOptionsTabEditorTab}); - this.editorTabs = this.editorTabs.concat(this.getEditorTabs()); - } - - getEditorTabs() { return [];} - - getMenu() { - let menu = []; - menu.push({text: 'View', click: 'ctrl.viewPanel(); dismiss();'}); - menu.push({text: 'Edit', click: 'ctrl.editPanel(); dismiss();', role: 'Editor'}); - menu.push({text: 'Duplicate', click: 'ctrl.duplicate()', role: 'Editor' }); - menu.push({text: 'Share', click: 'ctrl.share(); dismiss();'}); - return menu; - } -} - -export class PanelDirective { +class PanelDirective { template: string; templateUrl: string; bindToController: boolean; @@ -99,3 +34,8 @@ export class PanelDirective { } } +export { + PanelCtrl, + MetricsPanelCtrl, + PanelDirective, +} diff --git a/public/app/features/panel/panel_ctrl.ts b/public/app/features/panel/panel_ctrl.ts new file mode 100644 index 00000000000..b6b6e07ed86 --- /dev/null +++ b/public/app/features/panel/panel_ctrl.ts @@ -0,0 +1,69 @@ +/// + +import config from 'app/core/config'; + +function generalOptionsTabEditorTab() { + return {templateUrl: 'public/app/partials/panelgeneral.html'}; +} + +export class PanelCtrl { + panel: any; + row: any; + dashboard: any; + editorTabIndex: number; + name: string; + icon: string; + editorTabs: any; + $scope: any; + isMetricsPanel: boolean; + + constructor($scope) { + var plugin = config.panels[this.panel.type]; + + this.$scope = $scope; + this.name = plugin.name; + this.icon = plugin.info.icon; + this.editorTabIndex = 0; + this.publishAppEvent('panel-instantiated', {scope: $scope}); + } + + publishAppEvent(evtName, evt) { + this.$scope.$root.appEvent(evtName, evt); + } + + changeView(fullscreen, edit) { + this.publishAppEvent('panel-change-view', { + fullscreen: fullscreen, edit: edit, panelId: this.panel.id + }); + } + + viewPanel() { + this.changeView(true, false); + } + + editPanel() { + if (!this.editorTabs) { + this.initEditorTabs(); + } + + this.changeView(true, true); + } + + exitFullscreen() { + this.changeView(false, false); + } + + initEditorTabs() { + this.editorTabs = []; + this.editorTabs.push({title: 'General', directiveFn: generalOptionsTabEditorTab}); + } + + getMenu() { + let menu = []; + menu.push({text: 'View', click: 'ctrl.viewPanel(); dismiss();'}); + menu.push({text: 'Edit', click: 'ctrl.editPanel(); dismiss();', role: 'Editor'}); + menu.push({text: 'Duplicate', click: 'ctrl.duplicate()', role: 'Editor' }); + menu.push({text: 'Share', click: 'ctrl.share(); dismiss();'}); + return menu; + } +} diff --git a/public/app/plugins/panel/test/module.ts b/public/app/plugins/panel/test/module.ts index eed9fdc021d..0868002c2dd 100644 --- a/public/app/plugins/panel/test/module.ts +++ b/public/app/plugins/panel/test/module.ts @@ -1,20 +1,19 @@ /// -import {PanelDirective, PanelCtrl} from '../../../features/panel/panel'; +import {PanelDirective, MetricsPanelCtrl} from '../../../features/panel/panel'; function optionsTab() { - return { - template: '

    options!

    ' - }; + return {template: '

    options!

    ' }; } -export class TestPanelCtrl extends PanelCtrl { +export class TestPanelCtrl extends MetricsPanelCtrl { constructor($scope) { super($scope); } - getEditorTabs() { - return [{title: 'Options', directiveFn: optionsTab}]; + initEditorTabs() { + super.initEditorTabs(); + this.editorTabs.push({title: 'Options', directiveFn: optionsTab}); } } From 8f4cf6c797c716e7e22090f82f2792ab313322af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 25 Jan 2016 09:56:17 -0500 Subject: [PATCH 13/91] feat(panels): updated text panel to new format --- public/app/plugins/panel/text/editor.html | 10 +- public/app/plugins/panel/text/module.html | 4 +- public/app/plugins/panel/text/module.js | 113 ---------------------- public/app/plugins/panel/text/module.ts | 92 ++++++++++++++++++ 4 files changed, 98 insertions(+), 121 deletions(-) delete mode 100644 public/app/plugins/panel/text/module.js create mode 100644 public/app/plugins/panel/text/module.ts diff --git a/public/app/plugins/panel/text/editor.html b/public/app/plugins/panel/text/editor.html index b3b8afbbec0..ea281d61a30 100644 --- a/public/app/plugins/panel/text/editor.html +++ b/public/app/plugins/panel/text/editor.html @@ -1,17 +1,17 @@
    - +
    -
    - +
    +
    -
    diff --git a/public/app/plugins/panel/text/module.html b/public/app/plugins/panel/text/module.html index 5a933d5acc3..12a02597384 100644 --- a/public/app/plugins/panel/text/module.html +++ b/public/app/plugins/panel/text/module.html @@ -1,3 +1 @@ - -

    -
    +

    diff --git a/public/app/plugins/panel/text/module.js b/public/app/plugins/panel/text/module.js deleted file mode 100644 index e0e21c8e1ae..00000000000 --- a/public/app/plugins/panel/text/module.js +++ /dev/null @@ -1,113 +0,0 @@ -define([ - 'angular', - 'app/app', - 'lodash', - 'require', - 'app/features/panel/panel_meta', -], -function (angular, app, _, require, PanelMeta) { - 'use strict'; - - var converter; - - /** @ngInject */ - function TextPanelCtrl($scope, templateSrv, $sce, panelSrv) { - - $scope.panelMeta = new PanelMeta({ - panelName: 'Text', - editIcon: "fa fa-text-width", - fullscreen: true, - }); - - $scope.panelMeta.addEditorTab('Edit text', 'app/plugins/panel/text/editor.html'); - - // Set and populate defaults - var _d = { - title : 'default title', - mode : "markdown", // 'html', 'markdown', 'text' - content : "", - style: {}, - }; - - _.defaults($scope.panel, _d); - - $scope.init = function() { - panelSrv.init($scope); - $scope.ready = false; - $scope.render(); - }; - - $scope.refreshData = function() { - $scope.panelMeta.loading = false; - $scope.render(); - }; - - $scope.render = function() { - if ($scope.panel.mode === 'markdown') { - $scope.renderMarkdown($scope.panel.content); - } - else if ($scope.panel.mode === 'html') { - $scope.updateContent($scope.panel.content); - } - else if ($scope.panel.mode === 'text') { - $scope.renderText($scope.panel.content); - } - $scope.panelRenderingComplete(); - }; - - $scope.renderText = function(content) { - content = content - .replace(/&/g, '&') - .replace(/>/g, '>') - .replace(/'); - - $scope.updateContent(content); - }; - - $scope.renderMarkdown = function(content) { - var text = content - .replace(/&/g, '&') - .replace(/>/g, '>') - .replace(/ + +import _ from 'lodash'; +import {PanelDirective, PanelCtrl} from '../../../features/panel/panel'; + +function optionsEditorTab() { + return {templateUrl: 'public/app/plugins/panel/text/editor.html'}; +} + + // Set and populate defaults +var panelDefaults = { + mode : "markdown", // 'html', 'markdown', 'text' + content : "# title", +}; + +export class TextPanelCtrl extends PanelCtrl { + converter: any; + content: string; + + /** @ngInject */ + constructor($scope, private templateSrv, private $sce) { + super($scope); + + _.defaults(this.panel, panelDefaults); + this.render(); + } + + initEditorTabs() { + super.initEditorTabs(); + this.editorTabs.push({title: 'Options', directiveFn: optionsEditorTab}); + } + + render() { + if (this.panel.mode === 'markdown') { + this.renderMarkdown(this.panel.content); + } else if (this.panel.mode === 'html') { + this.updateContent(this.panel.content); + } else if (this.panel.mode === 'text') { + this.renderText(this.panel.content); + } + // this.panelRenderingComplete(); + } + + refreshData() { + this.render(); + } + + renderText(content) { + content = content + .replace(/&/g, '&') + .replace(/>/g, '>') + .replace(/'); + this.updateContent(content); + } + + renderMarkdown(content) { + var text = content + .replace(/&/g, '&') + .replace(/>/g, '>') + .replace(/ { + this.converter = new Showdown.converter(); + this.updateContent(this.converter.makeHtml(text)); + }); + } + } + + updateContent(html) { + try { + this.content = this.$sce.trustAsHtml(this.templateSrv.replace(html, this.panel.scopedVars)); + } catch (e) { + console.log('Text panel error: ', e); + this.content = this.$sce.trustAsHtml(html); + } + + if (!this.$scope.$$phase) { + this.$scope.$digest(); + } + } +} + +class TextPanel extends PanelDirective { + templateUrl = `app/plugins/panel/text/module.html`; + controller = TextPanelCtrl; +} + +export {TextPanel as Panel} From 73af4df96d648f24a5cae0a3e92f9b4df368b547 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 25 Jan 2016 11:41:20 -0500 Subject: [PATCH 14/91] feat(panels): upgraded text panel --- public/app/features/panel/panel_ctrl.ts | 15 +++++++++++++-- public/app/plugins/panel/text/module.ts | 19 +++++++------------ 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/public/app/features/panel/panel_ctrl.ts b/public/app/features/panel/panel_ctrl.ts index b6b6e07ed86..0fb94e660d5 100644 --- a/public/app/features/panel/panel_ctrl.ts +++ b/public/app/features/panel/panel_ctrl.ts @@ -25,6 +25,12 @@ export class PanelCtrl { this.icon = plugin.info.icon; this.editorTabIndex = 0; this.publishAppEvent('panel-instantiated', {scope: $scope}); + + $scope.$on("refresh", () => this.refresh()); + } + + refresh() { + return; } publishAppEvent(evtName, evt) { @@ -43,6 +49,8 @@ export class PanelCtrl { editPanel() { if (!this.editorTabs) { + this.editorTabs = []; + this.editorTabs.push({title: 'General', directiveFn: generalOptionsTabEditorTab}); this.initEditorTabs(); } @@ -54,8 +62,11 @@ export class PanelCtrl { } initEditorTabs() { - this.editorTabs = []; - this.editorTabs.push({title: 'General', directiveFn: generalOptionsTabEditorTab}); + return; + } + + addEditorTab(title, directiveFn) { + this.editorTabs.push({title: title, directiveFn: directiveFn}); } getMenu() { diff --git a/public/app/plugins/panel/text/module.ts b/public/app/plugins/panel/text/module.ts index 3697533c1c2..837ccfe713f 100644 --- a/public/app/plugins/panel/text/module.ts +++ b/public/app/plugins/panel/text/module.ts @@ -3,10 +3,6 @@ import _ from 'lodash'; import {PanelDirective, PanelCtrl} from '../../../features/panel/panel'; -function optionsEditorTab() { - return {templateUrl: 'public/app/plugins/panel/text/editor.html'}; -} - // Set and populate defaults var panelDefaults = { mode : "markdown", // 'html', 'markdown', 'text' @@ -26,8 +22,9 @@ export class TextPanelCtrl extends PanelCtrl { } initEditorTabs() { - super.initEditorTabs(); - this.editorTabs.push({title: 'Options', directiveFn: optionsEditorTab}); + this.addEditorTab('Options', () => { + return { templateUrl: 'public/app/plugins/panel/text/editor.html' }; + }); } render() { @@ -41,7 +38,7 @@ export class TextPanelCtrl extends PanelCtrl { // this.panelRenderingComplete(); } - refreshData() { + refresh() { this.render(); } @@ -65,7 +62,9 @@ export class TextPanelCtrl extends PanelCtrl { } else { System.import('vendor/showdown').then(Showdown => { this.converter = new Showdown.converter(); - this.updateContent(this.converter.makeHtml(text)); + this.$scope.$apply(() => { + this.updateContent(this.converter.makeHtml(text)); + }); }); } } @@ -77,10 +76,6 @@ export class TextPanelCtrl extends PanelCtrl { console.log('Text panel error: ', e); this.content = this.$sce.trustAsHtml(html); } - - if (!this.$scope.$$phase) { - this.$scope.$digest(); - } } } From 9c6698e87b13de400f876e2dc538565ec1c63d0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 25 Jan 2016 15:09:37 -0500 Subject: [PATCH 15/91] feat(panels): progress on new panel infrastructure, base classes --- public/app/features/dashboard/viewStateSrv.js | 7 ++- .../app/features/panel/metrics_panel_ctrl.ts | 60 ++++++++++++++++--- public/app/features/panel/panel.ts | 2 +- public/app/features/panel/panel_ctrl.ts | 7 ++- public/app/features/panel/panel_editor_tab.ts | 2 +- public/app/features/panel/partials/panel.html | 4 +- public/app/features/panel/query_editor.ts | 10 ++-- public/app/partials/dashboard.html | 6 +- public/app/partials/metrics.html | 16 ++--- public/app/partials/panelgeneral.html | 14 ++--- .../app/plugins/datasource/graphite/module.js | 5 +- .../graphite/partials/query.editor.html | 4 +- .../plugins/datasource/graphite/query_ctrl.js | 9 +-- public/app/plugins/panel/test/module.ts | 15 ++++- public/app/plugins/panel/text/editor.html | 10 ++-- 15 files changed, 118 insertions(+), 53 deletions(-) diff --git a/public/app/features/dashboard/viewStateSrv.js b/public/app/features/dashboard/viewStateSrv.js index c523cd709d7..abe1ec3279e 100644 --- a/public/app/features/dashboard/viewStateSrv.js +++ b/public/app/features/dashboard/viewStateSrv.js @@ -17,6 +17,7 @@ function (angular, _, $) { self.state = {}; self.panelScopes = []; self.$scope = $scope; + self.dashboard = $scope.dashboard; $scope.exitFullscreen = function() { if (self.state.fullscreen) { @@ -74,7 +75,7 @@ function (angular, _, $) { DashboardViewState.prototype.update = function(state, skipUrlSync) { _.extend(this.state, state); - this.fullscreen = this.state.fullscreen; + this.dashboard.meta.fullscreen = this.state.fullscreen; if (!this.state.fullscreen) { this.state.panelId = null; @@ -92,7 +93,7 @@ function (angular, _, $) { DashboardViewState.prototype.syncState = function() { if (this.panelScopes.length === 0) { return; } - if (this.fullscreen) { + if (this.dashboard.meta.fullscreen) { if (this.fullscreenPanel) { this.leaveFullscreen(false); } @@ -148,13 +149,13 @@ function (angular, _, $) { ctrl.editMode = this.state.edit && this.$scope.dashboardMeta.canEdit; ctrl.height = ctrl.editMode ? editHeight : fullscreenHeight; + ctrl.fullscreen = true; this.oldTimeRange = ctrl.range; this.fullscreenPanel = panelScope; $(window).scrollTop(0); - panelScope.fullscreen = true; this.$scope.appEvent('panel-fullscreen-enter', {panelId: ctrl.panel.id}); $timeout(function() { diff --git a/public/app/features/panel/metrics_panel_ctrl.ts b/public/app/features/panel/metrics_panel_ctrl.ts index 968bcea52b6..2ce1dfa847f 100644 --- a/public/app/features/panel/metrics_panel_ctrl.ts +++ b/public/app/features/panel/metrics_panel_ctrl.ts @@ -3,18 +3,64 @@ import config from 'app/core/config'; import {PanelCtrl} from './panel_ctrl'; -function metricsEditorTab() { - return {templateUrl: 'public/app/partials/metrics.html'}; -} - class MetricsPanelCtrl extends PanelCtrl { - constructor($scope) { + error: boolean; + loading: boolean; + datasource: any; + + constructor($scope, private $q, private datasourceSrv) { super($scope); + this.editorTabIndex = 1; + + if (!this.panel.targets) { + this.panel.targets = [{}]; + } } initEditorTabs() { - super.initEditorTabs(); - this.editorTabs.push({title: 'Metrics', directiveFn: metricsEditorTab}); + this.addEditorTab('Metrics', () => { + return { templateUrl: 'public/app/partials/metrics.html' }; + }); + } + + refresh() { + this.getData(); + } + + refreshData(data) { + // null op + return data; + } + + loadSnapshot(data) { + // null op + return data; + } + + getData() { + if (this.otherPanelInFullscreenMode()) { return; } + + if (this.panel.snapshotData) { + if (this.loadSnapshot) { + this.loadSnapshot(this.panel.snapshotData); + } + return; + } + + delete this.error; + this.loading = true; + + this.datasourceSrv.get(this.panel.datasource).then(datasource => { + this.datasource = datasource; + return this.refreshData(this.datasource) || this.$q.when({}); + }).then(() => { + this.loading = false; + }, err => { + console.log('Panel data error:', err); + this.loading = false; + this.error = err.message || "Timeseries data request error"; + this.inspector = {error: err}; + }); } } diff --git a/public/app/features/panel/panel.ts b/public/app/features/panel/panel.ts index 08ede83eb95..4d80bb9dade 100644 --- a/public/app/features/panel/panel.ts +++ b/public/app/features/panel/panel.ts @@ -29,7 +29,7 @@ class PanelDirective { }; } - link(scope) { + link(scope, elem) { return null; } } diff --git a/public/app/features/panel/panel_ctrl.ts b/public/app/features/panel/panel_ctrl.ts index 0fb94e660d5..9bceaa482c5 100644 --- a/public/app/features/panel/panel_ctrl.ts +++ b/public/app/features/panel/panel_ctrl.ts @@ -15,7 +15,8 @@ export class PanelCtrl { icon: string; editorTabs: any; $scope: any; - isMetricsPanel: boolean; + fullscreen: boolean; + inspector: any; constructor($scope) { var plugin = config.panels[this.panel.type]; @@ -77,4 +78,8 @@ export class PanelCtrl { menu.push({text: 'Share', click: 'ctrl.share(); dismiss();'}); return menu; } + + otherPanelInFullscreenMode() { + return this.dashboard.meta.fullscreen && !this.fullscreen; + } } diff --git a/public/app/features/panel/panel_editor_tab.ts b/public/app/features/panel/panel_editor_tab.ts index 56e3078962e..68b1e67c01c 100644 --- a/public/app/features/panel/panel_editor_tab.ts +++ b/public/app/features/panel/panel_editor_tab.ts @@ -9,7 +9,7 @@ var directiveModule = angular.module('grafana.directives'); function panelEditorTab(dynamicDirectiveSrv) { return dynamicDirectiveSrv.create({ scope: { - panelCtrl: "=", + ctrl: "=", editorTab: "=", }, directive: scope => { diff --git a/public/app/features/panel/partials/panel.html b/public/app/features/panel/partials/panel.html index bf946d16fc3..55ed28eafc4 100644 --- a/public/app/features/panel/partials/panel.html +++ b/public/app/features/panel/partials/panel.html @@ -1,4 +1,4 @@ -
    +
    @@ -39,7 +39,7 @@
    - +
    diff --git a/public/app/features/panel/query_editor.ts b/public/app/features/panel/query_editor.ts index 01beceaee99..5b493362762 100644 --- a/public/app/features/panel/query_editor.ts +++ b/public/app/features/panel/query_editor.ts @@ -5,11 +5,11 @@ import angular from 'angular'; /** @ngInject */ function metricsQueryEditor(dynamicDirectiveSrv, datasourceSrv) { return dynamicDirectiveSrv.create({ - watchPath: "panel.datasource", + watchPath: "ctrl.panel.datasource", directive: scope => { - let datasource = scope.target.datasource || scope.panel.datasource; + let datasource = scope.target.datasource || scope.ctrl.panel.datasource; return datasourceSrv.get(datasource).then(ds => { - scope.datasource = ds; + scope.ctrl.datasource = ds; if (!scope.target.refId) { scope.target.refId = 'A'; @@ -29,9 +29,9 @@ function metricsQueryEditor(dynamicDirectiveSrv, datasourceSrv) { /** @ngInject */ function metricsQueryOptions(dynamicDirectiveSrv, datasourceSrv) { return dynamicDirectiveSrv.create({ - watchPath: "panel.datasource", + watchPath: "ctrl.panel.datasource", directive: scope => { - return datasourceSrv.get(scope.panel.datasource).then(ds => { + return datasourceSrv.get(scope.ctrl.panel.datasource).then(ds => { return System.import(ds.meta.module).then(dsModule => { return { name: 'metrics-query-options-' + ds.meta.id, diff --git a/public/app/partials/dashboard.html b/public/app/partials/dashboard.html index 7aeefb20730..5a0464dc099 100644 --- a/public/app/partials/dashboard.html +++ b/public/app/partials/dashboard.html @@ -23,7 +23,7 @@
    -