Sfoglia il codice sorgente

Merge branch 'master' into utkarshcmu-move

bergquist 9 anni fa
parent
commit
e5c9a24c33
59 ha cambiato i file con 911 aggiunte e 231 eliminazioni
  1. 7 0
      conf/defaults.ini
  2. 7 0
      conf/sample.ini
  3. 3 3
      docs/sources/installation/debian.md
  4. 4 4
      docs/sources/installation/rpm.md
  5. 1 1
      docs/sources/installation/windows.md
  6. 4 4
      docs/sources/plugins/installation.md
  7. 2 2
      latest.json
  8. 1 1
      package.json
  9. 7 7
      packaging/publish/publish.sh
  10. 3 0
      pkg/api/api.go
  11. 11 6
      pkg/api/dtos/plugins.go
  12. 5 3
      pkg/api/frontendsettings.go
  13. 46 0
      pkg/api/gnetproxy.go
  14. 14 4
      pkg/api/plugins.go
  15. 10 7
      pkg/cmd/grafana-cli/commands/commands.go
  16. 1 1
      pkg/cmd/grafana-cli/commands/upgrade_all_command.go
  17. 1 1
      pkg/cmd/grafana-server/main.go
  18. 8 9
      pkg/models/stats.go
  19. 4 3
      pkg/plugins/frontend_plugin.go
  20. 4 0
      pkg/plugins/models.go
  21. 4 0
      pkg/plugins/plugins.go
  22. 119 0
      pkg/plugins/update_checker.go
  23. 1 6
      pkg/services/sqlstore/stats.go
  24. 2 0
      pkg/setting/setting.go
  25. 2 5
      public/app/core/components/switch.ts
  26. 3 1
      public/app/core/controllers/login_ctrl.js
  27. 0 4
      public/app/features/admin/partials/stats.html
  28. 1 0
      public/app/features/annotations/editor_ctrl.js
  29. 1 1
      public/app/features/panel/partials/panelTime.html
  30. 3 2
      public/app/features/plugins/partials/ds_list.html
  31. 3 0
      public/app/features/plugins/partials/plugin_edit.html
  32. 7 2
      public/app/features/plugins/partials/plugin_list.html
  33. 21 0
      public/app/features/plugins/partials/update_instructions.html
  34. 13 2
      public/app/features/plugins/plugin_edit_ctrl.ts
  35. 3 3
      public/app/partials/login.html
  36. 2 1
      public/app/plugins/datasource/cloudwatch/partials/annotations.editor.html
  37. 25 33
      public/app/plugins/panel/dashlist/editor.html
  38. 16 11
      public/app/plugins/panel/dashlist/module.html
  39. 79 25
      public/app/plugins/panel/dashlist/module.ts
  40. 2 2
      public/app/plugins/panel/graph/graph_tooltip.js
  41. 2 0
      public/app/plugins/panel/pluginlist/README.md
  42. 40 0
      public/app/plugins/panel/pluginlist/editor.html
  43. 119 0
      public/app/plugins/panel/pluginlist/img/icn-dashlist-panel.svg
  44. 30 0
      public/app/plugins/panel/pluginlist/module.html
  45. 72 0
      public/app/plugins/panel/pluginlist/module.ts
  46. 16 0
      public/app/plugins/panel/pluginlist/plugin.json
  47. 30 24
      public/dashboards/home.json
  48. 1 0
      public/sass/_grafana.scss
  49. 11 10
      public/sass/_variables.dark.scss
  50. 9 9
      public/sass/_variables.light.scss
  51. 10 1
      public/sass/base/_code.scss
  52. 1 1
      public/sass/base/_type.scss
  53. 27 2
      public/sass/components/_cards.scss
  54. 6 1
      public/sass/components/_panel_dashlist.scss
  55. 4 1
      public/sass/components/_panel_graph.scss
  56. 75 0
      public/sass/components/_panel_pluginlist.scss
  57. 1 1
      public/sass/components/_tooltip.scss
  58. 2 2
      public/sass/pages/_dashboard.scss
  59. 5 25
      tsconfig.json

+ 7 - 0
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 =
 

+ 7 - 0
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 =
 

+ 3 - 3
docs/sources/installation/debian.md

