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 @@ + checked="ctrl.panel.hideTimeOverride" switch-class="max-width-6" on-change="ctrl.refresh()"> diff --git a/public/app/features/plugins/partials/ds_list.html b/public/app/features/plugins/partials/ds_list.html index c5ca9f514a4..c4625abfe6b 100644 --- a/public/app/features/plugins/partials/ds_list.html +++ b/public/app/features/plugins/partials/ds_list.html @@ -20,8 +20,9 @@
  • - - {{ds.type}} +
    + {{ds.type}} +
    diff --git a/public/app/features/plugins/partials/plugin_edit.html b/public/app/features/plugins/partials/plugin_edit.html index d5f69df2366..402e2f24c3f 100644 --- a/public/app/features/plugins/partials/plugin_edit.html +++ b/public/app/features/plugins/partials/plugin_edit.html @@ -55,6 +55,9 @@

    Version

    {{ctrl.model.info.version}} +
    + Update Available! +
    Includes
    diff --git a/public/app/features/plugins/partials/plugin_list.html b/public/app/features/plugins/partials/plugin_list.html index 344dbd65fcf..64e511805dc 100644 --- a/public/app/features/plugins/partials/plugin_list.html +++ b/public/app/features/plugins/partials/plugin_list.html @@ -33,8 +33,13 @@
  • - - {{plugin.type}} +
    + + {{plugin.type}} +
    +
    + Update available! +
    diff --git a/public/app/features/plugins/partials/update_instructions.html b/public/app/features/plugins/partials/update_instructions.html new file mode 100644 index 00000000000..80e3a413d1d --- /dev/null +++ b/public/app/features/plugins/partials/update_instructions.html @@ -0,0 +1,21 @@ + +
    diff --git a/public/app/features/plugins/plugin_edit_ctrl.ts b/public/app/features/plugins/plugin_edit_ctrl.ts index 7f3f6bff08f..a56f746dd61 100644 --- a/public/app/features/plugins/plugin_edit_ctrl.ts +++ b/public/app/features/plugins/plugin_edit_ctrl.ts @@ -19,6 +19,7 @@ export class PluginEditCtrl { /** @ngInject */ constructor(private $scope, + private $rootScope, private backendSrv, private $routeParams, private $sce, @@ -73,7 +74,7 @@ export class PluginEditCtrl { case 'datasource': return 'icon-gf icon-gf-datasources'; case 'panel': return 'icon-gf icon-gf-panel'; case 'app': return 'icon-gf icon-gf-apps'; - case 'page': return 'icon-gf icon-gf-share'; + case 'page': return 'icon-gf icon-gf-endpoint-tiny'; case 'dashboard': return 'icon-gf icon-gf-dashboard'; } } @@ -128,6 +129,16 @@ export class PluginEditCtrl { this.postUpdateHook = callback; } + updateAvailable() { + var modalScope = this.$scope.$new(true); + modalScope.plugin = this.model; + + this.$rootScope.appEvent('show-modal', { + src: 'public/app/features/plugins/partials/update_instructions.html', + scope: modalScope + }); + } + enable() { this.model.enabled = true; this.model.pinned = true; @@ -142,4 +153,3 @@ export class PluginEditCtrl { } angular.module('grafana.controllers').controller('PluginEditCtrl', PluginEditCtrl); - diff --git a/public/app/partials/login.html b/public/app/partials/login.html index b63e587c00e..ff8d22451a8 100644 --- a/public/app/partials/login.html +++ b/public/app/partials/login.html @@ -78,9 +78,9 @@ Grafana version: {{buildInfo.version}}, commit: {{buildInfo.commit}}, build date: {{buildInfo.buildstamp | date: 'yyyy-MM-dd HH:mm:ss' }}
  • + - - - diff --git a/public/app/plugins/panel/dashlist/editor.html b/public/app/plugins/panel/dashlist/editor.html index c0577578598..d2159093476 100644 --- a/public/app/plugins/panel/dashlist/editor.html +++ b/public/app/plugins/panel/dashlist/editor.html @@ -1,40 +1,32 @@ -
    -
    -
    - Mode -
    - -
    -
    -
    - - - -
    -
    +
    +
    +
    Options
    -
    -
    - Search options - Query + + + - + -
    +
    + Max items + +
    +
    -
    - Tags +
    +
    Search
    - - -
    -
    +
    + Query + +
    + +
    + Tags + + +
    +
    -
    -
    - Limit number to - -
    -
    diff --git a/public/app/plugins/panel/dashlist/module.html b/public/app/plugins/panel/dashlist/module.html index 79952d0032c..b5c59862e5d 100644 --- a/public/app/plugins/panel/dashlist/module.html +++ b/public/app/plugins/panel/dashlist/module.html @@ -1,12 +1,17 @@ -
    - +
    +
    +
    + {{group.header}} +
    + +
    diff --git a/public/app/plugins/panel/dashlist/module.ts b/public/app/plugins/panel/dashlist/module.ts index b619b7ea7d7..77029bd7ceb 100644 --- a/public/app/plugins/panel/dashlist/module.ts +++ b/public/app/plugins/panel/dashlist/module.ts @@ -7,16 +7,19 @@ import {impressions} from 'app/features/dashboard/impression_store'; // Set and populate defaults var panelDefaults = { - mode: 'starred', query: '', limit: 10, - tags: [] + tags: [], + recent: false, + search: false, + starred: true, + headings: true, }; class DashListCtrl extends PanelCtrl { static templateUrl = 'module.html'; - dashList: any[]; + groups: any[]; modes: any[]; /** @ngInject */ @@ -31,6 +34,31 @@ class DashListCtrl extends PanelCtrl { this.events.on('refresh', this.onRefresh.bind(this)); this.events.on('init-edit-mode', this.onInitEditMode.bind(this)); + + this.groups = [ + {list: [], show: false, header: "Starred dashboards",}, + {list: [], show: false, header: "Recently viewed dashboards"}, + {list: [], show: false, header: "Search"}, + ]; + + // update capability + if (this.panel.mode) { + if (this.panel.mode === 'starred') { + this.panel.starred = true; + this.panel.headings = false; + } + if (this.panel.mode === 'recently viewed') { + this.panel.recent = true; + this.panel.starred = false; + this.panel.headings = false; + } + if (this.panel.mode === 'search') { + this.panel.search = true; + this.panel.starred = false; + this.panel.headings = false; + } + delete this.panel.mode; + } } onInitEditMode() { @@ -40,34 +68,60 @@ class DashListCtrl extends PanelCtrl { } onRefresh() { - var params: any = {limit: this.panel.limit}; + var promises = []; - if (this.panel.mode === 'recently viewed') { - var dashIds = _.first(impressions.getDashboardOpened(), this.panel.limit); + promises.push(this.getRecentDashboards()); + promises.push(this.getStarred()); + promises.push(this.getSearch()); - return this.backendSrv.search({dashboardIds: dashIds, limit: this.panel.limit}).then(result => { - this.dashList = dashIds.map(orderId => { - return _.find(result, dashboard => { - return dashboard.id === orderId; - }); - }).filter(el => { - return el !== undefined; - }); + return Promise.all(promises) + .then(this.renderingCompleted.bind(this)); + } - this.renderingCompleted(); - }); + getSearch() { + this.groups[2].show = this.panel.search; + if (!this.panel.search) { + return Promise.resolve(); } - if (this.panel.mode === 'starred') { - params.starred = "true"; - } else { - params.query = this.panel.query; - params.tag = this.panel.tags; - } + var params = { + limit: this.panel.limit, + query: this.panel.query, + tag: this.panel.tags, + }; return this.backendSrv.search(params).then(result => { - this.dashList = result; - this.renderingCompleted(); + this.groups[2].list = result; + }); + } + + getStarred() { + this.groups[0].show = this.panel.starred; + if (!this.panel.starred) { + return Promise.resolve(); + } + + var params = {limit: this.panel.limit, starred: "true"}; + return this.backendSrv.search(params).then(result => { + this.groups[0].list = result; + }); + } + + getRecentDashboards() { + this.groups[1].show = this.panel.recent; + if (!this.panel.recent) { + return Promise.resolve(); + } + + var dashIds = _.first(impressions.getDashboardOpened(), this.panel.limit); + return this.backendSrv.search({dashboardIds: dashIds, limit: this.panel.limit}).then(result => { + this.groups[1].list = dashIds.map(orderId => { + return _.find(result, dashboard => { + return dashboard.id === orderId; + }); + }).filter(el => { + return el !== undefined; + }); }); } } diff --git a/public/app/plugins/panel/graph/graph_tooltip.js b/public/app/plugins/panel/graph/graph_tooltip.js index 7f2b83220a8..9ab6369a6b2 100644 --- a/public/app/plugins/panel/graph/graph_tooltip.js +++ b/public/app/plugins/panel/graph/graph_tooltip.js @@ -9,7 +9,7 @@ function ($) { var ctrl = scope.ctrl; var panel = ctrl.panel; - var $tooltip = $('
    '); + var $tooltip = $('
    '); this.findHoverIndexFromDataPoints = function(posX, series, last) { var ps = series.datapoints.pointsize; @@ -34,7 +34,7 @@ function ($) { }; this.showTooltip = function(absoluteTime, innerHtml, pos) { - var body = '
    '+ absoluteTime + '
    '; + var body = '
    '+ absoluteTime + '
    '; body += innerHtml + '
    '; $tooltip.html(body).place_tt(pos.pageX + 20, pos.pageY); }; diff --git a/public/app/plugins/panel/pluginlist/README.md b/public/app/plugins/panel/pluginlist/README.md new file mode 100644 index 00000000000..463769dad1f --- /dev/null +++ b/public/app/plugins/panel/pluginlist/README.md @@ -0,0 +1,2 @@ +# Plugin List Panel - Native Plugin + diff --git a/public/app/plugins/panel/pluginlist/editor.html b/public/app/plugins/panel/pluginlist/editor.html new file mode 100644 index 00000000000..c0577578598 --- /dev/null +++ b/public/app/plugins/panel/pluginlist/editor.html @@ -0,0 +1,40 @@ +
    +
    +
    + Mode +
    + +
    +
    +
    + + + +
    +
    + +
    +
    + Search options + Query + + + +
    + +
    + Tags + + + +
    +
    + +
    +
    + Limit number to + +
    +
    +
    diff --git a/public/app/plugins/panel/pluginlist/img/icn-dashlist-panel.svg b/public/app/plugins/panel/pluginlist/img/icn-dashlist-panel.svg new file mode 100644 index 00000000000..8bac231bedf --- /dev/null +++ b/public/app/plugins/panel/pluginlist/img/icn-dashlist-panel.svg @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/app/plugins/panel/pluginlist/module.html b/public/app/plugins/panel/pluginlist/module.html new file mode 100644 index 00000000000..339eb1fe649 --- /dev/null +++ b/public/app/plugins/panel/pluginlist/module.html @@ -0,0 +1,30 @@ + diff --git a/public/app/plugins/panel/pluginlist/module.ts b/public/app/plugins/panel/pluginlist/module.ts new file mode 100644 index 00000000000..9ad43b25e56 --- /dev/null +++ b/public/app/plugins/panel/pluginlist/module.ts @@ -0,0 +1,72 @@ +/// + +import _ from 'lodash'; +import config from 'app/core/config'; +import {PanelCtrl} from '../../../features/panel/panel_ctrl'; + +// Set and populate defaults +var panelDefaults = { +}; + +class PluginListCtrl extends PanelCtrl { + static templateUrl = 'module.html'; + + pluginList: any[]; + viewModel: any; + + /** @ngInject */ + constructor($scope, $injector, private backendSrv, private $location) { + super($scope, $injector); + _.defaults(this.panel, panelDefaults); + + this.events.on('init-edit-mode', this.onInitEditMode.bind(this)); + this.pluginList = []; + this.viewModel = [ + {header: "Installed Apps", list: [], type: 'app'}, + {header: "Installed Panels", list: [], type: 'panel'}, + {header: "Installed Datasources", list: [], type: 'datasource'}, + ]; + + this.update(); + } + + onInitEditMode() { + this.editorTabIndex = 1; + this.addEditorTab('Options', 'public/app/plugins/panel/pluginlist/editor.html'); + } + + gotoPlugin(plugin) { + this.$location.path(`plugins/${plugin.id}/edit`); + } + + updateAvailable(plugin, $event) { + $event.stopPropagation(); + + var modalScope = this.$scope.$new(true); + modalScope.plugin = plugin; + + this.publishAppEvent('show-modal', { + src: 'public/app/features/plugins/partials/update_instructions.html', + scope: modalScope + }); + } + + update() { + this.backendSrv.get('api/plugins', {embedded: 0, core: 0}).then(plugins => { + this.pluginList = plugins; + this.viewModel[0].list = _.filter(plugins, {type: 'app'}); + this.viewModel[1].list = _.filter(plugins, {type: 'panel'}); + this.viewModel[2].list = _.filter(plugins, {type: 'datasource'}); + + for (let plugin of this.pluginList) { + if (plugin.hasUpdate) { + plugin.state = 'has-update'; + } else if (!plugin.enabled) { + plugin.state = 'not-enabled'; + } + } + }); + } +} + +export {PluginListCtrl, PluginListCtrl as PanelCtrl} diff --git a/public/app/plugins/panel/pluginlist/plugin.json b/public/app/plugins/panel/pluginlist/plugin.json new file mode 100644 index 00000000000..be6ae9a5985 --- /dev/null +++ b/public/app/plugins/panel/pluginlist/plugin.json @@ -0,0 +1,16 @@ +{ + "type": "panel", + "name": "Plugin list", + "id": "pluginlist", + + "info": { + "author": { + "name": "Grafana Project", + "url": "http://grafana.org" +}, + "logos": { + "small": "img/icn-dashlist-panel.svg", + "large": "img/icn-dashlist-panel.svg" + } + } +} diff --git a/public/dashboards/home.json b/public/dashboards/home.json index 1d57a37edfe..c3ed1017bad 100644 --- a/public/dashboards/home.json +++ b/public/dashboards/home.json @@ -9,55 +9,61 @@ "hideControls": true, "sharedCrosshair": false, "rows": [ - { + { "collapse": false, "editable": true, - "height": "90px", + "height": "25px", "panels": [ { "content": "
    \n Home Dashboard\n
    ", "editable": true, "id": 1, + "links": [], "mode": "html", "span": 12, "style": {}, "title": "", "transparent": true, - "type": "text", - "links": [] + "type": "text" } ], "title": "New row" - }, - { + }, + { "collapse": false, "editable": true, "height": "510px", "panels": [ { - "id": 2, - "limit": 10, - "mode": "starred", + "id": 3, + "limit": 4, + "links": [], "query": "", - "span": 6, + "span": 7, "tags": [], - "title": "Starred dashboards", - "type": "dashlist" + "title": "", + "transparent": false, + "type": "dashlist", + "recent": true, + "search": false, + "starred": true, + "headings": true }, { - "id": 3, - "limit": 10, - "mode": "recently viewed", - "query": "", - "span": 6, - "tags": [], - "title": "Recently viewed dashboards", - "type": "dashlist" + "editable": true, + "error": false, + "id": 4, + "isNew": true, + "links": [], + "span": 5, + "title": "", + "transparent": false, + "type": "pluginlist" } ], "title": "Row" - } - ], + } + ], "time": { "from": "now-6h", "to": "now" @@ -95,7 +101,7 @@ "annotations": { "list": [] }, - "schemaVersion": 9, - "version": 5, + "schemaVersion": 12, + "version": 2, "links": [] } diff --git a/public/sass/_grafana.scss b/public/sass/_grafana.scss index 900d0c00f80..460adebb9da 100644 --- a/public/sass/_grafana.scss +++ b/public/sass/_grafana.scss @@ -42,6 +42,7 @@ @import "components/panel_graph"; @import "components/submenu"; @import "components/panel_dashlist"; +@import "components/panel_pluginlist"; @import "components/panel_singlestat"; @import "components/panel_table"; @import "components/panel_text"; diff --git a/public/sass/_variables.dark.scss b/public/sass/_variables.dark.scss index 3a390e2f883..f0b55dbf381 100644 --- a/public/sass/_variables.dark.scss +++ b/public/sass/_variables.dark.scss @@ -70,6 +70,7 @@ $page-gradient: linear-gradient(60deg, transparent 70%, darken($page-bg, 4%) 98% $link-color: darken($white,11%); $link-color-disabled: darken($link-color,30%); $link-hover-color: $white; +$external-link-color: $blue; // Typography // ------------------------- @@ -241,14 +242,6 @@ $successBackground: $btn-success-bg; $infoText: $blue-dark; $infoBackground: $blue-dark; -// Tooltips and popovers -// ------------------------- -$tooltipColor: $text-color; -$tooltipBackground: $dark-4; -$tooltipArrowWidth: 5px; -$tooltipArrowColor: $tooltipBackground; -$tooltipLinkColor: $link-color; - // popover $popover-bg: $dark-4; $popover-color: $text-color; @@ -256,6 +249,16 @@ $popover-color: $text-color; $popover-help-bg: $btn-secondary-bg; $popover-help-color: $text-color; + +// Tooltips and popovers +// ------------------------- +$tooltipColor: $text-color; +$tooltipBackground: $dark-5; +$tooltipArrowWidth: 5px; +$tooltipArrowColor: $tooltipBackground; +$tooltipLinkColor: $link-color; +$graph-tooltip-bg: $dark-5; + // images $checkboxImageUrl: '../img/checkbox.png'; @@ -263,5 +266,3 @@ $checkboxImageUrl: '../img/checkbox.png'; $card-background: linear-gradient(135deg, #2f2f2f, #262626); $card-background-hover: linear-gradient(135deg, #343434, #262626); $card-shadow: -1px -1px 0 0 hsla(0, 0%, 100%, .1), 1px 1px 0 0 rgba(0, 0, 0, .3); - - diff --git a/public/sass/_variables.light.scss b/public/sass/_variables.light.scss index 91b42c5c6be..bea22947da5 100644 --- a/public/sass/_variables.light.scss +++ b/public/sass/_variables.light.scss @@ -76,6 +76,7 @@ $page-gradient: linear-gradient(60deg, transparent 70%, darken($page-bg, 4%) 98% $link-color: $gray-1; $link-color-disabled: lighten($link-color, 30%); $link-hover-color: darken($link-color, 20%); +$external-link-color: $blue; // Typography // ------------------------- @@ -267,6 +268,12 @@ $infoText: $blue; $infoBackground: $blue-dark; $infoBorder: transparent; +// popover +$popover-bg: $gray-5; +$popover-color: $text-color; + +$popover-help-bg: $blue-dark; +$popover-help-color: $gray-6; // Tooltips and popovers // ------------------------- @@ -274,14 +281,8 @@ $tooltipColor: $text-color; $tooltipBackground: $gray-5; $tooltipArrowWidth: 5px; $tooltipArrowColor: $tooltipBackground; -$tooltipLinkColor: $text-color; - -// popover -$popover-bg: $gray-5; -$popover-color: $text-color; - -$popover-help-bg: $blue-dark; -$popover-help-color: $gray-6; +$tooltipLinkColor: $link-color; +$graph-tooltip-bg: $gray-5; // images $checkboxImageUrl: '../img/checkbox_white.png'; @@ -290,4 +291,3 @@ $checkboxImageUrl: '../img/checkbox_white.png'; $card-background: linear-gradient(135deg, $gray-5, $gray-6); $card-background-hover: linear-gradient(135deg, $gray-6, $gray-7); $card-shadow: -1px -1px 0 0 hsla(0, 0%, 100%, .1), 1px 1px 0 0 rgba(0, 0, 0, .1); - diff --git a/public/sass/base/_code.scss b/public/sass/base/_code.scss index 2a4057a8547..464eed966ce 100644 --- a/public/sass/base/_code.scss +++ b/public/sass/base/_code.scss @@ -23,6 +23,16 @@ code { white-space: nowrap; } +code.code--small { + font-size: $font-size-xs; + padding: 5px; + margin: 0 2px; +} + +p.code--line { + line-height: 1.8; +} + // Blocks of code pre { display: block; @@ -49,4 +59,3 @@ pre { border: 0; } } - diff --git a/public/sass/base/_type.scss b/public/sass/base/_type.scss index e819f71b540..904dfa13b4e 100644 --- a/public/sass/base/_type.scss +++ b/public/sass/base/_type.scss @@ -114,7 +114,7 @@ hr { small, .small { - font-size: 85%; + font-size: $font-size-sm; font-weight: normal; } diff --git a/public/sass/components/_cards.scss b/public/sass/components/_cards.scss index 08baf146c93..c64fbbe8aa7 100644 --- a/public/sass/components/_cards.scss +++ b/public/sass/components/_cards.scss @@ -76,13 +76,20 @@ } .card-item-header { + margin-bottom: $spacer; +} + +.card-item-type { color: $text-color-weak; text-transform: uppercase; - margin-bottom: $spacer; font-size: $font-size-sm; font-weight: bold; } +.card-item-notice { + font-size: $font-size-sm; +} + .card-item-name { color: $headings-color; overflow: hidden; @@ -107,6 +114,16 @@ .card-list-layout-grid { + .card-item-type { + display: inline-block; + } + + .card-item-notice { + font-size: $font-size-sm; + display: inline-block; + margin-left: $spacer; + } + .card-item-header-action { float: right; } @@ -116,6 +133,10 @@ padding: 0 1.5rem 1.5rem 0rem; } + .card-item-wrapper--clickable { + cursor: pointer; + } + .card-item-figure { margin: 0 $spacer $spacer 0; height: 6rem; @@ -157,6 +178,10 @@ width: 100%; } + .card-item-wrapper--clickable { + cursor: pointer; + } + .card-item { border-bottom: .2rem solid $page-bg; border-radius: 0; @@ -165,6 +190,7 @@ .card-item-header { float: right; + text-align: right; } .card-item-figure { @@ -186,4 +212,3 @@ margin-right: 0; } } - diff --git a/public/sass/components/_panel_dashlist.scss b/public/sass/components/_panel_dashlist.scss index efde20fa113..dfb11dfb848 100644 --- a/public/sass/components/_panel_dashlist.scss +++ b/public/sass/components/_panel_dashlist.scss @@ -1,3 +1,11 @@ +.dashlist-section-header { + margin-bottom: $spacer; + color: $text-color-weak; +} + +.dashlist-section { + margin-bottom: $spacer; +} .dashlist-link { display: block; diff --git a/public/sass/components/_panel_graph.scss b/public/sass/components/_panel_graph.scss index c8d68bbdd46..4cd4a0d4abc 100644 --- a/public/sass/components/_panel_graph.scss +++ b/public/sass/components/_panel_graph.scss @@ -234,6 +234,8 @@ .graph-tooltip { white-space: nowrap; + font-size: $font-size-sm; + background-color: $graph-tooltip-bg; .graph-tooltip-time { text-align: center; diff --git a/public/sass/components/_panel_pluginlist.scss b/public/sass/components/_panel_pluginlist.scss new file mode 100644 index 00000000000..605e1afdb6a --- /dev/null +++ b/public/sass/components/_panel_pluginlist.scss @@ -0,0 +1,75 @@ +.pluginlist-section-header { + margin-bottom: $spacer; + color: $text-color-weak; +} + +.pluginlist-section { + margin-bottom: $spacer; +} + +.pluginlist-link { + display: block; + margin: 5px; + padding: 7px; + background-color: $tight-form-bg; + + &:hover { + background-color: $tight-form-func-bg; + } +} + +.pluginlist-icon { + vertical-align: sub; + font-size: $font-size-h1; + margin-right: $spacer / 2; +} + +.pluginlist-image { + width: 20px; +} + +.pluginlist-title { + margin-right: $spacer / 3; +} + +.pluginlist-version { + font-size: $font-size-sm; + color: $text-color-weak; +} + +.pluginlist-message { + float: right; + font-size: $font-size-sm; +} + +.pluginlist-message--update { + &:hover { + border-bottom: 1px solid $text-color; + } +} + +.pluginlist-message--enable{ + color: $external-link-color; + &:hover { + border-bottom: 1px solid $external-link-color; + } +} + +.pluginlist-message--no-update { + color: $text-color-weak; +} + +.pluginlist-emphasis { + font-weight: 600; +} + +.pluginlist-none-installed { + color: $text-color-weak; + font-size: $font-size-sm; +} + +.pluginlist-inline-logo { + vertical-align: sub; + margin-right: $spacer / 3; + width: 16px; +} diff --git a/public/sass/components/_tooltip.scss b/public/sass/components/_tooltip.scss index 6499d73c0d8..2f1408dbec1 100644 --- a/public/sass/components/_tooltip.scss +++ b/public/sass/components/_tooltip.scss @@ -2,7 +2,6 @@ // Tooltips // -------------------------------------------------- - // Base class .tooltip { position: absolute; @@ -37,6 +36,7 @@ border-color: transparent; border-style: solid; } + .tooltip { &.top .tooltip-arrow { bottom: 0; diff --git a/public/sass/pages/_dashboard.scss b/public/sass/pages/_dashboard.scss index 2b04f0e9b29..9249af41077 100644 --- a/public/sass/pages/_dashboard.scss +++ b/public/sass/pages/_dashboard.scss @@ -278,11 +278,11 @@ div.flot-text { .dashboard-header { font-family: $headings-font-family; - font-size: $font-size-h2; + font-size: $font-size-h3; text-align: center; span { display: inline-block; @include brand-bottom-border(); - padding: 1.2rem .5rem .4rem .5rem; + padding: 0.5rem .5rem .2rem .5rem; } } diff --git a/tsconfig.json b/tsconfig.json index 43a3888f711..0afaa053449 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,33 +6,13 @@ "noImplicitAny": false, "target": "es5", "rootDir": "public/", + "sourceRoot": "public/", "module": "system", "noEmitOnError": true, - "emitDecoratorMetadata": true + "emitDecoratorMetadata": true, + "experimentalDecorators": true }, "files": [ - "public/app/app.ts", - "public/app/core/controllers/grafana_ctrl.ts", - "public/app/core/controllers/signup_ctrl.ts", - "public/app/core/core.ts", - "public/app/core/core_module.ts", - "public/app/core/directives/array_join.ts", - "public/app/core/directives/give_focus.ts", - "public/app/core/filters/filters.ts", - "public/app/core/routes/bundle_loader.ts", - "public/app/core/table_model.ts", - "public/app/core/time_series.ts", - "public/app/core/utils/datemath.ts", - "public/app/core/utils/flatten.ts", - "public/app/core/utils/rangeutil.ts", - "public/app/features/dashboard/timepicker/timepicker.ts", - "public/app/features/panel/panel_meta.ts", - "public/app/plugins/datasource/influxdb/influx_query.ts", - "public/app/plugins/datasource/influxdb/query_part.ts", - "public/app/plugins/panels/table/controller.ts", - "public/app/plugins/panels/table/editor.ts", - "public/app/plugins/panels/table/module.ts", - "public/app/plugins/panels/table/renderer.ts", - "public/app/plugins/panels/table/transformers.ts" + "public/app/**/*.ts" ] -} \ No newline at end of file +}