mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge branch 'master' into influxdb-policy-selector
This commit is contained in:
commit
3b5a583903
@ -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
|
||||
|
@ -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.
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -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>
|
||||
|
@ -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) {
|
||||
|
@ -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'},
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -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">
|
||||
|
@ -2,11 +2,11 @@ define([
|
||||
'./dashboardCtrl',
|
||||
'./dashboardLoaderSrv',
|
||||
'./dashnav/dashnav',
|
||||
'./submenu/submenu',
|
||||
'./saveDashboardAsCtrl',
|
||||
'./rowCtrl',
|
||||
'./shareModalCtrl',
|
||||
'./shareSnapshotCtrl',
|
||||
'./submenuCtrl',
|
||||
'./dashboardSrv',
|
||||
'./keybindings',
|
||||
'./viewStateSrv',
|
||||
|
30
public/app/features/dashboard/submenu/submenu.html
Normal file
30
public/app/features/dashboard/submenu/submenu.html
Normal 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>
|
46
public/app/features/dashboard/submenu/submenu.ts
Normal file
46
public/app/features/dashboard/submenu/submenu.ts
Normal 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);
|
@ -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();
|
||||
|
||||
});
|
||||
|
||||
});
|
@ -1,6 +1,6 @@
|
||||
define([
|
||||
'./playlists_ctrl',
|
||||
'./playlistSrv',
|
||||
'./playlist_srv',
|
||||
'./playlist_edit_ctrl',
|
||||
'./playlist_routes'
|
||||
], function () {});
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
});
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
67
public/app/features/playlist/playlist_srv.ts
Normal file
67
public/app/features/playlist/playlist_srv.ts
Normal 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);
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
});
|
||||
});
|
||||
|
@ -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>
|
||||
|
||||
<a class="btn btn-inverse" href="profile">Cancel</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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>
|
@ -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
|
||||
});
|
||||
}
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -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
3
symlink_git_hooks.sh
Executable file
@ -0,0 +1,3 @@
|
||||
#/bin/bash
|
||||
|
||||
ln -s .hooks/* .git/hooks/
|
Loading…
Reference in New Issue
Block a user