mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
feat(plugins): a lot of work on #4298
This commit is contained in:
parent
7b1d827460
commit
dfaa6d8eb9
@ -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 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>
|
||||
<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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user