diff --git a/conf/defaults.ini b/conf/defaults.ini
index 851c88efc7c..6f63891d1ee 100644
--- a/conf/defaults.ini
+++ b/conf/defaults.ini
@@ -111,6 +111,13 @@ gc_interval_time = 86400
# Change this option to false to disable reporting.
reporting_enabled = true
+# Set to false to disable all checks to https://grafana.net
+# for new vesions (grafana itself and plugins), check is used
+# in some UI views to notify that grafana or plugin update exists
+# This option does not cause any auto updates, nor send any information
+# only a GET request to http://grafana.net to get latest versions
+check_for_updates = true
+
# Google Analytics universal tracking code, only enabled if you specify an id here
google_analytics_ua_id =
diff --git a/conf/sample.ini b/conf/sample.ini
index b875cbda093..4ab923c1376 100644
--- a/conf/sample.ini
+++ b/conf/sample.ini
@@ -100,6 +100,13 @@
# Change this option to false to disable reporting.
;reporting_enabled = true
+# Set to false to disable all checks to https://grafana.net
+# for new vesions (grafana itself and plugins), check is used
+# in some UI views to notify that grafana or plugin update exists
+# This option does not cause any auto updates, nor send any information
+# only a GET request to http://grafana.net to get latest versions
+check_for_updates = true
+
# Google Analytics universal tracking code, only enabled if you specify an id here
;google_analytics_ua_id =
diff --git a/latest.json b/latest.json
index b4ffb38bab9..48595c735e4 100644
--- a/latest.json
+++ b/latest.json
@@ -1,4 +1,4 @@
{
- "stable": "2.6.0",
- "testing": "3.0.0"
+ "stable": "2.6.0",
+ "testing": "3.0.0-beta2"
}
diff --git a/pkg/api/api.go b/pkg/api/api.go
index b1c89e54bc3..fae78053962 100644
--- a/pkg/api/api.go
+++ b/pkg/api/api.go
@@ -253,6 +253,9 @@ func Register(r *macaron.Macaron) {
// rendering
r.Get("/render/*", reqSignedIn, RenderToPng)
+ // grafana.net proxy
+ r.Any("/api/gnet/*", reqSignedIn, ProxyGnetRequest)
+
// Gravatar service.
avt := avatar.CacheServer()
r.Get("/avatar/:hash", avt.ServeHTTP)
diff --git a/pkg/api/dtos/plugins.go b/pkg/api/dtos/plugins.go
index 53c911c10fa..fbdf8d4e0ea 100644
--- a/pkg/api/dtos/plugins.go
+++ b/pkg/api/dtos/plugins.go
@@ -15,15 +15,20 @@ type PluginSetting struct {
Dependencies *plugins.PluginDependencies `json:"dependencies"`
JsonData map[string]interface{} `json:"jsonData"`
DefaultNavUrl string `json:"defaultNavUrl"`
+
+ LatestVersion string `json:"latestVersion"`
+ HasUpdate bool `json:"hasUpdate"`
}
type PluginListItem struct {
- Name string `json:"name"`
- Type string `json:"type"`
- Id string `json:"id"`
- Enabled bool `json:"enabled"`
- Pinned bool `json:"pinned"`
- Info *plugins.PluginInfo `json:"info"`
+ Name string `json:"name"`
+ Type string `json:"type"`
+ Id string `json:"id"`
+ Enabled bool `json:"enabled"`
+ Pinned bool `json:"pinned"`
+ Info *plugins.PluginInfo `json:"info"`
+ LatestVersion string `json:"latestVersion"`
+ HasUpdate bool `json:"hasUpdate"`
}
type PluginList []PluginListItem
diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go
index f23f416b47a..dd84f7827eb 100644
--- a/pkg/api/frontendsettings.go
+++ b/pkg/api/frontendsettings.go
@@ -137,9 +137,11 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro
"allowOrgCreate": (setting.AllowUserOrgCreate && c.IsSignedIn) || c.IsGrafanaAdmin,
"authProxyEnabled": setting.AuthProxyEnabled,
"buildInfo": map[string]interface{}{
- "version": setting.BuildVersion,
- "commit": setting.BuildCommit,
- "buildstamp": setting.BuildStamp,
+ "version": setting.BuildVersion,
+ "commit": setting.BuildCommit,
+ "buildstamp": setting.BuildStamp,
+ "latestVersion": plugins.GrafanaLatestVersion,
+ "hasUpdate": plugins.GrafanaHasUpdate,
},
}
diff --git a/pkg/api/gnetproxy.go b/pkg/api/gnetproxy.go
new file mode 100644
index 00000000000..6511afd39b7
--- /dev/null
+++ b/pkg/api/gnetproxy.go
@@ -0,0 +1,46 @@
+package api
+
+import (
+ "crypto/tls"
+ "net"
+ "net/http"
+ "net/http/httputil"
+ "time"
+
+ "github.com/grafana/grafana/pkg/middleware"
+ "github.com/grafana/grafana/pkg/util"
+)
+
+var gNetProxyTransport = &http.Transport{
+ TLSClientConfig: &tls.Config{InsecureSkipVerify: false},
+ Proxy: http.ProxyFromEnvironment,
+ Dial: (&net.Dialer{
+ Timeout: 30 * time.Second,
+ KeepAlive: 30 * time.Second,
+ }).Dial,
+ TLSHandshakeTimeout: 10 * time.Second,
+}
+
+func ReverseProxyGnetReq(proxyPath string) *httputil.ReverseProxy {
+ director := func(req *http.Request) {
+ req.URL.Scheme = "https"
+ req.URL.Host = "grafana.net"
+ req.Host = "grafana.net"
+
+ req.URL.Path = util.JoinUrlFragments("https://grafana.net/api", proxyPath)
+
+ // clear cookie headers
+ req.Header.Del("Cookie")
+ req.Header.Del("Set-Cookie")
+ }
+
+ return &httputil.ReverseProxy{Director: director}
+}
+
+func ProxyGnetRequest(c *middleware.Context) {
+ proxyPath := c.Params("*")
+ proxy := ReverseProxyGnetReq(proxyPath)
+ proxy.Transport = gNetProxyTransport
+ proxy.ServeHTTP(c.Resp, c.Req.Request)
+ c.Resp.Header().Del("Set-Cookie")
+}
diff --git a/pkg/api/plugins.go b/pkg/api/plugins.go
index 0411c0746ef..6446f7fee33 100644
--- a/pkg/api/plugins.go
+++ b/pkg/api/plugins.go
@@ -14,6 +14,7 @@ func GetPluginList(c *middleware.Context) Response {
typeFilter := c.Query("type")
enabledFilter := c.Query("enabled")
embeddedFilter := c.Query("embedded")
+ coreFilter := c.Query("core")
pluginSettingsMap, err := plugins.GetPluginSettings(c.OrgId)
@@ -28,16 +29,23 @@ func GetPluginList(c *middleware.Context) Response {
continue
}
+ // filter out core plugins
+ if coreFilter == "0" && pluginDef.IsCorePlugin {
+ continue
+ }
+
// filter on type
if typeFilter != "" && typeFilter != pluginDef.Type {
continue
}
listItem := dtos.PluginListItem{
- Id: pluginDef.Id,
- Name: pluginDef.Name,
- Type: pluginDef.Type,
- Info: &pluginDef.Info,
+ Id: pluginDef.Id,
+ Name: pluginDef.Name,
+ Type: pluginDef.Type,
+ Info: &pluginDef.Info,
+ LatestVersion: pluginDef.GrafanaNetVersion,
+ HasUpdate: pluginDef.GrafanaNetHasUpdate,
}
if pluginSetting, exists := pluginSettingsMap[pluginDef.Id]; exists {
@@ -81,6 +89,8 @@ func GetPluginSettingById(c *middleware.Context) Response {
BaseUrl: def.BaseUrl,
Module: def.Module,
DefaultNavUrl: def.DefaultNavUrl,
+ LatestVersion: def.GrafanaNetVersion,
+ HasUpdate: def.GrafanaNetHasUpdate,
}
query := m.GetPluginSettingByIdQuery{PluginId: pluginId, OrgId: c.OrgId}
diff --git a/pkg/cmd/grafana-server/main.go b/pkg/cmd/grafana-server/main.go
index dad3f437390..973997c4319 100644
--- a/pkg/cmd/grafana-server/main.go
+++ b/pkg/cmd/grafana-server/main.go
@@ -24,7 +24,7 @@ import (
"github.com/grafana/grafana/pkg/social"
)
-var version = "3.0.0-pre1"
+var version = "3.0.0-beta2"
var commit = "NA"
var buildstamp string
var build_date string
diff --git a/pkg/plugins/frontend_plugin.go b/pkg/plugins/frontend_plugin.go
index 55690777b2a..974559001d1 100644
--- a/pkg/plugins/frontend_plugin.go
+++ b/pkg/plugins/frontend_plugin.go
@@ -14,7 +14,7 @@ type FrontendPluginBase struct {
}
func (fp *FrontendPluginBase) initFrontendPlugin() {
- if isInternalPlugin(fp.PluginDir) {
+ if isExternalPlugin(fp.PluginDir) {
StaticRoutes = append(StaticRoutes, &PluginStaticRoute{
Directory: fp.PluginDir,
PluginId: fp.Id,
@@ -48,17 +48,18 @@ func (fp *FrontendPluginBase) setPathsBasedOnApp(app *AppPlugin) {
func (fp *FrontendPluginBase) handleModuleDefaults() {
- if isInternalPlugin(fp.PluginDir) {
+ if isExternalPlugin(fp.PluginDir) {
fp.Module = path.Join("plugins", fp.Id, "module")
fp.BaseUrl = path.Join("public/plugins", fp.Id)
return
}
+ fp.IsCorePlugin = true
fp.Module = path.Join("app/plugins", fp.Type, fp.Id, "module")
fp.BaseUrl = path.Join("public/app/plugins", fp.Type, fp.Id)
}
-func isInternalPlugin(pluginDir string) bool {
+func isExternalPlugin(pluginDir string) bool {
return !strings.Contains(pluginDir, setting.StaticRootPath)
}
diff --git a/pkg/plugins/models.go b/pkg/plugins/models.go
index 30f794285e1..68268239c51 100644
--- a/pkg/plugins/models.go
+++ b/pkg/plugins/models.go
@@ -43,6 +43,10 @@ type PluginBase struct {
IncludedInAppId string `json:"-"`
PluginDir string `json:"-"`
DefaultNavUrl string `json:"-"`
+ IsCorePlugin bool `json:"-"`
+
+ GrafanaNetVersion string `json:"-"`
+ GrafanaNetHasUpdate bool `json:"-"`
// cache for readme file contents
Readme []byte `json:"-"`
diff --git a/pkg/plugins/plugins.go b/pkg/plugins/plugins.go
index 0d69d5745b9..073685afc79 100644
--- a/pkg/plugins/plugins.go
+++ b/pkg/plugins/plugins.go
@@ -22,6 +22,9 @@ var (
Apps map[string]*AppPlugin
Plugins map[string]*PluginBase
PluginTypes map[string]interface{}
+
+ GrafanaLatestVersion string
+ GrafanaHasUpdate bool
)
type PluginScanner struct {
@@ -70,6 +73,7 @@ func Init() error {
app.initApp()
}
+ go StartPluginUpdateChecker()
return nil
}
diff --git a/pkg/plugins/update_checker.go b/pkg/plugins/update_checker.go
new file mode 100644
index 00000000000..33ca1b2277b
--- /dev/null
+++ b/pkg/plugins/update_checker.go
@@ -0,0 +1,119 @@
+package plugins
+
+import (
+ "encoding/json"
+ "io/ioutil"
+ "net/http"
+ "strings"
+ "time"
+
+ "github.com/grafana/grafana/pkg/log"
+ "github.com/grafana/grafana/pkg/setting"
+)
+
+type GrafanaNetPlugin struct {
+ Slug string `json:"slug"`
+ Version string `json:"version"`
+}
+
+type GithubLatest struct {
+ Stable string `json:"stable"`
+ Testing string `json:"testing"`
+}
+
+func StartPluginUpdateChecker() {
+ if !setting.CheckForUpdates {
+ return
+ }
+
+ // do one check directly
+ go checkForUpdates()
+
+ ticker := time.NewTicker(time.Minute * 10)
+ for {
+ select {
+ case <-ticker.C:
+ checkForUpdates()
+ }
+ }
+}
+
+func getAllExternalPluginSlugs() string {
+ str := ""
+
+ for _, plug := range Plugins {
+ if plug.IsCorePlugin {
+ continue
+ }
+
+ str += plug.Id + ","
+ }
+
+ return str
+}
+
+func checkForUpdates() {
+ log.Trace("Checking for updates")
+
+ client := http.Client{Timeout: time.Duration(5 * time.Second)}
+
+ pluginSlugs := getAllExternalPluginSlugs()
+ resp, err := client.Get("https://grafana.net/api/plugins/versioncheck?slugIn=" + pluginSlugs + "&grafanaVersion=" + setting.BuildVersion)
+
+ if err != nil {
+ log.Trace("Failed to get plugins repo from grafana.net, %v", err.Error())
+ return
+ }
+
+ defer resp.Body.Close()
+
+ body, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ log.Trace("Update check failed, reading response from grafana.net, %v", err.Error())
+ return
+ }
+
+ gNetPlugins := []GrafanaNetPlugin{}
+ err = json.Unmarshal(body, &gNetPlugins)
+ if err != nil {
+ log.Trace("Failed to unmarshal plugin repo, reading response from grafana.net, %v", err.Error())
+ return
+ }
+
+ for _, plug := range Plugins {
+ for _, gplug := range gNetPlugins {
+ if gplug.Slug == plug.Id {
+ plug.GrafanaNetVersion = gplug.Version
+ plug.GrafanaNetHasUpdate = plug.Info.Version != plug.GrafanaNetVersion
+ }
+ }
+ }
+
+ resp2, err := client.Get("https://raw.githubusercontent.com/grafana/grafana/master/latest.json")
+ if err != nil {
+ log.Trace("Failed to get lates.json repo from github: %v", err.Error())
+ return
+ }
+
+ defer resp2.Body.Close()
+ body, err = ioutil.ReadAll(resp2.Body)
+ if err != nil {
+ log.Trace("Update check failed, reading response from github.net, %v", err.Error())
+ return
+ }
+
+ var githubLatest GithubLatest
+ err = json.Unmarshal(body, &githubLatest)
+ if err != nil {
+ log.Trace("Failed to unmarshal github latest, reading response from github: %v", err.Error())
+ return
+ }
+
+ if strings.Contains(setting.BuildVersion, "-") {
+ GrafanaLatestVersion = githubLatest.Testing
+ GrafanaHasUpdate = strings.HasPrefix(setting.BuildVersion, githubLatest.Testing)
+ } else {
+ GrafanaLatestVersion = githubLatest.Stable
+ GrafanaHasUpdate = githubLatest.Stable != setting.BuildVersion
+ }
+}
diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go
index b8e8d7f7fcf..2d1bad945eb 100644
--- a/pkg/setting/setting.go
+++ b/pkg/setting/setting.go
@@ -124,6 +124,7 @@ var (
appliedEnvOverrides []string
ReportingEnabled bool
+ CheckForUpdates bool
GoogleAnalyticsId string
GoogleTagManagerId string
@@ -475,6 +476,7 @@ func NewConfigContext(args *CommandLineArgs) error {
analytics := Cfg.Section("analytics")
ReportingEnabled = analytics.Key("reporting_enabled").MustBool(true)
+ CheckForUpdates = analytics.Key("check_for_updates").MustBool(true)
GoogleAnalyticsId = analytics.Key("google_analytics_ua_id").String()
GoogleTagManagerId = analytics.Key("google_tag_manager_id").String()
diff --git a/public/app/core/components/switch.ts b/public/app/core/components/switch.ts
index 10211b5b914..e2ba60d92e8 100644
--- a/public/app/core/components/switch.ts
+++ b/public/app/core/components/switch.ts
@@ -27,11 +27,8 @@ export class SwitchCtrl {
}
internalOnChange() {
- return new Promise(resolve => {
- this.$timeout(() => {
- this.onChange();
- resolve();
- });
+ return this.$timeout(() => {
+ return this.onChange();
});
}
diff --git a/public/app/core/controllers/login_ctrl.js b/public/app/core/controllers/login_ctrl.js
index 89757c3d141..4249d55a44f 100644
--- a/public/app/core/controllers/login_ctrl.js
+++ b/public/app/core/controllers/login_ctrl.js
@@ -39,7 +39,9 @@ function (angular, coreModule, config) {
$scope.buildInfo = {
version: config.buildInfo.version,
commit: config.buildInfo.commit,
- buildstamp: new Date(config.buildInfo.buildstamp * 1000)
+ buildstamp: new Date(config.buildInfo.buildstamp * 1000),
+ latestVersion: config.buildInfo.latestVersion,
+ hasUpdate: config.buildInfo.hasUpdate,
};
$scope.submit = function() {
diff --git a/public/app/features/panel/partials/panelTime.html b/public/app/features/panel/partials/panelTime.html
index 4047558317f..18b9cbbda8e 100644
--- a/public/app/features/panel/partials/panelTime.html
+++ b/public/app/features/panel/partials/panelTime.html
@@ -31,7 +31,7 @@