benrubson 9 лет назад
Родитель
Сommit
f4037667fa
100 измененных файлов с 2069 добавлено и 2111 удалено
  1. 1 0
      .gitignore
  2. 1 0
      CHANGELOG.md
  3. 1 1
      docker/create_docker_compose.sh
  4. 3 3
      docs/sources/installation/debian.md
  5. 3 3
      pkg/api/api.go
  6. 0 59
      pkg/api/app_settings.go
  7. 44 0
      pkg/api/cloudwatch/cloudwatch.go
  8. 0 8
      pkg/api/dataproxy.go
  9. 0 40
      pkg/api/dtos/apps.go
  10. 6 5
      pkg/api/dtos/index.go
  11. 26 0
      pkg/api/dtos/plugins.go
  12. 7 7
      pkg/api/index.go
  13. 88 0
      pkg/api/plugin_setting.go
  14. 1 1
      pkg/api/pluginproxy/pluginproxy.go
  15. 2 2
      pkg/api/pluginproxy/pluginproxy_test.go
  16. 13 13
      pkg/models/plugin_setting.go
  17. 4 0
      pkg/plugins/frontend_plugin.go
  18. 11 15
      pkg/plugins/queries.go
  19. 0 70
      pkg/services/sqlstore/app_settings.go
  20. 6 8
      pkg/services/sqlstore/migrations/plugin_setting.go
  21. 70 0
      pkg/services/sqlstore/plugin_setting.go
  22. 81 0
      public/app/core/components/colorpicker/colorpicker.ts
  23. 1 1
      public/app/core/components/grafana_app.ts
  24. 55 0
      public/app/core/components/popover/popover.ts
  25. 24 26
      public/app/core/components/sidemenu/sidemenu.html
  26. 16 1
      public/app/core/components/sidemenu/sidemenu.ts
  27. 12 1
      public/app/core/core.ts
  28. 4 2
      public/app/core/directives/annotation_tooltip.js
  29. 1 6
      public/app/core/directives/dash_edit_link.js
  30. 1 1
      public/app/core/directives/misc.js
  31. 5 5
      public/app/core/directives/plugin_component.ts
  32. 12 12
      public/app/core/routes/routes.ts
  33. 0 60
      public/app/core/services/popover_srv.js
  34. 55 0
      public/app/core/services/popover_srv.ts
  35. 45 18
      public/app/core/utils/kbn.js
  36. 1 1
      public/app/features/admin/partials/admin_home.html
  37. 36 47
      public/app/features/admin/partials/edit_org.html
  38. 51 102
      public/app/features/admin/partials/edit_user.html
  39. 24 52
      public/app/features/admin/partials/new_user.html
  40. 5 4
      public/app/features/admin/partials/orgs.html
  41. 1 3
      public/app/features/admin/partials/settings.html
  42. 1 3
      public/app/features/admin/partials/stats.html
  43. 4 1
      public/app/features/admin/partials/users.html
  44. 76 83
      public/app/features/annotations/partials/editor.html
  45. 0 49
      public/app/features/apps/edit_ctrl.ts
  46. 0 17
      public/app/features/apps/list_ctrl.ts
  47. 0 108
      public/app/features/apps/partials/edit.html
  48. 0 47
      public/app/features/apps/partials/list.html
  49. 2 2
      public/app/features/dashboard/dashnav/dashnav.html
  50. 6 0
      public/app/features/dashboard/dashnav/dashnav.ts
  51. 8 0
      public/app/features/dashboard/keybindings.js
  52. 16 15
      public/app/features/dashboard/partials/graphiteImport.html
  53. 3 1
      public/app/features/dashboard/partials/import.html
  54. 0 62
      public/app/features/dashboard/partials/linksEditor.html
  55. 85 148
      public/app/features/dashboard/partials/settings.html
  56. 53 85
      public/app/features/dashboard/partials/shareModal.html
  57. 22 25
      public/app/features/dashboard/submenu/submenu.html
  58. 12 45
      public/app/features/dashboard/timepicker/settings.html
  59. 0 2
      public/app/features/dashboard/timepicker/timepicker.ts
  60. 65 79
      public/app/features/dashlinks/editor.html
  61. 7 0
      public/app/features/datasources/partials/edit.html
  62. 15 1
      public/app/features/datasources/partials/http_settings.html
  63. 2 2
      public/app/features/datasources/partials/list.html
  64. 7 14
      public/app/features/org/partials/apikeyModal.html
  65. 7 15
      public/app/features/org/partials/newOrg.html
  66. 1 1
      public/app/features/org/partials/orgDetails.html
  67. 1 1
      public/app/features/org/partials/orgUsers.html
  68. 16 13
      public/app/features/panel/panel_directive.ts
  69. 31 56
      public/app/features/panel/partials/panelTime.html
  70. 49 66
      public/app/features/panellinks/module.html
  71. 0 1
      public/app/features/panellinks/module.js
  72. 46 27
      public/app/features/playlist/partials/playlist.html
  73. 1 1
      public/app/features/playlist/partials/playlists.html
  74. 1 1
      public/app/features/playlist/playlist_edit_ctrl.ts
  75. 0 0
      public/app/features/plugins/all.ts
  76. 51 0
      public/app/features/plugins/edit_ctrl.ts
  77. 17 0
      public/app/features/plugins/list_ctrl.ts
  78. 0 0
      public/app/features/plugins/page_ctrl.ts
  79. 185 0
      public/app/features/plugins/partials/edit.html
  80. 42 0
      public/app/features/plugins/partials/list.html
  81. 0 0
      public/app/features/plugins/partials/page.html
  82. 1 1
      public/app/features/profile/partials/profile.html
  83. 31 31
      public/app/features/snapshot/partials/snapshots.html
  84. 1 1
      public/app/features/styleguide/styleguide.html
  85. 174 288
      public/app/features/templating/partials/editor.html
  86. 8 0
      public/app/headers/common.d.ts
  87. 2 1
      public/app/partials/bootstrap/tabset.html
  88. 0 11
      public/app/partials/colorpicker.html
  89. 12 13
      public/app/partials/edit_json.html
  90. 8 0
      public/app/partials/help_modal.html
  91. 1 5
      public/app/partials/panelgeneral.html
  92. 20 47
      public/app/partials/reset_password.html
  93. 33 50
      public/app/partials/roweditor.html
  94. 2 2
      public/app/partials/valueSelectDropdown.html
  95. 2 0
      public/app/plugins/datasource/cloudwatch/annotation_query.d.ts
  96. 105 0
      public/app/plugins/datasource/cloudwatch/annotation_query.js
  97. 21 58
      public/app/plugins/datasource/cloudwatch/datasource.js
  98. 18 0
      public/app/plugins/datasource/cloudwatch/partials/annotations.editor.html
  99. 81 0
      public/app/plugins/datasource/cloudwatch/specs/annotation_query_specs.ts
  100. 0 55
      public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts

+ 1 - 0
.gitignore

@@ -31,6 +31,7 @@ public/css/*.min.css
 
 conf/custom.ini
 fig.yml
+docker-compose.yml
 profile.cov
 /grafana
 .notouch

+ 1 - 0
CHANGELOG.md

@@ -7,6 +7,7 @@
 * **Snapshots UI**: Dashboard snapshots list can be managed through UI, closes[#1984](https://github.com/grafana/grafana/issues/1984)
 * **Prometheus**: Prometheus annotation support, closes[#2883](https://github.com/grafana/grafana/pull/2883)
 * **Cli**: New cli tool for downloading and updating plugins
+* **Annotations**: Annotations can now contain links that can be clicked (you can navigate on to annotation popovers), closes [#1588](https://github.com/grafana/grafana/issues/1588)
 
 ### Breaking changes
 * **Plugin API**: Both datasource and panel plugin api (and plugin.json schema) have been updated, requiring an update to plugins. See [plugin api](https://github.com/grafana/grafana/blob/master/public/app/plugins/plugin_api.md) for more info.

+ 1 - 1
docker/build_fig.sh → docker/create_docker_compose.sh

@@ -7,7 +7,7 @@ template_dir=templates
 grafana_config_file=conf.tmp
 grafana_config=config
 
-fig_file=fig.yml
+fig_file=docker-compose.yml
 fig_config=fig
 
 if [ "$#" == 0 ]; then

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

@@ -122,8 +122,8 @@ To configure Grafana add a configuration file named `custom.ini` to the
 `conf` folder and override any of the settings defined in
 `conf/defaults.ini`.
 
-Start Grafana by executing `./grafana-server web`. The `grafana-server` binary needs
-the working directory to be the root install directory (where the binary
-and the `public` folder is located).
+Start Grafana by executing `./bin/grafana-server web`. The `grafana-server`
+binary needs the working directory to be the root install directory (where the
+binary and the `public` folder is located).
 
 

+ 3 - 3
pkg/api/api.go

@@ -126,9 +126,9 @@ func Register(r *macaron.Macaron) {
 			r.Patch("/invites/:code/revoke", wrap(RevokeInvite))
 
 			// apps
-			r.Get("/apps", wrap(GetOrgAppsList))
-			r.Get("/apps/:appId/settings", wrap(GetAppSettingsById))
-			r.Post("/apps/:appId/settings", bind(m.UpdateAppSettingsCmd{}), wrap(UpdateAppSettings))
+			r.Get("/plugins", wrap(GetPluginList))
+			r.Get("/plugins/:pluginId/settings", wrap(GetPluginSettingById))
+			r.Post("/plugins/:pluginId/settings", bind(m.UpdatePluginSettingCmd{}), wrap(UpdatePluginSetting))
 		}, reqOrgAdmin)
 
 		// create new org

+ 0 - 59
pkg/api/app_settings.go

@@ -1,59 +0,0 @@
-package api
-
-import (
-	"github.com/grafana/grafana/pkg/api/dtos"
-	"github.com/grafana/grafana/pkg/bus"
-	"github.com/grafana/grafana/pkg/middleware"
-	m "github.com/grafana/grafana/pkg/models"
-	"github.com/grafana/grafana/pkg/plugins"
-)
-
-func GetOrgAppsList(c *middleware.Context) Response {
-	orgApps, err := plugins.GetOrgAppSettings(c.OrgId)
-
-	if err != nil {
-		return ApiError(500, "Failed to list of apps", err)
-	}
-
-	result := make([]*dtos.AppSettings, 0)
-	for _, app := range plugins.Apps {
-		orgApp := orgApps[app.Id]
-		result = append(result, dtos.NewAppSettingsDto(app, orgApp))
-	}
-
-	return Json(200, result)
-}
-
-func GetAppSettingsById(c *middleware.Context) Response {
-	appId := c.Params(":appId")
-
-	if pluginDef, exists := plugins.Apps[appId]; !exists {
-		return ApiError(404, "PluginId not found, no installed plugin with that id", nil)
-	} else {
-		orgApps, err := plugins.GetOrgAppSettings(c.OrgId)
-		if err != nil {
-			return ApiError(500, "Failed to get org app settings ", nil)
-		}
-		orgApp := orgApps[appId]
-
-		return Json(200, dtos.NewAppSettingsDto(pluginDef, orgApp))
-	}
-}
-
-func UpdateAppSettings(c *middleware.Context, cmd m.UpdateAppSettingsCmd) Response {
-	appId := c.Params(":appId")
-
-	cmd.OrgId = c.OrgId
-	cmd.AppId = appId
-
-	if _, ok := plugins.Apps[cmd.AppId]; !ok {
-		return ApiError(404, "App type not installed.", nil)
-	}
-
-	err := bus.Dispatch(&cmd)
-	if err != nil {
-		return ApiError(500, "Failed to update App Plugin", err)
-	}
-
-	return ApiSuccess("App updated")
-}

+ 44 - 0
pkg/api/cloudwatch/cloudwatch.go

@@ -33,6 +33,7 @@ func init() {
 	actionHandlers = map[string]actionHandler{
 		"GetMetricStatistics":     handleGetMetricStatistics,
 		"ListMetrics":             handleListMetrics,
+		"DescribeAlarms":          handleDescribeAlarms,
 		"DescribeAlarmsForMetric": handleDescribeAlarmsForMetric,
 		"DescribeAlarmHistory":    handleDescribeAlarmHistory,
 		"DescribeInstances":       handleDescribeInstances,
@@ -142,6 +143,49 @@ func handleListMetrics(req *cwRequest, c *middleware.Context) {
 	c.JSON(200, resp)
 }
 
+func handleDescribeAlarms(req *cwRequest, c *middleware.Context) {
+	cfg := &aws.Config{
+		Region:      aws.String(req.Region),
+		Credentials: getCredentials(req.DataSource.Database),
+	}
+
+	svc := cloudwatch.New(session.New(cfg), cfg)
+
+	reqParam := &struct {
+		Parameters struct {
+			ActionPrefix    string    `json:"actionPrefix"`
+			AlarmNamePrefix string    `json:"alarmNamePrefix"`
+			AlarmNames      []*string `json:"alarmNames"`
+			StateValue      string    `json:"stateValue"`
+		} `json:"parameters"`
+	}{}
+	json.Unmarshal(req.Body, reqParam)
+
+	params := &cloudwatch.DescribeAlarmsInput{
+		MaxRecords: aws.Int64(100),
+	}
+	if reqParam.Parameters.ActionPrefix != "" {
+		params.ActionPrefix = aws.String(reqParam.Parameters.ActionPrefix)
+	}
+	if reqParam.Parameters.AlarmNamePrefix != "" {
+		params.AlarmNamePrefix = aws.String(reqParam.Parameters.AlarmNamePrefix)
+	}
+	if len(reqParam.Parameters.AlarmNames) != 0 {
+		params.AlarmNames = reqParam.Parameters.AlarmNames
+	}
+	if reqParam.Parameters.StateValue != "" {
+		params.StateValue = aws.String(reqParam.Parameters.StateValue)
+	}
+
+	resp, err := svc.DescribeAlarms(params)
+	if err != nil {
+		c.JsonApiErr(500, "Unable to call AWS API", err)
+		return
+	}
+
+	c.JSON(200, resp)
+}
+
 func handleDescribeAlarmsForMetric(req *cwRequest, c *middleware.Context) {
 	cfg := &aws.Config{
 		Region:      aws.String(req.Region),

+ 0 - 8
pkg/api/dataproxy.go

@@ -64,20 +64,12 @@ func NewReverseProxy(ds *m.DataSource, proxyPath string, targetUrl *url.URL) *ht
 	return &httputil.ReverseProxy{Director: director}
 }
 
-var dsMap map[int64]*m.DataSource = make(map[int64]*m.DataSource)
-
 func getDatasource(id int64, orgId int64) (*m.DataSource, error) {
-	// ds, exists := dsMap[id]
-	// if exists && ds.OrgId == orgId {
-	// 	return ds, nil
-	// }
-
 	query := m.GetDataSourceByIdQuery{Id: id, OrgId: orgId}
 	if err := bus.Dispatch(&query); err != nil {
 		return nil, err
 	}
 
-	dsMap[id] = &query.Result
 	return &query.Result, nil
 }
 

+ 0 - 40
pkg/api/dtos/apps.go

@@ -1,40 +0,0 @@
-package dtos
-
-import (
-	"github.com/grafana/grafana/pkg/models"
-	"github.com/grafana/grafana/pkg/plugins"
-)
-
-type AppSettings struct {
-	Name     string                    `json:"name"`
-	AppId    string                    `json:"appId"`
-	Enabled  bool                      `json:"enabled"`
-	Pinned   bool                      `json:"pinned"`
-	Module   string                    `json:"module"`
-	BaseUrl  string                    `json:"baseUrl"`
-	Info     *plugins.PluginInfo       `json:"info"`
-	Pages    []*plugins.AppPluginPage  `json:"pages"`
-	Includes []*plugins.AppIncludeInfo `json:"includes"`
-	JsonData map[string]interface{}    `json:"jsonData"`
-}
-
-func NewAppSettingsDto(def *plugins.AppPlugin, data *models.AppSettings) *AppSettings {
-	dto := &AppSettings{
-		AppId:    def.Id,
-		Name:     def.Name,
-		Info:     &def.Info,
-		Module:   def.Module,
-		BaseUrl:  def.BaseUrl,
-		Pages:    def.Pages,
-		Includes: def.Includes,
-	}
-
-	if data != nil {
-		dto.Enabled = data.Enabled
-		dto.Pinned = data.Pinned
-		dto.Info = &def.Info
-		dto.JsonData = data.JsonData
-	}
-
-	return dto
-}

+ 6 - 5
pkg/api/dtos/index.go

@@ -16,9 +16,10 @@ type PluginCss struct {
 }
 
 type NavLink struct {
-	Text     string     `json:"text"`
-	Icon     string     `json:"icon"`
-	Img      string     `json:"img"`
-	Url      string     `json:"url"`
-	Children []*NavLink `json:"children"`
+	Text     string     `json:"text,omitempty"`
+	Icon     string     `json:"icon,omitempty"`
+	Img      string     `json:"img,omitempty"`
+	Url      string     `json:"url,omitempty"`
+	Divider  bool       `json:"divider,omitempty"`
+	Children []*NavLink `json:"children,omitempty"`
 }

+ 26 - 0
pkg/api/dtos/plugins.go

@@ -0,0 +1,26 @@
+package dtos
+
+import "github.com/grafana/grafana/pkg/plugins"
+
+type PluginSetting struct {
+	Name     string                    `json:"name"`
+	Type     string                    `json:"type"`
+	PluginId string                    `json:"pluginId"`
+	Enabled  bool                      `json:"enabled"`
+	Pinned   bool                      `json:"pinned"`
+	Module   string                    `json:"module"`
+	BaseUrl  string                    `json:"baseUrl"`
+	Info     *plugins.PluginInfo       `json:"info"`
+	Pages    []*plugins.AppPluginPage  `json:"pages"`
+	Includes []*plugins.AppIncludeInfo `json:"includes"`
+	JsonData map[string]interface{}    `json:"jsonData"`
+}
+
+type PluginListItem struct {
+	Name     string              `json:"name"`
+	Type     string              `json:"type"`
+	PluginId string              `json:"pluginId"`
+	Enabled  bool                `json:"enabled"`
+	Pinned   bool                `json:"pinned"`
+	Info     *plugins.PluginInfo `json:"info"`
+}

+ 7 - 7
pkg/api/index.go

@@ -53,15 +53,15 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
 		Icon: "icon-gf icon-gf-dashboard",
 		Url:  setting.AppSubUrl + "/",
 		Children: []*dtos.NavLink{
-			{Text: "Home dashboard", Icon: "fa fa-fw fa-list", Url: setting.AppSubUrl + "/"},
-			{Text: "Playlists", Icon: "fa fa-fw fa-list", Url: setting.AppSubUrl + "/playlists"},
-			{Text: "Snapshots", Icon: "fa-fw icon-gf icon-gf-snapshot", Url: setting.AppSubUrl + "/dashboard/snapshots"},
+			{Text: "Home", Url: setting.AppSubUrl + "/"},
+			{Text: "Playlists", Url: setting.AppSubUrl + "/playlists"},
+			{Text: "Snapshots", Url: setting.AppSubUrl + "/dashboard/snapshots"},
+			{Divider: true},
+			{Text: "New", Url: setting.AppSubUrl + "/dashboard/new"},
+			{Text: "Import", Url: setting.AppSubUrl + "/import/dashboard"},
 		},
 	})
 
-	// data.MainNavLinks = append(data.MainNavLinks, &dtos.NavLink{Text: "Playlists", Icon: "fa fa-fw fa-list", Url: setting.AppSubUrl + "/playlists"})
-	// data.MainNavLinks = append(data.MainNavLinks, &dtos.NavLink{Text: "Snapshots", Icon: "fa-fw icon-gf icon-gf-snapshot", Url: setting.AppSubUrl + "/dashboard/snapshots"})
-
 	if c.OrgRole == m.ROLE_ADMIN {
 		data.MainNavLinks = append(data.MainNavLinks, &dtos.NavLink{
 			Text: "Data Sources",
@@ -72,7 +72,7 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
 		data.MainNavLinks = append(data.MainNavLinks, &dtos.NavLink{
 			Text: "Plugins",
 			Icon: "icon-gf icon-gf-apps",
-			Url:  setting.AppSubUrl + "/apps",
+			Url:  setting.AppSubUrl + "/plugins",
 		})
 	}
 

+ 88 - 0
pkg/api/plugin_setting.go

@@ -0,0 +1,88 @@
+package api
+
+import (
+	"github.com/grafana/grafana/pkg/api/dtos"
+	"github.com/grafana/grafana/pkg/bus"
+	"github.com/grafana/grafana/pkg/middleware"
+	m "github.com/grafana/grafana/pkg/models"
+	"github.com/grafana/grafana/pkg/plugins"
+)
+
+func GetPluginList(c *middleware.Context) Response {
+	pluginSettingsMap, err := plugins.GetPluginSettings(c.OrgId)
+
+	if err != nil {
+		return ApiError(500, "Failed to get list of plugins", err)
+	}
+
+	result := make([]*dtos.PluginListItem, 0)
+	for _, pluginDef := range plugins.Plugins {
+		listItem := &dtos.PluginListItem{
+			PluginId: pluginDef.Id,
+			Name:     pluginDef.Name,
+			Type:     pluginDef.Type,
+			Info:     &pluginDef.Info,
+		}
+
+		if pluginSetting, exists := pluginSettingsMap[pluginDef.Id]; exists {
+			listItem.Enabled = pluginSetting.Enabled
+			listItem.Pinned = pluginSetting.Pinned
+		}
+
+		result = append(result, listItem)
+	}
+
+	return Json(200, result)
+}
+
+func GetPluginSettingById(c *middleware.Context) Response {
+	pluginId := c.Params(":pluginId")
+
+	if def, exists := plugins.Plugins[pluginId]; !exists {
+		return ApiError(404, "Plugin not found, no installed plugin with that id", nil)
+	} else {
+		dto := &dtos.PluginSetting{
+			Type:     def.Type,
+			PluginId: def.Id,
+			Name:     def.Name,
+			Info:     &def.Info,
+		}
+
+		if app, exists := plugins.Apps[pluginId]; exists {
+			dto.Pages = app.Pages
+			dto.Includes = app.Includes
+			dto.BaseUrl = app.BaseUrl
+			dto.Module = app.Module
+		}
+
+		query := m.GetPluginSettingByIdQuery{PluginId: pluginId, OrgId: c.OrgId}
+		if err := bus.Dispatch(&query); err != nil {
+			if err != m.ErrPluginSettingNotFound {
+				return ApiError(500, "Failed to get login settings", nil)
+			}
+		} else {
+			dto.Enabled = query.Result.Enabled
+			dto.Pinned = query.Result.Pinned
+			dto.JsonData = query.Result.JsonData
+		}
+
+		return Json(200, dto)
+	}
+}
+
+func UpdatePluginSetting(c *middleware.Context, cmd m.UpdatePluginSettingCmd) Response {
+	pluginId := c.Params(":pluginId")
+
+	cmd.OrgId = c.OrgId
+	cmd.PluginId = pluginId
+
+	if _, ok := plugins.Apps[cmd.PluginId]; !ok {
+		return ApiError(404, "Plugin not installed.", nil)
+	}
+
+	if err := bus.Dispatch(&cmd); err != nil {
+		return ApiError(500, "Failed to update plugin setting", err)
+	}
+
+	return ApiSuccess("Plugin settings updated")
+}

+ 1 - 1
pkg/api/pluginproxy/pluginproxy.go

@@ -26,7 +26,7 @@ type templateData struct {
 func getHeaders(route *plugins.AppPluginRoute, orgId int64, appId string) (http.Header, error) {
 	result := http.Header{}
 
-	query := m.GetAppSettingByAppIdQuery{OrgId: orgId, AppId: appId}
+	query := m.GetPluginSettingByIdQuery{OrgId: orgId, PluginId: appId}
 
 	if err := bus.Dispatch(&query); err != nil {
 		return nil, err

+ 2 - 2
pkg/api/pluginproxy/pluginproxy_test.go

@@ -22,8 +22,8 @@ func TestPluginProxy(t *testing.T) {
 
 		setting.SecretKey = "password"
 
-		bus.AddHandler("test", func(query *m.GetAppSettingByAppIdQuery) error {
-			query.Result = &m.AppSettings{
+		bus.AddHandler("test", func(query *m.GetPluginSettingByIdQuery) error {
+			query.Result = &m.PluginSetting{
 				SecureJsonData: map[string][]byte{
 					"key": util.Encrypt([]byte("123"), "password"),
 				},

+ 13 - 13
pkg/models/app_settings.go → pkg/models/plugin_setting.go

@@ -9,12 +9,12 @@ import (
 )
 
 var (
-	ErrAppSettingNotFound = errors.New("AppSetting not found")
+	ErrPluginSettingNotFound = errors.New("Plugin setting not found")
 )
 
-type AppSettings struct {
+type PluginSetting struct {
 	Id             int64
-	AppId          string
+	PluginId       string
 	OrgId          int64
 	Enabled        bool
 	Pinned         bool
@@ -39,17 +39,17 @@ func (s SecureJsonData) Decrypt() map[string]string {
 // COMMANDS
 
 // Also acts as api DTO
-type UpdateAppSettingsCmd struct {
+type UpdatePluginSettingCmd struct {
 	Enabled        bool                   `json:"enabled"`
 	Pinned         bool                   `json:"pinned"`
 	JsonData       map[string]interface{} `json:"jsonData"`
 	SecureJsonData map[string]string      `json:"secureJsonData"`
 
-	AppId string `json:"-"`
-	OrgId int64  `json:"-"`
+	PluginId string `json:"-"`
+	OrgId    int64  `json:"-"`
 }
 
-func (cmd *UpdateAppSettingsCmd) GetEncryptedJsonData() SecureJsonData {
+func (cmd *UpdatePluginSettingCmd) GetEncryptedJsonData() SecureJsonData {
 	encrypted := make(SecureJsonData)
 	for key, data := range cmd.SecureJsonData {
 		encrypted[key] = util.Encrypt([]byte(data), setting.SecretKey)
@@ -59,13 +59,13 @@ func (cmd *UpdateAppSettingsCmd) GetEncryptedJsonData() SecureJsonData {
 
 // ---------------------
 // QUERIES
-type GetAppSettingsQuery struct {
+type GetPluginSettingsQuery struct {
 	OrgId  int64
-	Result []*AppSettings
+	Result []*PluginSetting
 }
 
-type GetAppSettingByAppIdQuery struct {
-	AppId  string
-	OrgId  int64
-	Result *AppSettings
+type GetPluginSettingByIdQuery struct {
+	PluginId string
+	OrgId    int64
+	Result   *PluginSetting
 }

+ 4 - 0
pkg/plugins/frontend_plugin.go

@@ -56,6 +56,10 @@ func (fp *FrontendPluginBase) handleModuleDefaults() {
 }
 
 func evalRelativePluginUrlPath(pathStr string, pluginId string) string {
+	if pathStr == "" {
+		return ""
+	}
+
 	u, _ := url.Parse(pathStr)
 	if u.IsAbs() {
 		return pathStr

+ 11 - 15
pkg/plugins/queries.go

@@ -5,44 +5,40 @@ import (
 	m "github.com/grafana/grafana/pkg/models"
 )
 
-func GetOrgAppSettings(orgId int64) (map[string]*m.AppSettings, error) {
-	query := m.GetAppSettingsQuery{OrgId: orgId}
+func GetPluginSettings(orgId int64) (map[string]*m.PluginSetting, error) {
+	query := m.GetPluginSettingsQuery{OrgId: orgId}
 
 	if err := bus.Dispatch(&query); err != nil {
 		return nil, err
 	}
 
-	orgAppsMap := make(map[string]*m.AppSettings)
-	for _, orgApp := range query.Result {
-		orgAppsMap[orgApp.AppId] = orgApp
+	pluginMap := make(map[string]*m.PluginSetting)
+	for _, plug := range query.Result {
+		pluginMap[plug.PluginId] = plug
 	}
 
-	return orgAppsMap, nil
+	return pluginMap, nil
 }
 
 func GetEnabledPlugins(orgId int64) (*EnabledPlugins, error) {
 	enabledPlugins := NewEnabledPlugins()
-	orgApps, err := GetOrgAppSettings(orgId)
+	orgPlugins, err := GetPluginSettings(orgId)
 	if err != nil {
 		return nil, err
 	}
 
 	enabledApps := make(map[string]bool)
 
-	for appId, installedApp := range Apps {
-		var app AppPlugin
-		app = *installedApp
+	for pluginId, app := range Apps {
 
-		// check if the app is stored in the DB for this org and if so, use the
-		// state stored there.
-		if b, ok := orgApps[appId]; ok {
+		if b, ok := orgPlugins[pluginId]; ok {
 			app.Enabled = b.Enabled
 			app.Pinned = b.Pinned
 		}
 
 		if app.Enabled {
-			enabledApps[app.Id] = true
-			enabledPlugins.Apps = append(enabledPlugins.Apps, &app)
+			enabledApps[pluginId] = true
+			enabledPlugins.Apps = append(enabledPlugins.Apps, app)
 		}
 	}
 

+ 0 - 70
pkg/services/sqlstore/app_settings.go

@@ -1,70 +0,0 @@
-package sqlstore
-
-import (
-	"time"
-
-	"github.com/grafana/grafana/pkg/bus"
-	m "github.com/grafana/grafana/pkg/models"
-	"github.com/grafana/grafana/pkg/setting"
-	"github.com/grafana/grafana/pkg/util"
-)
-
-func init() {
-	bus.AddHandler("sql", GetAppSettings)
-	bus.AddHandler("sql", GetAppSettingByAppId)
-	bus.AddHandler("sql", UpdateAppSettings)
-}
-
-func GetAppSettings(query *m.GetAppSettingsQuery) error {
-	sess := x.Where("org_id=?", query.OrgId)
-
-	query.Result = make([]*m.AppSettings, 0)
-	return sess.Find(&query.Result)
-}
-
-func GetAppSettingByAppId(query *m.GetAppSettingByAppIdQuery) error {
-	appSetting := m.AppSettings{OrgId: query.OrgId, AppId: query.AppId}
-	has, err := x.Get(&appSetting)
-	if err != nil {
-		return err
-	} else if has == false {
-		return m.ErrAppSettingNotFound
-	}
-	query.Result = &appSetting
-	return nil
-}
-
-func UpdateAppSettings(cmd *m.UpdateAppSettingsCmd) error {
-	return inTransaction2(func(sess *session) error {
-		var app m.AppSettings
-
-		exists, err := sess.Where("org_id=? and app_id=?", cmd.OrgId, cmd.AppId).Get(&app)
-		sess.UseBool("enabled")
-		sess.UseBool("pinned")
-		if !exists {
-			app = m.AppSettings{
-				AppId:          cmd.AppId,
-				OrgId:          cmd.OrgId,
-				Enabled:        cmd.Enabled,
-				Pinned:         cmd.Pinned,
-				JsonData:       cmd.JsonData,
-				SecureJsonData: cmd.GetEncryptedJsonData(),
-				Created:        time.Now(),
-				Updated:        time.Now(),
-			}
-			_, err = sess.Insert(&app)
-			return err
-		} else {
-			for key, data := range cmd.SecureJsonData {
-				app.SecureJsonData[key] = util.Encrypt([]byte(data), setting.SecretKey)
-			}
-			app.SecureJsonData = cmd.GetEncryptedJsonData()
-			app.Updated = time.Now()
-			app.Enabled = cmd.Enabled
-			app.JsonData = cmd.JsonData
-			app.Pinned = cmd.Pinned
-			_, err = sess.Id(app.Id).Update(&app)
-			return err
-		}
-	})
-}

+ 6 - 8
pkg/services/sqlstore/migrations/app_settings.go → pkg/services/sqlstore/migrations/plugin_setting.go

@@ -4,12 +4,12 @@ import . "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
 
 func addAppSettingsMigration(mg *Migrator) {
 
-	appSettingsV2 := Table{
-		Name: "app_settings",
+	pluginSettingTable := Table{
+		Name: "plugin_setting",
 		Columns: []*Column{
 			{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
 			{Name: "org_id", Type: DB_BigInt, Nullable: true},
-			{Name: "app_id", Type: DB_NVarchar, Length: 255, Nullable: false},
+			{Name: "plugin_id", Type: DB_NVarchar, Length: 255, Nullable: false},
 			{Name: "enabled", Type: DB_Bool, Nullable: false},
 			{Name: "pinned", Type: DB_Bool, Nullable: false},
 			{Name: "json_data", Type: DB_Text, Nullable: true},
@@ -18,14 +18,12 @@ func addAppSettingsMigration(mg *Migrator) {
 			{Name: "updated", Type: DB_DateTime, Nullable: false},
 		},
 		Indices: []*Index{
-			{Cols: []string{"org_id", "app_id"}, Type: UniqueIndex},
+			{Cols: []string{"org_id", "plugin_id"}, Type: UniqueIndex},
 		},
 	}
 
-	mg.AddMigration("Drop old table app_settings v1", NewDropTableMigration("app_settings"))
-
-	mg.AddMigration("create app_settings table v2", NewAddTableMigration(appSettingsV2))
+	mg.AddMigration("create plugin_setting table", NewAddTableMigration(pluginSettingTable))
 
 	//-------  indexes ------------------
-	addTableIndicesMigrations(mg, "v3", appSettingsV2)
+	addTableIndicesMigrations(mg, "v1", pluginSettingTable)
 }

+ 70 - 0
pkg/services/sqlstore/plugin_setting.go

@@ -0,0 +1,70 @@
+package sqlstore
+
+import (
+	"time"
+
+	"github.com/grafana/grafana/pkg/bus"
+	m "github.com/grafana/grafana/pkg/models"
+	"github.com/grafana/grafana/pkg/setting"
+	"github.com/grafana/grafana/pkg/util"
+)
+
+func init() {
+	bus.AddHandler("sql", GetPluginSettings)
+	bus.AddHandler("sql", GetPluginSettingById)
+	bus.AddHandler("sql", UpdatePluginSetting)
+}
+
+func GetPluginSettings(query *m.GetPluginSettingsQuery) error {
+	sess := x.Where("org_id=?", query.OrgId)
+
+	query.Result = make([]*m.PluginSetting, 0)
+	return sess.Find(&query.Result)
+}
+
+func GetPluginSettingById(query *m.GetPluginSettingByIdQuery) error {
+	pluginSetting := m.PluginSetting{OrgId: query.OrgId, PluginId: query.PluginId}
+	has, err := x.Get(&pluginSetting)
+	if err != nil {
+		return err
+	} else if has == false {
+		return m.ErrPluginSettingNotFound
+	}
+	query.Result = &pluginSetting
+	return nil
+}
+
+func UpdatePluginSetting(cmd *m.UpdatePluginSettingCmd) error {
+	return inTransaction2(func(sess *session) error {
+		var pluginSetting m.PluginSetting
+
+		exists, err := sess.Where("org_id=? and plugin_id=?", cmd.OrgId, cmd.PluginId).Get(&pluginSetting)
+		sess.UseBool("enabled")
+		sess.UseBool("pinned")
+		if !exists {
+			pluginSetting = m.PluginSetting{
+				PluginId:       cmd.PluginId,
+				OrgId:          cmd.OrgId,
+				Enabled:        cmd.Enabled,
+				Pinned:         cmd.Pinned,
+				JsonData:       cmd.JsonData,
+				SecureJsonData: cmd.GetEncryptedJsonData(),
+				Created:        time.Now(),
+				Updated:        time.Now(),
+			}
+			_, err = sess.Insert(&pluginSetting)
+			return err
+		} else {
+			for key, data := range cmd.SecureJsonData {
+				pluginSetting.SecureJsonData[key] = util.Encrypt([]byte(data), setting.SecretKey)
+			}
+			pluginSetting.SecureJsonData = cmd.GetEncryptedJsonData()
+			pluginSetting.Updated = time.Now()
+			pluginSetting.Enabled = cmd.Enabled
+			pluginSetting.JsonData = cmd.JsonData
+			pluginSetting.Pinned = cmd.Pinned
+			_, err = sess.Id(pluginSetting.Id).Update(&pluginSetting)
+			return err
+		}
+	})
+}

+ 81 - 0
public/app/core/components/colorpicker/colorpicker.ts

@@ -0,0 +1,81 @@
+///<reference path="../../../headers/common.d.ts" />
+
+import config from 'app/core/config';
+import _ from 'lodash';
+import $ from 'jquery';
+import coreModule from 'app/core/core_module';
+
+var template = `
+<div class="graph-legend-popover">
+  <a class="drop-popopver-close" ng-click="ctrl.close();" href="" ng-hide="ctrl.autoClose">
+    <i class="fa fa-times-circle"></i>
+  </a>
+
+  <div ng-show="ctrl.series" class="p-b-1">
+    <label>Y Axis:</label>
+    <button ng-click="ctrl.toggleAxis(yaxis);" class="btn btn-small"
+            ng-class="{'btn-success': ctrl.series.yaxis === 1,
+                       'btn-inverse': ctrl.series.yaxis === 2}">
+      Left
+    </button>
+    <button ng-click="ctrl.toggleAxis(yaxis);"
+      class="btn btn-small"
+      ng-class="{'btn-success': ctrl.series.yaxis === 2,
+                 'btn-inverse': ctrl.series.yaxis === 1}">
+      Right
+    </button>
+  </div>
+
+  <p class="m-b-0">
+   <i ng-repeat="color in ctrl.colors" class="pointer fa fa-circle"
+    ng-style="{color:color}"
+    ng-click="ctrl.colorSelected(color);">&nbsp;</i>
+  </p>
+
+</div>
+`;
+
+export class ColorPickerCtrl {
+  colors: any;
+  autoClose: boolean;
+  series: any;
+  showAxisControls: boolean;
+
+  /** @ngInject */
+  constructor(private $scope, private $rootScope) {
+    this.colors = $rootScope.colors;
+    this.autoClose = $scope.autoClose;
+    this.series = $scope.series;
+  }
+
+  toggleAxis(yaxis) {
+    this.$scope.toggleAxis();
+
+    if (!this.$scope.autoClose) {
+      this.$scope.dismiss();
+    }
+  }
+
+  colorSelected(color) {
+    this.$scope.colorSelected(color);
+    if (!this.$scope.autoClose) {
+      this.$scope.dismiss();
+    }
+  }
+
+  close() {
+    this.$scope.dismiss();
+  }
+}
+
+export function colorPicker() {
+  return {
+    restrict: 'E',
+    controller: ColorPickerCtrl,
+    bindToController: true,
+    controllerAs: 'ctrl',
+    template: template,
+  };
+}
+
+coreModule.directive('gfColorPicker', colorPicker);

