Merge branch 'master' into influxdb-policy-selector

This commit is contained in:
Torkel Ödegaard 2016-01-18 18:10:01 +01:00
commit 3b5a583903
33 changed files with 358 additions and 297 deletions

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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))
}

View File

@ -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)

View File

@ -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();
}
});
}
};

View File

@ -13,7 +13,7 @@
<span class="icon-circle top-nav-icon">
<i ng-class="ctrl.icon"></i>
</span>
<a ng-href="{{ctl.titleUrl}}" class="top-nav-title">
<a ng-href="{{ctrl.titleUrl}}" class="top-nav-title">
{{ctrl.title}}
</a>
<i ng-show="ctrl.subnav" class="top-nav-breadcrumb-icon fa fa-angle-right"></i>

View File

@ -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) {

View File

@ -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'},
]
},
{

View File

@ -1,9 +1,8 @@
<topnav title="Apps" icon="fa fa-fw fa-cubes" subnav="true">
<navbar title="Apps" title-url="apps" icon="fa fa-fw fa-cubes" subnav="true">
<ul class="nav">
<li ><a href="apps">Overview</a></li>
<li class="active" ><a href="apps/edit/{{ctrl.current.type}}">Edit</a></li>
<li class="active" ><a href="apps/edit/{{ctrl.current.type}}">{{ctrl.appModel.name}}</a></li>
</ul>
</topnav>
</navbar>
<div class="page-container">
<div class="apps-side-box">

View File

@ -2,11 +2,11 @@ define([
'./dashboardCtrl',
'./dashboardLoaderSrv',
'./dashnav/dashnav',
'./submenu/submenu',
'./saveDashboardAsCtrl',
'./rowCtrl',
'./shareModalCtrl',
'./shareSnapshotCtrl',
'./submenuCtrl',
'./dashboardSrv',
'./keybindings',
'./viewStateSrv',

View File

@ -0,0 +1,30 @@
<div class="submenu-controls">
<div class="tight-form borderless">
<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 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="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>

View File

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

View File

@ -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();
});
});

View File

@ -1,6 +1,6 @@
define([
'./playlists_ctrl',
'./playlistSrv',
'./playlist_srv',
'./playlist_edit_ctrl',
'./playlist_routes'
], function () {});

View File

@ -1,7 +1,7 @@
<navbar title="Playlists" title-url="playlists" icon="fa fa-fw fa-list" subnav="true">
<ul class="nav">
<li ng-class="{active: isNew()}" ng-show="isNew()"><a href="datasources/create">New</a></li>
<li class="active" ng-show="!isNew()"><a href="playlists/edit/{{playlist.id}}">{{playlist.title}}</a></li>
<li class="active" ng-show="!isNew()"><a href="playlists/edit/{{playlist.id}}">{{playlist.name}}</a></li>
</ul>
</navbar>
@ -20,7 +20,7 @@
Name
</li>
<li>
<input type="text" required ng-model="playlist.title" class="input-xlarge tight-form-input">
<input type="text" required ng-model="playlist.name" class="input-xlarge tight-form-input">
</li>
</ul>
<div class="clearfix"></div>

View File

@ -1,12 +1,12 @@
<topnav icon="fa fa-fw fa-list" title="Playlists"></topnav>
<navbar icon="fa fa-fw fa-list" title="Playlists"></navbar>
<div class="page-container">
<div class="page-wide">
<button type="submit" class="btn btn-inverse pull-right" ng-click="createPlaylist()">
<a class="btn btn-inverse pull-right" href="playlists/create">
<i class="fa fa-plus"></i>
New playlist
</button>
</a>
<h2>Saved playlists</h2>
@ -21,7 +21,7 @@
</thead>
<tr ng-repeat="playlist in playlists">
<td>
<a href="playlists/edit/{{playlist.id}}">{{playlist.title}}</a>
<a href="playlists/edit/{{playlist.id}}">{{playlist.name}}</a>
</td>
<td >
<a href="playlists/play/{{playlist.id}}">playlists/play/{{playlist.id}}</a>

View File

@ -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;
};
});
});

View File

@ -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) {

View File

@ -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);
}
}
});

View File

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

View File

@ -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);
}
});
};
});
});

View File

@ -1,8 +1,8 @@
<topnav title="Profile" title-url="profile" icon="fa fa-user" subnav="true">
<navbar title="Profile" title-url="profile" icon="fa fa-fw fa-user" subnav="true">
<ul class="nav">
<li class="active"><a href="profile/password">Change password</a></li>
</ul>
</topnav>
</navbar>
<div class="page-container">
<div class="page">
@ -47,7 +47,11 @@
</div>
<br>
<button type="submit" class="pull-right btn btn-success" ng-click="changePassword()">Change Password</button>
<div class="pull-right">
<button type="submit" class="btn btn-success" ng-click="changePassword()">Change Password</button>
&nbsp;
<a class="btn btn-inverse" href="profile">Cancel</a>
</div>
</form>
</div>

View File

@ -1,10 +1,10 @@
<topnav title="Account" title-url="profile" icon="fa fa-fw fa-user">
</topnav>
<navbar title="Profile" title-url="profile" icon="fa fa-fw fa-user">
</navbar>
<div class="page-container">
<div class="page-wide">
<h1>Account & Preferences</h1>
<h1>Profile</h1>
<section class="simple-box">
<h3 class="simple-box-header">Preferences</h3>

View File

@ -6,8 +6,7 @@
<div dash-search-view></div>
<div class="clearfix"></div>
<div ng-if="submenuEnabled" ng-include="'app/partials/submenu.html'">
</div>
<dashboard-submenu ng-if="submenuEnabled" dashboard="dashboard"></dashboard-submenu>
<div class="clearfix"></div>

View File

@ -1,30 +0,0 @@
<div class="submenu-controls" ng-controller="SubmenuCtrl">
<div class="tight-form borderless">
<ul class="tight-form-list" ng-if="dashboard.templating.list.length > 0">
<li ng-repeat="variable in 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="variableUpdated(variable)" get-values-for-tag="getValuesForTag(variable, tagKey)"></value-select-dropdown>
</li>
</ul>
<ul class="tight-form-list" ng-if="dashboard.annotations.list.length > 0">
<li ng-repeat="annotation in dashboard.annotations.list" class="submenu-item annotation-segment" ng-class="{'annotation-disabled': !annotation.enable}">
<a ng-click="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 pull-right" ng-if="dashboard.links.length > 0">
<dash-links-container links="dashboard.links"></dash-links-container>
</ul>
<div class="clearfix"></div>
</div>
</div>

View File

@ -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
});
}

View File

@ -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 = $('<div id="tooltip" class="">hello</div>"');
function hookupDrilldownLinkTooltip() {
// drilldown link tooltip
var drilldownTooltip = $('<div id="tooltip" class="">hello</div>"');
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);
});
}
}
};
}

View File

@ -27,6 +27,7 @@
<body ng-cloak>
<grafana-app>
<aside class="sidemenu-wrapper">
<sidemenu ng-if="contextSrv.sidemenu"></sidemenu>
</aside>

3
symlink_git_hooks.sh Executable file
View File

@ -0,0 +1,3 @@
#/bin/bash
ln -s .hooks/* .git/hooks/