diff --git a/pkg/api/api.go b/pkg/api/api.go
index 27eb3c749db..e9d85dc0c8d 100644
--- a/pkg/api/api.go
+++ b/pkg/api/api.go
@@ -185,5 +185,7 @@ func Register(r *macaron.Macaron) {
// rendering
r.Get("/render/*", reqSignedIn, RenderToPng)
+ InitThirdPartyRoutes(r)
+
r.NotFound(NotFoundHandler)
}
diff --git a/pkg/api/index.go b/pkg/api/index.go
index 556db006b2f..072878a2cfd 100644
--- a/pkg/api/index.go
+++ b/pkg/api/index.go
@@ -3,6 +3,7 @@ package api
import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/middleware"
+ "github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/setting"
)
@@ -51,6 +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)
+ }
+ for _, css := range integration.Css {
+ thirdPartyCss = append(thirdPartyCss, css.Href)
+ }
+ for _, item := range integration.MenuItems {
+ thirdPartyMenu = append(thirdPartyMenu, item)
+ }
+
+ }
+ c.Data["ThirdPartyJs"] = thirdPartyJs
+ c.Data["ThirdPartyCss"] = thirdPartyCss
+ c.Data["ThirdPartyMenu"] = thirdPartyMenu
return nil
}
diff --git a/pkg/api/thirdparty.go b/pkg/api/thirdparty.go
new file mode 100644
index 00000000000..588c3b40ce7
--- /dev/null
+++ b/pkg/api/thirdparty.go
@@ -0,0 +1,75 @@
+package api
+
+import (
+ "encoding/json"
+ "github.com/Unknwon/macaron"
+ "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) {
+ /*
+ // Handle Auth and role requirements
+ if route.ReqSignedIn {
+ c.Invoke(middleware.Auth(&middleware.AuthOptions{ReqSignedIn: true}))
+ }
+ if route.ReqGrafanaAdmin {
+ c.Invoke(middleware.Auth(&middleware.AuthOptions{ReqSignedIn: true, ReqGrafanaAdmin: true}))
+ }
+ if route.ReqRole != nil {
+ if *route.ReqRole == m.ROLE_EDITOR {
+ c.Invoke(middleware.RoleAuth(m.ROLE_EDITOR, m.ROLE_ADMIN))
+ }
+ if *route.ReqRole == m.ROLE_ADMIN {
+ c.Invoke(middleware.RoleAuth(m.ROLE_ADMIN))
+ }
+ }
+ */
+ 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))
+ }
+ }
+}
+
+func ThirdParty(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)
+ return
+ }
+ log.Printf(string(ctx))
+ targetUrl, _ := url.Parse(routeUrl)
+ proxy := NewThirdPartyProxy(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 {
+ 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}
+}
diff --git a/pkg/models/third_party.go b/pkg/models/third_party.go
new file mode 100644
index 00000000000..0bce0d86f8f
--- /dev/null
+++ b/pkg/models/third_party.go
@@ -0,0 +1,31 @@
+package models
+
+type ThirdPartyRoute struct {
+ Path string `json:"path"`
+ Method string `json:"method"`
+ ReqSignedIn bool `json:"req_signed_in"`
+ ReqGrafanaAdmin bool `json:"req_grafana_admin"`
+ ReqRole RoleType `json:"req_role"`
+ Url string `json:"url"`
+}
+
+type ThirdPartyJs struct {
+ src string `json:"src"`
+}
+
+type ThirdPartyMenuItem struct {
+ Text string `json:"text"`
+ Icon string `json:"icon"`
+ Href string `json:"href"`
+}
+
+type ThirdPartyCss 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"`
+}
diff --git a/pkg/plugins/plugins.go b/pkg/plugins/plugins.go
index 2f7e5264e53..c411a47a977 100644
--- a/pkg/plugins/plugins.go
+++ b/pkg/plugins/plugins.go
@@ -16,8 +16,44 @@ type PluginMeta struct {
Name string `json:"name"`
}
+type ThirdPartyRoute struct {
+ Path string `json:"path"`
+ Method string `json:"method"`
+ ReqSignedIn bool `json:"req_signed_in"`
+ ReqGrafanaAdmin bool `json:"req_grafana_admin"`
+ ReqRole string `json:"req_role"`
+ Url string `json:"url"`
+}
+
+type ThirdPartyJs struct {
+ Src string `json:"src"`
+}
+
+type ThirdPartyMenuItem struct {
+ Text string `json:"text"`
+ Icon string `json:"icon"`
+ Href string `json:"href"`
+}
+
+type ThirdPartyCss 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 ThirdPartyPlugin struct {
+ PluginType string `json:"pluginType"`
+ Integration ThirdPartyIntegration `json:"integration"`
+}
+
var (
- DataSources map[string]interface{}
+ DataSources map[string]interface{}
+ Integrations []ThirdPartyIntegration
)
type PluginScanner struct {
@@ -31,6 +67,7 @@ func Init() {
func scan(pluginDir string) error {
DataSources = make(map[string]interface{})
+ Integrations = make([]ThirdPartyIntegration, 0)
scanner := &PluginScanner{
pluginPath: pluginDir,
@@ -94,6 +131,14 @@ func (scanner *PluginScanner) loadPluginJson(path string) error {
DataSources[datasourceType.(string)] = pluginJson
}
+ if pluginType == "thirdPartyIntegration" {
+ p := ThirdPartyPlugin{}
+ reader.Seek(0, 0)
+ if err := jsonParser.Decode(&p); err != nil {
+ return err
+ }
+ Integrations = append(Integrations, p.Integration)
+ }
return nil
}
diff --git a/public/app/app.js b/public/app/app.js
index 4f30df34d89..16ce8a454d8 100644
--- a/public/app/app.js
+++ b/public/app/app.js
@@ -37,6 +37,8 @@ function (angular, $, _, appLevelRequire) {
} else {
_.extend(module, register_fns);
}
+ // push it into the apps dependencies
+ apps_deps.push(module.name);
return module;
};
@@ -66,8 +68,6 @@ 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 = [
diff --git a/public/app/controllers/sidemenuCtrl.js b/public/app/controllers/sidemenuCtrl.js
index b7ba32f0d35..bd6538b15cb 100644
--- a/public/app/controllers/sidemenuCtrl.js
+++ b/public/app/controllers/sidemenuCtrl.js
@@ -29,6 +29,16 @@ function (angular, _, $, config) {
href: $scope.getUrl("/datasources"),
});
}
+
+ 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)
+ });
+ });
+ }
};
$scope.loadOrgs = function() {
diff --git a/public/app/core/routes/all.js b/public/app/core/routes/all.js
index a7e36a0e228..b0e41bc956d 100644
--- a/public/app/core/routes/all.js
+++ b/public/app/core/routes/all.js
@@ -10,7 +10,7 @@ define([
$locationProvider.html5Mode(true);
var loadOrgBundle = new BundleLoader.BundleLoader('app/features/org/all');
-
+ console.log("adding grafana routes");
$routeProvider
.when('/', {
templateUrl: 'app/partials/dashboard.html',
diff --git a/public/app/plugins/thirdPartyIntegration/raintank/plugin.json b/public/app/plugins/thirdPartyIntegration/raintank/plugin.json
new file mode 100644
index 00000000000..7c1dcb0dcee
--- /dev/null
+++ b/public/app/plugins/thirdPartyIntegration/raintank/plugin.json
@@ -0,0 +1,40 @@
+{
+ "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 600e18fb954..9998b0d2b68 100644
--- a/public/views/index.html
+++ b/public/views/index.html
@@ -13,6 +13,9 @@
[[else]]
[[end]]
+ [[ range $css := .ThirdPartyCss ]]
+
+ [[ end ]]
@@ -53,11 +56,17 @@
settings: [[.Settings]],
};
+ window.thirdParty = {
+ MainLinks: [[.ThirdPartyMenu]]
+ };
+
require(['app/app'], function (app) {
- app.boot();
+ app.boot();
})
-
+ [[ range $js := .ThirdPartyJs]]
+
+ [[ end ]]
[[if .GoogleAnalyticsId]]