diff --git a/.hooks/pre-commit b/.hooks/pre-commit
index 83ec0adba1a..2519d4c44f4 100755
--- a/.hooks/pre-commit
+++ b/.hooks/pre-commit
@@ -5,3 +5,6 @@ if [ $? -gt 0 ]; then
echo "Some files aren't formatted, please run 'go fmt ./pkg/...' to format your source code before committing"
exit 1
fi
+
+
+grunt test
diff --git a/docs/sources/datasources/graphite.md b/docs/sources/datasources/graphite.md
index c4cae0b0c65..af53d0bf60c 100644
--- a/docs/sources/datasources/graphite.md
+++ b/docs/sources/datasources/graphite.md
@@ -25,7 +25,7 @@ Name | Description
------------ | -------------
Name | The data source name, important that this is the same as in Grafana v1.x if you plan to import old dashboards.
Default | Default data source means that it will be pre-selected for new panels.
-Url | The http protocol, ip and port of you graphite-web or graphite-api install.
+Url | The http protocol, ip and port of your graphite-web or graphite-api install.
Access | Proxy = access via Grafana backend, Direct = access directory from browser.
diff --git a/pkg/api/api.go b/pkg/api/api.go
index 1770b210439..ea434ed71da 100644
--- a/pkg/api/api.go
+++ b/pkg/api/api.go
@@ -189,8 +189,8 @@ func Register(r *macaron.Macaron) {
r.Get("/:id/items", ValidateOrgPlaylist, wrap(GetPlaylistItems))
r.Get("/:id/dashboards", ValidateOrgPlaylist, wrap(GetPlaylistDashboards))
r.Delete("/:id", reqEditorRole, ValidateOrgPlaylist, wrap(DeletePlaylist))
- r.Put("/:id", reqEditorRole, bind(m.UpdatePlaylistQuery{}), ValidateOrgPlaylist, wrap(UpdatePlaylist))
- r.Post("/", reqEditorRole, bind(m.CreatePlaylistQuery{}), wrap(CreatePlaylist))
+ r.Put("/:id", reqEditorRole, bind(m.UpdatePlaylistCommand{}), ValidateOrgPlaylist, wrap(UpdatePlaylist))
+ r.Post("/", reqEditorRole, bind(m.CreatePlaylistCommand{}), wrap(CreatePlaylist))
})
// Search
diff --git a/pkg/api/playlist.go b/pkg/api/playlist.go
index d9503114ff3..0b017d6e2cb 100644
--- a/pkg/api/playlist.go
+++ b/pkg/api/playlist.go
@@ -2,11 +2,12 @@ package api
import (
"errors"
+ "strconv"
+
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/middleware"
m "github.com/grafana/grafana/pkg/models"
- "strconv"
)
func ValidateOrgPlaylist(c *middleware.Context) {
@@ -33,8 +34,8 @@ func SearchPlaylists(c *middleware.Context) Response {
limit = 1000
}
- searchQuery := m.PlaylistQuery{
- Title: query,
+ searchQuery := m.GetPlaylistsQuery{
+ Name: query,
Limit: limit,
OrgId: c.OrgId,
}
@@ -59,7 +60,7 @@ func GetPlaylist(c *middleware.Context) Response {
dto := &m.PlaylistDTO{
Id: cmd.Result.Id,
- Title: cmd.Result.Title,
+ Name: cmd.Result.Name,
Interval: cmd.Result.Interval,
OrgId: cmd.Result.OrgId,
Items: playlistDTOs,
@@ -159,7 +160,7 @@ func GetPlaylistDashboards(c *middleware.Context) Response {
func DeletePlaylist(c *middleware.Context) Response {
id := c.ParamsInt64(":id")
- cmd := m.DeletePlaylistQuery{Id: id}
+ cmd := m.DeletePlaylistCommand{Id: id, OrgId: c.OrgId}
if err := bus.Dispatch(&cmd); err != nil {
return ApiError(500, "Failed to delete playlist", err)
}
@@ -167,28 +168,28 @@ func DeletePlaylist(c *middleware.Context) Response {
return Json(200, "")
}
-func CreatePlaylist(c *middleware.Context, query m.CreatePlaylistQuery) Response {
- query.OrgId = c.OrgId
- err := bus.Dispatch(&query)
- if err != nil {
+func CreatePlaylist(c *middleware.Context, cmd m.CreatePlaylistCommand) Response {
+ cmd.OrgId = c.OrgId
+
+ if err := bus.Dispatch(&cmd); err != nil {
return ApiError(500, "Failed to create playlist", err)
}
- return Json(200, query.Result)
+ return Json(200, cmd.Result)
}
-func UpdatePlaylist(c *middleware.Context, query m.UpdatePlaylistQuery) Response {
- err := bus.Dispatch(&query)
+func UpdatePlaylist(c *middleware.Context, cmd m.UpdatePlaylistCommand) Response {
+ cmd.OrgId = c.OrgId
+
+ if err := bus.Dispatch(&cmd); err != nil {
+ return ApiError(500, "Failed to save playlist", err)
+ }
+
+ playlistDTOs, err := LoadPlaylistItemDTOs(cmd.Id)
if err != nil {
return ApiError(500, "Failed to save playlist", err)
}
- playlistDTOs, err := LoadPlaylistItemDTOs(query.Id)
- if err != nil {
- return ApiError(500, "Failed to save playlist", err)
- }
-
- query.Result.Items = playlistDTOs
-
- return Json(200, query.Result)
+ cmd.Result.Items = playlistDTOs
+ return Json(200, cmd.Result)
}
diff --git a/pkg/login/auth.go b/pkg/login/auth.go
index 38d87444e23..f2034476f81 100644
--- a/pkg/login/auth.go
+++ b/pkg/login/auth.go
@@ -3,6 +3,7 @@ package login
import (
"errors"
+ "crypto/subtle"
"github.com/grafana/grafana/pkg/bus"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
@@ -56,7 +57,7 @@ func loginUsingGrafanaDB(query *LoginUserQuery) error {
user := userQuery.Result
passwordHashed := util.EncodePassword(query.Password, user.Salt)
- if passwordHashed != user.Password {
+ if subtle.ConstantTimeCompare([]byte(passwordHashed), []byte(user.Password)) != 1 {
return ErrInvalidCredentials
}
diff --git a/pkg/models/playlist.go b/pkg/models/playlist.go
index 5d861dc11b0..4c37c369b3e 100644
--- a/pkg/models/playlist.go
+++ b/pkg/models/playlist.go
@@ -13,14 +13,14 @@ var (
// Playlist model
type Playlist struct {
Id int64 `json:"id"`
- Title string `json:"title"`
+ Name string `json:"name"`
Interval string `json:"interval"`
OrgId int64 `json:"-"`
}
type PlaylistDTO struct {
Id int64 `json:"id"`
- Title string `json:"title"`
+ Name string `json:"name"`
Interval string `json:"interval"`
OrgId int64 `json:"-"`
Items []PlaylistItemDTO `json:"items"`
@@ -71,35 +71,47 @@ type PlaylistDashboardDto struct {
//
// COMMANDS
//
-type PlaylistQuery struct {
- Title string
- Limit int
- OrgId int64
- Result Playlists
-}
-
-type UpdatePlaylistQuery struct {
- Id int64
- Title string
- Type string
- Interval string
- Items []PlaylistItemDTO
+type UpdatePlaylistCommand struct {
+ OrgId int64 `json:"-"`
+ Id int64 `json:"id" binding:"Required"`
+ Name string `json:"name" binding:"Required"`
+ Type string `json:"type"`
+ Interval string `json:"interval"`
+ Data []int64 `json:"data"`
+ Items []PlaylistItemDTO `json:"items"`
Result *PlaylistDTO
}
-type CreatePlaylistQuery struct {
- Title string
- Type string
- Interval string
- Data []int64
- OrgId int64
- Items []PlaylistItemDTO
+type CreatePlaylistCommand struct {
+ Name string `json:"name" binding:"Required"`
+ Type string `json:"type"`
+ Interval string `json:"interval"`
+ Data []int64 `json:"data"`
+ Items []PlaylistItemDTO `json:"items"`
+ OrgId int64 `json:"-"`
Result *Playlist
}
+type DeletePlaylistCommand struct {
+ Id int64
+ OrgId int64
+}
+
+//
+// QUERIES
+//
+
+type GetPlaylistsQuery struct {
+ Name string
+ Limit int
+ OrgId int64
+
+ Result Playlists
+}
+
type GetPlaylistByIdQuery struct {
Id int64
Result *Playlist
@@ -114,7 +126,3 @@ type GetPlaylistDashboardsQuery struct {
DashboardIds []int64
Result *PlaylistDashboards
}
-
-type DeletePlaylistQuery struct {
- Id int64
-}
diff --git a/pkg/services/sqlstore/migrations/playlist_mig.go b/pkg/services/sqlstore/migrations/playlist_mig.go
index b972994e45b..636fc091006 100644
--- a/pkg/services/sqlstore/migrations/playlist_mig.go
+++ b/pkg/services/sqlstore/migrations/playlist_mig.go
@@ -3,20 +3,23 @@ package migrations
import . "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
func addPlaylistMigrations(mg *Migrator) {
- playlistV1 := Table{
+ mg.AddMigration("Drop old table playlist table", NewDropTableMigration("playlist"))
+ mg.AddMigration("Drop old table playlist_item table", NewDropTableMigration("playlist_item"))
+
+ playlistV2 := Table{
Name: "playlist",
Columns: []*Column{
{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
- {Name: "title", Type: DB_NVarchar, Length: 255, Nullable: false},
+ {Name: "name", Type: DB_NVarchar, Length: 255, Nullable: false},
{Name: "interval", Type: DB_NVarchar, Length: 255, Nullable: false},
{Name: "org_id", Type: DB_BigInt, Nullable: false},
},
}
// create table
- mg.AddMigration("create playlist table v1", NewAddTableMigration(playlistV1))
+ mg.AddMigration("create playlist table v2", NewAddTableMigration(playlistV2))
- playlistItemV1 := Table{
+ playlistItemV2 := Table{
Name: "playlist_item",
Columns: []*Column{
{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
@@ -28,5 +31,5 @@ func addPlaylistMigrations(mg *Migrator) {
},
}
- mg.AddMigration("create playlist item table v1", NewAddTableMigration(playlistItemV1))
+ mg.AddMigration("create playlist item table v2", NewAddTableMigration(playlistItemV2))
}
diff --git a/pkg/services/sqlstore/playlist.go b/pkg/services/sqlstore/playlist.go
index fd3a49b2b2a..ee0b3b950c7 100644
--- a/pkg/services/sqlstore/playlist.go
+++ b/pkg/services/sqlstore/playlist.go
@@ -2,6 +2,7 @@ package sqlstore
import (
"fmt"
+
"github.com/go-xorm/xorm"
"github.com/grafana/grafana/pkg/bus"
@@ -18,13 +19,13 @@ func init() {
bus.AddHandler("sql", GetPlaylistItem)
}
-func CreatePlaylist(query *m.CreatePlaylistQuery) error {
+func CreatePlaylist(cmd *m.CreatePlaylistCommand) error {
var err error
playlist := m.Playlist{
- Title: query.Title,
- Interval: query.Interval,
- OrgId: query.OrgId,
+ Name: cmd.Name,
+ Interval: cmd.Interval,
+ OrgId: cmd.OrgId,
}
_, err = x.Insert(&playlist)
@@ -32,7 +33,7 @@ func CreatePlaylist(query *m.CreatePlaylistQuery) error {
fmt.Printf("%v", playlist.Id)
playlistItems := make([]m.PlaylistItem, 0)
- for _, item := range query.Items {
+ for _, item := range cmd.Items {
playlistItems = append(playlistItems, m.PlaylistItem{
PlaylistId: playlist.Id,
Type: item.Type,
@@ -44,40 +45,40 @@ func CreatePlaylist(query *m.CreatePlaylistQuery) error {
_, err = x.Insert(&playlistItems)
- query.Result = &playlist
+ cmd.Result = &playlist
return err
}
-func UpdatePlaylist(query *m.UpdatePlaylistQuery) error {
+func UpdatePlaylist(cmd *m.UpdatePlaylistCommand) error {
var err error
- x.Logger.SetLevel(5)
playlist := m.Playlist{
- Id: query.Id,
- Title: query.Title,
- Interval: query.Interval,
+ Id: cmd.Id,
+ OrgId: cmd.OrgId,
+ Name: cmd.Name,
+ Interval: cmd.Interval,
}
- existingPlaylist := x.Where("id = ?", query.Id).Find(m.Playlist{})
+ existingPlaylist := x.Where("id = ? AND org_id = ?", cmd.Id, cmd.OrgId).Find(m.Playlist{})
if existingPlaylist == nil {
return m.ErrPlaylistNotFound
}
- query.Result = &m.PlaylistDTO{
+ cmd.Result = &m.PlaylistDTO{
Id: playlist.Id,
OrgId: playlist.OrgId,
- Title: playlist.Title,
+ Name: playlist.Name,
Interval: playlist.Interval,
}
- _, err = x.Id(query.Id).Cols("id", "title", "timespan").Update(&playlist)
+ _, err = x.Id(cmd.Id).Cols("id", "name", "interval").Update(&playlist)
if err != nil {
return err
}
rawSql := "DELETE FROM playlist_item WHERE playlist_id = ?"
- _, err = x.Exec(rawSql, query.Id)
+ _, err = x.Exec(rawSql, cmd.Id)
if err != nil {
return err
@@ -85,7 +86,7 @@ func UpdatePlaylist(query *m.UpdatePlaylistQuery) error {
playlistItems := make([]m.PlaylistItem, 0)
- for _, item := range query.Items {
+ for _, item := range cmd.Items {
playlistItems = append(playlistItems, m.PlaylistItem{
PlaylistId: playlist.Id,
Type: item.Type,
@@ -113,33 +114,33 @@ func GetPlaylist(query *m.GetPlaylistByIdQuery) error {
return err
}
-func DeletePlaylist(query *m.DeletePlaylistQuery) error {
- if query.Id == 0 {
+func DeletePlaylist(cmd *m.DeletePlaylistCommand) error {
+ if cmd.Id == 0 {
return m.ErrCommandValidationFailed
}
return inTransaction(func(sess *xorm.Session) error {
- var rawPlaylistSql = "DELETE FROM playlist WHERE id = ?"
- _, err := sess.Exec(rawPlaylistSql, query.Id)
+ var rawPlaylistSql = "DELETE FROM playlist WHERE id = ? and org_id = ?"
+ _, err := sess.Exec(rawPlaylistSql, cmd.Id, cmd.OrgId)
if err != nil {
return err
}
var rawItemSql = "DELETE FROM playlist_item WHERE playlist_id = ?"
- _, err2 := sess.Exec(rawItemSql, query.Id)
+ _, err2 := sess.Exec(rawItemSql, cmd.Id)
return err2
})
}
-func SearchPlaylists(query *m.PlaylistQuery) error {
+func SearchPlaylists(query *m.GetPlaylistsQuery) error {
var playlists = make(m.Playlists, 0)
sess := x.Limit(query.Limit)
- if query.Title != "" {
- sess.Where("title LIKE ?", query.Title)
+ if query.Name != "" {
+ sess.Where("name LIKE ?", query.Name)
}
sess.Where("org_id = ?", query.OrgId)
diff --git a/public/app/core/components/grafana_app.ts b/public/app/core/components/grafana_app.ts
index 35ea141d03c..15c7f7fdb3b 100644
--- a/public/app/core/components/grafana_app.ts
+++ b/public/app/core/components/grafana_app.ts
@@ -139,7 +139,8 @@ export class GrafanaCtrl {
}
}
-export function grafanaAppDirective() {
+/** @ngInject */
+export function grafanaAppDirective(playlistSrv) {
return {
restrict: 'E',
controller: GrafanaCtrl,
@@ -165,22 +166,33 @@ export function grafanaAppDirective() {
// handle document clicks that should hide things
elem.click(function(evt) {
- if ($(evt.target).parents().length === 0) {
+ var target = $(evt.target);
+ if (target.parents().length === 0) {
return;
}
+ if (target.parents('.dash-playlist-actions').length === 0) {
+ playlistSrv.stop();
+ }
+
// hide search
if (elem.find('.search-container').length > 0) {
- if ($(evt.target).parents('.search-container').length === 0) {
+ if (target.parents('.search-container').length === 0) {
scope.appEvent('hide-dash-search');
}
}
// hide sidemenu
if (!ignoreSideMenuHide && elem.find('.sidemenu').length > 0) {
- if ($(evt.target).parents('.sidemenu').length === 0) {
+ if (target.parents('.sidemenu').length === 0) {
scope.$apply(() => scope.contextSrv.toggleSideMenu());
}
}
+
+ // hide popovers
+ var popover = elem.find('.popover');
+ if (popover.length > 0 && target.parents('.graph-legend').length === 0) {
+ popover.hide();
+ }
});
}
};
diff --git a/public/app/core/components/navbar/navbar.html b/public/app/core/components/navbar/navbar.html
index ab33b9ae2e9..5c1e08de1f6 100644
--- a/public/app/core/components/navbar/navbar.html
+++ b/public/app/core/components/navbar/navbar.html
@@ -13,7 +13,7 @@
-
+
{{ctrl.title}}
diff --git a/public/app/core/components/sidemenu/sidemenu.ts b/public/app/core/components/sidemenu/sidemenu.ts
index 83a1c520089..d2a640c1345 100644
--- a/public/app/core/components/sidemenu/sidemenu.ts
+++ b/public/app/core/components/sidemenu/sidemenu.ts
@@ -41,7 +41,7 @@ export class SideMenuCtrl {
this.orgMenu = [
{section: 'You', cssClass: 'dropdown-menu-title'},
{text: 'Preferences', url: this.getUrl('/profile')},
- {text: 'Account', url: this.getUrl('/profile')},
+ {text: 'Profile', url: this.getUrl('/profile')},
];
if (this.isSignedIn) {
diff --git a/public/app/core/utils/kbn.js b/public/app/core/utils/kbn.js
index cc296324eb5..e323a9223e3 100644
--- a/public/app/core/utils/kbn.js
+++ b/public/app/core/utils/kbn.js
@@ -352,9 +352,15 @@ function($, _) {
kbn.valueFormats.gbytes = kbn.formatBuilders.binarySIPrefix('B', 3);
// Data Rate
- kbn.valueFormats.pps = kbn.formatBuilders.decimalSIPrefix('pps');
- kbn.valueFormats.bps = kbn.formatBuilders.decimalSIPrefix('bps');
- kbn.valueFormats.Bps = kbn.formatBuilders.decimalSIPrefix('Bps');
+ kbn.valueFormats.pps = kbn.formatBuilders.decimalSIPrefix('pps');
+ kbn.valueFormats.bps = kbn.formatBuilders.decimalSIPrefix('bps');
+ kbn.valueFormats.Bps = kbn.formatBuilders.decimalSIPrefix('Bps');
+ kbn.valueFormats.KBs = kbn.formatBuilders.decimalSIPrefix('Bs', 1);
+ kbn.valueFormats.Kbits = kbn.formatBuilders.decimalSIPrefix('bits', 1);
+ kbn.valueFormats.MBs = kbn.formatBuilders.decimalSIPrefix('Bs', 2);
+ kbn.valueFormats.Mbits = kbn.formatBuilders.decimalSIPrefix('bits', 2);
+ kbn.valueFormats.GBs = kbn.formatBuilders.decimalSIPrefix('Bs', 3);
+ kbn.valueFormats.Gbits = kbn.formatBuilders.decimalSIPrefix('bits', 3);
// Throughput
kbn.valueFormats.ops = kbn.formatBuilders.simpleCountUnit('ops');
@@ -595,6 +601,12 @@ function($, _) {
{text: 'packets/sec', value: 'pps'},
{text: 'bits/sec', value: 'bps'},
{text: 'bytes/sec', value: 'Bps'},
+ {text: 'kilobites/sec', value: 'Kbits'},
+ {text: 'kilobytes/sec', value: 'KBs'},
+ {text: 'megabites/sec', value: 'Mbits'},
+ {text: 'megabytes/sec', value: 'MBs'},
+ {text: 'gigabytes/sec', value: 'GBs'},
+ {text: 'gigabites/sec', value: 'Gbits'},
]
},
{
diff --git a/public/app/features/apps/partials/edit.html b/public/app/features/apps/partials/edit.html
index cb3adece43c..fc09dc4754c 100644
--- a/public/app/features/apps/partials/edit.html
+++ b/public/app/features/apps/partials/edit.html
@@ -1,9 +1,8 @@
-