+ 1 - 1
public/app/core/components/grafana_app.ts

@@ -77,8 +77,8 @@ export class GrafanaCtrl {
         });
       };
 
-      $rootScope.performance.scopeCount = scopes;
       f(root);
+      $rootScope.performance.scopeCount = scopes;
       return count;
     };
 

+ 55 - 0
public/app/core/components/popover/popover.ts

@@ -0,0 +1,55 @@
+///<reference path="../../../headers/common.d.ts" />
+
+import _ from 'lodash';
+import $ from 'jquery';
+import coreModule from '../../core_module';
+import Drop from 'tether-drop';
+
+export function popoverDirective() {
+  return {
+    restrict: 'E',
+    transclude: true,
+    link: function(scope, elem, attrs, ctrl, transclude) {
+      var inputElem = elem.prev();
+      if (inputElem.length === 0) {
+        console.log('Failed to find input element for popover');
+        return;
+      }
+
+      var offset = attrs.offset || '0 -10px';
+
+      transclude(function(clone, newScope) {
+        var content = document.createElement("div");
+        _.each(clone, (node) => {
+          content.appendChild(node);
+        });
+
+        var drop = new Drop({
+          target: inputElem[0],
+          content: content,
+          position: 'right middle',
+          classes: 'drop-help',
+          openOn: 'click',
+          tetherOptions: {
+            offset: offset
+          }
+        });
+
+      // inputElem.on('focus.popover', function() {
+      //   drop.open();
+      // });
+      //
+      // inputElem.on('blur.popover', function() {
+      //   close();
+      // });
+
+        scope.$on('$destroy', function() {
+          drop.destroy();
+        });
+
+      });
+    }
+  };
+}
+
+coreModule.directive('gfPopover', popoverDirective);

+ 24 - 26
public/app/core/components/sidemenu/sidemenu.html

@@ -1,53 +1,51 @@
 <ul class="sidemenu">
 
-	<li class="sidemenu-org-section" ng-if="ctrl.isSignedIn" class="dropdown">
-		<div class="sidemenu-org">
+	<li class="sidemenu-org-section" ng-if="::ctrl.isSignedIn" class="dropdown">
+		<a class="sidemenu-org" href="profile">
 			<div class="sidemenu-org-avatar">
-				<img ng-if="ctrl.user.gravatarUrl" ng-src="{{ctrl.user.gravatarUrl}}">
+				<img ng-src="{{::ctrl.user.gravatarUrl}}">
 				<span class="sidemenu-org-avatar--missing">
 					<i class="fa fa-fw fa-user"></i>
 				</span>
 			</div>
 			<div class="sidemenu-org-details">
-				<span class="sidemenu-org-user sidemenu-item-text">{{ctrl.user.name}}</span>
-				<span class="sidemenu-org-name sidemenu-item-text">{{ctrl.user.orgName}}</span>
+				<span class="sidemenu-org-user sidemenu-item-text">{{::ctrl.user.name}}</span>
+				<span class="sidemenu-org-name sidemenu-item-text">{{::ctrl.user.orgName}}</span>
 			</div>
-		</div>
+		</a>
 		<i class="fa fa-caret-right"></i>
 		<ul class="dropdown-menu" role="menu">
-			<li ng-repeat="menuItem in ctrl.orgMenu" ng-class="menuItem.cssClass">
-				<span ng-if="menuItem.section">{{menuItem.section}}</span>
-				<a href="{{menuItem.url}}" ng-if="menuItem.url" target="{{menuItem.target}}">
-					<i class="{{menuItem.icon}}" ng-if="menuItem.icon"></i>
-					{{menuItem.text}}
+			<li ng-repeat="menuItem in ctrl.orgMenu" ng-class="::menuItem.cssClass">
+				<span ng-show="::menuItem.section">{{::menuItem.section}}</span>
+				<a href="{{::menuItem.url}}" ng-show="::menuItem.url" target="{{::menuItem.target}}">
+					<i class="{{::menuItem.icon}}" ng-show="::menuItem.icon"></i>
+					{{::menuItem.text}}
 				</a>
-				<a ng-click="menuItem.click()" ng-if="menuItem.click">
-					<i class="{{menuItem.icon}}"></i>
-					{{menuItem.text}}
+				<a ng-click="menuItem.click()" ng-show="::menuItem.click">
+					<i class="{{::menuItem.icon}}"></i>
+					{{::menuItem.text}}
 				</a>
 			</li>
 		</ul>
 	</li>
 
-	<li ng-repeat="item in ctrl.mainLinks" class="dropdown">
-		<a href="{{item.url}}" class="sidemenu-item sidemenu-main-link" target="{{item.target}}">
+	<li ng-repeat="item in ::ctrl.mainLinks" class="dropdown">
+		<a href="{{::item.url}}" class="sidemenu-item sidemenu-main-link" target="{{::item.target}}">
 			<span class="icon-circle sidemenu-icon">
-				<i class="{{item.icon}}" ng-show="item.icon"></i>
-				<img ng-src="{{item.img}}" ng-show="item.img">
+				<i class="{{::item.icon}}" ng-show="::item.icon"></i>
+				<img ng-src="{{::item.img}}" ng-show="::item.img">
 			</span>
-			<span class="sidemenu-item-text">{{item.text}}</span>
-			<span class="fa fa-caret-right" ng-if="item.children"></span>
+			<span class="sidemenu-item-text">{{::item.text}}</span>
+			<span class="fa fa-caret-right" ng-if="::item.children"></span>
 		</a>
-		<ul class="dropdown-menu" role="menu" ng-if="item.children">
-			<li ng-repeat="child in item.children">
-				<a href="{{child.url}}">
-					{{child.text}}
-				</a>
+		<ul class="dropdown-menu" role="menu" ng-if="::item.children">
+			<li ng-repeat="child in ::item.children" ng-class="{divider: child.divider}">
+				<a href="{{::child.url}}">{{::child.text}}</a>
 			</li>
 		</ul>
 	</li>
 
-	<li ng-if="!ctrl.isSignedIn">
+	<li ng-show="::!ctrl.isSignedIn">
 		<a href="login" class="sidemenu-item" target="_self">
 			<span class="icon-circle sidemenu-icon"><i class="fa fa-fw fa-sign-in"></i></span>
 			<span class="sidemenu-item-text">Sign in</span>

+ 16 - 1
public/app/core/components/sidemenu/sidemenu.ts

