Commit dfaa6d8e by Torkel Ödegaard

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

parent 7b1d8274
......@@ -171,7 +171,6 @@ func Register(r *macaron.Macaron) {
r.Put("/:id", bind(m.UpdateDataSourceCommand{}), UpdateDataSource)
r.Delete("/:id", DeleteDataSource)
r.Get("/:id", wrap(GetDataSourceById))
r.Get("/plugins", GetDataSourcePlugins)
}, reqOrgAdmin)
r.Group("/datasources/name/:name", func() {
......
......@@ -6,7 +6,6 @@ import (
//"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/middleware"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/util"
)
......@@ -100,24 +99,6 @@ func UpdateDataSource(c *middleware.Context, cmd m.UpdateDataSourceCommand) {
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
func GetDataSourceByName(c *middleware.Context) Response {
query := m.GetDataSourceByNameQuery{Name: c.Params(":name"), OrgId: c.OrgId}
......
......@@ -5,23 +5,23 @@ import "github.com/grafana/grafana/pkg/plugins"
type PluginSetting struct {
Name string `json:"name"`
Type string `json:"type"`
PluginId string `json:"pluginId"`
Id string `json:"id"`
Enabled bool `json:"enabled"`
Pinned bool `json:"pinned"`
Module string `json:"module"`
BaseUrl string `json:"baseUrl"`
Info *plugins.PluginInfo `json:"info"`
Pages []*plugins.AppPluginPage `json:"pages"`
Includes []*plugins.AppIncludeInfo `json:"includes"`
Includes []*plugins.PluginInclude `json:"includes"`
Dependencies *plugins.PluginDependencies `json:"dependencies"`
JsonData map[string]interface{} `json:"jsonData"`
}
type PluginListItem struct {
Name string `json:"name"`
Type string `json:"type"`
PluginId string `json:"pluginId"`
Enabled bool `json:"enabled"`
Pinned bool `json:"pinned"`
Info *plugins.PluginInfo `json:"info"`
Name string `json:"name"`
Type string `json:"type"`
Id string `json:"id"`
Enabled bool `json:"enabled"`
Pinned bool `json:"pinned"`
Info *plugins.PluginInfo `json:"info"`
}
......@@ -9,6 +9,10 @@ import (
)
func GetPluginList(c *middleware.Context) Response {
typeFilter := c.Query("type")
enabledFilter := c.Query("enabled")
embeddedFilter := c.Query("embedded")
pluginSettingsMap, err := plugins.GetPluginSettings(c.OrgId)
if err != nil {
......@@ -17,16 +21,21 @@ func GetPluginList(c *middleware.Context) Response {
result := make([]*dtos.PluginListItem, 0)
for _, pluginDef := range plugins.Plugins {
// filter out plugin components
if pluginDef.IncludedInAppId != "" {
// filter out app sub plugins
if embeddedFilter == "0" && pluginDef.IncludedInAppId != "" {
continue
}
// filter on type
if typeFilter != "" && typeFilter != pluginDef.Type {
continue
}
listItem := &dtos.PluginListItem{
PluginId: pluginDef.Id,
Name: pluginDef.Name,
Type: pluginDef.Type,
Info: &pluginDef.Info,
Id: pluginDef.Id,
Name: pluginDef.Name,
Type: pluginDef.Type,
Info: &pluginDef.Info,
}
if pluginSetting, exists := pluginSettingsMap[pluginDef.Id]; exists {
......@@ -34,6 +43,11 @@ func GetPluginList(c *middleware.Context) Response {
listItem.Pinned = pluginSetting.Pinned
}
// filter out disabled
if enabledFilter == "1" && !listItem.Enabled {
continue
}
result = append(result, listItem)
}
......@@ -46,19 +60,20 @@ func GetPluginSettingById(c *middleware.Context) Response {
if def, exists := plugins.Plugins[pluginId]; !exists {
return ApiError(404, "Plugin not found, no installed plugin with that id", nil)
} else {
dto := &dtos.PluginSetting{
Type: def.Type,
PluginId: def.Id,
Id: def.Id,
Name: def.Name,
Info: &def.Info,
Dependencies: &def.Dependencies,
Includes: def.Includes,
BaseUrl: def.BaseUrl,
Module: def.Module,
}
if app, exists := plugins.Apps[pluginId]; exists {
dto.Pages = app.Pages
dto.Includes = app.Includes
dto.BaseUrl = app.BaseUrl
dto.Module = app.Module
}
query := m.GetPluginSettingByIdQuery{PluginId: pluginId, OrgId: c.OrgId}
......
......@@ -61,7 +61,14 @@ func (cmd *UpdatePluginSettingCmd) GetEncryptedJsonData() SecureJsonData {
// QUERIES
type GetPluginSettingsQuery struct {
OrgId int64
Result []*PluginSetting
Result []*PluginSettingInfoDTO
}
type PluginSettingInfoDTO struct {
OrgId int64
PluginId string
Enabled bool
Pinned bool
}
type GetPluginSettingByIdQuery struct {
......
......@@ -21,20 +21,13 @@ type AppPluginCss struct {
Dark string `json:"dark"`
}
type AppIncludeInfo struct {
Name string `json:"name"`
Type string `json:"type"`
Id string `json:"id"`
}
type AppPlugin struct {
FrontendPluginBase
Pages []*AppPluginPage `json:"pages"`
Routes []*AppPluginRoute `json:"routes"`
Includes []*AppIncludeInfo `json:"-"`
Pages []*AppPluginPage `json:"pages"`
Routes []*AppPluginRoute `json:"routes"`
Pinned bool `json:"-"`
Enabled bool `json:"-"`
FoundChildPlugins []*PluginInclude `json:"-"`
Pinned bool `json:"-"`
}
type AppPluginRoute struct {
......@@ -71,7 +64,7 @@ func (app *AppPlugin) initApp() {
for _, panel := range Panels {
if strings.HasPrefix(panel.PluginDir, app.PluginDir) {
panel.setPathsBasedOnApp(app)
app.Includes = append(app.Includes, &AppIncludeInfo{
app.FoundChildPlugins = append(app.FoundChildPlugins, &PluginInclude{
Name: panel.Name,
Id: panel.Id,
Type: panel.Type,
......@@ -83,7 +76,7 @@ func (app *AppPlugin) initApp() {
for _, ds := range DataSources {
if strings.HasPrefix(ds.PluginDir, app.PluginDir) {
ds.setPathsBasedOnApp(app)
app.Includes = append(app.Includes, &AppIncludeInfo{
app.FoundChildPlugins = append(app.FoundChildPlugins, &PluginInclude{
Name: ds.Name,
Id: ds.Id,
Type: ds.Type,
......
......@@ -4,12 +4,11 @@ import "encoding/json"
type DataSourcePlugin struct {
FrontendPluginBase
DefaultMatchFormat string `json:"defaultMatchFormat"`
Annotations bool `json:"annotations"`
Metrics bool `json:"metrics"`
BuiltIn bool `json:"builtIn"`
Mixed bool `json:"mixed"`
App string `json:"app"`
Annotations bool `json:"annotations"`
Metrics bool `json:"metrics"`
BuiltIn bool `json:"builtIn"`
Mixed bool `json:"mixed"`
App string `json:"app"`
}
func (p *DataSourcePlugin) Load(decoder *json.Decoder, pluginDir string) error {
......
......@@ -11,10 +11,6 @@ import (
type FrontendPluginBase struct {
PluginBase
Module string `json:"module"`
BaseUrl string `json:"baseUrl"`
StaticRoot string `json:"staticRoot"`
StaticRootAbs string `json:"-"`
}
func (fp *FrontendPluginBase) initFrontendPlugin() {
......
......@@ -14,11 +14,16 @@ type PluginLoader interface {
}
type PluginBase struct {
Type string `json:"type"`
Name string `json:"name"`
Id string `json:"id"`
Info PluginInfo `json:"info"`
Dependencies PluginDependencies `json:"dependencies"`
Type string `json:"type"`
Name string `json:"name"`
Id string `json:"id"`
Info PluginInfo `json:"info"`
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:"-"`
PluginDir string `json:"-"`
......@@ -51,6 +56,13 @@ type PluginDependencies struct {
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 string `json:"type"`
Id string `json:"id"`
......
......@@ -5,61 +5,71 @@ import (
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}
if err := bus.Dispatch(&query); err != nil {
return nil, err
}
pluginMap := make(map[string]*m.PluginSetting)
pluginMap := make(map[string]*m.PluginSettingInfoDTO)
for _, plug := range query.Result {
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
}
func GetEnabledPlugins(orgId int64) (*EnabledPlugins, error) {
enabledPlugins := NewEnabledPlugins()
orgPlugins, err := GetPluginSettings(orgId)
pluginSettingMap, err := GetPluginSettings(orgId)
if err != nil {
return nil, err
}
enabledApps := make(map[string]bool)
isPluginEnabled := func(pluginId string) bool {
_, ok := pluginSettingMap[pluginId]
return ok
}
for pluginId, app := range Apps {
if b, ok := orgPlugins[pluginId]; ok {
app.Enabled = b.Enabled
if b, ok := pluginSettingMap[pluginId]; ok {
app.Pinned = b.Pinned
}
if app.Enabled {
enabledApps[pluginId] = true
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.
for dsId, ds := range DataSources {
if isPluginEnabled(ds.IncludedInAppId) {
if isPluginEnabled(ds.Id) {
enabledPlugins.DataSources[dsId] = ds
}
}
for _, panel := range Panels {
if isPluginEnabled(panel.IncludedInAppId) {
if isPluginEnabled(panel.Id) {
enabledPlugins.Panels = append(enabledPlugins.Panels, panel)
}
}
......
......@@ -16,9 +16,12 @@ func init() {
}
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)
}
......
......@@ -26,7 +26,10 @@ export class DashImportListCtrl {
}
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() {
......
......@@ -40,7 +40,7 @@ function (angular, _, config) {
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;
$scope.types = plugins;
});
......@@ -55,7 +55,9 @@ function (angular, _, config) {
};
$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() {
......
......@@ -27,34 +27,36 @@ icon="icon-gf icon-gf-datasources">
<div class="gf-form">
<span class="gf-form-label width-7">Type</span>
<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>
<dashboard-import-list plugin="current"></dashboard-import-list>
<rebuild-on-change property="datasourceMeta.id">
<plugin-component type="datasource-config-ctrl">
</plugin-component>
</rebuild-on-change>
<rebuild-on-change property="datasourceMeta.id">
<plugin-component type="datasource-config-ctrl">
</plugin-component>
</rebuild-on-change>
<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">Test results</h5>
<div class="alert-{{testing.status}} alert">
<div class="alert-title">{{testing.title}}</div>
<div ng-bind='testing.message'></div>
</div>
</div>
<div class="gf-form-button-row">
<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>
<button type="submit" class="btn btn-secondary" ng-show="!isNew" ng-click="saveChanges(true)">
Test Connection
</button>
<a class="btn btn-link" href="datasources">Cancel</a>
<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">Test results</h5>
<div class="alert-{{testing.status}} alert">
<div class="alert-title">{{testing.title}}</div>
<div ng-bind='testing.message'></div>
</div>
</div>
<dashboard-import-list plugin="current"></dashboard-import-list>
<div class="gf-form-button-row">
<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>
<button type="submit" class="btn btn-secondary" ng-show="!isNew" ng-click="saveChanges(true)">
Test Connection
</button>
<a class="btn btn-link" href="datasources">Cancel</a>
</div>
</form>
</div>
......
......@@ -72,8 +72,6 @@ export class PluginEditCtrl {
// Perform the core update procedure
chain = chain.then(function() {
var updateCmd = _.extend({
pluginId: self.model.pluginId,
orgId: self.model.orgId,
enabled: self.model.enabled,
pinned: self.model.pinned,
jsonData: self.model.jsonData,
......
......@@ -48,7 +48,7 @@
</div>
</div>
<div ng-if="ctrl.model.pluginId">
<div ng-if="ctrl.model.id">
<plugin-component type="app-config-ctrl"></plugin-component>
<div class="clearfix"></div>
<button type="submit" class="btn btn-success" ng-click="ctrl.update()">Save</button>
......@@ -70,7 +70,7 @@
</li>
<li ng-repeat="page in ctrl.model.pages" class="plugin-info-list-item">
<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>
</ul>
</section>
......
......@@ -18,7 +18,7 @@
<tbody>
<tr ng-repeat="plugin in ctrl.plugins">
<td>
<a href="plugins/{{plugin.pluginId}}/edit">
<a href="plugins/{{plugin.id}}/edit">
{{plugin.name}}
</a>
</td>
......@@ -30,7 +30,7 @@
<span class="label label-info" ng-if="plugin.pinned">Pinned</span>
</td>
<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>
Edit
</a>
......
......@@ -3,7 +3,10 @@
"type": "datasource",
"id": "graphite",
"defaultMatchFormat": "glob",
"includes": [
{"type": "dashboard", "name": "Carbon Stats", "path": "dashboards/carbon_stats.json"}
],
"metrics": true,
"annotations": true
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment