mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge branch 'external-plugins'
This commit is contained in:
commit
2ec5bc77d7
@ -13,7 +13,7 @@ func Register(r *macaron.Macaron) {
|
||||
reqSignedIn := middleware.Auth(&middleware.AuthOptions{ReqSignedIn: true})
|
||||
reqGrafanaAdmin := middleware.Auth(&middleware.AuthOptions{ReqSignedIn: true, ReqGrafanaAdmin: true})
|
||||
reqEditorRole := middleware.RoleAuth(m.ROLE_EDITOR, m.ROLE_ADMIN)
|
||||
regOrgAdmin := middleware.RoleAuth(m.ROLE_ADMIN)
|
||||
reqOrgAdmin := middleware.RoleAuth(m.ROLE_ADMIN)
|
||||
quota := middleware.Quota
|
||||
bind := binding.Bind
|
||||
|
||||
@ -41,6 +41,9 @@ func Register(r *macaron.Macaron) {
|
||||
r.Get("/admin/orgs", reqGrafanaAdmin, Index)
|
||||
r.Get("/admin/orgs/edit/:id", reqGrafanaAdmin, Index)
|
||||
|
||||
r.Get("/plugins", reqSignedIn, Index)
|
||||
r.Get("/plugins/edit/*", reqSignedIn, Index)
|
||||
|
||||
r.Get("/dashboard/*", reqSignedIn, Index)
|
||||
r.Get("/dashboard-solo/*", reqSignedIn, Index)
|
||||
|
||||
@ -114,7 +117,7 @@ func Register(r *macaron.Macaron) {
|
||||
r.Get("/invites", wrap(GetPendingOrgInvites))
|
||||
r.Post("/invites", quota("user"), bind(dtos.AddInviteForm{}), wrap(AddOrgInvite))
|
||||
r.Patch("/invites/:code/revoke", wrap(RevokeInvite))
|
||||
}, regOrgAdmin)
|
||||
}, reqOrgAdmin)
|
||||
|
||||
// create new org
|
||||
r.Post("/orgs", quota("org"), bind(m.CreateOrgCommand{}), wrap(CreateOrg))
|
||||
@ -141,7 +144,7 @@ func Register(r *macaron.Macaron) {
|
||||
r.Get("/", wrap(GetApiKeys))
|
||||
r.Post("/", quota("api_key"), bind(m.AddApiKeyCommand{}), wrap(AddApiKey))
|
||||
r.Delete("/:id", wrap(DeleteApiKey))
|
||||
}, regOrgAdmin)
|
||||
}, reqOrgAdmin)
|
||||
|
||||
// Data sources
|
||||
r.Group("/datasources", func() {
|
||||
@ -151,7 +154,13 @@ func Register(r *macaron.Macaron) {
|
||||
r.Delete("/:id", DeleteDataSource)
|
||||
r.Get("/:id", wrap(GetDataSourceById))
|
||||
r.Get("/plugins", GetDataSourcePlugins)
|
||||
}, regOrgAdmin)
|
||||
}, reqOrgAdmin)
|
||||
|
||||
// PluginBundles
|
||||
r.Group("/plugins", func() {
|
||||
r.Get("/", wrap(GetPluginBundles))
|
||||
r.Post("/", bind(m.UpdatePluginBundleCmd{}), wrap(UpdatePluginBundle))
|
||||
}, reqOrgAdmin)
|
||||
|
||||
r.Get("/frontend/settings/", GetFrontendSettings)
|
||||
r.Any("/datasources/proxy/:id/*", reqSignedIn, ProxyDataSourceRequest)
|
||||
@ -188,5 +197,7 @@ func Register(r *macaron.Macaron) {
|
||||
// rendering
|
||||
r.Get("/render/*", reqSignedIn, RenderToPng)
|
||||
|
||||
InitExternalPluginRoutes(r)
|
||||
|
||||
r.NotFound(NotFoundHandler)
|
||||
}
|
||||
|
@ -117,8 +117,15 @@ func UpdateDataSource(c *middleware.Context, cmd m.UpdateDataSourceCommand) {
|
||||
func GetDataSourcePlugins(c *middleware.Context) {
|
||||
dsList := make(map[string]interface{})
|
||||
|
||||
for key, value := range plugins.DataSources {
|
||||
if value.(map[string]interface{})["builtIn"] == nil {
|
||||
orgBundles := m.GetPluginBundlesQuery{OrgId: c.OrgId}
|
||||
err := bus.Dispatch(&orgBundles)
|
||||
if err != nil {
|
||||
c.JsonApiErr(500, "Failed to get org plugin Bundles", err)
|
||||
}
|
||||
enabledPlugins := plugins.GetEnabledPlugins(orgBundles.Result)
|
||||
|
||||
for key, value := range enabledPlugins.DataSourcePlugins {
|
||||
if !value.BuiltIn {
|
||||
dsList[key] = value
|
||||
}
|
||||
}
|
||||
|
25
pkg/api/dtos/index.go
Normal file
25
pkg/api/dtos/index.go
Normal file
@ -0,0 +1,25 @@
|
||||
package dtos
|
||||
|
||||
type IndexViewData struct {
|
||||
User *CurrentUser
|
||||
Settings map[string]interface{}
|
||||
AppUrl string
|
||||
AppSubUrl string
|
||||
GoogleAnalyticsId string
|
||||
GoogleTagManagerId string
|
||||
|
||||
PluginCss []*PluginCss
|
||||
PluginJs []string
|
||||
MainNavLinks []*NavLink
|
||||
}
|
||||
|
||||
type PluginCss struct {
|
||||
Light string `json:"light"`
|
||||
Dark string `json:"dark"`
|
||||
}
|
||||
|
||||
type NavLink struct {
|
||||
Text string `json:"text"`
|
||||
Icon string `json:"icon"`
|
||||
Href string `json:"href"`
|
||||
}
|
8
pkg/api/dtos/plugin_bundle.go
Normal file
8
pkg/api/dtos/plugin_bundle.go
Normal file
@ -0,0 +1,8 @@
|
||||
package dtos
|
||||
|
||||
type PluginBundle struct {
|
||||
Type string `json:"type"`
|
||||
Enabled bool `json:"enabled"`
|
||||
Module string `json:"module"`
|
||||
JsonData map[string]interface{} `json:"jsonData"`
|
||||
}
|
75
pkg/api/externalplugin.go
Normal file
75
pkg/api/externalplugin.go
Normal file
@ -0,0 +1,75 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
|
||||
"github.com/Unknwon/macaron"
|
||||
"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"
|
||||
)
|
||||
|
||||
func InitExternalPluginRoutes(r *macaron.Macaron) {
|
||||
for _, plugin := range plugins.ExternalPlugins {
|
||||
log.Info("Plugin: Adding proxy routes for backend plugin")
|
||||
for _, route := range plugin.Routes {
|
||||
url := util.JoinUrlFragments("/api/plugin-proxy/", route.Path)
|
||||
handlers := make([]macaron.Handler, 0)
|
||||
if route.ReqSignedIn {
|
||||
handlers = append(handlers, middleware.Auth(&middleware.AuthOptions{ReqSignedIn: true}))
|
||||
}
|
||||
if route.ReqGrafanaAdmin {
|
||||
handlers = append(handlers, middleware.Auth(&middleware.AuthOptions{ReqSignedIn: true, ReqGrafanaAdmin: true}))
|
||||
}
|
||||
if route.ReqSignedIn && route.ReqRole != "" {
|
||||
if route.ReqRole == m.ROLE_ADMIN {
|
||||
handlers = append(handlers, middleware.RoleAuth(m.ROLE_ADMIN))
|
||||
} else if route.ReqRole == m.ROLE_EDITOR {
|
||||
handlers = append(handlers, middleware.RoleAuth(m.ROLE_EDITOR, m.ROLE_ADMIN))
|
||||
}
|
||||
}
|
||||
handlers = append(handlers, ExternalPlugin(route.Url))
|
||||
r.Route(url, route.Method, handlers...)
|
||||
log.Info("Plugin: Adding route %s", url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ExternalPlugin(routeUrl string) macaron.Handler {
|
||||
return func(c *middleware.Context) {
|
||||
path := c.Params("*")
|
||||
|
||||
//Create a HTTP header with the context in it.
|
||||
ctx, err := json.Marshal(c.SignedInUser)
|
||||
if err != nil {
|
||||
c.JsonApiErr(500, "failed to marshal context to json.", err)
|
||||
return
|
||||
}
|
||||
targetUrl, _ := url.Parse(routeUrl)
|
||||
proxy := NewExternalPluginProxy(string(ctx), path, targetUrl)
|
||||
proxy.Transport = dataProxyTransport
|
||||
proxy.ServeHTTP(c.RW(), c.Req.Request)
|
||||
}
|
||||
}
|
||||
|
||||
func NewExternalPluginProxy(ctx string, proxyPath string, targetUrl *url.URL) *httputil.ReverseProxy {
|
||||
director := func(req *http.Request) {
|
||||
req.URL.Scheme = targetUrl.Scheme
|
||||
req.URL.Host = targetUrl.Host
|
||||
req.Host = targetUrl.Host
|
||||
|
||||
req.URL.Path = util.JoinUrlFragments(targetUrl.Path, proxyPath)
|
||||
|
||||
// clear cookie headers
|
||||
req.Header.Del("Cookie")
|
||||
req.Header.Del("Set-Cookie")
|
||||
req.Header.Add("Grafana-Context", ctx)
|
||||
}
|
||||
|
||||
return &httputil.ReverseProxy{Director: director}
|
||||
}
|
@ -29,6 +29,13 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro
|
||||
datasources := make(map[string]interface{})
|
||||
var defaultDatasource string
|
||||
|
||||
orgBundles := m.GetPluginBundlesQuery{OrgId: c.OrgId}
|
||||
err := bus.Dispatch(&orgBundles)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
enabledPlugins := plugins.GetEnabledPlugins(orgBundles.Result)
|
||||
|
||||
for _, ds := range orgDataSources {
|
||||
url := ds.Url
|
||||
|
||||
@ -42,7 +49,7 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro
|
||||
"url": url,
|
||||
}
|
||||
|
||||
meta, exists := plugins.DataSources[ds.Type]
|
||||
meta, exists := enabledPlugins.DataSourcePlugins[ds.Type]
|
||||
if !exists {
|
||||
log.Error(3, "Could not find plugin definition for data source: %v", ds.Type)
|
||||
continue
|
||||
@ -109,9 +116,18 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro
|
||||
defaultDatasource = "-- Grafana --"
|
||||
}
|
||||
|
||||
panels := map[string]interface{}{}
|
||||
for _, panel := range enabledPlugins.PanelPlugins {
|
||||
panels[panel.Type] = map[string]interface{}{
|
||||
"module": panel.Module,
|
||||
"name": panel.Name,
|
||||
}
|
||||
}
|
||||
|
||||
jsonObj := map[string]interface{}{
|
||||
"defaultDatasource": defaultDatasource,
|
||||
"datasources": datasources,
|
||||
"panels": panels,
|
||||
"appSubUrl": setting.AppSubUrl,
|
||||
"allowOrgCreate": (setting.AllowUserOrgCreate && c.IsSignedIn) || c.IsGrafanaAdmin,
|
||||
"authProxyEnabled": setting.AuthProxyEnabled,
|
||||
|
121
pkg/api/index.go
121
pkg/api/index.go
@ -2,66 +2,121 @@ 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"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
func setIndexViewData(c *middleware.Context) error {
|
||||
func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
|
||||
settings, err := getFrontendSettingsMap(c)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
currentUser := &dtos.CurrentUser{
|
||||
Id: c.UserId,
|
||||
IsSignedIn: c.IsSignedIn,
|
||||
Login: c.Login,
|
||||
Email: c.Email,
|
||||
Name: c.Name,
|
||||
LightTheme: c.Theme == "light",
|
||||
OrgId: c.OrgId,
|
||||
OrgName: c.OrgName,
|
||||
OrgRole: c.OrgRole,
|
||||
GravatarUrl: dtos.GetGravatarUrl(c.Email),
|
||||
IsGrafanaAdmin: c.IsGrafanaAdmin,
|
||||
var data = dtos.IndexViewData{
|
||||
User: &dtos.CurrentUser{
|
||||
Id: c.UserId,
|
||||
IsSignedIn: c.IsSignedIn,
|
||||
Login: c.Login,
|
||||
Email: c.Email,
|
||||
Name: c.Name,
|
||||
LightTheme: c.Theme == "light",
|
||||
OrgId: c.OrgId,
|
||||
OrgName: c.OrgName,
|
||||
OrgRole: c.OrgRole,
|
||||
GravatarUrl: dtos.GetGravatarUrl(c.Email),
|
||||
IsGrafanaAdmin: c.IsGrafanaAdmin,
|
||||
},
|
||||
Settings: settings,
|
||||
AppUrl: setting.AppUrl,
|
||||
AppSubUrl: setting.AppSubUrl,
|
||||
GoogleAnalyticsId: setting.GoogleAnalyticsId,
|
||||
GoogleTagManagerId: setting.GoogleTagManagerId,
|
||||
}
|
||||
|
||||
if setting.DisableGravatar {
|
||||
currentUser.GravatarUrl = setting.AppSubUrl + "/img/user_profile.png"
|
||||
data.User.GravatarUrl = setting.AppSubUrl + "/img/user_profile.png"
|
||||
}
|
||||
|
||||
if len(currentUser.Name) == 0 {
|
||||
currentUser.Name = currentUser.Login
|
||||
if len(data.User.Name) == 0 {
|
||||
data.User.Name = data.User.Login
|
||||
}
|
||||
|
||||
themeUrlParam := c.Query("theme")
|
||||
if themeUrlParam == "light" {
|
||||
currentUser.LightTheme = true
|
||||
data.User.LightTheme = true
|
||||
}
|
||||
|
||||
c.Data["User"] = currentUser
|
||||
c.Data["Settings"] = settings
|
||||
c.Data["AppUrl"] = setting.AppUrl
|
||||
c.Data["AppSubUrl"] = setting.AppSubUrl
|
||||
data.MainNavLinks = append(data.MainNavLinks, &dtos.NavLink{
|
||||
Text: "Dashboards",
|
||||
Icon: "fa fa-fw fa-th-large",
|
||||
Href: "/",
|
||||
})
|
||||
|
||||
if setting.GoogleAnalyticsId != "" {
|
||||
c.Data["GoogleAnalyticsId"] = setting.GoogleAnalyticsId
|
||||
if c.OrgRole == m.ROLE_ADMIN {
|
||||
data.MainNavLinks = append(data.MainNavLinks, &dtos.NavLink{
|
||||
Text: "Data Sources",
|
||||
Icon: "fa fa-fw fa-database",
|
||||
Href: "/datasources",
|
||||
}, &dtos.NavLink{
|
||||
Text: "Plugins",
|
||||
Icon: "fa fa-fw fa-cubes",
|
||||
Href: "/plugins",
|
||||
})
|
||||
}
|
||||
|
||||
if setting.GoogleTagManagerId != "" {
|
||||
c.Data["GoogleTagManagerId"] = setting.GoogleTagManagerId
|
||||
orgBundles := m.GetPluginBundlesQuery{OrgId: c.OrgId}
|
||||
err = bus.Dispatch(&orgBundles)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
enabledPlugins := plugins.GetEnabledPlugins(orgBundles.Result)
|
||||
|
||||
for _, plugin := range enabledPlugins.ExternalPlugins {
|
||||
for _, js := range plugin.Js {
|
||||
data.PluginJs = append(data.PluginJs, js.Module)
|
||||
}
|
||||
for _, css := range plugin.Css {
|
||||
data.PluginCss = append(data.PluginCss, &dtos.PluginCss{Light: css.Light, Dark: css.Dark})
|
||||
}
|
||||
for _, item := range plugin.MainNavLinks {
|
||||
// only show menu items for the specified roles.
|
||||
var validRoles []m.RoleType
|
||||
if string(item.ReqRole) == "" || item.ReqRole == m.ROLE_VIEWER {
|
||||
validRoles = []m.RoleType{m.ROLE_ADMIN, m.ROLE_EDITOR, m.ROLE_VIEWER}
|
||||
} else if item.ReqRole == m.ROLE_EDITOR {
|
||||
validRoles = []m.RoleType{m.ROLE_ADMIN, m.ROLE_EDITOR}
|
||||
} else if item.ReqRole == m.ROLE_ADMIN {
|
||||
validRoles = []m.RoleType{m.ROLE_ADMIN}
|
||||
}
|
||||
ok := true
|
||||
if len(validRoles) > 0 {
|
||||
ok = false
|
||||
for _, role := range validRoles {
|
||||
if role == c.OrgRole {
|
||||
ok = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if ok {
|
||||
data.MainNavLinks = append(data.MainNavLinks, &dtos.NavLink{Text: item.Text, Href: item.Href, Icon: item.Icon})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return &data, nil
|
||||
}
|
||||
|
||||
func Index(c *middleware.Context) {
|
||||
if err := setIndexViewData(c); err != nil {
|
||||
if data, err := setIndexViewData(c); err != nil {
|
||||
c.Handle(500, "Failed to get settings", err)
|
||||
return
|
||||
} else {
|
||||
c.HTML(200, "index", data)
|
||||
}
|
||||
|
||||
c.HTML(200, "index")
|
||||
}
|
||||
|
||||
func NotFoundHandler(c *middleware.Context) {
|
||||
@ -70,10 +125,10 @@ func NotFoundHandler(c *middleware.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := setIndexViewData(c); err != nil {
|
||||
if data, err := setIndexViewData(c); err != nil {
|
||||
c.Handle(500, "Failed to get settings", err)
|
||||
return
|
||||
} else {
|
||||
c.HTML(404, "index", data)
|
||||
}
|
||||
|
||||
c.HTML(404, "index")
|
||||
}
|
||||
|
@ -19,19 +19,19 @@ const (
|
||||
)
|
||||
|
||||
func LoginView(c *middleware.Context) {
|
||||
if err := setIndexViewData(c); err != nil {
|
||||
viewData, err := setIndexViewData(c)
|
||||
if err != nil {
|
||||
c.Handle(500, "Failed to get settings", err)
|
||||
return
|
||||
}
|
||||
|
||||
settings := c.Data["Settings"].(map[string]interface{})
|
||||
settings["googleAuthEnabled"] = setting.OAuthService.Google
|
||||
settings["githubAuthEnabled"] = setting.OAuthService.GitHub
|
||||
settings["disableUserSignUp"] = !setting.AllowUserSignUp
|
||||
settings["loginHint"] = setting.LoginHint
|
||||
viewData.Settings["googleAuthEnabled"] = setting.OAuthService.Google
|
||||
viewData.Settings["githubAuthEnabled"] = setting.OAuthService.GitHub
|
||||
viewData.Settings["disableUserSignUp"] = !setting.AllowUserSignUp
|
||||
viewData.Settings["loginHint"] = setting.LoginHint
|
||||
|
||||
if !tryLoginUsingRememberCookie(c) {
|
||||
c.HTML(200, VIEW_INDEX)
|
||||
c.HTML(200, VIEW_INDEX, viewData)
|
||||
return
|
||||
}
|
||||
|
||||
|
65
pkg/api/plugin_bundle.go
Normal file
65
pkg/api/plugin_bundle.go
Normal file
@ -0,0 +1,65 @@
|
||||
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 GetPluginBundles(c *middleware.Context) Response {
|
||||
query := m.GetPluginBundlesQuery{OrgId: c.OrgId}
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
return ApiError(500, "Failed to list Plugin Bundles", err)
|
||||
}
|
||||
|
||||
installedBundlesMap := make(map[string]*dtos.PluginBundle)
|
||||
for t, b := range plugins.Bundles {
|
||||
installedBundlesMap[t] = &dtos.PluginBundle{
|
||||
Type: b.Type,
|
||||
Enabled: b.Enabled,
|
||||
Module: b.Module,
|
||||
JsonData: make(map[string]interface{}),
|
||||
}
|
||||
}
|
||||
|
||||
seenBundles := make(map[string]bool)
|
||||
|
||||
result := make([]*dtos.PluginBundle, 0)
|
||||
for _, b := range query.Result {
|
||||
if def, ok := installedBundlesMap[b.Type]; ok {
|
||||
result = append(result, &dtos.PluginBundle{
|
||||
Type: b.Type,
|
||||
Enabled: b.Enabled,
|
||||
Module: def.Module,
|
||||
JsonData: b.JsonData,
|
||||
})
|
||||
seenBundles[b.Type] = true
|
||||
}
|
||||
}
|
||||
|
||||
for t, b := range installedBundlesMap {
|
||||
if _, ok := seenBundles[t]; !ok {
|
||||
result = append(result, b)
|
||||
}
|
||||
}
|
||||
|
||||
return Json(200, result)
|
||||
}
|
||||
|
||||
func UpdatePluginBundle(c *middleware.Context, cmd m.UpdatePluginBundleCmd) Response {
|
||||
cmd.OrgId = c.OrgId
|
||||
|
||||
if _, ok := plugins.Bundles[cmd.Type]; !ok {
|
||||
return ApiError(404, "Bundle type not installed.", nil)
|
||||
}
|
||||
|
||||
err := bus.Dispatch(&cmd)
|
||||
if err != nil {
|
||||
return ApiError(500, "Failed to update plugin bundle", err)
|
||||
}
|
||||
|
||||
return ApiSuccess("Plugin updated")
|
||||
}
|
@ -14,6 +14,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/api/static"
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
"github.com/grafana/grafana/pkg/middleware"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
@ -28,12 +29,18 @@ func newMacaron() *macaron.Macaron {
|
||||
m.Use(middleware.Gziper())
|
||||
}
|
||||
|
||||
mapStatic(m, "", "public")
|
||||
mapStatic(m, "app", "app")
|
||||
mapStatic(m, "css", "css")
|
||||
mapStatic(m, "img", "img")
|
||||
mapStatic(m, "fonts", "fonts")
|
||||
mapStatic(m, "robots.txt", "robots.txt")
|
||||
for _, route := range plugins.StaticRoutes {
|
||||
pluginRoute := path.Join("/public/plugins/", route.Url)
|
||||
log.Info("Plugin: Adding static route %s -> %s", pluginRoute, route.Path)
|
||||
mapStatic(m, route.Path, "", pluginRoute)
|
||||
}
|
||||
|
||||
mapStatic(m, setting.StaticRootPath, "", "public")
|
||||
mapStatic(m, setting.StaticRootPath, "app", "app")
|
||||
mapStatic(m, setting.StaticRootPath, "css", "css")
|
||||
mapStatic(m, setting.StaticRootPath, "img", "img")
|
||||
mapStatic(m, setting.StaticRootPath, "fonts", "fonts")
|
||||
mapStatic(m, setting.StaticRootPath, "robots.txt", "robots.txt")
|
||||
|
||||
m.Use(macaron.Renderer(macaron.RenderOptions{
|
||||
Directory: path.Join(setting.StaticRootPath, "views"),
|
||||
@ -51,7 +58,7 @@ func newMacaron() *macaron.Macaron {
|
||||
return m
|
||||
}
|
||||
|
||||
func mapStatic(m *macaron.Macaron, dir string, prefix string) {
|
||||
func mapStatic(m *macaron.Macaron, rootDir string, dir string, prefix string) {
|
||||
headers := func(c *macaron.Context) {
|
||||
c.Resp.Header().Set("Cache-Control", "public, max-age=3600")
|
||||
}
|
||||
@ -63,7 +70,7 @@ func mapStatic(m *macaron.Macaron, dir string, prefix string) {
|
||||
}
|
||||
|
||||
m.Use(httpstatic.Static(
|
||||
path.Join(setting.StaticRootPath, dir),
|
||||
path.Join(rootDir, dir),
|
||||
httpstatic.StaticOptions{
|
||||
SkipLogging: true,
|
||||
Prefix: prefix,
|
||||
|
@ -131,8 +131,8 @@ func (a *ldapAuther) getGrafanaUserFor(ldapUser *ldapUserInfo) (*m.User, error)
|
||||
}
|
||||
|
||||
return userQuery.Result, nil
|
||||
}
|
||||
|
||||
}
|
||||
func (a *ldapAuther) createGrafanaUser(ldapUser *ldapUserInfo) (*m.User, error) {
|
||||
cmd := m.CreateUserCommand{
|
||||
Login: ldapUser.Username,
|
||||
|
34
pkg/models/plugin_bundle.go
Normal file
34
pkg/models/plugin_bundle.go
Normal file
@ -0,0 +1,34 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
type PluginBundle struct {
|
||||
Id int64
|
||||
Type string
|
||||
OrgId int64
|
||||
Enabled bool
|
||||
JsonData map[string]interface{}
|
||||
|
||||
Created time.Time
|
||||
Updated time.Time
|
||||
}
|
||||
|
||||
// ----------------------
|
||||
// COMMANDS
|
||||
|
||||
// Also acts as api DTO
|
||||
type UpdatePluginBundleCmd struct {
|
||||
Type string `json:"type" binding:"Required"`
|
||||
Enabled bool `json:"enabled"`
|
||||
JsonData map[string]interface{} `json:"jsonData"`
|
||||
|
||||
Id int64 `json:"-"`
|
||||
OrgId int64 `json:"-"`
|
||||
}
|
||||
|
||||
// ---------------------
|
||||
// QUERIES
|
||||
type GetPluginBundlesQuery struct {
|
||||
OrgId int64
|
||||
Result []*PluginBundle
|
||||
}
|
85
pkg/plugins/models.go
Normal file
85
pkg/plugins/models.go
Normal file
@ -0,0 +1,85 @@
|
||||
package plugins
|
||||
|
||||
import "github.com/grafana/grafana/pkg/models"
|
||||
|
||||
type DataSourcePlugin struct {
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
ServiceName string `json:"serviceName"`
|
||||
Module string `json:"module"`
|
||||
Partials map[string]interface{} `json:"partials"`
|
||||
DefaultMatchFormat string `json:"defaultMatchFormat"`
|
||||
Annotations bool `json:"annotations"`
|
||||
Metrics bool `json:"metrics"`
|
||||
BuiltIn bool `json:"builtIn"`
|
||||
StaticRootConfig *StaticRootConfig `json:"staticRoot"`
|
||||
}
|
||||
|
||||
type PanelPlugin struct {
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Module string `json:"module"`
|
||||
StaticRootConfig *StaticRootConfig `json:"staticRoot"`
|
||||
}
|
||||
|
||||
type StaticRootConfig struct {
|
||||
Url string `json:"url"`
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
type ExternalPluginRoute struct {
|
||||
Path string `json:"path"`
|
||||
Method string `json:"method"`
|
||||
ReqSignedIn bool `json:"reqSignedIn"`
|
||||
ReqGrafanaAdmin bool `json:"reqGrafanaAdmin"`
|
||||
ReqRole models.RoleType `json:"reqRole"`
|
||||
Url string `json:"url"`
|
||||
}
|
||||
|
||||
type ExternalPluginJs struct {
|
||||
Module string `json:"module"`
|
||||
}
|
||||
|
||||
type ExternalPluginNavLink struct {
|
||||
Text string `json:"text"`
|
||||
Icon string `json:"icon"`
|
||||
Href string `json:"href"`
|
||||
ReqRole models.RoleType `json:"reqRole"`
|
||||
}
|
||||
|
||||
type ExternalPluginCss struct {
|
||||
Light string `json:"light"`
|
||||
Dark string `json:"dark"`
|
||||
}
|
||||
|
||||
type ExternalPlugin struct {
|
||||
Type string `json:"type"`
|
||||
Routes []*ExternalPluginRoute `json:"routes"`
|
||||
Js []*ExternalPluginJs `json:"js"`
|
||||
Css []*ExternalPluginCss `json:"css"`
|
||||
MainNavLinks []*ExternalPluginNavLink `json:"mainNavLinks"`
|
||||
StaticRootConfig *StaticRootConfig `json:"staticRoot"`
|
||||
}
|
||||
|
||||
type PluginBundle struct {
|
||||
Type string `json:"type"`
|
||||
Enabled bool `json:"enabled"`
|
||||
PanelPlugins []string `json:"panelPlugins"`
|
||||
DatasourcePlugins []string `json:"datasourcePlugins"`
|
||||
ExternalPlugins []string `json:"externalPlugins"`
|
||||
Module string `json:"module"`
|
||||
}
|
||||
|
||||
type EnabledPlugins struct {
|
||||
PanelPlugins []*PanelPlugin
|
||||
DataSourcePlugins map[string]*DataSourcePlugin
|
||||
ExternalPlugins []*ExternalPlugin
|
||||
}
|
||||
|
||||
func NewEnabledPlugins() EnabledPlugins {
|
||||
return EnabledPlugins{
|
||||
PanelPlugins: make([]*PanelPlugin, 0),
|
||||
DataSourcePlugins: make(map[string]*DataSourcePlugin),
|
||||
ExternalPlugins: make([]*ExternalPlugin, 0),
|
||||
}
|
||||
}
|
@ -6,18 +6,19 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
type PluginMeta struct {
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
var (
|
||||
DataSources map[string]interface{}
|
||||
DataSources map[string]DataSourcePlugin
|
||||
Panels map[string]PanelPlugin
|
||||
ExternalPlugins map[string]ExternalPlugin
|
||||
StaticRoutes []*StaticRootConfig
|
||||
Bundles map[string]PluginBundle
|
||||
)
|
||||
|
||||
type PluginScanner struct {
|
||||
@ -25,13 +26,53 @@ type PluginScanner struct {
|
||||
errors []error
|
||||
}
|
||||
|
||||
func Init() {
|
||||
func Init() error {
|
||||
DataSources = make(map[string]DataSourcePlugin)
|
||||
ExternalPlugins = make(map[string]ExternalPlugin)
|
||||
StaticRoutes = make([]*StaticRootConfig, 0)
|
||||
Panels = make(map[string]PanelPlugin)
|
||||
Bundles = make(map[string]PluginBundle)
|
||||
|
||||
scan(path.Join(setting.StaticRootPath, "app/plugins"))
|
||||
checkExternalPluginPaths()
|
||||
checkDependencies()
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkDependencies() {
|
||||
for bundleType, bundle := range Bundles {
|
||||
for _, reqPanel := range bundle.PanelPlugins {
|
||||
if _, ok := Panels[reqPanel]; !ok {
|
||||
log.Fatal(4, "Bundle %s requires Panel type %s, but it is not present.", bundleType, reqPanel)
|
||||
}
|
||||
}
|
||||
for _, reqDataSource := range bundle.DatasourcePlugins {
|
||||
if _, ok := DataSources[reqDataSource]; !ok {
|
||||
log.Fatal(4, "Bundle %s requires DataSource type %s, but it is not present.", bundleType, reqDataSource)
|
||||
}
|
||||
}
|
||||
for _, reqExtPlugin := range bundle.ExternalPlugins {
|
||||
if _, ok := ExternalPlugins[reqExtPlugin]; !ok {
|
||||
log.Fatal(4, "Bundle %s requires DataSource type %s, but it is not present.", bundleType, reqExtPlugin)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func checkExternalPluginPaths() error {
|
||||
for _, section := range setting.Cfg.Sections() {
|
||||
if strings.HasPrefix(section.Name(), "plugin.") {
|
||||
path := section.Key("path").String()
|
||||
if path != "" {
|
||||
log.Info("Plugin: Scaning dir %s", path)
|
||||
scan(path)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func scan(pluginDir string) error {
|
||||
DataSources = make(map[string]interface{})
|
||||
|
||||
scanner := &PluginScanner{
|
||||
pluginPath: pluginDir,
|
||||
}
|
||||
@ -47,7 +88,7 @@ func scan(pluginDir string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (scanner *PluginScanner) walker(path string, f os.FileInfo, err error) error {
|
||||
func (scanner *PluginScanner) walker(currentPath string, f os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -57,17 +98,25 @@ func (scanner *PluginScanner) walker(path string, f os.FileInfo, err error) erro
|
||||
}
|
||||
|
||||
if f.Name() == "plugin.json" {
|
||||
err := scanner.loadPluginJson(path)
|
||||
err := scanner.loadPluginJson(currentPath)
|
||||
if err != nil {
|
||||
log.Error(3, "Failed to load plugin json file: %v, err: %v", path, err)
|
||||
log.Error(3, "Failed to load plugin json file: %v, err: %v", currentPath, err)
|
||||
scanner.errors = append(scanner.errors, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (scanner *PluginScanner) loadPluginJson(path string) error {
|
||||
reader, err := os.Open(path)
|
||||
func addStaticRoot(staticRootConfig *StaticRootConfig, currentDir string) {
|
||||
if staticRootConfig != nil {
|
||||
staticRootConfig.Path = path.Join(currentDir, staticRootConfig.Path)
|
||||
StaticRoutes = append(StaticRoutes, staticRootConfig)
|
||||
}
|
||||
}
|
||||
|
||||
func (scanner *PluginScanner) loadPluginJson(pluginJsonFilePath string) error {
|
||||
currentDir := filepath.Dir(pluginJsonFilePath)
|
||||
reader, err := os.Open(pluginJsonFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -87,12 +136,95 @@ func (scanner *PluginScanner) loadPluginJson(path string) error {
|
||||
}
|
||||
|
||||
if pluginType == "datasource" {
|
||||
datasourceType, exists := pluginJson["type"]
|
||||
if !exists {
|
||||
p := DataSourcePlugin{}
|
||||
reader.Seek(0, 0)
|
||||
if err := jsonParser.Decode(&p); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if p.Type == "" {
|
||||
return errors.New("Did not find type property in plugin.json")
|
||||
}
|
||||
DataSources[datasourceType.(string)] = pluginJson
|
||||
|
||||
DataSources[p.Type] = p
|
||||
addStaticRoot(p.StaticRootConfig, currentDir)
|
||||
}
|
||||
|
||||
if pluginType == "panel" {
|
||||
p := PanelPlugin{}
|
||||
reader.Seek(0, 0)
|
||||
if err := jsonParser.Decode(&p); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if p.Type == "" {
|
||||
return errors.New("Did not find type property in plugin.json")
|
||||
}
|
||||
|
||||
Panels[p.Type] = p
|
||||
addStaticRoot(p.StaticRootConfig, currentDir)
|
||||
}
|
||||
|
||||
if pluginType == "external" {
|
||||
p := ExternalPlugin{}
|
||||
reader.Seek(0, 0)
|
||||
if err := jsonParser.Decode(&p); err != nil {
|
||||
return err
|
||||
}
|
||||
if p.Type == "" {
|
||||
return errors.New("Did not find type property in plugin.json")
|
||||
}
|
||||
ExternalPlugins[p.Type] = p
|
||||
addStaticRoot(p.StaticRootConfig, currentDir)
|
||||
}
|
||||
|
||||
if pluginType == "bundle" {
|
||||
p := PluginBundle{}
|
||||
reader.Seek(0, 0)
|
||||
if err := jsonParser.Decode(&p); err != nil {
|
||||
return err
|
||||
}
|
||||
if p.Type == "" {
|
||||
return errors.New("Did not find type property in plugin.json")
|
||||
}
|
||||
Bundles[p.Type] = p
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetEnabledPlugins(orgBundles []*models.PluginBundle) EnabledPlugins {
|
||||
enabledPlugins := NewEnabledPlugins()
|
||||
|
||||
orgBundlesMap := make(map[string]*models.PluginBundle)
|
||||
for _, orgBundle := range orgBundles {
|
||||
orgBundlesMap[orgBundle.Type] = orgBundle
|
||||
}
|
||||
|
||||
for bundleType, bundle := range Bundles {
|
||||
enabled := bundle.Enabled
|
||||
// check if the bundle is stored in the DB.
|
||||
if b, ok := orgBundlesMap[bundleType]; ok {
|
||||
enabled = b.Enabled
|
||||
}
|
||||
|
||||
if enabled {
|
||||
for _, d := range bundle.DatasourcePlugins {
|
||||
if ds, ok := DataSources[d]; ok {
|
||||
enabledPlugins.DataSourcePlugins[d] = &ds
|
||||
}
|
||||
}
|
||||
for _, p := range bundle.PanelPlugins {
|
||||
if panel, ok := Panels[p]; ok {
|
||||
enabledPlugins.PanelPlugins = append(enabledPlugins.PanelPlugins, &panel)
|
||||
}
|
||||
}
|
||||
for _, e := range bundle.ExternalPlugins {
|
||||
if external, ok := ExternalPlugins[e]; ok {
|
||||
enabledPlugins.ExternalPlugins = append(enabledPlugins.ExternalPlugins, &external)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return enabledPlugins
|
||||
}
|
||||
|
@ -4,14 +4,17 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
func TestPluginScans(t *testing.T) {
|
||||
|
||||
Convey("When scaning for plugins", t, func() {
|
||||
path, _ := filepath.Abs("../../public/app/plugins")
|
||||
err := scan(path)
|
||||
setting.StaticRootPath, _ = filepath.Abs("../../public/")
|
||||
setting.Cfg = ini.Empty()
|
||||
err := Init()
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(len(DataSources), ShouldBeGreaterThan, 1)
|
||||
|
@ -18,6 +18,7 @@ func AddMigrations(mg *Migrator) {
|
||||
addApiKeyMigrations(mg)
|
||||
addDashboardSnapshotMigrations(mg)
|
||||
addQuotaMigration(mg)
|
||||
addPluginBundleMigration(mg)
|
||||
}
|
||||
|
||||
func addMigrationLogMigrations(mg *Migrator) {
|
||||
|
26
pkg/services/sqlstore/migrations/plugin_bundle.go
Normal file
26
pkg/services/sqlstore/migrations/plugin_bundle.go
Normal file
@ -0,0 +1,26 @@
|
||||
package migrations
|
||||
|
||||
import . "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
||||
|
||||
func addPluginBundleMigration(mg *Migrator) {
|
||||
|
||||
var pluginBundleV1 = Table{
|
||||
Name: "plugin_bundle",
|
||||
Columns: []*Column{
|
||||
{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
|
||||
{Name: "org_id", Type: DB_BigInt, Nullable: true},
|
||||
{Name: "type", Type: DB_NVarchar, Length: 255, Nullable: false},
|
||||
{Name: "enabled", Type: DB_Bool, Nullable: false},
|
||||
{Name: "json_data", Type: DB_Text, Nullable: true},
|
||||
{Name: "created", Type: DB_DateTime, Nullable: false},
|
||||
{Name: "updated", Type: DB_DateTime, Nullable: false},
|
||||
},
|
||||
Indices: []*Index{
|
||||
{Cols: []string{"org_id", "type"}, Type: UniqueIndex},
|
||||
},
|
||||
}
|
||||
mg.AddMigration("create plugin_bundle table v1", NewAddTableMigration(pluginBundleV1))
|
||||
|
||||
//------- indexes ------------------
|
||||
addTableIndicesMigrations(mg, "v1", pluginBundleV1)
|
||||
}
|
46
pkg/services/sqlstore/plugin_bundle.go
Normal file
46
pkg/services/sqlstore/plugin_bundle.go
Normal file
@ -0,0 +1,46 @@
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
|
||||
func init() {
|
||||
bus.AddHandler("sql", GetPluginBundles)
|
||||
bus.AddHandler("sql", UpdatePluginBundle)
|
||||
}
|
||||
|
||||
func GetPluginBundles(query *m.GetPluginBundlesQuery) error {
|
||||
sess := x.Where("org_id=?", query.OrgId)
|
||||
|
||||
query.Result = make([]*m.PluginBundle, 0)
|
||||
return sess.Find(&query.Result)
|
||||
}
|
||||
|
||||
func UpdatePluginBundle(cmd *m.UpdatePluginBundleCmd) error {
|
||||
return inTransaction2(func(sess *session) error {
|
||||
var bundle m.PluginBundle
|
||||
|
||||
exists, err := sess.Where("org_id=? and type=?", cmd.OrgId, cmd.Type).Get(&bundle)
|
||||
sess.UseBool("enabled")
|
||||
if !exists {
|
||||
bundle = m.PluginBundle{
|
||||
Type: cmd.Type,
|
||||
OrgId: cmd.OrgId,
|
||||
Enabled: cmd.Enabled,
|
||||
JsonData: cmd.JsonData,
|
||||
Created: time.Now(),
|
||||
Updated: time.Now(),
|
||||
}
|
||||
_, err = sess.Insert(&bundle)
|
||||
return err
|
||||
} else {
|
||||
bundle.Enabled = cmd.Enabled
|
||||
bundle.JsonData = cmd.JsonData
|
||||
_, err = sess.Id(bundle.Id).Update(&bundle)
|
||||
return err
|
||||
}
|
||||
})
|
||||
}
|
@ -287,13 +287,11 @@ func loadSpecifedConfigFile(configFile string) {
|
||||
|
||||
defaultSec, err := Cfg.GetSection(section.Name())
|
||||
if err != nil {
|
||||
log.Error(3, "Unknown config section %s defined in %s", section.Name(), configFile)
|
||||
continue
|
||||
defaultSec, _ = Cfg.NewSection(section.Name())
|
||||
}
|
||||
defaultKey, err := defaultSec.GetKey(key.Name())
|
||||
if err != nil {
|
||||
log.Error(3, "Unknown config key %s defined in section %s, in file %s", key.Name(), section.Name(), configFile)
|
||||
continue
|
||||
defaultKey, _ = defaultSec.NewKey(key.Name(), key.Value())
|
||||
}
|
||||
defaultKey.SetValue(key.Value())
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ define([
|
||||
'angular',
|
||||
'jquery',
|
||||
'lodash',
|
||||
'app/core/config',
|
||||
'require',
|
||||
'bootstrap',
|
||||
'angular-route',
|
||||
@ -12,7 +13,7 @@ define([
|
||||
'bindonce',
|
||||
'app/core/core',
|
||||
],
|
||||
function (angular, $, _, appLevelRequire) {
|
||||
function (angular, $, _, config, appLevelRequire) {
|
||||
"use strict";
|
||||
|
||||
var app = angular.module('grafana', []);
|
||||
@ -35,6 +36,8 @@ function (angular, $, _, appLevelRequire) {
|
||||
} else {
|
||||
_.extend(module, register_fns);
|
||||
}
|
||||
// push it into the apps dependencies
|
||||
apps_deps.push(module.name);
|
||||
return module;
|
||||
};
|
||||
|
||||
@ -64,13 +67,15 @@ function (angular, $, _, appLevelRequire) {
|
||||
var module_name = 'grafana.'+type;
|
||||
// create the module
|
||||
app.useModule(angular.module(module_name, []));
|
||||
// push it into the apps dependencies
|
||||
apps_deps.push(module_name);
|
||||
});
|
||||
|
||||
var preBootRequires = [
|
||||
'app/features/all',
|
||||
];
|
||||
var preBootRequires = ['app/features/all'];
|
||||
var pluginModules = config.bootData.pluginModules || [];
|
||||
|
||||
// add plugin modules
|
||||
for (var i = 0; i < pluginModules.length; i++) {
|
||||
preBootRequires.push(pluginModules[i]);
|
||||
}
|
||||
|
||||
app.boot = function() {
|
||||
require(preBootRequires, function () {
|
||||
|
@ -6,6 +6,7 @@ function (Settings) {
|
||||
|
||||
var bootData = window.grafanaBootData || { settings: {} };
|
||||
var options = bootData.settings;
|
||||
options.bootData = bootData;
|
||||
|
||||
return new Settings(options);
|
||||
|
||||
|
@ -15,19 +15,13 @@ function (angular, _, $, coreModule, config) {
|
||||
};
|
||||
|
||||
$scope.setupMainNav = function() {
|
||||
$scope.mainLinks.push({
|
||||
text: "Dashboards",
|
||||
icon: "fa fa-fw fa-th-large",
|
||||
href: $scope.getUrl("/"),
|
||||
});
|
||||
|
||||
if (contextSrv.hasRole('Admin')) {
|
||||
_.each(config.bootData.mainNavLinks, function(item) {
|
||||
$scope.mainLinks.push({
|
||||
text: "Data Sources",
|
||||
icon: "fa fa-fw fa-database",
|
||||
href: $scope.getUrl("/datasources"),
|
||||
text: item.text,
|
||||
icon: item.icon,
|
||||
href: $scope.getUrl(item.href)
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.loadOrgs = function() {
|
||||
|
@ -131,6 +131,16 @@ define([
|
||||
templateUrl: 'app/partials/reset_password.html',
|
||||
controller : 'ResetPasswordCtrl',
|
||||
})
|
||||
.when('/plugins', {
|
||||
templateUrl: 'app/features/org/partials/plugins.html',
|
||||
controller: 'PluginsCtrl',
|
||||
resolve: loadOrgBundle,
|
||||
})
|
||||
.when('/plugins/edit/:type', {
|
||||
templateUrl: 'app/features/org/partials/pluginEdit.html',
|
||||
controller: 'PluginEditCtrl',
|
||||
resolve: loadOrgBundle,
|
||||
})
|
||||
.when('/global-alerts', {
|
||||
templateUrl: 'app/features/dashboard/partials/globalAlerts.html',
|
||||
})
|
||||
|
@ -12,8 +12,8 @@ function (angular, _, coreModule, store, config) {
|
||||
var self = this;
|
||||
|
||||
function User() {
|
||||
if (window.grafanaBootData.user) {
|
||||
_.extend(this, window.grafanaBootData.user);
|
||||
if (config.bootData.user) {
|
||||
_.extend(this, config.bootData.user);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,15 +8,8 @@ function (_) {
|
||||
var defaults = {
|
||||
datasources : {},
|
||||
window_title_prefix : 'Grafana - ',
|
||||
panels : {
|
||||
'graph': { path: 'app/panels/graph', name: 'Graph' },
|
||||
'table': { path: 'app/panels/table', name: 'Table' },
|
||||
'singlestat': { path: 'app/panels/singlestat', name: 'Single stat' },
|
||||
'text': { path: 'app/panels/text', name: 'Text' },
|
||||
'dashlist': { path: 'app/panels/dashlist', name: 'Dashboard list' },
|
||||
},
|
||||
panels : {},
|
||||
new_panel_title: 'Panel Title',
|
||||
plugins: {},
|
||||
playlist_timespan: "1m",
|
||||
unsaved_changes_warning: true,
|
||||
appSubUrl: ""
|
||||
|
@ -6,4 +6,8 @@ define([
|
||||
'./userInviteCtrl',
|
||||
'./orgApiKeysCtrl',
|
||||
'./orgDetailsCtrl',
|
||||
'./pluginsCtrl',
|
||||
'./pluginEditCtrl',
|
||||
'./plugin_srv',
|
||||
'./plugin_directive',
|
||||
], function () {});
|
||||
|
3
public/app/features/org/partials/pluginConfigCore.html
Normal file
3
public/app/features/org/partials/pluginConfigCore.html
Normal file
@ -0,0 +1,3 @@
|
||||
<div>
|
||||
{{current.type}} plugin does not have any additional config.
|
||||
</div>
|
42
public/app/features/org/partials/pluginEdit.html
Normal file
42
public/app/features/org/partials/pluginEdit.html
Normal file
@ -0,0 +1,42 @@
|
||||
<topnav title="Plugins" icon="fa fa-fw fa-cubes" subnav="true">
|
||||
<ul class="nav">
|
||||
<li ><a href="plugins">Overview</a></li>
|
||||
<li class="active" ><a href="plugins/edit/{{current.type}}">Edit</a></li>
|
||||
</ul>
|
||||
</topnav>
|
||||
|
||||
<div class="page-container">
|
||||
<div class="page">
|
||||
<h2>Edit Plugin</h2>
|
||||
|
||||
|
||||
<form name="editForm">
|
||||
<div class="tight-form">
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item" style="width: 80px">
|
||||
Type
|
||||
</li>
|
||||
<li>
|
||||
<li>
|
||||
<input type="text" disabled="disabled" class="input-xlarge tight-form-input" ng-model="current.type">
|
||||
</li>
|
||||
</li>
|
||||
<li class="tight-form-item">
|
||||
Default
|
||||
<input class="cr1" id="current.enabled" type="checkbox" ng-model="current.enabled" ng-checked="current.enabled">
|
||||
<label for="current.enabled" class="cr1"></label>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<br>
|
||||
<plugin-config-loader plugin="current"></plugin-config-loader>
|
||||
<div class="pull-right" style="margin-top: 35px">
|
||||
<button type="submit" class="btn btn-success" ng-click="update()">Save</button>
|
||||
<a class="btn btn-inverse" href="plugins">Cancel</a>
|
||||
</div>
|
||||
<br>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
41
public/app/features/org/partials/plugins.html
Normal file
41
public/app/features/org/partials/plugins.html
Normal file
@ -0,0 +1,41 @@
|
||||
<topnav title="Plugins" icon="fa fa-fw fa-cubes" subnav="true">
|
||||
<ul class="nav">
|
||||
<li class="active" ><a href="plugins">Overview</a></li>
|
||||
</ul>
|
||||
</topnav>
|
||||
|
||||
<div class="page-container">
|
||||
<div class="page">
|
||||
<h2>Plugins</h2>
|
||||
|
||||
<div ng-if="!plugins">
|
||||
<em>No plugins defined</em>
|
||||
</div>
|
||||
|
||||
<table class="grafana-options-table" ng-if="plugins">
|
||||
<tr>
|
||||
<td><strong>Type</strong></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr ng-repeat="(type, p) in plugins">
|
||||
<td style="width:1%">
|
||||
<i class="fa fa-cubes"></i>
|
||||
{{p.type}}
|
||||
</td>
|
||||
<td style="width: 1%">
|
||||
<a href="plugins/edit/{{p.type}}" class="btn btn-inverse btn-mini">
|
||||
<i class="fa fa-edit"></i>
|
||||
Edit
|
||||
</a>
|
||||
</td>
|
||||
<td style="width: 1%">
|
||||
Enabled
|
||||
<input id="p.enabled" type="checkbox" ng-model="p.enabled" ng-checked="p.enabled" ng-change="update(p)">
|
||||
<label for="p.enabled"></label>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
</div>
|
35
public/app/features/org/pluginEditCtrl.js
Normal file
35
public/app/features/org/pluginEditCtrl.js
Normal file
@ -0,0 +1,35 @@
|
||||
define([
|
||||
'angular',
|
||||
'lodash',
|
||||
'app/core/config',
|
||||
],
|
||||
function (angular, _, config) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.controllers');
|
||||
|
||||
module.controller('PluginEditCtrl', function($scope, pluginSrv, $routeParams) {
|
||||
$scope.init = function() {
|
||||
$scope.current = {};
|
||||
$scope.getPlugins();
|
||||
};
|
||||
|
||||
$scope.getPlugins = function() {
|
||||
pluginSrv.get($routeParams.type).then(function(result) {
|
||||
$scope.current = _.clone(result);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.update = function() {
|
||||
$scope._update();
|
||||
};
|
||||
|
||||
$scope._update = function() {
|
||||
pluginSrv.update($scope.current).then(function() {
|
||||
window.location.href = config.appSubUrl + "plugins";
|
||||
});
|
||||
};
|
||||
|
||||
$scope.init();
|
||||
});
|
||||
});
|
47
public/app/features/org/plugin_directive.js
Normal file
47
public/app/features/org/plugin_directive.js
Normal file
@ -0,0 +1,47 @@
|
||||
define([
|
||||
'angular',
|
||||
],
|
||||
function (angular) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.directives');
|
||||
|
||||
module.directive('pluginConfigLoader', function($compile) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
link: function(scope, elem) {
|
||||
var directive = 'grafana-plugin-core';
|
||||
//wait for the parent scope to be applied.
|
||||
scope.$watch("current", function(newVal) {
|
||||
if (newVal) {
|
||||
if (newVal.module) {
|
||||
directive = 'grafana-plugin-'+newVal.type;
|
||||
}
|
||||
scope.require([newVal.module], function () {
|
||||
var panelEl = angular.element(document.createElement(directive));
|
||||
elem.append(panelEl);
|
||||
$compile(panelEl)(scope);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
module.directive('grafanaPluginCore', function() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
templateUrl: 'app/features/org/partials/pluginConfigCore.html',
|
||||
transclude: true,
|
||||
link: function(scope) {
|
||||
scope.update = function() {
|
||||
//Perform custom save events to the plugins own backend if needed.
|
||||
|
||||
// call parent update to commit the change to the plugin object.
|
||||
// this will cause the page to reload.
|
||||
scope._update();
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
58
public/app/features/org/plugin_srv.js
Normal file
58
public/app/features/org/plugin_srv.js
Normal file
@ -0,0 +1,58 @@
|
||||
define([
|
||||
'angular',
|
||||
'lodash',
|
||||
],
|
||||
function (angular, _) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.services');
|
||||
|
||||
module.service('pluginSrv', function($rootScope, $timeout, $q, backendSrv) {
|
||||
var self = this;
|
||||
this.init = function() {
|
||||
console.log("pluginSrv init");
|
||||
this.plugins = {};
|
||||
};
|
||||
|
||||
this.get = function(type) {
|
||||
return $q(function(resolve) {
|
||||
if (type in self.plugins) {
|
||||
return resolve(self.plugins[type]);
|
||||
}
|
||||
backendSrv.get('/api/plugins').then(function(results) {
|
||||
_.forEach(results, function(p) {
|
||||
self.plugins[p.type] = p;
|
||||
});
|
||||
return resolve(self.plugins[type]);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
this.getAll = function() {
|
||||
return $q(function(resolve) {
|
||||
if (!_.isEmpty(self.plugins)) {
|
||||
return resolve(self.plugins);
|
||||
}
|
||||
backendSrv.get('api/plugins').then(function(results) {
|
||||
_.forEach(results, function(p) {
|
||||
self.plugins[p.type] = p;
|
||||
});
|
||||
return resolve(self.plugins);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
this.update = function(plugin) {
|
||||
return $q(function(resolve, reject) {
|
||||
backendSrv.post('/api/plugins', plugin).then(function(resp) {
|
||||
self.plugins[plugin.type] = plugin;
|
||||
resolve(resp);
|
||||
}, function(resp) {
|
||||
reject(resp);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
this.init();
|
||||
});
|
||||
});
|
33
public/app/features/org/pluginsCtrl.js
Normal file
33
public/app/features/org/pluginsCtrl.js
Normal file
@ -0,0 +1,33 @@
|
||||
define([
|
||||
'angular',
|
||||
'app/core/config',
|
||||
],
|
||||
function (angular, config) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.controllers');
|
||||
|
||||
module.controller('PluginsCtrl', function($scope, $location, pluginSrv) {
|
||||
|
||||
$scope.init = function() {
|
||||
$scope.plugins = {};
|
||||
$scope.getPlugins();
|
||||
};
|
||||
|
||||
$scope.getPlugins = function() {
|
||||
pluginSrv.getAll().then(function(result) {
|
||||
console.log(result);
|
||||
$scope.plugins = result;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.update = function(plugin) {
|
||||
pluginSrv.update(plugin).then(function() {
|
||||
window.location.href = config.appSubUrl + $location.path();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.init();
|
||||
|
||||
});
|
||||
});
|
@ -13,9 +13,9 @@ function (angular, $, config) {
|
||||
restrict: 'E',
|
||||
link: function(scope, elem, attr) {
|
||||
var getter = $parse(attr.type), panelType = getter(scope);
|
||||
var panelPath = config.panels[panelType].path;
|
||||
var module = config.panels[panelType].module;
|
||||
|
||||
scope.require([panelPath + "/module"], function () {
|
||||
scope.require([module], function () {
|
||||
var panelEl = angular.element(document.createElement('grafana-panel-' + panelType));
|
||||
elem.append(panelEl);
|
||||
$compile(panelEl)(scope);
|
||||
|
13
public/app/plugins/external/example/readme.md
vendored
Normal file
13
public/app/plugins/external/example/readme.md
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
Example app is available at https://github.com/raintank/grafana-plugin-example
|
||||
|
||||
* Clone plugin repo git@github.com:raintank/grafana-plugin-example.git
|
||||
|
||||
* Modify grafana.ini (or custom.ini if your developing Grafana locally)
|
||||
|
||||
```ini
|
||||
[plugin.external-test]
|
||||
path = /<the_path_were_you_cloned_it>/grafana-plugin-example
|
||||
```
|
||||
|
||||
|
||||
|
@ -14,7 +14,7 @@ function (angular, app, _, config, PanelMeta) {
|
||||
module.directive('grafanaPanelDashlist', function() {
|
||||
return {
|
||||
controller: 'DashListPanelCtrl',
|
||||
templateUrl: 'app/panels/dashlist/module.html',
|
||||
templateUrl: 'app/plugins/panels/dashlist/module.html',
|
||||
};
|
||||
});
|
||||
|
||||
@ -26,7 +26,7 @@ function (angular, app, _, config, PanelMeta) {
|
||||
fullscreen: true,
|
||||
});
|
||||
|
||||
$scope.panelMeta.addEditorTab('Options', 'app/panels/dashlist/editor.html');
|
||||
$scope.panelMeta.addEditorTab('Options', 'app/plugins/panels/dashlist/editor.html');
|
||||
|
||||
var defaults = {
|
||||
mode: 'starred',
|
8
public/app/plugins/panels/dashlist/plugin.json
Normal file
8
public/app/plugins/panels/dashlist/plugin.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"pluginType": "panel",
|
||||
|
||||
"name": "Dashboard list",
|
||||
"type": "dashlist",
|
||||
|
||||
"module": "app/plugins/panels/dashlist/module"
|
||||
}
|
@ -45,7 +45,7 @@ function (angular, _, $) {
|
||||
popoverScope.series = seriesInfo;
|
||||
popoverSrv.show({
|
||||
element: el,
|
||||
templateUrl: 'app/panels/graph/legend.popover.html',
|
||||
templateUrl: 'app/plugins/panels/graph/legend.popover.html',
|
||||
scope: popoverScope
|
||||
});
|
||||
}
|
@ -17,7 +17,7 @@ function (angular, _, moment, kbn, TimeSeries, PanelMeta) {
|
||||
module.directive('grafanaPanelGraph', function() {
|
||||
return {
|
||||
controller: 'GraphCtrl',
|
||||
templateUrl: 'app/panels/graph/module.html',
|
||||
templateUrl: 'app/plugins/panels/graph/module.html',
|
||||
};
|
||||
});
|
||||
|
||||
@ -30,8 +30,8 @@ function (angular, _, moment, kbn, TimeSeries, PanelMeta) {
|
||||
metricsEditor: true,
|
||||
});
|
||||
|
||||
$scope.panelMeta.addEditorTab('Axes & Grid', 'app/panels/graph/axisEditor.html');
|
||||
$scope.panelMeta.addEditorTab('Display Styles', 'app/panels/graph/styleEditor.html');
|
||||
$scope.panelMeta.addEditorTab('Axes & Grid', 'app/plugins/panels/graph/axisEditor.html');
|
||||
$scope.panelMeta.addEditorTab('Display Styles', 'app/plugins/panels/graph/styleEditor.html');
|
||||
$scope.panelMeta.addEditorTab('Time range', 'app/features/panel/partials/panelTime.html');
|
||||
|
||||
$scope.panelMeta.addExtendedMenuItem('Export CSV', '', 'exportCsv()');
|
8
public/app/plugins/panels/graph/plugin.json
Normal file
8
public/app/plugins/panels/graph/plugin.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"pluginType": "panel",
|
||||
|
||||
"name": "Graph",
|
||||
"type": "graph",
|
||||
|
||||
"module": "app/plugins/panels/graph/module"
|
||||
}
|
@ -16,7 +16,7 @@ function (angular, app, _, kbn, TimeSeries, PanelMeta) {
|
||||
module.directive('grafanaPanelSinglestat', function() {
|
||||
return {
|
||||
controller: 'SingleStatCtrl',
|
||||
templateUrl: 'app/panels/singlestat/module.html',
|
||||
templateUrl: 'app/plugins/panels/singlestat/module.html',
|
||||
};
|
||||
});
|
||||
|
||||
@ -31,7 +31,7 @@ function (angular, app, _, kbn, TimeSeries, PanelMeta) {
|
||||
|
||||
$scope.fontSizes = ['20%', '30%','50%','70%','80%','100%', '110%', '120%', '150%', '170%', '200%'];
|
||||
|
||||
$scope.panelMeta.addEditorTab('Options', 'app/panels/singlestat/editor.html');
|
||||
$scope.panelMeta.addEditorTab('Options', 'app/plugins/panels/singlestat/editor.html');
|
||||
$scope.panelMeta.addEditorTab('Time range', 'app/features/panel/partials/panelTime.html');
|
||||
|
||||
// Set and populate defaults
|
8
public/app/plugins/panels/singlestat/plugin.json
Normal file
8
public/app/plugins/panels/singlestat/plugin.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"pluginType": "panel",
|
||||
|
||||
"name": "Singlestat",
|
||||
"type": "singlestat",
|
||||
|
||||
"module": "app/plugins/panels/singlestat/module"
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
///<reference path="../../headers/common.d.ts" />
|
||||
///<reference path="../../../headers/common.d.ts" />
|
||||
|
||||
import angular = require('angular');
|
||||
import _ = require('lodash');
|
||||
@ -21,7 +21,7 @@ export class TablePanelCtrl {
|
||||
metricsEditor: true,
|
||||
});
|
||||
|
||||
$scope.panelMeta.addEditorTab('Options', 'app/panels/table/options.html');
|
||||
$scope.panelMeta.addEditorTab('Options', 'app/plugins/panels/table/options.html');
|
||||
$scope.panelMeta.addEditorTab('Time range', 'app/features/panel/partials/panelTime.html');
|
||||
|
||||
var panelDefaults = {
|
@ -1,5 +1,4 @@
|
||||
///<reference path="../../headers/common.d.ts" />
|
||||
|
||||
///<reference path="../../../headers/common.d.ts" />
|
||||
|
||||
import angular = require('angular');
|
||||
import $ = require('jquery');
|
||||
@ -122,4 +121,3 @@ export function tablePanelEditor($q, uiSegmentSrv) {
|
||||
controller: TablePanelEditorCtrl,
|
||||
};
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
///<reference path="../../headers/common.d.ts" />
|
||||
///<reference path="../../../headers/common.d.ts" />
|
||||
|
||||
import angular = require('angular');
|
||||
import $ = require('jquery');
|
||||
@ -14,7 +14,7 @@ export function tablePanel() {
|
||||
'use strict';
|
||||
return {
|
||||
restrict: 'E',
|
||||
templateUrl: 'app/panels/table/module.html',
|
||||
templateUrl: 'app/plugins/panels/table/module.html',
|
||||
controller: TablePanelCtrl,
|
||||
link: function(scope, elem) {
|
||||
var data;
|
8
public/app/plugins/panels/table/plugin.json
Normal file
8
public/app/plugins/panels/table/plugin.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"pluginType": "panel",
|
||||
|
||||
"name": "Table",
|
||||
"type": "table",
|
||||
|
||||
"module": "app/plugins/panels/table/module"
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
///<reference path="../../headers/common.d.ts" />
|
||||
///<reference path="../../../headers/common.d.ts" />
|
||||
|
||||
import _ = require('lodash');
|
||||
import kbn = require('app/core/utils/kbn');
|
52
public/app/plugins/panels/table/table_model.ts
Normal file
52
public/app/plugins/panels/table/table_model.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import {transformers} from './transformers';
|
||||
|
||||
export class TableModel {
|
||||
columns: any[];
|
||||
rows: any[];
|
||||
|
||||
constructor() {
|
||||
this.columns = [];
|
||||
this.rows = [];
|
||||
}
|
||||
|
||||
sort(options) {
|
||||
if (options.col === null || this.columns.length <= options.col) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.rows.sort(function(a, b) {
|
||||
a = a[options.col];
|
||||
b = b[options.col];
|
||||
if (a < b) {
|
||||
return -1;
|
||||
}
|
||||
if (a > b) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
this.columns[options.col].sort = true;
|
||||
|
||||
if (options.desc) {
|
||||
this.rows.reverse();
|
||||
this.columns[options.col].desc = true;
|
||||
}
|
||||
}
|
||||
|
||||
static transform(data, panel) {
|
||||
var model = new TableModel();
|
||||
|
||||
if (!data || data.length === 0) {
|
||||
return model;
|
||||
}
|
||||
|
||||
var transformer = transformers[panel.transform];
|
||||
if (!transformer) {
|
||||
throw {message: 'Transformer ' + panel.transformer + ' not found'};
|
||||
}
|
||||
|
||||
transformer.transform(data, panel, model);
|
||||
return model;
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
///<reference path="../../headers/common.d.ts" />
|
||||
///<reference path="../../../headers/common.d.ts" />
|
||||
|
||||
import moment = require('moment');
|
||||
import _ = require('lodash');
|
@ -16,7 +16,7 @@ function (angular, app, _, require, PanelMeta) {
|
||||
module.directive('grafanaPanelText', function() {
|
||||
return {
|
||||
controller: 'TextPanelCtrl',
|
||||
templateUrl: 'app/panels/text/module.html',
|
||||
templateUrl: 'app/plugins/panels/text/module.html',
|
||||
};
|
||||
});
|
||||
|
||||
@ -28,7 +28,7 @@ function (angular, app, _, require, PanelMeta) {
|
||||
fullscreen: true,
|
||||
});
|
||||
|
||||
$scope.panelMeta.addEditorTab('Edit text', 'app/panels/text/editor.html');
|
||||
$scope.panelMeta.addEditorTab('Edit text', 'app/plugins/panels/text/editor.html');
|
||||
|
||||
// Set and populate defaults
|
||||
var _d = {
|
||||
@ -84,7 +84,7 @@ function (angular, app, _, require, PanelMeta) {
|
||||
$scope.updateContent(converter.makeHtml(text));
|
||||
}
|
||||
else {
|
||||
require(['./lib/showdown'], function (Showdown) {
|
||||
require(['vendor/showdown'], function (Showdown) {
|
||||
converter = new Showdown.converter();
|
||||
$scope.updateContent(converter.makeHtml(text));
|
||||
});
|
8
public/app/plugins/panels/text/plugin.json
Normal file
8
public/app/plugins/panels/text/plugin.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"pluginType": "panel",
|
||||
|
||||
"name": "Text",
|
||||
"type": "text",
|
||||
|
||||
"module": "app/plugins/panels/text/module"
|
||||
}
|
9
public/app/plugins/plugin.json
Normal file
9
public/app/plugins/plugin.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"pluginType": "bundle",
|
||||
"type": "core",
|
||||
"module": "",
|
||||
"enabled": true,
|
||||
"panelPlugins": ["graph", "singlestat", "text", "dashlist", "table"],
|
||||
"datasourcePlugins": ["mixed", "grafana", "graphite", "cloudwatch", "elasticsearch", "influxdb", "influxdb_08", "kairosdb", "opentsdb", "prometheus"],
|
||||
"externalPlugins": []
|
||||
}
|
@ -2,7 +2,7 @@ define([
|
||||
'./helpers',
|
||||
'app/features/panel/panel_srv',
|
||||
'app/features/panel/panel_helper',
|
||||
'app/panels/graph/module'
|
||||
'app/plugins/panels/graph/module'
|
||||
], function(helpers) {
|
||||
'use strict';
|
||||
|
||||
|
@ -3,7 +3,7 @@ define([
|
||||
'angular',
|
||||
'jquery',
|
||||
'app/core/time_series',
|
||||
'app/panels/graph/graph'
|
||||
'app/plugins/panels/graph/graph'
|
||||
], function(helpers, angular, $, TimeSeries) {
|
||||
'use strict';
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
define([
|
||||
'jquery',
|
||||
'app/panels/graph/graph.tooltip'
|
||||
'app/plugins/panels/graph/graph.tooltip'
|
||||
], function($, GraphTooltip) {
|
||||
'use strict';
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
define([
|
||||
'./helpers',
|
||||
'app/panels/graph/seriesOverridesCtrl'
|
||||
'app/plugins/panels/graph/seriesOverridesCtrl'
|
||||
], function(helpers) {
|
||||
'use strict';
|
||||
|
||||
|
@ -2,7 +2,7 @@ define([
|
||||
'./helpers',
|
||||
'app/features/panel/panel_srv',
|
||||
'app/features/panel/panel_helper',
|
||||
'app/panels/singlestat/module'
|
||||
'app/plugins/panels/singlestat/module'
|
||||
], function(helpers) {
|
||||
'use strict';
|
||||
|
||||
|
@ -95,6 +95,8 @@ function file2moduleName(filePath) {
|
||||
.replace(/\.\w*$/, '');
|
||||
}
|
||||
|
||||
window.grafanaBootData = {settings: {}};
|
||||
|
||||
require([
|
||||
'lodash',
|
||||
'angular',
|
||||
|
@ -10,10 +10,18 @@
|
||||
|
||||
[[if .User.LightTheme]]
|
||||
<link rel="stylesheet" href="[[.AppSubUrl]]/public/css/grafana.light.min.css">
|
||||
[[ range $css := .PluginCss ]]
|
||||
<link rel="stylesheet" href="[[$.AppSubUrl]]/[[ $css.Light ]]">
|
||||
[[ end ]]
|
||||
[[else]]
|
||||
<link rel="stylesheet" href="[[.AppSubUrl]]/public/css/grafana.dark.min.css">
|
||||
[[ range $css := .PluginCss ]]
|
||||
<link rel="stylesheet" href="[[$.AppSubUrl]]/[[ $css.Dark ]]">
|
||||
[[ end ]]
|
||||
[[end]]
|
||||
|
||||
|
||||
|
||||
<link rel="icon" type="image/png" href="[[.AppSubUrl]]/public/img/fav32.png">
|
||||
<base href="[[.AppSubUrl]]/" />
|
||||
|
||||
@ -50,10 +58,12 @@
|
||||
window.grafanaBootData = {
|
||||
user:[[.User]],
|
||||
settings: [[.Settings]],
|
||||
pluginModules: [[.PluginJs]],
|
||||
mainNavLinks: [[.MainNavLinks]]
|
||||
};
|
||||
|
||||
require(['app/app'], function (app) {
|
||||
app.boot();
|
||||
app.boot();
|
||||
})
|
||||
</script>
|
||||
|
||||
|
@ -8,9 +8,7 @@ module.exports = function(config) {
|
||||
expand: true,
|
||||
cwd: '<%= genDir %>',
|
||||
src: [
|
||||
//'index.html',
|
||||
'app/panels/**/*.html',
|
||||
'app/partials/**/*.html'
|
||||
'app/**/*.html',
|
||||
],
|
||||
dest: '<%= genDir %>'
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ module.exports = function(config) {
|
||||
'Gruntfile.js',
|
||||
'<%= srcDir %>/app/**/*.js',
|
||||
'<%= srcDir %>/plugins/**/*.js',
|
||||
'!<%= srcDir %>/app/panels/*/{lib,leaflet}/*',
|
||||
'!<%= srcDir %>/app/dashboards/*'
|
||||
],
|
||||
options: {
|
||||
@ -20,4 +19,4 @@ module.exports = function(config) {
|
||||
"disallowRightStickedOperators": ["?", "+", "/", "*", ":", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<="],
|
||||
"requireRightStickedOperators": ["!"],
|
||||
"requireLeftStickedOperators": [","],
|
||||
*/
|
||||
*/
|
||||
|
@ -18,9 +18,8 @@ module.exports = function(config) {
|
||||
'dist/*',
|
||||
'sample/*',
|
||||
'<%= srcDir %>/vendor/*',
|
||||
'<%= srcDir %>/app/panels/*/{lib,leaflet}/*',
|
||||
'<%= srcDir %>/app/dashboards/*'
|
||||
]
|
||||
}
|
||||
};
|
||||
};
|
||||
};
|
||||
|
@ -62,11 +62,11 @@ module.exports = function(config,grunt) {
|
||||
];
|
||||
|
||||
var fs = require('fs');
|
||||
var panelPath = config.srcDir + '/app/panels';
|
||||
var panelPath = config.srcDir + '/app/plugins/panels';
|
||||
|
||||
// create a module for each directory in public/app/panels/
|
||||
fs.readdirSync(panelPath).forEach(function (panelName) {
|
||||
requireModules[0].include.push('app/panels/'+panelName+'/module');
|
||||
requireModules[0].include.push('app/plugins/panels/'+panelName+'/module');
|
||||
});
|
||||
|
||||
return { options: options };
|
||||
|
Loading…
Reference in New Issue
Block a user