@@ -38,7 +38,6 @@ export class SideMenuCtrl {
  openUserDropdown() {
    this.orgMenu = [
      {section: 'You', cssClass: 'dropdown-menu-title'},
-     {text: 'Preferences', url: this.getUrl('/profile')},
      {text: 'Profile', url: this.getUrl('/profile')},
    ];
 
@@ -100,6 +99,22 @@ export function sideMenuDirective() {
     bindToController: true,
     controllerAs: 'ctrl',
     scope: {},
+    link: function(scope, elem) {
+      // hack to hide dropdown menu
+      elem.on('click.dropdown', '.dropdown-menu a', function(evt) {
+        var menu = $(evt.target).parents('.dropdown-menu');
+        var parent = menu.parent();
+        menu.detach();
+
+        setTimeout(function() {
+          parent.append(menu);
+        }, 100);
+      });
+
+      scope.$on("$destory", function() {
+        elem.off('click.dropdown');
+      });
+    }
   };
 }
 

+ 12 - 1
public/app/core/core.ts

@@ -24,6 +24,8 @@ import './partials';
 import {grafanaAppDirective} from './components/grafana_app';
 import {sideMenuDirective} from './components/sidemenu/sidemenu';
 import {searchDirective} from './components/search/search';
+import {popoverDirective} from './components/popover/popover';
+import {colorPicker} from './components/colorpicker/colorpicker';
 import {navbarDirective} from './components/navbar/navbar';
 import {arrayJoin} from './directives/array_join';
 import 'app/core/controllers/all';
@@ -32,4 +34,13 @@ import 'app/core/routes/routes';
 import './filters/filters';
 import coreModule from './core_module';
 
-export {arrayJoin, coreModule, grafanaAppDirective, sideMenuDirective, navbarDirective, searchDirective};
+export {
+  arrayJoin,
+  coreModule,
+  grafanaAppDirective,
+  sideMenuDirective,
+  navbarDirective,
+  searchDirective,
+  colorPicker,
+  popoverDirective
+};

+ 4 - 2
public/app/core/directives/annotation_tooltip.js

@@ -25,7 +25,8 @@ function ($, _, coreModule) {
         var dashboard = dashboardSrv.getCurrent();
         var time = '<i>' + dashboard.formatDate(event.min) + '</i>';
 
-        var tooltip = '<div class="graph-tooltip small"><div class="graph-tooltip-time">' + title + ' ' + time + '</div> ' ;
+        var tooltip = '<div class="graph-annotation">';
+        tooltip += '<div class="graph-annotation-title">' + title + "</div>";
 
         if (event.text) {
           var text = sanitizeString(event.text);
@@ -42,9 +43,10 @@ function ($, _, coreModule) {
 
         if (tags && tags.length) {
           scope.tags = tags;
-          tooltip += '<span class="label label-tag" ng-repeat="tag in tags" tag-color-from-name="tag">{{tag}}</span><br/>';
+          tooltip += '<span class="label label-tag small" ng-repeat="tag in tags" tag-color-from-name="tag">{{tag}}</span><br/>';
         }
 
+        tooltip += '<div class="graph-annotation-time">' + time + '</div>' ;
         tooltip += "</div>";
 
         var $tooltip = $(tooltip);

+ 1 - 6
public/app/core/directives/dash_edit_link.js

@@ -73,12 +73,7 @@ function ($, coreModule) {
           };
 
           var src = "'" + payload.src + "'";
-          var cssClass = payload.cssClass || 'gf-box';
-          var view = $('<div class="' + cssClass + '" ng-include="' + src + '"></div>');
-
-          if (payload.cssClass) {
-            view.addClass(payload.cssClass);
-          }
+          var view = $('<div class="tabbed-view" ng-include="' + src + '"></div>');
 
           elem.append(view);
           $compile(elem.contents())(editorScope);

+ 1 - 1
public/app/core/directives/misc.js

@@ -39,7 +39,7 @@ function (angular, coreModule, kbn) {
         var tip = attrs.tip ? (' <tip>' + attrs.tip + '</tip>') : '';
         var showIf = attrs.showIf ? (' ng-show="' + attrs.showIf + '" ') : '';
 
-        var template = '<div class="editor-option text-center"' + showIf + '>' +
+        var template = '<div class="editor-option gf-form-checkbox text-center"' + showIf + '>' +
           ' <label for="' + attrs.model + '" class="small">' +
           attrs.text + tip + '</label>' +
           '<input class="cr1" id="' + attrs.model + '" type="checkbox" ' +

+ 5 - 5
public/app/core/directives/plugin_component.ts

@@ -160,13 +160,13 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $
       }
       // AppConfigCtrl
       case 'app-config-ctrl': {
-        let appModel = scope.ctrl.appModel;
-        return System.import(appModel.module).then(function(appModule) {
+        let model = scope.ctrl.model;
+        return System.import(model.module).then(function(appModule) {
           return {
-            baseUrl: appModel.baseUrl,
-            name: 'app-config-' + appModel.appId,
+            baseUrl: model.baseUrl,
+            name: 'app-config-' + model.pluginId,
             bindings: {appModel: "=", appEditCtrl: "="},
-            attrs: {"app-model": "ctrl.appModel", "app-edit-ctrl": "ctrl"},
+            attrs: {"app-model": "ctrl.model", "app-edit-ctrl": "ctrl"},
             Component: appModule.ConfigCtrl,
           };
         });

+ 12 - 12
public/app/core/routes/routes.ts

@@ -11,7 +11,7 @@ function setupAngularRoutes($routeProvider, $locationProvider) {
   $locationProvider.html5Mode(true);
 
   var loadOrgBundle = new BundleLoader('app/features/org/all');
-  var loadAppsBundle = new BundleLoader('app/features/apps/all');
+  var loadPluginsBundle = new BundleLoader('app/features/plugins/all');
   var loadAdminBundle = new BundleLoader('app/features/admin/admin');
 
   $routeProvider
@@ -165,23 +165,23 @@ function setupAngularRoutes($routeProvider, $locationProvider) {
     controller : 'SnapshotsCtrl',
     controllerAs: 'ctrl',
   })
-  .when('/apps', {
-    templateUrl: 'public/app/features/apps/partials/list.html',
-    controller: 'AppListCtrl',
+  .when('/plugins', {
+    templateUrl: 'public/app/features/plugins/partials/list.html',
+    controller: 'PluginListCtrl',
     controllerAs: 'ctrl',
-    resolve: loadAppsBundle,
+    resolve: loadPluginsBundle,
   })
-  .when('/apps/:appId/edit', {
-    templateUrl: 'public/app/features/apps/partials/edit.html',
-    controller: 'AppEditCtrl',
+  .when('/plugins/:pluginId/edit', {
+    templateUrl: 'public/app/features/plugins/partials/edit.html',
+    controller: 'PluginEditCtrl',
     controllerAs: 'ctrl',
-    resolve: loadAppsBundle,
+    resolve: loadPluginsBundle,
   })
-  .when('/apps/:appId/page/:slug', {
-    templateUrl: 'public/app/features/apps/partials/page.html',
+  .when('/plugins/:pluginId/page/:slug', {
+    templateUrl: 'public/app/features/plugins/partials/page.html',
     controller: 'AppPageCtrl',
     controllerAs: 'ctrl',
-    resolve: loadAppsBundle,
+    resolve: loadPluginsBundle,
   })
   .when('/global-alerts', {
     templateUrl: 'public/app/features/dashboard/partials/globalAlerts.html',

+ 0 - 60
public/app/core/services/popover_srv.js

@@ -1,60 +0,0 @@
-define([
-  'angular',
-  'lodash',
-  'jquery',
-  '../core_module',
-],
-function (angular, _, $, coreModule) {
-  'use strict';
-
-  coreModule.default.service('popoverSrv', function($templateCache, $timeout, $q, $http, $compile) {
-
-    this.getTemplate = function(url) {
-      return $q.when($templateCache.get(url) || $http.get(url, {cache: true}));
-    };
-
-    this.show = function(options) {
-      var popover;
-
-      // hide other popovers
-      $('.popover').each(function() {
-        popover = $(this).prev().data('popover');
-        if (popover) {
-          popover.scope.$destroy();
-          popover.destroy();
-        }
-      });
-
-      options.scope.dismiss = function() {
-        popover = options.element.data('popover');
-        if (popover) {
-          popover.destroy();
-        }
-        options.scope.$destroy();
-      };
-
-      this.getTemplate(options.templateUrl).then(function(result) {
-        $timeout(function() {
-          var template = _.isString(result) ? result : result.data;
-
-          options.element.popover({
-            content: template,
-            placement: options.placement || 'bottom',
-            html: true
-          });
-
-          popover = options.element.data('popover');
-          popover.hasContent = function () {
-            return template;
-          };
-
-          popover.toggle();
-          popover.scope = options.scope;
-          $compile(popover.$tip)(popover.scope);
-        }, 1);
-      });
-    };
-
-  });
-
-});

+ 55 - 0
public/app/core/services/popover_srv.ts

@@ -0,0 +1,55 @@
+///<reference path="../../headers/common.d.ts" />
+
+import config from 'app/core/config';
+import _ from 'lodash';
+import $ from 'jquery';
+import coreModule from 'app/core/core_module';
+import Drop from 'tether-drop';
+
+/** @ngInject **/
+function popoverSrv($compile, $rootScope) {
+
+  this.show = function(options) {
+    var popoverScope = _.extend($rootScope.$new(true), options.model);
+    var drop;
+
+    function destroyDrop() {
+      setTimeout(function() {
+        if (drop.tether) {
+          drop.destroy();
+        }
+      });
+    }
+
+    popoverScope.dismiss = function() {
+      popoverScope.$destroy();
+      destroyDrop();
+    };
+
+    var contentElement = document.createElement('div');
+    contentElement.innerHTML = options.template;
+
+    $compile(contentElement)(popoverScope);
+
+    drop = new Drop({
+      target: options.element,
+      content: contentElement,
+      position: options.position,
+      classes: 'drop-popover',
+      openOn: options.openOn || 'hover',
+      hoverCloseDelay: 200,
+      tetherOptions: {
+        constraints: [{to: 'window', pin: true, attachment: "both"}]
+      }
+    });
+
+    drop.on('close', () => {
+      popoverScope.dismiss({fromDropClose: true});
+      destroyDrop();
+    });
+
+    drop.open();
+  };
+}
+
+coreModule.service('popoverSrv', popoverSrv);

+ 45 - 18
public/app/core/utils/kbn.js

@@ -12,39 +12,66 @@ function($, _) {
 
   kbn.round_interval = function(interval) {
     switch (true) {
-    // 0.5s
-    case (interval <= 500):
+    // 0.3s
+    case (interval <= 300):
       return 100;       // 0.1s
-    // 5s
-    case (interval <= 5000):
+    // 0.75s
+    case (interval <= 750):
+      return 500;       // 0.5s
+    // 1.5s
+    case (interval <= 1500):
       return 1000;      // 1s
+    // 3.5s
+    case (interval <= 3500):
+      return 2000;      // 2s
     // 7.5s
     case (interval <= 7500):
       return 5000;      // 5s
-    // 15s
-    case (interval <= 15000):
+    // 12.5s
+    case (interval <= 12500):
       return 10000;     // 10s
+    // 17.5s
+    case (interval <= 17500):
+      return 15000;     // 15s
+    // 25s
+    case (interval <= 25000):
+      return 20000;     // 20s
     // 45s
     case (interval <= 45000):
       return 30000;     // 30s
-    // 3m
-    case (interval <= 180000):
+    // 1.5m
+    case (interval <= 90000):
       return 60000;     // 1m
-    // 9m
+    // 3.5m
+    case (interval <= 210000):
+      return 120000;    // 2m
+    // 7.5m
     case (interval <= 450000):
       return 300000;    // 5m
-    // 20m
-    case (interval <= 1200000):
+    // 12.5m
+    case (interval <= 750000):
       return 600000;    // 10m
+    // 12.5m
+    case (interval <= 1050000):
+      return 900000;    // 15m
+    // 25m
+    case (interval <= 1500000):
+      return 1200000;   // 20m
     // 45m
     case (interval <= 2700000):
       return 1800000;   // 30m
-    // 2h
-    case (interval <= 7200000):
+    // 1.5h
+    case (interval <= 5400000):
       return 3600000;   // 1h
-    // 6h
-    case (interval <= 21600000):
+    // 2.5h
+    case (interval <= 9000000):
+      return 7200000;   // 2h
+    // 4.5h
+    case (interval <= 16200000):
       return 10800000;  // 3h
+    // 9h
+    case (interval <= 32400000):
+      return 21600000;  // 6h
     // 24h
     case (interval <= 86400000):
       return 43200000;  // 12h
@@ -593,12 +620,12 @@ function($, _) {
           {text: 'packets/sec', value: 'pps'},
           {text: 'bits/sec',    value: 'bps'},
           {text: 'bytes/sec',   value: 'Bps'},
-          {text: 'kilobites/sec', value: 'Kbits'},
+          {text: 'kilobits/sec', value: 'Kbits'},
           {text: 'kilobytes/sec',    value: 'KBs'},
-          {text: 'megabites/sec', value: 'Mbits'},
+          {text: 'megabits/sec', value: 'Mbits'},
           {text: 'megabytes/sec',    value: 'MBs'},
           {text: 'gigabytes/sec',   value: 'GBs'},
-          {text: 'gigabites/sec',   value: 'Gbits'},
+          {text: 'gigabits/sec',   value: 'Gbits'},
         ]
       },
       {

+ 1 - 1
public/app/features/admin/partials/admin_home.html

@@ -1,4 +1,4 @@
-<navbar icon="fa fa-fw fa-cogs" title="Admin">
+<navbar icon="fa fa-fw fa-cogs" title="Admin" title-url="admin">
 </navbar>
 
 <div class="page-container">

+ 36 - 47
public/app/features/admin/partials/edit_org.html

@@ -1,58 +1,47 @@
 <navbar icon="fa fa-fw fa-cogs" title="Admin" title-url="admin">
-	<nav-button title="Orgs" title-url="admin/orgs" icon="icon-gf icon-gf-users"></nav-button>
+	<a href="admin/orgs" class="navbar-page-btn">
+		<i class="icon-gf icon-gf-users"></i>
+		Orgs
+	</a>
 </navbar>
 
 <div class="page-container">
 	<div class="page-header">
-		<h1>
-			Edit Organization
-		</h1>
+		<h1>Edit Organization</h1>
 	</div>
 
-		<form name="orgDetailsForm">
-			<div>
-				<div class="tight-form">
-					<ul class="tight-form-list">
-						<li class="tight-form-item" style="width: 100px">
-							Name
-						</li>
-						<li>
-							<input type="text" required ng-model="org.name" class="input-xxlarge tight-form-input last" >
-						</li>
-					</ul>
-					<div class="clearfix"></div>
-				</div>
-			</div>
+	<form name="orgDetailsForm" class="gf-form-group">
+		<div class="gf-form">
+			<span class="gf-form-label width-10">Name</span>
+			<input type="text" required ng-model="org.name" class="gf-form-input max-width-14" >
+		</div>
 
-			<br>
-			<button type="submit" class="pull-right btn btn-success" ng-click="update()" ng-show="!createMode">Update</button>
-		</form>
+		<div class="gf-form-button-row">
+			<button type="submit" class="btn btn-success" ng-click="update()" ng-show="!createMode">Update</button>
+		</div>
+	</form>
 
-		<h3>
-			Organization Users
-		</h3>
+	<h3 class="page-heading">Organization Users</h3>
 
-		<table class="grafana-options-table form-inline">
-			<tr>
-				<th>Username</th>
-				<th>Email</th>
-				<th>Role</th>
-				<th></th>
-			</tr>
-			<tr ng-repeat="orgUser in orgUsers">
-				<td>{{orgUser.login}}</td>
-				<td>{{orgUser.email}}</td>
-				<td>
-					<select type="text" ng-model="orgUser.role" class="input-small" ng-options="f for f in ['Viewer', 'Editor', 'Read Only Editor', 'Admin']" ng-change="updateOrgUser(orgUser)">
-					</select>
-				</td>
-				<td style="width: 1%">
-					<a ng-click="removeOrgUser(orgUser)" class="btn btn-danger btn-mini">
-						<i class="fa fa-remove"></i>
-					</a>
-				</td>
-			</tr>
-		</table>
-
-	</div>
+	<table class="grafana-options-table">
+		<tr>
+			<th>Username</th>
+			<th>Email</th>
+			<th>Role</th>
+			<th></th>
+		</tr>
+		<tr ng-repeat="orgUser in orgUsers">
+			<td>{{orgUser.login}}</td>
+			<td>{{orgUser.email}}</td>
+			<td>
+				<select type="text" ng-model="orgUser.role" class="gf-form-input max-width-8" ng-options="f for f in ['Viewer', 'Editor', 'Read Only Editor', 'Admin']" ng-change="updateOrgUser(orgUser)">
+				</select>
+			</td>
+			<td style="width: 1%">
+				<a ng-click="removeOrgUser(orgUser)" class="btn btn-danger btn-mini">
+					<i class="fa fa-remove"></i>
+				</a>
+			</td>
+		</tr>
+	</table>
 </div>

+ 51 - 102
public/app/features/admin/partials/edit_user.html

@@ -1,129 +1,78 @@
 <navbar icon="fa fa-fw fa-cogs" title="Admin" title-url="admin">
-	<nav-button title="Users" title-url="admin/users" icon="icon-gf icon-gf-users"></nav-button>
+	<a href="admin/users" class="navbar-page-btn">
+		<i class="icon-gf icon-gf-users"></i>
+		Users
+	</a>
 </navbar>
 
 <div class="page-container">
 	<div class="page-header">
-		<h1>
-			Edit User
-		</h1>
+		<h1>Edit User</h1>
 	</div>
 
-	<form name="userForm">
-		<div>
-			<div class="tight-form">
-				<ul class="tight-form-list">
-					<li class="tight-form-item" style="width: 100px">
-						Name
-					</li>
-					<li>
-						<input type="text" required ng-model="user.name" class="input-xxlarge tight-form-input last" >
-					</li>
-				</ul>
-				<div class="clearfix"></div>
+	<form name="userForm" class="gf-form-group">
+			<div class="gf-form">
+				<span class="gf-form-label width-10">Name</span>
+				<input type="text" required ng-model="user.name" class="gf-form-input max-width-25" >
 			</div>
-			<div class="tight-form">
-				<ul class="tight-form-list">
-					<li class="tight-form-item" style="width: 100px">
-						Email
-					</li>
-					<li>
-						<input type="email" ng-model="user.email" class="input-xxlarge tight-form-input last" >
-					</li>
-				</ul>
-				<div class="clearfix"></div>
+			<div class="gf-form">
+				<span class="gf-form-label width-10">Email</span>
+				<input type="email" ng-model="user.email" class="gf-form-input max-width-25" >
 			</div>
-			<div class="tight-form">
-				<ul class="tight-form-list">
-					<li class="tight-form-item" style="width: 100px">
-						Username
-					</li>
-					<li>
-						<input type="text" ng-model="user.login" class="input-xxlarge tight-form-input last" >
-					</li>
-				</ul>
-				<div class="clearfix"></div>
+			<div class="gf-form">
+				<span class="gf-form-label width-10">Username</span>
+				<input type="text" ng-model="user.login" class="gf-form-input max-width-25" >
 			</div>
-		</div>
 
-		<br>
-		<button type="submit" class="pull-right btn btn-success" ng-click="update()" ng-show="!createMode">Update</button>
+			<div class="gf-form-button-row">
+				<button type="submit" class="btn btn-success" ng-click="update()" ng-show="!createMode">Update</button>
+			</div>
 	</form>
 
-	<h3>
-		Change password
-	</h3>
+	<h3 class="page-heading">Change password</h3>
 
-	<form name="passwordForm">
-		<div>
-			<div class="tight-form">
-				<ul class="tight-form-list">
-					<li class="tight-form-item" style="width: 100px">
-						New password
-					</li>
-					<li>
-						<input type="password" required ng-minlength="4" ng-model="password" class="input-xxlarge tight-form-input last">
-					</li>
-				</ul>
-				<div class="clearfix"></div>
-			</div>
+	<form name="passwordForm" class="gf-form-group">
+		<div class="gf-form">
+				<span class="gf-form-label width-10">New password</span>
+				<input type="password" required ng-minlength="4" ng-model="password" class="gf-form-input max-width-25">
 		</div>
 
-		<br>
-		<button type="submit" class="pull-right btn btn-success" ng-click="setPassword()">Update</button>
+		<div class="gf-form-button-row">
+			<button type="submit" class="btn btn-success" ng-click="setPassword()">Update</button>
+		</div>
 	</form>
 
-	<h3>
-		Permissions
-	</h3>
+	<h3 class="page-heading">Permissions</h3>
 
-	<div>
-		<div class="tight-form last">
-			<ul class="tight-form-list">
-				<li class="tight-form-item last">
-					Grafana Admin&nbsp;
-					<input class="cr1" id="permissions.isGrafanaAdmin" type="checkbox"
-					ng-model="permissions.isGrafanaAdmin" ng-checked="permissions.isGrafanaAdmin">
-					<label for="permissions.isGrafanaAdmin" class="cr1"></label>
-				</li>
-			</ul>
-			<div class="clearfix"></div>
+	<form name="passwordForm" class="gf-form-group">
+		<div class="gf-form" >
+			<editor-checkbox text="Grafana Admin" model="permissions.isGrafanaAdmin" style="line-height: 1.5rem;"></editor-checkbox>
 		</div>
-		<br>
-		<button type="submit" class="pull-right btn btn-success" ng-click="updatePermissions()">Update</button>
-		<br>
-	</div>
 
-	<h3>
-		Organizations
-	</h3>
+		<div class="gf-form-button-row">
+			<button type="submit" class="btn btn-success" ng-click="updatePermissions()">Update</button>
+		</div>
+	</form>
+
+	<h3 class="page-heading">Organizations</h3>
 
-	<form name="addOrgForm">
-		<div class="tight-form">
-			<ul class="tight-form-list">
-				<li class="tight-form-item" style="width: 160px">
-					Add organization
-				</li>
-				<li>
-					<input type="text" ng-model="newOrg.name" bs-typeahead="searchOrgs"
-					required class="input-xlarge tight-form-input" placeholder="organization name">
-				</li>
-				<li class="tight-form-item">
-					Role
-				</li>
-				<li>
-					<select type="text" ng-model="newOrg.role" class="input-small tight-form-input" ng-options="f for f in ['Viewer', 'Editor', 'Read Only Editor', 'Admin']">
-					</select>
-				</li>
-				<li>
-					<button class="btn btn-success tight-form-btn" ng-click="addOrgUser()">Add</button>
-				</li>
-				<div class="clearfix"></div>
-			</ul>
+	<form name="addOrgForm" class="gf-form-group">
+		<div class="gf-form-inline">
+			<div class="gf-form">
+				<span class="gf-form-label width-12">Add organization</span>
+				<input type="text" ng-model="newOrg.name" bs-typeahead="searchOrgs"	required class="gf-form-input max-width-20" placeholder="organization name">
+			</div>
+			<div class="gf-form">
+				<span class="gf-form-label">Role</span>
+				<select type="text" ng-model="newOrg.role" class="gf-form-input width-10" ng-options="f for f in ['Viewer', 'Editor', 'Read Only Editor', 'Admin']"></select>
+			</div>
+			<div class="gf-form">
+				<button class="btn btn-success gf-form-btn" ng-click="addOrgUser()">Add</button>
+			</div>
 		</div>
 	</form>
 
-	<table class="grafana-options-table form-inline">
+	<table class="grafana-options-table">
 		<tr>
 			<th>Name</th>
 			<th>Role</th>
@@ -134,7 +83,7 @@
 				{{org.name}} <span class="label label-info" ng-show="org.orgId === user.orgId">Current</span>
 			</td>
 			<td>
-				<select type="text" ng-model="org.role" class="input-small" ng-options="f for f in ['Viewer', 'Editor', 'Read Only Editor', 'Admin']" ng-change="updateOrgUser(org)">
+				<select type="text" ng-model="org.role" class="gf-form-input max-width-12" ng-options="f for f in ['Viewer', 'Editor', 'Read Only Editor', 'Admin']" ng-change="updateOrgUser(org)">
 				</select>
 			</td>
 			<td style="width: 1%">

+ 24 - 52
public/app/features/admin/partials/new_user.html

@@ -1,63 +1,35 @@
 <navbar icon="fa fa-fw fa-cogs" title="Admin" title-url="admin">
-	<nav-button title="Users" title-url="admin/users" icon="icon-gf icon-gf-users"></nav-button>
+	<a href="admin/users" class="navbar-page-btn">
+		<i class="icon-gf icon-gf-users"></i>
+		Users
+	</a>
 </navbar>
 
 <div class="page-container">
 	<div class="page-header">
-		<h1>
-			Add new user
-		</h1>
+		<h1>Add new user</h1>
 	</div>
 
-	<form name="userForm">
-		<div>
-			<div class="tight-form">
-				<ul class="tight-form-list">
-					<li class="tight-form-item" style="width: 100px">
-						<strong>Name</strong>
-					</li>
-					<li>
-						<input type="text" required ng-model="user.name" class="input-xxlarge tight-form-input last" >
-					</li>
-				</ul>
-				<div class="clearfix"></div>
-			</div>
-			<div class="tight-form">
-				<ul class="tight-form-list">
-					<li class="tight-form-item" style="width: 100px">
-						<strong>Email</strong>
-					</li>
-					<li>
-						<input type="email" ng-model="user.email" class="input-xxlarge tight-form-input last" >
-					</li>
-				</ul>
-				<div class="clearfix"></div>
-			</div>
-			<div class="tight-form">
-				<ul class="tight-form-list">
-					<li class="tight-form-item" style="width: 100px">
-						<strong>Username</strong>
-					</li>
-					<li>
-						<input type="text" ng-model="user.login" class="input-xxlarge tight-form-input last" >
-					</li>
-				</ul>
-				<div class="clearfix"></div>
-			</div>
-			<div class="tight-form">
-				<ul class="tight-form-list">
-					<li class="tight-form-item" style="width: 100px">
-						<strong>Password</strong>
-					</li>
-					<li>
-						<input type="password" required ng-model="user.password" class="input-xxlarge tight-form-input last" >
-					</li>
-				</ul>
-				<div class="clearfix"></div>
-			</div>
+	<form name="userForm" class="gf-form-group">
+		<div class="gf-form">
+			<span class="gf-form-label width-10">Name</span>
+			<input type="text" required ng-model="user.name" class="gf-form-input max-width-20" >
+		</div>
+		<div class="gf-form">
+			<span class="gf-form-label width-10">Email</span>
+			<input type="email" ng-model="user.email" class="gf-form-input max-width-20" >
+		</div>
+		<div class="gf-form">
+			<span class="gf-form-label width-10">Username</span>
+			<input type="text" ng-model="user.login" class="gf-form-input max-width-20" >
+		</div>
+		<div class="gf-form">
+			<span class="gf-form-label width-10">Password</span>
+			<input type="password" required ng-model="user.password" class="gf-form-input max-width-20" >
 		</div>
 
-		<br>
-		<button type="submit" class="pull-right btn btn-success" ng-click="create()">Create</button>
+		<div class="gf-form-button-row">
+			<button type="submit" class="btn btn-success" ng-click="create()">Create</button>
+		</div>
 	</form>
 </div>

+ 5 - 4
public/app/features/admin/partials/orgs.html

@@ -1,12 +1,13 @@
 <navbar icon="fa fa-fw fa-cogs" title="Admin" title-url="admin">
-	<nav-button title="Orgs" title-url="admin/orgs" icon="icon-gf icon-gf-users"></nav-button>
+	<a href="admin/orgs" class="navbar-page-btn">
+		<i class="icon-gf icon-gf-users"></i>
+		Orgs
+	</a>
 </navbar>
 
 <div class="page-container">
 	<div class="page-header">
-		<h1>
-			Organizations
-		</h1>
+		<h1>Organizations</h1>
 	</div>
 
 	<table class="filter-table form-inline">

+ 1 - 3
public/app/features/admin/partials/settings.html

@@ -3,9 +3,7 @@
 
 <div class="page-container">
 	<div class="page-header">
-		<h1>
-			Server settings
-		</h1>
+		<h1>Server settings</h1>
 	</div>
 
 		<div class="grafana-info-box span8" style="margin: 20px 0 25px 0">

+ 1 - 3
public/app/features/admin/partials/stats.html

@@ -3,9 +3,7 @@
 
 <div class="page-container">
 	<div class="page-header">
-		<h1>
-			Stats
-		</h1>
+		<h1>Stats</h1>
 	</div>
 
 	<table class="filter-table form-inline">

+ 4 - 1
public/app/features/admin/partials/users.html

@@ -1,5 +1,8 @@
 <navbar icon="fa fa-fw fa-cogs" title="Admin" title-url="admin">
-	<nav-button title="Users" title-url="admin/users" icon="icon-gf icon-gf-users"></nav-button>
+	<a href="admin/users" class="navbar-page-btn">
+		<i class="icon-gf icon-gf-users"></i>
+		Users
+	</a>
 </navbar>
 
 <div class="page-container">

+ 76 - 83
public/app/features/annotations/partials/editor.html

@@ -1,106 +1,99 @@
 <div ng-controller="AnnotationsEditorCtrl" ng-init="init()">
-
-	<div class="gf-box-header">
-		<div class="gf-box-title">
-			<i class="fa fa-bolt"></i>
+	<div class="tabbed-view-header">
+		<h2 class="tabbed-view-title">
 			Annotations
-		</div>
-
-		<div class="tabs">
-			<ul class="nav nav-tabs">
-				<li ng-class="{active: mode === 'list'}">
-					<a ng-click="mode = 'list';">
-						List
-					</a>
-				</li>
+		</h2>
 
-				<li ng-class="{active: mode === 'edit'}" ng-show="mode === 'edit'">
-					<a>
-						{{currentAnnotation.name}}
-					</a>
-				</li>
-
-				<li ng-class="{active: mode === 'new'}">
-					<a ng-click="mode = 'new';">
-						<i class="fa fa-plus"></i>
-						New
-					</a>
-				</li>
-			</ul>
-		</div>
+		<ul class="gf-tabs">
+			<li class="gf-tabs-item" >
+				<a class="gf-tabs-link" ng-click="mode = 'list';" ng-class="{active: mode === 'list'}">
+					List
+				</a>
+			</li>
+			<li class="gf-tabs-item" ng-show="mode === 'edit'">
+				<a class="gf-tabs-link" ng-class="{active: mode === 'edit'}">
+					{{currentAnnotation.name}}
+				</a>
+			</li>
+			<li class="gf-tabs-item" ng-show="mode === 'new'">
+				<span class="active gf-tabs-link">New</span>
+			</li>
+		</ul>
 
-		<button class="gf-box-header-close-btn" ng-click="dismiss();">
+		<button class="tabbed-view-close-btn" ng-click="dismiss();">
 			<i class="fa fa-remove"></i>
 		</button>
 	</div>
-	<div class="gf-box-body">
-
 
+	<div class="tabbed-view-body">
 		<div class="editor-row row" ng-if="mode === 'list'">
-			<div class="span6">
-				<div ng-if="annotations.length === 0">
-					<em>No annotations defined</em>
-				</div>
-				<table class="grafana-options-table">
-					<tr ng-repeat="annotation in annotations">
-						<td style="width:90%">
-							<i class="fa fa-bolt" style="color:{{annotation.iconColor}}"></i> &nbsp;
-							{{annotation.name}}
-						</td>
-						<td style="width: 1%"><i ng-click="_.move(annotations,$index,$index-1)" ng-hide="$first" class="pointer fa fa-arrow-up"></i></td>
-						<td style="width: 1%"><i ng-click="_.move(annotations,$index,$index+1)" ng-hide="$last" class="pointer fa fa-arrow-down"></i></td>
+			<div ng-if="annotations.length === 0">
+				<em>No annotations defined</em>
+			</div>
+			<table class="grafana-options-table">
+				<tr ng-repeat="annotation in annotations">
+					<td style="width:90%">
+						<i class="fa fa-bolt" style="color:{{annotation.iconColor}}"></i> &nbsp;
+						{{annotation.name}}
+					</td>
+					<td style="width: 1%"><i ng-click="_.move(annotations,$index,$index-1)" ng-hide="$first" class="pointer fa fa-arrow-up"></i></td>
+					<td style="width: 1%"><i ng-click="_.move(annotations,$index,$index+1)" ng-hide="$last" class="pointer fa fa-arrow-down"></i></td>
 
-						<td style="width: 1%" class="nobg">
-							<a ng-click="edit(annotation)" class="btn btn-inverse btn-mini">
-								<i class="fa fa-edit"></i>
-								Edit
-							</a>
-						</td>
-						<td style="width: 1%" class="nobg">
-							<a ng-click="removeAnnotation(annotation)" class="btn btn-danger btn-mini">
-								<i class="fa fa-remove"></i>
-							</a>
-						</td>
-					</tr>
-				</table>
+					<td style="width: 1%">
+						<a ng-click="edit(annotation)" class="btn btn-inverse btn-mini">
+							<i class="fa fa-edit"></i>
+							Edit
+						</a>
+					</td>
+					<td style="width: 1%">
+						<a ng-click="removeAnnotation(annotation)" class="btn btn-danger btn-mini">
+							<i class="fa fa-remove"></i>
+						</a>
+					</td>
+				</tr>
+			</table>
+		</div>
+
+		<div class="gf-form" ng-show="mode === 'list'">
+			<div class="gf-form-button-row">
+				<a type="button" class="btn gf-form-button btn-success" ng-click="mode = 'new';"><i class="fa fa-plus" ></i>&nbsp;&nbsp;New</a>
 			</div>
 		</div>
 
-		<div ng-if="mode === 'edit' || mode === 'new'">
-			<div class="editor-row">
-				<div class="editor-option">
-					<label class="small">Name</label>
-					<input type="text" class="input-medium" ng-model='currentAnnotation.name' placeholder="name"></input>
-				</div>
-				<div class="editor-option">
-					<label class="small">Datasource</label>
-					<select ng-model="currentAnnotation.datasource" ng-options="f.name as f.name for f in datasources" ng-change="datasourceChanged()"></select>
-				</div>
-				<div class="editor-option text-center">
-					<label class="small">Icon color</label>
-					<spectrum-picker ng-model="currentAnnotation.iconColor"></spectrum-picker>
-				</div>
-				<div class="editor-option">
-					<label class="small">Icon size</label>
-					<select class="input-mini" ng-model="currentAnnotation.iconSize" ng-options="f for f in [7,8,9,10,13,15,17,20,25,30]"></select>
-				</div>
-				<editor-opt-bool text="Grid line" model="currentAnnotation.showLine"></editor-opt-bool>
-				<div class="editor-option text-center">
-					<label class="small">Line color</label>
-					<spectrum-picker ng-model="currentAnnotation.lineColor"></spectrum-picker>
+		<div class="annotations-basic-settings" ng-if="mode === 'edit' || mode === 'new'">
+			<div class="gf-form-group">
+				<div class="gf-form-inline">
+					<div class="gf-form gf-size-max-xxl">
+						<span class="gf-form-label">Name</span>
+						<input type="text" class="gf-form-input" ng-model='currentAnnotation.name' placeholder="name"></input>
+					</div>
+					<div class="gf-form">
+						<span class="gf-form-label max-width-10">Datasource</span>
+						<div class="gf-form-select-wrapper">
+							<select class="gf-form-input gf-size-auto" ng-model="currentAnnotation.datasource" ng-options="f.name as f.name for f in datasources" ng-change="datasourceChanged()"></select>
+						</div>
+					</div>
+					<div class="gf-form">
+						<label class="gf-form-label">
+							<span>Color</span>
+							<spectrum-picker ng-model="currentAnnotation.iconColor"></spectrum-picker>
+						</label>
+					</div>
 				</div>
 			</div>
 
-			<rebuild-on-change property="currentAnnotation.datasource">
+			<rebuild-on-change property="currentDatasource">
 				<plugin-component type="annotations-query-ctrl">
 				</plugin-component>
 			</rebuild-on-change>
 
-			<br>
-			<button ng-show="mode === 'new'" type="button" class="btn btn-success" ng-click="add()">Add</button>
-			<button ng-show="mode === 'edit'" type="button" class="btn btn-success pull-left" ng-click="update();">Update</button>
-			<br>
-			<br>
+			<div class="gf-form">
+				<div class="gf-form-button-row p-y-0">
+					<button ng-show="mode === 'new'" type="button" class="btn gf-form-button btn-success" ng-click="add()">Add</button>
+					<button ng-show="mode === 'edit'" type="button" class="btn btn-success pull-left" ng-click="update()">Update</button>
+				</div>
+			</div>
 		</div>
+
 	</div>
 </div>

+ 0 - 49
public/app/features/apps/edit_ctrl.ts

@@ -1,49 +0,0 @@
-///<reference path="../../headers/common.d.ts" />
-
-import angular from 'angular';
-import _ from 'lodash';
-
-export class AppEditCtrl {
-  appModel: any;
-  appId: any;
-  includedPanels: any;
-  includedDatasources: any;
-
-  /** @ngInject */
-  constructor(private backendSrv: any, private $routeParams: any) {
-    this.appModel = {};
-    this.appId = $routeParams.appId;
-
-    this.backendSrv.get(`/api/org/apps/${this.appId}/settings`).then(result => {
-      this.appModel = result;
-      this.includedPanels = _.where(result.includes, {type: 'panel'});
-      this.includedDatasources = _.where(result.includes, {type: 'datasource'});
-    });
-  }
-
-  update() {
-    var updateCmd = _.extend({
-      appId: this.appModel.appId,
-      orgId: this.appModel.orgId,
-      enabled: this.appModel.enabled,
-      pinned: this.appModel.pinned,
-      jsonData: this.appModel.jsonData,
-      secureJsonData: this.appModel.secureJsonData,
-    }, {});
-
-    this.backendSrv.post(`/api/org/apps/${this.appId}/settings`, updateCmd).then(function() {
-      window.location.href = window.location.href;
-    });
-  }
-
-  toggleEnabled() {
-    this.update();
-  }
-
-  togglePinned() {
-    this.update();
-  }
-}
-
-angular.module('grafana.controllers').controller('AppEditCtrl', AppEditCtrl);
-

+ 0 - 17
public/app/features/apps/list_ctrl.ts

@@ -1,17 +0,0 @@
-///<reference path="../../headers/common.d.ts" />
-
-import angular from 'angular';
-
-export class AppListCtrl {
-  apps: any[];
-
-  /** @ngInject */
-  constructor(private backendSrv: any) {
-
-    this.backendSrv.get('api/org/apps').then(apps => {
-      this.apps = apps;
-    });
-  }
-}
-
-angular.module('grafana.controllers').controller('AppListCtrl', AppListCtrl);

+ 0 - 108
public/app/features/apps/partials/edit.html

@@ -1,108 +0,0 @@
-<navbar title="Plugins" title-url="Plugins" icon="icon-gf icon-gf-apps">
-</navbar>
-
-<div class="page-container">
-	<div class="flex-container">
-		<div class="flex-column app-edit-logo-box">
-			<img src="{{ctrl.appModel.info.logos.large}}">
-		</div>
-		<div class="flex-column">
-			<h1>
-				{{ctrl.appModel.name}}
-			</h1>
-			<div class="app-edit-description">
-				{{ctrl.appModel.info.description}}<br>
-				<span style="small">
-					Version: {{ctrl.appModel.info.version}} &nbsp; &nbsp; Updated: {{ctrl.appModel.info.updated}}
-				</span>
-			</div>
-
-			<div class="form-inline">
-				<editor-checkbox text="Enabled" model="ctrl.appModel.enabled" change="ctrl.toggleEnabled()"></editor-checkbox>
-				&nbsp; &nbsp; &nbsp;
-				<editor-checkbox text="Pinned" model="ctrl.appModel.pinned" change="ctrl.togglePinned()"></editor-checkbox>
-			</div>
-		</div>
-		<div class="flex-column">
-			<ul class="app-edit-links">
-				<li>
-					By <a href="{{ctrl.appModel.info.author.url}}" class="external-link" target="_blank">{{ctrl.appModel.info.author.name}}</a>
-				</li>
-				<li ng-repeat="link in ctrl.appModel.info.links">
-					<a href="{{link.url}}" class="external-link" target="_blank">{{link.name}}</a>
-				</li>
-			</ul>
-		</div>
-	</div>
-
-	<section class="simple-box">
-		<h3 class="simple-box-header">Included with app:</h3>
-		<div class="flex-container">
-			<div class="simple-box-body simple-box-column">
-				<div class="simple-box-column-header">
-					<i class="fa fa-th-large"></i>
-					Dashboards
-				</div>
-				<ul>
-					<li><em class="small">None</em></li>
-				</ul>
-			</div>
-			<div class="simple-box-body simple-box-column">
-				<div class="simple-box-column-header">
-					<i class="fa fa-line-chart"></i>
-					Panels
-				</div>
-				<ul>
-					<li ng-show="!ctrl.includedPanels.length"><em class="small">None</em></li>
-					<li ng-repeat="panel in ctrl.includedPanels">
-						{{panel.name}}
-					</li>
-				</ul>
-			</div>
-			<div class="simple-box-body simple-box-column">
-				<div class="simple-box-column-header">
-					<i class="fa fa-database"></i>
-					Datasources
-				</div>
-				<ul>
-					<li ng-show="!ctrl.includedDatasources.length"><em class="small">None</em></li>
-					<li ng-repeat="ds in ctrl.includedDatasources">
-						{{ds.name}}
-					</li>
-				</ul>
-			</div>
-			<div class="simple-box-body simple-box-column">
-				<div class="simple-box-column-header">
-					<i class="fa fa-files-o"></i>
-					Pages
-				</div>
-				<ul>
-					<li ng-repeat="page in ctrl.appModel.pages">
-						<a href="apps/{{ctrl.appId}}/page/{{page.slug}}" class="external-link">{{page.name}}</a>
-					</li>
-				</ul>
-			</div>
-
-		</div>
-	</section>
-
-	<section class="simple-box">
-		<h3 class="simple-box-header">Dependencies:</h3>
-		<div class="simple-box-body">
-			Grafana 2.6.x
-		</div>
-	</section>
-
-	<section class="simple-box">
-		<h3 class="simple-box-header">Configuration:</h3>
-		<div class="simple-box-body">
-			<div ng-if="ctrl.appModel.appId">
-				<plugin-component type="app-config-ctrl"></plugin-component>
-				<div class="clearfix"></div>
-				<button type="submit" class="btn btn-success" ng-click="ctrl.update()">Save</button>
-			</div>
-		</div>
-	</section>
-
-
-</div>

+ 0 - 47
public/app/features/apps/partials/list.html

@@ -1,47 +0,0 @@
-<navbar title="Plugins" icon="icon-gf icon-gf-apps">
-</navbar>
-
-<div class="page-container">
-  <div class="page-header">
-    <h1>Plugins</h1>
-    </div>
-	<div ng-if="!ctrl.apps">
-		<em>No apps defined</em>
-	</div>
-
-	<ul class="filter-list">
-      <li ng-repeat="app in ctrl.apps">
-        <ul class="filter-list-card">
-					<li class="filter-list-card-image">
-						<img ng-src="{{app.info.logos.small}}">
-					</li>
-          <li>
-            <div class="filter-list-card-controls">
-              <div class="filter-list-card-config">
-								<a href="apps/{{app.appId}}/edit">
-									<i class="fa fa-cog"></i>
-								</a>
-              </div>
-            </div>
-						<span class="filter-list-card-title">
-							<a href="apps/{{app.appId}}/edit">
-								{{app.name}}
-							</a>
-							&nbsp; &nbsp;
-							<span class="label label-info" ng-if="app.enabled">
-								Enabled
-							</span>
-							&nbsp;
-							<span class="label label-info" ng-if="app.pinned">
-								Pinned
-							</span>
-
-						</span>
-            <span class="filter-list-card-status">
-              <span class="filter-list-card-state">Dashboards: 1</span>
-            </span>
-          </li>
-        </ul>
-      </li>
-	</ul>
-</div>

+ 2 - 2
public/app/features/dashboard/dashnav/dashnav.html

@@ -23,12 +23,12 @@
 	<li ng-show="dashboardMeta.canShare" class="dropdown">
 		<a class="pointer" ng-click="hideTooltip($event)" bs-tooltip="'Share dashboard'" data-placement="bottom" data-toggle="dropdown"><i class="fa fa-share-square-o"></i></a>
 		<ul class="dropdown-menu">
-			<li ng-if="dashboardMeta.canEdit">
+			<li>
 				<a class="pointer" ng-click="shareDashboard(0)">
 					<i class="fa fa-link"></i> Link to Dashboard
 				</a>
 			</li>
-			<li ng-if="dashboardMeta.canEdit">
+			<li>
 				<a class="pointer" ng-click="shareDashboard(1)">
 					<i class="icon-gf icon-gf-snapshot"></i>Snapshot sharing
 				</a>

+ 6 - 0
public/app/features/dashboard/dashnav/dashnav.ts

@@ -12,6 +12,8 @@ export class DashNavCtrl {
     $scope.init = function() {
       $scope.onAppEvent('save-dashboard', $scope.saveDashboard);
       $scope.onAppEvent('delete-dashboard', $scope.deleteDashboard);
+      $scope.onAppEvent('export-dashboard', $scope.snapshot);
+      $scope.onAppEvent('quick-snapshot', $scope.quickSnapshot);
 
       $scope.showSettingsMenu = $scope.dashboardMeta.canEdit || $scope.contextSrv.isEditor;
 
@@ -52,6 +54,10 @@ export class DashNavCtrl {
       });
     };
 
+    $scope.quickSnapshot = function() {
+      $scope.shareDashboard(1);
+    };
+
     $scope.openSearch = function() {
       $scope.appEvent('show-dash-search');
     };

+ 8 - 0
public/app/features/dashboard/keybindings.js

@@ -60,6 +60,14 @@ function(angular, $) {
         scope.appEvent('zoom-out', evt);
       }, { inputDisabled: true });
 
+      keyboardManager.bind('ctrl+e', function(evt) {
+        scope.appEvent('export-dashboard', evt);
+      }, { inputDisabled: true });
+
+      keyboardManager.bind('ctrl+i', function(evt) {
+        scope.appEvent('quick-snapshot', evt);
+      }, { inputDisabled: true });
+
       keyboardManager.bind('esc', function() {
         var popups = $('.popover.in');
         if (popups.length > 0) {

+ 16 - 15
public/app/features/dashboard/partials/graphiteImport.html

@@ -1,22 +1,23 @@
 <div ng-controller="GraphiteImportCtrl" ng-init="init()">
 
 	<div ng-if="datasources.length > 0">
-		<h2 style="margin-top: 30px;">Load dashboard from Graphite-Web</h2>
+		<h2 class="page-heading">Load dashboard from Graphite-Web</h2>
 
-		<div class="tight-form last">
-			<ul class="tight-form-list">
-				<li class="tight-form-item" style="width: 150px">
-					<strong>Data source</strong>
-				</li>
-				<li>
-					<select type="text" ng-model="options.sourceName" class="input-medium tight-form-input" ng-options="f for f in datasources">
-					</select>
-				</li>
-				<li style="float: right">
-					<button class="btn btn-inverse tight-form-btn" ng-click="listAll()">List dashboards</button>
-				</li>
-				<div class="clearfix"></div>
-			</ul>
+		<div class="gf-form-group">
+			<div class="gf-form-inline">
+				<div class="gf-form">
+					<span class="gf-form-label width-10">Data source</span>
+				</div>
+				<div class="gf-form">
+					<div class="gf-form-select-wrapper">
+						<select type="text" ng-model="options.sourceName" class="gf-form-input gf-size-auto" ng-options="f for f in datasources">
+						</select>
+					</div>
+				</div>
+				<div class="gf-form">
+					<button class="btn btn-success pull-right" ng-click="listAll()">List dashboards</button>
+				</div>
+			</div>
 		</div>
 
 		<table class="grafana-options-table" style="margin-top: 20px;">

+ 3 - 1
public/app/features/dashboard/partials/import.html

@@ -24,7 +24,9 @@
 		<div class="gf-form">
 			<div class="gf-form-label">Dashboard source</div>
 			<div>
-				<select type="text" ng-model="sourceName" class="input-medium tight-form-input" ng-options="f for f in datasources"></select>
+				<div class="gf-form-select-wrapper">
+					<select class="gf-form-input gf-size-auto" ng-model="sourceName" ng-options="f for f in datasources"></select>
+				</div>
 			</div>
 			<div class="gf-form-btn">
 				<button class="btn btn-success" ng-click="startImport()">Import</button>

+ 0 - 62
public/app/features/dashboard/partials/linksEditor.html

@@ -1,62 +0,0 @@
-<div ng-controller="DashLinksController">
-	<div class="editor-row">
-		<div class="tight-form-section">
-			<h5>Links and Dash Navigation</h5>
-
-			<div ng-repeat="link in panel.links">
-				<div class="tight-form" >
-					<ul class="tight-form-list">
-						<li class="tight-form-item">
-							<i class="fa fa-remove pointer" ng-click="deleteLink(link)"></i>
-						</li>
-
-						<li class="tight-form-item" style="width: 80px;">Link title</li>
-						<li>
-							<input type="text" ng-model="link.title" class="input-medium tight-form-input">
-						</li>
-
-						<li class="tight-form-item">Type</li>
-						<li>
-							<select class="input-medium tight-form-input" style="width: 101px;" ng-model="link.type" ng-options="f for f in ['dashboard','absolute']"></select>
-						</li>
-
-						<li class="tight-form-item" ng-show="link.type === 'dashboard'">Dashboard</li>
-						<li ng-show="link.type === 'dashboard'">
-							<input type="text"
-							ng-model="link.dashboard"
-							bs-typeahead="searchDashboards"
-							class="input-large tight-form-input">
-						</li>
-
-						<li class="tight-form-item" ng-show="link.type === 'absolute'">Url</li>
-						<li ng-show="link.type === 'absolute'">
-							<input type="text" ng-model="link.url" class="input-xlarge tight-form-input">
-						</li>
-					</ul>
-					<div class="clearfix"></div>
-				</div>
-				<div class="tight-form">
-					<ul class="tight-form-list" role="menu">
-						<li class="tight-form-item">
-							<i class="fa fa-remove invisible"></i>
-						</li>
-						<li class="tight-form-item" style="width: 80px;">
-							Params
-							<tip>Use var-variableName=value to pass templating variables.</tip>
-						</li>
-						<li>
-							<input type="text" ng-model="link.params" class="input-xxlarge tight-form-input">
-						</li>
-					</ul>
-					<div class="clearfix"></div>
-				</div>
-			</div>
-
-		</div>
-	</div>
-
-	<div class="editor-row">
-		<br>
-		<button class="btn btn-inverse" ng-click="addLink()"><i class="fa fa-plus"></i> Add link</button>
-	</div>
-</div>

+ 85 - 148
public/app/features/dashboard/partials/settings.html

@@ -1,106 +1,81 @@
-<div class="gf-box-header">
-	<div class="gf-box-title">
-		<i class="fa fa-cogs"></i>
+<div class="tabbed-view-header">
+	<h2 class="tabbed-view-title">
 		Settings
-	</div>
+	</h2>
 
-	<div ng-model="editor.index" bs-tabs style="text-transform:capitalize;">
-		<div ng-repeat="tab in ['General', 'Rows', 'Links', 'Time picker', 'Metadata']" data-title="{{tab}}">
-		</div>
-	</div>
+	<ul class="gf-tabs">
+		<li class="gf-tabs-item" ng-repeat="tab in ::['General', 'Rows', 'Links', 'Time picker', 'Metadata']">
+			<a class="gf-tabs-link" ng-click="editor.index = $index" ng-class="{active: editor.index === $index}">
+				{{::tab}}
+			</a>
+		</li>
+	</ul>
 
-	<button class="gf-box-header-close-btn" ng-click="dismiss();">
+	<button class="tabbed-view-close-btn" ng-click="dismiss();">
 		<i class="fa fa-remove"></i>
 	</button>
 </div>
 
-<div class="gf-box-body" style="padding-bottom: 50px;">
+<div class="tabbed-view-body">
 	<div ng-if="editor.index == 0">
-		<div class="editor-row">
-			<div class="tight-form-section">
-				<h5>Dashboard info</h5>
-				<div class="tight-form">
-					<ul class="tight-form-list">
-						<li class="tight-form-item" style="width: 90px">
-							Title
-						</li>
-						<li>
-							<input type="text" class="input-large tight-form-input" ng-model='dashboard.title'></input>
-						</li>
-						<li class="tight-form-item">
-							Tags
-							<tip>Press enter to a add tag</tip>
-						</li>
-						<li>
-							<bootstrap-tagsinput ng-model="dashboard.tags" tagclass="label label-tag" placeholder="add tags">
-							</bootstrap-tagsinput>
-						</li>
-					</ul>
-					<div class="clearfix"></div>
-				</div>
-				<div class="tight-form last">
-					<ul class="tight-form-list">
-						<li class="tight-form-item" style="width: 90px">
-							Timezone
-						</li>
-						<li>
-							<select ng-model="dashboard.timezone" class='input-small tight-form-input' ng-options="f for f in ['browser','utc']"></select>
-						</li>
-					</ul>
-					<div class="clearfix"></div>
+
+		<h5 class="section-heading">Dashboard Detail</h5>
+		<div class="gf-form-group">
+			<div class="gf-form">
+				<label class="gf-form-label width-7">Title</label>
+				<input type="text" class="gf-form-input max-width-25" ng-model='dashboard.title'></input>
+			</div>
+			<div class="gf-form">
+				<label class="gf-form-label width-7">Tags<tip>Press enter to a add tag</tip></label>
+				<bootstrap-tagsinput ng-model="dashboard.tags" tagclass="label label-tag" placeholder="add tags">
+				</bootstrap-tagsinput>
+			</div>
+
+			<div class="gf-form">
+				<label class="gf-form-label width-7">Timezone</label>
+				<div class="gf-form-select-wrapper">
+					<select ng-model="dashboard.timezone" class='gf-form-input' ng-options="f for f in ['browser','utc']"></select>
 				</div>
 			</div>
 		</div>
 
-		<div class="editor-row">
-			<div class="tight-form-section">
-				<h5>Toggles</h5>
-				<div class="tight-form last">
-					<ul class="tight-form-list">
-						<li class="tight-form-item">
-							<editor-checkbox text="Editable" model="dashboard.editable"></editor-checkbox>
-						</li>
-						<li class="tight-form-item">
-							<editor-checkbox text="Hide Controls (CTRL+H)" model="dashboard.hideControls"></editor-checkbox>
-						</li>
-						<li class="tight-form-item last">
-							<editor-checkbox text="Shared Crosshair (CTRL+O)" model="dashboard.sharedCrosshair"></editor-checkbox>
-						</li>
-					</ul>
-					<div class="clearfix"></div>
+		<h5 class="section-heading">On/Off Toggles</h5>
+		<div class="gf-form-group">
+			<div class="gf-form-inline">
+				<div class="gf-form">
+					<editor-checkbox text="Editable" model="dashboard.editable"></editor-checkbox>
+				</div>
+				<div class="gf-form">
+					<editor-checkbox text="Hide Controls (CTRL+H)" model="dashboard.hideControls"></editor-checkbox>
+				</div>
+				<div class="gf-form">
+					<editor-checkbox text="Shared Crosshair (CTRL+O)" model="dashboard.sharedCrosshair"></editor-checkbox>
 				</div>
 			</div>
 		</div>
 	</div>
 
 	<div ng-if="editor.index == 1">
-		<div class="editor-row">
-			<div class="tight-form-section">
-				<h5>Rows settings</h5>
-				<div class="tight-form-container">
-					<div class="tight-form" ng-repeat="row in dashboard.rows">
-						<ul class="tight-form-list">
-							<li class="tight-form-item">
-								Title
-							</li>
-							<li>
-								<input type="text" class="input tight-form-input" style="width: 400px;" ng-model='row.title'></input>
-							</li>
-							<li class="tight-form-item">
-								<editor-checkbox text="Show title" model="row.showTitle"></editor-checkbox>
-							</li>
-							<li class="tight-form-item last">
-								<i ng-click="_.move(dashboard.rows,$index,$index-1)" ng-class="{'invisible': $first}" class="pointer fa fa-arrow-up"></i>
-							</li>
-							<li class="tight-form-item last">
-								<i ng-click="_.move(dashboard.rows,$index,$index+1)" ng-class="{'invisible': $last}" class="pointer fa fa-fw fa-arrow-down"></i>
-							</li>
-							<li class="tight-form-item last">
-								<i ng-click="dashboard.rows = _.without(dashboard.rows,row)" class="pointer fa fa-remove"></i>
-							</li>
-						</ul>
-						<div class="clearfix"></div>
-					</div>
+		<h5 class="section-heading">Rows settings</h5>
+
+		<div class="gf-form-group">
+			<div class="gf-form-inline" ng-repeat="row in dashboard.rows">
+				<div class="gf-form">
+					<span class="gf-form-label">Title</span>
+					<input type="text" class="gf-form-input max-width-14" ng-model='row.title'></input>
+					<editor-checkbox text="Show title" model="row.showTitle"></editor-checkbox>
+				</div>
+
+				<div class="gf-form">
+					<button class="btn btn-inverse btn-mini" style="margin-right: 5px;" ng-click="dashboard.rows = _.without(dashboard.rows,row)">
+						<i class="fa fa-trash"></i>
+					</button>
+					<button class="btn btn-inverse btn-mini" ng-hide="$first" style="margin-right: 5px;" ng-click="_.move(dashboard.rows,$index,$index-1)">
+						<i ng-class="{'invisible': $first}" class="fa fa-arrow-up"></i>
+					</button>
+					<button class="btn btn-inverse btn-mini" ng-hide="$last" style="margin-right: 5px;" ng-click="_.move(dashboard.rows,$index,$index+1)">
+						<i ng-class="{'invisible': $last}" class="fa fa-arrow-down"></i>
+					</button>
 				</div>
 			</div>
 		</div>
@@ -114,69 +89,31 @@
 		<gf-time-picker-settings dashboard="dashboard"></gf-time-picker-settings>
 	</div>
 
-  <div ng-if="editor.index == 4">
-    <div class="row">
-      <h5>Dashboard info</h5>
-      <div class="pull-left tight-form">
-        <div class="tight-form">
-          <ul class="tight-form-list">
-            <li class="tight-form-item" style="width: 120px">
-              Last updated at:
-            </li>
-            <li class="tight-form-item" style="width: 180px">
-              {{formatDate(dashboardMeta.updated)}}
-            </li>
-          </ul>
-          <div class="clearfix"></div>
-        </div>
-        <div class="tight-form">
-          <ul class="tight-form-list">
-            <li class="tight-form-item" style="width: 120px">
-              Last updated by:
-            </li>
-            <li class="tight-form-item" style="width: 180px">
-              {{dashboardMeta.updatedBy}}
-            </li>
-          </ul>
-          <div class="clearfix"></div>
-        </div> 
-        <div class="tight-form">
-          <ul class="tight-form-list">
-            <li class="tight-form-item" style="width: 120px">
-              Created at:
-            </li>
-            <li class="tight-form-item" style="width: 180px">
-              {{formatDate(dashboardMeta.created)}}
-            </li>
-          </ul>
-          <div class="clearfix"></div>
-        </div>
-        <div class="tight-form">
-          <ul class="tight-form-list">
-            <li class="tight-form-item" style="width: 120px">
-              Created by:
-            </li>
-            <li class="tight-form-item" style="width: 180px">
-              {{dashboardMeta.createdBy}}
-            </li>
-          </ul>
-          <div class="clearfix"></div>
-        </div>
-        <div class="tight-form">
-          <ul class="tight-form-list">
-            <li class="tight-form-item" style="width: 120px">
-              Current version:
-            </li>
-            <li class="tight-form-item" style="width: 180px">
-              {{dashboardMeta.version}}
-            </li>
-          </ul>
-          <div class="clearfix"></div>
-        </div>
-      </div>
-    </div>
-  </div>
-
+	<div ng-if="editor.index == 4">
+		<h5 class="section-heading">Dashboard info</h5>
+		<div class="gf-form-group">
+			<div class="gf-form">
+				<span class="gf-form-label width-10">Last updated at:</span>
+				<span class="gf-form-label width-18">{{formatDate(dashboardMeta.updated)}}</span>
+			</div>
+			<div class="gf-form">
+				<span class="gf-form-label width-10">Last updated by:</span>
+				<span class="gf-form-label width-18">{{dashboardMeta.updatedBy}}&nbsp;</span>
+			</div>
+			<div class="gf-form">
+				<span class="gf-form-label width-10">Created at:</span>
+				<span class="gf-form-label width-18">{{formatDate(dashboardMeta.created)}}&nbsp;</span>
+			</div>
+			<div class="gf-form">
+				<span class="gf-form-label width-10">Created by:</span>
+				<span class="gf-form-label width-18">{{dashboardMeta.createdBy}}&nbsp;</span>
+			</div>
+			<div class="gf-form">
+				<span class="gf-form-label width-10">Current version:</span>
+				<span class="gf-form-label width-18">{{dashboardMeta.version}}&nbsp;</span>
+			</div>
+		</div>
+	</div>
 </div>
 
 <div class="clearfix"></div>

+ 53 - 85
public/app/features/dashboard/partials/shareModal.html

@@ -35,53 +35,30 @@
 
 	<div ng-include src="'shareLinkOptions.html'"></div>
 
-	<div class="gf-form">
-		<div class="gf-form-row">
-			<span class="gf-fluid-input">
-				<textarea rows="5" data-share-panel-url class="input" ng-model='iframeHtml'></textarea>
-			</span>
+	<div class="gf-form-group position-center">
+		<div class="gf-form width-30" >
+			<textarea rows="5" data-share-panel-url class="gf-form-input width-30" ng-model='iframeHtml'></textarea>
+		</div>
+	</div>
+	<div class="gf-form-group">
+		<div class="gf-form position-center">
+			<button class="btn btn-inverse" data-clipboard-text="{{iframeHtml}}" clipboard-button><i class="fa fa-clipboard"></i> Copy</button>
 		</div>
-		<button class="btn btn-inverse" data-clipboard-text="{{iframeHtml}}" clipboard-button><i class="fa fa-clipboard"></i> Copy</button>
 	</div>
 </script>
 
 <script type="text/ng-template" id="shareLinkOptions.html">
-	<div class="editor-row" style="margin: 11px 20px 33px 20px">
-		<div class="section">
-			<div class="tight-form">
-				<ul class="tight-form-list">
-					<li class="tight-form-item" style="width: 170px;">
-						<label class="checkbox-label" for="options.forCurrent">Current time range</label>
-					</li>
-					<li class="tight-form-item last">
-						<input class="cr1" id="options.forCurrent" type="checkbox" ng-model="options.forCurrent" ng-checked="options.forCurrent" ng-change="buildUrl()">
-						<label for="options.forCurrent" class="cr1"></label>
-					</li>
-				</ul>
-				<div class="clearfix"></div>
-			</div>
-			<div class="tight-form">
-				<ul class="tight-form-list">
-					<li class="tight-form-item" style="width: 170px">
-						<label class="checkbox-label" for="options.includeTemplateVars">Include template variables</label>
-					</li>
-					<li class="tight-form-item last">
-						<input class="cr1" id="options.includeTemplateVars" type="checkbox" ng-model="options.includeTemplateVars" ng-checked="options.includeTemplateVars" ng-change="buildUrl()">
-						<label for="options.includeTemplateVars" class="cr1"></label>
-					</li>
-				</ul>
-				<div class="clearfix"></div>
-			</div>
-			<div class="tight-form">
-				<ul class="tight-form-list">
-					<li class="tight-form-item" style="width: 170px">
-						Theme
-					</li>
-					<li>
-						<select class="input-small tight-form-input last" style="width: 211px" ng-model="options.theme" ng-options="f as f for f in ['current', 'dark', 'light']" ng-change="buildUrl()"></select>
-					</li>
-				</ul>
-				<div class="clearfix"></div>
+	<div class="gf-form-group position-center">
+		<div class="gf-form">
+			<editor-checkbox text="Current time range" model="options.forCurrent" change="updated()"></editor-checkbox>
+		</div>
+		<div class="gf-form">
+			<editor-checkbox text="Include template variables" model="options.includeTemplateVars" change="updated()"></editor-checkbox>
+		</div>
+		<div class="gf-form">
+			<span class="gf-form-label">Theme</span>
+			<div class="gf-form-select-wrapper max-width-10">
+				<select class="gf-form-input" ng-model="options.theme" ng-options="f as f for f in ['current', 'dark', 'light']" ng-change="buildUrl()"></select>
 			</div>
 		</div>
 	</div>
@@ -93,14 +70,19 @@
 	</div>
 
 	<div ng-include src="'shareLinkOptions.html'"></div>
-	<div class="gf-form">
-		<div class="gf-form-row">
-			<button class="btn btn-inverse pull-right" data-clipboard-text="{{shareUrl}}" clipboard-button><i class="fa fa-clipboard"></i> Copy</button>
-			<span class="gf-fluid-input">
-				<input type="text" data-share-panel-url class="input" ng-model='shareUrl'></input>
-			</span>
+	<div class="gf-form-group position-center">
+		<div class="gf-form-inline">
+
+			<div class="gf-form width-30">
+				<input type="text" data-share-panel-url class="gf-form-input" ng-model="shareUrl"></input>
+			</div>
+			<div class="gf-form pull-right">
+				<button class="btn btn-inverse pull-right" data-clipboard-text="{{shareUrl}}" clipboard-button><i class="fa fa-clipboard"></i> Copy</button>
+			</div>
 		</div>
-		<div class="editor-row" style="margin-top: 5px;" ng-show="modeSharePanel">
+	</div>
+	<div class="gf-form-group">
+		<div class="gf-form position-center" ng-show="modeSharePanel">
 			<a href="{{imageUrl}}" target="_blank"><i class="fa fa-camera"></i> Direct link rendered image</a>
 		</div>
 	</div>
@@ -132,29 +114,15 @@
 			</p>
 		</div>
 
-		<div class="editor-row share-modal-options" style="">
-			<div class="section" ng-if="step === 1">
-				<div class="tight-form">
-					<ul class="tight-form-list">
-						<li class="tight-form-item" style="width: 110px;">
-							Snapshot name
-						</li>
-						<li>
-							<input type="text" ng-model="snapshot.name" class="input-large tight-form-input last" >
-						</li>
-					</ul>
-					<div class="clearfix"></div>
-				</div>
-				<div class="tight-form">
-					<ul class="tight-form-list">
-						<li class="tight-form-item" style="width: 110px">
-							Expire
-						</li>
-						<li>
-							<select class="input-small tight-form-input last" style="width: 211px" ng-model="snapshot.expires" ng-options="f.value as f.text for f in expireOptions"></select>
-						</li>
-					</ul>
-					<div class="clearfix"></div>
+		<div class="gf-form-group share-modal-options position-center">
+			<div class="gf-form" ng-if="step === 1">
+				<span class="gf-form-label width-12">Snapshot name</span>
+				<input type="text" ng-model="snapshot.name" class="gf-form-input max-width-15" >
+			</div>
+			<div class="gf-form" ng-if="step === 1">
+				<span class="gf-form-label width-12">Expire</span>
+				<div class="gf-form-select-wrapper max-width-15">
+					<select class="gf-form-input" ng-model="snapshot.expires" ng-options="f.value as f.text for f in expireOptions"></select>
 				</div>
 			</div>
 
@@ -168,21 +136,21 @@
 					<button class="btn btn-inverse btn-large" data-clipboard-text="{{snapshotUrl}}" clipboard-button><i class="fa fa-clipboard"></i> Copy Link</button>
 				</div>
 			</div>
-		</div>
 
-		<div ng-if="step === 1">
-			<button class="btn btn-success btn-large" ng-click="createSnapshot()" ng-disabled="loading">
-				<i class="fa fa-save"></i>
-				Local Snapshot
-			</button>
-			<button class="btn btn-primary btn-large" ng-if="externalEnabled" ng-click="createSnapshot(true)" ng-disabled="loading">
-				<i class="fa fa-cloud-upload"></i>
-				{{sharingButtonText}}
-			</button>
-		</div>
+			<div ng-if="step === 1" class="gf-form-buttons-row">
+				<button class="btn btn-success btn-large" ng-click="createSnapshot()" ng-disabled="loading">
+					<i class="fa fa-save"></i>
+					Local Snapshot
+				</button>
+				<button class="btn btn-primary btn-large" ng-if="externalEnabled" ng-click="createSnapshot(true)" ng-disabled="loading">
+					<i class="fa fa-cloud-upload"></i>
+					{{sharingButtonText}}
+				</button>
+			</div>
 
-		<div class="pull-right" ng-if="step === 2" style="padding: 5px">
-			Did you make a mistake? <a class="pointer" ng-click="deleteSnapshot()" target="_blank">delete snapshot.</a>
+			<div class="pull-right" ng-if="step === 2" style="padding: 5px">
+				Did you make a mistake? <a class="pointer" ng-click="deleteSnapshot()" target="_blank">delete snapshot.</a>
+			</div>
 		</div>
 
 	</div>

+ 22 - 25
public/app/features/dashboard/submenu/submenu.html

@@ -1,30 +1,27 @@
 <div class="submenu-controls">
-	<div class="tight-form borderless">
+	<ul ng-if="ctrl.dashboard.templating.list.length > 0">
+		<li ng-repeat="variable in ctrl.variables" class="submenu-item">
+			<span class="submenu-item-label template-variable " ng-show="!variable.hideLabel">
+				{{variable.label || variable.name}}:
+			</span>
+			<value-select-dropdown variable="variable" on-updated="ctrl.variableUpdated(variable)" get-values-for-tag="ctrl.getValuesForTag(variable, tagKey)"></value-select-dropdown>
+		</li>
+	</ul>
 
-		<ul class="tight-form-list" ng-if="ctrl.dashboard.templating.list.length > 0">
-			<li ng-repeat="variable in ctrl.variables" class="submenu-item">
-				<span class="template-variable tight-form-item" ng-show="!variable.hideLabel" style="padding-right: 5px">
-					{{variable.label || variable.name}}:
-				</span>
-				<value-select-dropdown variable="variable" on-updated="ctrl.variableUpdated(variable)" get-values-for-tag="ctrl.getValuesForTag(variable, tagKey)"></value-select-dropdown>
-			</li>
-		</ul>
+	<ul ng-if="ctrl.dashboard.annotations.list.length > 0">
+		<li ng-repeat="annotation in ctrl.dashboard.annotations.list" class="submenu-item annotation-segment" ng-class="{'annotation-disabled': !annotation.enable}">
+			<a ng-click="ctrl.disableAnnotation(annotation)">
+				<i class="fa fa-bolt" style="color:{{annotation.iconColor}}"></i>
+				{{annotation.name}}
+				<input class="cr1" id="hideYAxis" type="checkbox" ng-model="annotation.enable" ng-checked="annotation.enable">
+				<label for="hideYAxis" class="cr1"></label>
+			</a>
+		</li>
+	</ul>
 
-		<ul class="tight-form-list" ng-if="ctrl.dashboard.annotations.list.length > 0">
-			<li ng-repeat="annotation in ctrl.dashboard.annotations.list" class="submenu-item annotation-segment" ng-class="{'annotation-disabled': !annotation.enable}">
-				<a ng-click="ctrl.disableAnnotation(annotation)">
-					<i class="fa fa-bolt" style="color:{{annotation.iconColor}}"></i>
-					{{annotation.name}}
-					<input class="cr1" id="hideYAxis" type="checkbox" ng-model="annotation.enable" ng-checked="annotation.enable">
-					<label for="hideYAxis" class="cr1"></label>
-				</a>
-			</li>
-		</ul>
+	<ul class="pull-right" ng-if="ctrl.dashboard.links.length > 0">
+		<dash-links-container links="ctrl.dashboard.links"></dash-links-container>
+	</ul>
 
-		<ul class="tight-form-list pull-right" ng-if="ctrl.dashboard.links.length > 0">
-			<dash-links-container links="ctrl.dashboard.links"></dash-links-container>
-		</ul>
-
-		<div class="clearfix"></div>
-	</div>
+	<div class="clearfix"></div>
 </div>

+ 12 - 45
public/app/features/dashboard/timepicker/settings.html

@@ -1,48 +1,15 @@
 <div class="editor-row">
-	<div class="section">
-		<div>
-			<!-- <div class="tight&#45;form"> -->
-			<!-- 	<ul class="tight&#45;form&#45;list"> -->
-			<!-- 		<li class="tight&#45;form&#45;item" style="width: 118px"> -->
-			<!-- 			Relative times -->
-			<!-- 		</li> -->
-			<!-- 		<li> -->
-			<!-- 			<input type="text" class="input&#45;xlarge tight&#45;form&#45;input last" style="width: 450px" ng&#45;model="ctrl.panel.time_options" array&#45;join> -->
-			<!-- 		</li> -->
-			<!-- 	</ul> -->
-			<!-- 	<div class="clearfix"></div> -->
-			<!-- </div> -->
-			<div class="tight-form">
-				<ul class="tight-form-list">
-					<li class="tight-form-item" style="width: 118px">
-						Auto-refresh
-					</li>
-					<li>
-						<input type="text" class="input-xlarge tight-form-input last" style="width: 450px" ng-model="ctrl.panel.refresh_intervals" array-join>
-					</li>
-				</ul>
-				<div class="clearfix"></div>
-			</div>
-
-			<div class="tight-form last">
-				<ul class="tight-form-list">
-					<li class="tight-form-item" style="width: 118px">
-						Now delay
-					</li>
-					<li class="tight-form-item">
-						now-
-					</li>
-					<li>
-						<input type="text" class="input-mini tight-form-input last"
-						ng-model="ctrl.panel.nowDelay" placeholder="0m"
-						valid-time-span
-						bs-tooltip="'Enter 1m to ignore the last minute (because it can contain incomplete metrics)'"
-						data-placement="right">
-					</li>
-
-				</ul>
-				<div class="clearfix"></div>
-			</div>
-		</div>
+<div class="gf-form-group">
+	<div class="gf-form">
+		<span class="gf-form-label width-10">Auto-refresh</span>
+		<input type="text" class="gf-form-input max-width-25" ng-model="ctrl.panel.refresh_intervals" array-join>
+	</div>
+	<div class="gf-form">
+		<span class="gf-form-label width-10">Now delay now-</span>
+		<input type="text" class="gf-form-input max-width-25"
+				ng-model="ctrl.panel.nowDelay" placeholder="0m"
+				valid-time-span
+				bs-tooltip="'Enter 1m to ignore the last minute (because it can contain incomplete metrics)'"
+				data-placement="right">
 	</div>
 </div>

+ 0 - 2
public/app/features/dashboard/timepicker/timepicker.ts

@@ -129,8 +129,6 @@ export class TimePickerCtrl {
   }
 
   setRelativeFilter(timespan) {
-    this.panel.now = true;
-
     var range = {from: timespan.from, to: timespan.to};
 
     if (this.panel.nowDelay && range.to === 'now') {

+ 65 - 79
public/app/features/dashlinks/editor.html

@@ -1,90 +1,76 @@
 <div class="editor-row">
-	<h5>Links and Dash Navigation</h5>
+	<h5 class="section-heading">Links and Dash Navigation</h5>
 
-	<div ng-repeat="link in dashboard.links" style="margin-top: 10px;">
-		<div class="tight-form">
-			<ul class="tight-form-list pull-right">
-				<li class="tight-form-item">
-					<i ng-click="moveLink($index, -1)" ng-hide="$first" class="pointer fa fa-arrow-up"></i>
-					<i ng-click="moveLink($index, 1)" ng-hide="$last" class="pointer fa fa-fw fa-arrow-down"></i>
-				</li>
-				<li class="tight-form-item last">
-					<i class="fa fa-remove pointer" ng-click="deleteLink($index)"></i>
-				</li>
-			</ul>
+	<div ng-repeat="link in dashboard.links">
 
-			<ul class="tight-form-list">
-				<li class="tight-form-item" style="width: 20px">
-					<i class="fa fa-fw fa-unlink"></i>
-				</li>
+		<div class="gf-form-group">
+			<div class="gf-form-inline">
+				<div class="gf-form">
+					<span class="gf-form-label width-6">Type</span>
+					<div class="gf-form-select-wrapper width-10">
+						<select class="gf-form-input" ng-model="link.type" ng-options="f for f in ['dashboards','link']" ng-change="updated()"></select>
+					</div>
+				</div>
 
-				<li class="tight-form-item">Type</li>
-				<li>
-					<select class="input-medium tight-form-input" style="width: 150px;" ng-model="link.type" ng-options="f for f in ['dashboards','link']" ng-change="updated()"></select>
-				</li>
+				<div class="gf-form" ng-show="link.type === 'dashboards'">
+					<span class="gf-form-label">With tags</span>
+					<bootstrap-tagsinput ng-model="link.tags" tagclass="label label-tag" placeholder="add tags"></bootstrap-tagsinput>
+				</div>
 
-				<li class="tight-form-item" ng-show="link.type === 'dashboards'">With tags</li>
-				<li ng-show="link.type === 'dashboards'">
-					<bootstrap-tagsinput ng-model="link.tags" tagclass="label label-tag" placeholder="add tags">
-					</bootstrap-tagsinput>
-				</li>
-				<li class="tight-form-item" ng-show="link.type === 'dashboards'">
+				<div class="gf-form" ng-show="link.type === 'dashboards'">
 					<editor-checkbox text="As dropdown" model="link.asDropdown" change="updated()"></editor-checkbox>
-				</li>
-				<li class="tight-form-item" ng-show="link.type === 'dashboards' && link.asDropdown">
-					Title
-				</li>
-				<li ng-show="link.type === 'dashboards' && link.asDropdown">
-					<input type="text" ng-model="link.title" class="input-medium tight-form-input" ng-model-onblur ng-change="updated()">
-				</li>
-				<li class="tight-form-item" ng-show="link.type === 'link'" style="width: 51px">Url</li>
-				<li ng-show="link.type === 'link'">
-					<input type="text" ng-model="link.url" class="input-xlarge tight-form-input" style="width: 302px;" ng-model-onblur ng-change="updated()">
-				</li>
-				<li class="tight-form-item" ng-show="link.type === 'link'">
+				</div>
+
+				<div class="gf-form max-width-30" ng-show="link.type === 'link'">
+					<li class="gf-form-label width-6">Url</li>
+					<input type="text" ng-model="link.url" class="gf-form-input" ng-model-onblur ng-change="updated()">
+				</div>
+
+				<div class="gf-form">
+					<button class="btn btn-inverse btn-mini" ng-click="moveLink($index, -1)" ng-hide="$first"><i class="fa fa-arrow-up"></i></button>
+				</div>
+				<div class="gf-form">
+					<button class="btn btn-inverse btn-mini" ng-click="moveLink($index, 1)" ng-hide="$last"><i class="fa fa-arrow-down"></i></button>
+				</div>
+				<div class="gf-form">
+					<button class="btn btn-inverse btn-mini" ng-click="deleteLink($index)"><i class="fa fa-trash" ></i></button>
+				</div>
+			</div>
+
+			<div class="gf-form" ng-show="link.type === 'dashboards' && link.asDropdown">
+				<span class="gf-form-label width-6">Title</span>
+				<input type="text" ng-model="link.title" class="gf-form-input max-width-25" ng-model-onblur ng-change="updated()">
+			</div>
+
+			<div class="gf-form-inline" ng-show="link.type === 'link'">
+				<div class="gf-form">
+					<span class="gf-form-label width-6">Title</span>
+					<input type="text" ng-model="link.title" class="gf-form-input max-width-10" ng-model-onblur ng-change="updated()">
+				</div>
+
+				<div class="gf-form">
+					<span class="gf-form-label width-6">Tooltip</span>
+					<input type="text" ng-model="link.tooltip" class="gf-form-input max-width-10" placeholder="Open dashboard" ng-model-onblur ng-change="updated()">
+				</div>
+
+				<div class="gf-form">
+					<span class="gf-form-label width-6">Icon</span>
+					<div class="gf-form-select-wrapper max-width-10">
+						<select class="gf-form-input" ng-model="link.icon" ng-options="k as k for (k, v) in iconMap" ng-change="updated()"></select>
+					</div>
+				</div>
+			</div>
+
+			<div class="gf-form-inline">
+				<div class="gf-form">
+					<span class="gf-form-label width-6">Include</span>
+					<editor-checkbox text="Time range" model="link.keepTime" change="updated()"></editor-checkbox>
+					<editor-checkbox text="Variable values" model="link.includeVars" change="updated()"></editor-checkbox>
 					<editor-checkbox text="Open in new tab " model="link.targetBlank" change="updated()"></editor-checkbox>
-				</li>
-			</ul>
-			<div class="clearfix"></div>
-		</div>
-		<div class="tight-form" ng-if="link.type === 'link'">
-			<ul class="tight-form-list">
-				<li class="tight-form-item" style="width: 20px">
-					<i class="fa fa-fw fa-unlink invisible"></i>
-				</li>
-				<li class="tight-form-item" ng-show="link.type === 'link'" style="width: 31px">Title</li>
-				<li ng-show="link.type === 'link'">
-					<input type="text" ng-model="link.title" class="input-medium tight-form-input" ng-model-onblur ng-change="updated()">
-				</li>
-				<li class="tight-form-item" ng-show="link.type === 'link'" style="width: 51px">Tooltip</li>
-				<li ng-show="link.type === 'link'">
-					<input type="text" ng-model="link.tooltip" class="input-medium tight-form-input" style="width: 151px" placeholder="Open dashboard" ng-model-onblur ng-change="updated()">
-				</li>
-				<li class="tight-form-item" ng-show="link.type === 'link'">Icon</li>
-				<li ng-show="link.type === 'link'">
-					<select class="input-medium tight-form-input" style="width: 110px;" ng-model="link.icon" ng-options="k as k for (k, v) in iconMap" ng-change="updated()"></select>
-				</li>
-			</ul>
-			<div class="clearfix"></div>
-		</div>
-		<div class="tight-form last">
-			<ul class="tight-form-list">
-				<li class="tight-form-item" style="width: 20px">
-					<i class="fa fa-fw fa-unlink invisible"></i>
-				</li>
-				<li class="tight-form-item">
-					<editor-checkbox text="Keep current time range" model="link.keepTime" change="updated()"></editor-checkbox>
-				</li>
-				<li class="tight-form-item">
-					<editor-checkbox text="Add current variable values" model="link.includeVars" change="updated()"></editor-checkbox>
-				</li>
-			</ul>
-			<div class="clearfix"></div>
+				</div>
+			</div>
 		</div>
 	</div>
 </div>
-<div class="editor-row">
-	<br>
-	<button class="btn btn-inverse" ng-click="addLink()"><i class="fa fa-plus"></i> Add link</button>
-</div>
 
+<button class="btn btn-inverse" ng-click="addLink()"><i class="fa fa-plus"></i> Add link</button>

+ 7 - 0
public/app/features/datasources/partials/edit.html

@@ -15,6 +15,12 @@
 			<div class="gf-form">
 				<span class="gf-form-label width-7">Name</span>
 				<input class="gf-form-input max-width-21" type="text" ng-model="current.name" placeholder="My data source name" required>
+				<gf-popover offset="0px -95px">
+					The name is used when you select the data source in panels.
+					The <code>Default</code> data source is preselected in new
+					panels.
+				</gf-popover>
+
 				<editor-checkbox text="Default" model="current.isDefault"></editor-checkbox>
 			</div>
 
@@ -24,6 +30,7 @@
 					<select class="gf-form-input gf-size-auto" ng-model="current.type" ng-options="k as v.name for (k, v) in types" ng-change="typeChanged()"></select>
 				</div>
 			</div>
+
 		</div>
 
 		<rebuild-on-change property="datasourceMeta.id">

+ 15 - 1
public/app/features/datasources/partials/http_settings.html

@@ -6,13 +6,27 @@
 	<div class="gf-form">
 		<span class="gf-form-label width-7">Url</span>
 		<input class="gf-form-input max-width-21" type="text" ng-model='current.url' placeholder="http://my.server.com:8080" ng-pattern="/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/" required></input>
+
+		<gf-popover>
+			<p>Specify a complete HTTP url (http://your_server:8080)</p>
+			<span ng-show="current.access === 'direct'">
+				Your access method is <code>Direct</code>, this means the url
+				needs to be accessable from the browser.
+			</span>
+			<span ng-show="current.access === 'proxy'">
+				Your access method is currently <code>Proxy</code>, this means the url
+				needs to be accessable from the grafana backend.
+			</span>
+		</gf-popover>
 	</div>
 
 	<div class="gf-form">
 		<span class="gf-form-label width-7">
 			Access <tip>Direct = url is used directly from browser, Proxy = Grafana backend will proxy the request</tip>
 		</span>
-		<select class="gf-form-input gf-size-auto" ng-model="current.access" ng-options="f for f in ['direct', 'proxy']"></select>
+		<div class="gf-form-select-wrapper">
+			<select class="gf-form-input gf-size-auto" ng-model="current.access" ng-options="f for f in ['direct', 'proxy']"></select>
+		</div>
 	</div>
 
 	<div class="gf-form">

+ 2 - 2
public/app/features/datasources/partials/list.html

@@ -22,8 +22,8 @@
 		<table class="filter-table" ng-if="ctrl.datasources.length > 0">
 			<thead>
 				<tr>
-					<th><strong>Name</strong></th>
-					<th><strong>Url</strong></th>
+					<th><strong>name</strong></th>
+					<th><strong>url</strong></th>
 					<th style="width: 60px;"></th>
 					<th style="width: 85px;"></th>
 					<th style="width: 44px;"></th>

+ 7 - 14
public/app/features/org/partials/apikeyModal.html

@@ -10,23 +10,16 @@
 		</button>
 	</div>
 
-	<div class="gf-box-body" style="min-height: 0px;">
+	<div class="gf-box-body">
 
-		<div class="tight-form last">
-			<ul class="tight-form-list">
-				<li class="tight-form-item">
-					<strong>Key</strong>
-				</li>
-				<li class="tight-form-item last">
-					{{key}}
-				</li>
-			</ul>
-			<div class="clearfix"></div>
+		<div class="gf-form-group">
+			<div class="gf-form">
+				<span class="gf-form-label">Key</span>
+				<span class="gf-form-label">{{key}}</span>
+			</div>
 		</div>
-		<br>
-		<br>
 
-		<div class="grafana-info-box" style="text-align: left">
+		<div class="grafana-info-box" style="border: 0;">
 			You will only be able to view this key here once! It is not stored in this form. So be sure to copy it now.
 			<br>
 			<br>

+ 7 - 15
public/app/features/org/partials/newOrg.html

@@ -6,24 +6,16 @@
 
 		<h2 style="margin-top: 30px;">Add Organization</h2>
 
-		<form name="form">
-			<div class="tight-form last">
-				<ul class="tight-form-list">
-					<li class="tight-form-item" style="width: 100px;">
-						<strong>Org. name</strong>
-					</li>
-					<li>
-						<input type="text" ng-model="newOrg.name" required class="input-xxlarge tight-form-input last" placeholder="organization name">
-					</li>
-					<li>
-					</li>
-				</ul>
-				<div class="clearfix"></div>
+		<form name="form" class="gf-form-group">
+			<div class="gf-form">
+				<span class="gf-form-label width-10">Org. name</span>
+				<input type="text" ng-model="newOrg.name" required class="gf-form-input" placeholder="organization name">
 			</div>
 			<br>
-			<button class="btn btn-success pull-right" ng-click="createOrg()">Create</button>
+			<div class="gf-form-buttons-row">
+				<button class="btn btn-success pull-right" ng-click="createOrg()">Create</button>
+			</div>
 		</form>
-
 	</div>
 </div>
 

+ 1 - 1
public/app/features/org/partials/orgDetails.html

@@ -1,4 +1,4 @@
-<navbar icon="icon-gf icon-gf-users" title="Organization">
+<navbar icon="icon-gf icon-gf-users" title="Organization" title-url="org">
 </navbar>
 
 <div class="page-container">

+ 1 - 1
public/app/features/org/partials/orgUsers.html

@@ -1,4 +1,4 @@
-<navbar icon="icon-gf icon-gf-users" title="Organization Users" title-url="org">
+<navbar icon="icon-gf icon-gf-users" title="Organization Users" title-url="org/users">
 </navbar>
 
 <div class="page-container">

+ 16 - 13
public/app/features/panel/panel_directive.ts

@@ -28,24 +28,27 @@ var panelTemplate = `
   </div>
 
   <div class="panel-full-edit" ng-if="ctrl.editMode">
-    <div class="gf-box">
-      <div class="gf-box-header">
-        <div class="gf-box-title">
+    <div class="tabbed-view tabbed-view--panel-edit">
+      <div class="tabbed-view-header">
+        <h2 class="tabbed-view-title">
           <i ng-class="ctrl.icon"></i>
           {{ctrl.pluginName}}
-        </div>
-
-        <div ng-model="ctrl.editorTabIndex" bs-tabs>
-          <div ng-repeat="tab in ctrl.editorTabs" data-title="{{tab.title}}">
-          </div>
-        </div>
-
-        <button class="gf-box-header-close-btn" ng-click="ctrl.exitFullscreen();">
-          Back to dashboard
+        </h2>
+
+        <ul class="gf-tabs">
+          <li class="gf-tabs-item" ng-repeat="tab in ::ctrl.editorTabs">
+            <a class="gf-tabs-link" ng-click="ctrl.editorTabIndex = $index" ng-class="{active: ctrl.editorTabIndex === $index}">
+              {{::tab.title}}
+            </a>
+          </li>
+        </ul>
+
+        <button class="tabbed-view-close-btn" ng-click="ctrl.exitFullscreen();">
+          <i class="fa fa-remove"></i>
         </button>
       </div>
 
-      <div class="gf-box-body">
+      <div class="tabbed-view-body">
         <div ng-repeat="tab in ctrl.editorTabs" ng-if="ctrl.editorTabIndex === $index">
           <panel-editor-tab editor-tab="tab" ctrl="ctrl" index="$index"></panel-editor-tab>
         </div>

+ 31 - 56
public/app/features/panel/partials/panelTime.html

@@ -1,59 +1,34 @@
-<div class="editor-row">
-	<div class="section tight-form-container" style="margin-bottom: 20px">
-		<div class="tight-form">
-			<ul class="tight-form-list">
-				<li class="tight-form-item tight-form-item-icon">
-					<i class="fa fa-clock-o"></i>
-				</li>
-				<li class="tight-form-item" style="width: 178px">
-					<strong>Override relative time</strong>
-				</li>
-				<li class="tight-form-item" style="width: 50px">
-					Last
-				</li>
-				<li>
-					<input type="text" class="input-small tight-form-input last" placeholder="1h"
-					  empty-to-null ng-model="ctrl.panel.timeFrom" valid-time-span
-					  ng-change="ctrl.refresh()" ng-model-onblur>
-				</li>
-			</ul>
-			<div class="clearfix"></div>
-		</div>
-		<div class="tight-form">
-			<ul class="tight-form-list">
-				<li class="tight-form-item tight-form-item-icon">
-					<i class="fa fa-clock-o"></i>
-				</li>
-				<li class="tight-form-item" style="width: 178px">
-					<strong>Add time shift</strong>
-				</li>
-				<li class="tight-form-item" style="width: 50px">
-					Amount
-				</li>
-				<li>
-					<input type="text" class="input-small tight-form-input last" placeholder="1h"
-					empty-to-null ng-model="ctrl.panel.timeShift" valid-time-span
-					ng-change="ctrl.refresh()" ng-model-onblur>
-				</li>
-			</ul>
-			<div class="clearfix"></div>
-		</div>
-		<div class="tight-form">
-			<ul class="tight-form-list">
-				<li class="tight-form-item tight-form-item-icon">
-					<i class="fa fa-clock-o"></i>
-				</li>
-				<li class="tight-form-item" style="width: 178px">
-					<strong>Hide time override info</strong>
-				</li>
-				<li class="tight-form-item last">
-					<input class="cr1" id="ctrl.panel.hideTimeOverride" type="checkbox"
-					ng-model="ctrl.panel.hideTimeOverride" ng-checked="ctrl.panel.hideTimeOverride" ng-change="ctrl.refresh()">
-					<label for="ctrl.panel.hideTimeOverride" class="cr1"></label>
-				</li>
-			</ul>
-			<div class="clearfix"></div>
-		</div>
+<div class="gf-form-group">
+	<div class="gf-form">
+		<span class="gf-form-label">
+			<i class="fa fa-clock-o"></i>
+		</span>
+
+		<span class="gf-form-label width-12">Override relative time</span>
+		<span class="gf-form-label width-6">Last</span>
+
+		<input type="text" class="gf-form-input max-width-8" placeholder="1h"
+			empty-to-null ng-model="ctrl.panel.timeFrom" valid-time-span
+			ng-change="ctrl.refresh()" ng-model-onblur>
+	</div>
+
+	<div class="gf-form">
+		<span class="gf-form-label">
+			<i class="fa fa-clock-o"></i>
+		</span>
+		<span class="gf-form-label width-12">Add time shift</span>
+		<span class="gf-form-label width-6">Amount</span>
+		<input type="text" class="gf-form-input max-width-8" placeholder="1h"
+			empty-to-null ng-model="ctrl.panel.timeShift" valid-time-span
+			ng-change="ctrl.refresh()" ng-model-onblur>
+	</div>
+
+	<div class="gf-form">
+		<span class="gf-form-label">
+			<i class="fa fa-clock-o"></i>
+		</span>
+		<editor-checkbox text="Hide time override info" model="ctrl.panel.hideTimeOverride" change="ctrl.refresh()"></editor-checkbox>
 	</div>
 </div>
 
+

+ 49 - 66
public/app/features/panellinks/module.html

@@ -1,84 +1,67 @@
 <div class="editor-row">
-  <div class="section">
-		<h5>Drilldown / detail link<tip>These links appear in the dropdown menu in the panel menu. </tip></h5>
+	<h5 class="section-heading">
+		Drilldown / detail link<tip>These links appear in the dropdown menu in the panel menu. </tip></h5>
+	</h5>
 
-		<div ng-repeat="link in panel.links" style="margin-top: 20px;">
-			<div class="tight-form">
-				<ul class="tight-form-list pull-right">
-					<li class="tight-form-item">
-						<i ng-click="moveLink($index, -1)" ng-hide="$first" class="pointer fa fa-arrow-up"></i>
-						<i ng-click="moveLink($index, 1)" ng-hide="$last" class="pointer fa fa-fw fa-arrow-down"></i>
-					</li>
-					<li class="tight-form-item last">
-						<i class="fa fa-remove pointer" ng-click="deleteLink(link)"></i>
-					</li>
-				</ul>
+	<div ng-repeat="link in panel.links" style="margin-top: 20px;">
+		<div class="gf-form-group">
 
-				<ul class="tight-form-list">
-					<li class="tight-form-item" style="width: 20px">
-						<i class="fa fa-fw fa-unlink"></i>
-					</li>
+			<div class="gf-form-inline">
+				<div class="gf-form width-2">
+					<i class="fa fa-fw fa-unlink"></i>
+				</div>
 
-					<li class="tight-form-item">Type</li>
-					<li>
-						<select class="input-medium tight-form-input" style="width: 150px;" ng-model="link.type" ng-options="f for f in ['dashboard','absolute']"></select>
-					</li>
+				<div class="gf-form">
+					<span class="gf-form-label width-7">Type</span>
+					<div class="gf-form-select-wrapper width-14">
+						<select class="gf-form-input" ng-model="link.type" ng-options="f for f in ['dashboard','absolute']"></select>
+					</div>
+				</div>
 
-					<li class="tight-form-item" ng-show="link.type === 'dashboard'" style="width: 73px;">Dashboard</li>
-					<li ng-show="link.type === 'dashboard'">
-						<input type="text" ng-model="link.dashboard" bs-typeahead="searchDashboards" class="input-large tight-form-input" ng-blur="dashboardChanged(link)">
-					</li>
+				<div class="gf-form">
+					<span class="gf-form-label width-7" ng-show="link.type === 'dashboard'">Dashboard</span>
+					<input ng-show="link.type === 'dashboard'" type="text" ng-model="link.dashboard" bs-typeahead="searchDashboards" class="gf-form-input max-width-14" ng-blur="dashboardChanged(link)">
 
-					<li class="tight-form-item" ng-show="link.type === 'absolute'" style="width: 73px;">Url</li>
-					<li ng-show="link.type === 'absolute'">
-						<input type="text" ng-model="link.url" class="input-large tight-form-input">
-					</li>
+					<span class="gf-form-label width-7" ng-show="link.type === 'absolute'">Url</span>
+					<input ng-show="link.type === 'absolute'" type="text" ng-model="link.url" class="gf-form-input max-width-14">
+				</div>
 
-				</ul>
-				<div class="clearfix"></div>
+				<div class="gf-form">
+					<button class="btn-inverse gf-form-btn btn-small" ng-click="deleteLink(link)"><i class="fa fa-trash"></i></button>
+				</div>
 			</div>
 
-			<div class="tight-form">
-				<ul class="tight-form-list">
-					<li class="tight-form-item" style="width: 20px">
-						<i class="fa fa-fw fa-unlink invisible"></i>
-					</li>
-					<li class="tight-form-item" style="width: 31px">Title</li>
-					<li>
-						<input type="text" ng-model="link.title" class="input-medium tight-form-input">
-					</li>
-					<li class="tight-form-item" style="width: 73px;">
-						Url params
-					</li>
-					<li>
-						<input type="text" ng-model="link.params" class="input-large tight-form-input">
-					</li>
-				</ul>
-				<div class="clearfix"></div>
+			<div class="gf-form-inline">
+				<div class="gf-form width-2">
+					<i class="fa fa-fw fa-unlink invisible"></i>
+				</div>
+
+				<div class="gf-form">
+					<div class="gf-form-label width-7">Title</div>
+					<input type="text" ng-model="link.title" class="gf-form-input">
+				</div>
+
+				<div class="gf-form">
+					<span class="gf-form-label width-7">Url params</span>
+					<input type="text" ng-model="link.params" class="gf-form-input">
+				</div>
 			</div>
-			<div class="tight-form last">
-				<ul class="tight-form-list">
-					<li class="tight-form-item" style="width: 20px">
-						<i class="fa fa-fw fa-unlink invisible"></i>
-					</li>
-					<li class="tight-form-item">
-						<editor-checkbox text="Keep current time range" model="link.keepTime"></editor-checkbox>
-					</li>
-					<li class="tight-form-item">
-						<editor-checkbox text="Add current variable values" model="link.includeVars"></editor-checkbox>
-					</li>
-					<li class="tight-form-item last">
-						<editor-checkbox text="Open in new tab " model="link.targetBlank"></editor-checkbox>
-					</li>
-				</ul>
-				<div class="clearfix"></div>
+
+			<div class="gf-form-inline">
+				<div class="gf-form width-2">
+					<i class="fa fa-fw fa-unlink invisible"></i>
+				</div>
+
+				<div class="gf-form">
+					<editor-checkbox text="Keep current time range" model="link.keepTime"></editor-checkbox>
+					<editor-checkbox text="Add current variable values" model="link.includeVars"></editor-checkbox>
+					<editor-checkbox text="Open in new tab " model="link.targetBlank"></editor-checkbox>
+				</div>
 			</div>
 		</div>
-
 	</div>
 </div>
 
 <div class="editor-row">
-	<br>
 	<button class="btn btn-inverse" ng-click="addLink()"><i class="fa fa-plus"></i> Add link</button>
 </div>

+ 0 - 1
public/app/features/panellinks/module.js

@@ -52,6 +52,5 @@ function (angular, _) {
       $scope.deleteLink = function(link) {
         $scope.panel.links = _.without($scope.panel.links, link);
       };
-
     });
 });

+ 46 - 27
public/app/features/playlist/partials/playlist.html

@@ -1,4 +1,4 @@
-<navbar icon="fa fa-fw fa-list" title="Playlist">
+<navbar icon="fa fa-fw fa-list" title="Playlists" title-url="playlists">
 </navbar>
 
 <div class="page-container" ng-form="playlistEditForm">
@@ -7,6 +7,8 @@
 		<h1 ng-show="!ctrl.isNew()">Edit Playlist</h1>
 	</div>
 
+	<p class="playlist-description">A playlist rotates through a pre-selected list of Dashboards. A Playlist can be a great way to build situational awareness, or just show off your metrics to your team or visitors.</p>
+
 	<div class="gf-form-group">
 		<div class="gf-form">
 			<span class="gf-form-label width-7">Name</span>
@@ -19,24 +21,29 @@
 	</div>
 
 	<div class="gf-form-group">
-		<div class="max-width-28">
-			<h5 class="page-headering">Add dashboards</h5>
-			<div style="">
-				<playlist-search class="playlist-search-container" search-started="ctrl.searchStarted(promise)"></playlist-search>
-			</div>
-		</div>
+		<h3 class="page-headering">Dashboards</h3>
 	</div>
 
 	<div class="row">
 		<div class="col-md-6">
-			<h5>Search results ({{ctrl.filteredDashboards.length + ctrl.filteredTags.length}})</h5>
+			<div class="playlist-search-containerwrapper">
+				<div class="max-width-32">
+					<h5 class="page-headering playlist-column-header">Available</h5>
+					<div style="">
+						<playlist-search class="playlist-search-container" search-started="ctrl.searchStarted(promise)"></playlist-search>
+					</div>
+				</div>
+			</div>
+
 			<div ng-if="ctrl.filteredDashboards.length > 0">
-				<table class="grafana-options-table">
+				<table class="grafana-options-table playlist-available-list">
 					<tr ng-repeat="playlistItem in ctrl.filteredDashboards">
-						<td style="white-space: nowrap;">
-							{{playlistItem.title}}
+						<td>
+							<i class="icon-gf icon-gf-dashboard"></i>
+							&nbsp;&nbsp;{{playlistItem.title}}
+							<i class="fa fa-star" ng-show="playlistItem.isStarred"></i>
 						</td>
-						<td style="text-align: center">
+						<td class="add-dashboard">
 							<button class="btn btn-inverse btn-mini pull-right" ng-click="ctrl.addPlaylistItem(playlistItem)">
 								<i class="fa fa-plus"></i>
 								Add to playlist
@@ -46,31 +53,40 @@
 				</table>
 			</div>
 			<div class="playlist-search-results-container" ng-if="ctrl.filteredTags.length > 0;">
-					<div ng-repeat="tag in ctrl.filteredTags" class="pointer" style="width: 180px; float: left;"
-						ng-click="ctrl.addTagPlaylistItem(tag, $event)">
-						<a class="search-result-tag label label-tag" tag-color-from-name="tag.term">
-							<i class="fa fa-tag"></i>
-							<span>{{tag.term}} &nbsp;({{tag.count}})</span>
-						</a>
-					</div>
-				</div>
+				<table class="grafana-options-table playlist-available-list">
+					<tr ng-repeat="tag in ctrl.filteredTags">
+						<td>
+							<a class="search-result-tag label label-tag" tag-color-from-name="tag.term">
+								<i class="fa fa-tag"></i>
+								<span>{{tag.term}} &nbsp;({{tag.count}})</span>
+							</a>
+						</td>
+						<td class="add-dashboard">
+							<button class="btn btn-inverse btn-mini pull-right" ng-click="ctrl.addPlaylistItem(playlistItem)">
+								<i class="fa fa-plus"></i>
+								Add to playlist
+							</button>
+						</td>
+					</tr>
+				</table>
+			</div>
 		</div>
 
 		<div class="col-md-6">
-			<h5>Added dashboards</h5>
-			<table class="grafana-options-table">
+			<h5 class="page headering playlist-column-header">Selected</h5>
+			<table class="grafana-options-table playlist-available-list">
 				<tr ng-repeat="playlistItem in ctrl.playlistItems">
-					<td style="white-space: nowrap;" ng-if="playlistItem.type === 'dashboard_by_id'">
-						{{playlistItem.title}}
+					<td ng-if="playlistItem.type === 'dashboard_by_id'">
+						<i class="icon-gf icon-gf-dashboard"></i>&nbsp;&nbsp;{{playlistItem.title}}
 					</td>
-					<td style="white-space: nowrap;"  ng-if="playlistItem.type === 'dashboard_by_tag'">
+					<td ng-if="playlistItem.type === 'dashboard_by_tag'">
 						<a class="search-result-tag label label-tag" tag-color-from-name="playlistItem.title">
 							<i class="fa fa-tag"></i>
 							<span>{{playlistItem.title}}</span>
 						</a>
 					</td>
 
-					<td style="text-align: right">
+					<td class="selected-playlistitem-settings">
 						<button class="btn btn-inverse btn-mini" ng-hide="$first" ng-click="ctrl.movePlaylistItemUp(playlistItem)">
 							<i class="fa fa-arrow-up"></i>
 						</button>
@@ -89,7 +105,10 @@
 	<div class="clearfix"></div>
 
 	<div class="gf-form-button-row">
-		<a class="btn btn-success"
+		<a class="btn btn-success " ng-show="ctrl.isNew()"
+			ng-disabled="ctrl.playlistEditForm.$invalid || ctrl.isPlaylistEmpty()"
+			ng-click="ctrl.savePlaylist(ctrl.playlist, ctrl.playlistItems)">Create new playlist</a>
+		<a class="btn btn-success" ng-show="!ctrl.isNew()"
 			ng-disabled="ctrl.playlistEditForm.$invalid || ctrl.isPlaylistEmpty()"
 			ng-click="ctrl.savePlaylist(ctrl.playlist, ctrl.playlistItems)">Save</a>
 		<a class="btn-text" ng-click="ctrl.backToList()">Cancel</a>

+ 1 - 1
public/app/features/playlist/partials/playlists.html

@@ -1,4 +1,4 @@
-<navbar icon="fa fa-fw fa-list" title="Playlists">
+<navbar icon="fa fa-fw fa-list" title="Playlists" title-url="playlists">
 </navbar>
 
 <div class="page-container">

+ 1 - 1
public/app/features/playlist/playlist_edit_ctrl.ts

@@ -11,7 +11,7 @@ export class PlaylistEditCtrl {
   searchQuery: string = '';
   loading: boolean = false;
   playlist: any = {
-    interval: '10m',
+    interval: '5m',
   };
   playlistItems: any = [];
   dashboardresult: any = [];

+ 0 - 0
public/app/features/apps/all.ts → public/app/features/plugins/all.ts


+ 51 - 0
public/app/features/plugins/edit_ctrl.ts

@@ -0,0 +1,51 @@
+///<reference path="../../headers/common.d.ts" />
+
+import angular from 'angular';
+import _ from 'lodash';
+
+export class PluginEditCtrl {
+  model: any;
+  pluginId: any;
+  includedPanels: any;
+  includedDatasources: any;
+  tabIndex: number;
+
+  /** @ngInject */
+  constructor(private backendSrv: any, private $routeParams: any) {
+    this.model = {};
+    this.pluginId = $routeParams.pluginId;
+    this.tabIndex = 0;
+
+    this.backendSrv.get(`/api/org/plugins/${this.pluginId}/settings`).then(result => {
+      this.model = result;
+      this.includedPanels = _.where(result.includes, {type: 'panel'});
+      this.includedDatasources = _.where(result.includes, {type: 'datasource'});
+    });
+  }
+
+  update() {
+    var updateCmd = _.extend({
+      pluginId: this.model.pluginId,
+      orgId: this.model.orgId,
+      enabled: this.model.enabled,
+      pinned: this.model.pinned,
+      jsonData: this.model.jsonData,
+      secureJsonData: this.model.secureJsonData,
+    }, {});
+
+    this.backendSrv.post(`/api/org/plugins/${this.pluginId}/settings`, updateCmd).then(function() {
+      window.location.href = window.location.href;
+    });
+  }
+
+  toggleEnabled() {
+    this.update();
+  }
+
+  togglePinned() {
+    this.update();
+  }
+}
+
+angular.module('grafana.controllers').controller('PluginEditCtrl', PluginEditCtrl);
+

+ 17 - 0
public/app/features/plugins/list_ctrl.ts

@@ -0,0 +1,17 @@
+///<reference path="../../headers/common.d.ts" />
+
+import angular from 'angular';
+
+export class PluginListCtrl {
+  plugins: any[];
+
+  /** @ngInject */
+  constructor(private backendSrv: any) {
+
+    this.backendSrv.get('api/org/plugins').then(plugins => {
+      this.plugins = plugins;
+    });
+  }
+}
+
+angular.module('grafana.controllers').controller('PluginListCtrl', PluginListCtrl);

+ 0 - 0
public/app/features/apps/page_ctrl.ts → public/app/features/plugins/page_ctrl.ts


+ 185 - 0
public/app/features/plugins/partials/edit.html

@@ -0,0 +1,185 @@
+<navbar title="Plugins" title-url="plugins" icon="icon-gf icon-gf-apps">
+	<a href="plugins/apps" class="navbar-page-btn">
+		<i class="fa fa-chevron-right"></i>
+		Apps
+	</a>
+</navbar>
+
+<div class="page-container">
+  <div class="plugin-header">
+		<span ng-show="ctrl.model.info.logos.large" class="plugin-header-logo">
+			<img src="{{ctrl.model.info.logos.large}}">
+		</span>
+		<div class="plugin-header-info-block">
+			<h1 class="plugin-header-name">{{ctrl.model.name}}</h1>
+			<div class="plugin-header-author">By {{ctrl.model.info.author.name}}</div>
+			<div class="plugin-header-stamps">
+				<span class="plugin-header-stamps-type">
+					<i class="icon-gf icon-gf-apps"></i> {{ctrl.model.type}}
+				</span>
+			</div>
+		</div>
+	</div>
+
+	<ul class="nav nav-tabs nav-tabs-alt">
+		<li ng-repeat="tab in ::['Overview', 'Details', 'Config']" ng-class="{active: ctrl.tabIndex === $index}">
+			<a ng-click="ctrl.tabIndex= $index">
+				{{::tab}}
+			</a>
+		</li>
+	</ul>
+
+	<div class="page-body">
+		<div class="tab-content page-content-with-sidebar" ng-if="ctrl.tabIndex === 0">
+			README.md
+		</div>
+
+		<div class="tab-content page-content-with-sidebar" ng-if="ctrl.tabIndex === 1">
+			Details
+		</div>
+
+		<div class="tab-content page-content-with-sidebar" ng-if="ctrl.tabIndex === 2">
+			<div class="gf-form-inline">
+				<div class="gf-form">
+					<editor-checkbox text="Enabled" model="ctrl.model.enabled" change="ctrl.toggleEnabled()"></editor-checkbox>
+				</div>
+				<div class="gf-form">
+					<editor-checkbox text="Pinned" model="ctrl.model.pinned" change="ctrl.togglePinned()"></editor-checkbox>
+				</div>
+			</div>
+
+			<div ng-if="ctrl.model.pluginId">
+				<plugin-component type="app-config-ctrl"></plugin-component>
+				<div class="clearfix"></div>
+				<button type="submit" class="btn btn-success" ng-click="ctrl.update()">Save</button>
+			</div>
+
+		</div>
+
+		<aside class="page-sidebar">
+			<section class="page-sidebar-section">
+				<h4>Version</h4>
+				<span>1.0.1</span>
+			</section>
+			<section class="page-sidebar-section" ng-show="ctrl.model.type === 'app'">
+				<h5>Includes</h4>
+				<ul class="ui-list">
+					<li ng-show="!ctrl.includedPanels.length"><em>None</em></li>
+					<li ng-repeat="panel in ctrl.includedPanels">
+						{{panel.name}}
+					</li>
+					<li ng-repeat="ds in ctrl.includedDatasources">
+						{{ds.name}}
+					</li>
+					<li ng-repeat="page in ctrl.model.pages">
+						<a href="apps/{{ctrl.appId}}/page/{{page.slug}}" class="external-link">{{page.name}}</a>
+					</li>
+				</ul>
+			</section>
+			<section class="page-sidebar-section">
+				<h5>Dependencies</h4>
+				<span>TODO</span>
+			</section>
+			<section class="page-sidebar-section">
+				<h5>Links</h4>
+				<ul class="ui-list">
+					<li ng-repeat="link in ctrl.model.info.links">
+						<a href="{{link.url}}" class="external-link" target="_blank">{{link.name}}</a>
+					</li>
+				</ul>
+			</section>
+		</aside>
+	</div>
+</div>
+</div>
+
+<!-- 	<div class="app&#45;edit&#45;description"> -->
+	<!-- 		{{ctrl.model.info.description}}<br> -->
+	<!-- 		<span style="small"> -->
+		<!-- 			Version: {{ctrl.model.info.version}} &#38;nbsp; &#38;nbsp; Updated: {{ctrl.model.info.updated}} -->
+		<!-- 		</span> -->
+	<!-- 	</div> -->
+<!--  -->
+<!-- </div> -->
+		<!-- <div class="flex&#45;column"> -->
+			<!-- 	<ul class="app&#45;edit&#45;links"> -->
+				<!-- 		<li> -->
+					<!-- 			By <a href="{{ctrl.model.info.author.url}}" class="external&#45;link" target="_blank">{{ctrl.model.info.author.name}}</a> -->
+					<!-- 		</li> -->
+				<!-- 		<li ng&#45;repeat="link in ctrl.model.info.links"> -->
+					<!-- 			<a href="{{link.url}}" class="external&#45;link" target="_blank">{{link.name}}</a> -->
+					<!-- 		</li> -->
+				<!-- 	</ul> -->
+			<!-- </div> -->
+
+		<!-- <section class="simple&#45;box"> -->
+			<!-- 	<h3 class="simple&#45;box&#45;header">Included with app:</h3> -->
+			<!-- 	<div class="flex&#45;container"> -->
+				<!-- 		<div class="simple&#45;box&#45;body simple&#45;box&#45;column"> -->
+					<!-- 			<div class="simple&#45;box&#45;column&#45;header"> -->
+						<!-- 				<i class="fa fa&#45;th&#45;large"></i> -->
+						<!-- 				Dashboards -->
+						<!-- 			</div> -->
+					<!-- 			<ul> -->
+						<!-- 				<li><em class="small">None</em></li> -->
+						<!-- 			</ul> -->
+					<!-- 		</div> -->
+				<!-- 		<div class="simple&#45;box&#45;body simple&#45;box&#45;column"> -->
+					<!-- 			<div class="simple&#45;box&#45;column&#45;header"> -->
+						<!-- 				<i class="fa fa&#45;line&#45;chart"></i> -->
+						<!-- 				Panels -->
+						<!-- 			</div> -->
+					<!-- 			<ul> -->
+						<!-- 				<li ng&#45;show="!ctrl.includedPanels.length"><em class="small">None</em></li> -->
+						<!-- 				<li ng&#45;repeat="panel in ctrl.includedPanels"> -->
+							<!-- 					{{panel.name}} -->
+							<!-- 				</li> -->
+						<!-- 			</ul> -->
+					<!-- 		</div> -->
+				<!-- 		<div class="simple&#45;box&#45;body simple&#45;box&#45;column"> -->
+					<!-- 			<div class="simple&#45;box&#45;column&#45;header"> -->
+						<!-- 				<i class="fa fa&#45;database"></i> -->
+						<!-- 				Datasources -->
+						<!-- 			</div> -->
+					<!-- 			<ul> -->
+						<!-- 				<li ng&#45;show="!ctrl.includedDatasources.length"><em class="small">None</em></li> -->
+						<!-- 				<li ng&#45;repeat="ds in ctrl.includedDatasources"> -->
+							<!-- 					{{ds.name}} -->
+							<!-- 				</li> -->
+						<!-- 			</ul> -->
+					<!-- 		</div> -->
+				<!-- 		<div class="simple&#45;box&#45;body simple&#45;box&#45;column"> -->
+					<!-- 			<div class="simple&#45;box&#45;column&#45;header"> -->
+						<!-- 				<i class="fa fa&#45;files&#45;o"></i> -->
+						<!-- 				Pages -->
+						<!-- 			</div> -->
+					<!-- 			<ul> -->
+						<!-- 				<li ng&#45;repeat="page in ctrl.model.pages"> -->
+							<!-- 					<a href="apps/{{ctrl.appId}}/page/{{page.slug}}" class="external&#45;link">{{page.name}}</a> -->
+							<!-- 				</li> -->
+						<!-- 			</ul> -->
+					<!-- 		</div> -->
+				<!--  -->
+				<!-- 	</div> -->
+			<!-- </section> -->
+		<!--  -->
+		<!-- <section class="simple&#45;box"> -->
+			<!-- 	<h3 class="simple&#45;box&#45;header">Dependencies:</h3> -->
+			<!-- 	<div class="simple&#45;box&#45;body"> -->
+				<!-- 		Grafana 2.6.x -->
+				<!-- 	</div> -->
+			<!-- </section> -->
+		<!--  -->
+		<!-- <section class="simple&#45;box"> -->
+			<!-- 	<h3 class="simple&#45;box&#45;header">Configuration:</h3> -->
+			<!-- 	<div class="simple&#45;box&#45;body"> -->
+				<!-- 		<div ng&#45;if="ctrl.model.appId"> -->
+					<!-- 			<plugin&#45;component type="app&#45;config&#45;ctrl"></plugin&#45;component> -->
+					<!-- 			<div class="clearfix"></div> -->
+					<!-- 			<button type="submit" class="btn btn&#45;success" ng&#45;click="ctrl.update()">Save</button> -->
+					<!-- 		</div> -->
+				<!-- 	</div> -->
+			<!-- </section> -->
+		<!--  -->
+		<!--  -->
+		<!-- </div> -->

+ 42 - 0
public/app/features/plugins/partials/list.html

@@ -0,0 +1,42 @@
+<navbar title="Plugins" icon="icon-gf icon-gf-apps" title-url="plugins">
+</navbar>
+
+<div class="page-container">
+  <div class="page-header">
+    <h1>Plugins</h1>
+	</div>
+
+	<table class="filter-table">
+		<thead>
+			<tr>
+				<th><strong>Name</strong></th>
+				<th><strong>Type</strong></th>
+				<th style="width: 60px;"></th>
+				<th style="width: 80px;"></th>
+			</tr>
+		</thead>
+		<tbody>
+			<tr ng-repeat="plugin in ctrl.plugins">
+				<td>
+					<a href="plugins/{{plugin.pluginId}}/edit">
+						{{plugin.name}}
+					</a>
+				</td>
+				<td>
+					{{plugin.type}}
+				</td>
+				<td>
+					<span class="label label-info" ng-if="plugin.enabled">Enabled</span>
+					<span class="label label-info" ng-if="plugin.pinned">Pinned</span>
+				</td>
+				<td class="text-right">
+					<a href="plugins/{{plugin.pluginId}}/edit" class="btn btn-inverse btn-small">
+							<i class="fa fa-edit"></i>
+							Edit
+						</a>
+					</td>
+				</tr>
+			</tbody>
+		</table>
+	</div>
+</div>

+ 0 - 0
public/app/features/apps/partials/page.html → public/app/features/plugins/partials/page.html


+ 1 - 1
public/app/features/profile/partials/profile.html

@@ -1,4 +1,4 @@
-<navbar icon="icon-gf icon-gf-users" title="Profile">
+<navbar icon="icon-gf icon-gf-users" title="Profile" title-url="profile">
 </navbar>
 
 <div class="page-container">

+ 31 - 31
public/app/features/snapshot/partials/snapshots.html

@@ -1,40 +1,40 @@
-<navbar icon="icon-gf icon-gf-snapshot" title="Snapshots">
+<navbar icon="icon-gf icon-gf-snapshot" title="Snapshots" title-url="dashboard/snapshots">
 </navbar>
 
 <div class="page-container">
-  <div class="page-wide">
-
+  <div class="page-header">
     <h1>Available snapshots</h1>
+  </div>
 
-     <table class="filter-table" style="margin-top: 20px">
-      <thead>
-        <th><strong>Name</strong></th>
-        <th><strong>Snapshot url</strong></th>
-        <th style="width: 70px"></th>
-        <th style="width: 25px"></th>
 
-     </thead>
+   <table class="filter-table" style="margin-top: 20px">
+    <thead>
+      <th><strong>Name</strong></th>
+      <th><strong>Snapshot url</strong></th>
+      <th style="width: 70px"></th>
+      <th style="width: 25px"></th>
 
-      <tr ng-repeat="snapshot in ctrl.snapshots">
-        <td>
-					<a href="dashboard/snapshot/{{snapshot.key}}">{{snapshot.name}}</a>
-        </td>
-        <td >
-          <a href="dashboard/snapshot/{{snapshot.key}}">dashboard/snapshot/{{snapshot.key}}</a>
-        </td>
-        <td class="text-center">
-          <a href="dashboard/snapshot/{{snapshot.key}}" class="btn btn-inverse btn-mini">
-            <i class="fa fa-eye"></i>
-            View
-          </a>
-        </td>
-        <td  class="text-right">
-          <a ng-click="ctrl.removeSnapshot(snapshot)" class="btn btn-danger btn-mini">
-            <i class="fa fa-remove"></i>
-          </a>
-        </td>
-      </tr>
-    </table>
+   </thead>
+
+    <tr ng-repeat="snapshot in ctrl.snapshots">
+      <td>
+				<a href="dashboard/snapshot/{{snapshot.key}}">{{snapshot.name}}</a>
+      </td>
+      <td >
+        <a href="dashboard/snapshot/{{snapshot.key}}">dashboard/snapshot/{{snapshot.key}}</a>
+      </td>
+      <td class="text-center">
+        <a href="dashboard/snapshot/{{snapshot.key}}" class="btn btn-inverse btn-mini">
+          <i class="fa fa-eye"></i>
+          View
+        </a>
+      </td>
+      <td  class="text-right">
+        <a ng-click="ctrl.removeSnapshot(snapshot)" class="btn btn-danger btn-mini">
+          <i class="fa fa-remove"></i>
+        </a>
+      </td>
+    </tr>
+  </table>
 
-  </div>
 </div>

+ 1 - 1
public/app/features/styleguide/styleguide.html

@@ -1,4 +1,4 @@
-<navbar icon="fa fa-fw fa-adjust" title="Style Guide">
+<navbar icon="fa fa-fw fa-adjust" title="Style Guide" title-url="styleguide">
 </navbar>
 
 <div class="page-container">

+ 174 - 288
public/app/features/templating/partials/editor.html

@@ -1,332 +1,218 @@
 <div ng-controller="TemplateEditorCtrl" ng-init="init()">
-	<div class="gf-box-header">
-		<div class="gf-box-title">
-			<i class="fa fa-code"></i>
+	<div class="tabbed-view-header">
+		<h2 class="tabbed-view-title">
 			Templating
-		</div>
-
-		<div class="tabs">
-			<ul class="nav nav-tabs">
-				<li ng-class="{active: mode === 'list'}">
-					<a ng-click="mode = 'list';">
-						Variables
-					</a>
-				</li>
-
-				<li ng-class="{active: mode === 'edit'}" ng-show="mode === 'edit'">
-					<a>
-						{{current.name}}
-					</a>
-				</li>
-
-				<li ng-class="{active: mode === 'new'}">
-					<a ng-click="mode = 'new';">
-						<i class="fa fa-plus"></i>
-						New
-					</a>
-				</li>
-			</ul>
-		</div>
-
-		<button class="gf-box-header-close-btn" ng-click="dismiss();dashboard.refresh();">
+		</h2>
+
+		<ul class="gf-tabs">
+			<li class="gf-tabs-item" >
+				<a class="gf-tabs-link" ng-click="mode = 'list';" ng-class="{active: mode === 'list'}">
+					Variables
+				</a>
+			</li>
+			<li class="gf-tabs-item" ng-show="mode === 'edit'">
+				<a class="gf-tabs-link" ng-class="{active: mode === 'edit'}">
+					{{current.name}}
+				</a>
+			</li>
+			<li class="gf-tabs-item" ng-show="mode === 'new'">
+				<span class="active gf-tabs-link">New</span>
+			</li>
+		</ul>
+
+		<button class="tabbed-view-close-btn" ng-click="dismiss();dashboard.refresh();">
 			<i class="fa fa-remove"></i>
 		</button>
-
 	</div>
 
-	<div class="gf-box-body">
+	<div class="tabbed-view-body">
 
 		<div ng-if="mode === 'list'">
+			<div ng-if="variables.length === 0">
+				<em>No template variables defined</em>
+			</div>
+			<table class="grafana-options-table">
+				<tr ng-repeat="variable in variables">
+					<td style="width: 1%">
+						<span class="template-variable">
+							${{variable.name}}
+						</span>
+					</td>
+					<td class="max-width" style="max-width: 200px;">
+						{{variable.query}}
+					</td>
+
+					<td style="width: 1%"><i ng-click="_.move(variables,$index,$index-1)" ng-hide="$first" class="pointer fa fa-arrow-up"></i></td>
+					<td style="width: 1%"><i ng-click="_.move(variables,$index,$index+1)" ng-hide="$last" class="pointer fa fa-arrow-down"></i></td>
+					<td style="width: 1%">
+            <a ng-click="duplicate(variable)" class="btn btn-inverse btn-mini">
+              Duplicate
+            </a>
+          </td>
+					<td style="width: 1%">
+						<a ng-click="edit(variable)" class="btn btn-inverse btn-mini">
+							<i class="fa fa-edit"></i>
+							Edit
+						</a>
+					</td>
+					<td style="width: 1%">
+						<a ng-click="removeVariable(variable)" class="btn btn-danger btn-mini">
+							<i class="fa fa-remove"></i>
+						</a>
+					</td>
+				</tr>
+			</table>
+		</div>
 
-			<div class="editor-row row">
-				<div style="max-width: 1024px">
-					<div ng-if="variables.length === 0">
-						<em>No template variables defined</em>
-					</div>
-					<table class="grafana-options-table">
-						<tr ng-repeat="variable in variables">
-							<td style="width: 1%">
-								<span class="template-variable">
-									${{variable.name}}
-								</span>
-							</td>
-							<td class="max-width" style="max-width: 200px;">
-								{{variable.query}}
-							</td>
-							<td style="width: 1%">
-								<a ng-click="edit(variable)" class="btn btn-inverse btn-small">
-									<i class="fa fa-edit"></i>
-									Edit
-								</a>
-							</td>
-              <td style="width: 1%">
-                <a ng-click="duplicate(variable)" class="btn btn-inverse btn-small">
-                  Duplicate
-                </a>
-              </td>
-							<td style="width: 1%"><i ng-click="_.move(variables,$index,$index-1)" ng-hide="$first" class="pointer fa fa-arrow-up"></i></td>
-							<td style="width: 1%"><i ng-click="_.move(variables,$index,$index+1)" ng-hide="$last" class="pointer fa fa-arrow-down"></i></td>
-							<td style="width: 1%">
-								<a ng-click="removeVariable(variable)" class="btn btn-danger btn-small">
-									<i class="fa fa-remove"></i>
-								</a>
-							</td>
-						</tr>
-					</table>
-				</div>
+		<div class="gf-form" ng-show="mode === 'list'">
+			<div class="gf-form-button-row">
+				<a type="button" class="btn gf-form-button btn-success" ng-click="mode = 'new';"><i class="fa fa-plus" ></i>&nbsp;&nbsp;New</a>
 			</div>
 		</div>
 
 		<div ng-if="mode === 'edit' || mode === 'new'">
-			<div class="editor-row">
-				<div class="tight-form-section">
-					<h5>Variable</h5>
-					<div class="tight-form last">
-						<ul class="tight-form-list">
-							<li class="tight-form-item" style="width: 100px">
-								Name
-							</li>
-							<li>
-								<input type="text" class="input-large tight-form-input" placeholder="name" ng-model='current.name'></input>
-							</li>
-							<li class="tight-form-item">
-								Type
-							</li>
-							<li>
-								<select class="input-small tight-form-input" ng-model="current.type" ng-options="f for f in ['query', 'interval', 'custom']" ng-change="typeChanged()"></select>
-							</li>
-							<li class="tight-form-item" ng-show="current.type === 'query'">
-								Data source
-							</li>
-							<li ng-show="current.type === 'query'">
-								<select class="input input-medium tight-form-input last" ng-model="current.datasource" ng-options="f.value as f.name for f in datasources"></select>
-							</li>
-						</ul>
-						<div class="clearfix"></div>
+			<h5 class="section-heading">Variable</h5>
+			<div class="gf-form-group">
+				<div class="gf-form-inline">
+					<div class="gf-form">
+						<span class="gf-form-label width-7">Name</span>
+						<input type="text" class="gf-form-input max-width-14" placeholder="name" ng-model='current.name'></input>
 					</div>
-				</div>
-			</div>
-
-			<div class="editor-row">
-				<div class="tight-form-section">
-					<h5>Value Options</h5>
-
-					<div ng-show="current.type === 'interval'">
-						<div class="tight-form">
-							<ul class="tight-form-list">
-								<li class="tight-form-item" style="width: 160px">
-									Values
-								</li>
-								<li>
-									<input type="text" style="width: 345px" class="input-xxlarge tight-form-input last" placeholder="name" ng-model='current.query' placeholder="1m,10m,1h,6h,1d,7d" ng-model-onblur ng-change="runQuery()"></input>
-								</li>
-							</ul>
-							<div class="clearfix"></div>
-						</div>
-						<div class="tight-form last">
-							<ul class="tight-form-list">
-								<li class="tight-form-item" style="width: 160px">
-									<editor-checkbox text="Include auto interval" model="current.auto" change="runQuery()"></editor-checkbox>
-								</li>
-								<li class="tight-form-item" ng-show="current.auto">
-									Auto interval steps <tip>How many times should the current time range be divided to calculate the value</tip>
-								</li>
-								<li>
-									<select class="input-mini tight-form-input last" ng-model="current.auto_count" ng-options="f for f in [3,5,10,30,50,100,200]" ng-change="runQuery()"></select>
-								</li>
-								<li class="tight-form-item" ng-show="current.auto">
-									Auto interval min value <tip>The calculated value will not go below this threshold</tip>
-								</li>
-								<li>
-									<input type="text" style="width: 35px" class="input-xxlarge tight-form-input last" ng-model="current.auto_min" ng-change="runQuery()"></input>
-								</li>
-							</ul>
-							<div class="clearfix"></div>
+					<div class="gf-form">
+						<span class="gf-form-label width-7">Type</span>
+						<div class="gf-form-select-wrapper max-width-10">
+							<select class="gf-form-input  max-width-10" ng-model="current.type" ng-options="f for f in ['query', 'interval', 'custom']" ng-change="typeChanged()"></select>
 						</div>
 					</div>
-
-					<div ng-show="current.type === 'custom'">
-						<div class="tight-form last">
-							<ul class="tight-form-list">
-								<li class="tight-form-item" style="width: 180px">
-									Values seperated by comma
-								</li>
-								<li>
-									<input type="text" class="input tight-form-input last" style="width: 325px;" ng-model='current.query' ng-blur="runQuery()" placeholder="1, 10, 20, myvalue"></input>
-								</li>
-							</ul>
-							<div class="clearfix"></div>
-						</div>
-            <div class="tight-form">
-              <ul class="tight-form-list">
-                <li class="tight-form-item" style="width: 100px;">
-                  <editor-checkbox text="All value" model="current.includeAll" change="runQuery()"></editor-checkbox>
-                </li>
-                <li ng-show="current.includeAll">
-                  <input type="text" class="input-xlarge tight-form-input" style="width:364px" ng-model='current.options[0].value'></input>
-                </li>
-                <li class="tight-form-item" ng-show="current.includeAll">
-                  All format
-                </li>
-                <li ng-show="current.includeAll">
-                  <select class="input-medium tight-form-input last" ng-model="current.allFormat" ng-change="runQuery()" ng-options="f for f in ['glob', 'wildcard', 'regex wildcard', 'regex values', 'lucene', 'pipe']"></select>
-                </li>
-              </ul>
-              <div class="clearfix"></div>
-            </div>
-					</div>
-
-					<div ng-show="current.type === 'query'">
-
-						<div class="tight-form">
-							<ul class="tight-form-list">
-								<li class="tight-form-item" style="width: 100px">
-									Query
-								</li>
-								<li>
-									<input type="text" style="width: 588px" class="input-xxlarge tight-form-input last" ng-model='current.query' placeholder="metric name or tags query" ng-model-onblur ng-change="runQuery()"></input>
-								</li>
-							</ul>
-							<div class="clearfix"></div>
-						</div>
-						<div class="tight-form">
-							<ul class="tight-form-list">
-								<li class="tight-form-item" style="width: 100px;">
-									Regex
-									<tip>Optional, if you want to extract part of a series name or metric node segment</tip>
-								</li>
-								<li>
-									<input type="text" style="width: 588px" class="input tight-form-input last" ng-model='current.regex' placeholder="/.*-(.*)-.*/" ng-model-onblur ng-change="runQuery()"></input>
-								</li>
-							</ul>
-							<div class="clearfix"></div>
-						</div>
-
-						<div class="tight-form">
-							<ul class="tight-form-list">
-								<li class="tight-form-item" style="width: 100px;">
-									<editor-checkbox text="All value" model="current.includeAll" change="runQuery()"></editor-checkbox>
-								</li>
-								<li ng-show="current.includeAll">
-									<input type="text" class="input-xlarge tight-form-input" style="width:364px" ng-model='current.options[0].value'></input>
-								</li>
-								<li class="tight-form-item" ng-show="current.includeAll">
-									All format
-								</li>
-								<li ng-show="current.includeAll">
-									<select class="input-medium tight-form-input last" ng-model="current.allFormat" ng-change="runQuery()" ng-options="f for f in ['glob', 'wildcard', 'regex wildcard', 'regex values', 'lucene', 'pipe']"></select>
-								</li>
-							</ul>
-							<div class="clearfix"></div>
-						</div>
-
-						<div class="tight-form last">
-							<ul class="tight-form-list">
-								<li class="tight-form-item last">
-									<editor-checkbox text="Refresh on load" model="current.refresh"></editor-checkbox>
-									<tip>Check if you want values to be updated on dashboard load, will slow down dashboard load time</tip>
-								</li>
-							</ul>
-							<div class="clearfix"></div>
+					<div class="gf-form">
+						<span class="gf-form-label width-7" ng-show="current.type === 'query'">Data source</span>
+						<div class="gf-form-select-wrapper" ng-show="current.type === 'query'">
+							<select class="gf-form-input max-width-14" ng-model="current.datasource" ng-options="f.value as f.name for f in datasources"></select>
 						</div>
 					</div>
 				</div>
+				<div class="gf-form">
+					<span class="gf-form-label width-7">Label</span>
+					<input type="text" class="gf-form-input max-width-14" ng-model='current.label' placeholder="optional display name"></input>
+					<editor-checkbox class="width-13" text="Hide label" model="current.hideLabel" change="runQuery()"></editor-checkbox>
+				</div>
 			</div>
 
-			<div class="editor-row">
-				<div class="tight-form-section" ng-hide="current.type === 'interval'">
-					<h5>Multi-value selection <tip>Enables multiple values to be selected at the same time</tip></h5>
-					<div class="tight-form last">
-						<ul class="tight-form-list">
-							<li class="tight-form-item last" style="width: 100px;">
-								<editor-checkbox text="Enable" model="current.multi" change="runQuery()"></editor-checkbox>
-							</li>
-							<li class="tight-form-item" ng-show="current.multi">
-								Multi format
-							</li>
-							<li ng-show="current.multi">
-								<select class="input-medium tight-form-input last" ng-model="current.multiFormat" ng-change="runQuery()" ng-options="f for f in ['glob', 'regex values', 'lucene', 'pipe']"></select>
-							</li>
-						</ul>
-						<div class="clearfix"></div>
+			<h5 class="section-heading">Value Options</h5>
+			<div ng-show="current.type === 'interval'" class="gf-form-group">
+				<div class="gf-form">
+					<span class="gf-form-label width-7">Values</span>
+					<input type="text" class="gf-form-input max-width-28" placeholder="name" ng-model='current.query' placeholder="1m,10m,1h,6h,1d,7d" ng-model-onblur ng-change="runQuery()"></input>
+				</div>
+				<div class="gf-form">
+					<editor-checkbox text="Include auto interval" model="current.auto" change="runQuery()"></editor-checkbox>
+					<span class="gf-form-label" ng-show="current.auto">
+						Auto interval steps <tip>How many times should the current time range be divided to calculate the value</tip>
+					</span>
+					<div class="gf-form-select-wrapper max-width-10" ng-show="current.auto">
+						<select class="gf-form-input" ng-model="current.auto_count" ng-options="f for f in [3,5,10,30,50,100,200]" ng-change="runQuery()"></select>
 					</div>
 				</div>
+				<div class="gf-form">
+					<span class="gf-form-label" ng-show="current.auto">
+						Auto interval min value <tip>The calculated value will not go below this threshold</tip>
+					</span>
+					<input type="text" class="gf-form-input max-width-10" ng-show="current.auto" ng-model="current.auto_min" ng-change="runQuery()"></input>
+				</div>
+			</div>
 
-				<div class="tight-form-section">
-					<h5>Display options</h5>
-					<div class="tight-form last">
-						<ul class="tight-form-list">
-							<li class="tight-form-item" style="width: 100px">
-								Variable Label
-							</li>
-							<li>
-								<input type="text" class="input-medium tight-form-input" ng-model='current.label' placeholder=""></input>
-							</li>
-							<li class="tight-form-item last">
-								<editor-checkbox text="Hide label" model="current.hideLabel" change="runQuery()"></editor-checkbox>
-							</li>
-						</ul>
-						<div class="clearfix"></div>
+			<div ng-show="current.type === 'custom'" class="gf-form-group">
+				<div class="gf-form">
+					<span class="gf-form-label width-13">Values seperated by comma</span>
+					<input type="text" class="gf-form-input max-width-22" ng-model='current.query' ng-blur="runQuery()" placeholder="1, 10, 20, myvalue"></input>
+				</div>
+				<div class="gf-form ">
+					<editor-checkbox class="width-13" text="All value" model="current.includeAll" change="runQuery()"></editor-checkbox>
+					<input ng-show="current.includeAll" type="text" class="gf-form-input max-width-22" ng-model='current.options[0].value' style="margin-left: 4px;"></input>
+				</div>
+				<div class="gf-form">
+					<span class="gf-form-label width-13" ng-show="current.includeAll">All format</span>
+					<div class="gf-form-select-wrapper max-width-10" ng-show="current.includeAll">
+						<select class="gf-form-input" ng-model="current.allFormat" ng-change="runQuery()" ng-options="f for f in ['glob', 'wildcard', 'regex wildcard', 'regex values', 'lucene', 'pipe']"></select>
 					</div>
 				</div>
 			</div>
 
-			<div class="editor-row" ng-if="current.type === 'query'">
-				<div class="tight-form-section">
-					<h5>Value groups/tags (Experimental feature)</h5>
-					<div class="tight-form last" ng-if="current.useTags">
-						<ul class="tight-form-list">
-							<li class="tight-form-item" style="width: 135px">
-								Tags query
-							</li>
-							<li>
-								<input type="text" style="width: 588px" class="input-xxlarge tight-form-input last" ng-model='current.tagsQuery' placeholder="metric name or tags query" ng-model-onblur></input>
-							</li>
-						</ul>
-						<div class="clearfix"></div>
-					</div>
-					<div class="tight-form" ng-if="current.useTags">
-						<ul class="tight-form-list">
-							<li class="tight-form-item" style="width: 135px;">
-								Tag values query
-							</li>
-							<li>
-								<input type="text" style="width: 588px" class="input tight-form-input last" ng-model='current.tagValuesQuery' placeholder="apps.$tag.*" ng-model-onblur></input>
-							</li>
-						</ul>
-						<div class="clearfix"></div>
+			<div ng-show="current.type === 'query'" class="gf-form-group">
+				<div class="gf-form">
+					<span class="gf-form-label width-7">Query</span>
+					<input type="text" class="gf-form-input" ng-model='current.query' placeholder="metric name or tags query" ng-model-onblur ng-change="runQuery()"></input>
+				</div>
+				<div class="gf-form">
+					<span class="gf-form-label width-7">
+						Regex
+						<tip>Optional, if you want to extract part of a series name or metric node segment</tip>
+					</span>
+					<input type="text" class="gf-form-input" ng-model='current.regex' placeholder="/.*-(.*)-.*/" ng-model-onblur ng-change="runQuery()"></input>
+				</div>
+				<div class="gf-form">
+					<span class="gf-form-label width-7">All value</span>
+					<editor-checkbox class="width-13" text="Enable" model="current.includeAll" change="runQuery()"></editor-checkbox>
+				</div>
+				<div class="gf-form-inline" ng-show="current.includeAll">
+					<div class="gf-form">
+						<span class="gf-form-label width-7">All format</span>
+						<div class="gf-form-select-wrapper">
+							<select class="gf-form-input" ng-model="current.allFormat" ng-change="runQuery()" ng-options="f for f in ['glob', 'wildcard', 'regex wildcard', 'regex values', 'lucene', 'pipe']"></select>
+						</div>
 					</div>
-					<div class="tight-form">
-						<ul class="tight-form-list">
-							<li class="tight-form-item last">
-								<editor-checkbox text="Enable" model="current.useTags" change="runQuery()"></editor-checkbox>
-							</li>
-						</ul>
-						<div class="clearfix"></div>
+					<div class="gf-form max-width-30">
+						<span class="gf-form-label width-7">All value</span>
+						<input type="text" class="gf-form-input" ng-model='current.options[0].value'></input>
 					</div>
 				</div>
+				<div class="gf-form">
+					<span class="gf-form-label width-7">Update</span>
+					<editor-checkbox text="On Dashboard Load" model="current.refresh"></editor-checkbox>
+					<tip>Check if you want values to be updated on dashboard load, will slow down dashboard load time</tip>
+				</div>
 			</div>
 
-			<div class="editor-row">
-				<div class="tight-form-section">
-					<h5>Preview of values (shows max 20)</h5>
-					<div class="tight-form last">
-						<ul class="tight-form-list">
-							<li class="tight-form-item" ng-repeat="option in current.options | limitTo: 20">
-								{{option.text}}
-							</li>
-						</ul>
-						<div class="clearfix"></div>
+			<div class="gf-form-group" >
+				<h5 class="section-heading">Multi-value selection <tip>Enables multiple values to be selected at the same time</tip></h5>
+				<div class="gf-form">
+					<editor-checkbox text="Enable" model="current.multi" change="runQuery()"></editor-checkbox>
+					<span class="gf-form-label" ng-show="current.multi">Multi format</span>
+					<div class="gf-form-select-wrapper max-width-10" ng-show="current.multi">
+						<select class="gf-form-input" ng-model="current.multiFormat" ng-change="runQuery()" ng-options="f for f in ['glob', 'regex values', 'lucene', 'pipe']"></select>
 					</div>
 				</div>
 			</div>
+
+			<div class="gf-form-group" ng-if="current.type === 'query'">
+				<h5>Value groups/tags (Experimental feature)</h5>
+				<div class="gf-form">
+					<editor-checkbox text="Enable" model="current.useTags" change="runQuery()"></editor-checkbox>
+				</div>
+				<div class="gf-form last" ng-if="current.useTags">
+					<span class="gf-form-label width-10">Tags query</span>
+					<input type="text" class="gf-form-input" ng-model='current.tagsQuery' placeholder="metric name or tags query" ng-model-onblur></input>
+				</div>
+				<div class="gf-form" ng-if="current.useTags">
+					<li class="gf-form-label width-10">Tag values query</li>
+					<input type="text" class="gf-form-input" ng-model='current.tagValuesQuery' placeholder="apps.$tag.*" ng-model-onblur></input>
+				</div>
+			</div>
+
+			<div class="gf-form-group">
+				<h5>Preview of values (shows max 20)</h5>
+				<div class="gf-form">
+					<span class="gf-form-label" ng-repeat="option in current.options | limitTo: 20">
+						{{option.text}}
+					</span>
+				</div>
+			</div>
 		</div>
 
-		<div class="editor-row" style="margin-top: 20px">
+		<div class="gf-form-button-row p-y-0">
 			<button type="button" class="btn btn-success" ng-show="mode === 'edit'" ng-click="update();">Update</button>
 			<button type="button" class="btn btn-success" ng-show="mode === 'new'" ng-click="add();">Add</button>
 		</div>
-
 	</div>
 </div>
 

+ 8 - 0
public/app/headers/common.d.ts

@@ -39,4 +39,12 @@ declare module 'app/core/store' {
   export default store;
 }
 
+declare module 'tether' {
+  var config: any;
+  export default config;
+}
 
+declare module 'tether-drop' {
+  var config: any;
+  export default config;
+}

+ 2 - 1
public/app/partials/bootstrap/tabset.html

@@ -1,5 +1,6 @@
 <div>
-  <ul class="nav nav-{{type || 'tabs'}} nav-tabs-alt" ng-class="{'nav-stacked': vertical, 'nav-justified': justified}" ng-transclude></ul>
+	<ul class="nav nav-tabs" ng-class="{'nav-stacked': vertical, 'nav-justified': justified}" ng-transclude>
+	</ul>
   <div class="tab-content">
     <div class="tab-pane"
          ng-repeat="tab in tabs"

+ 0 - 11
public/app/partials/colorpicker.html

@@ -1,11 +0,0 @@
-<div class="graph-legend-popover">
-	<a class="close" ng-click="dismiss();" href="">×</a>
-
-	<div class="editor-row">
-		<i ng-repeat="color in colors" class="pointer fa fa-circle"
-			ng-style="{color:color}"
-			ng-click="colorSelected(color);dismiss();">&nbsp;</i>
-	</div>
-
-</div>
-

+ 12 - 13
public/app/partials/edit_json.html

@@ -1,22 +1,21 @@
 <div ng-controller="JsonEditorCtrl">
+	<div class="tabbed-view-header">
+		<h2 class="tabbed-view-title">
+			JSON
+		</h2>
 
-	<div class="gf-box-header">
-		<div class="gf-box-title">
-			<i class="fa fa-edit"></i>
-		  JSON
-		</div>
-
-		<button class="gf-box-header-close-btn" ng-click="dismiss();">
+		<button class="tabbed-view-close-btn" ng-click="dismiss()">
 			<i class="fa fa-remove"></i>
 		</button>
 	</div>
 
-	<div class="gf-box-body" style="height: 500px">
-		<textarea ng-model="json" rows="20" spellcheck="false" style="width: 100%;"></textarea>
-		<br>
-		<br>
+	<div class="tabbed-view-body">
+		<div class="gf-form">
+			<textarea class="gf-form-input" ng-model="json" rows="20" spellcheck="false"></textarea>
+		</div>
 
-		<button type="button" class="btn btn-success" ng-show="canUpdate" ng-click="update(); dismiss();">Update</button>
+		<div class="gf-form-button-row">
+			<button type="button" class="btn btn-success" ng-show="canUpdate" ng-click="update(); dismiss();">Update</button>
+		</div>
 	</div>
-
 </div>

+ 8 - 0
public/app/partials/help_modal.html

@@ -32,6 +32,10 @@
 				<td><span class="label label-info">CTRL+S</span></td>
 				<td>Save dashboard</td>
 			</tr>
+      <tr>
+        <td><span class="label label-info">CTRL+E</span></td>
+        <td>Export dashboard</td>
+      </tr>
 			<tr>
 				<td><span class="label label-info">CTRL+H</span></td>
 				<td>Hide row controls</td>
@@ -40,6 +44,10 @@
 				<td><span class="label label-info">CTRL+Z</span></td>
 				<td>Zoom out</td>
 			</tr>
+      <tr>
+        <td><span class="label label-info">CTRL+I</span></td>
+        <td>Quick snapshot</td>
+      </tr>
 			<tr>
 				<td><span class="label label-info">CTRL+O</span></td>
 				<td>Enable/Disable shared graph crosshair</td>

+ 1 - 5
public/app/partials/panelgeneral.html

@@ -8,7 +8,7 @@
 			<span class="gf-form-label width-6">Span</span>
 			<select class="gf-form-input gf-size-auto" ng-model="ctrl.panel.span" ng-options="f for f in [0,1,2,3,4,5,6,7,8,9,10,11,12]"></select>
 		</div>
-		<div class="gf-form max-width-21">
+		<div class="gf-form max-width-26">
 			<span class="gf-form-label width-8">Height</span>
 			<input type="text" class="gf-form-input max-width-6" ng-model='ctrl.panel.height' placeholder="100px"></input>
 			<editor-checkbox text="Transparent" model="ctrl.panel.transparent"></editor-checkbox>
@@ -31,10 +31,6 @@
 	</div>
 </div>
 
-<br>
-<br>
-<br>
-
 <panel-links-editor panel="ctrl.panel"></panel-links-editor>
 
 

+ 20 - 47
public/app/partials/reset_password.html

@@ -1,11 +1,10 @@
-<div class="container">
+<div class="login-container container">
 
 	<div class="login-box">
 
 		<div class="login-box-logo">
-			<a href="login">
-				<img src="img/logo_transparent_200x75.png">
-			</a>
+			<img class="logo-icon" src="public/img/grafana_icon.svg"></img><br>
+			<i class="icon-gf icon-gf-grafana_wordmark"></i>
 		</div>
 
 		<div class="login-inner-box">
@@ -15,76 +14,50 @@
 				</button>
 			</div>
 
-			<form name="sendResetForm" class="login-form" ng-show="mode === 'send'">
-				<div class="tight-form last">
-					<ul class="tight-form-list">
-						<li class="tight-form-item" style="width: 78px">
-							<strong>User</strong>
-						</li>
-						<li>
-							<input type="text" name="username" class="tight-form-input last" required ng-model='formModel.userOrEmail' placeholder="email or username" style="width: 253px">
-						</li>
-					</ul>
-					<div class="clearfix"></div>
+			<form name="sendResetForm" class="login-form gf-form-group" ng-show="mode === 'send'">
+				<div class="gf-form">
+						<span class="gf-form-label width-7">User</span>
+						<input type="text" name="username" class="gf-form-input max-width-14" required ng-model='formModel.userOrEmail' placeholder="email or username">
 				</div>
 
-				<div class="login-submit-button-row">
-					<button type="submit" class="btn" ng-click="sendResetEmail();" ng-class="{'btn-inverse': !sendResetForm.$valid, 'btn-primary': sendResetForm.$valid}">
+				<div class="gf-form-button-row">
+					<button type="submit" class="btn btn-large" ng-click="sendResetEmail();" ng-class="{'btn-inverse': !sendResetForm.$valid, 'btn-primary': sendResetForm.$valid}">
 						Send reset instructions
 					</button>
 				</div>
 			</form>
 
-			<h5 ng-if="mode === 'email-sent'" style="text-align: center; padding: 20px;">
+			<h5 style="text-align: center; padding: 20px;" ng-if="mode === 'email-sent'">
 				An email with a reset link as been sent to the email address, you should receive it shortly.
 			</h5>
 
-			<form name="resetForm" class="login-form" ng-show="mode === 'reset'">
-				<div class="tight-form">
-					<ul class="tight-form-list">
-						<li class="tight-form-item" style="width: 125px">
-							<strong>New Password</strong>
-						</li>
-						<li>
-							<input type="password" name="NewPassword" class="tight-form-input last" required ng-minlength="4" ng-model='formModel.newPassword' placeholder="password" style="width: 207px" watch-change="formModel.newPassword = inputValue;">
-						</li>
-					</ul>
-					<div class="clearfix"></div>
+			<form name="resetForm" class="login-form gf-form-group" ng-show="mode === 'reset'">
+				<div class="gf-form">
+					<span class="gf-form-label width-10">New Password</span>
+					<input type="password" name="NewPassword" class="gf-form-input max-width-14" required ng-minlength="4" ng-model='formModel.newPassword' placeholder="password" watch-change="formModel.newPassword = inputValue;">
 				</div>
-				<div class="tight-form last">
-					<ul class="tight-form-list">
-						<li class="tight-form-item" style="width: 125px">
-							<strong>Confirm Password</strong>
-						</li>
-						<li>
-							<input type="password" name="ConfirmPassword" class="tight-form-input last" required ng-minlength="4" ng-model='formModel.confirmPassword' placeholder="confirm password" style="width: 207px">
-						</li>
-					</ul>
-					<div class="clearfix"></div>
+				<div class="gf-form">
+					<span class="gf-form-label width-10">Confirm Password</span>
+					<input type="password" name="ConfirmPassword" class="gf-form-input max-width-14" required ng-minlength="4" ng-model='formModel.confirmPassword' placeholder="confirm password">
 				</div>
 
 				<div style="margin-left: 141px; width: 207px;">
 					<password-strength password="formModel.newPassword"></password-strength>
 				</div>
 
-				<div class="login-submit-button-row">
+				<div class="gf-form-button-row">
 					<button type="submit" class="btn" ng-click="submitReset();" ng-class="{'btn-inverse': !resetForm.$valid, 'btn-primary': resetForm.$valid}">
 						Reset Password
 					</button>
 				</div>
 			</form>
-
-			<div class="clearfix"></div>
 		</div>
 
-		<div class="row" style="margin-top: 40px">
+		<div class="row" style="margin-top: 20px">
 			<div class="text-center">
-				<a href="login">
-					Back to login
-				</a>
+				<a href="login">Back to login</a>
 			</div>
 		</div>
-
 	</div>
 </div>
 

+ 33 - 50
public/app/partials/roweditor.html

@@ -1,64 +1,47 @@
-
-<div class="gf-box-header">
-	<div class="gf-box-title">
-		<i class="fa fa-th-list"></i>
+<div class="tabbed-view-header">
+	<h2 class="tabbed-view-title">
 		Row settings
-	</div>
-
-	<div ng-model="editor.index" bs-tabs style="text-transform:capitalize;">
-		<div ng-repeat="tab in ['General']" data-title="{{tab}}">
-		</div>
-	</div>
+	</h2>
 
-	<button class="gf-box-header-close-btn" ng-click="dismiss();">
+	<button class="tabbed-view-close-btn" ng-click="dismiss();">
 		<i class="fa fa-remove"></i>
 	</button>
 </div>
 
-<div class="gf-box-body">
+<div class="tabbed-view-body">
+	<div class="row">
+		<div class="col-md-8">
+			<div class="page-heading">
+				<h5>Row details</h5>
+			</div>
+			<div class="gf-form-group">
+				<div class="gf-form-inline">
 
-	<div class="editor-row">
-		<div class="section">
-			<h5>Row details</h5>
-			<div class="tight-form last">
-				<ul class="tight-form-list">
-					<li class="tight-form-item">
-						Title
-					</li>
-					<li>
-						<input type="text" class="input-xlarge tight-form-input" ng-model='row.title'></input>
-					</li>
-					<li class="tight-form-item">
-						Height
-					</li>
-					<li>
-						<input type="text" class="input-small tight-form-input" ng-model='row.height'></input>
-					</li>
-					<li class="tight-form-item last">
-						<label class="checkbox-label" for="row.showTitle">Show Title</label>
-						<input class="cr1" id="row.showTitle" type="checkbox" ng-model="row.showTitle" ng-checked="row.showTitle">
-						<label for="row.showTitle" class="cr1"></label>
-					</li>
-				</ul>
-				<div class="clearfix"></div>
+					<div class="gf-form">
+						<span class="gf-form-label width-6">Title</span>
+						<input type="text" class="gf-form-input max-width-14" ng-model='row.title'></input>
+					</div>
+					<div class="gf-form">
+						<span class="gf-form-label width-6">Height</span>
+						<input type="text" class="gf-form-input max-width-8" ng-model='row.height'></input>
+						<editor-checkbox text="Show Title" model="row.showTitle"></editor-checkbox>
+					</div>
+				</div>
 			</div>
 		</div>
-		<div class="section">
-			<h5>Templating options</h5>
-			<div class="tight-form last">
-				<ul class="tight-form-list">
-					<li class="tight-form-item">
-						Repeat Row
-					</li>
-					<li>
-						<select class="input-small tight-form-input last" ng-model="row.repeat" ng-options="f.name as f.name for f in dashboard.templating.list">
+		<div class="col-md-4">
+			<div class="page-heading">
+				<h5>Templating options</h5>
+			</div>
+			<div class="gf-form-group">
+				<div class="gf-form">
+					<span class="gf-form-label">Repeat Row</span>
+					<div class="gf-form-select-wrapper max-width-10">
+						<select class="gf-form-input" ng-model="row.repeat" ng-options="f.name as f.name for f in dashboard.templating.list">
 							<option value=""></option>
-						</select>
-					</li>
-				</ul>
-				<div class="clearfix"></div>
+					</div>
+				</div>
 			</div>
 		</div>
 	</div>
-
 </div>

+ 2 - 2
public/app/partials/valueSelectDropdown.html

@@ -1,5 +1,5 @@
 <div class="variable-link-wrapper">
-	<a ng-click="vm.show()" class="variable-value-link tight-form-item">
+	<a ng-click="vm.show()" class="variable-value-link">
 		{{vm.linkText}}
 		<span ng-repeat="tag in vm.selectedTags" bs-tooltip='tag.valuesText' data-placement="bottom">
 			<span class="label-tag"tag-color-from-name="tag.text">
@@ -10,7 +10,7 @@
 		<i class="fa fa-caret-down"></i>
 	</a>
 
-	<input type="text" class="tight-form-clear-input input-small" style="display: none" ng-keydown="vm.keyDown($event)" ng-model="vm.search.query" ng-change="vm.queryChanged()" ></input>
+	<input type="text" class="hidden-input input-small" style="display: none" ng-keydown="vm.keyDown($event)" ng-model="vm.search.query" ng-change="vm.queryChanged()" ></input>
 
 	<div class="variable-value-dropdown" ng-if="vm.dropdownVisible" ng-class="{'multi': vm.variable.multi, 'single': !vm.variable.multi}">
 		<div class="variable-options-wrapper">

+ 2 - 0
public/app/plugins/datasource/cloudwatch/annotation_query.d.ts

@@ -0,0 +1,2 @@
+declare var test: any;
+export default test;

+ 105 - 0
public/app/plugins/datasource/cloudwatch/annotation_query.js

@@ -0,0 +1,105 @@
+define([
+  'lodash',
+],
+function (_) {
+  'use strict';
+
+  function CloudWatchAnnotationQuery(datasource, annotation, $q, templateSrv) {
+    this.datasource = datasource;
+    this.annotation = annotation;
+    this.$q = $q;
+    this.templateSrv = templateSrv;
+  }
+
+  CloudWatchAnnotationQuery.prototype.process = function(from, to) {
+    var self = this;
+    var usePrefixMatch = this.annotation.prefixMatching;
+    var region = this.templateSrv.replace(this.annotation.region);
+    var namespace = this.templateSrv.replace(this.annotation.namespace);
+    var metricName = this.templateSrv.replace(this.annotation.metricName);
+    var dimensions = this.datasource.convertDimensionFormat(this.annotation.dimensions);
+    var statistics = _.map(this.annotation.statistics, function(s) { return self.templateSrv.replace(s); });
+    var defaultPeriod = usePrefixMatch ? '' : '300';
+    var period = this.annotation.period || defaultPeriod;
+    period = parseInt(period, 10);
+    var actionPrefix = this.annotation.actionPrefix || '';
+    var alarmNamePrefix = this.annotation.alarmNamePrefix || '';
+
+    var d = this.$q.defer();
+    var allQueryPromise;
+    if (usePrefixMatch) {
+      allQueryPromise = [
+        this.datasource.performDescribeAlarms(region, actionPrefix, alarmNamePrefix, [], '').then(function(alarms) {
+          alarms.MetricAlarms = self.filterAlarms(alarms, namespace, metricName, dimensions, statistics, period);
+          return alarms;
+        })
+      ];
+    } else {
+      if (!region || !namespace || !metricName || _.isEmpty(statistics)) { return this.$q.when([]); }
+
+      allQueryPromise = _.map(statistics, function(statistic) {
+        return self.datasource.performDescribeAlarmsForMetric(region, namespace, metricName, dimensions, statistic, period);
+      });
+    }
+    this.$q.all(allQueryPromise).then(function(alarms) {
+      var eventList = [];
+
+      var start = self.datasource.convertToCloudWatchTime(from, false);
+      var end = self.datasource.convertToCloudWatchTime(to, true);
+      _.chain(alarms)
+      .pluck('MetricAlarms')
+      .flatten()
+      .each(function(alarm) {
+        if (!alarm) {
+          d.resolve(eventList);
+          return;
+        }
+
+        self.datasource.performDescribeAlarmHistory(region, alarm.AlarmName, start, end).then(function(history) {
+          _.each(history.AlarmHistoryItems, function(h) {
+            var event = {
+              annotation: self.annotation,
+              time: Date.parse(h.Timestamp),
+              title: h.AlarmName,
+              tags: [h.HistoryItemType],
+              text: h.HistorySummary
+            };
+
+            eventList.push(event);
+          });
+
+          d.resolve(eventList);
+        });
+      });
+    });
+
+    return d.promise;
+  };
+
+  CloudWatchAnnotationQuery.prototype.filterAlarms = function(alarms, namespace, metricName, dimensions, statistics, period) {
+    return _.filter(alarms.MetricAlarms, function(alarm) {
+      if (!_.isEmpty(namespace) && alarm.Namespace !== namespace) {
+        return false;
+      }
+      if (!_.isEmpty(metricName) && alarm.MetricName !== metricName) {
+        return false;
+      }
+      var sd = function(d) {
+        return d.Name;
+      };
+      var isSameDimensions = JSON.stringify(_.sortBy(alarm.Dimensions, sd)) === JSON.stringify(_.sortBy(dimensions, sd));
+      if (!_.isEmpty(dimensions) && !isSameDimensions) {
+        return false;
+      }
+      if (!_.isEmpty(statistics) && !_.contains(statistics, alarm.Statistic)) {
+        return false;
+      }
+      if (!_.isNaN(period) && alarm.Period !== period) {
+        return false;
+      }
+      return true;
+    });
+  };
+
+  return CloudWatchAnnotationQuery;
+});

+ 21 - 58
public/app/plugins/datasource/cloudwatch/datasource.js

@@ -3,8 +3,9 @@ define([
   'lodash',
   'moment',
   'app/core/utils/datemath',
+  './annotation_query',
 ],
-function (angular, _, moment, dateMath) {
+function (angular, _, moment, dateMath, CloudWatchAnnotationQuery) {
   'use strict';
 
   /** @ngInject */
@@ -15,9 +16,10 @@ function (angular, _, moment, dateMath) {
     this.proxyUrl = instanceSettings.url;
     this.defaultRegion = instanceSettings.jsonData.defaultRegion;
 
+    var self = this;
     this.query = function(options) {
-      var start = convertToCloudWatchTime(options.range.from, false);
-      var end = convertToCloudWatchTime(options.range.to, true);
+      var start = self.convertToCloudWatchTime(options.range.from, false);
+      var end = self.convertToCloudWatchTime(options.range.to, true);
 
       var queries = [];
       options = angular.copy(options);
@@ -30,7 +32,7 @@ function (angular, _, moment, dateMath) {
         query.region = templateSrv.replace(target.region, options.scopedVars);
         query.namespace = templateSrv.replace(target.namespace, options.scopedVars);
         query.metricName = templateSrv.replace(target.metricName, options.scopedVars);
-        query.dimensions = convertDimensionFormat(target.dimensions, options.scopedVars);
+        query.dimensions = self.convertDimensionFormat(target.dimensions, options.scopedVars);
         query.statistics = target.statistics;
 
         var range = end - start;
@@ -117,7 +119,7 @@ function (angular, _, moment, dateMath) {
         parameters: {
           namespace: templateSrv.replace(namespace),
           metricName: templateSrv.replace(metricName),
-          dimensions: convertDimensionFormat(filterDimensions, {}),
+          dimensions: this.convertDimensionFormat(filterDimensions, {}),
         }
       };
 
@@ -206,6 +208,14 @@ function (angular, _, moment, dateMath) {
       return $q.when([]);
     };
 
+    this.performDescribeAlarms = function(region, actionPrefix, alarmNamePrefix, alarmNames, stateValue) {
+      return this.awsRequest({
+        region: region,
+        action: 'DescribeAlarms',
+        parameters: { actionPrefix: actionPrefix, alarmNamePrefix: alarmNamePrefix, alarmNames: alarmNames, stateValue: stateValue }
+      });
+    };
+
     this.performDescribeAlarmsForMetric = function(region, namespace, metricName, dimensions, statistic, period) {
       return this.awsRequest({
         region: region,
@@ -223,55 +233,8 @@ function (angular, _, moment, dateMath) {
     };
 
     this.annotationQuery = function(options) {
-      var annotation = options.annotation;
-      var region = templateSrv.replace(annotation.region);
-      var namespace = templateSrv.replace(annotation.namespace);
-      var metricName = templateSrv.replace(annotation.metricName);
-      var dimensions = convertDimensionFormat(annotation.dimensions);
-      var statistics = _.map(annotation.statistics, function(s) { return templateSrv.replace(s); });
-      var period = annotation.period || '300';
-      period = parseInt(period, 10);
-
-      if (!region || !namespace || !metricName || _.isEmpty(statistics)) { return $q.when([]); }
-
-      var d = $q.defer();
-      var self = this;
-      var allQueryPromise = _.map(statistics, function(statistic) {
-        return self.performDescribeAlarmsForMetric(region, namespace, metricName, dimensions, statistic, period);
-      });
-      $q.all(allQueryPromise).then(function(alarms) {
-        var eventList = [];
-
-        var start = convertToCloudWatchTime(options.range.from, false);
-        var end = convertToCloudWatchTime(options.range.to, true);
-        _.chain(alarms)
-        .pluck('MetricAlarms')
-        .flatten()
-        .each(function(alarm) {
-          if (!alarm) {
-            d.resolve(eventList);
-            return;
-          }
-
-          self.performDescribeAlarmHistory(region, alarm.AlarmName, start, end).then(function(history) {
-            _.each(history.AlarmHistoryItems, function(h) {
-              var event = {
-                annotation: annotation,
-                time: Date.parse(h.Timestamp),
-                title: h.AlarmName,
-                tags: [h.HistoryItemType],
-                text: h.HistorySummary
-              };
-
-              eventList.push(event);
-            });
-
-            d.resolve(eventList);
-          });
-        });
-      });
-
-      return d.promise;
+      var annotationQuery = new CloudWatchAnnotationQuery(this, options.annotation, $q, templateSrv);
+      return annotationQuery.process(options.range.from, options.range.to);
     };
 
     this.testDatasource = function() {
@@ -347,21 +310,21 @@ function (angular, _, moment, dateMath) {
       });
     }
 
-    function convertToCloudWatchTime(date, roundUp) {
+    this.convertToCloudWatchTime = function(date, roundUp) {
       if (_.isString(date)) {
         date = dateMath.parse(date, roundUp);
       }
       return Math.round(date.valueOf() / 1000);
-    }
+    };
 
-    function convertDimensionFormat(dimensions, scopedVars) {
+    this.convertDimensionFormat = function(dimensions, scopedVars) {
       return _.map(dimensions, function(value, key) {
         return {
           Name: templateSrv.replace(key, scopedVars),
           Value: templateSrv.replace(value, scopedVars)
         };
       });
-    }
+    };
 
   }
 

+ 18 - 0
public/app/plugins/datasource/cloudwatch/partials/annotations.editor.html

@@ -1 +1,19 @@
 <cloudwatch-query-parameter target="ctrl.annotation" datasource="ctrl.datasource"></cloudwatch-query-parameter>
+<div class="editor-row">
+	<div class="section">
+		<h5>Prefix matching</h5>
+		<div class="editor-option">
+			<editor-checkbox text="Enable" model="ctrl.annotation.prefixMatching"></editor-checkbox>
+		</div>
+
+		<div class="editor-option" ng-if="ctrl.annotation.prefixMatching">
+			<label class="small">Action</label>
+			<input type="text" class="input-small" ng-model='ctrl.annotation.actionPrefix'></input>
+		</div>
+
+		<div class="editor-option" ng-if="ctrl.annotation.prefixMatching">
+			<label class="small">Alarm Name</label>
+			<input type="text" class="input-small" ng-model='ctrl.annotation.alarmNamePrefix'></input>
+		</div>
+	</div>
+</div>

+ 81 - 0
public/app/plugins/datasource/cloudwatch/specs/annotation_query_specs.ts

@@ -0,0 +1,81 @@
+import "../datasource";
+import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common';
+import moment from 'moment';
+import helpers from 'test/specs/helpers';
+import {CloudWatchDatasource} from "../datasource";
+import CloudWatchAnnotationQuery from '../annotation_query';
+
+describe('CloudWatchAnnotationQuery', function() {
+  var ctx = new helpers.ServiceTestContext();
+  var instanceSettings = {
+    jsonData: {defaultRegion: 'us-east-1', access: 'proxy'},
+  };
+
+  beforeEach(angularMocks.module('grafana.core'));
+  beforeEach(angularMocks.module('grafana.services'));
+  beforeEach(angularMocks.module('grafana.controllers'));
+  beforeEach(ctx.providePhase(['templateSrv', 'backendSrv']));
+
+  beforeEach(angularMocks.inject(function($q, $rootScope, $httpBackend, $injector) {
+    ctx.$q = $q;
+    ctx.$httpBackend =  $httpBackend;
+    ctx.$rootScope = $rootScope;
+    ctx.ds = $injector.instantiate(CloudWatchDatasource, {instanceSettings: instanceSettings});
+  }));
+
+  describe('When performing annotationQuery', function() {
+    var parameter = {
+      annotation: {
+        region: 'us-east-1',
+        namespace: 'AWS/EC2',
+        metricName: 'CPUUtilization',
+        dimensions: {
+          InstanceId: 'i-12345678'
+        },
+        statistics: ['Average'],
+        period: 300
+      },
+      range: {
+        from: moment(1443438674760),
+        to: moment(1443460274760)
+      }
+    };
+    var alarmResponse = {
+      MetricAlarms: [
+        {
+          AlarmName: 'test_alarm_name'
+        }
+      ]
+    };
+    var historyResponse = {
+      AlarmHistoryItems: [
+        {
+          Timestamp: '2015-01-01T00:00:00.000Z',
+          HistoryItemType: 'StateUpdate',
+          AlarmName: 'test_alarm_name',
+          HistoryData: '{}',
+          HistorySummary: 'test_history_summary'
+        }
+      ]
+    };
+    beforeEach(function() {
+      ctx.backendSrv.datasourceRequest = function(params) {
+        switch (params.data.action) {
+        case 'DescribeAlarmsForMetric':
+          return ctx.$q.when({data: alarmResponse});
+        case 'DescribeAlarmHistory':
+          return ctx.$q.when({data: historyResponse});
+        }
+      };
+    });
+    it('should return annotation list', function(done) {
+      var annotationQuery = new CloudWatchAnnotationQuery(ctx.ds, parameter.annotation, ctx.$q, ctx.templateSrv);
+      annotationQuery.process(parameter.range.from, parameter.range.to).then(function(result) {
+        expect(result[0].title).to.be('test_alarm_name');
+        expect(result[0].text).to.be('test_history_summary');
+        done();
+      });
+      ctx.$rootScope.$apply();
+    });
+  });
+});

+ 0 - 55
public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts

@@ -187,59 +187,4 @@ describe('CloudWatchDatasource', function() {
       expect(scenario.request.data.action).to.be('ListMetrics');
     });
   });
-
-  describe('When performing annotationQuery', function() {
-    var parameter = {
-      annotation: {
-        region: 'us-east-1',
-        namespace: 'AWS/EC2',
-        metricName: 'CPUUtilization',
-        dimensions: {
-          InstanceId: 'i-12345678'
-        },
-        statistics: ['Average'],
-        period: 300
-      },
-      range: {
-        from: moment(1443438674760),
-        to: moment(1443460274760)
-      }
-    };
-    var alarmResponse = {
-      MetricAlarms: [
-        {
-          AlarmName: 'test_alarm_name'
-        }
-      ]
-    };
-    var historyResponse = {
-      AlarmHistoryItems: [
-        {
-          Timestamp: '2015-01-01T00:00:00.000Z',
-          HistoryItemType: 'StateUpdate',
-          AlarmName: 'test_alarm_name',
-          HistoryData: '{}',
-          HistorySummary: 'test_history_summary'
-        }
-      ]
-    };
-    beforeEach(function() {
-      ctx.backendSrv.datasourceRequest = function(params) {
-        switch (params.data.action) {
-        case 'DescribeAlarmsForMetric':
-          return ctx.$q.when({data: alarmResponse});
-        case 'DescribeAlarmHistory':
-          return ctx.$q.when({data: historyResponse});
-        }
-      };
-    });
-    it('should return annotation list', function(done) {
-      ctx.ds.annotationQuery(parameter).then(function(result) {
-        expect(result[0].title).to.be('test_alarm_name');
-        expect(result[0].text).to.be('test_history_summary');
-        done();
-      });
-      ctx.$rootScope.$apply();
-    });
-  });
 });

Некоторые файлы не были показаны из-за большого количества измененных файлов