feat(plugins): a lot of work on #4298

This commit is contained in:
Torkel Ödegaard 2016-03-08 18:17:47 +01:00
parent 7b1d827460
commit dfaa6d8eb9
18 changed files with 145 additions and 122 deletions

View File

@ -171,7 +171,6 @@ func Register(r *macaron.Macaron) {
r.Put("/:id", bind(m.UpdateDataSourceCommand{}), UpdateDataSource) r.Put("/:id", bind(m.UpdateDataSourceCommand{}), UpdateDataSource)
r.Delete("/:id", DeleteDataSource) r.Delete("/:id", DeleteDataSource)
r.Get("/:id", wrap(GetDataSourceById)) r.Get("/:id", wrap(GetDataSourceById))
r.Get("/plugins", GetDataSourcePlugins)
}, reqOrgAdmin) }, reqOrgAdmin)
r.Group("/datasources/name/:name", func() { r.Group("/datasources/name/:name", func() {

View File

@ -6,7 +6,6 @@ import (
//"github.com/grafana/grafana/pkg/log" //"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/middleware" "github.com/grafana/grafana/pkg/middleware"
m "github.com/grafana/grafana/pkg/models" m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
) )
@ -100,24 +99,6 @@ func UpdateDataSource(c *middleware.Context, cmd m.UpdateDataSourceCommand) {
c.JsonOK("Datasource updated") c.JsonOK("Datasource updated")
} }
func GetDataSourcePlugins(c *middleware.Context) {
dsList := make(map[string]*plugins.DataSourcePlugin)
if enabledPlugins, err := plugins.GetEnabledPlugins(c.OrgId); err != nil {
c.JsonApiErr(500, "Failed to get org apps", err)
return
} else {
for key, value := range enabledPlugins.DataSources {
if !value.BuiltIn {
dsList[key] = value
}
}
c.JSON(200, dsList)
}
}
// Get /api/datasources/name/:name // Get /api/datasources/name/:name
func GetDataSourceByName(c *middleware.Context) Response { func GetDataSourceByName(c *middleware.Context) Response {
query := m.GetDataSourceByNameQuery{Name: c.Params(":name"), OrgId: c.OrgId} query := m.GetDataSourceByNameQuery{Name: c.Params(":name"), OrgId: c.OrgId}

View File

@ -5,23 +5,23 @@ import "github.com/grafana/grafana/pkg/plugins"
type PluginSetting struct { type PluginSetting struct {
Name string `json:"name"` Name string `json:"name"`
Type string `json:"type"` Type string `json:"type"`
PluginId string `json:"pluginId"` Id string `json:"id"`
Enabled bool `json:"enabled"` Enabled bool `json:"enabled"`
Pinned bool `json:"pinned"` Pinned bool `json:"pinned"`
Module string `json:"module"` Module string `json:"module"`
BaseUrl string `json:"baseUrl"` BaseUrl string `json:"baseUrl"`
Info *plugins.PluginInfo `json:"info"` Info *plugins.PluginInfo `json:"info"`
Pages []*plugins.AppPluginPage `json:"pages"` Pages []*plugins.AppPluginPage `json:"pages"`
Includes []*plugins.AppIncludeInfo `json:"includes"` Includes []*plugins.PluginInclude `json:"includes"`
Dependencies *plugins.PluginDependencies `json:"dependencies"` Dependencies *plugins.PluginDependencies `json:"dependencies"`
JsonData map[string]interface{} `json:"jsonData"` JsonData map[string]interface{} `json:"jsonData"`
} }
type PluginListItem struct { type PluginListItem struct {
Name string `json:"name"` Name string `json:"name"`
Type string `json:"type"` Type string `json:"type"`
PluginId string `json:"pluginId"` Id string `json:"id"`
Enabled bool `json:"enabled"` Enabled bool `json:"enabled"`
Pinned bool `json:"pinned"` Pinned bool `json:"pinned"`
Info *plugins.PluginInfo `json:"info"` Info *plugins.PluginInfo `json:"info"`
} }

View File

@ -9,6 +9,10 @@ import (
) )
func GetPluginList(c *middleware.Context) Response { func GetPluginList(c *middleware.Context) Response {
typeFilter := c.Query("type")
enabledFilter := c.Query("enabled")
embeddedFilter := c.Query("embedded")
pluginSettingsMap, err := plugins.GetPluginSettings(c.OrgId) pluginSettingsMap, err := plugins.GetPluginSettings(c.OrgId)
if err != nil { if err != nil {
@ -17,16 +21,21 @@ func GetPluginList(c *middleware.Context) Response {
result := make([]*dtos.PluginListItem, 0) result := make([]*dtos.PluginListItem, 0)
for _, pluginDef := range plugins.Plugins { for _, pluginDef := range plugins.Plugins {
// filter out plugin components // filter out app sub plugins
if pluginDef.IncludedInAppId != "" { if embeddedFilter == "0" && pluginDef.IncludedInAppId != "" {
continue
}
// filter on type
if typeFilter != "" && typeFilter != pluginDef.Type {
continue continue
} }
listItem := &dtos.PluginListItem{ listItem := &dtos.PluginListItem{
PluginId: pluginDef.Id, Id: pluginDef.Id,
Name: pluginDef.Name, Name: pluginDef.Name,
Type: pluginDef.Type, Type: pluginDef.Type,
Info: &pluginDef.Info, Info: &pluginDef.Info,
} }
if pluginSetting, exists := pluginSettingsMap[pluginDef.Id]; exists { if pluginSetting, exists := pluginSettingsMap[pluginDef.Id]; exists {
@ -34,6 +43,11 @@ func GetPluginList(c *middleware.Context) Response {
listItem.Pinned = pluginSetting.Pinned listItem.Pinned = pluginSetting.Pinned
} }
// filter out disabled
if enabledFilter == "1" && !listItem.Enabled {
continue
}
result = append(result, listItem) result = append(result, listItem)
} }
@ -46,19 +60,20 @@ func GetPluginSettingById(c *middleware.Context) Response {
if def, exists := plugins.Plugins[pluginId]; !exists { if def, exists := plugins.Plugins[pluginId]; !exists {
return ApiError(404, "Plugin not found, no installed plugin with that id", nil) return ApiError(404, "Plugin not found, no installed plugin with that id", nil)
} else { } else {
dto := &dtos.PluginSetting{ dto := &dtos.PluginSetting{
Type: def.Type, Type: def.Type,
PluginId: def.Id, Id: def.Id,
Name: def.Name, Name: def.Name,
Info: &def.Info, Info: &def.Info,
Dependencies: &def.Dependencies, Dependencies: &def.Dependencies,
Includes: def.Includes,
BaseUrl: def.BaseUrl,
Module: def.Module,
} }
if app, exists := plugins.Apps[pluginId]; exists { if app, exists := plugins.Apps[pluginId]; exists {
dto.Pages = app.Pages dto.Pages = app.Pages
dto.Includes = app.Includes
dto.BaseUrl = app.BaseUrl
dto.Module = app.Module
} }
query := m.GetPluginSettingByIdQuery{PluginId: pluginId, OrgId: c.OrgId} query := m.GetPluginSettingByIdQuery{PluginId: pluginId, OrgId: c.OrgId}

View File

@ -61,7 +61,14 @@ func (cmd *UpdatePluginSettingCmd) GetEncryptedJsonData() SecureJsonData {
// QUERIES // QUERIES
type GetPluginSettingsQuery struct { type GetPluginSettingsQuery struct {
OrgId int64 OrgId int64
Result []*PluginSetting Result []*PluginSettingInfoDTO
}
type PluginSettingInfoDTO struct {
OrgId int64
PluginId string
Enabled bool
Pinned bool
} }
type GetPluginSettingByIdQuery struct { type GetPluginSettingByIdQuery struct {

View File

@ -21,20 +21,13 @@ type AppPluginCss struct {
Dark string `json:"dark"` Dark string `json:"dark"`
} }
type AppIncludeInfo struct {
Name string `json:"name"`
Type string `json:"type"`
Id string `json:"id"`
}
type AppPlugin struct { type AppPlugin struct {
FrontendPluginBase FrontendPluginBase
Pages []*AppPluginPage `json:"pages"` Pages []*AppPluginPage `json:"pages"`
Routes []*AppPluginRoute `json:"routes"` Routes []*AppPluginRoute `json:"routes"`
Includes []*AppIncludeInfo `json:"-"`
Pinned bool `json:"-"` FoundChildPlugins []*PluginInclude `json:"-"`
Enabled bool `json:"-"` Pinned bool `json:"-"`
} }
type AppPluginRoute struct { type AppPluginRoute struct {
@ -71,7 +64,7 @@ func (app *AppPlugin) initApp() {
for _, panel := range Panels { for _, panel := range Panels {
if strings.HasPrefix(panel.PluginDir, app.PluginDir) { if strings.HasPrefix(panel.PluginDir, app.PluginDir) {
panel.setPathsBasedOnApp(app) panel.setPathsBasedOnApp(app)
app.Includes = append(app.Includes, &AppIncludeInfo{ app.FoundChildPlugins = append(app.FoundChildPlugins, &PluginInclude{
Name: panel.Name, Name: panel.Name,
Id: panel.Id, Id: panel.Id,
Type: panel.Type, Type: panel.Type,
@ -83,7 +76,7 @@ func (app *AppPlugin) initApp() {
for _, ds := range DataSources { for _, ds := range DataSources {
if strings.HasPrefix(ds.PluginDir, app.PluginDir) { if strings.HasPrefix(ds.PluginDir, app.PluginDir) {
ds.setPathsBasedOnApp(app) ds.setPathsBasedOnApp(app)
app.Includes = append(app.Includes, &AppIncludeInfo{ app.FoundChildPlugins = append(app.FoundChildPlugins, &PluginInclude{
Name: ds.Name, Name: ds.Name,
Id: ds.Id, Id: ds.Id,
Type: ds.Type, Type: ds.Type,

View File

@ -4,12 +4,11 @@ import "encoding/json"
type DataSourcePlugin struct { type DataSourcePlugin struct {
FrontendPluginBase FrontendPluginBase
DefaultMatchFormat string `json:"defaultMatchFormat"` Annotations bool `json:"annotations"`
Annotations bool `json:"annotations"` Metrics bool `json:"metrics"`
Metrics bool `json:"metrics"` BuiltIn bool `json:"builtIn"`
BuiltIn bool `json:"builtIn"` Mixed bool `json:"mixed"`
Mixed bool `json:"mixed"` App string `json:"app"`
App string `json:"app"`
} }
func (p *DataSourcePlugin) Load(decoder *json.Decoder, pluginDir string) error { func (p *DataSourcePlugin) Load(decoder *json.Decoder, pluginDir string) error {

View File

@ -11,10 +11,6 @@ import (
type FrontendPluginBase struct { type FrontendPluginBase struct {
PluginBase PluginBase
Module string `json:"module"`
BaseUrl string `json:"baseUrl"`
StaticRoot string `json:"staticRoot"`
StaticRootAbs string `json:"-"`
} }
func (fp *FrontendPluginBase) initFrontendPlugin() { func (fp *FrontendPluginBase) initFrontendPlugin() {

View File

@ -14,11 +14,16 @@ type PluginLoader interface {
} }
type PluginBase struct { type PluginBase struct {
Type string `json:"type"` Type string `json:"type"`
Name string `json:"name"` Name string `json:"name"`
Id string `json:"id"` Id string `json:"id"`
Info PluginInfo `json:"info"` Info PluginInfo `json:"info"`
Dependencies PluginDependencies `json:"dependencies"` Dependencies PluginDependencies `json:"dependencies"`
Includes []*PluginInclude `json:"includes"`
Module string `json:"module"`
BaseUrl string `json:"baseUrl"`
StaticRoot string `json:"staticRoot"`
StaticRootAbs string `json:"-"`
IncludedInAppId string `json:"-"` IncludedInAppId string `json:"-"`
PluginDir string `json:"-"` PluginDir string `json:"-"`
@ -51,6 +56,13 @@ type PluginDependencies struct {
Plugins []PluginDependencyItem `json:"plugins"` Plugins []PluginDependencyItem `json:"plugins"`
} }
type PluginInclude struct {
Name string `json:"name"`
Path string `json:"path"`
Type string `json:"type"`
Id string `json:"id"`
}
type PluginDependencyItem struct { type PluginDependencyItem struct {
Type string `json:"type"` Type string `json:"type"`
Id string `json:"id"` Id string `json:"id"`

View File

@ -5,61 +5,71 @@ import (
m "github.com/grafana/grafana/pkg/models" m "github.com/grafana/grafana/pkg/models"
) )
func GetPluginSettings(orgId int64) (map[string]*m.PluginSetting, error) { func GetPluginSettings(orgId int64) (map[string]*m.PluginSettingInfoDTO, error) {
query := m.GetPluginSettingsQuery{OrgId: orgId} query := m.GetPluginSettingsQuery{OrgId: orgId}
if err := bus.Dispatch(&query); err != nil { if err := bus.Dispatch(&query); err != nil {
return nil, err return nil, err
} }
pluginMap := make(map[string]*m.PluginSetting) pluginMap := make(map[string]*m.PluginSettingInfoDTO)
for _, plug := range query.Result { for _, plug := range query.Result {
pluginMap[plug.PluginId] = plug pluginMap[plug.PluginId] = plug
} }
for _, pluginDef := range Plugins {
// ignore entries that exists
if _, ok := pluginMap[pluginDef.Id]; ok {
continue
}
// default to enabled true
opt := &m.PluginSettingInfoDTO{Enabled: true}
// if it's included in app check app settings
if pluginDef.IncludedInAppId != "" {
// app componets are by default disabled
opt.Enabled = false
if appSettings, ok := pluginMap[pluginDef.IncludedInAppId]; ok {
opt.Enabled = appSettings.Enabled
}
}
pluginMap[pluginDef.Id] = opt
}
return pluginMap, nil return pluginMap, nil
} }
func GetEnabledPlugins(orgId int64) (*EnabledPlugins, error) { func GetEnabledPlugins(orgId int64) (*EnabledPlugins, error) {
enabledPlugins := NewEnabledPlugins() enabledPlugins := NewEnabledPlugins()
orgPlugins, err := GetPluginSettings(orgId) pluginSettingMap, err := GetPluginSettings(orgId)
if err != nil { if err != nil {
return nil, err return nil, err
} }
enabledApps := make(map[string]bool) isPluginEnabled := func(pluginId string) bool {
_, ok := pluginSettingMap[pluginId]
return ok
}
for pluginId, app := range Apps { for pluginId, app := range Apps {
if b, ok := pluginSettingMap[pluginId]; ok {
if b, ok := orgPlugins[pluginId]; ok {
app.Enabled = b.Enabled
app.Pinned = b.Pinned app.Pinned = b.Pinned
}
if app.Enabled {
enabledApps[pluginId] = true
enabledPlugins.Apps = append(enabledPlugins.Apps, app) enabledPlugins.Apps = append(enabledPlugins.Apps, app)
} }
} }
isPluginEnabled := func(appId string) bool {
if appId == "" {
return true
}
_, ok := enabledApps[appId]
return ok
}
// add all plugins that are not part of an App. // add all plugins that are not part of an App.
for dsId, ds := range DataSources { for dsId, ds := range DataSources {
if isPluginEnabled(ds.IncludedInAppId) { if isPluginEnabled(ds.Id) {
enabledPlugins.DataSources[dsId] = ds enabledPlugins.DataSources[dsId] = ds
} }
} }
for _, panel := range Panels { for _, panel := range Panels {
if isPluginEnabled(panel.IncludedInAppId) { if isPluginEnabled(panel.Id) {
enabledPlugins.Panels = append(enabledPlugins.Panels, panel) enabledPlugins.Panels = append(enabledPlugins.Panels, panel)
} }
} }

View File

@ -16,9 +16,12 @@ func init() {
} }
func GetPluginSettings(query *m.GetPluginSettingsQuery) error { func GetPluginSettings(query *m.GetPluginSettingsQuery) error {
sess := x.Where("org_id=?", query.OrgId) sql := `SELECT org_id, plugin_id, enabled, pinned
FROM plugin_setting
WHERE org_id=?`
query.Result = make([]*m.PluginSetting, 0) sess := x.Sql(sql, query.OrgId)
query.Result = make([]*m.PluginSettingInfoDTO, 0)
return sess.Find(&query.Result) return sess.Find(&query.Result)
} }

View File

@ -26,7 +26,10 @@ export class DashImportListCtrl {
} }
var template = ` var template = `
<button class="btn btn-mini btn-inverse" ng-click="ctrl.import(dash)">Import</span> <h3 class="page-heading">Dashboards</h3>
<div class="gf-form-group">
<button class="btn btn-mini btn-inverse" ng-click="ctrl.import(dash)">Import</button>
</div>
`; `;
export function dashboardImportList() { export function dashboardImportList() {

View File

@ -40,7 +40,7 @@ function (angular, _, config) {
return $q.when(null); return $q.when(null);
} }
return backendSrv.get('/api/datasources/plugins').then(function(plugins) { return backendSrv.get('/api/org/plugins', {enabled: 1, type: 'datasource'}).then(function(plugins) {
datasourceTypes = plugins; datasourceTypes = plugins;
$scope.types = plugins; $scope.types = plugins;
}); });
@ -55,7 +55,9 @@ function (angular, _, config) {
}; };
$scope.typeChanged = function() { $scope.typeChanged = function() {
$scope.datasourceMeta = $scope.types[$scope.current.type]; return backendSrv.get('/api/org/plugins/' + $scope.current.type + '/settings').then(function(pluginInfo) {
$scope.datasourceMeta = pluginInfo;
});
}; };
$scope.updateFrontendSettings = function() { $scope.updateFrontendSettings = function() {

View File

@ -27,34 +27,36 @@ icon="icon-gf icon-gf-datasources">
<div class="gf-form"> <div class="gf-form">
<span class="gf-form-label width-7">Type</span> <span class="gf-form-label width-7">Type</span>
<div class="gf-form-select-wrapper"> <div class="gf-form-select-wrapper">
<select class="gf-form-input gf-size-auto" ng-model="current.type" ng-options="k as v.name for (k, v) in types" ng-change="typeChanged()"></select> <select class="gf-form-input gf-size-auto" ng-model="current.type" ng-options="v.id as v.name for v in types" ng-change="typeChanged()"></select>
</div> </div>
</div> </div>
</div>
<dashboard-import-list plugin="current"></dashboard-import-list>
<rebuild-on-change property="datasourceMeta.id"> <rebuild-on-change property="datasourceMeta.id">
<plugin-component type="datasource-config-ctrl"> <plugin-component type="datasource-config-ctrl">
</plugin-component> </plugin-component>
</rebuild-on-change> </rebuild-on-change>
<div ng-if="testing" style="margin-top: 25px"> <div ng-if="testing" style="margin-top: 25px">
<h5 ng-show="!testing.done">Testing.... <i class="fa fa-spiner fa-spin"></i></h5> <h5 ng-show="!testing.done">Testing.... <i class="fa fa-spiner fa-spin"></i></h5>
<h5 ng-show="testing.done">Test results</h5> <h5 ng-show="testing.done">Test results</h5>
<div class="alert-{{testing.status}} alert"> <div class="alert-{{testing.status}} alert">
<div class="alert-title">{{testing.title}}</div> <div class="alert-title">{{testing.title}}</div>
<div ng-bind='testing.message'></div> <div ng-bind='testing.message'></div>
</div>
</div> </div>
</div>
<div class="gf-form-button-row"> <dashboard-import-list plugin="current"></dashboard-import-list>
<button type="submit" class="btn btn-success" ng-show="isNew" ng-click="saveChanges()">Add</button>
<button type="submit" class="btn btn-success" ng-show="!isNew" ng-click="saveChanges()">Save</button> <div class="gf-form-button-row">
<button type="submit" class="btn btn-secondary" ng-show="!isNew" ng-click="saveChanges(true)"> <button type="submit" class="btn btn-success" ng-show="isNew" ng-click="saveChanges()">Add</button>
Test Connection <button type="submit" class="btn btn-success" ng-show="!isNew" ng-click="saveChanges()">Save</button>
</button> <button type="submit" class="btn btn-secondary" ng-show="!isNew" ng-click="saveChanges(true)">
<a class="btn btn-link" href="datasources">Cancel</a> Test Connection
</div> </button>
<a class="btn btn-link" href="datasources">Cancel</a>
</div>
</form> </form>
</div> </div>

View File

@ -72,8 +72,6 @@ export class PluginEditCtrl {
// Perform the core update procedure // Perform the core update procedure
chain = chain.then(function() { chain = chain.then(function() {
var updateCmd = _.extend({ var updateCmd = _.extend({
pluginId: self.model.pluginId,
orgId: self.model.orgId,
enabled: self.model.enabled, enabled: self.model.enabled,
pinned: self.model.pinned, pinned: self.model.pinned,
jsonData: self.model.jsonData, jsonData: self.model.jsonData,

View File

@ -48,7 +48,7 @@
</div> </div>
</div> </div>
<div ng-if="ctrl.model.pluginId"> <div ng-if="ctrl.model.id">
<plugin-component type="app-config-ctrl"></plugin-component> <plugin-component type="app-config-ctrl"></plugin-component>
<div class="clearfix"></div> <div class="clearfix"></div>
<button type="submit" class="btn btn-success" ng-click="ctrl.update()">Save</button> <button type="submit" class="btn btn-success" ng-click="ctrl.update()">Save</button>
@ -70,7 +70,7 @@
</li> </li>
<li ng-repeat="page in ctrl.model.pages" class="plugin-info-list-item"> <li ng-repeat="page in ctrl.model.pages" class="plugin-info-list-item">
<i class="icon-gf icon-gf-share"></i> <i class="icon-gf icon-gf-share"></i>
<a href="plugins/{{ctrl.pluginId}}/page/{{page.slug}}">{{page.name}}</a> <a href="plugins/{{ctrl.model.id}}/page/{{page.slug}}">{{page.name}}</a>
</li> </li>
</ul> </ul>
</section> </section>

View File

@ -18,7 +18,7 @@
<tbody> <tbody>
<tr ng-repeat="plugin in ctrl.plugins"> <tr ng-repeat="plugin in ctrl.plugins">
<td> <td>
<a href="plugins/{{plugin.pluginId}}/edit"> <a href="plugins/{{plugin.id}}/edit">
{{plugin.name}} {{plugin.name}}
</a> </a>
</td> </td>
@ -30,7 +30,7 @@
<span class="label label-info" ng-if="plugin.pinned">Pinned</span> <span class="label label-info" ng-if="plugin.pinned">Pinned</span>
</td> </td>
<td class="text-right"> <td class="text-right">
<a href="plugins/{{plugin.pluginId}}/edit" class="btn btn-inverse btn-small"> <a href="plugins/{{plugin.id}}/edit" class="btn btn-inverse btn-small">
<i class="fa fa-edit"></i> <i class="fa fa-edit"></i>
Edit Edit
</a> </a>

View File

@ -3,7 +3,10 @@
"type": "datasource", "type": "datasource",
"id": "graphite", "id": "graphite",
"defaultMatchFormat": "glob", "includes": [
{"type": "dashboard", "name": "Carbon Stats", "path": "dashboards/carbon_stats.json"}
],
"metrics": true, "metrics": true,
"annotations": true "annotations": true
} }