mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge branch 'plugins-list-and-edit-view'
This commit is contained in:
commit
4fcca7c61b
@ -126,9 +126,9 @@ func Register(r *macaron.Macaron) {
|
||||
r.Patch("/invites/:code/revoke", wrap(RevokeInvite))
|
||||
|
||||
// apps
|
||||
r.Get("/apps", wrap(GetOrgAppsList))
|
||||
r.Get("/apps/:appId/settings", wrap(GetAppSettingsById))
|
||||
r.Post("/apps/:appId/settings", bind(m.UpdateAppSettingsCmd{}), wrap(UpdateAppSettings))
|
||||
r.Get("/plugins", wrap(GetPluginList))
|
||||
r.Get("/plugins/:pluginId/settings", wrap(GetPluginSettingById))
|
||||
r.Post("/plugins/:pluginId/settings", bind(m.UpdatePluginSettingCmd{}), wrap(UpdatePluginSetting))
|
||||
}, reqOrgAdmin)
|
||||
|
||||
// create new org
|
||||
|
@ -1,59 +0,0 @@
|
||||
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")
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
package dtos
|
||||
|
||||
import (
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
)
|
||||
|
||||
type AppSettings struct {
|
||||
Name string `json:"name"`
|
||||
AppId string `json:"appId"`
|
||||
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"`
|
||||
JsonData map[string]interface{} `json:"jsonData"`
|
||||
}
|
||||
|
||||
func NewAppSettingsDto(def *plugins.AppPlugin, data *models.AppSettings) *AppSettings {
|
||||
dto := &AppSettings{
|
||||
AppId: def.Id,
|
||||
Name: def.Name,
|
||||
Info: &def.Info,
|
||||
Module: def.Module,
|
||||
BaseUrl: def.BaseUrl,
|
||||
Pages: def.Pages,
|
||||
Includes: def.Includes,
|
||||
}
|
||||
|
||||
if data != nil {
|
||||
dto.Enabled = data.Enabled
|
||||
dto.Pinned = data.Pinned
|
||||
dto.Info = &def.Info
|
||||
dto.JsonData = data.JsonData
|
||||
}
|
||||
|
||||
return dto
|
||||
}
|
26
pkg/api/dtos/plugins.go
Normal file
26
pkg/api/dtos/plugins.go
Normal file
@ -0,0 +1,26 @@
|
||||
package dtos
|
||||
|
||||
import "github.com/grafana/grafana/pkg/plugins"
|
||||
|
||||
type PluginSetting struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
PluginId string `json:"pluginId"`
|
||||
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"`
|
||||
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"`
|
||||
}
|
@ -72,7 +72,7 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
|
||||
data.MainNavLinks = append(data.MainNavLinks, &dtos.NavLink{
|
||||
Text: "Plugins",
|
||||
Icon: "icon-gf icon-gf-apps",
|
||||
Url: setting.AppSubUrl + "/apps",
|
||||
Url: setting.AppSubUrl + "/plugins",
|
||||
})
|
||||
}
|
||||
|
||||
|
88
pkg/api/plugin_setting.go
Normal file
88
pkg/api/plugin_setting.go
Normal file
@ -0,0 +1,88 @@
|
||||
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 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,
|
||||
Name: def.Name,
|
||||
Info: &def.Info,
|
||||
}
|
||||
|
||||
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}
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
if err != m.ErrPluginSettingNotFound {
|
||||
return ApiError(500, "Failed to get login settings", nil)
|
||||
}
|
||||
} else {
|
||||
dto.Enabled = query.Result.Enabled
|
||||
dto.Pinned = query.Result.Pinned
|
||||
dto.JsonData = query.Result.JsonData
|
||||
}
|
||||
|
||||
return Json(200, dto)
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
func getHeaders(route *plugins.AppPluginRoute, orgId int64, appId string) (http.Header, error) {
|
||||
result := http.Header{}
|
||||
|
||||
query := m.GetAppSettingByAppIdQuery{OrgId: orgId, AppId: appId}
|
||||
query := m.GetPluginSettingByIdQuery{OrgId: orgId, PluginId: appId}
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
return nil, err
|
||||
|
@ -9,12 +9,12 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
ErrAppSettingNotFound = errors.New("AppSetting not found")
|
||||
ErrPluginSettingNotFound = errors.New("Plugin setting not found")
|
||||
)
|
||||
|
||||
type AppSettings struct {
|
||||
type PluginSetting struct {
|
||||
Id int64
|
||||
AppId string
|
||||
PluginId string
|
||||
OrgId int64
|
||||
Enabled bool
|
||||
Pinned bool
|
||||
@ -39,17 +39,17 @@ func (s SecureJsonData) Decrypt() map[string]string {
|
||||
// COMMANDS
|
||||
|
||||
// Also acts as api DTO
|
||||
type UpdateAppSettingsCmd struct {
|
||||
type UpdatePluginSettingCmd struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Pinned bool `json:"pinned"`
|
||||
JsonData map[string]interface{} `json:"jsonData"`
|
||||
SecureJsonData map[string]string `json:"secureJsonData"`
|
||||
|
||||
AppId string `json:"-"`
|
||||
OrgId int64 `json:"-"`
|
||||
PluginId string `json:"-"`
|
||||
OrgId int64 `json:"-"`
|
||||
}
|
||||
|
||||
func (cmd *UpdateAppSettingsCmd) GetEncryptedJsonData() SecureJsonData {
|
||||
func (cmd *UpdatePluginSettingCmd) GetEncryptedJsonData() SecureJsonData {
|
||||
encrypted := make(SecureJsonData)
|
||||
for key, data := range cmd.SecureJsonData {
|
||||
encrypted[key] = util.Encrypt([]byte(data), setting.SecretKey)
|
||||
@ -59,13 +59,13 @@ func (cmd *UpdateAppSettingsCmd) GetEncryptedJsonData() SecureJsonData {
|
||||
|
||||
// ---------------------
|
||||
// QUERIES
|
||||
type GetAppSettingsQuery struct {
|
||||
type GetPluginSettingsQuery struct {
|
||||
OrgId int64
|
||||
Result []*AppSettings
|
||||
Result []*PluginSetting
|
||||
}
|
||||
|
||||
type GetAppSettingByAppIdQuery struct {
|
||||
AppId string
|
||||
OrgId int64
|
||||
Result *AppSettings
|
||||
type GetPluginSettingByIdQuery struct {
|
||||
PluginId string
|
||||
OrgId int64
|
||||
Result *PluginSetting
|
||||
}
|
@ -56,6 +56,10 @@ func (fp *FrontendPluginBase) handleModuleDefaults() {
|
||||
}
|
||||
|
||||
func evalRelativePluginUrlPath(pathStr string, pluginId string) string {
|
||||
if pathStr == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
u, _ := url.Parse(pathStr)
|
||||
if u.IsAbs() {
|
||||
return pathStr
|
||||
|
@ -5,44 +5,40 @@ import (
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
|
||||
func GetOrgAppSettings(orgId int64) (map[string]*m.AppSettings, error) {
|
||||
query := m.GetAppSettingsQuery{OrgId: orgId}
|
||||
func GetPluginSettings(orgId int64) (map[string]*m.PluginSetting, error) {
|
||||
query := m.GetPluginSettingsQuery{OrgId: orgId}
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
orgAppsMap := make(map[string]*m.AppSettings)
|
||||
for _, orgApp := range query.Result {
|
||||
orgAppsMap[orgApp.AppId] = orgApp
|
||||
pluginMap := make(map[string]*m.PluginSetting)
|
||||
for _, plug := range query.Result {
|
||||
pluginMap[plug.PluginId] = plug
|
||||
}
|
||||
|
||||
return orgAppsMap, nil
|
||||
return pluginMap, nil
|
||||
}
|
||||
|
||||
func GetEnabledPlugins(orgId int64) (*EnabledPlugins, error) {
|
||||
enabledPlugins := NewEnabledPlugins()
|
||||
orgApps, err := GetOrgAppSettings(orgId)
|
||||
orgPlugins, err := GetPluginSettings(orgId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
enabledApps := make(map[string]bool)
|
||||
|
||||
for appId, installedApp := range Apps {
|
||||
var app AppPlugin
|
||||
app = *installedApp
|
||||
for pluginId, app := range Apps {
|
||||
|
||||
// check if the app is stored in the DB for this org and if so, use the
|
||||
// state stored there.
|
||||
if b, ok := orgApps[appId]; ok {
|
||||
if b, ok := orgPlugins[pluginId]; ok {
|
||||
app.Enabled = b.Enabled
|
||||
app.Pinned = b.Pinned
|
||||
}
|
||||
|
||||
if app.Enabled {
|
||||
enabledApps[app.Id] = true
|
||||
enabledPlugins.Apps = append(enabledPlugins.Apps, &app)
|
||||
enabledApps[pluginId] = true
|
||||
enabledPlugins.Apps = append(enabledPlugins.Apps, app)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,70 +0,0 @@
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
func init() {
|
||||
bus.AddHandler("sql", GetAppSettings)
|
||||
bus.AddHandler("sql", GetAppSettingByAppId)
|
||||
bus.AddHandler("sql", UpdateAppSettings)
|
||||
}
|
||||
|
||||
func GetAppSettings(query *m.GetAppSettingsQuery) error {
|
||||
sess := x.Where("org_id=?", query.OrgId)
|
||||
|
||||
query.Result = make([]*m.AppSettings, 0)
|
||||
return sess.Find(&query.Result)
|
||||
}
|
||||
|
||||
func GetAppSettingByAppId(query *m.GetAppSettingByAppIdQuery) error {
|
||||
appSetting := m.AppSettings{OrgId: query.OrgId, AppId: query.AppId}
|
||||
has, err := x.Get(&appSetting)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if has == false {
|
||||
return m.ErrAppSettingNotFound
|
||||
}
|
||||
query.Result = &appSetting
|
||||
return nil
|
||||
}
|
||||
|
||||
func UpdateAppSettings(cmd *m.UpdateAppSettingsCmd) error {
|
||||
return inTransaction2(func(sess *session) error {
|
||||
var app m.AppSettings
|
||||
|
||||
exists, err := sess.Where("org_id=? and app_id=?", cmd.OrgId, cmd.AppId).Get(&app)
|
||||
sess.UseBool("enabled")
|
||||
sess.UseBool("pinned")
|
||||
if !exists {
|
||||
app = m.AppSettings{
|
||||
AppId: cmd.AppId,
|
||||
OrgId: cmd.OrgId,
|
||||
Enabled: cmd.Enabled,
|
||||
Pinned: cmd.Pinned,
|
||||
JsonData: cmd.JsonData,
|
||||
SecureJsonData: cmd.GetEncryptedJsonData(),
|
||||
Created: time.Now(),
|
||||
Updated: time.Now(),
|
||||
}
|
||||
_, err = sess.Insert(&app)
|
||||
return err
|
||||
} else {
|
||||
for key, data := range cmd.SecureJsonData {
|
||||
app.SecureJsonData[key] = util.Encrypt([]byte(data), setting.SecretKey)
|
||||
}
|
||||
app.SecureJsonData = cmd.GetEncryptedJsonData()
|
||||
app.Updated = time.Now()
|
||||
app.Enabled = cmd.Enabled
|
||||
app.JsonData = cmd.JsonData
|
||||
app.Pinned = cmd.Pinned
|
||||
_, err = sess.Id(app.Id).Update(&app)
|
||||
return err
|
||||
}
|
||||
})
|
||||
}
|
@ -4,12 +4,12 @@ import . "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
||||
|
||||
func addAppSettingsMigration(mg *Migrator) {
|
||||
|
||||
appSettingsV2 := Table{
|
||||
Name: "app_settings",
|
||||
pluginSettingTable := Table{
|
||||
Name: "plugin_setting",
|
||||
Columns: []*Column{
|
||||
{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: 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: "pinned", Type: DB_Bool, Nullable: false},
|
||||
{Name: "json_data", Type: DB_Text, Nullable: true},
|
||||
@ -18,14 +18,12 @@ func addAppSettingsMigration(mg *Migrator) {
|
||||
{Name: "updated", Type: DB_DateTime, Nullable: false},
|
||||
},
|
||||
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 app_settings table v2", NewAddTableMigration(appSettingsV2))
|
||||
mg.AddMigration("create plugin_setting table", NewAddTableMigration(pluginSettingTable))
|
||||
|
||||
//------- indexes ------------------
|
||||
addTableIndicesMigrations(mg, "v3", appSettingsV2)
|
||||
addTableIndicesMigrations(mg, "v1", pluginSettingTable)
|
||||
}
|
70
pkg/services/sqlstore/plugin_setting.go
Normal file
70
pkg/services/sqlstore/plugin_setting.go
Normal file
@ -0,0 +1,70 @@
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
func init() {
|
||||
bus.AddHandler("sql", GetPluginSettings)
|
||||
bus.AddHandler("sql", GetPluginSettingById)
|
||||
bus.AddHandler("sql", UpdatePluginSetting)
|
||||
}
|
||||
|
||||
func GetPluginSettings(query *m.GetPluginSettingsQuery) error {
|
||||
sess := x.Where("org_id=?", query.OrgId)
|
||||
|
||||
query.Result = make([]*m.PluginSetting, 0)
|
||||
return sess.Find(&query.Result)
|
||||
}
|
||||
|
||||
func GetPluginSettingById(query *m.GetPluginSettingByIdQuery) error {
|
||||
pluginSetting := m.PluginSetting{OrgId: query.OrgId, PluginId: query.PluginId}
|
||||
has, err := x.Get(&pluginSetting)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if has == false {
|
||||
return m.ErrPluginSettingNotFound
|
||||
}
|
||||
query.Result = &pluginSetting
|
||||
return nil
|
||||
}
|
||||
|
||||
func UpdatePluginSetting(cmd *m.UpdatePluginSettingCmd) error {
|
||||
return inTransaction2(func(sess *session) error {
|
||||
var pluginSetting m.PluginSetting
|
||||
|
||||
exists, err := sess.Where("org_id=? and plugin_id=?", cmd.OrgId, cmd.PluginId).Get(&pluginSetting)
|
||||
sess.UseBool("enabled")
|
||||
sess.UseBool("pinned")
|
||||
if !exists {
|
||||
pluginSetting = m.PluginSetting{
|
||||
PluginId: cmd.PluginId,
|
||||
OrgId: cmd.OrgId,
|
||||
Enabled: cmd.Enabled,
|
||||
Pinned: cmd.Pinned,
|
||||
JsonData: cmd.JsonData,
|
||||
SecureJsonData: cmd.GetEncryptedJsonData(),
|
||||
Created: time.Now(),
|
||||
Updated: time.Now(),
|
||||
}
|
||||
_, err = sess.Insert(&pluginSetting)
|
||||
return err
|
||||
} else {
|
||||
for key, data := range cmd.SecureJsonData {
|
||||
pluginSetting.SecureJsonData[key] = util.Encrypt([]byte(data), setting.SecretKey)
|
||||
}
|
||||
pluginSetting.SecureJsonData = cmd.GetEncryptedJsonData()
|
||||
pluginSetting.Updated = time.Now()
|
||||
pluginSetting.Enabled = cmd.Enabled
|
||||
pluginSetting.JsonData = cmd.JsonData
|
||||
pluginSetting.Pinned = cmd.Pinned
|
||||
_, err = sess.Id(pluginSetting.Id).Update(&pluginSetting)
|
||||
return err
|
||||
}
|
||||
})
|
||||
}
|
@ -38,7 +38,6 @@ export class SideMenuCtrl {
|
||||
openUserDropdown() {
|
||||
this.orgMenu = [
|
||||
{section: 'You', cssClass: 'dropdown-menu-title'},
|
||||
{text: 'Preferences', url: this.getUrl('/profile')},
|
||||
{text: 'Profile', url: this.getUrl('/profile')},
|
||||
];
|
||||
|
||||
@ -100,6 +99,22 @@ export function sideMenuDirective() {
|
||||
bindToController: true,
|
||||
controllerAs: 'ctrl',
|
||||
scope: {},
|
||||
link: function(scope, elem) {
|
||||
// hack to hide dropdown menu
|
||||
elem.on('click.dropdown', '.dropdown-menu a', function(evt) {
|
||||
var menu = $(evt.target).parents('.dropdown-menu');
|
||||
var parent = menu.parent();
|
||||
menu.detach();
|
||||
|
||||
setTimeout(function() {
|
||||
parent.append(menu);
|
||||
}, 100);
|
||||
});
|
||||
|
||||
scope.$on("$destory", function() {
|
||||
elem.off('click.dropdown');
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -160,13 +160,13 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $
|
||||
}
|
||||
// AppConfigCtrl
|
||||
case 'app-config-ctrl': {
|
||||
let appModel = scope.ctrl.appModel;
|
||||
return System.import(appModel.module).then(function(appModule) {
|
||||
let model = scope.ctrl.model;
|
||||
return System.import(model.module).then(function(appModule) {
|
||||
return {
|
||||
baseUrl: appModel.baseUrl,
|
||||
name: 'app-config-' + appModel.appId,
|
||||
baseUrl: model.baseUrl,
|
||||
name: 'app-config-' + model.pluginId,
|
||||
bindings: {appModel: "=", appEditCtrl: "="},
|
||||
attrs: {"app-model": "ctrl.appModel", "app-edit-ctrl": "ctrl"},
|
||||
attrs: {"app-model": "ctrl.model", "app-edit-ctrl": "ctrl"},
|
||||
Component: appModule.ConfigCtrl,
|
||||
};
|
||||
});
|
||||
|
@ -11,7 +11,7 @@ function setupAngularRoutes($routeProvider, $locationProvider) {
|
||||
$locationProvider.html5Mode(true);
|
||||
|
||||
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');
|
||||
|
||||
$routeProvider
|
||||
@ -165,23 +165,23 @@ function setupAngularRoutes($routeProvider, $locationProvider) {
|
||||
controller : 'SnapshotsCtrl',
|
||||
controllerAs: 'ctrl',
|
||||
})
|
||||
.when('/apps', {
|
||||
templateUrl: 'public/app/features/apps/partials/list.html',
|
||||
controller: 'AppListCtrl',
|
||||
.when('/plugins', {
|
||||
templateUrl: 'public/app/features/plugins/partials/list.html',
|
||||
controller: 'PluginListCtrl',
|
||||
controllerAs: 'ctrl',
|
||||
resolve: loadAppsBundle,
|
||||
resolve: loadPluginsBundle,
|
||||
})
|
||||
.when('/apps/:appId/edit', {
|
||||
templateUrl: 'public/app/features/apps/partials/edit.html',
|
||||
controller: 'AppEditCtrl',
|
||||
.when('/plugins/:pluginId/edit', {
|
||||
templateUrl: 'public/app/features/plugins/partials/edit.html',
|
||||
controller: 'PluginEditCtrl',
|
||||
controllerAs: 'ctrl',
|
||||
resolve: loadAppsBundle,
|
||||
resolve: loadPluginsBundle,
|
||||
})
|
||||
.when('/apps/:appId/page/:slug', {
|
||||
templateUrl: 'public/app/features/apps/partials/page.html',
|
||||
.when('/plugins/:pluginId/page/:slug', {
|
||||
templateUrl: 'public/app/features/plugins/partials/page.html',
|
||||
controller: 'AppPageCtrl',
|
||||
controllerAs: 'ctrl',
|
||||
resolve: loadAppsBundle,
|
||||
resolve: loadPluginsBundle,
|
||||
})
|
||||
.when('/global-alerts', {
|
||||
templateUrl: 'public/app/features/dashboard/partials/globalAlerts.html',
|
||||
|
@ -1,5 +1,8 @@
|
||||
<navbar icon="fa fa-fw fa-cogs" title="Admin" title-url="admin">
|
||||
<nav-button title="Orgs" title-url="admin/orgs" icon="icon-gf icon-gf-users"></nav-button>
|
||||
<a href="admin/orgs" class="navbar-page-btn">
|
||||
<i class="icon-gf icon-gf-users"></i>
|
||||
Orgs
|
||||
</a>
|
||||
</navbar>
|
||||
|
||||
<div class="page-container">
|
||||
|
@ -1,5 +1,8 @@
|
||||
<navbar icon="fa fa-fw fa-cogs" title="Admin" title-url="admin">
|
||||
<nav-button title="Users" title-url="admin/users" icon="icon-gf icon-gf-users"></nav-button>
|
||||
<a href="admin/users" class="navbar-page-btn">
|
||||
<i class="icon-gf icon-gf-users"></i>
|
||||
Users
|
||||
</a>
|
||||
</navbar>
|
||||
|
||||
<div class="page-container">
|
||||
|
@ -1,5 +1,8 @@
|
||||
<navbar icon="fa fa-fw fa-cogs" title="Admin" title-url="admin">
|
||||
<nav-button title="Users" title-url="admin/users" icon="icon-gf icon-gf-users"></nav-button>
|
||||
<a href="admin/users" class="navbar-page-btn">
|
||||
<i class="icon-gf icon-gf-users"></i>
|
||||
Users
|
||||
</a>
|
||||
</navbar>
|
||||
|
||||
<div class="page-container">
|
||||
|
@ -1,5 +1,8 @@
|
||||
<navbar icon="fa fa-fw fa-cogs" title="Admin" title-url="admin">
|
||||
<nav-button title="Orgs" title-url="admin/orgs" icon="icon-gf icon-gf-users"></nav-button>
|
||||
<a href="admin/orgs" class="navbar-page-btn">
|
||||
<i class="icon-gf icon-gf-users"></i>
|
||||
Orgs
|
||||
</a>
|
||||
</navbar>
|
||||
|
||||
<div class="page-container">
|
||||
|
@ -1,5 +1,8 @@
|
||||
<navbar icon="fa fa-fw fa-cogs" title="Admin" title-url="admin">
|
||||
<nav-button title="Users" title-url="admin/users" icon="icon-gf icon-gf-users"></nav-button>
|
||||
<a href="admin/users" class="navbar-page-btn">
|
||||
<i class="icon-gf icon-gf-users"></i>
|
||||
Users
|
||||
</a>
|
||||
</navbar>
|
||||
|
||||
<div class="page-container">
|
||||
|
@ -1,49 +0,0 @@
|
||||
///<reference path="../../headers/common.d.ts" />
|
||||
|
||||
import angular from 'angular';
|
||||
import _ from 'lodash';
|
||||
|
||||
export class AppEditCtrl {
|
||||
appModel: any;
|
||||
appId: any;
|
||||
includedPanels: any;
|
||||
includedDatasources: any;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private backendSrv: any, private $routeParams: any) {
|
||||
this.appModel = {};
|
||||
this.appId = $routeParams.appId;
|
||||
|
||||
this.backendSrv.get(`/api/org/apps/${this.appId}/settings`).then(result => {
|
||||
this.appModel = result;
|
||||
this.includedPanels = _.where(result.includes, {type: 'panel'});
|
||||
this.includedDatasources = _.where(result.includes, {type: 'datasource'});
|
||||
});
|
||||
}
|
||||
|
||||
update() {
|
||||
var updateCmd = _.extend({
|
||||
appId: this.appModel.appId,
|
||||
orgId: this.appModel.orgId,
|
||||
enabled: this.appModel.enabled,
|
||||
pinned: this.appModel.pinned,
|
||||
jsonData: this.appModel.jsonData,
|
||||
secureJsonData: this.appModel.secureJsonData,
|
||||
}, {});
|
||||
|
||||
this.backendSrv.post(`/api/org/apps/${this.appId}/settings`, updateCmd).then(function() {
|
||||
window.location.href = window.location.href;
|
||||
});
|
||||
}
|
||||
|
||||
toggleEnabled() {
|
||||
this.update();
|
||||
}
|
||||
|
||||
togglePinned() {
|
||||
this.update();
|
||||
}
|
||||
}
|
||||
|
||||
angular.module('grafana.controllers').controller('AppEditCtrl', AppEditCtrl);
|
||||
|
@ -1,17 +0,0 @@
|
||||
///<reference path="../../headers/common.d.ts" />
|
||||
|
||||
import angular from 'angular';
|
||||
|
||||
export class AppListCtrl {
|
||||
apps: any[];
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private backendSrv: any) {
|
||||
|
||||
this.backendSrv.get('api/org/apps').then(apps => {
|
||||
this.apps = apps;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
angular.module('grafana.controllers').controller('AppListCtrl', AppListCtrl);
|
@ -1,108 +0,0 @@
|
||||
<navbar title="Plugins" title-url="Plugins" icon="icon-gf icon-gf-apps">
|
||||
</navbar>
|
||||
|
||||
<div class="page-container">
|
||||
<div class="flex-container">
|
||||
<div class="flex-column app-edit-logo-box">
|
||||
<img src="{{ctrl.appModel.info.logos.large}}">
|
||||
</div>
|
||||
<div class="flex-column">
|
||||
<h1>
|
||||
{{ctrl.appModel.name}}
|
||||
</h1>
|
||||
<div class="app-edit-description">
|
||||
{{ctrl.appModel.info.description}}<br>
|
||||
<span style="small">
|
||||
Version: {{ctrl.appModel.info.version}} Updated: {{ctrl.appModel.info.updated}}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="form-inline">
|
||||
<editor-checkbox text="Enabled" model="ctrl.appModel.enabled" change="ctrl.toggleEnabled()"></editor-checkbox>
|
||||
|
||||
<editor-checkbox text="Pinned" model="ctrl.appModel.pinned" change="ctrl.togglePinned()"></editor-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-column">
|
||||
<ul class="app-edit-links">
|
||||
<li>
|
||||
By <a href="{{ctrl.appModel.info.author.url}}" class="external-link" target="_blank">{{ctrl.appModel.info.author.name}}</a>
|
||||
</li>
|
||||
<li ng-repeat="link in ctrl.appModel.info.links">
|
||||
<a href="{{link.url}}" class="external-link" target="_blank">{{link.name}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="simple-box">
|
||||
<h3 class="simple-box-header">Included with app:</h3>
|
||||
<div class="flex-container">
|
||||
<div class="simple-box-body simple-box-column">
|
||||
<div class="simple-box-column-header">
|
||||
<i class="fa fa-th-large"></i>
|
||||
Dashboards
|
||||
</div>
|
||||
<ul>
|
||||
<li><em class="small">None</em></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="simple-box-body simple-box-column">
|
||||
<div class="simple-box-column-header">
|
||||
<i class="fa fa-line-chart"></i>
|
||||
Panels
|
||||
</div>
|
||||
<ul>
|
||||
<li ng-show="!ctrl.includedPanels.length"><em class="small">None</em></li>
|
||||
<li ng-repeat="panel in ctrl.includedPanels">
|
||||
{{panel.name}}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="simple-box-body simple-box-column">
|
||||
<div class="simple-box-column-header">
|
||||
<i class="fa fa-database"></i>
|
||||
Datasources
|
||||
</div>
|
||||
<ul>
|
||||
<li ng-show="!ctrl.includedDatasources.length"><em class="small">None</em></li>
|
||||
<li ng-repeat="ds in ctrl.includedDatasources">
|
||||
{{ds.name}}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="simple-box-body simple-box-column">
|
||||
<div class="simple-box-column-header">
|
||||
<i class="fa fa-files-o"></i>
|
||||
Pages
|
||||
</div>
|
||||
<ul>
|
||||
<li ng-repeat="page in ctrl.appModel.pages">
|
||||
<a href="apps/{{ctrl.appId}}/page/{{page.slug}}" class="external-link">{{page.name}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="simple-box">
|
||||
<h3 class="simple-box-header">Dependencies:</h3>
|
||||
<div class="simple-box-body">
|
||||
Grafana 2.6.x
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="simple-box">
|
||||
<h3 class="simple-box-header">Configuration:</h3>
|
||||
<div class="simple-box-body">
|
||||
<div ng-if="ctrl.appModel.appId">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
</div>
|
@ -1,47 +0,0 @@
|
||||
<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>
|
||||
|
||||
<span class="label label-info" ng-if="app.enabled">
|
||||
Enabled
|
||||
</span>
|
||||
|
||||
<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>
|
@ -22,8 +22,8 @@
|
||||
<table class="filter-table" ng-if="ctrl.datasources.length > 0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><strong>Name</strong></th>
|
||||
<th><strong>Url</strong></th>
|
||||
<th><strong>name</strong></th>
|
||||
<th><strong>url</strong></th>
|
||||
<th style="width: 60px;"></th>
|
||||
<th style="width: 85px;"></th>
|
||||
<th style="width: 44px;"></th>
|
||||
|
51
public/app/features/plugins/edit_ctrl.ts
Normal file
51
public/app/features/plugins/edit_ctrl.ts
Normal file
@ -0,0 +1,51 @@
|
||||
///<reference path="../../headers/common.d.ts" />
|
||||
|
||||
import angular from 'angular';
|
||||
import _ from 'lodash';
|
||||
|
||||
export class PluginEditCtrl {
|
||||
model: any;
|
||||
pluginId: any;
|
||||
includedPanels: any;
|
||||
includedDatasources: any;
|
||||
tabIndex: number;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private backendSrv: any, private $routeParams: any) {
|
||||
this.model = {};
|
||||
this.pluginId = $routeParams.pluginId;
|
||||
this.tabIndex = 0;
|
||||
|
||||
this.backendSrv.get(`/api/org/plugins/${this.pluginId}/settings`).then(result => {
|
||||
this.model = result;
|
||||
this.includedPanels = _.where(result.includes, {type: 'panel'});
|
||||
this.includedDatasources = _.where(result.includes, {type: 'datasource'});
|
||||
});
|
||||
}
|
||||
|
||||
update() {
|
||||
var updateCmd = _.extend({
|
||||
pluginId: this.model.pluginId,
|
||||
orgId: this.model.orgId,
|
||||
enabled: this.model.enabled,
|
||||
pinned: this.model.pinned,
|
||||
jsonData: this.model.jsonData,
|
||||
secureJsonData: this.model.secureJsonData,
|
||||
}, {});
|
||||
|
||||
this.backendSrv.post(`/api/org/plugins/${this.pluginId}/settings`, updateCmd).then(function() {
|
||||
window.location.href = window.location.href;
|
||||
});
|
||||
}
|
||||
|
||||
toggleEnabled() {
|
||||
this.update();
|
||||
}
|
||||
|
||||
togglePinned() {
|
||||
this.update();
|
||||
}
|
||||
}
|
||||
|
||||
angular.module('grafana.controllers').controller('PluginEditCtrl', PluginEditCtrl);
|
||||
|
17
public/app/features/plugins/list_ctrl.ts
Normal file
17
public/app/features/plugins/list_ctrl.ts
Normal file
@ -0,0 +1,17 @@
|
||||
///<reference path="../../headers/common.d.ts" />
|
||||
|
||||
import angular from 'angular';
|
||||
|
||||
export class PluginListCtrl {
|
||||
plugins: any[];
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private backendSrv: any) {
|
||||
|
||||
this.backendSrv.get('api/org/plugins').then(plugins => {
|
||||
this.plugins = plugins;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
angular.module('grafana.controllers').controller('PluginListCtrl', PluginListCtrl);
|
185
public/app/features/plugins/partials/edit.html
Normal file
185
public/app/features/plugins/partials/edit.html
Normal file
@ -0,0 +1,185 @@
|
||||
<navbar title="Plugins" title-url="plugins" icon="icon-gf icon-gf-apps">
|
||||
<a href="plugins/apps" class="navbar-page-btn">
|
||||
<i class="fa fa-chevron-right"></i>
|
||||
Apps
|
||||
</a>
|
||||
</navbar>
|
||||
|
||||
<div class="page-container">
|
||||
<div class="plugin-header">
|
||||
<span ng-show="ctrl.model.info.logos.large" class="plugin-header-logo">
|
||||
<img src="{{ctrl.model.info.logos.large}}">
|
||||
</span>
|
||||
<div class="plugin-header-info-block">
|
||||
<h1 class="plugin-header-name">{{ctrl.model.name}}</h1>
|
||||
<div class="plugin-header-author">By {{ctrl.model.info.author.name}}</div>
|
||||
<div class="plugin-header-stamps">
|
||||
<span class="plugin-header-stamps-type">
|
||||
<i class="icon-gf icon-gf-apps"></i> {{ctrl.model.type}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul class="nav nav-tabs nav-tabs-alt">
|
||||
<li ng-repeat="tab in ::['Overview', 'Details', 'Config']" ng-class="{active: ctrl.tabIndex === $index}">
|
||||
<a ng-click="ctrl.tabIndex= $index">
|
||||
{{::tab}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="page-body">
|
||||
<div class="tab-content page-content-with-sidebar" ng-if="ctrl.tabIndex === 0">
|
||||
README.md
|
||||
</div>
|
||||
|
||||
<div class="tab-content page-content-with-sidebar" ng-if="ctrl.tabIndex === 1">
|
||||
Details
|
||||
</div>
|
||||
|
||||
<div class="tab-content page-content-with-sidebar" ng-if="ctrl.tabIndex === 2">
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form">
|
||||
<editor-checkbox text="Enabled" model="ctrl.model.enabled" change="ctrl.toggleEnabled()"></editor-checkbox>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<editor-checkbox text="Pinned" model="ctrl.model.pinned" change="ctrl.togglePinned()"></editor-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-if="ctrl.model.pluginId">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<aside class="page-sidebar">
|
||||
<section class="page-sidebar-section">
|
||||
<h4>Version</h4>
|
||||
<span>1.0.1</span>
|
||||
</section>
|
||||
<section class="page-sidebar-section" ng-show="ctrl.model.type === 'app'">
|
||||
<h5>Includes</h4>
|
||||
<ul class="ui-list">
|
||||
<li ng-show="!ctrl.includedPanels.length"><em>None</em></li>
|
||||
<li ng-repeat="panel in ctrl.includedPanels">
|
||||
{{panel.name}}
|
||||
</li>
|
||||
<li ng-repeat="ds in ctrl.includedDatasources">
|
||||
{{ds.name}}
|
||||
</li>
|
||||
<li ng-repeat="page in ctrl.model.pages">
|
||||
<a href="apps/{{ctrl.appId}}/page/{{page.slug}}" class="external-link">{{page.name}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
<section class="page-sidebar-section">
|
||||
<h5>Dependencies</h4>
|
||||
<span>TODO</span>
|
||||
</section>
|
||||
<section class="page-sidebar-section">
|
||||
<h5>Links</h4>
|
||||
<ul class="ui-list">
|
||||
<li ng-repeat="link in ctrl.model.info.links">
|
||||
<a href="{{link.url}}" class="external-link" target="_blank">{{link.name}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <div class="app-edit-description"> -->
|
||||
<!-- {{ctrl.model.info.description}}<br> -->
|
||||
<!-- <span style="small"> -->
|
||||
<!-- Version: {{ctrl.model.info.version}} &nbsp; &nbsp; Updated: {{ctrl.model.info.updated}} -->
|
||||
<!-- </span> -->
|
||||
<!-- </div> -->
|
||||
<!-- -->
|
||||
<!-- </div> -->
|
||||
<!-- <div class="flex-column"> -->
|
||||
<!-- <ul class="app-edit-links"> -->
|
||||
<!-- <li> -->
|
||||
<!-- By <a href="{{ctrl.model.info.author.url}}" class="external-link" target="_blank">{{ctrl.model.info.author.name}}</a> -->
|
||||
<!-- </li> -->
|
||||
<!-- <li ng-repeat="link in ctrl.model.info.links"> -->
|
||||
<!-- <a href="{{link.url}}" class="external-link" target="_blank">{{link.name}}</a> -->
|
||||
<!-- </li> -->
|
||||
<!-- </ul> -->
|
||||
<!-- </div> -->
|
||||
|
||||
<!-- <section class="simple-box"> -->
|
||||
<!-- <h3 class="simple-box-header">Included with app:</h3> -->
|
||||
<!-- <div class="flex-container"> -->
|
||||
<!-- <div class="simple-box-body simple-box-column"> -->
|
||||
<!-- <div class="simple-box-column-header"> -->
|
||||
<!-- <i class="fa fa-th-large"></i> -->
|
||||
<!-- Dashboards -->
|
||||
<!-- </div> -->
|
||||
<!-- <ul> -->
|
||||
<!-- <li><em class="small">None</em></li> -->
|
||||
<!-- </ul> -->
|
||||
<!-- </div> -->
|
||||
<!-- <div class="simple-box-body simple-box-column"> -->
|
||||
<!-- <div class="simple-box-column-header"> -->
|
||||
<!-- <i class="fa fa-line-chart"></i> -->
|
||||
<!-- Panels -->
|
||||
<!-- </div> -->
|
||||
<!-- <ul> -->
|
||||
<!-- <li ng-show="!ctrl.includedPanels.length"><em class="small">None</em></li> -->
|
||||
<!-- <li ng-repeat="panel in ctrl.includedPanels"> -->
|
||||
<!-- {{panel.name}} -->
|
||||
<!-- </li> -->
|
||||
<!-- </ul> -->
|
||||
<!-- </div> -->
|
||||
<!-- <div class="simple-box-body simple-box-column"> -->
|
||||
<!-- <div class="simple-box-column-header"> -->
|
||||
<!-- <i class="fa fa-database"></i> -->
|
||||
<!-- Datasources -->
|
||||
<!-- </div> -->
|
||||
<!-- <ul> -->
|
||||
<!-- <li ng-show="!ctrl.includedDatasources.length"><em class="small">None</em></li> -->
|
||||
<!-- <li ng-repeat="ds in ctrl.includedDatasources"> -->
|
||||
<!-- {{ds.name}} -->
|
||||
<!-- </li> -->
|
||||
<!-- </ul> -->
|
||||
<!-- </div> -->
|
||||
<!-- <div class="simple-box-body simple-box-column"> -->
|
||||
<!-- <div class="simple-box-column-header"> -->
|
||||
<!-- <i class="fa fa-files-o"></i> -->
|
||||
<!-- Pages -->
|
||||
<!-- </div> -->
|
||||
<!-- <ul> -->
|
||||
<!-- <li ng-repeat="page in ctrl.model.pages"> -->
|
||||
<!-- <a href="apps/{{ctrl.appId}}/page/{{page.slug}}" class="external-link">{{page.name}}</a> -->
|
||||
<!-- </li> -->
|
||||
<!-- </ul> -->
|
||||
<!-- </div> -->
|
||||
<!-- -->
|
||||
<!-- </div> -->
|
||||
<!-- </section> -->
|
||||
<!-- -->
|
||||
<!-- <section class="simple-box"> -->
|
||||
<!-- <h3 class="simple-box-header">Dependencies:</h3> -->
|
||||
<!-- <div class="simple-box-body"> -->
|
||||
<!-- Grafana 2.6.x -->
|
||||
<!-- </div> -->
|
||||
<!-- </section> -->
|
||||
<!-- -->
|
||||
<!-- <section class="simple-box"> -->
|
||||
<!-- <h3 class="simple-box-header">Configuration:</h3> -->
|
||||
<!-- <div class="simple-box-body"> -->
|
||||
<!-- <div ng-if="ctrl.model.appId"> -->
|
||||
<!-- <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> -->
|
||||
<!-- </div> -->
|
||||
<!-- </div> -->
|
||||
<!-- </section> -->
|
||||
<!-- -->
|
||||
<!-- -->
|
||||
<!-- </div> -->
|
42
public/app/features/plugins/partials/list.html
Normal file
42
public/app/features/plugins/partials/list.html
Normal file
@ -0,0 +1,42 @@
|
||||
<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>
|
@ -26,6 +26,7 @@
|
||||
@import "utils/widths";
|
||||
|
||||
// LAYOUTS
|
||||
@import "layout/lists";
|
||||
@import "layout/page";
|
||||
|
||||
// COMPONENTS
|
||||
@ -44,7 +45,6 @@
|
||||
@import "components/tagsinput";
|
||||
@import "components/tables_lists";
|
||||
@import "components/search";
|
||||
@import "components/dashboard";
|
||||
@import "components/tightform";
|
||||
@import "components/gf-form";
|
||||
@import "components/sidemenu";
|
||||
@ -69,10 +69,11 @@
|
||||
|
||||
// PAGES
|
||||
@import "pages/login";
|
||||
@import "pages/dashboard";
|
||||
@import "pages/playlist";
|
||||
@import "pages/admin";
|
||||
@import "pages/alerting";
|
||||
@import "pages/apps";
|
||||
@import "pages/plugins";
|
||||
@import "pages/signup";
|
||||
@import "pages/styleguide";
|
||||
|
||||
|
@ -50,8 +50,10 @@ $critical: #ed2e18;
|
||||
// -------------------------
|
||||
$body-bg: rgb(20,20,20);
|
||||
$page-bg: $dark-2;
|
||||
$body-color: $gray-4;
|
||||
$text-color: $gray-4;
|
||||
$body-color: $gray-4;
|
||||
$text-color: $gray-4;
|
||||
$text-color-strong: $white;
|
||||
$text-color-weak: $gray-2;
|
||||
|
||||
// gradients
|
||||
$brand-gradient: linear-gradient(to right, rgba(255,213,0,0.7) 0%, rgba(255,68,0,0.7) 99%, rgba(255,68,0,0.7) 100%);
|
||||
|
@ -58,6 +58,8 @@ $body-bg: $white;
|
||||
$page-bg: $white;
|
||||
$body-color: $gray-1;
|
||||
$text-color: $gray-1;
|
||||
$text-color-strong: $white;
|
||||
$text-color-weak: $gray-1;
|
||||
|
||||
// gradients
|
||||
$brand-gradient: linear-gradient(to right, rgba(255,213,0,1.0) 0%, rgba(255,68,0,1.0) 99%, rgba(255,68,0,1.0) 100%);
|
||||
|
@ -93,7 +93,7 @@ $font-size-sm: .875rem !default;
|
||||
$font-size-xs: .75rem !default;
|
||||
|
||||
$line-height-base: 1.5 !default;
|
||||
$font-weight-semi-bold: 600;
|
||||
$font-weight-semi-bold: 600;
|
||||
|
||||
$font-size-h1: 2.0rem !default;
|
||||
$font-size-h2: 1.75rem !default;
|
||||
@ -141,6 +141,11 @@ $border-radius-sm: 0.1rem !default;
|
||||
$caret-width: .3em !default;
|
||||
$caret-width-lg: $caret-width !default;
|
||||
|
||||
// Page
|
||||
|
||||
$page-sidebar-width: 10rem;
|
||||
$page-sidebar-margin: 5rem;
|
||||
|
||||
// Links
|
||||
// -------------------------
|
||||
$link-decoration: none !default;
|
||||
|
@ -65,7 +65,7 @@ h1, h2, h3, h4, h5, h6,
|
||||
color: $headings-color;
|
||||
}
|
||||
|
||||
h1, .h1 { font-size: $font-size-h1; }
|
||||
h1, .h1 { font-size: $font-size-h1; font-style: italic; }
|
||||
h2, .h2 { font-size: $font-size-h2; }
|
||||
h3, .h3 { font-size: $font-size-h3; }
|
||||
h4, .h4 { font-size: $font-size-h4; }
|
||||
|
@ -73,7 +73,7 @@
|
||||
// again by the mouse getting outside the hover space
|
||||
left: $side-menu-width - 0.2rem;
|
||||
background-color: rgba($side-menu-bg,$side-menu-opacity);
|
||||
@include animation('dropdown-anim 100ms ease-in-out 100ms forwards');
|
||||
@include animation('dropdown-anim 150ms ease-in-out 100ms forwards');
|
||||
z-index: -9999;
|
||||
}
|
||||
}
|
||||
@ -82,11 +82,6 @@
|
||||
|
||||
@include keyframes(dropdown-anim) {
|
||||
0% {
|
||||
display: none;
|
||||
opacity: 0;
|
||||
}
|
||||
1% {
|
||||
display: block;
|
||||
opacity: 0;
|
||||
transform: translate3d(-5%,0,0);
|
||||
}
|
||||
|
@ -13,7 +13,7 @@
|
||||
@include border-radius(3px);
|
||||
border: 1px solid $divider-border-color;
|
||||
background-color: transparent;
|
||||
border-bottom: 1px solid $panel-bg;
|
||||
border-bottom: 1px solid $page-bg;
|
||||
color: $link-color;
|
||||
}
|
||||
|
||||
|
14
public/sass/layout/_lists.scss
Normal file
14
public/sass/layout/_lists.scss
Normal file
@ -0,0 +1,14 @@
|
||||
|
||||
.ui-list {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
|
||||
> li {
|
||||
margin-bottom: 0.3125rem;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
@ -3,21 +3,10 @@
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.dashboard-container {
|
||||
padding: $dashboard-padding;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.main-view {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.page-dashboard {
|
||||
.main-view {
|
||||
background-image: none;
|
||||
}
|
||||
}
|
||||
|
||||
.page-container {
|
||||
background-color: $page-bg;
|
||||
padding: ($spacer * 2) ($spacer * 4);
|
||||
@ -27,8 +16,29 @@
|
||||
background-image: linear-gradient(60deg, transparent 70%, darken($page-bg, 4%) 98%)
|
||||
}
|
||||
|
||||
.page-body {
|
||||
@include media-breakpoint-up(md) {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
.page-content-with-sidebar {
|
||||
width: calc(100% - #{$page-sidebar-width + $page-sidebar-margin}); // sidebar width + margin
|
||||
}
|
||||
|
||||
.page-sidebar {
|
||||
@include media-breakpoint-up(md) {
|
||||
width: $page-sidebar-width;
|
||||
margin-left: $page-sidebar-margin;
|
||||
}
|
||||
}
|
||||
|
||||
.page-header {
|
||||
padding: $spacer 0 $spacer/2 0;
|
||||
margin-bottom: 2rem;
|
||||
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: flex-end;
|
||||
@ -36,13 +46,12 @@
|
||||
@include brand-bottom-border();
|
||||
|
||||
h1 {
|
||||
font-style: italic;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
button, a {
|
||||
margin-left: $spacer;
|
||||
}
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.page-heading {
|
||||
@ -71,3 +80,20 @@
|
||||
}
|
||||
}
|
||||
|
||||
.page-sidebar {
|
||||
color: $text-color-weak;
|
||||
h4 {
|
||||
font-size: $font-size-base;
|
||||
font-weight: $font-weight-semi-bold;
|
||||
color: $text-color-strong;
|
||||
}
|
||||
h5 {
|
||||
font-size: $font-size-base;
|
||||
color: $text-color-weak;
|
||||
font-weight: $font-weight-semi-bold;
|
||||
}
|
||||
}
|
||||
|
||||
.page-sidebar-section {
|
||||
margin-bottom: $spacer*2;
|
||||
}
|
||||
|
@ -1,26 +0,0 @@
|
||||
|
||||
.app-edit-logo-box {
|
||||
padding: 1.2rem;
|
||||
background: $panel-bg;
|
||||
text-align: center;
|
||||
img {
|
||||
max-width: 7rem;
|
||||
}
|
||||
margin-right: 2rem;
|
||||
}
|
||||
|
||||
.app-edit-links {
|
||||
list-style: none;
|
||||
margin: 0 0 0 2rem;
|
||||
|
||||
li {
|
||||
background: $panel-bg;
|
||||
margin-top: 4px;
|
||||
padding: 0.2rem 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.app-edit-description {
|
||||
font-style: italic;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
@ -1,3 +1,14 @@
|
||||
.dashboard-container {
|
||||
padding: $dashboard-padding;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.page-dashboard {
|
||||
.main-view {
|
||||
background-image: none;
|
||||
}
|
||||
}
|
||||
|
||||
.template-variable {
|
||||
color: $variable;
|
||||
}
|
53
public/sass/pages/_plugins.scss
Normal file
53
public/sass/pages/_plugins.scss
Normal file
@ -0,0 +1,53 @@
|
||||
.plugin-header {
|
||||
@include clearfix();
|
||||
|
||||
padding: $spacer 0 $spacer/2 0;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.plugin-header-logo {
|
||||
float: left;
|
||||
width: 7rem;
|
||||
img {
|
||||
width: 7rem;
|
||||
}
|
||||
margin-right: $spacer;
|
||||
}
|
||||
|
||||
.plugin-header-info-block {
|
||||
padding-top: $spacer;
|
||||
}
|
||||
|
||||
.plugin-header-author {
|
||||
}
|
||||
|
||||
.plugin-header-stamps-type {
|
||||
color: $link-color-disabled;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
// .app-edit-logo-box {
|
||||
// padding: 1.2rem;
|
||||
// background: $panel-bg;
|
||||
// text-align: center;
|
||||
// img {
|
||||
// max-width: 7rem;
|
||||
// }
|
||||
// margin-right: 2rem;
|
||||
// }
|
||||
//
|
||||
// .app-edit-links {
|
||||
// list-style: none;
|
||||
// margin: 0 0 0 2rem;
|
||||
//
|
||||
// li {
|
||||
// background: $panel-bg;
|
||||
// margin-top: 4px;
|
||||
// padding: 0.2rem 1rem;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// .app-edit-description {
|
||||
// font-style: italic;
|
||||
// margin-bottom: 1.5rem;
|
||||
// }
|
Loading…
Reference in New Issue
Block a user