diff --git a/pkg/api/api.go b/pkg/api/api.go index e9d85dc0c8d..0d783968c57 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -185,7 +185,7 @@ func Register(r *macaron.Macaron) { // rendering r.Get("/render/*", reqSignedIn, RenderToPng) - InitThirdPartyRoutes(r) + InitExternalPluginRoutes(r) r.NotFound(NotFoundHandler) } diff --git a/pkg/api/thirdparty.go b/pkg/api/externalplugin.go similarity index 67% rename from pkg/api/thirdparty.go rename to pkg/api/externalplugin.go index 588c3b40ce7..e1ee5806b9e 100644 --- a/pkg/api/thirdparty.go +++ b/pkg/api/externalplugin.go @@ -3,16 +3,16 @@ package api import ( "encoding/json" "github.com/Unknwon/macaron" + "github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/middleware" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/util" - "log" "net/http" "net/http/httputil" "net/url" ) -func InitThirdPartyRoutes(r *macaron.Macaron) { +func InitExternalPluginRoutes(r *macaron.Macaron) { /* // Handle Auth and role requirements if route.ReqSignedIn { @@ -30,34 +30,33 @@ func InitThirdPartyRoutes(r *macaron.Macaron) { } } */ - for _, integration := range plugins.Integrations { - log.Printf("adding routes for integration") - for _, route := range integration.Routes { - log.Printf("adding route %s %s", route.Method, route.Path) - r.Route(util.JoinUrlFragments("/thirdparty/", route.Path), route.Method, ThirdParty(route.Url)) + for _, plugin := range plugins.ExternalPlugins { + log.Info("adding routes for external plugin") + for _, route := range plugin.Settings.Routes { + log.Info("adding route %s /plugins%s", route.Method, route.Path) + r.Route(util.JoinUrlFragments("/plugins/", route.Path), route.Method, ExternalPlugin(route.Url)) } } } -func ThirdParty(routeUrl string) macaron.Handler { +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, "Not found", err) + c.JsonApiErr(500, "failed to marshal context to json.", err) return } - log.Printf(string(ctx)) targetUrl, _ := url.Parse(routeUrl) - proxy := NewThirdPartyProxy(string(ctx), path, targetUrl) + proxy := NewExternalPluginProxy(string(ctx), path, targetUrl) proxy.Transport = dataProxyTransport proxy.ServeHTTP(c.RW(), c.Req.Request) } } -func NewThirdPartyProxy(ctx string, proxyPath string, targetUrl *url.URL) *httputil.ReverseProxy { +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 diff --git a/pkg/api/index.go b/pkg/api/index.go index 072878a2cfd..e41db285efc 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -52,25 +52,25 @@ func setIndexViewData(c *middleware.Context) error { if setting.GoogleTagManagerId != "" { c.Data["GoogleTagManagerId"] = setting.GoogleTagManagerId } - // This can be loaded from the DB/file to allow 3rdParty integration - thirdPartyJs := make([]string, 0) - thirdPartyCss := make([]string, 0) - thirdPartyMenu := make([]*plugins.ThirdPartyMenuItem, 0) - for _, integration := range plugins.Integrations { - for _, js := range integration.Js { - thirdPartyJs = append(thirdPartyJs, js.Src) + + externalPluginJs := make([]string, 0) + externalPluginCss := make([]string, 0) + externalPluginMenu := make([]*plugins.ExternalPluginMenuItem, 0) + for _, plugin := range plugins.ExternalPlugins { + for _, js := range plugin.Settings.Js { + externalPluginJs = append(externalPluginJs, js.Src) } - for _, css := range integration.Css { - thirdPartyCss = append(thirdPartyCss, css.Href) + for _, css := range plugin.Settings.Css { + externalPluginCss = append(externalPluginCss, css.Href) } - for _, item := range integration.MenuItems { - thirdPartyMenu = append(thirdPartyMenu, item) + for _, item := range plugin.Settings.MenuItems { + externalPluginMenu = append(externalPluginMenu, item) } } - c.Data["ThirdPartyJs"] = thirdPartyJs - c.Data["ThirdPartyCss"] = thirdPartyCss - c.Data["ThirdPartyMenu"] = thirdPartyMenu + c.Data["ExternalPluginJs"] = externalPluginJs + c.Data["ExternalPluginCss"] = externalPluginCss + c.Data["ExternalPluginMenu"] = externalPluginMenu return nil } diff --git a/pkg/models/third_party.go b/pkg/models/external_plugin.go similarity index 52% rename from pkg/models/third_party.go rename to pkg/models/external_plugin.go index 0bce0d86f8f..a5fab329fb1 100644 --- a/pkg/models/third_party.go +++ b/pkg/models/external_plugin.go @@ -1,6 +1,6 @@ package models -type ThirdPartyRoute struct { +type ExternalPluginRoute struct { Path string `json:"path"` Method string `json:"method"` ReqSignedIn bool `json:"req_signed_in"` @@ -9,23 +9,23 @@ type ThirdPartyRoute struct { Url string `json:"url"` } -type ThirdPartyJs struct { +type ExternalPluginJs struct { src string `json:"src"` } -type ThirdPartyMenuItem struct { +type ExternalPluginMenuItem struct { Text string `json:"text"` Icon string `json:"icon"` Href string `json:"href"` } -type ThirdPartyCss struct { +type ExternalPluginCss struct { Href string `json:"href"` } -type ThirdPartyIntegration struct { - Routes []*ThirdPartyRoute `json:"routes"` - Js []*ThirdPartyJs `json:"js"` - Css []*ThirdPartyCss `json:"css"` - MenuItems []*ThirdPartyMenuItem `json:"menu_items"` +type ExternalPluginIntegration struct { + Routes []*ExternalPluginRoute `json:"routes"` + Js []*ExternalPluginJs `json:"js"` + Css []*ExternalPluginCss `json:"css"` + MenuItems []*ExternalPluginMenuItem `json:"menu_items"` } diff --git a/pkg/plugins/plugins.go b/pkg/plugins/plugins.go index c411a47a977..73c642176bb 100644 --- a/pkg/plugins/plugins.go +++ b/pkg/plugins/plugins.go @@ -16,7 +16,7 @@ type PluginMeta struct { Name string `json:"name"` } -type ThirdPartyRoute struct { +type ExternalPluginRoute struct { Path string `json:"path"` Method string `json:"method"` ReqSignedIn bool `json:"req_signed_in"` @@ -25,35 +25,35 @@ type ThirdPartyRoute struct { Url string `json:"url"` } -type ThirdPartyJs struct { +type ExternalPluginJs struct { Src string `json:"src"` } -type ThirdPartyMenuItem struct { +type ExternalPluginMenuItem struct { Text string `json:"text"` Icon string `json:"icon"` Href string `json:"href"` } -type ThirdPartyCss struct { +type ExternalPluginCss struct { Href string `json:"href"` } -type ThirdPartyIntegration struct { - Routes []*ThirdPartyRoute `json:"routes"` - Js []*ThirdPartyJs `json:"js"` - Css []*ThirdPartyCss `json:"css"` - MenuItems []*ThirdPartyMenuItem `json:"menu_items"` +type ExternalPluginSettings struct { + Routes []*ExternalPluginRoute `json:"routes"` + Js []*ExternalPluginJs `json:"js"` + Css []*ExternalPluginCss `json:"css"` + MenuItems []*ExternalPluginMenuItem `json:"menu_items"` } -type ThirdPartyPlugin struct { - PluginType string `json:"pluginType"` - Integration ThirdPartyIntegration `json:"integration"` +type ExternalPlugin struct { + PluginType string `json:"pluginType"` + Settings ExternalPluginSettings `json:"settings"` } var ( - DataSources map[string]interface{} - Integrations []ThirdPartyIntegration + DataSources map[string]interface{} + ExternalPlugins []ExternalPlugin ) type PluginScanner struct { @@ -67,7 +67,7 @@ func Init() { func scan(pluginDir string) error { DataSources = make(map[string]interface{}) - Integrations = make([]ThirdPartyIntegration, 0) + ExternalPlugins = make([]ExternalPlugin, 0) scanner := &PluginScanner{ pluginPath: pluginDir, @@ -131,13 +131,13 @@ func (scanner *PluginScanner) loadPluginJson(path string) error { DataSources[datasourceType.(string)] = pluginJson } - if pluginType == "thirdPartyIntegration" { - p := ThirdPartyPlugin{} + if pluginType == "externalPlugin" { + p := ExternalPlugin{} reader.Seek(0, 0) if err := jsonParser.Decode(&p); err != nil { return err } - Integrations = append(Integrations, p.Integration) + ExternalPlugins = append(ExternalPlugins, p) } return nil diff --git a/public/app/controllers/sidemenuCtrl.js b/public/app/controllers/sidemenuCtrl.js index bd6538b15cb..64fe3763aeb 100644 --- a/public/app/controllers/sidemenuCtrl.js +++ b/public/app/controllers/sidemenuCtrl.js @@ -30,13 +30,15 @@ function (angular, _, $, config) { }); } - if (_.isArray(window.thirdParty.MainLinks)) { - _.forEach(window.thirdParty.MainLinks, function(item) { - $scope.mainLinks.push({ - text: item.text, - icon: item.icon, - href: $scope.getUrl(item.href) - }); + if (_.isArray(window.externalPlugins.MainLinks)) { + _.forEach(window.externalPlugins.MainLinks, function(item) { + if (!item.adminOnly || contextSrv.hasRole('Admin')) { + $scope.mainLinks.push({ + text: item.text, + icon: item.icon, + href: $scope.getUrl(item.href) + }); + } }); } }; diff --git a/public/app/plugins/externalPlugins/example/README.TXT b/public/app/plugins/externalPlugins/example/README.TXT new file mode 100644 index 00000000000..0963375e9fe --- /dev/null +++ b/public/app/plugins/externalPlugins/example/README.TXT @@ -0,0 +1,3 @@ +Example app is available at https://github.com/raintank/grafana-plugin-example + +To use, download the example app from github and run it (requires python Flask). Then rename the "_plugin.json" file in this director to "plugin.json" and restart Grafana. diff --git a/public/app/plugins/externalPlugins/example/_plugin.json b/public/app/plugins/externalPlugins/example/_plugin.json new file mode 100644 index 00000000000..a8469eadd62 --- /dev/null +++ b/public/app/plugins/externalPlugins/example/_plugin.json @@ -0,0 +1,41 @@ +{ + "pluginType": "externalPlugin", + "settings": { + "routes": [ + { + "path": "/example/static/*", + "method": "*", + "req_signed_in": false, + "req_grafana_admin": false, + "req_role": "Admin", + "url": "http://localhost:5000/static" + }, + { + "path": "/example/api/*", + "method": "*", + "req_signed_in": true, + "req_grafana_admin": false, + "req_role": "Admin", + "url": "http://localhost:5000/api" + } + ], + "css": [ + { + "href": "/example/static/css/example.css" + } + ], + "js": [ + { + "src": "/example/static/js/app.js" + } + ], + "menu_items": [ + { + "text": "Example Plugin", + "icon": "fa fa-fw fa-smile-o", + "href": "/example/servers", + "adminOnly": false, + } + ] + } +} diff --git a/public/app/plugins/thirdPartyIntegration/raintank/plugin.json b/public/app/plugins/thirdPartyIntegration/raintank/plugin.json deleted file mode 100644 index 7c1dcb0dcee..00000000000 --- a/public/app/plugins/thirdPartyIntegration/raintank/plugin.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "pluginType": "thirdPartyIntegration", - "integration": { - "routes": [ - { - "path": "/raintank/public/*", - "method": "*", - "req_signed_in": false, - "req_grafana_admin": false, - "req_role": "Admin", - "url": "http://localhost:3001/public" - }, - { - "path": "/raintank/api/*", - "method": "*", - "req_signed_in": true, - "req_grafana_admin": false, - "req_role": "Admin", - "url": "http://localhost:3001/api" - } - ], - "css": [ - { - "href": "/path/to/file.css" - } - ], - "js": [ - { - "src": "/raintank/public/app.js" - } - ], - "menu_items": [ - { - "text": "Menu Text", - "icon": "fa fa-fw fa-database", - "href": "/raintank/test" - } - ] - } -} diff --git a/public/views/index.html b/public/views/index.html index 9998b0d2b68..5dc46550fef 100644 --- a/public/views/index.html +++ b/public/views/index.html @@ -13,8 +13,8 @@ [[else]] [[end]] - [[ range $css := .ThirdPartyCss ]] - + [[ range $css := .ExternalPluginCss ]] + [[ end ]] @@ -56,16 +56,16 @@ settings: [[.Settings]], }; - window.thirdParty = { - MainLinks: [[.ThirdPartyMenu]] + window.externalPlugins = { + MainLinks: [[.ExternalPluginMenu]] }; require(['app/app'], function (app) { app.boot(); }) - [[ range $js := .ThirdPartyJs]] - + [[ range $js := .ExternalPluginJs]] + [[ end ]] [[if .GoogleAnalyticsId]]