mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge remote-tracking branch 'upstream/master' into dashboard_permissions
This commit is contained in:
@@ -9,5 +9,6 @@ FROM grafana/docs-base:latest
|
|||||||
|
|
||||||
COPY config.toml /site
|
COPY config.toml /site
|
||||||
COPY awsconfig /site
|
COPY awsconfig /site
|
||||||
|
COPY versions.json /site/static/js
|
||||||
|
|
||||||
VOLUME ["/site/content"]
|
VOLUME ["/site/content"]
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ title = "Provisioning"
|
|||||||
description = ""
|
description = ""
|
||||||
keywords = ["grafana", "provisioning"]
|
keywords = ["grafana", "provisioning"]
|
||||||
type = "docs"
|
type = "docs"
|
||||||
|
aliases = ["/installation/provisioning"]
|
||||||
[menu.docs]
|
[menu.docs]
|
||||||
parent = "admin"
|
parent = "admin"
|
||||||
weight = 8
|
weight = 8
|
||||||
@@ -66,7 +67,6 @@ Tool | Project
|
|||||||
-----|------------
|
-----|------------
|
||||||
Puppet | [https://forge.puppet.com/puppet/grafana](https://forge.puppet.com/puppet/grafana)
|
Puppet | [https://forge.puppet.com/puppet/grafana](https://forge.puppet.com/puppet/grafana)
|
||||||
Ansible | [https://github.com/cloudalchemy/ansible-grafana](https://github.com/cloudalchemy/ansible-grafana)
|
Ansible | [https://github.com/cloudalchemy/ansible-grafana](https://github.com/cloudalchemy/ansible-grafana)
|
||||||
Ansible | [https://github.com/picotrading/ansible-grafana](https://github.com/picotrading/ansible-grafana)
|
|
||||||
Chef | [https://github.com/JonathanTron/chef-grafana](https://github.com/JonathanTron/chef-grafana)
|
Chef | [https://github.com/JonathanTron/chef-grafana](https://github.com/JonathanTron/chef-grafana)
|
||||||
Saltstack | [https://github.com/salt-formulas/salt-formula-grafana](https://github.com/salt-formulas/salt-formula-grafana)
|
Saltstack | [https://github.com/salt-formulas/salt-formula-grafana](https://github.com/salt-formulas/salt-formula-grafana)
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ type = "docs"
|
|||||||
[menu.docs]
|
[menu.docs]
|
||||||
name = "Features"
|
name = "Features"
|
||||||
identifier = "features"
|
identifier = "features"
|
||||||
weight = 3
|
weight = 4
|
||||||
+++
|
+++
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ type = "docs"
|
|||||||
name = "Basic Concepts"
|
name = "Basic Concepts"
|
||||||
identifier = "basic_concepts"
|
identifier = "basic_concepts"
|
||||||
parent = "guides"
|
parent = "guides"
|
||||||
|
weight = 2
|
||||||
+++
|
+++
|
||||||
|
|
||||||
# Basic Concepts
|
# Basic Concepts
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ aliases = ["/guides/gettingstarted"]
|
|||||||
name = "Getting Started"
|
name = "Getting Started"
|
||||||
identifier = "getting_started_guide"
|
identifier = "getting_started_guide"
|
||||||
parent = "guides"
|
parent = "guides"
|
||||||
|
weight = 1
|
||||||
+++
|
+++
|
||||||
|
|
||||||
# Getting started
|
# Getting started
|
||||||
@@ -24,7 +25,7 @@ Read the [Basic Concepts](/guides/basic_concepts) document to get a crash course
|
|||||||
|
|
||||||
### Top header
|
### Top header
|
||||||
|
|
||||||
Let's start with creating a new Dashboard. You can find the new Dashboard link on the right side of the Dashboard picker. You now have a blank Dashboard.
|
Let's start with creating a new Dashboard. You can find the new Dashboard link on the right side of the Dashboard picker. You now have a blank Dashboard.
|
||||||
|
|
||||||
<img class="no-shadow" src="/img/docs/v45/top_nav_annotated.png">
|
<img class="no-shadow" src="/img/docs/v45/top_nav_annotated.png">
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,6 @@ type = "docs"
|
|||||||
[menu.docs]
|
[menu.docs]
|
||||||
name = "Getting Started"
|
name = "Getting Started"
|
||||||
identifier = "guides"
|
identifier = "guides"
|
||||||
weight = 2
|
weight = 3
|
||||||
+++
|
+++
|
||||||
|
|
||||||
|
|||||||
@@ -4,10 +4,6 @@ description = "Install guide for Grafana"
|
|||||||
keywords = ["grafana", "installation", "documentation"]
|
keywords = ["grafana", "installation", "documentation"]
|
||||||
type = "docs"
|
type = "docs"
|
||||||
aliases = ["v1.1", "guides/reference/admin"]
|
aliases = ["v1.1", "guides/reference/admin"]
|
||||||
[menu.docs]
|
|
||||||
name = "Welcome to the Docs"
|
|
||||||
identifier = "root"
|
|
||||||
weight = -1
|
|
||||||
+++
|
+++
|
||||||
|
|
||||||
# Welcome to the Grafana Documentation
|
# Welcome to the Grafana Documentation
|
||||||
@@ -22,7 +18,7 @@ other domains including industrial sensors, home automation, weather, and proces
|
|||||||
- [Installing on Mac OS X](installation/mac)
|
- [Installing on Mac OS X](installation/mac)
|
||||||
- [Installing on Windows](installation/windows)
|
- [Installing on Windows](installation/windows)
|
||||||
- [Installing on Docker](installation/docker)
|
- [Installing on Docker](installation/docker)
|
||||||
- [Installing using Provisioning (Chef, Puppet, Salt, Ansible, etc)](installation/provisioning)
|
- [Installing using Provisioning (Chef, Puppet, Salt, Ansible, etc)](administration/provisioning#configuration-management-tools)
|
||||||
- [Nightly Builds](https://grafana.com/grafana/download)
|
- [Nightly Builds](https://grafana.com/grafana/download)
|
||||||
|
|
||||||
For other platforms Read the [build from source]({{< relref "project/building_from_source.md" >}})
|
For other platforms Read the [build from source]({{< relref "project/building_from_source.md" >}})
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ aliases = ["installation/installation/", "v2.1/installation/install/"]
|
|||||||
[menu.docs]
|
[menu.docs]
|
||||||
name = "Installation"
|
name = "Installation"
|
||||||
identifier = "installation"
|
identifier = "installation"
|
||||||
|
weight = 1
|
||||||
+++
|
+++
|
||||||
|
|
||||||
## Installing Grafana
|
## Installing Grafana
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ title = "What's New in Grafana"
|
|||||||
[menu.docs]
|
[menu.docs]
|
||||||
name = "What's New In Grafana"
|
name = "What's New In Grafana"
|
||||||
identifier = "whatsnew"
|
identifier = "whatsnew"
|
||||||
weight = 2
|
weight = 3
|
||||||
+++
|
+++
|
||||||
|
|
||||||
|
|
||||||
9
docs/versions.json
Normal file
9
docs/versions.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[
|
||||||
|
{ "version": "v5.0", "path": "/v5.0", "archived": false },
|
||||||
|
{ "version": "v4.6", "path": "/", "archived": false, "current": true },
|
||||||
|
{ "version": "v4.5", "path": "/v4.5", "archived": true },
|
||||||
|
{ "version": "v4.4", "path": "/v4.4", "archived": true },
|
||||||
|
{ "version": "v4.3", "path": "/v4.3", "archived": true },
|
||||||
|
{ "version": "v4.1", "path": "/v4.1", "archived": true },
|
||||||
|
{ "version": "v3.1", "path": "/v3.1", "archived": true }
|
||||||
|
]
|
||||||
@@ -71,6 +71,7 @@ func (tw *DatasourcePluginWrapper) Query(ctx context.Context, ds *models.DataSou
|
|||||||
qr := &tsdb.QueryResult{
|
qr := &tsdb.QueryResult{
|
||||||
RefId: r.RefId,
|
RefId: r.RefId,
|
||||||
Series: []*tsdb.TimeSeries{},
|
Series: []*tsdb.TimeSeries{},
|
||||||
|
Tables: []*tsdb.Table{},
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.Error != "" {
|
if r.Error != "" {
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ func TestMappingRowValue(t *testing.T) {
|
|||||||
boolRowValue, _ := dpw.mapRowValue(&datasource.RowValue{Kind: datasource.RowValue_TYPE_BOOL, BoolValue: true})
|
boolRowValue, _ := dpw.mapRowValue(&datasource.RowValue{Kind: datasource.RowValue_TYPE_BOOL, BoolValue: true})
|
||||||
haveBool, ok := boolRowValue.(bool)
|
haveBool, ok := boolRowValue.(bool)
|
||||||
if !ok || haveBool != true {
|
if !ok || haveBool != true {
|
||||||
t.Fatalf("Expected true, was %s", haveBool)
|
t.Fatalf("Expected true, was %v", haveBool)
|
||||||
}
|
}
|
||||||
|
|
||||||
intRowValue, _ := dpw.mapRowValue(&datasource.RowValue{Kind: datasource.RowValue_TYPE_INT64, Int64Value: 42})
|
intRowValue, _ := dpw.mapRowValue(&datasource.RowValue{Kind: datasource.RowValue_TYPE_INT64, Int64Value: 42})
|
||||||
|
|||||||
@@ -1,121 +1,121 @@
|
|||||||
package alerting
|
package alerting
|
||||||
|
|
||||||
import (
|
//import (
|
||||||
"testing"
|
// "testing"
|
||||||
"time"
|
// "time"
|
||||||
|
//
|
||||||
"github.com/benbjohnson/clock"
|
// "github.com/benbjohnson/clock"
|
||||||
)
|
//)
|
||||||
|
//
|
||||||
func inspectTick(tick time.Time, last time.Time, offset time.Duration, t *testing.T) {
|
//func inspectTick(tick time.Time, last time.Time, offset time.Duration, t *testing.T) {
|
||||||
if !tick.Equal(last.Add(time.Duration(1) * time.Second)) {
|
// if !tick.Equal(last.Add(time.Duration(1) * time.Second)) {
|
||||||
t.Fatalf("expected a tick 1 second more than prev, %s. got: %s", last, tick)
|
// t.Fatalf("expected a tick 1 second more than prev, %s. got: %s", last, tick)
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
// returns the new last tick seen
|
//// returns the new last tick seen
|
||||||
func assertAdvanceUntil(ticker *Ticker, last, desiredLast time.Time, offset, wait time.Duration, t *testing.T) time.Time {
|
//func assertAdvanceUntil(ticker *Ticker, last, desiredLast time.Time, offset, wait time.Duration, t *testing.T) time.Time {
|
||||||
for {
|
// for {
|
||||||
select {
|
// select {
|
||||||
case tick := <-ticker.C:
|
// case tick := <-ticker.C:
|
||||||
inspectTick(tick, last, offset, t)
|
// inspectTick(tick, last, offset, t)
|
||||||
last = tick
|
// last = tick
|
||||||
case <-time.NewTimer(wait).C:
|
// case <-time.NewTimer(wait).C:
|
||||||
if last.Before(desiredLast) {
|
// if last.Before(desiredLast) {
|
||||||
t.Fatalf("waited %s for ticker to advance to %s, but only went up to %s", wait, desiredLast, last)
|
// t.Fatalf("waited %s for ticker to advance to %s, but only went up to %s", wait, desiredLast, last)
|
||||||
}
|
// }
|
||||||
if last.After(desiredLast) {
|
// if last.After(desiredLast) {
|
||||||
t.Fatalf("timer advanced too far. should only have gone up to %s, but it went up to %s", desiredLast, last)
|
// t.Fatalf("timer advanced too far. should only have gone up to %s, but it went up to %s", desiredLast, last)
|
||||||
}
|
// }
|
||||||
return last
|
// return last
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
func assertNoAdvance(ticker *Ticker, desiredLast time.Time, wait time.Duration, t *testing.T) {
|
//func assertNoAdvance(ticker *Ticker, desiredLast time.Time, wait time.Duration, t *testing.T) {
|
||||||
for {
|
// for {
|
||||||
select {
|
// select {
|
||||||
case tick := <-ticker.C:
|
// case tick := <-ticker.C:
|
||||||
t.Fatalf("timer should have stayed at %s, instead it advanced to %s", desiredLast, tick)
|
// t.Fatalf("timer should have stayed at %s, instead it advanced to %s", desiredLast, tick)
|
||||||
case <-time.NewTimer(wait).C:
|
// case <-time.NewTimer(wait).C:
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
func TestTickerRetro1Hour(t *testing.T) {
|
//func TestTickerRetro1Hour(t *testing.T) {
|
||||||
offset := time.Duration(10) * time.Second
|
// offset := time.Duration(10) * time.Second
|
||||||
last := time.Unix(0, 0)
|
// last := time.Unix(0, 0)
|
||||||
mock := clock.NewMock()
|
// mock := clock.NewMock()
|
||||||
mock.Add(time.Duration(1) * time.Hour)
|
// mock.Add(time.Duration(1) * time.Hour)
|
||||||
desiredLast := mock.Now().Add(-offset)
|
// desiredLast := mock.Now().Add(-offset)
|
||||||
ticker := NewTicker(last, offset, mock)
|
// ticker := NewTicker(last, offset, mock)
|
||||||
|
//
|
||||||
last = assertAdvanceUntil(ticker, last, desiredLast, offset, time.Duration(10)*time.Millisecond, t)
|
// last = assertAdvanceUntil(ticker, last, desiredLast, offset, time.Duration(10)*time.Millisecond, t)
|
||||||
assertNoAdvance(ticker, last, time.Duration(500)*time.Millisecond, t)
|
// assertNoAdvance(ticker, last, time.Duration(500)*time.Millisecond, t)
|
||||||
|
//
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
func TestAdvanceWithUpdateOffset(t *testing.T) {
|
//func TestAdvanceWithUpdateOffset(t *testing.T) {
|
||||||
offset := time.Duration(10) * time.Second
|
// offset := time.Duration(10) * time.Second
|
||||||
last := time.Unix(0, 0)
|
// last := time.Unix(0, 0)
|
||||||
mock := clock.NewMock()
|
// mock := clock.NewMock()
|
||||||
mock.Add(time.Duration(1) * time.Hour)
|
// mock.Add(time.Duration(1) * time.Hour)
|
||||||
desiredLast := mock.Now().Add(-offset)
|
// desiredLast := mock.Now().Add(-offset)
|
||||||
ticker := NewTicker(last, offset, mock)
|
// ticker := NewTicker(last, offset, mock)
|
||||||
|
//
|
||||||
last = assertAdvanceUntil(ticker, last, desiredLast, offset, time.Duration(10)*time.Millisecond, t)
|
// last = assertAdvanceUntil(ticker, last, desiredLast, offset, time.Duration(10)*time.Millisecond, t)
|
||||||
assertNoAdvance(ticker, last, time.Duration(500)*time.Millisecond, t)
|
// assertNoAdvance(ticker, last, time.Duration(500)*time.Millisecond, t)
|
||||||
|
//
|
||||||
// lowering offset should see a few more ticks
|
// // lowering offset should see a few more ticks
|
||||||
offset = time.Duration(5) * time.Second
|
// offset = time.Duration(5) * time.Second
|
||||||
ticker.updateOffset(offset)
|
// ticker.updateOffset(offset)
|
||||||
desiredLast = mock.Now().Add(-offset)
|
// desiredLast = mock.Now().Add(-offset)
|
||||||
last = assertAdvanceUntil(ticker, last, desiredLast, offset, time.Duration(9)*time.Millisecond, t)
|
// last = assertAdvanceUntil(ticker, last, desiredLast, offset, time.Duration(9)*time.Millisecond, t)
|
||||||
assertNoAdvance(ticker, last, time.Duration(500)*time.Millisecond, t)
|
// assertNoAdvance(ticker, last, time.Duration(500)*time.Millisecond, t)
|
||||||
|
//
|
||||||
// advancing clock should see even more ticks
|
// // advancing clock should see even more ticks
|
||||||
mock.Add(time.Duration(1) * time.Hour)
|
// mock.Add(time.Duration(1) * time.Hour)
|
||||||
desiredLast = mock.Now().Add(-offset)
|
// desiredLast = mock.Now().Add(-offset)
|
||||||
last = assertAdvanceUntil(ticker, last, desiredLast, offset, time.Duration(8)*time.Millisecond, t)
|
// last = assertAdvanceUntil(ticker, last, desiredLast, offset, time.Duration(8)*time.Millisecond, t)
|
||||||
assertNoAdvance(ticker, last, time.Duration(500)*time.Millisecond, t)
|
// assertNoAdvance(ticker, last, time.Duration(500)*time.Millisecond, t)
|
||||||
|
//
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
func getCase(lastSeconds, offsetSeconds int) (time.Time, time.Duration) {
|
//func getCase(lastSeconds, offsetSeconds int) (time.Time, time.Duration) {
|
||||||
last := time.Unix(int64(lastSeconds), 0)
|
// last := time.Unix(int64(lastSeconds), 0)
|
||||||
offset := time.Duration(offsetSeconds) * time.Second
|
// offset := time.Duration(offsetSeconds) * time.Second
|
||||||
return last, offset
|
// return last, offset
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
func TestTickerNoAdvance(t *testing.T) {
|
//func TestTickerNoAdvance(t *testing.T) {
|
||||||
|
//
|
||||||
// it's 00:01:00 now. what are some cases where we don't want the ticker to advance?
|
// // it's 00:01:00 now. what are some cases where we don't want the ticker to advance?
|
||||||
mock := clock.NewMock()
|
// mock := clock.NewMock()
|
||||||
mock.Add(time.Duration(60) * time.Second)
|
// mock.Add(time.Duration(60) * time.Second)
|
||||||
|
//
|
||||||
type Case struct {
|
// type Case struct {
|
||||||
last int
|
// last int
|
||||||
offset int
|
// offset int
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
// note that some cases add up to now, others go into the future
|
// // note that some cases add up to now, others go into the future
|
||||||
cases := []Case{
|
// cases := []Case{
|
||||||
{50, 10},
|
// {50, 10},
|
||||||
{50, 30},
|
// {50, 30},
|
||||||
{59, 1},
|
// {59, 1},
|
||||||
{59, 10},
|
// {59, 10},
|
||||||
{59, 30},
|
// {59, 30},
|
||||||
{60, 1},
|
// {60, 1},
|
||||||
{60, 10},
|
// {60, 10},
|
||||||
{60, 30},
|
// {60, 30},
|
||||||
{90, 1},
|
// {90, 1},
|
||||||
{90, 10},
|
// {90, 10},
|
||||||
{90, 30},
|
// {90, 30},
|
||||||
}
|
// }
|
||||||
for _, c := range cases {
|
// for _, c := range cases {
|
||||||
last, offset := getCase(c.last, c.offset)
|
// last, offset := getCase(c.last, c.offset)
|
||||||
ticker := NewTicker(last, offset, mock)
|
// ticker := NewTicker(last, offset, mock)
|
||||||
assertNoAdvance(ticker, last, time.Duration(500)*time.Millisecond, t)
|
// assertNoAdvance(ticker, last, time.Duration(500)*time.Millisecond, t)
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|||||||
@@ -12,7 +12,11 @@ export function toUrlParams(a) {
|
|||||||
|
|
||||||
let add = function(k, v) {
|
let add = function(k, v) {
|
||||||
v = typeof v === 'function' ? v() : v === null ? '' : v === undefined ? '' : v;
|
v = typeof v === 'function' ? v() : v === null ? '' : v === undefined ? '' : v;
|
||||||
s[s.length] = encodeURIComponent(k) + '=' + encodeURIComponent(v);
|
if (typeof v !== 'boolean') {
|
||||||
|
s[s.length] = encodeURIComponent(k) + '=' + encodeURIComponent(v);
|
||||||
|
} else {
|
||||||
|
s[s.length] = encodeURIComponent(k);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let buildParams = function(prefix, obj) {
|
let buildParams = function(prefix, obj) {
|
||||||
|
|||||||
@@ -352,16 +352,10 @@ export class DashboardModel {
|
|||||||
copy.scopedVars[variable.name] = option;
|
copy.scopedVars[variable.name] = option;
|
||||||
|
|
||||||
if (panel.repeatDirection === REPEAT_DIR_VERTICAL) {
|
if (panel.repeatDirection === REPEAT_DIR_VERTICAL) {
|
||||||
copy.gridPos.y = yPos;
|
if (index > 0) {
|
||||||
yPos += copy.gridPos.h;
|
yPos += copy.gridPos.h;
|
||||||
|
|
||||||
// Update gridPos for panels below
|
|
||||||
let panelBelowIndex = panelIndex + index + 1;
|
|
||||||
for (let i = panelBelowIndex; i < this.panels.length; i++) {
|
|
||||||
if (this.panels[i].gridPos.y < yPos) {
|
|
||||||
this.panels[i].gridPos.y += copy.gridPos.h;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
copy.gridPos.y = yPos;
|
||||||
} else {
|
} else {
|
||||||
// set width based on how many are selected
|
// set width based on how many are selected
|
||||||
// assumed the repeated panels should take up full row width
|
// assumed the repeated panels should take up full row width
|
||||||
@@ -378,6 +372,15 @@ export class DashboardModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update gridPos for panels below
|
||||||
|
let yOffset = yPos - panel.gridPos.y;
|
||||||
|
if (yOffset > 0) {
|
||||||
|
let panelBelowIndex = panelIndex + selectedOptions.length;
|
||||||
|
for (let i = panelBelowIndex; i < this.panels.length; i++) {
|
||||||
|
this.panels[i].gridPos.y += yOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
repeatRow(panel: PanelModel, panelIndex: number, variable) {
|
repeatRow(panel: PanelModel, panelIndex: number, variable) {
|
||||||
@@ -566,6 +569,7 @@ export class DashboardModel {
|
|||||||
|
|
||||||
if (row.collapsed) {
|
if (row.collapsed) {
|
||||||
row.collapsed = false;
|
row.collapsed = false;
|
||||||
|
let hasRepeat = false;
|
||||||
|
|
||||||
if (row.panels.length > 0) {
|
if (row.panels.length > 0) {
|
||||||
// Use first panel to figure out if it was moved or pushed
|
// Use first panel to figure out if it was moved or pushed
|
||||||
@@ -586,6 +590,10 @@ export class DashboardModel {
|
|||||||
// update insert post and y max
|
// update insert post and y max
|
||||||
insertPos += 1;
|
insertPos += 1;
|
||||||
yMax = Math.max(yMax, panel.gridPos.y + panel.gridPos.h);
|
yMax = Math.max(yMax, panel.gridPos.y + panel.gridPos.h);
|
||||||
|
|
||||||
|
if (panel.repeat) {
|
||||||
|
hasRepeat = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const pushDownAmount = yMax - row.gridPos.y;
|
const pushDownAmount = yMax - row.gridPos.y;
|
||||||
@@ -596,6 +604,10 @@ export class DashboardModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
row.panels = [];
|
row.panels = [];
|
||||||
|
|
||||||
|
if (hasRepeat) {
|
||||||
|
this.processRepeats();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// sort panels
|
// sort panels
|
||||||
|
|||||||
@@ -4,6 +4,57 @@ import { expect } from 'test/lib/common';
|
|||||||
|
|
||||||
jest.mock('app/core/services/context_srv', () => ({}));
|
jest.mock('app/core/services/context_srv', () => ({}));
|
||||||
|
|
||||||
|
describe('given dashboard with panel repeat', function() {
|
||||||
|
var dashboard;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
let dashboardJSON = {
|
||||||
|
panels: [
|
||||||
|
{ id: 1, type: 'row', gridPos: { x: 0, y: 0, h: 1, w: 24 } },
|
||||||
|
{ id: 2, repeat: 'apps', repeatDirection: 'h', gridPos: { x: 0, y: 1, h: 2, w: 8 } },
|
||||||
|
],
|
||||||
|
templating: {
|
||||||
|
list: [
|
||||||
|
{
|
||||||
|
name: 'apps',
|
||||||
|
current: {
|
||||||
|
text: 'se1, se2, se3',
|
||||||
|
value: ['se1', 'se2', 'se3'],
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{ text: 'se1', value: 'se1', selected: true },
|
||||||
|
{ text: 'se2', value: 'se2', selected: true },
|
||||||
|
{ text: 'se3', value: 'se3', selected: true },
|
||||||
|
{ text: 'se4', value: 'se4', selected: false },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
dashboard = new DashboardModel(dashboardJSON);
|
||||||
|
dashboard.processRepeats();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should repeat panels when row is expanding', function() {
|
||||||
|
expect(dashboard.panels.length).toBe(4);
|
||||||
|
|
||||||
|
// toggle row
|
||||||
|
dashboard.toggleRow(dashboard.panels[0]);
|
||||||
|
expect(dashboard.panels.length).toBe(1);
|
||||||
|
|
||||||
|
// change variable
|
||||||
|
dashboard.templating.list[0].options[2].selected = false;
|
||||||
|
dashboard.templating.list[0].current = {
|
||||||
|
text: 'se1, se2',
|
||||||
|
value: ['se1', 'se2'],
|
||||||
|
};
|
||||||
|
|
||||||
|
// toggle row back
|
||||||
|
dashboard.toggleRow(dashboard.panels[0]);
|
||||||
|
expect(dashboard.panels.length).toBe(3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('given dashboard with panel repeat in horizontal direction', function() {
|
describe('given dashboard with panel repeat in horizontal direction', function() {
|
||||||
var dashboard;
|
var dashboard;
|
||||||
|
|
||||||
@@ -178,6 +229,88 @@ describe('given dashboard with panel repeat in vertical direction', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('given dashboard with row repeat and panel repeat in horizontal direction', () => {
|
||||||
|
let dashboard, dashboardJSON;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
dashboardJSON = {
|
||||||
|
panels: [
|
||||||
|
{ id: 1, type: 'row', repeat: 'region', gridPos: { x: 0, y: 0, h: 1, w: 24 } },
|
||||||
|
{ id: 2, type: 'graph', repeat: 'app', gridPos: { x: 0, y: 1, h: 2, w: 6 } },
|
||||||
|
],
|
||||||
|
templating: {
|
||||||
|
list: [
|
||||||
|
{
|
||||||
|
name: 'region',
|
||||||
|
current: {
|
||||||
|
text: 'reg1, reg2',
|
||||||
|
value: ['reg1', 'reg2'],
|
||||||
|
},
|
||||||
|
options: [{ text: 'reg1', value: 'reg1', selected: true }, { text: 'reg2', value: 'reg2', selected: true }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'app',
|
||||||
|
current: {
|
||||||
|
text: 'se1, se2, se3, se4, se5, se6',
|
||||||
|
value: ['se1', 'se2', 'se3', 'se4', 'se5', 'se6'],
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{ text: 'se1', value: 'se1', selected: true },
|
||||||
|
{ text: 'se2', value: 'se2', selected: true },
|
||||||
|
{ text: 'se3', value: 'se3', selected: true },
|
||||||
|
{ text: 'se4', value: 'se4', selected: true },
|
||||||
|
{ text: 'se5', value: 'se5', selected: true },
|
||||||
|
{ text: 'se6', value: 'se6', selected: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
dashboard = new DashboardModel(dashboardJSON);
|
||||||
|
dashboard.processRepeats(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should panels in self row', () => {
|
||||||
|
const panel_types = _.map(dashboard.panels, 'type');
|
||||||
|
expect(panel_types).toEqual([
|
||||||
|
'row',
|
||||||
|
'graph',
|
||||||
|
'graph',
|
||||||
|
'graph',
|
||||||
|
'graph',
|
||||||
|
'graph',
|
||||||
|
'graph',
|
||||||
|
'row',
|
||||||
|
'graph',
|
||||||
|
'graph',
|
||||||
|
'graph',
|
||||||
|
'graph',
|
||||||
|
'graph',
|
||||||
|
'graph',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be placed in their places', function() {
|
||||||
|
expect(dashboard.panels[0].gridPos).toMatchObject({ x: 0, y: 0, h: 1, w: 24 }); // 1st row
|
||||||
|
|
||||||
|
expect(dashboard.panels[1].gridPos).toMatchObject({ x: 0, y: 1, h: 2, w: 6 });
|
||||||
|
expect(dashboard.panels[2].gridPos).toMatchObject({ x: 6, y: 1, h: 2, w: 6 });
|
||||||
|
expect(dashboard.panels[3].gridPos).toMatchObject({ x: 12, y: 1, h: 2, w: 6 });
|
||||||
|
expect(dashboard.panels[4].gridPos).toMatchObject({ x: 18, y: 1, h: 2, w: 6 });
|
||||||
|
expect(dashboard.panels[5].gridPos).toMatchObject({ x: 0, y: 3, h: 2, w: 6 }); // next row
|
||||||
|
expect(dashboard.panels[6].gridPos).toMatchObject({ x: 6, y: 3, h: 2, w: 6 });
|
||||||
|
|
||||||
|
expect(dashboard.panels[7].gridPos).toMatchObject({ x: 0, y: 5, h: 1, w: 24 });
|
||||||
|
|
||||||
|
expect(dashboard.panels[8].gridPos).toMatchObject({ x: 0, y: 6, h: 2, w: 6 }); // 2nd row
|
||||||
|
expect(dashboard.panels[9].gridPos).toMatchObject({ x: 6, y: 6, h: 2, w: 6 });
|
||||||
|
expect(dashboard.panels[10].gridPos).toMatchObject({ x: 12, y: 6, h: 2, w: 6 });
|
||||||
|
expect(dashboard.panels[11].gridPos).toMatchObject({ x: 18, y: 6, h: 2, w: 6 }); // next row
|
||||||
|
expect(dashboard.panels[12].gridPos).toMatchObject({ x: 0, y: 8, h: 2, w: 6 });
|
||||||
|
expect(dashboard.panels[13].gridPos).toMatchObject({ x: 6, y: 8, h: 2, w: 6 });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('given dashboard with row repeat', function() {
|
describe('given dashboard with row repeat', function() {
|
||||||
let dashboard, dashboardJSON;
|
let dashboard, dashboardJSON;
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export class PlaylistSearchCtrl {
|
|||||||
|
|
||||||
$timeout(() => {
|
$timeout(() => {
|
||||||
this.query.query = '';
|
this.query.query = '';
|
||||||
|
this.query.type = 'dash-db';
|
||||||
this.searchDashboards();
|
this.searchDashboards();
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<div class="gf-form-inline">
|
<div class="gf-form-inline">
|
||||||
<div class="gf-form">
|
<div class="gf-form">
|
||||||
<label class="gf-form-label query-keyword width-7">Metric</label>
|
<label class="gf-form-label query-keyword width-8">Metric</label>
|
||||||
|
|
||||||
<metric-segment segment="regionSegment" get-options="getRegions()" on-change="regionChanged()"></metric-segment>
|
<metric-segment segment="regionSegment" get-options="getRegions()" on-change="regionChanged()"></metric-segment>
|
||||||
<metric-segment segment="namespaceSegment" get-options="getNamespaces()" on-change="namespaceChanged()"></metric-segment>
|
<metric-segment segment="namespaceSegment" get-options="getNamespaces()" on-change="namespaceChanged()"></metric-segment>
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
<div class="gf-form-inline">
|
<div class="gf-form-inline">
|
||||||
<div class="gf-form">
|
<div class="gf-form">
|
||||||
<label class="gf-form-label query-keyword width-7">Dimensions</label>
|
<label class="gf-form-label query-keyword width-8">Dimensions</label>
|
||||||
<metric-segment ng-repeat="segment in dimSegments" segment="segment" get-options="getDimSegments(segment, $index)" on-change="dimSegmentChanged(segment, $index)"></metric-segment>
|
<metric-segment ng-repeat="segment in dimSegments" segment="segment" get-options="getDimSegments(segment, $index)" on-change="dimSegmentChanged(segment, $index)"></metric-segment>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -33,9 +33,9 @@
|
|||||||
|
|
||||||
<div class="gf-form-inline">
|
<div class="gf-form-inline">
|
||||||
<div class="gf-form">
|
<div class="gf-form">
|
||||||
<label class="gf-form-label query-keyword width-7">
|
<label class="gf-form-label query-keyword width-8">
|
||||||
Period
|
Min period
|
||||||
<info-popover mode="right-normal">Interval between points in seconds</info-popover>
|
<info-popover mode="right-normal">Minimum interval between points in seconds</info-popover>
|
||||||
</label>
|
</label>
|
||||||
<input type="text" class="gf-form-input" ng-model="target.period" spellcheck='false' placeholder="auto" ng-model-onblur ng-change="onChange()" />
|
<input type="text" class="gf-form-input" ng-model="target.period" spellcheck='false' placeholder="auto" ng-model-onblur ng-change="onChange()" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
<div class="panel-alert-list">
|
<div class="panel-alert-list">
|
||||||
<div class="panel-alert-list__no-alerts" ng-show="ctrl.noAlertsMessage">
|
<div class="panel-alert-list__no-alerts" ng-show="ctrl.noAlertsMessage">
|
||||||
{{ctrl.noAlertsMessage}}
|
{{ctrl.noAlertsMessage}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section ng-if="ctrl.panel.show === 'current'">
|
<section ng-if="ctrl.panel.show === 'current'">
|
||||||
<ol class="alert-rule-list">
|
<ol class="alert-rule-list">
|
||||||
<li class="alert-rule-item" ng-repeat="alert in ctrl.currentAlerts">
|
<li class="alert-rule-item" ng-repeat="alert in ctrl.currentAlerts">
|
||||||
|
<div class="alert-rule-item__icon {{alert.stateModel.stateClass}}">
|
||||||
|
<i class="{{alert.stateModel.iconClass}}"></i>
|
||||||
|
</div>
|
||||||
<div class="alert-rule-item__body">
|
<div class="alert-rule-item__body">
|
||||||
<div class="alert-rule-item__icon {{alert.stateModel.stateClass}}">
|
|
||||||
<i class="{{alert.stateModel.iconClass}}"></i>
|
|
||||||
</div>
|
|
||||||
<div class="alert-rule-item__header">
|
<div class="alert-rule-item__header">
|
||||||
<p class="alert-rule-item__name">
|
<p class="alert-rule-item__name">
|
||||||
<a href="dashboard/{{alert.dashboardUri}}?panelId={{alert.panelId}}&fullscreen&edit&tab=alert">
|
<a href="dashboard/{{alert.dashboardUri}}?panelId={{alert.panelId}}&fullscreen&edit&tab=alert">
|
||||||
|
|||||||
@@ -24,4 +24,10 @@ describe('ViewStore', () => {
|
|||||||
expect(toJS(store.query.get('values'))).toMatchObject(['A', 'B']);
|
expect(toJS(store.query.get('values'))).toMatchObject(['A', 'B']);
|
||||||
expect(store.currentUrl).toBe('/hello?values=A&values=B');
|
expect(store.currentUrl).toBe('/hello?values=A&values=B');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Query can contain boolean', () => {
|
||||||
|
store.updatePathAndQuery('/hello', { abool: true });
|
||||||
|
expect(toJS(store.query.get('abool'))).toBe(true);
|
||||||
|
expect(store.currentUrl).toBe('/hello?abool');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -135,6 +135,7 @@ $list-item-bg: $card-background;
|
|||||||
$list-item-hover-bg: lighten($gray-blue, 2%);
|
$list-item-hover-bg: lighten($gray-blue, 2%);
|
||||||
$list-item-link-color: $text-color;
|
$list-item-link-color: $text-color;
|
||||||
$list-item-shadow: $card-shadow;
|
$list-item-shadow: $card-shadow;
|
||||||
|
$empty-list-cta-bg: $gray-blue;
|
||||||
|
|
||||||
// Scrollbars
|
// Scrollbars
|
||||||
$scrollbarBackground: #404357;
|
$scrollbarBackground: #404357;
|
||||||
|
|||||||
@@ -133,6 +133,7 @@ $list-item-bg: linear-gradient(135deg, $gray-5, $gray-6); //$card-background;
|
|||||||
$list-item-hover-bg: darken($gray-5, 5%);
|
$list-item-hover-bg: darken($gray-5, 5%);
|
||||||
$list-item-link-color: $text-color;
|
$list-item-link-color: $text-color;
|
||||||
$list-item-shadow: $card-shadow;
|
$list-item-shadow: $card-shadow;
|
||||||
|
$empty-list-cta-bg: $gray-6;
|
||||||
|
|
||||||
// Tables
|
// Tables
|
||||||
// -------------------------
|
// -------------------------
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
.empty-list-cta {
|
.empty-list-cta {
|
||||||
background-color: $search-filter-box-bg;
|
background-color: $empty-list-cta-bg;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: $spacer*2;
|
padding: $spacer*2;
|
||||||
border-radius: $border-radius;
|
border-radius: $border-radius;
|
||||||
|
|||||||
Reference in New Issue
Block a user