Commit 7a26d309 by Torkel Ödegaard

feat(apps): more work on apps and how apps can include panels

parent 6b85a6fd
...@@ -12,17 +12,19 @@ type AppSettings struct { ...@@ -12,17 +12,19 @@ type AppSettings struct {
Pinned bool `json:"pinned"` Pinned bool `json:"pinned"`
Module string `json:"module"` Module string `json:"module"`
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"`
JsonData map[string]interface{} `json:"jsonData"` JsonData map[string]interface{} `json:"jsonData"`
} }
func NewAppSettingsDto(def *plugins.AppPlugin, data *models.AppSettings) *AppSettings { func NewAppSettingsDto(def *plugins.AppPlugin, data *models.AppSettings) *AppSettings {
dto := &AppSettings{ dto := &AppSettings{
AppId: def.Id, AppId: def.Id,
Name: def.Name, Name: def.Name,
Info: &def.Info, Info: &def.Info,
Module: def.Module, Module: def.Module,
Pages: def.Pages, Pages: def.Pages,
Includes: def.Includes,
} }
if data != nil { if data != nil {
......
...@@ -2,6 +2,7 @@ package plugins ...@@ -2,6 +2,7 @@ package plugins
import ( import (
"encoding/json" "encoding/json"
"strings"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
) )
...@@ -17,10 +18,17 @@ type AppPluginCss struct { ...@@ -17,10 +18,17 @@ 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
Css *AppPluginCss `json:"css"` Css *AppPluginCss `json:"css"`
Pages []*AppPluginPage `json:"pages"` Pages []AppPluginPage `json:"pages"`
Includes []AppIncludeInfo `json:"-"`
Pinned bool `json:"-"` Pinned bool `json:"-"`
Enabled bool `json:"-"` Enabled bool `json:"-"`
...@@ -38,6 +46,19 @@ func (app *AppPlugin) Load(decoder *json.Decoder, pluginDir string) error { ...@@ -38,6 +46,19 @@ func (app *AppPlugin) Load(decoder *json.Decoder, pluginDir string) error {
app.PluginDir = pluginDir app.PluginDir = pluginDir
app.initFrontendPlugin() app.initFrontendPlugin()
// check if we have child panels
for _, panel := range Panels {
if strings.HasPrefix(panel.PluginDir, app.PluginDir) {
panel.IncludedInAppId = app.Id
app.Includes = append(app.Includes, AppIncludeInfo{
Name: panel.Name,
Id: panel.Id,
Type: panel.Type,
})
}
}
Apps[app.Id] = app Apps[app.Id] = app
return nil return nil
} }
...@@ -8,21 +8,22 @@ import ( ...@@ -8,21 +8,22 @@ import (
type FrontendPluginBase struct { type FrontendPluginBase struct {
PluginBase PluginBase
Module string `json:"module"` Module string `json:"module"`
StaticRoot string `json:"staticRoot"` StaticRoot string `json:"staticRoot"`
StaticRootAbs string `json:"-"`
} }
func (fp *FrontendPluginBase) initFrontendPlugin() { func (fp *FrontendPluginBase) initFrontendPlugin() {
if fp.StaticRoot != "" { if fp.StaticRoot != "" {
fp.StaticRootAbs = filepath.Join(fp.PluginDir, fp.StaticRoot)
StaticRoutes = append(StaticRoutes, &PluginStaticRoute{ StaticRoutes = append(StaticRoutes, &PluginStaticRoute{
Directory: filepath.Join(fp.PluginDir, fp.StaticRoot), Directory: fp.StaticRootAbs,
PluginId: fp.Id, PluginId: fp.Id,
}) })
} }
fp.Info.Logos.Small = evalRelativePluginUrlPath(fp.Info.Logos.Small, fp.Id) fp.Info.Logos.Small = evalRelativePluginUrlPath(fp.Info.Logos.Small, fp.Id)
fp.Info.Logos.Large = evalRelativePluginUrlPath(fp.Info.Logos.Large, fp.Id) fp.Info.Logos.Large = evalRelativePluginUrlPath(fp.Info.Logos.Large, fp.Id)
fp.handleModuleDefaults() fp.handleModuleDefaults()
} }
......
...@@ -11,12 +11,13 @@ type PluginLoader interface { ...@@ -11,12 +11,13 @@ 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"`
App string `json:"app"` Info PluginInfo `json:"info"`
Info PluginInfo `json:"info"`
PluginDir string `json:"-"` IncludedInAppId string `json:"-"`
PluginDir string `json:"-"`
} }
type PluginInfo struct { type PluginInfo struct {
......
package plugins package plugins
import ( import (
"bytes"
"encoding/json" "encoding/json"
"errors" "errors"
"io"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
"reflect" "reflect"
"strings" "strings"
"text/template"
"github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
...@@ -122,28 +119,6 @@ func (scanner *PluginScanner) walker(currentPath string, f os.FileInfo, err erro ...@@ -122,28 +119,6 @@ func (scanner *PluginScanner) walker(currentPath string, f os.FileInfo, err erro
return nil return nil
} }
func interpolatePluginJson(reader io.Reader, pluginCommon *PluginBase) (io.Reader, error) {
buf := new(bytes.Buffer)
buf.ReadFrom(reader)
jsonStr := buf.String() //
tmpl, err := template.New("json").Parse(jsonStr)
if err != nil {
return nil, err
}
data := map[string]interface{}{
"PluginPublicRoot": "public/plugins/" + pluginCommon.Id,
}
var resultBuffer bytes.Buffer
if err := tmpl.ExecuteTemplate(&resultBuffer, "json", data); err != nil {
return nil, err
}
return bytes.NewReader(resultBuffer.Bytes()), nil
}
func (scanner *PluginScanner) loadPluginJson(pluginJsonFilePath string) error { func (scanner *PluginScanner) loadPluginJson(pluginJsonFilePath string) error {
currentDir := filepath.Dir(pluginJsonFilePath) currentDir := filepath.Dir(pluginJsonFilePath)
reader, err := os.Open(pluginJsonFilePath) reader, err := os.Open(pluginJsonFilePath)
...@@ -163,20 +138,13 @@ func (scanner *PluginScanner) loadPluginJson(pluginJsonFilePath string) error { ...@@ -163,20 +138,13 @@ func (scanner *PluginScanner) loadPluginJson(pluginJsonFilePath string) error {
return errors.New("Did not find type and id property in plugin.json") return errors.New("Did not find type and id property in plugin.json")
} }
reader.Seek(0, 0)
if newReader, err := interpolatePluginJson(reader, &pluginCommon); err != nil {
return err
} else {
jsonParser = json.NewDecoder(newReader)
}
var loader PluginLoader var loader PluginLoader
if pluginGoType, exists := PluginTypes[pluginCommon.Type]; !exists { if pluginGoType, exists := PluginTypes[pluginCommon.Type]; !exists {
return errors.New("Unkown plugin type " + pluginCommon.Type) return errors.New("Unkown plugin type " + pluginCommon.Type)
} else { } else {
loader = reflect.New(reflect.TypeOf(pluginGoType)).Interface().(PluginLoader) loader = reflect.New(reflect.TypeOf(pluginGoType)).Interface().(PluginLoader)
} }
reader.Seek(0, 0)
return loader.Load(jsonParser, currentDir) return loader.Load(jsonParser, currentDir)
} }
...@@ -27,8 +27,7 @@ func GetEnabledPlugins(orgId int64) (*EnabledPlugins, error) { ...@@ -27,8 +27,7 @@ func GetEnabledPlugins(orgId int64) (*EnabledPlugins, error) {
return nil, err return nil, err
} }
seenPanels := make(map[string]bool) enabledApps := make(map[string]bool)
seenApi := make(map[string]bool)
for appId, installedApp := range Apps { for appId, installedApp := range Apps {
var app AppPlugin var app AppPlugin
...@@ -42,32 +41,36 @@ func GetEnabledPlugins(orgId int64) (*EnabledPlugins, error) { ...@@ -42,32 +41,36 @@ func GetEnabledPlugins(orgId int64) (*EnabledPlugins, error) {
} }
if app.Enabled { if app.Enabled {
enabledApps[app.Id] = 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 d, installedDs := range DataSources { for dsId, ds := range DataSources {
if installedDs.App == "" { if isPluginEnabled(ds.IncludedInAppId) {
enabledPlugins.DataSources[d] = installedDs enabledPlugins.DataSources[dsId] = ds
} }
} }
for p, panel := range Panels { for _, panel := range Panels {
if panel.App == "" { if isPluginEnabled(panel.IncludedInAppId) {
if _, ok := seenPanels[p]; !ok { enabledPlugins.Panels = append(enabledPlugins.Panels, panel)
seenPanels[p] = true
enabledPlugins.Panels = append(enabledPlugins.Panels, panel)
}
} }
} }
for a, api := range ApiPlugins { for _, api := range ApiPlugins {
if api.App == "" { if isPluginEnabled(api.IncludedInAppId) {
if _, ok := seenApi[a]; !ok { enabledPlugins.ApiList = append(enabledPlugins.ApiList, api)
seenApi[a] = true
enabledPlugins.ApiList = append(enabledPlugins.ApiList, api)
}
} }
} }
......
...@@ -5,6 +5,7 @@ import _ from 'lodash'; ...@@ -5,6 +5,7 @@ import _ from 'lodash';
export class AppEditCtrl { export class AppEditCtrl {
appModel: any; appModel: any;
includedPanels: any;
/** @ngInject */ /** @ngInject */
constructor(private backendSrv: any, private $routeParams: any) { constructor(private backendSrv: any, private $routeParams: any) {
...@@ -12,6 +13,7 @@ export class AppEditCtrl { ...@@ -12,6 +13,7 @@ export class AppEditCtrl {
this.backendSrv.get(`/api/org/apps/${this.$routeParams.appId}/settings`).then(result => { this.backendSrv.get(`/api/org/apps/${this.$routeParams.appId}/settings`).then(result => {
this.appModel = result; this.appModel = result;
this.includedPanels = _.where(result.includes, {type: 'panel'});
}); });
} }
......
...@@ -56,7 +56,10 @@ ...@@ -56,7 +56,10 @@
Panels Panels
</div> </div>
<ul> <ul>
<li><em class="small">None</em></li> <li ng-show="!ctrl.includedPanels.length"><em class="small">None</em></li>
<li ng-repeat="panel in ctrl.includedPanels">
{{panel.name}}
</li>
</ul> </ul>
</div> </div>
<div class="simple-box-body simple-box-column"> <div class="simple-box-body simple-box-column">
......
...@@ -3,13 +3,21 @@ ...@@ -3,13 +3,21 @@
import angular from 'angular'; import angular from 'angular';
import config from 'app/core/config'; import config from 'app/core/config';
import {unknownPanelDirective} from '../../plugins/panel/unknown/module';
/** @ngInject */ /** @ngInject */
function panelLoader($parse, dynamicDirectiveSrv) { function panelLoader($parse, dynamicDirectiveSrv) {
return dynamicDirectiveSrv.create({ return dynamicDirectiveSrv.create({
directive: scope => { directive: scope => {
let modulePath = config.panels[scope.panel.type].module; let panelInfo = config.panels[scope.panel.type];
if (!panelInfo) {
return Promise.resolve({
name: 'panel-directive-' + scope.panel.type,
fn: unknownPanelDirective
});
}
return System.import(modulePath).then(function(panelModule) { return System.import(panelInfo.module).then(function(panelModule) {
return { return {
name: 'panel-directive-' + scope.panel.type, name: 'panel-directive-' + scope.panel.type,
fn: panelModule.panel, fn: panelModule.panel,
......
///<reference path="../../../headers/common.d.ts" />
export function unknownPanelDirective() {
return {
restrict: 'E',
template: `
<grafana-panel>
<div class="text-center" style="padding-top: 2rem">
Unknown panel type: <strong>{{panel.type}}</strong>
</div>
</grafana-panel>
`,
};
}
@simpleBoxBorderWidth: 0.2rem; @simpleBoxBorderWidth: 0.2rem;
@simpleBoxMargin: 1.5rem; @simpleBoxMargin: 1.5rem;
@simpleBoxBodyPadding: 0.5rem 0 0.5rem 1rem; @simpleBoxBodyPadding: 1rem 0 0.5rem 1rem;
.simple-box { .simple-box {
margin-top: @simpleBoxMargin; margin-top: @simpleBoxMargin;
......
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