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 @@ - + - +
diff --git a/public/app/features/dashboard/all.js b/public/app/features/dashboard/all.js index f2716979903..9d7cd06019b 100644 --- a/public/app/features/dashboard/all.js +++ b/public/app/features/dashboard/all.js @@ -2,11 +2,11 @@ define([ './dashboardCtrl', './dashboardLoaderSrv', './dashnav/dashnav', + './submenu/submenu', './saveDashboardAsCtrl', './rowCtrl', './shareModalCtrl', './shareSnapshotCtrl', - './submenuCtrl', './dashboardSrv', './keybindings', './viewStateSrv', diff --git a/public/app/features/dashboard/submenu/submenu.html b/public/app/features/dashboard/submenu/submenu.html new file mode 100644 index 00000000000..b7bf0e89232 --- /dev/null +++ b/public/app/features/dashboard/submenu/submenu.html @@ -0,0 +1,30 @@ + diff --git a/public/app/features/dashboard/submenu/submenu.ts b/public/app/features/dashboard/submenu/submenu.ts new file mode 100644 index 00000000000..7e304d88464 --- /dev/null +++ b/public/app/features/dashboard/submenu/submenu.ts @@ -0,0 +1,46 @@ +/// + +import angular from 'angular'; + +export class SubmenuCtrl { + annotations: any; + variables: any; + dashboard: any; + + constructor(private $rootScope, private templateValuesSrv, private dynamicDashboardSrv) { + this.annotations = this.dashboard.templating.list; + this.variables = this.dashboard.templating.list; + } + + disableAnnotation(annotation) { + annotation.enable = !annotation.enable; + this.$rootScope.$broadcast('refresh'); + } + + getValuesForTag(variable, tagKey) { + return this.templateValuesSrv.getValuesForTag(variable, tagKey); + } + + variableUpdated(variable) { + this.templateValuesSrv.variableUpdated(variable).then(() => { + this.dynamicDashboardSrv.update(this.dashboard); + this.$rootScope.$emit('template-variable-value-updated'); + this.$rootScope.$broadcast('refresh'); + }); + } +} + +export function submenuDirective() { + return { + restrict: 'E', + templateUrl: 'app/features/dashboard/submenu/submenu.html', + controller: SubmenuCtrl, + bindToController: true, + controllerAs: 'ctrl', + scope: { + dashboard: "=", + } + }; +} + +angular.module('grafana.directives').directive('dashboardSubmenu', submenuDirective); diff --git a/public/app/features/dashboard/submenuCtrl.js b/public/app/features/dashboard/submenuCtrl.js deleted file mode 100644 index b1d0dc3ae32..00000000000 --- a/public/app/features/dashboard/submenuCtrl.js +++ /dev/null @@ -1,39 +0,0 @@ -define([ - 'angular', -], -function (angular) { - 'use strict'; - - var module = angular.module('grafana.controllers'); - - module.controller('SubmenuCtrl', function($scope, $q, $rootScope, templateValuesSrv, dynamicDashboardSrv) { - - $scope.init = function() { - $scope.panel = $scope.pulldown; - $scope.row = $scope.pulldown; - $scope.annotations = $scope.dashboard.templating.list; - $scope.variables = $scope.dashboard.templating.list; - }; - - $scope.disableAnnotation = function (annotation) { - annotation.enable = !annotation.enable; - $rootScope.$broadcast('refresh'); - }; - - $scope.getValuesForTag = function(variable, tagKey) { - return templateValuesSrv.getValuesForTag(variable, tagKey); - }; - - $scope.variableUpdated = function(variable) { - templateValuesSrv.variableUpdated(variable).then(function() { - dynamicDashboardSrv.update($scope.dashboard); - $rootScope.$emit('template-variable-value-updated'); - $rootScope.$broadcast('refresh'); - }); - }; - - $scope.init(); - - }); - -}); diff --git a/public/app/features/playlist/all.js b/public/app/features/playlist/all.js index df0d6983c06..a37de0f4612 100644 --- a/public/app/features/playlist/all.js +++ b/public/app/features/playlist/all.js @@ -1,6 +1,6 @@ define([ './playlists_ctrl', - './playlistSrv', + './playlist_srv', './playlist_edit_ctrl', './playlist_routes' ], function () {}); diff --git a/public/app/features/playlist/partials/playlist.html b/public/app/features/playlist/partials/playlist.html index bf5fe4fd485..6f47c65568f 100644 --- a/public/app/features/playlist/partials/playlist.html +++ b/public/app/features/playlist/partials/playlist.html @@ -1,7 +1,7 @@ @@ -20,7 +20,7 @@ Name
  • - +
  • diff --git a/public/app/features/playlist/partials/playlists.html b/public/app/features/playlist/partials/playlists.html index 9f95c64a566..a8a4724b34c 100644 --- a/public/app/features/playlist/partials/playlists.html +++ b/public/app/features/playlist/partials/playlists.html @@ -1,12 +1,12 @@ - +
    - +

    Saved playlists

    @@ -21,7 +21,7 @@ - {{playlist.title}} + {{playlist.name}} playlists/play/{{playlist.id}} diff --git a/public/app/features/playlist/playlistSrv.js b/public/app/features/playlist/playlistSrv.js deleted file mode 100644 index f7ea59b6c98..00000000000 --- a/public/app/features/playlist/playlistSrv.js +++ /dev/null @@ -1,56 +0,0 @@ -define([ - 'angular', - 'lodash', - 'app/core/utils/kbn', -], -function (angular, _, kbn) { - 'use strict'; - - var module = angular.module('grafana.services'); - - module.service('playlistSrv', function($location, $rootScope, $timeout) { - var self = this; - - this.next = function() { - $timeout.cancel(self.cancelPromise); - - angular.element(window).unbind('resize'); - var dash = self.dashboards[self.index % self.dashboards.length]; - - $location.url('dashboard/' + dash.uri); - - self.index++; - self.cancelPromise = $timeout(self.next, self.interval); - }; - - this.prev = function() { - self.index = Math.max(self.index - 2, 0); - self.next(); - }; - - this.start = function(dashboards, interval) { - self.stop(); - - self.index = 0; - self.interval = kbn.interval_to_ms(interval); - - self.dashboards = dashboards; - $rootScope.playlistSrv = this; - - self.cancelPromise = $timeout(self.next, self.interval); - self.next(); - }; - - this.stop = function() { - self.index = 0; - - if (self.cancelPromise) { - $timeout.cancel(self.cancelPromise); - } - - $rootScope.playlistSrv = null; - }; - - }); - -}); diff --git a/public/app/features/playlist/playlist_edit_ctrl.js b/public/app/features/playlist/playlist_edit_ctrl.js index 4efda3695eb..3817ec12634 100644 --- a/public/app/features/playlist/playlist_edit_ctrl.js +++ b/public/app/features/playlist/playlist_edit_ctrl.js @@ -13,7 +13,9 @@ function (angular, config, _) { $scope.foundPlaylistItems = []; $scope.searchQuery = ''; $scope.loading = false; - $scope.playlist = {}; + $scope.playlist = { + interval: '10m', + }; $scope.playlistItems = []; $scope.init = function() { @@ -68,7 +70,6 @@ function (angular, config, _) { $scope.playlistItems.push(playlistItem); $scope.filterFoundPlaylistItems(); - }; $scope.removePlaylistItem = function(playlistItem) { diff --git a/public/app/features/playlist/playlist_routes.js b/public/app/features/playlist/playlist_routes.js index b9358148f8a..2de1f7b30c8 100644 --- a/public/app/features/playlist/playlist_routes.js +++ b/public/app/features/playlist/playlist_routes.js @@ -23,19 +23,10 @@ function (angular) { controller : 'PlaylistEditCtrl' }) .when('/playlists/play/:id', { - templateUrl: 'app/partials/dashboard.html', - controller : 'LoadDashboardCtrl', resolve: { - init: function(backendSrv, playlistSrv, $route) { + init: function(playlistSrv, $route) { var playlistId = $route.current.params.id; - - return backendSrv.get('/api/playlists/' + playlistId) - .then(function(playlist) { - return backendSrv.get('/api/playlists/' + playlistId + '/dashboards') - .then(function(dashboards) { - playlistSrv.start(dashboards, playlist.interval); - }); - }); + playlistSrv.start(playlistId); } } }); diff --git a/public/app/features/playlist/playlist_srv.ts b/public/app/features/playlist/playlist_srv.ts new file mode 100644 index 00000000000..0570a3bfc85 --- /dev/null +++ b/public/app/features/playlist/playlist_srv.ts @@ -0,0 +1,67 @@ +/// + +import angular from 'angular'; +import coreModule from '../../core/core_module'; +import kbn from 'app/core/utils/kbn'; + +class PlaylistSrv { + private cancelPromise: any; + private dashboards: any; + private index: number; + private interval: any; + private playlistId: number; + + /** @ngInject */ + constructor(private $rootScope: any, private $location: any, private $timeout: any, private backendSrv: any) { } + + next() { + this.$timeout.cancel(this.cancelPromise); + + var playedAllDashboards = this.index > this.dashboards.length - 1; + + if (playedAllDashboards) { + this.start(this.playlistId); + } else { + var dash = this.dashboards[this.index]; + + this.$location.url('dashboard/' + dash.uri); + + this.index++; + this.cancelPromise = this.$timeout(() => this.next(), this.interval); + } + } + + prev() { + this.index = Math.max(this.index - 2, 0); + this.next(); + } + + start(playlistId) { + this.stop(); + + this.index = 0; + this.playlistId = playlistId; + this.$rootScope.playlistSrv = this; + + this.backendSrv.get(`/api/playlists/${playlistId}`).then(playlist => { + this.backendSrv.get(`/api/playlists/${playlistId}/dashboards`).then(dashboards => { + this.dashboards = dashboards; + this.interval = kbn.interval_to_ms(playlist.interval); + this.next(); + }); + }); + } + + stop() { + this.index = 0; + this.playlistId = 0; + + if (this.cancelPromise) { + this.$timeout.cancel(this.cancelPromise); + } + + this.$rootScope.playlistSrv = null; + } +} + +coreModule.service('playlistSrv', PlaylistSrv); diff --git a/public/app/features/playlist/playlists_ctrl.js b/public/app/features/playlist/playlists_ctrl.js index b4bc464825c..375492e6772 100644 --- a/public/app/features/playlist/playlists_ctrl.js +++ b/public/app/features/playlist/playlists_ctrl.js @@ -13,31 +13,31 @@ function (angular, _) { $scope.playlists = result; }); - $scope.removePlaylist = function(playlist) { - var modalScope = $scope.$new(true); + $scope.removePlaylistConfirmed = function(playlist) { + _.remove($scope.playlists, {id: playlist.id}); - modalScope.playlist = playlist; - modalScope.removePlaylist = function() { - modalScope.dismiss(); - _.remove($scope.playlists, {id: playlist.id}); - - backendSrv.delete('/api/playlists/' + playlist.id) - .then(function() { - $scope.appEvent('alert-success', ['Playlist deleted', '']); - }, function() { - $scope.appEvent('alert-error', ['Unable to delete playlist', '']); - $scope.playlists.push(playlist); - }); - }; - - $scope.appEvent('show-modal', { - src: './app/features/playlist/partials/playlist-remove.html', - scope: modalScope + backendSrv.delete('/api/playlists/' + playlist.id) + .then(function() { + $scope.appEvent('alert-success', ['Playlist deleted', '']); + }, function() { + $scope.appEvent('alert-error', ['Unable to delete playlist', '']); + $scope.playlists.push(playlist); }); }; - $scope.createPlaylist = function() { - $location.path('/playlists/create'); + $scope.removePlaylist = function(playlist) { + + $scope.appEvent('confirm-modal', { + title: 'Confirm delete playlist', + text: 'Are you sure you want to delete playlist ' + playlist.name + '?', + yesText: "Delete", + icon: "fa-warning", + onConfirm: function() { + $scope.removePlaylistConfirmed(playlist); + } + }); + }; + }); }); diff --git a/public/app/features/profile/partials/password.html b/public/app/features/profile/partials/password.html index 24e828ee790..df940dd14a5 100644 --- a/public/app/features/profile/partials/password.html +++ b/public/app/features/profile/partials/password.html @@ -1,8 +1,8 @@ - + - +
    @@ -47,7 +47,11 @@

    - +
    + +   + Cancel +
    diff --git a/public/app/features/profile/partials/profile.html b/public/app/features/profile/partials/profile.html index 425d2bac0fe..4e8c15044c2 100644 --- a/public/app/features/profile/partials/profile.html +++ b/public/app/features/profile/partials/profile.html @@ -1,10 +1,10 @@ - - + +
    -

    Account & Preferences

    +

    Profile

    Preferences

    diff --git a/public/app/partials/dashboard.html b/public/app/partials/dashboard.html index 4c5336b88f6..9d20da3d7b4 100644 --- a/public/app/partials/dashboard.html +++ b/public/app/partials/dashboard.html @@ -6,8 +6,7 @@
    -
    -
    +
    diff --git a/public/app/partials/submenu.html b/public/app/partials/submenu.html deleted file mode 100644 index 485186696c3..00000000000 --- a/public/app/partials/submenu.html +++ /dev/null @@ -1,30 +0,0 @@ - diff --git a/public/app/plugins/panel/graph/legend.js b/public/app/plugins/panel/graph/legend.js index 2d3f8ac3b90..cf64d90414b 100644 --- a/public/app/plugins/panel/graph/legend.js +++ b/public/app/plugins/panel/graph/legend.js @@ -45,7 +45,7 @@ function (angular, _, $) { popoverScope.series = seriesInfo; popoverSrv.show({ element: el, - templateUrl: 'app/plugins/panels/graph/legend.popover.html', + templateUrl: 'app/plugins/panel/graph/legend.popover.html', scope: popoverScope }); } diff --git a/public/app/plugins/panel/singlestat/module.js b/public/app/plugins/panel/singlestat/module.js index 2ed8221150a..8ce441818e6 100644 --- a/public/app/plugins/panel/singlestat/module.js +++ b/public/app/plugins/panel/singlestat/module.js @@ -23,6 +23,7 @@ function (SingleStatCtrl, _, $) { elem = inner; $panelContainer = elem.parents('.panel-container'); firstRender = false; + hookupDrilldownLinkTooltip(); } } @@ -186,41 +187,44 @@ function (SingleStatCtrl, _, $) { } } - // drilldown link tooltip - var drilldownTooltip = $('
    hello
    "'); + function hookupDrilldownLinkTooltip() { + // drilldown link tooltip + var drilldownTooltip = $('
    hello
    "'); - elem.mouseleave(function() { - if (panel.links.length === 0) { return;} - drilldownTooltip.detach(); - }); + elem.mouseleave(function() { + if (panel.links.length === 0) { return;} + drilldownTooltip.detach(); + }); - elem.click(function() { - if (!linkInfo) { return; } + elem.click(function(evt) { + if (!linkInfo) { return; } + // ignore title clicks in title + if ($(evt).parents('.panel-header').length > 0) { return; } - if (linkInfo.target === '_blank') { - var redirectWindow = window.open(linkInfo.href, '_blank'); - redirectWindow.location; - return; - } + if (linkInfo.target === '_blank') { + var redirectWindow = window.open(linkInfo.href, '_blank'); + redirectWindow.location; + return; + } - if (linkInfo.href.indexOf('http') === 0) { - window.location.href = linkInfo.href; - } else { - $timeout(function() { - $location.url(linkInfo.href); - }); - } + if (linkInfo.href.indexOf('http') === 0) { + window.location.href = linkInfo.href; + } else { + $timeout(function() { + $location.url(linkInfo.href); + }); + } - drilldownTooltip.detach(); - }); + drilldownTooltip.detach(); + }); - elem.mousemove(function(e) { - if (!linkInfo) { return;} + elem.mousemove(function(e) { + if (!linkInfo) { return;} - drilldownTooltip.text('click to go to: ' + linkInfo.title); - - drilldownTooltip.place_tt(e.pageX+20, e.pageY-15); - }); + drilldownTooltip.text('click to go to: ' + linkInfo.title); + drilldownTooltip.place_tt(e.pageX+20, e.pageY-15); + }); + } } }; } diff --git a/public/views/index.html b/public/views/index.html index a3e21da4a04..2d05f414658 100644 --- a/public/views/index.html +++ b/public/views/index.html @@ -27,6 +27,7 @@ + diff --git a/symlink_git_hooks.sh b/symlink_git_hooks.sh new file mode 100755 index 00000000000..7c3b58feaca --- /dev/null +++ b/symlink_git_hooks.sh @@ -0,0 +1,3 @@ +#/bin/bash + +ln -s .hooks/* .git/hooks/