@@ -11,7 +11,7 @@ page_keywords: grafana, installation, debian, ubuntu, guide
 Description | Download
 ------------ | -------------
 Stable .deb for Debian-based Linux | [grafana_2.6.0_amd64.deb](https://grafanarel.s3.amazonaws.com/builds/grafana_2.6.0_amd64.deb)
-Beta .deb for Debian-based Linux |   [grafana_3.0.0-beta21459801392_amd64.deb](https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.0-beta21459801392_amd64.deb)
+Beta .deb for Debian-based Linux |   [grafana_3.0.0-beta31460467884_amd64.deb](https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.0-beta31460467884_amd64.deb)
 
 ## Install Stable
 
@@ -21,9 +21,9 @@ Beta .deb for Debian-based Linux |   [grafana_3.0.0-beta21459801392_amd64.deb](h
 
 ## Install 3.0 Beta
 
-    $ wget https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.0-beta21459801392_amd64.deb
+    $ wget https://grafanarel.s3.amazonaws.com/builds/grafana_3.0.0-beta31460467884_amd64.deb
     $ sudo apt-get install -y adduser libfontconfig
-    $ sudo dpkg -i grafana_3.0.0-beta21459801392_amd64.deb
+    $ sudo dpkg -i grafana_3.0.0-beta31460467884_amd64.deb
 
 ## APT Repository
 

+ 4 - 4
docs/sources/installation/rpm.md

@@ -11,7 +11,7 @@ page_keywords: grafana, installation, centos, fedora, opensuse, redhat, guide
 Description | Download
 ------------ | -------------
 Stable .RPM for CentOS / Fedora / OpenSuse / Redhat Linux | [grafana-2.6.0-1.x86_64.rpm](https://grafanarel.s3.amazonaws.com/builds/grafana-2.6.0-1.x86_64.rpm)
-Beta .RPM for CentOS / Fedor / OpenSuse / Redhat Linux | [grafana-3.0.0-beta21459801392.x86_64.rpm](https://grafanarel.s3.amazonaws.com/builds/grafana-3.0.0-beta21459801392.x86_64.rpm)
+Beta .RPM for CentOS / Fedor / OpenSuse / Redhat Linux | [grafana-3.0.0-beta31460467884.x86_64.rpm](https://grafanarel.s3.amazonaws.com/builds/grafana-3.0.0-beta31460467884§.x86_64.rpm)
 
 ## Install Stable Release from package file
 
@@ -34,18 +34,18 @@ Or install manually using `rpm`.
 
 You can install Grafana using Yum directly.
 
-    $ sudo yum install https://grafanarel.s3.amazonaws.com/builds/grafana-3.0.0-beta21459801392.x86_64.rpm
+    $ sudo yum install https://grafanarel.s3.amazonaws.com/builds/grafana-3.0.0-beta31460467884.x86_64.rpm
 
 Or install manually using `rpm`.
 
 #### On CentOS / Fedora / Redhat:
 
     $ sudo yum install initscripts fontconfig
-    $ sudo rpm -Uvh grafana-3.0.0-beta21459801392.x86_64.rpm
+    $ sudo rpm -Uvh grafana-3.0.0-beta31460467884.x86_64.rpm
 
 #### On OpenSuse:
 
-    $ sudo rpm -i --nodeps grafana-3.0.0-beta21459801392.x86_64.rpm
+    $ sudo rpm -i --nodeps grafana-3.0.0-beta31460467884.x86_64.rpm
 
 
 ## Install via YUM Repository

+ 1 - 1
docs/sources/installation/windows.md

@@ -10,7 +10,7 @@ page_keywords: grafana, installation, windows guide
 
 Description | Download
 ------------ | -------------
-Stable Zip package for Windows | [grafana.2.5.0.windows-x64.zip](https://grafanarel.s3.amazonaws.com/winbuilds/dist/grafana-2.5.0.windows-x64.zip)
+Stable Zip package for Windows | [grafana.2.6.0.windows-x64.zip](https://grafanarel.s3.amazonaws.com/winbuilds/dist/grafana-2.5.0.windows-x64.zip)
 
 ## Configure
 

+ 4 - 4
docs/sources/plugins/installation.md

@@ -28,14 +28,14 @@ List installed plugins
 grafana-cli plugins ls
 ```
 
-Upgrade all installed plugins
+Update all installed plugins
 ```
-grafana-cli plugins upgrade-all
+grafana-cli plugins update-all
 ```
 
-Upgrade one plugin
+Update one plugin
 ```
-grafana-cli plugins upgrade <plugin-id>
+grafana-cli plugins update <plugin-id>
 ```
 
 Remove one plugin

+ 2 - 2
latest.json

@@ -1,4 +1,4 @@
 {
-	"stable": "2.6.0",
-	"testing": "3.0.0"
+  "stable": "2.6.0",
+	"testing": "3.0.0-beta2"
 }

+ 1 - 1
package.json

@@ -4,7 +4,7 @@
     "company": "Coding Instinct AB"
   },
   "name": "grafana",
-  "version": "3.0.0-beta3",
+  "version": "3.0.0-beta4",
   "repository": {
     "type": "git",
     "url": "http://github.com/grafana/grafana.git"

+ 7 - 7
packaging/publish/publish.sh

@@ -1,22 +1,22 @@
 #! /usr/bin/env bash
 
-deb_ver=3.0.0-beta21459801392
-rpm_ver=3.0.0-beta21459801392
+deb_ver=3.0.0-beta31460467884
+rpm_ver=3.0.0-beta31460467884
 
 #rpm_ver=3.0.0-1
 
-#wget https://grafanarel.s3.amazonaws.com/builds/grafana_${deb_ver}_amd64.deb
+# wget https://grafanarel.s3.amazonaws.com/builds/grafana_${deb_ver}_amd64.deb
 
 #package_cloud push grafana/stable/debian/jessie grafana_${deb_ver}_amd64.deb
 #package_cloud push grafana/stable/debian/wheezy grafana_${deb_ver}_amd64.deb
 
-#package_cloud push grafana/testing/debian/jessie grafana_${deb_ver}_amd64.deb
-#package_cloud push grafana/testing/debian/wheezy grafana_${deb_ver}_amd64.deb
+# package_cloud push grafana/testing/debian/jessie grafana_${deb_ver}_amd64.deb
+package_cloud push grafana/testing/debian/wheezy grafana_${deb_ver}_amd64.deb
 
-#wget https://grafanarel.s3.amazonaws.com/builds/grafana-${rpm_ver}.x86_64.rpm
+wget https://grafanarel.s3.amazonaws.com/builds/grafana-${rpm_ver}.x86_64.rpm
 
 package_cloud push grafana/testing/el/6 grafana-${rpm_ver}.x86_64.rpm
-#package_cloud push grafana/testing/el/7 grafana-${rpm_ver}.x86_64.rpm
+ackage_cloud push grafana/testing/el/7 grafana-${rpm_ver}.x86_64.rpm
 
 # package_cloud push grafana/stable/el/7 grafana-${version}-1.x86_64.rpm
 # package_cloud push grafana/stable/el/6 grafana-${version}-1.x86_64.rpm

+ 3 - 0
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)

+ 11 - 6
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

+ 5 - 3
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,
 		},
 	}
 

+ 46 - 0
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")
+}

+ 14 - 4
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}

+ 10 - 7
pkg/cmd/grafana-cli/commands/commands.go

@@ -1,9 +1,10 @@
 package commands
 
 import (
+	"os"
+
 	"github.com/codegangsta/cli"
 	"github.com/grafana/grafana/pkg/cmd/grafana-cli/log"
-	"os"
 )
 
 func runCommand(command func(commandLine CommandLine) error) func(context *cli.Context) {
@@ -32,13 +33,15 @@ var pluginCommands = []cli.Command{
 		Usage:  "list remote available plugins",
 		Action: runCommand(listremoteCommand),
 	}, {
-		Name:   "upgrade",
-		Usage:  "upgrade <plugin id>",
-		Action: runCommand(upgradeCommand),
+		Name:    "update",
+		Usage:   "update <plugin id>",
+		Aliases: []string{"upgrade"},
+		Action:  runCommand(upgradeCommand),
 	}, {
-		Name:   "upgrade-all",
-		Usage:  "upgrades all your installed plugins",
-		Action: runCommand(upgradeAllCommand),
+		Name:    "update-all",
+		Aliases: []string{"upgrade-all"},
+		Usage:   "update all your installed plugins",
+		Action:  runCommand(upgradeAllCommand),
 	}, {
 		Name:   "ls",
 		Usage:  "list all installed plugins",

+ 1 - 1
pkg/cmd/grafana-cli/commands/upgrade_all_command.go

@@ -51,7 +51,7 @@ func upgradeAllCommand(c CommandLine) error {
 	}
 
 	for _, p := range pluginsToUpgrade {
-		log.Infof("Upgrading %v \n", p.Id)
+		log.Infof("Updating %v \n", p.Id)
 
 		s.RemoveInstalledPlugin(pluginsDir, p.Id)
 		InstallPlugin(p.Id, "", c)

+ 1 - 1
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-beta4"
 var commit = "NA"
 var buildstamp string
 var build_date string

+ 8 - 9
pkg/models/stats.go

@@ -21,15 +21,14 @@ type GetDataSourceStatsQuery struct {
 }
 
 type AdminStats struct {
-	UserCount         int `json:"user_count"`
-	OrgCount          int `json:"org_count"`
-	DashboardCount    int `json:"dashboard_count"`
-	DbSnapshotCount   int `json:"db_snapshot_count"`
-	DbTagCount        int `json:"db_tag_count"`
-	DataSourceCount   int `json:"data_source_count"`
-	PlaylistCount     int `json:"playlist_count"`
-	StarredDbCount    int `json:"starred_db_count"`
-	GrafanaAdminCount int `json:"grafana_admin_count"`
+	UserCount       int `json:"user_count"`
+	OrgCount        int `json:"org_count"`
+	DashboardCount  int `json:"dashboard_count"`
+	DbSnapshotCount int `json:"db_snapshot_count"`
+	DbTagCount      int `json:"db_tag_count"`
+	DataSourceCount int `json:"data_source_count"`
+	PlaylistCount   int `json:"playlist_count"`
+	StarredDbCount  int `json:"starred_db_count"`
 }
 
 type GetAdminStatsQuery struct {

+ 4 - 3
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)
 }
 

+ 4 - 0
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:"-"`

+ 4 - 0
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
 }
 

+ 119 - 0
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
+	}
+}

+ 1 - 6
pkg/services/sqlstore/stats.go

@@ -85,12 +85,7 @@ func GetAdminStats(query *m.GetAdminStatsQuery) error {
       (
         SELECT COUNT(DISTINCT ` + dialect.Quote("dashboard_id") + ` )
         FROM ` + dialect.Quote("star") + `
-      ) AS starred_db_count,
-      (
-        SELECT COUNT(*)
-        FROM ` + dialect.Quote("user") + `
-        WHERE ` + dialect.Quote("is_admin") + ` = 1
-      ) AS grafana_admin_count
+      ) AS starred_db_count
       `
 
 	var stats m.AdminStats

+ 2 - 0
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()
 

+ 2 - 5
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();
     });
   }
 

+ 3 - 1
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() {

+ 0 - 4
public/app/features/admin/partials/stats.html

@@ -22,10 +22,6 @@
 				<td>Total users</td>
 				<td>{{ctrl.stats.user_count}}</td>
 			</tr>
-			<tr>
-				<td>Total grafana admins</td>
-				<td>{{ctrl.stats.grafana_admin_count}}</td>
-			</tr>
 			<tr>
 				<td>Total organizations</td>
 				<td>{{ctrl.stats.org_count}}</td>

+ 1 - 0
public/app/features/annotations/editor_ctrl.js

@@ -45,6 +45,7 @@ function (angular, _, $) {
 
     $scope.reset = function() {
       $scope.currentAnnotation = angular.copy(annotationDefaults);
+      $scope.currentAnnotation.datasource = $scope.datasources[0].name;
       $scope.currentIsNew = true;
       $scope.datasourceChanged();
     };

+ 1 - 1
public/app/features/panel/partials/panelTime.html

@@ -31,7 +31,7 @@
 		</div>
 		<gf-form-switch class="gf-form max-width-30"
 			label="Hide time override info" label-class="width-12"
-			checked="ctrl.panel.hideTimeOverride" switch-class="max-width-6" on-change="ctrl.render()">
+			checked="ctrl.panel.hideTimeOverride" switch-class="max-width-6" on-change="ctrl.refresh()">
 		</gf-form-switch>
 	</div>
 </div>

+ 3 - 2
public/app/features/plugins/partials/ds_list.html

@@ -20,8 +20,9 @@
 			<li class="card-item-wrapper" ng-repeat="ds in ctrl.datasources">
 				<a class="card-item" href="datasources/edit/{{ds.id}}/">
 					<div class="card-item-header">
-						<i class="icon-gf icon-gf-{{ds.type}}"></i>
-						{{ds.type}}
+						<div class="card-item-type">
+							{{ds.type}}
+						</div>
 					</div>
 					<div class="card-item-body">
 						<figure class="card-item-figure">

+ 3 - 0
public/app/features/plugins/partials/plugin_edit.html

@@ -55,6 +55,9 @@
       <section class="page-sidebar-section">
         <h4>Version</h4>
         <span>{{ctrl.model.info.version}}</span>
+				<div ng-show="ctrl.model.hasUpdate">
+          <a ng-click="ctrl.updateAvailable()" bs-tooltip="ctrl.model.latestVersion">Update Available!</a>
+				</div>
       </section>
       <section class="page-sidebar-section" ng-show="ctrl.model.type === 'app'">
         <h5>Includes</h4>

+ 7 - 2
public/app/features/plugins/partials/plugin_list.html

@@ -33,8 +33,13 @@
 			<li class="card-item-wrapper" ng-repeat="plugin in ctrl.plugins">
 				<a class="card-item" href="plugins/{{plugin.id}}/edit">
 					<div class="card-item-header">
-						<i class="icon-gf icon-gf-{{plugin.type}}"></i>
-						{{plugin.type}}
+						<div class="card-item-type">
+							<i class="icon-gf icon-gf-{{plugin.type}}"></i>
+							{{plugin.type}}
+						</div>
+					  <div class="card-item-notice" ng-show="plugin.hasUpdate">
+							<span bs-tooltip="plugin.latestVersion">Update available!</span>
+						</div>
 					</div>
 					<div class="card-item-body">
 						<figure class="card-item-figure">

+ 21 - 0
public/app/features/plugins/partials/update_instructions.html

@@ -0,0 +1,21 @@
+<div class="modal-body">
+	<div class="modal-header">
+		<h2 class="modal-header-title">
+			<i class="fa fa-cloud-download"></i>
+			<span class="p-l-1">Update Plugin</span>
+		</h2>
+
+		<a class="modal-header-close" ng-click="dismiss();">
+			<i class="fa fa-remove"></i>
+		</a>
+	</div>
+
+	<div class="modal-content">
+		<div class="gf-form-group">
+			<p>Type the following on the command line to update {{plugin.name}}.</p>
+			<pre><code>grafana-cli plugins update {{plugin.id}}</code></pre>
+			<span class="small">Check out {{plugin.name}} on <a href="http://grafana/net/plugins/{{plugin.id}}">Grafana.net</a> for README and changelog. If you do not have access to the command line, ask your Grafana administator.</span>
+		</div>
+		<p class="pluginlist-none-installed code--line"><img class="pluginlist-inline-logo" src="public/img/grafana_icon.svg"><strong>Pro tip</strong>: To update all plugins at once, type <code class="code--small">grafana-cli plugins update-all</code> on the command line.</div>
+	</div>
+</div>

+ 13 - 2
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,
@@ -47,6 +48,7 @@ export class PluginEditCtrl {
       });
 
       if (this.model.type === 'app') {
+        this.tabIndex = 1;
         this.tabs.push('Config');
 
         this.hasDashboards = _.findWhere(result.includes, {type: 'dashboard'});
@@ -73,7 +75,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 +130,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 +154,3 @@ export class PluginEditCtrl {
 }
 
 angular.module('grafana.controllers').controller('PluginEditCtrl', PluginEditCtrl);
-

+ 3 - 3
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' }}
 			</div>
+			<div class="version-footer text-center small" ng-show="buildInfo.hasUpdate">
+				<a class="external-link" target="_blank" href="http://grafana.org/download">New Grafana Version Available ({{buildInfo.latestVersion}})</a>
+			</div>
 		</div>
 	</div>
-
 </div>
-
-

+ 2 - 1
public/app/plugins/datasource/cloudwatch/partials/annotations.editor.html

@@ -1,5 +1,6 @@
 <cloudwatch-query-parameter target="ctrl.annotation" datasource="ctrl.datasource"></cloudwatch-query-parameter>
-<div class="editor-row">
+
+<div class="editor-row" style="padding: 2rem 0">
 	<div class="section">
 		<h5>Prefix matching</h5>
 		<div class="editor-option">

+ 25 - 33
public/app/plugins/panel/dashlist/editor.html

@@ -1,40 +1,32 @@
-<div class="gf-form-group">
-	<div class="gf-form-inline">
-		<div class="gf-form">
-			<span class="gf-form-label width-10">Mode</span>
-			<div class="gf-form-select-wrapper max-width-10">
-				<select class="gf-form-input" ng-model="ctrl.panel.mode" ng-options="f for f in ctrl.modes" ng-change="ctrl.refresh()"></select>
-			</div>
-		</div>
-		<div class="gf-form" ng-show="ctrl.panel.mode === 'recently viewed'">
-			<span class="gf-form-label">
-				<i class="grafana-tip fa fa-question-circle ng-scope" bs-tooltip="'WARNING: This list will be cleared when clearing browser cache'" data-original-title="" title=""></i>
-			</span>
-		</div>
-	</div>
+<div>
+  <div class="section gf-form-group">
+    <h5 class="section-heading">Options</h5>
 
-	<div class="gf-form-inline" ng-if="ctrl.panel.mode === 'search'">
-		<div class="gf-form">
-			<span class="gf-form-label width-10">Search options</span>
-			<span class="gf-form-label">Query</span>
+    <gf-form-switch class="gf-form" label="Starred" label-class="width-9" checked="ctrl.panel.starred" on-change="ctrl.refresh()"></gf-form-switch>
+    <gf-form-switch class="gf-form" label="Recently viewed" label-class="width-9" checked="ctrl.panel.recent" on-change="ctrl.refresh()"></gf-form-switch>
+    <gf-form-switch class="gf-form" label="Search" label-class="width-9" checked="ctrl.panel.search" on-change="ctrl.refresh()"></gf-form-switch>
 
-			<input type="text" class="gf-form-input" placeholder="title query"
-				ng-model="ctrl.panel.query" ng-change="ctrl.refresh()" ng-model-onblur>
+    <gf-form-switch class="gf-form" label="Show headings" label-class="width-9" checked="ctrl.panel.headings" on-change="ctrl.refresh()"></gf-form-switch>
 
-		</div>
+    <div class="gf-form">
+      <span class="gf-form-label width-9">Max items</span>
+      <input class="gf-form-input max-width-5" type="number" ng-model="ctrl.panel.limit" ng-model-onblur ng-change="ctrl.refresh()">
+    </div>
+  </div>
 
-		<div class="gf-form">
-			<span class="gf-form-label">Tags</span>
+  <div class="section gf-form-group">
+    <h5 class="section-heading">Search</h5>
 
-			<bootstrap-tagsinput ng-model="ctrl.panel.tags" tagclass="label label-tag" placeholder="add tags" on-tags-updated="ctrl.refresh()">
-			</bootstrap-tagsinput>
-		</div>
-	</div>
+    <div class="gf-form">
+      <span class="gf-form-label width-6">Query</span>
+      <input type="text" class="gf-form-input" placeholder="title query" ng-model="ctrl.panel.query" ng-change="ctrl.refresh()" ng-model-onblur>
+    </div>
+
+    <div class="gf-form">
+      <span class="gf-form-label width-6">Tags</span>
+      <bootstrap-tagsinput ng-model="ctrl.panel.tags" tagclass="label label-tag" placeholder="add tags" on-tags-updated="ctrl.refresh()">
+      </bootstrap-tagsinput>
+    </div>
+  </div>
 
-	<div class="gf-form-inline">
-		<div class="gf-form">
-			<span class="gf-form-label width-10">Limit number to</span>
-			<input class="gf-form-input" type="number" ng-model="ctrl.panel.limit" ng-model-onblur ng-change="ctrl.refresh()">
-		</div>
-	</div>
 </div>

+ 16 - 11
public/app/plugins/panel/dashlist/module.html

@@ -1,12 +1,17 @@
-<div class="dashlist">
-	<div class="dashlist-item" ng-repeat="dash in ctrl.dashList">
-		<a class="dashlist-link dashlist-link-{{dash.type}}" href="dashboard/{{dash.uri}}">
-			<span class="dashlist-title">
-				{{dash.title}}
-			</span>
-			<span class="dashlist-star">
-				<i class="fa" ng-class="{'fa-star': dash.isStarred, 'fa-star-o': dash.isStarred === false}"></i>
-			</span>
-		</a>
-	</div>
+<div class="dashlist" ng-repeat="group in ctrl.groups">
+  <div class="dashlist-section" ng-if="group.show">
+    <h6 class="dashlist-section-header" ng-show="ctrl.panel.headings">
+      {{group.header}}
+    </h6>
+    <div class="dashlist-item" ng-repeat="dash in group.list">
+      <a class="dashlist-link dashlist-link-{{dash.type}}" href="dashboard/{{dash.uri}}">
+        <span class="dashlist-title">
+          {{dash.title}}
+        </span>
+        <span class="dashlist-star">
+          <i class="fa" ng-class="{'fa-star': dash.isStarred, 'fa-star-o': dash.isStarred === false}"></i>
+        </span>
+      </a>
+    </div>
+  </div>
 </div>

+ 79 - 25
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};
-
-    if (this.panel.mode === 'recently viewed') {
-      var dashIds = _.first(impressions.getDashboardOpened(), this.panel.limit);
-
-      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;
-        });
+    var promises = [];
 
-        this.renderingCompleted();
-      });
+    promises.push(this.getRecentDashboards());
+    promises.push(this.getStarred());
+    promises.push(this.getSearch());
+
+    return Promise.all(promises)
+      .then(this.renderingCompleted.bind(this));
+  }
+
+  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.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.dashList = result;
-      this.renderingCompleted();
+      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;
+      });
     });
   }
 }

+ 2 - 2
public/app/plugins/panel/graph/graph_tooltip.js

@@ -9,7 +9,7 @@ function ($) {
     var ctrl = scope.ctrl;
     var panel = ctrl.panel;
 
-    var $tooltip = $('<div id="tooltip">');
+    var $tooltip = $('<div id="tooltip" class="graph-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 = '<div class="graph-tooltip small"><div class="graph-tooltip-time">'+ absoluteTime + '</div> ';
+      var body = '<div class="graph-tooltip-time">'+ absoluteTime + '</div>';
       body += innerHtml + '</div>';
       $tooltip.html(body).place_tt(pos.pageX + 20, pos.pageY);
     };

+ 2 - 0
public/app/plugins/panel/pluginlist/README.md

@@ -0,0 +1,2 @@
+# Plugin List Panel -  Native Plugin
+

+ 40 - 0
public/app/plugins/panel/pluginlist/editor.html

@@ -0,0 +1,40 @@
+<div class="gf-form-group">
+	<div class="gf-form-inline">
+		<div class="gf-form">
+			<span class="gf-form-label width-10">Mode</span>
+			<div class="gf-form-select-wrapper max-width-10">
+				<select class="gf-form-input" ng-model="ctrl.panel.mode" ng-options="f for f in ctrl.modes" ng-change="ctrl.refresh()"></select>
+			</div>
+		</div>
+		<div class="gf-form" ng-show="ctrl.panel.mode === 'recently viewed'">
+			<span class="gf-form-label">
+				<i class="grafana-tip fa fa-question-circle ng-scope" bs-tooltip="'WARNING: This list will be cleared when clearing browser cache'" data-original-title="" title=""></i>
+			</span>
+		</div>
+	</div>
+
+	<div class="gf-form-inline" ng-if="ctrl.panel.mode === 'search'">
+		<div class="gf-form">
+			<span class="gf-form-label width-10">Search options</span>
+			<span class="gf-form-label">Query</span>
+
+			<input type="text" class="gf-form-input" placeholder="title query"
+				ng-model="ctrl.panel.query" ng-change="ctrl.refresh()" ng-model-onblur>
+
+		</div>
+
+		<div class="gf-form">
+			<span class="gf-form-label">Tags</span>
+
+			<bootstrap-tagsinput ng-model="ctrl.panel.tags" tagclass="label label-tag" placeholder="add tags" on-tags-updated="ctrl.refresh()">
+			</bootstrap-tagsinput>
+		</div>
+	</div>
+
+	<div class="gf-form-inline">
+		<div class="gf-form">
+			<span class="gf-form-label width-10">Limit number to</span>
+			<input class="gf-form-input" type="number" ng-model="ctrl.panel.limit" ng-model-onblur ng-change="ctrl.refresh()">
+		</div>
+	</div>
+</div>

+ 119 - 0
public/app/plugins/panel/pluginlist/img/icn-dashlist-panel.svg

@@ -0,0 +1,119 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 width="100px" height="100px" viewBox="0 0 100 100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
+<g>
+	<g>
+		<path style="fill:#666666;" d="M8.842,11.219h0.1c1.228,0,2.227-0.999,2.227-2.227v-0.1L8.842,11.219z"/>
+		<path style="fill:#666666;" d="M0.008,2.113l2.054-2.054C0.966,0.139,0.089,1.016,0.008,2.113z"/>
+		<polygon style="fill:#666666;" points="0,2.998 0,5.533 5.484,0.05 2.948,0.05 		"/>
+		<polygon style="fill:#666666;" points="6.361,0.05 0,6.411 0,8.946 8.896,0.05 		"/>
+		<path style="fill:#666666;" d="M11.169,2.277c0-0.068-0.004-0.134-0.01-0.2l-9.132,9.132c0.066,0.006,0.133,0.01,0.2,0.01h2.325
+			l6.617-6.617V2.277z"/>
+		<path style="fill:#666666;" d="M9.654,0.169L0.119,9.704c0.201,0.592,0.643,1.073,1.211,1.324l9.649-9.649
+			C10.728,0.812,10.247,0.37,9.654,0.169z"/>
+		<polygon style="fill:#666666;" points="11.169,5.479 5.429,11.219 7.964,11.219 11.169,8.014 		"/>
+	</g>
+	<path style="fill:#898989;" d="M88.146,11.031H14.866c-1.011,0-1.83-0.82-1.83-1.83v-7.37c0-1.011,0.82-1.831,1.83-1.831h73.281
+		c1.011,0,1.83,0.82,1.83,1.831v7.37C89.977,10.212,89.157,11.031,88.146,11.031z"/>
+	<g>
+		<path style="fill:#666666;" d="M8.842,23.902h0.1c1.228,0,2.227-0.999,2.227-2.227v-0.1L8.842,23.902z"/>
+		<path style="fill:#666666;" d="M0.008,14.796l2.054-2.054C0.966,12.822,0.089,13.699,0.008,14.796z"/>
+		<polygon style="fill:#666666;" points="0,15.681 0,18.216 5.484,12.733 2.948,12.733 		"/>
+		<polygon style="fill:#666666;" points="6.361,12.733 0,19.094 0,21.629 8.896,12.733 		"/>
+		<path style="fill:#666666;" d="M11.169,14.96c0-0.068-0.004-0.134-0.01-0.2l-9.132,9.132c0.066,0.006,0.133,0.01,0.2,0.01h2.325
+			l6.617-6.617V14.96z"/>
+		<path style="fill:#666666;" d="M9.654,12.852l-9.536,9.536c0.201,0.592,0.643,1.073,1.211,1.324l9.649-9.649
+			C10.728,13.495,10.247,13.053,9.654,12.852z"/>
+		<polygon style="fill:#666666;" points="11.169,18.162 5.429,23.902 7.964,23.902 11.169,20.697 		"/>
+	</g>
+	<path style="fill:#898989;" d="M88.146,23.714H14.866c-1.011,0-1.83-0.82-1.83-1.83v-7.37c0-1.011,0.82-1.83,1.83-1.83h73.281
+		c1.011,0,1.83,0.82,1.83,1.83v7.37C89.977,22.895,89.157,23.714,88.146,23.714z"/>
+	<g>
+		<path style="fill:#666666;" d="M8.842,36.585h0.1c1.228,0,2.227-0.999,2.227-2.227v-0.1L8.842,36.585z"/>
+		<path style="fill:#666666;" d="M0.008,27.479l2.054-2.054C0.966,25.505,0.089,26.382,0.008,27.479z"/>
+		<polygon style="fill:#666666;" points="0,28.364 0,30.899 5.484,25.416 2.948,25.416 		"/>
+		<polygon style="fill:#666666;" points="6.361,25.416 0,31.777 0,34.312 8.896,25.416 		"/>
+		<path style="fill:#666666;" d="M11.169,27.643c0-0.068-0.004-0.134-0.01-0.2l-9.132,9.132c0.066,0.006,0.133,0.01,0.2,0.01h2.325
+			l6.617-6.617V27.643z"/>
+		<path style="fill:#666666;" d="M9.654,25.535L0.119,35.07c0.201,0.592,0.643,1.073,1.211,1.324l9.649-9.649
+			C10.728,26.178,10.247,25.736,9.654,25.535z"/>
+		<polygon style="fill:#666666;" points="11.169,30.845 5.429,36.585 7.964,36.585 11.169,33.38 		"/>
+	</g>
+	<path style="fill:#898989;" d="M88.146,36.397H14.866c-1.011,0-1.83-0.82-1.83-1.831v-7.37c0-1.011,0.82-1.83,1.83-1.83h73.281
+		c1.011,0,1.83,0.82,1.83,1.83v7.37C89.977,35.578,89.157,36.397,88.146,36.397z"/>
+	<g>
+		<path style="fill:#666666;" d="M8.842,49.268h0.1c1.228,0,2.227-0.999,2.227-2.227v-0.1L8.842,49.268z"/>
+		<path style="fill:#666666;" d="M0.008,40.162l2.054-2.054C0.966,38.188,0.089,39.065,0.008,40.162z"/>
+		<polygon style="fill:#666666;" points="0,41.047 0,43.582 5.484,38.099 2.948,38.099 		"/>
+		<polygon style="fill:#666666;" points="6.361,38.099 0,44.46 0,46.995 8.896,38.099 		"/>
+		<path style="fill:#666666;" d="M11.169,40.326c0-0.068-0.004-0.134-0.01-0.2l-9.132,9.132c0.066,0.006,0.133,0.01,0.2,0.01h2.325
+			l6.617-6.617V40.326z"/>
+		<path style="fill:#666666;" d="M9.654,38.218l-9.536,9.536c0.201,0.592,0.643,1.073,1.211,1.324l9.649-9.649
+			C10.728,38.861,10.247,38.419,9.654,38.218z"/>
+		<polygon style="fill:#666666;" points="11.169,43.528 5.429,49.268 7.964,49.268 11.169,46.063 		"/>
+	</g>
+	<path style="fill:#898989;" d="M88.146,49.08H14.866c-1.011,0-1.83-0.82-1.83-1.831v-7.37c0-1.011,0.82-1.831,1.83-1.831h73.281
+		c1.011,0,1.83,0.82,1.83,1.831v7.37C89.977,48.261,89.157,49.08,88.146,49.08z"/>
+	<g>
+		<path style="fill:#666666;" d="M8.842,61.951h0.1c1.228,0,2.227-0.999,2.227-2.227v-0.1L8.842,61.951z"/>
+		<path style="fill:#666666;" d="M0.008,52.845l2.054-2.054C0.966,50.871,0.089,51.748,0.008,52.845z"/>
+		<polygon style="fill:#666666;" points="0,53.73 0,56.265 5.484,50.782 2.948,50.782 		"/>
+		<polygon style="fill:#666666;" points="6.361,50.782 0,57.143 0,59.678 8.896,50.782 		"/>
+		<path style="fill:#666666;" d="M11.169,53.009c0-0.068-0.004-0.134-0.01-0.2l-9.132,9.132c0.066,0.006,0.133,0.01,0.2,0.01h2.325
+			l6.617-6.617V53.009z"/>
+		<path style="fill:#666666;" d="M9.654,50.901l-9.536,9.536c0.201,0.592,0.643,1.073,1.211,1.324l9.649-9.649
+			C10.728,51.544,10.247,51.102,9.654,50.901z"/>
+		<polygon style="fill:#666666;" points="11.169,56.211 5.429,61.951 7.964,61.951 11.169,58.746 		"/>
+	</g>
+	<path style="fill:#898989;" d="M88.146,61.763H14.866c-1.011,0-1.83-0.82-1.83-1.83v-7.37c0-1.011,0.82-1.831,1.83-1.831h73.281
+		c1.011,0,1.83,0.82,1.83,1.831v7.37C89.977,60.944,89.157,61.763,88.146,61.763z"/>
+	<g>
+		<path style="fill:#666666;" d="M8.842,74.634h0.1c1.228,0,2.227-0.999,2.227-2.227v-0.1L8.842,74.634z"/>
+		<path style="fill:#666666;" d="M0.008,65.528l2.054-2.054C0.966,63.554,0.089,64.431,0.008,65.528z"/>
+		<polygon style="fill:#666666;" points="0,66.413 0,68.948 5.484,63.465 2.948,63.465 		"/>
+		<polygon style="fill:#666666;" points="6.361,63.465 0,69.826 0,72.361 8.896,63.465 		"/>
+		<path style="fill:#666666;" d="M11.169,65.692c0-0.068-0.004-0.134-0.01-0.2l-9.132,9.132c0.066,0.006,0.133,0.01,0.2,0.01h2.325
+			l6.617-6.617V65.692z"/>
+		<path style="fill:#666666;" d="M9.654,63.584l-9.536,9.536c0.201,0.592,0.643,1.073,1.211,1.324l9.649-9.649
+			C10.728,64.227,10.247,63.785,9.654,63.584z"/>
+		<polygon style="fill:#666666;" points="11.169,68.894 5.429,74.634 7.964,74.634 11.169,71.429 		"/>
+	</g>
+	<path style="fill:#898989;" d="M88.146,74.446H14.866c-1.011,0-1.83-0.82-1.83-1.83v-7.37c0-1.011,0.82-1.831,1.83-1.831h73.281
+		c1.011,0,1.83,0.82,1.83,1.831v7.37C89.977,73.627,89.157,74.446,88.146,74.446z"/>
+	<g>
+		<path style="fill:#666666;" d="M8.842,87.317h0.1c1.228,0,2.227-0.999,2.227-2.227v-0.1L8.842,87.317z"/>
+		<path style="fill:#666666;" d="M0.008,78.211l2.054-2.054C0.966,76.237,0.089,77.114,0.008,78.211z"/>
+		<polygon style="fill:#666666;" points="0,79.096 0,81.631 5.484,76.148 2.948,76.148 		"/>
+		<polygon style="fill:#666666;" points="6.361,76.148 0,82.509 0,85.044 8.896,76.148 		"/>
+		<path style="fill:#666666;" d="M11.169,78.375c0-0.068-0.004-0.134-0.01-0.2l-9.132,9.132c0.066,0.006,0.133,0.01,0.2,0.01h2.325
+			l6.617-6.617V78.375z"/>
+		<path style="fill:#666666;" d="M9.654,76.267l-9.536,9.536c0.201,0.592,0.643,1.073,1.211,1.324l9.649-9.649
+			C10.728,76.91,10.247,76.468,9.654,76.267z"/>
+		<polygon style="fill:#666666;" points="11.169,81.577 5.429,87.317 7.964,87.317 11.169,84.112 		"/>
+	</g>
+	<path style="fill:#898989;" d="M88.146,87.129H14.866c-1.011,0-1.83-0.82-1.83-1.83v-7.37c0-1.011,0.82-1.831,1.83-1.831h73.281
+		c1.011,0,1.83,0.82,1.83,1.831v7.37C89.977,86.31,89.157,87.129,88.146,87.129z"/>
+	<g>
+		<path style="fill:#666666;" d="M8.842,100h0.1c1.228,0,2.227-0.999,2.227-2.227v-0.1L8.842,100z"/>
+		<path style="fill:#666666;" d="M0.008,90.894l2.054-2.054C0.966,88.92,0.089,89.797,0.008,90.894z"/>
+		<polygon style="fill:#666666;" points="0,91.779 0,94.314 5.484,88.831 2.948,88.831 		"/>
+		<polygon style="fill:#666666;" points="6.361,88.831 0,95.192 0,97.727 8.896,88.831 		"/>
+		<path style="fill:#666666;" d="M11.169,91.058c0-0.068-0.004-0.134-0.01-0.2L2.027,99.99c0.066,0.006,0.133,0.01,0.2,0.01h2.325
+			l6.617-6.617V91.058z"/>
+		<path style="fill:#666666;" d="M9.654,88.95l-9.536,9.536c0.201,0.592,0.643,1.073,1.211,1.324l9.649-9.649
+			C10.728,89.593,10.247,89.151,9.654,88.95z"/>
+		<polygon style="fill:#666666;" points="11.169,94.26 5.429,100 7.964,100 11.169,96.795 		"/>
+	</g>
+	<path style="fill:#898989;" d="M88.146,99.812H14.866c-1.011,0-1.83-0.82-1.83-1.83v-7.37c0-1.011,0.82-1.83,1.83-1.83h73.281
+		c1.011,0,1.83,0.82,1.83,1.83v7.37C89.977,98.993,89.157,99.812,88.146,99.812z"/>
+	<circle style="fill:#F7941E;" cx="96.125" cy="5.637" r="3.875"/>
+	<circle style="fill:#898989;" cx="96.125" cy="18.37" r="3.875"/>
+	<circle style="fill:#898989;" cx="96.125" cy="31.104" r="3.875"/>
+	<circle style="fill:#F7941E;" cx="96.125" cy="43.837" r="3.875"/>
+	<circle style="fill:#F7941E;" cx="96.125" cy="56.57" r="3.875"/>
+	<circle style="fill:#898989;" cx="96.125" cy="69.304" r="3.875"/>
+	<circle style="fill:#F7941E;" cx="96.125" cy="82.037" r="3.875"/>
+	<circle style="fill:#898989;" cx="96.125" cy="94.77" r="3.875"/>
+</g>
+</svg>

+ 30 - 0
public/app/plugins/panel/pluginlist/module.html

@@ -0,0 +1,30 @@
+<div class="pluginlist">
+	<div class="pluginlist-section" ng-repeat="category in ctrl.viewModel">
+		<h6 class="pluginlist-section-header">
+			{{category.header}}
+		</h6>
+		<div class="pluginlist-item" ng-repeat="plugin in category.list">
+			<div class="pluginlist-link pluginlist-link-{{plugin.state}} pointer" ng-click="ctrl.gotoPlugin(plugin)">
+				<a href="plugins/{{plugin.id}}/edit">
+						<img ng-src="{{plugin.info.logos.small}}" class="pluginlist-image">
+						<span class="pluginlist-title">{{plugin.name}}</span>
+						<span class="pluginlist-version">v{{plugin.info.version}}</span>
+				</a>
+				<a class="pluginlist-message pluginlist-message--update" ng-show="plugin.hasUpdate" ng-click="ctrl.updateAvailable(plugin, $event)" bs-tooltip="plugin.latestVersion">
+					Update available!
+				</a>
+				<span class="pluginlist-message pluginlist-message--enable" ng-show="!plugin.enabled && !plugin.hasUpdate">
+					Enable now
+				</span>
+				<span class="pluginlist-message pluginlist-message--no-update" ng-show="plugin.enabled && !plugin.hasUpdate">
+					Up to date
+				</span>
+			</div>
+		</div>
+		<div class="pluginlist-item" ng-show="category.list.length === 0">
+			<a class="pluginlist-link pluginlist-link-{{plugin.state}}" href="http://grafana/net/plugins/">
+				<span class="pluginlist-none-installed">No additional panels installed. <span class="pluginlist-emphasis">Browse Grafana.net</span></span>
+			</a>
+		</div>
+	</div>
+</div>

+ 72 - 0
public/app/plugins/panel/pluginlist/module.ts

@@ -0,0 +1,72 @@
+///<reference path="../../../headers/common.d.ts" />
+
+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}

+ 16 - 0
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"
+    }
+  }
+}

+ 30 - 24
public/dashboards/home.json

@@ -9,55 +9,61 @@
   "hideControls": true,
   "sharedCrosshair": false,
   "rows": [
-    {
+   {
       "collapse": false,
       "editable": true,
-      "height": "90px",
+      "height": "25px",
       "panels": [
         {
           "content": "<div class=\"text-center dashboard-header\">\n  <span>Home Dashboard</span>\n</div>",
           "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": []
 }

+ 1 - 0
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";

+ 11 - 10
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);
-
-

+ 9 - 9
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);
-

+ 10 - 1
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;
   }
 }
-

+ 1 - 1
public/sass/base/_type.scss

@@ -114,7 +114,7 @@ hr {
 
 small,
 .small {
-  font-size: 85%;
+  font-size: $font-size-sm;
   font-weight: normal;
 }
 

+ 27 - 2
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;
   }
 }
-

+ 6 - 1
public/sass/components/_panel_dashlist.scss

@@ -1,5 +1,10 @@
-.dashlist-item {
+.dashlist-section-header {
+  margin-bottom: $spacer;
+  color: $text-color-weak;
+}
 
+.dashlist-section {
+  margin-bottom: $spacer;
 }
 
 .dashlist-link {

+ 4 - 1
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;
@@ -246,7 +248,8 @@
     display: table-row;
 
     &--highlight {
-      color: $brand-text-highlight;
+      color: $link-color;
+      font-weight: bold;
     }
   }
 

+ 75 - 0
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;
+}

+ 1 - 1
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;

+ 2 - 2
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;
   }
 }

+ 5 - 25
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"
     ]
-}
+}