From 5bd5713a5210012287b63066c6097b8d44a3726a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 27 Feb 2015 13:45:00 +0100 Subject: [PATCH] Began work on plugin system --- pkg/api/frontendsettings.go | 15 ++-- pkg/cmd/dashboard.go | 2 + pkg/plugins/plugins.go | 81 +++++++++++++++++++ pkg/plugins/plugins_test.go | 19 +++++ src/app/controllers/search.js | 42 +++++----- src/app/features/all.js | 10 +-- src/app/features/panel/panelSrv.js | 4 +- src/app/panels/graph/module.js | 22 ++--- .../plugins/custom.panel.example/editor.html | 0 .../plugins/custom.panel.example/module.html | 0 .../plugins/custom.panel.example/module.js | 0 src/{ => app}/plugins/datasource.example.js | 0 .../plugins/datasources/graphite/module.js | 20 +++++ .../plugins/datasources/graphite/plugin.json | 7 ++ src/app/routes/backend/dashboard.js | 8 +- src/app/services/datasourceSrv.js | 81 ++++++++++--------- 16 files changed, 222 insertions(+), 89 deletions(-) create mode 100644 pkg/plugins/plugins.go create mode 100644 pkg/plugins/plugins_test.go rename src/{ => app}/plugins/custom.panel.example/editor.html (100%) rename src/{ => app}/plugins/custom.panel.example/module.html (100%) rename src/{ => app}/plugins/custom.panel.example/module.js (100%) rename src/{ => app}/plugins/datasource.example.js (100%) create mode 100644 src/app/plugins/datasources/graphite/module.js create mode 100644 src/app/plugins/datasources/graphite/plugin.json diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go index 568155d5d43..e3abe8eebf3 100644 --- a/pkg/api/frontendsettings.go +++ b/pkg/api/frontendsettings.go @@ -24,6 +24,7 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro } datasources := make(map[string]interface{}) + var defaultDatasource string for _, ds := range orgDataSources { url := ds.Url @@ -33,9 +34,12 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro } var dsMap = map[string]interface{}{ - "type": ds.Type, - "url": url, - "default": ds.IsDefault, + "type": ds.Type, + "url": url, + } + + if ds.IsDefault { + defaultDatasource = ds.Name } if ds.Type == m.DS_INFLUXDB_08 { @@ -69,8 +73,9 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro } jsonObj := map[string]interface{}{ - "datasources": datasources, - "appSubUrl": setting.AppSubUrl, + "defaultDatasource": defaultDatasource, + "datasources": datasources, + "appSubUrl": setting.AppSubUrl, "buildInfo": map[string]interface{}{ "version": setting.BuildVersion, "commit": setting.BuildCommit, diff --git a/pkg/cmd/dashboard.go b/pkg/cmd/dashboard.go index 04645fb618e..bd64b5b5ef6 100644 --- a/pkg/cmd/dashboard.go +++ b/pkg/cmd/dashboard.go @@ -83,6 +83,8 @@ func importDashboard(path string, orgId int64) error { return err } + defer reader.Close() + dash := m.NewDashboard("temp") jsonParser := json.NewDecoder(reader) diff --git a/pkg/plugins/plugins.go b/pkg/plugins/plugins.go new file mode 100644 index 00000000000..aab0a0446ca --- /dev/null +++ b/pkg/plugins/plugins.go @@ -0,0 +1,81 @@ +package plugins + +import ( + "encoding/json" + "errors" + "os" + "path/filepath" + + "github.com/grafana/grafana/pkg/log" +) + +type PluginMeta struct { + Type string `json:"type"` + Name string `json:"name"` +} + +var ( + List []*PluginMeta +) + +type PluginScanner struct { + pluginPath string + errors []error +} + +func Scan(pluginDir string) error { + List = make([]*PluginMeta, 0) + + scanner := &PluginScanner{ + pluginPath: pluginDir, + } + + if err := filepath.Walk(pluginDir, scanner.walker); err != nil { + return err + } + + if len(scanner.errors) > 0 { + return errors.New("Some plugins failed to load") + } + + return nil +} + +func (scanner *PluginScanner) walker(path string, f os.FileInfo, err error) error { + if err != nil { + return err + } + + if f.IsDir() { + return nil + } + + if f.Name() == "plugin.json" { + pluginMeta, err := loadPluginMeta(path) + if err != nil { + log.Error(3, "Failed to load plugin json file: %v, err: %v", path, err) + scanner.errors = append(scanner.errors, err) + } else { + List = append(List, pluginMeta) + } + } + return nil +} + +func loadPluginMeta(path string) (*PluginMeta, error) { + reader, err := os.Open(path) + if err != nil { + return nil, err + } + + defer reader.Close() + + jsonParser := json.NewDecoder(reader) + + pluginMeta := &PluginMeta{} + if err := jsonParser.Decode(pluginMeta); err != nil { + return nil, err + } + + return pluginMeta, nil +} diff --git a/pkg/plugins/plugins_test.go b/pkg/plugins/plugins_test.go new file mode 100644 index 00000000000..43e48afdc1d --- /dev/null +++ b/pkg/plugins/plugins_test.go @@ -0,0 +1,19 @@ +package plugins + +import ( + "path/filepath" + "testing" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestPluginScans(t *testing.T) { + + Convey("When scaning for plugins", t, func() { + path, _ := filepath.Abs("../../src/app/plugins") + err := Scan(path) + + So(err, ShouldBeNil) + So(len(List), ShouldEqual, 1) + }) +} diff --git a/src/app/controllers/search.js b/src/app/controllers/search.js index 767b1e5d88b..896ba0f7907 100644 --- a/src/app/controllers/search.js +++ b/src/app/controllers/search.js @@ -8,14 +8,13 @@ function (angular, _, config) { var module = angular.module('grafana.controllers'); - module.controller('SearchCtrl', function($scope, $rootScope, $element, $location, datasourceSrv, $timeout) { + module.controller('SearchCtrl', function($scope, $location, $timeout, backendSrv) { $scope.init = function() { $scope.giveSearchFocus = 0; $scope.selectedIndex = -1; $scope.results = {dashboards: [], tags: [], metrics: []}; $scope.query = { query: '', tag: '', starred: false }; - $scope.db = datasourceSrv.getGrafanaDB(); $scope.currentSearchId = 0; $timeout(function() { @@ -61,21 +60,20 @@ function (angular, _, config) { $scope.currentSearchId = $scope.currentSearchId + 1; var localSearchId = $scope.currentSearchId; - return $scope.db.searchDashboards($scope.query) - .then(function(results) { - if (localSearchId < $scope.currentSearchId) { return; } + return backendSrv.get('/api/search', $scope.query).then(function(results) { + if (localSearchId < $scope.currentSearchId) { return; } - $scope.resultCount = results.tagsOnly ? results.tags.length : results.dashboards.length; - $scope.results.tags = results.tags; - $scope.results.dashboards = _.map(results.dashboards, function(dash) { - dash.url = 'dashboard/db/' + dash.slug; - return dash; - }); - - if ($scope.queryHasNoFilters()) { - $scope.results.dashboards.unshift({ title: 'Home', url: config.appSubUrl + '/', isHome: true }); - } + $scope.resultCount = results.tagsOnly ? results.tags.length : results.dashboards.length; + $scope.results.tags = results.tags; + $scope.results.dashboards = _.map(results.dashboards, function(dash) { + dash.url = 'dashboard/db/' + dash.slug; + return dash; }); + + if ($scope.queryHasNoFilters()) { + $scope.results.dashboards.unshift({ title: 'Home', url: config.appSubUrl + '/', isHome: true }); + } + }); }; $scope.queryHasNoFilters = function() { @@ -118,13 +116,13 @@ function (angular, _, config) { height: '250px', editable: true, panels: [ - { - type: 'graphite', - title: 'test', - span: 12, - targets: [{ target: metricId }] - } - ] + { + type: 'graphite', + title: 'test', + span: 12, + targets: [{ target: metricId }] + } + ] }); }; diff --git a/src/app/features/all.js b/src/app/features/all.js index 6a3294f3487..620f8c1bee1 100644 --- a/src/app/features/all.js +++ b/src/app/features/all.js @@ -2,11 +2,11 @@ define([ './panellinkeditor/module', './annotations/annotationsSrv', './templating/templateSrv', - './graphite/datasource', - './influxdb/datasource', - './influxdb_08/datasource', - './opentsdb/datasource', - './elasticsearch/datasource', +// './graphite/datasource', +// './influxdb/datasource', +// './influxdb_08/datasource', +// './opentsdb/datasource', +// './elasticsearch/datasource', './dashboard/all', './panel/all', './profile/profileCtrl', diff --git a/src/app/features/panel/panelSrv.js b/src/app/features/panel/panelSrv.js index d29fbd93252..488deff691e 100644 --- a/src/app/features/panel/panelSrv.js +++ b/src/app/features/panel/panelSrv.js @@ -94,8 +94,8 @@ function (angular, _, config) { $scope.fullscreen = false; $scope.editor = { index: 1 }; - $scope.datasources = datasourceSrv.getMetricSources(); - $scope.setDatasource($scope.panel.datasource); + // $scope.datasources = datasourceSrv.getMetricSources(); + // $scope.setDatasource($scope.panel.datasource); $scope.dashboardViewState.registerPanel($scope); if ($scope.get_data) { diff --git a/src/app/panels/graph/module.js b/src/app/panels/graph/module.js index 1b8344ed92d..fa8ac32297c 100644 --- a/src/app/panels/graph/module.js +++ b/src/app/panels/graph/module.js @@ -23,7 +23,7 @@ function (angular, app, $, _, kbn, moment, TimeSeries, PanelMeta) { }; }); - module.controller('GraphCtrl', function($scope, $rootScope, panelSrv, annotationsSrv, timeSrv) { + module.controller('GraphCtrl', function($scope, $rootScope, panelSrv, annotationsSrv, timeSrv, datasourceSrv) { $scope.panelMeta = new PanelMeta({ panelName: 'Graph', @@ -183,15 +183,17 @@ function (angular, app, $, _, kbn, moment, TimeSeries, PanelMeta) { $scope.annotationsPromise = annotationsSrv.getAnnotations($scope.rangeUnparsed, $scope.dashboard); - return $scope.datasource.query(metricsQuery) - .then($scope.dataHandler) - .then(null, function(err) { - $scope.panelMeta.loading = false; - $scope.panelMeta.error = err.message || "Timeseries data request error"; - $scope.inspector.error = err; - $scope.seriesList = []; - $scope.render([]); - }); + return datasourceSrv.get($scope.panel.datasource).then(function(ds) { + return ds.query(metricsQuery) + .then($scope.dataHandler) + .then(null, function(err) { + $scope.panelMeta.loading = false; + $scope.panelMeta.error = err.message || "Timeseries data request error"; + $scope.inspector.error = err; + $scope.seriesList = []; + $scope.render([]); + }); + }); }; $scope.dataHandler = function(results) { diff --git a/src/plugins/custom.panel.example/editor.html b/src/app/plugins/custom.panel.example/editor.html similarity index 100% rename from src/plugins/custom.panel.example/editor.html rename to src/app/plugins/custom.panel.example/editor.html diff --git a/src/plugins/custom.panel.example/module.html b/src/app/plugins/custom.panel.example/module.html similarity index 100% rename from src/plugins/custom.panel.example/module.html rename to src/app/plugins/custom.panel.example/module.html diff --git a/src/plugins/custom.panel.example/module.js b/src/app/plugins/custom.panel.example/module.js similarity index 100% rename from src/plugins/custom.panel.example/module.js rename to src/app/plugins/custom.panel.example/module.js diff --git a/src/plugins/datasource.example.js b/src/app/plugins/datasource.example.js similarity index 100% rename from src/plugins/datasource.example.js rename to src/app/plugins/datasource.example.js diff --git a/src/app/plugins/datasources/graphite/module.js b/src/app/plugins/datasources/graphite/module.js new file mode 100644 index 00000000000..67dca2f3738 --- /dev/null +++ b/src/app/plugins/datasources/graphite/module.js @@ -0,0 +1,20 @@ +define([ + 'angular', +], +function (angular) { + 'use strict'; + + var module = angular.module('grafana.services'); + + module.factory('MyDataSource', function() { + + function MyDataSource(datasource) { + this.type = 'my_ds'; + this.datasource = datasource; + } + + return MyDataSource; + + }); + +}); diff --git a/src/app/plugins/datasources/graphite/plugin.json b/src/app/plugins/datasources/graphite/plugin.json new file mode 100644 index 00000000000..13af2064baf --- /dev/null +++ b/src/app/plugins/datasources/graphite/plugin.json @@ -0,0 +1,7 @@ +{ + "type": "datasource", + "name": "My Data source", + "keyName": "graphite" + "serviceName": "MyDataSource", + "editPartial": "./partials/edit.html", +} diff --git a/src/app/routes/backend/dashboard.js b/src/app/routes/backend/dashboard.js index 9ef275f1236..05886b73a53 100644 --- a/src/app/routes/backend/dashboard.js +++ b/src/app/routes/backend/dashboard.js @@ -7,9 +7,7 @@ function (angular) { var module = angular.module('grafana.routes'); - module.controller('DashFromDBProvider', function($scope, datasourceSrv, $routeParams, backendSrv) { - - var db = datasourceSrv.getGrafanaDB(); + module.controller('DashFromDBProvider', function($scope, $routeParams, backendSrv) { if (!$routeParams.id) { backendSrv.get('/api/dashboards/home').then(function(result) { @@ -22,9 +20,9 @@ function (angular) { return; } - db.getDashboard($routeParams.id, false).then(function(result) { + return backendSrv.get('/api/dashboards/db/' + $routeParams.id).then(function(result) { $scope.initDashboard(result, $scope); - }).then(null, function() { + }, function() { $scope.initDashboard({ meta: {}, model: { title: 'Not found' } diff --git a/src/app/services/datasourceSrv.js b/src/app/services/datasourceSrv.js index 122b108c29f..6dfac466494 100644 --- a/src/app/services/datasourceSrv.js +++ b/src/app/services/datasourceSrv.js @@ -16,46 +16,22 @@ function (angular, _, config) { 'grafana': 'GrafanaDatasource', }; - module.service('datasourceSrv', function($q, $http, $injector) { + var plugins = { + datasources: { + 'graphite': { + 'serviceName': 'GraphiteDatasource', + 'module': 'features/graphite/datasource' + } + } + }; + + module.service('datasourceSrv', function($q, $injector, $rootScope) { + var self = this; + + this.datasources = {}; this.init = function(dsSettingList) { config.datasources = dsSettingList; - - this.datasources = {}; - this.metricSources = []; - this.annotationSources = []; - - _.each(dsSettingList, function(value, key) { - var ds = this.datasourceFactory(value); - ds.name = key; - if (value.default) { - this.default = ds; - ds.default = true; - } - this.datasources[key] = ds; - }, this); - - if (!this.default) { - this.default = this.datasources[_.keys(this.datasources)[0]]; - this.default.default = true; - } - - // create list of different source types - _.each(this.datasources, function(value, key) { - if (value.supportMetrics) { - this.metricSources.push({ - name: value.name, - value: value.default ? null : key, - default: value.default, - }); - } - if (value.supportAnnotations) { - this.annotationSources.push({ name: key, editorSrc: value.annotationEditorSrc }); - } - if (value.grafanaDB) { - this.grafanaDB = value; - } - }, this); }; this.datasourceFactory = function(ds) { @@ -65,10 +41,35 @@ function (angular, _, config) { }; this.get = function(name) { - if (!name) { return this.default; } - if (this.datasources[name]) { return this.datasources[name]; } + if (!name) { + return this.get(config.defaultDatasource); + } - return this.default; + if (this.datasources[name]) { + return $q.when(this.datasources[name]); + } + + return this.loadDatasource(name); + }; + + this.loadDatasource = function(name) { + var datasourceConfig = config.datasources[name]; + var pluginDef = plugins.datasources[datasourceConfig.type]; + + if (!pluginDef) { + throw { message: "No plugin definition for data source: " + name }; + } + + var deferred = $q.defer(); + + $rootScope.require([pluginDef.module], function() { + var AngularService = $injector.get(pluginDef.serviceName); + var instance = new AngularService(datasourceConfig); + self.datasources[name] = instance; + deferred.resolve(instance); + }); + + return deferred.promise; }; this.getAll = function() {