Commit 8db7cf49 by Torkel Ödegaard

feat(plugins): began refactoring AppSettings -> PluginSettings, and have the…

feat(plugins): began refactoring AppSettings -> PluginSettings, and have the plugins list view and plugin edit view be common for all plugins
parent 30f3b55b
...@@ -126,9 +126,9 @@ func Register(r *macaron.Macaron) { ...@@ -126,9 +126,9 @@ func Register(r *macaron.Macaron) {
r.Patch("/invites/:code/revoke", wrap(RevokeInvite)) r.Patch("/invites/:code/revoke", wrap(RevokeInvite))
// apps // apps
r.Get("/apps", wrap(GetOrgAppsList)) r.Get("/plugins", wrap(GetPluginList))
r.Get("/apps/:appId/settings", wrap(GetAppSettingsById)) r.Get("/plugins/:pluginId/settings", wrap(GetPluginSettingById))
r.Post("/apps/:appId/settings", bind(m.UpdateAppSettingsCmd{}), wrap(UpdateAppSettings)) r.Post("/plugins/:pluginId/settings", bind(m.UpdatePluginSettingCmd{}), wrap(UpdatePluginSetting))
}, reqOrgAdmin) }, reqOrgAdmin)
// create new org // create new org
......
package api
import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/middleware"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
)
func GetOrgAppsList(c *middleware.Context) Response {
orgApps, err := plugins.GetOrgAppSettings(c.OrgId)
if err != nil {
return ApiError(500, "Failed to list of apps", err)
}
result := make([]*dtos.AppSettings, 0)
for _, app := range plugins.Apps {
orgApp := orgApps[app.Id]
result = append(result, dtos.NewAppSettingsDto(app, orgApp))
}
return Json(200, result)
}
func GetAppSettingsById(c *middleware.Context) Response {
appId := c.Params(":appId")
if pluginDef, exists := plugins.Apps[appId]; !exists {
return ApiError(404, "PluginId not found, no installed plugin with that id", nil)
} else {
orgApps, err := plugins.GetOrgAppSettings(c.OrgId)
if err != nil {
return ApiError(500, "Failed to get org app settings ", nil)
}
orgApp := orgApps[appId]
return Json(200, dtos.NewAppSettingsDto(pluginDef, orgApp))
}
}
func UpdateAppSettings(c *middleware.Context, cmd m.UpdateAppSettingsCmd) Response {
appId := c.Params(":appId")
cmd.OrgId = c.OrgId
cmd.AppId = appId
if _, ok := plugins.Apps[cmd.AppId]; !ok {
return ApiError(404, "App type not installed.", nil)
}
err := bus.Dispatch(&cmd)
if err != nil {
return ApiError(500, "Failed to update App Plugin", err)
}
return ApiSuccess("App updated")
}
...@@ -5,7 +5,7 @@ import ( ...@@ -5,7 +5,7 @@ import (
"github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins"
) )
type AppSettings struct { type PluginSetting struct {
Name string `json:"name"` Name string `json:"name"`
AppId string `json:"appId"` AppId string `json:"appId"`
Enabled bool `json:"enabled"` Enabled bool `json:"enabled"`
...@@ -18,8 +18,17 @@ type AppSettings struct { ...@@ -18,8 +18,17 @@ type AppSettings struct {
JsonData map[string]interface{} `json:"jsonData"` JsonData map[string]interface{} `json:"jsonData"`
} }
func NewAppSettingsDto(def *plugins.AppPlugin, data *models.AppSettings) *AppSettings { type PluginListItem struct {
dto := &AppSettings{ 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"`
}
func NewPluginSettingDto(def *plugins.AppPlugin, data *models.PluginSetting) *PluginSetting {
dto := &PluginSetting{
AppId: def.Id, AppId: def.Id,
Name: def.Name, Name: def.Name,
Info: &def.Info, Info: &def.Info,
......
package api
import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/middleware"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
)
func GetPluginList(c *middleware.Context) Response {
pluginSettingsMap, err := plugins.GetPluginSettings(c.OrgId)
if err != nil {
return ApiError(500, "Failed to get list of plugins", err)
}
result := make([]*dtos.PluginListItem, 0)
for _, pluginDef := range plugins.Plugins {
listItem := &dtos.PluginListItem{
PluginId: pluginDef.Id,
Name: pluginDef.Name,
Type: pluginDef.Type,
Info: &pluginDef.Info,
}
if pluginSetting, exists := pluginSettingsMap[pluginDef.Id]; exists {
listItem.Enabled = pluginSetting.Enabled
listItem.Pinned = pluginSetting.Pinned
}
result = append(result, listItem)
}
return Json(200, result)
}
func GetPluginSettingById(c *middleware.Context) Response {
pluginId := c.Params(":pluginId")
if pluginDef, exists := plugins.Apps[pluginId]; !exists {
return ApiError(404, "PluginId not found, no installed plugin with that id", nil)
} else {
query := m.GetPluginSettingByIdQuery{PluginId: pluginId, OrgId: c.OrgId}
if err := bus.Dispatch(&query); err != nil {
return ApiError(500, "Failed to get login settings", nil)
}
return Json(200, dtos.NewPluginSettingDto(pluginDef, query.Result))
}
}
func UpdatePluginSetting(c *middleware.Context, cmd m.UpdatePluginSettingCmd) Response {
pluginId := c.Params(":pluginId")
cmd.OrgId = c.OrgId
cmd.PluginId = pluginId
if _, ok := plugins.Apps[cmd.PluginId]; !ok {
return ApiError(404, "Plugin not installed.", nil)
}
if err := bus.Dispatch(&cmd); err != nil {
return ApiError(500, "Failed to update plugin setting", err)
}
return ApiSuccess("Plugin settings updated")
}
...@@ -26,7 +26,7 @@ type templateData struct { ...@@ -26,7 +26,7 @@ type templateData struct {
func getHeaders(route *plugins.AppPluginRoute, orgId int64, appId string) (http.Header, error) { func getHeaders(route *plugins.AppPluginRoute, orgId int64, appId string) (http.Header, error) {
result := http.Header{} result := http.Header{}
query := m.GetAppSettingByAppIdQuery{OrgId: orgId, AppId: appId} query := m.GetPluginSettingByIdQuery{OrgId: orgId, PluginId: appId}
if err := bus.Dispatch(&query); err != nil { if err := bus.Dispatch(&query); err != nil {
return nil, err return nil, err
......
...@@ -9,12 +9,12 @@ import ( ...@@ -9,12 +9,12 @@ import (
) )
var ( var (
ErrAppSettingNotFound = errors.New("AppSetting not found") ErrPluginSettingNotFound = errors.New("Plugin setting not found")
) )
type AppSettings struct { type PluginSetting struct {
Id int64 Id int64
AppId string PluginId string
OrgId int64 OrgId int64
Enabled bool Enabled bool
Pinned bool Pinned bool
...@@ -39,17 +39,17 @@ func (s SecureJsonData) Decrypt() map[string]string { ...@@ -39,17 +39,17 @@ func (s SecureJsonData) Decrypt() map[string]string {
// COMMANDS // COMMANDS
// Also acts as api DTO // Also acts as api DTO
type UpdateAppSettingsCmd struct { type UpdatePluginSettingCmd struct {
Enabled bool `json:"enabled"` Enabled bool `json:"enabled"`
Pinned bool `json:"pinned"` Pinned bool `json:"pinned"`
JsonData map[string]interface{} `json:"jsonData"` JsonData map[string]interface{} `json:"jsonData"`
SecureJsonData map[string]string `json:"secureJsonData"` SecureJsonData map[string]string `json:"secureJsonData"`
AppId string `json:"-"` PluginId string `json:"-"`
OrgId int64 `json:"-"` OrgId int64 `json:"-"`
} }
func (cmd *UpdateAppSettingsCmd) GetEncryptedJsonData() SecureJsonData { func (cmd *UpdatePluginSettingCmd) GetEncryptedJsonData() SecureJsonData {
encrypted := make(SecureJsonData) encrypted := make(SecureJsonData)
for key, data := range cmd.SecureJsonData { for key, data := range cmd.SecureJsonData {
encrypted[key] = util.Encrypt([]byte(data), setting.SecretKey) encrypted[key] = util.Encrypt([]byte(data), setting.SecretKey)
...@@ -59,13 +59,13 @@ func (cmd *UpdateAppSettingsCmd) GetEncryptedJsonData() SecureJsonData { ...@@ -59,13 +59,13 @@ func (cmd *UpdateAppSettingsCmd) GetEncryptedJsonData() SecureJsonData {
// --------------------- // ---------------------
// QUERIES // QUERIES
type GetAppSettingsQuery struct { type GetPluginSettingsQuery struct {
OrgId int64 OrgId int64
Result []*AppSettings Result []*PluginSetting
} }
type GetAppSettingByAppIdQuery struct { type GetPluginSettingByIdQuery struct {
AppId string PluginId string
OrgId int64 OrgId int64
Result *AppSettings Result *PluginSetting
} }
...@@ -5,44 +5,40 @@ import ( ...@@ -5,44 +5,40 @@ import (
m "github.com/grafana/grafana/pkg/models" m "github.com/grafana/grafana/pkg/models"
) )
func GetOrgAppSettings(orgId int64) (map[string]*m.AppSettings, error) { func GetPluginSettings(orgId int64) (map[string]*m.PluginSetting, error) {
query := m.GetAppSettingsQuery{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
} }
orgAppsMap := make(map[string]*m.AppSettings) pluginMap := make(map[string]*m.PluginSetting)
for _, orgApp := range query.Result { for _, plug := range query.Result {
orgAppsMap[orgApp.AppId] = orgApp pluginMap[plug.PluginId] = plug
} }
return orgAppsMap, nil return pluginMap, nil
} }
func GetEnabledPlugins(orgId int64) (*EnabledPlugins, error) { func GetEnabledPlugins(orgId int64) (*EnabledPlugins, error) {
enabledPlugins := NewEnabledPlugins() enabledPlugins := NewEnabledPlugins()
orgApps, err := GetOrgAppSettings(orgId) orgPlugins, err := GetPluginSettings(orgId)
if err != nil { if err != nil {
return nil, err return nil, err
} }
enabledApps := make(map[string]bool) enabledApps := make(map[string]bool)
for appId, installedApp := range Apps { for pluginId, app := range Apps {
var app AppPlugin
app = *installedApp
// check if the app is stored in the DB for this org and if so, use the if b, ok := orgPlugins[pluginId]; ok {
// state stored there.
if b, ok := orgApps[appId]; ok {
app.Enabled = b.Enabled app.Enabled = b.Enabled
app.Pinned = b.Pinned app.Pinned = b.Pinned
} }
if app.Enabled { if app.Enabled {
enabledApps[app.Id] = true enabledApps[pluginId] = true
enabledPlugins.Apps = append(enabledPlugins.Apps, &app) enabledPlugins.Apps = append(enabledPlugins.Apps, app)
} }
} }
......
...@@ -4,12 +4,12 @@ import . "github.com/grafana/grafana/pkg/services/sqlstore/migrator" ...@@ -4,12 +4,12 @@ import . "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
func addAppSettingsMigration(mg *Migrator) { func addAppSettingsMigration(mg *Migrator) {
appSettingsV2 := Table{ pluginSettingTable := Table{
Name: "app_settings", Name: "plugin_setting",
Columns: []*Column{ Columns: []*Column{
{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true}, {Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
{Name: "org_id", Type: DB_BigInt, Nullable: true}, {Name: "org_id", Type: DB_BigInt, Nullable: true},
{Name: "app_id", Type: DB_NVarchar, Length: 255, Nullable: false}, {Name: "plugin_id", Type: DB_NVarchar, Length: 255, Nullable: false},
{Name: "enabled", Type: DB_Bool, Nullable: false}, {Name: "enabled", Type: DB_Bool, Nullable: false},
{Name: "pinned", Type: DB_Bool, Nullable: false}, {Name: "pinned", Type: DB_Bool, Nullable: false},
{Name: "json_data", Type: DB_Text, Nullable: true}, {Name: "json_data", Type: DB_Text, Nullable: true},
...@@ -18,14 +18,12 @@ func addAppSettingsMigration(mg *Migrator) { ...@@ -18,14 +18,12 @@ func addAppSettingsMigration(mg *Migrator) {
{Name: "updated", Type: DB_DateTime, Nullable: false}, {Name: "updated", Type: DB_DateTime, Nullable: false},
}, },
Indices: []*Index{ Indices: []*Index{
{Cols: []string{"org_id", "app_id"}, Type: UniqueIndex}, {Cols: []string{"org_id", "plugin_id"}, Type: UniqueIndex},
}, },
} }
mg.AddMigration("Drop old table app_settings v1", NewDropTableMigration("app_settings")) mg.AddMigration("create plugin_setting table", NewAddTableMigration(pluginSettingTable))
mg.AddMigration("create app_settings table v2", NewAddTableMigration(appSettingsV2))
//------- indexes ------------------ //------- indexes ------------------
addTableIndicesMigrations(mg, "v3", appSettingsV2) addTableIndicesMigrations(mg, "v1", pluginSettingTable)
} }
...@@ -10,40 +10,40 @@ import ( ...@@ -10,40 +10,40 @@ import (
) )
func init() { func init() {
bus.AddHandler("sql", GetAppSettings) bus.AddHandler("sql", GetPluginSettings)
bus.AddHandler("sql", GetAppSettingByAppId) bus.AddHandler("sql", GetPluginSettingById)
bus.AddHandler("sql", UpdateAppSettings) bus.AddHandler("sql", UpdatePluginSetting)
} }
func GetAppSettings(query *m.GetAppSettingsQuery) error { func GetPluginSettings(query *m.GetPluginSettingsQuery) error {
sess := x.Where("org_id=?", query.OrgId) sess := x.Where("org_id=?", query.OrgId)
query.Result = make([]*m.AppSettings, 0) query.Result = make([]*m.PluginSetting, 0)
return sess.Find(&query.Result) return sess.Find(&query.Result)
} }
func GetAppSettingByAppId(query *m.GetAppSettingByAppIdQuery) error { func GetPluginSettingById(query *m.GetPluginSettingByIdQuery) error {
appSetting := m.AppSettings{OrgId: query.OrgId, AppId: query.AppId} pluginSetting := m.PluginSetting{OrgId: query.OrgId, PluginId: query.PluginId}
has, err := x.Get(&appSetting) has, err := x.Get(&pluginSetting)
if err != nil { if err != nil {
return err return err
} else if has == false { } else if has == false {
return m.ErrAppSettingNotFound return m.ErrPluginSettingNotFound
} }
query.Result = &appSetting query.Result = &pluginSetting
return nil return nil
} }
func UpdateAppSettings(cmd *m.UpdateAppSettingsCmd) error { func UpdatePluginSetting(cmd *m.UpdatePluginSettingCmd) error {
return inTransaction2(func(sess *session) error { return inTransaction2(func(sess *session) error {
var app m.AppSettings var pluginSetting m.PluginSetting
exists, err := sess.Where("org_id=? and app_id=?", cmd.OrgId, cmd.AppId).Get(&app) exists, err := sess.Where("org_id=? and plugin_id=?", cmd.OrgId, cmd.PluginId).Get(&pluginSetting)
sess.UseBool("enabled") sess.UseBool("enabled")
sess.UseBool("pinned") sess.UseBool("pinned")
if !exists { if !exists {
app = m.AppSettings{ pluginSetting = m.PluginSetting{
AppId: cmd.AppId, PluginId: cmd.PluginId,
OrgId: cmd.OrgId, OrgId: cmd.OrgId,
Enabled: cmd.Enabled, Enabled: cmd.Enabled,
Pinned: cmd.Pinned, Pinned: cmd.Pinned,
...@@ -52,18 +52,18 @@ func UpdateAppSettings(cmd *m.UpdateAppSettingsCmd) error { ...@@ -52,18 +52,18 @@ func UpdateAppSettings(cmd *m.UpdateAppSettingsCmd) error {
Created: time.Now(), Created: time.Now(),
Updated: time.Now(), Updated: time.Now(),
} }
_, err = sess.Insert(&app) _, err = sess.Insert(&pluginSetting)
return err return err
} else { } else {
for key, data := range cmd.SecureJsonData { for key, data := range cmd.SecureJsonData {
app.SecureJsonData[key] = util.Encrypt([]byte(data), setting.SecretKey) pluginSetting.SecureJsonData[key] = util.Encrypt([]byte(data), setting.SecretKey)
} }
app.SecureJsonData = cmd.GetEncryptedJsonData() pluginSetting.SecureJsonData = cmd.GetEncryptedJsonData()
app.Updated = time.Now() pluginSetting.Updated = time.Now()
app.Enabled = cmd.Enabled pluginSetting.Enabled = cmd.Enabled
app.JsonData = cmd.JsonData pluginSetting.JsonData = cmd.JsonData
app.Pinned = cmd.Pinned pluginSetting.Pinned = cmd.Pinned
_, err = sess.Id(app.Id).Update(&app) _, err = sess.Id(pluginSetting.Id).Update(&pluginSetting)
return err return err
} }
}) })
......
...@@ -11,7 +11,7 @@ function setupAngularRoutes($routeProvider, $locationProvider) { ...@@ -11,7 +11,7 @@ function setupAngularRoutes($routeProvider, $locationProvider) {
$locationProvider.html5Mode(true); $locationProvider.html5Mode(true);
var loadOrgBundle = new BundleLoader('app/features/org/all'); var loadOrgBundle = new BundleLoader('app/features/org/all');
var loadAppsBundle = new BundleLoader('app/features/apps/all'); var loadPluginsBundle = new BundleLoader('app/features/plugins/all');
var loadAdminBundle = new BundleLoader('app/features/admin/admin'); var loadAdminBundle = new BundleLoader('app/features/admin/admin');
$routeProvider $routeProvider
...@@ -165,23 +165,23 @@ function setupAngularRoutes($routeProvider, $locationProvider) { ...@@ -165,23 +165,23 @@ function setupAngularRoutes($routeProvider, $locationProvider) {
controller : 'SnapshotsCtrl', controller : 'SnapshotsCtrl',
controllerAs: 'ctrl', controllerAs: 'ctrl',
}) })
.when('/apps', { .when('/plugins', {
templateUrl: 'public/app/features/apps/partials/list.html', templateUrl: 'public/app/features/plugins/partials/list.html',
controller: 'AppListCtrl', controller: 'PluginListCtrl',
controllerAs: 'ctrl', controllerAs: 'ctrl',
resolve: loadAppsBundle, resolve: loadPluginsBundle,
}) })
.when('/apps/:appId/edit', { .when('/plugins/:pluginId/edit', {
templateUrl: 'public/app/features/apps/partials/edit.html', templateUrl: 'public/app/features/plugins/partials/edit.html',
controller: 'AppEditCtrl', controller: 'PluginEditCtrl',
controllerAs: 'ctrl', controllerAs: 'ctrl',
resolve: loadAppsBundle, resolve: loadPluginsBundle,
}) })
.when('/apps/:appId/page/:slug', { .when('/plugins/:pluginId/page/:slug', {
templateUrl: 'public/app/features/apps/partials/page.html', templateUrl: 'public/app/features/plugins/partials/page.html',
controller: 'AppPageCtrl', controller: 'AppPageCtrl',
controllerAs: 'ctrl', controllerAs: 'ctrl',
resolve: loadAppsBundle, resolve: loadPluginsBundle,
}) })
.when('/global-alerts', { .when('/global-alerts', {
templateUrl: 'public/app/features/dashboard/partials/globalAlerts.html', templateUrl: 'public/app/features/dashboard/partials/globalAlerts.html',
......
<navbar title="Plugins" icon="icon-gf icon-gf-apps" title-url="apps">
</navbar>
<div class="page-container">
<div class="page-header">
<h1>Plugins</h1>
</div>
<div ng-if="!ctrl.apps">
<em>No apps defined</em>
</div>
<ul class="filter-list">
<li ng-repeat="app in ctrl.apps">
<ul class="filter-list-card">
<li class="filter-list-card-image">
<img ng-src="{{app.info.logos.small}}">
</li>
<li>
<div class="filter-list-card-controls">
<div class="filter-list-card-config">
<a href="apps/{{app.appId}}/edit">
<i class="fa fa-cog"></i>
</a>
</div>
</div>
<span class="filter-list-card-title">
<a href="apps/{{app.appId}}/edit">
{{app.name}}
</a>
&nbsp; &nbsp;
<span class="label label-info" ng-if="app.enabled">
Enabled
</span>
&nbsp;
<span class="label label-info" ng-if="app.pinned">
Pinned
</span>
</span>
<span class="filter-list-card-status">
<span class="filter-list-card-state">Dashboards: 1</span>
</span>
</li>
</ul>
</li>
</ul>
</div>
...@@ -3,19 +3,19 @@ ...@@ -3,19 +3,19 @@
import angular from 'angular'; import angular from 'angular';
import _ from 'lodash'; import _ from 'lodash';
export class AppEditCtrl { export class PluginEditCtrl {
appModel: any; model: any;
appId: any; pluginId: any;
includedPanels: any; includedPanels: any;
includedDatasources: any; includedDatasources: any;
/** @ngInject */ /** @ngInject */
constructor(private backendSrv: any, private $routeParams: any) { constructor(private backendSrv: any, private $routeParams: any) {
this.appModel = {}; this.model = {};
this.appId = $routeParams.appId; this.pluginId = $routeParams.pluginId;
this.backendSrv.get(`/api/org/apps/${this.appId}/settings`).then(result => { this.backendSrv.get(`/api/org/plugins/${this.pluginId}/settings`).then(result => {
this.appModel = result; this.model = result;
this.includedPanels = _.where(result.includes, {type: 'panel'}); this.includedPanels = _.where(result.includes, {type: 'panel'});
this.includedDatasources = _.where(result.includes, {type: 'datasource'}); this.includedDatasources = _.where(result.includes, {type: 'datasource'});
}); });
...@@ -23,15 +23,15 @@ export class AppEditCtrl { ...@@ -23,15 +23,15 @@ export class AppEditCtrl {
update() { update() {
var updateCmd = _.extend({ var updateCmd = _.extend({
appId: this.appModel.appId, pluginId: this.model.pluginId,
orgId: this.appModel.orgId, orgId: this.model.orgId,
enabled: this.appModel.enabled, enabled: this.model.enabled,
pinned: this.appModel.pinned, pinned: this.model.pinned,
jsonData: this.appModel.jsonData, jsonData: this.model.jsonData,
secureJsonData: this.appModel.secureJsonData, secureJsonData: this.model.secureJsonData,
}, {}); }, {});
this.backendSrv.post(`/api/org/apps/${this.appId}/settings`, updateCmd).then(function() { this.backendSrv.post(`/api/org/plugins/${this.pluginId}/settings`, updateCmd).then(function() {
window.location.href = window.location.href; window.location.href = window.location.href;
}); });
} }
...@@ -45,5 +45,5 @@ export class AppEditCtrl { ...@@ -45,5 +45,5 @@ export class AppEditCtrl {
} }
} }
angular.module('grafana.controllers').controller('AppEditCtrl', AppEditCtrl); angular.module('grafana.controllers').controller('PluginEditCtrl', PluginEditCtrl);
...@@ -2,16 +2,16 @@ ...@@ -2,16 +2,16 @@
import angular from 'angular'; import angular from 'angular';
export class AppListCtrl { export class PluginListCtrl {
apps: any[]; plugins: any[];
/** @ngInject */ /** @ngInject */
constructor(private backendSrv: any) { constructor(private backendSrv: any) {
this.backendSrv.get('api/org/apps').then(apps => { this.backendSrv.get('api/org/plugins').then(plugins => {
this.apps = apps; this.plugins = plugins;
}); });
} }
} }
angular.module('grafana.controllers').controller('AppListCtrl', AppListCtrl); angular.module('grafana.controllers').controller('PluginListCtrl', PluginListCtrl);
<navbar title="Plugins" icon="icon-gf icon-gf-apps" title-url="plugins">
</navbar>
<div class="page-container">
<div class="page-header">
<h1>Plugins</h1>
</div>
<table class="filter-table">
<thead>
<tr>
<th><strong>Name</strong></th>
<th><strong>Type</strong></th>
<th style="width: 60px;"></th>
<th style="width: 80px;"></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="plugin in ctrl.plugins">
<td>
<a href="plugins/{{plugin.pluginId}}/edit">
{{plugin.name}}
</a>
</td>
<td>
{{plugin.type}}
</td>
<td>
<span class="label label-info" ng-if="plugin.enabled">Enabled</span>
<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">
<i class="fa fa-edit"></i>
Edit
</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
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