Compare commits

...

37 Commits

Author SHA1 Message Date
Torkel Ödegaard
5d16da732f Search: removed old not working search shortcuts (#17226)
(cherry picked from commit e421723992)
2019-05-22 12:28:56 +02:00
Daniel Lee
ee6703fedd azuremonitor: revert to clearing chained dropdowns (#17212)
* azuremonitor: revert to clearing chained dropdowns

After feedback from users, changing back to clearing
dropdowns to the right in the chain. E.g. if the user
changes the subscription dropdown which is first in
the chain then all the dependent dropdowns to the right
should be cleared (reset to default values).

Also, now triggers getting subscriptions every time the
dropdown menu is shown rather than just the first time.
It is apparently common to add subscriptions while
building queries.

(cherry picked from commit 577beebcca)
2019-05-22 12:24:43 +02:00
Torkel Ödegaard
ec8be5bf61 Search: changed how search filter on current folder works (#17219)
(cherry picked from commit a96b522a42)
2019-05-22 12:24:43 +02:00
Torkel Ödegaard
f3c2d03637 codespell: update codespell ignore list 2019-05-21 17:28:57 +02:00
Torkel Ödegaard
eb2b31007d Release: Updated version 2019-05-21 16:58:15 +02:00
Ryan McKinley
98f9dbde95 DataSourceMeta: add an option to get hidden queries (#17124)
* add an option to get hidden queries

* make sure you have meta

* supportsHiddenQueries

* remove spaces

* DataSources: hidden queries flag

(cherry picked from commit 1033f0f905)
2019-05-21 16:46:52 +02:00
Dominik Prokop
7e090ea2a6 Panel: Apply option defaults on panel init and on save model retrieval (#17174)
* Apply panel options defaults on panel init and on save model retrieval

* Remove unnecessary argument, added tests

* Make FieldPropertiesEditor statefull to enable onBlur changes

* Remove unnecessary import

* Post-review updates

Fixes #17154

(cherry picked from commit 73e4178aef)
2019-05-21 16:38:36 +02:00
Torkel Ödegaard
6a02faeeab BarGauge: Fix for negative min values (#17192)
(cherry picked from commit 874039992f)
2019-05-21 15:54:46 +02:00
Daniel Lee
a6a939cfd3 Azuremonitor: multiple subscription support for alerting (#17195)
* fix: azuremonitor adds multi-sub support to alerting

* fix: AzureMonitor missing parameter in metadata func

getMetricMetadata function when called in the query ctrl
was missing a parameter for Subscription Id.

Also, made some tweaks to what happens when a chained
dropdown is changed to not reset all the fields that
are dependent on it.

(cherry picked from commit fa9ffe38d2)
2019-05-21 15:54:46 +02:00
Brian Gann
5a10298bd3 MSI: Generate sha256sum during MSI build process in circleci (#17120)
* build: generate sha256 during msi build

(cherry picked from commit f98095d629)
2019-05-21 15:54:46 +02:00
Torkel Ödegaard
f52f7c101c Release: Improved cherry pick task (#17087)
* Release: Improved cherry pick task

* Minor tweak to formatting

(cherry picked from commit 058f5a1682)
2019-05-21 15:54:10 +02:00
Torkel Ödegaard
7824f66cd3 Explore: fixed cherrypick / merge issue 2019-05-15 13:10:59 +02:00
Torkel Ödegaard
71c1e8a731 Release: Bumped version to v6.2.0-beta2 2019-05-15 12:21:21 +02:00
Carl Bergquist
74bc94bcec Remotecache: Avoid race condition in Set causing error on insert. (#17082)
* remotecache: avoid race condition in set

since set called the database twice without transactions another
operation could insert a value before the first operation completed.
which would raise an error on insert since the data have been inserted
by the other request.

closes #17079

(cherry picked from commit aed3d0d3ad)
2019-05-15 12:14:16 +02:00
Brian Gann
f566da0ff6 Build: Support publishing MSI to grafana.com (#17073)
* add test for msi, and support for publishing msi
* update arch and os in test
* Build: Fixed issues with os naming

(cherry picked from commit d0ea98f6bd)
2019-05-15 12:14:16 +02:00
Torkel Ödegaard
216aff96fd Panels: Fixed alert icon position in panel header (#17070)
(cherry picked from commit 238a929262)
2019-05-15 12:14:15 +02:00
Torkel Ödegaard
9de11c25a6 Gauge: Fix switching orientation issue when switching from BarGauge to Gauge (#17064)
(cherry picked from commit 68ad93f410)
2019-05-15 12:14:15 +02:00
Torkel Ödegaard
599e1030d8 Dashboard: Fixes lazy loading & expanding collapsed rows on mobile (#17055)
* Dashboard: Fixes lazy loading & expanding collapsing rows on mobile

Fixes #16978

* Updated dashboard tags

(cherry picked from commit 74a31bd9b4)
2019-05-15 12:14:15 +02:00
Daniel Lee
d62da61d8a fix: Azure Monitor adds missing closing div tag to query editor (#17057)
(cherry picked from commit 4bc1a66fe4)
2019-05-15 12:14:15 +02:00
Johannes Schill
a2ca973925 Search: Set element height to 100% to avoid Chrome 74's overflow (#17054)
Fixes #16981

(cherry picked from commit ceb21bd653)
2019-05-15 12:14:15 +02:00
Johannes Schill
98da29fd7b Dashboard: Fixes scrolling issues for Edge browser (#17033)
* Fix: Only set scrollTop on CustomScroll element when it's needed and move arrow functions out of the props

* Fix: Update snapshots

* Minor refactoring to reuse same functions when rendering custom scrollbar

Fixes #16796

(cherry picked from commit 1001cd7ac3)
2019-05-15 12:14:15 +02:00
Torkel Ödegaard
888ff61d30 Dashboard: show refresh button in kiosk mode (#17032)
Fixes #16945

(cherry picked from commit 3ce2f3b58d)
2019-05-15 12:14:15 +02:00
Torkel Ödegaard
c1be3adf3b Gauge: tweaks to background color and height usage (#17019)
(cherry picked from commit 597c380ead)
2019-05-15 12:14:15 +02:00
Marcus Efraimsson
ede9d9964d Explore: Fix empty result from datasource should render logs container (#16999)
Make sure to return an empty logs model instead of undefined so that 
the logs container renders an empty graph and log result in Explore.

Fixes #16997

(cherry picked from commit 8eb78ea931)
2019-05-15 12:14:15 +02:00
Hugo Häggmark
5cd69e8d39 Explore: Fixes zoom exception in Loki/Graph (#16991)
Fixes: #16986
(cherry picked from commit d5a35f3631)
2019-05-15 12:14:14 +02:00
Torkel Ödegaard
ceb3672482 Panels: Fixed error panel tooltip (#16993)
Fixes #16989

(cherry picked from commit 5573d28582)
2019-05-15 12:13:29 +02:00
Will Medlar
e519a9d2c4 Docker: Prevent a permission denied error when writing files to the default provisioning directory (#16831)
* build: Grant ownership of provisioning directory to runtime user

(cherry picked from commit 5e44f001fb)
2019-05-15 12:07:59 +02:00
Ryan McKinley
2a3d6604c0 GettingStarted: convert to react panel plugin (#16985)
* getting started

* getting started

(cherry picked from commit f617cd8975)
2019-05-15 11:53:46 +02:00
Marcus Efraimsson
9cf0ea5395 plugins: fix how datemath utils are exposed to plugins (#16976)
Fixes a regression accidentally introduced by #16890 so that datemath 
utils are exposed to plugins in a backward-compatible way.

Fixes #16962

(cherry picked from commit 0c1530c7a8)
2019-05-15 11:53:35 +02:00
Torkel Ödegaard
db37e138bf GettingStarted: Fixes layout issues, fixes #16926 (#16941)
(cherry picked from commit a9e01d8b04)
2019-05-15 11:53:27 +02:00
Torkel Ödegaard
a5a6d43f47 PanelModel: Fix crash after window resize, fixes #16933 (#16942)
(cherry picked from commit f58ab7945b)
2019-05-15 11:52:31 +02:00
Torkel Ödegaard
d9950aa4f1 Singlestat: fixed centering issue for very small panels (#16944)
(cherry picked from commit e97853abc9)
2019-05-15 11:52:23 +02:00
Stephen SORRIAUX
6e9a395063 InfluxDB: Fix HTTP method should default to GET (#16949)
Fixes #16929

(cherry picked from commit e7a9afe983)
2019-05-15 11:52:14 +02:00
Ryan McKinley
7374aafb90 AppPlugin: Menu Edit Url Fix (#16934)
(cherry picked from commit 17ce1273ca)
2019-05-15 11:52:02 +02:00
Ryan McKinley
b54e9880b4 Plugins: update beta notice style (#16928)
(cherry picked from commit b08cf1e7ac)
2019-05-15 11:51:50 +02:00
Brian Gann
09672a287f Plugins: Support templated urls in routes (#16599)
This adds support for using templated/dynamic urls in routes.
* refactor interpolateString into utils and add interpolation support for app plugin routes.
* cleanup and add error check for url parse failure
* add docs for interpolated route urls

Closes #16835

(cherry picked from commit b07d0b1026)
2019-05-15 11:50:34 +02:00
Andrej Ocenas
9d877d670e release 6.2.0-beta1 2019-05-07 16:08:46 +02:00
89 changed files with 4603 additions and 1962 deletions

View File

@@ -81,7 +81,7 @@ jobs:
- run:
# Important: all words have to be in lowercase, and separated by "\n".
name: exclude known exceptions
command: 'echo -e "unknwon" > words_to_ignore.txt'
command: 'echo -e "unknwon\nreferer\nerrorstring" > words_to_ignore.txt'
- run:
name: check documentation spelling errors
command: 'codespell -I ./words_to_ignore.txt docs/'
@@ -566,6 +566,7 @@ jobs:
root: .
paths:
- dist/grafana-*.msi
- dist/grafana-*.msi.sha256
store-build-artifacts:
docker:

View File

@@ -66,8 +66,8 @@ RUN mkdir -p "$GF_PATHS_HOME/.aws" && \
"$GF_PATHS_DATA" && \
cp "$GF_PATHS_HOME/conf/sample.ini" "$GF_PATHS_CONFIG" && \
cp "$GF_PATHS_HOME/conf/ldap.toml" /etc/grafana/ldap.toml && \
chown -R grafana:grafana "$GF_PATHS_DATA" "$GF_PATHS_HOME/.aws" "$GF_PATHS_LOGS" "$GF_PATHS_PLUGINS" && \
chmod 777 "$GF_PATHS_DATA" "$GF_PATHS_HOME/.aws" "$GF_PATHS_LOGS" "$GF_PATHS_PLUGINS"
chown -R grafana:grafana "$GF_PATHS_DATA" "$GF_PATHS_HOME/.aws" "$GF_PATHS_LOGS" "$GF_PATHS_PLUGINS" "$GF_PATHS_PROVISIONING" && \
chmod 777 -R "$GF_PATHS_DATA" "$GF_PATHS_HOME/.aws" "$GF_PATHS_LOGS" "$GF_PATHS_PLUGINS" "$GF_PATHS_PROVISIONING"
COPY --from=0 /go/src/github.com/grafana/grafana/bin/linux-amd64/grafana-server /go/src/github.com/grafana/grafana/bin/linux-amd64/grafana-cli ./bin/
COPY --from=1 /usr/src/app/public ./public

File diff suppressed because it is too large Load Diff

View File

@@ -48,28 +48,6 @@
"y": 0
},
"headings": false,
"id": 8,
"limit": 1000,
"links": [],
"query": "",
"recent": false,
"search": true,
"starred": false,
"tags": ["panel-demo"],
"timeFrom": null,
"timeShift": null,
"title": "tag: panel-demo",
"type": "dashlist"
},
{
"folderId": null,
"gridPos": {
"h": 13,
"w": 6,
"x": 12,
"y": 0
},
"headings": false,
"id": 2,
"limit": 1000,
"links": [],
@@ -83,6 +61,28 @@
"title": "tag: panel-tests",
"type": "dashlist"
},
{
"folderId": null,
"gridPos": {
"h": 26,
"w": 6,
"x": 12,
"y": 0
},
"headings": false,
"id": 3,
"limit": 1000,
"links": [],
"query": "",
"recent": false,
"search": true,
"starred": false,
"tags": ["gdev", "demo"],
"timeFrom": null,
"timeShift": null,
"title": "tag: dashboard-demo",
"type": "dashlist"
},
{
"folderId": null,
"gridPos": {
@@ -114,28 +114,6 @@
"y": 13
},
"headings": false,
"id": 3,
"limit": 1000,
"links": [],
"query": "",
"recent": false,
"search": true,
"starred": false,
"tags": ["gdev", "demo"],
"timeFrom": null,
"timeShift": null,
"title": "tag: dashboard-demo",
"type": "dashlist"
},
{
"folderId": null,
"gridPos": {
"h": 13,
"w": 6,
"x": 12,
"y": 13
},
"headings": false,
"id": 4,
"limit": 1000,
"links": [],
@@ -146,7 +124,7 @@
"tags": ["templating", "gdev"],
"timeFrom": null,
"timeShift": null,
"title": "tag: templating",
"title": "tag: templating ",
"type": "dashlist"
}
],
@@ -167,5 +145,5 @@
"timezone": "",
"title": "Grafana Dev Overview & Home",
"uid": "j6T00KRZz",
"version": 1
"version": 2
}

View File

@@ -15,26 +15,27 @@
"editable": true,
"gnetId": null,
"graphTooltip": 0,
"id": 7501,
"links": [],
"panels": [
{
"datasource": "gdev-testdata",
"gridPos": {
"h": 7,
"w": 18,
"w": 24,
"x": 0,
"y": 0
},
"id": 7,
"id": 2,
"links": [],
"options": {
"displayMode": "gradient",
"displayMode": "lcd",
"fieldOptions": {
"calcs": ["mean"],
"defaults": {
"decimals": null,
"max": 100,
"min": 0,
"unit": "watt"
"unit": "decgbytes"
},
"mappings": [],
"override": {},
@@ -47,7 +48,7 @@
{
"color": "orange",
"index": 1,
"value": 40
"value": 60
},
{
"color": "red",
@@ -59,95 +60,188 @@
},
"orientation": "vertical"
},
"pluginVersion": "6.2.0-pre",
"targets": [
{
"alias": "sda1",
"refId": "A",
"scenarioId": "random_walk"
},
{
"alias": "sda2",
"refId": "B",
"scenarioId": "random_walk"
},
{
"alias": "sda3",
"refId": "C",
"scenarioId": "random_walk"
},
{
"alias": "sda4",
"refId": "D",
"scenarioId": "random_walk"
},
{
"alias": "sda5",
"refId": "E",
"scenarioId": "csv_metric_values",
"stringInput": "10003,33333"
"scenarioId": "random_walk"
},
{
"alias": "sda6",
"refId": "F",
"scenarioId": "random_walk"
},
{
"alias": "sda7",
"refId": "G",
"scenarioId": "random_walk"
},
{
"alias": "sda8",
"refId": "H",
"scenarioId": "csv_metric_values",
"stringInput": "100,100,100"
"scenarioId": "random_walk"
},
{
"alias": "sda9",
"refId": "I",
"scenarioId": "random_walk"
},
{
"alias": "sda10",
"refId": "J",
"scenarioId": "random_walk"
},
{
"alias": "sda11",
"refId": "K",
"scenarioId": "random_walk"
},
{
"alias": "sda12",
"refId": "L",
"scenarioId": "random_walk"
},
{
"alias": "sda13",
"refId": "M",
"scenarioId": "random_walk"
},
{
"alias": "sda14",
"refId": "N",
"scenarioId": "random_walk"
},
{
"alias": "sda15",
"refId": "O",
"scenarioId": "random_walk"
},
{
"alias": "sda16",
"refId": "P",
"scenarioId": "random_walk"
},
{
"refId": "Q",
"scenarioId": "random_walk"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Usage",
"title": "",
"transparent": true,
"type": "bargauge"
},
{
"datasource": "gdev-testdata",
"gridPos": {
"h": 22,
"w": 6,
"x": 18,
"y": 0
"h": 10,
"w": 16,
"x": 0,
"y": 7
},
"id": 8,
"id": 4,
"links": [],
"options": {
"displayMode": "gradient",
"fieldOptions": {
"calcs": ["mean"],
"defaults": {
"decimals": null,
"max": 100,
"min": 0,
"unit": "celsius"
},
"mappings": [],
"override": {},
"thresholds": [
{
"color": "blue",
"index": 0,
"value": null
},
{
"color": "green",
"index": 1,
"value": 20
},
{
"color": "orange",
"index": 2,
"value": 40
},
{
"color": "red",
"index": 3,
"value": 80
}
],
"values": false
},
"orientation": "horizontal"
},
"pluginVersion": "6.2.0-pre",
"targets": [
{
"alias": "Inside",
"refId": "H",
"scenarioId": "csv_metric_values",
"stringInput": "100,100,100"
},
{
"alias": "Outhouse",
"refId": "A",
"scenarioId": "random_walk"
},
{
"alias": "Area B",
"refId": "B",
"scenarioId": "random_walk"
},
{
"alias": "Basement",
"refId": "C",
"scenarioId": "random_walk"
},
{
"alias": "Garage",
"refId": "D",
"scenarioId": "random_walk"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Gradient mode",
"type": "bargauge"
},
{
"datasource": "gdev-testdata",
"gridPos": {
"h": 10,
"w": 6,
"x": 16,
"y": 7
},
"id": 6,
"links": [],
"options": {
"displayMode": "basic",
"fieldOptions": {
"calcs": ["mean"],
"defaults": {
@@ -160,19 +254,24 @@
"override": {},
"thresholds": [
{
"color": "green",
"color": "blue",
"index": 0,
"value": null
},
{
"color": "orange",
"color": "green",
"index": 1,
"value": 55
"value": 42.5
},
{
"color": "orange",
"index": 2,
"value": 80
},
{
"color": "red",
"index": 2,
"value": 95
"index": 3,
"value": 90
}
],
"values": false
@@ -181,10 +280,6 @@
},
"pluginVersion": "6.2.0-pre",
"targets": [
{
"refId": "E",
"scenarioId": "random_walk"
},
{
"refId": "H",
"scenarioId": "csv_metric_values",
@@ -194,22 +289,6 @@
"refId": "A",
"scenarioId": "random_walk"
},
{
"refId": "B",
"scenarioId": "random_walk"
},
{
"refId": "C",
"scenarioId": "random_walk"
},
{
"refId": "D",
"scenarioId": "random_walk"
},
{
"refId": "I",
"scenarioId": "random_walk"
},
{
"refId": "J",
"scenarioId": "random_walk"
@@ -241,47 +320,78 @@
{
"refId": "Q",
"scenarioId": "random_walk"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Basic",
"type": "bargauge"
},
{
"datasource": "gdev-testdata",
"gridPos": {
"h": 22,
"w": 2,
"x": 22,
"y": 7
},
"id": 8,
"links": [],
"options": {
"displayMode": "lcd",
"fieldOptions": {
"calcs": ["mean"],
"defaults": {
"max": 100,
"min": 0
},
"mappings": [],
"override": {},
"thresholds": [
{
"color": "red",
"index": 0,
"value": null
},
{
"color": "red",
"index": 1,
"value": 90
}
],
"values": false
},
"orientation": "vertical"
},
"targets": [
{
"refId": "F",
"scenarioId": "random_walk"
},
{
"refId": "G",
"scenarioId": "random_walk"
},
{
"refId": "R",
"scenarioId": "random_walk"
},
{
"refId": "S",
"refId": "A",
"scenarioId": "random_walk"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Usage",
"title": "Completion",
"type": "bargauge"
},
{
"datasource": "gdev-testdata",
"gridPos": {
"h": 15,
"w": 11,
"h": 12,
"w": 22,
"x": 0,
"y": 7
"y": 17
},
"id": 6,
"id": 10,
"links": [],
"options": {
"displayMode": "gradient",
"fieldOptions": {
"calcs": ["mean"],
"defaults": {
"decimals": null,
"max": 100,
"min": 0,
"unit": "celsius"
"unit": "decgbytes"
},
"mappings": [],
"override": {},
@@ -294,12 +404,12 @@
{
"color": "green",
"index": 1,
"value": 20
"value": 30
},
{
"color": "orange",
"index": 2,
"value": 40
"value": 60
},
{
"color": "red",
@@ -309,69 +419,113 @@
],
"values": false
},
"orientation": "horizontal"
"orientation": "vertical"
},
"pluginVersion": "6.2.0-pre",
"targets": [
{
"alias": "Inside",
"refId": "H",
"scenarioId": "csv_metric_values",
"stringInput": "100,100,100"
},
{
"alias": "Outhouse",
"alias": "sda1",
"refId": "A",
"scenarioId": "random_walk"
},
{
"alias": "Area B",
"alias": "sda2",
"refId": "B",
"scenarioId": "random_walk"
},
{
"alias": "Basement",
"alias": "sda3",
"refId": "C",
"scenarioId": "random_walk"
},
{
"alias": "Garage",
"alias": "sda4",
"refId": "D",
"scenarioId": "random_walk"
},
{
"alias": "Attic",
"alias": "sda5",
"refId": "E",
"scenarioId": "random_walk"
},
{
"alias": "sda6",
"refId": "F",
"scenarioId": "random_walk"
},
{
"alias": "sda7",
"refId": "G",
"scenarioId": "random_walk"
},
{
"alias": "sda8",
"refId": "H",
"scenarioId": "random_walk"
},
{
"alias": "sda9",
"refId": "I",
"scenarioId": "random_walk"
},
{
"alias": "sda10",
"refId": "J",
"scenarioId": "random_walk"
},
{
"alias": "sda11",
"refId": "K",
"scenarioId": "random_walk"
},
{
"alias": "sda12",
"refId": "L",
"scenarioId": "random_walk"
},
{
"alias": "sda13",
"refId": "M",
"scenarioId": "random_walk"
},
{
"alias": "sda14",
"refId": "N",
"scenarioId": "random_walk"
},
{
"alias": "sda15",
"refId": "O",
"scenarioId": "random_walk"
},
{
"alias": "sda16",
"refId": "P",
"scenarioId": "random_walk"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Temperature",
"title": "",
"type": "bargauge"
},
{
"datasource": "gdev-testdata",
"gridPos": {
"h": 15,
"w": 7,
"x": 11,
"y": 7
"h": 8,
"w": 24,
"x": 0,
"y": 29
},
"id": 9,
"id": 11,
"links": [],
"options": {
"displayMode": "basic",
"fieldOptions": {
"calcs": ["mean"],
"defaults": {
"decimals": null,
"max": 100,
"min": 0,
"unit": "celsius"
"unit": "decgbytes"
},
"mappings": [],
"override": {},
@@ -384,12 +538,12 @@
{
"color": "green",
"index": 1,
"value": 20
"value": 30
},
{
"color": "orange",
"index": 2,
"value": 40
"value": 60
},
{
"color": "red",
@@ -399,89 +553,113 @@
],
"values": false
},
"orientation": "horizontal"
"orientation": "vertical"
},
"pluginVersion": "6.2.0-pre",
"targets": [
{
"alias": "Inside",
"refId": "H",
"scenarioId": "csv_metric_values",
"stringInput": "100,100,100"
},
{
"alias": "Outhouse",
"alias": "sda1",
"refId": "A",
"scenarioId": "random_walk"
},
{
"alias": "Area B",
"alias": "sda2",
"refId": "B",
"scenarioId": "random_walk"
},
{
"alias": "Basement",
"alias": "sda3",
"refId": "C",
"scenarioId": "random_walk"
},
{
"alias": "Garage",
"alias": "sda4",
"refId": "D",
"scenarioId": "random_walk"
},
{
"alias": "Attic",
"alias": "sda5",
"refId": "E",
"scenarioId": "random_walk"
},
{
"alias": "sda6",
"refId": "F",
"scenarioId": "random_walk"
},
{
"alias": "sda7",
"refId": "G",
"scenarioId": "random_walk"
},
{
"alias": "sda8",
"refId": "H",
"scenarioId": "random_walk"
},
{
"alias": "sda9",
"refId": "I",
"scenarioId": "random_walk"
},
{
"alias": "sda10",
"refId": "J",
"scenarioId": "random_walk"
},
{
"alias": "sda11",
"refId": "K",
"scenarioId": "random_walk"
},
{
"alias": "sda12",
"refId": "L",
"scenarioId": "random_walk"
},
{
"alias": "sda13",
"refId": "M",
"scenarioId": "random_walk"
},
{
"alias": "sda14",
"refId": "N",
"scenarioId": "random_walk"
},
{
"alias": "sda15",
"refId": "O",
"scenarioId": "random_walk"
},
{
"alias": "sda16",
"refId": "P",
"scenarioId": "random_walk"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Temperature",
"title": "",
"type": "bargauge"
}
],
"refresh": false,
"refresh": "10s",
"schemaVersion": 18,
"style": "dark",
"tags": ["gdev", "bargauge", "panel-demo"],
"tags": ["gdev", "demo"],
"templating": {
"list": []
},
"time": {
"from": "now-30m",
"from": "now-6h",
"to": "now"
},
"timepicker": {
"refresh_intervals": ["1s", "5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"],
"refresh_intervals": ["2s", "5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"],
"time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"]
},
"timezone": "",
"title": "Bar Gauge Animated Demo",
"uid": "k5IUwQeikaa",
"version": 1
"title": "Bar Gauge Demo",
"uid": "vmie2cmWz",
"version": 3
}

View File

@@ -1,376 +0,0 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": "-- Grafana --",
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"gnetId": null,
"graphTooltip": 0,
"links": [],
"panels": [
{
"gridPos": {
"h": 7,
"w": 18,
"x": 0,
"y": 0
},
"id": 7,
"links": [],
"options": {
"displayMode": "gradient",
"fieldOptions": {
"calcs": ["mean"],
"defaults": {
"decimals": null,
"max": 100,
"min": 0,
"unit": "watt"
},
"mappings": [],
"override": {},
"thresholds": [
{
"color": "green",
"index": 0,
"value": null
},
{
"color": "orange",
"index": 1,
"value": 40
},
{
"color": "red",
"index": 2,
"value": 80
}
],
"values": false
},
"orientation": "vertical"
},
"pluginVersion": "6.2.0-pre",
"targets": [
{
"refId": "A",
"scenarioId": "random_walk"
},
{
"refId": "B",
"scenarioId": "random_walk"
},
{
"refId": "C",
"scenarioId": "random_walk"
},
{
"refId": "D",
"scenarioId": "random_walk"
},
{
"refId": "E",
"scenarioId": "csv_metric_values",
"stringInput": "10003,33333"
},
{
"refId": "F",
"scenarioId": "random_walk"
},
{
"refId": "G",
"scenarioId": "random_walk"
},
{
"refId": "H",
"scenarioId": "csv_metric_values",
"stringInput": "100,100,100"
},
{
"refId": "I",
"scenarioId": "random_walk"
},
{
"refId": "J",
"scenarioId": "random_walk"
},
{
"refId": "K",
"scenarioId": "random_walk"
},
{
"refId": "L",
"scenarioId": "random_walk"
},
{
"refId": "M",
"scenarioId": "random_walk"
},
{
"refId": "N",
"scenarioId": "random_walk"
},
{
"refId": "O",
"scenarioId": "random_walk"
},
{
"refId": "P",
"scenarioId": "random_walk"
},
{
"refId": "Q",
"scenarioId": "random_walk"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Usage",
"type": "bargauge"
},
{
"gridPos": {
"h": 20,
"w": 6,
"x": 18,
"y": 0
},
"id": 8,
"links": [],
"options": {
"displayMode": "gradient",
"fieldOptions": {
"calcs": ["mean"],
"defaults": {
"decimals": null,
"max": 100,
"min": 0,
"unit": "watt"
},
"mappings": [],
"override": {},
"thresholds": [
{
"color": "green",
"index": 0,
"value": null
},
{
"color": "orange",
"index": 1,
"value": 65
},
{
"color": "red",
"index": 2,
"value": 95
}
],
"values": false
},
"orientation": "horizontal"
},
"pluginVersion": "6.2.0-pre",
"targets": [
{
"refId": "E",
"scenarioId": "random_walk"
},
{
"refId": "H",
"scenarioId": "csv_metric_values",
"stringInput": "100,100,100"
},
{
"refId": "A",
"scenarioId": "random_walk"
},
{
"refId": "B",
"scenarioId": "random_walk"
},
{
"refId": "C",
"scenarioId": "random_walk"
},
{
"refId": "D",
"scenarioId": "random_walk"
},
{
"refId": "I",
"scenarioId": "random_walk"
},
{
"refId": "J",
"scenarioId": "random_walk"
},
{
"refId": "K",
"scenarioId": "random_walk"
},
{
"refId": "L",
"scenarioId": "random_walk"
},
{
"refId": "M",
"scenarioId": "random_walk"
},
{
"refId": "N",
"scenarioId": "random_walk"
},
{
"refId": "O",
"scenarioId": "random_walk"
},
{
"refId": "P",
"scenarioId": "random_walk"
},
{
"refId": "Q",
"scenarioId": "random_walk"
},
{
"refId": "F",
"scenarioId": "random_walk"
},
{
"refId": "G",
"scenarioId": "random_walk"
},
{
"refId": "R",
"scenarioId": "random_walk"
},
{
"refId": "S",
"scenarioId": "random_walk"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Usage",
"type": "bargauge"
},
{
"gridPos": {
"h": 13,
"w": 18,
"x": 0,
"y": 7
},
"id": 6,
"links": [],
"options": {
"displayMode": "gradient",
"fieldOptions": {
"calcs": ["mean"],
"defaults": {
"decimals": null,
"max": 100,
"min": 0,
"unit": "celsius"
},
"mappings": [],
"override": {},
"thresholds": [
{
"color": "blue",
"index": 0,
"value": null
},
{
"color": "green",
"index": 1,
"value": 20
},
{
"color": "orange",
"index": 2,
"value": 40
},
{
"color": "red",
"index": 3,
"value": 80
}
],
"values": false
},
"orientation": "horizontal"
},
"pluginVersion": "6.2.0-pre",
"targets": [
{
"alias": "Inside",
"refId": "H",
"scenarioId": "csv_metric_values",
"stringInput": "100,100,100"
},
{
"alias": "Outhouse",
"refId": "A",
"scenarioId": "random_walk"
},
{
"alias": "Area B",
"refId": "B",
"scenarioId": "random_walk"
},
{
"alias": "Basement",
"refId": "C",
"scenarioId": "random_walk"
},
{
"alias": "Garage",
"refId": "D",
"scenarioId": "random_walk"
},
{
"alias": "Attic",
"refId": "E",
"scenarioId": "random_walk"
},
{
"refId": "F",
"scenarioId": "random_walk"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Temperature",
"type": "bargauge"
}
],
"schemaVersion": 18,
"style": "dark",
"tags": ["gdev", "bargauge", "panel-demo"],
"templating": {
"list": []
},
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {
"refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"],
"time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"]
},
"timezone": "",
"title": "Bar Gauge Gradient Demo",
"uid": "RndRQw6mz",
"version": 1
}

View File

@@ -1,405 +0,0 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": "-- Grafana --",
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"gnetId": null,
"graphTooltip": 0,
"links": [],
"panels": [
{
"gridPos": {
"h": 7,
"w": 22,
"x": 0,
"y": 0
},
"id": 7,
"links": [],
"options": {
"displayMode": "lcd",
"fieldOptions": {
"calcs": ["mean"],
"defaults": {
"decimals": null,
"max": 100,
"min": 0,
"unit": "watt"
},
"mappings": [],
"override": {},
"thresholds": [
{
"color": "green",
"index": 0,
"value": null
},
{
"color": "orange",
"index": 1,
"value": 40
},
{
"color": "red",
"index": 2,
"value": 80
}
],
"values": false
},
"orientation": "vertical"
},
"pluginVersion": "6.2.0-pre",
"targets": [
{
"refId": "A",
"scenarioId": "random_walk"
},
{
"refId": "B",
"scenarioId": "random_walk"
},
{
"refId": "C",
"scenarioId": "random_walk"
},
{
"refId": "D",
"scenarioId": "random_walk"
},
{
"refId": "E",
"scenarioId": "csv_metric_values",
"stringInput": "10003,33333"
},
{
"refId": "F",
"scenarioId": "random_walk"
},
{
"refId": "G",
"scenarioId": "random_walk"
},
{
"refId": "H",
"scenarioId": "csv_metric_values",
"stringInput": "100,100,100"
},
{
"refId": "I",
"scenarioId": "random_walk"
},
{
"refId": "J",
"scenarioId": "random_walk"
},
{
"refId": "K",
"scenarioId": "random_walk"
},
{
"refId": "L",
"scenarioId": "random_walk"
},
{
"refId": "M",
"scenarioId": "random_walk"
},
{
"refId": "N",
"scenarioId": "random_walk"
},
{
"refId": "O",
"scenarioId": "random_walk"
},
{
"refId": "P",
"scenarioId": "random_walk"
},
{
"refId": "Q",
"scenarioId": "random_walk"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Usage",
"type": "bargauge"
},
{
"gridPos": {
"h": 20,
"w": 2,
"x": 22,
"y": 0
},
"id": 11,
"links": [],
"options": {
"displayMode": "lcd",
"fieldOptions": {
"calcs": ["mean"],
"defaults": {
"decimals": null,
"max": 100,
"min": 0,
"unit": "percent"
},
"mappings": [],
"override": {},
"thresholds": [
{
"color": "green",
"index": 0,
"value": null
},
{
"color": "red",
"index": 1,
"value": 80
}
],
"values": false
},
"orientation": "vertical"
},
"pluginVersion": "6.2.0-pre",
"targets": [
{
"refId": "A",
"scenarioId": "random_walk"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Progress",
"type": "bargauge"
},
{
"gridPos": {
"h": 13,
"w": 10,
"x": 0,
"y": 7
},
"id": 6,
"links": [],
"options": {
"displayMode": "gradient",
"fieldOptions": {
"calcs": ["mean"],
"defaults": {
"decimals": null,
"max": 100,
"min": 0,
"unit": "celsius"
},
"mappings": [],
"override": {},
"thresholds": [
{
"color": "blue",
"index": 0,
"value": null
},
{
"color": "green",
"index": 1,
"value": 20
},
{
"color": "orange",
"index": 2,
"value": 40
},
{
"color": "red",
"index": 3,
"value": 80
}
],
"values": false
},
"orientation": "horizontal"
},
"pluginVersion": "6.2.0-pre",
"targets": [
{
"alias": "Inside",
"refId": "H",
"scenarioId": "csv_metric_values",
"stringInput": "100,100,100"
},
{
"alias": "Outhouse",
"refId": "A",
"scenarioId": "random_walk"
},
{
"alias": "Area B",
"refId": "B",
"scenarioId": "random_walk"
},
{
"alias": "Basement",
"refId": "C",
"scenarioId": "random_walk"
},
{
"alias": "Garage",
"refId": "D",
"scenarioId": "random_walk"
},
{
"alias": "Attic",
"refId": "E",
"scenarioId": "random_walk"
},
{
"refId": "F",
"scenarioId": "random_walk"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Temperature",
"type": "bargauge"
},
{
"gridPos": {
"h": 13,
"w": 12,
"x": 10,
"y": 7
},
"id": 8,
"links": [],
"options": {
"displayMode": "basic",
"fieldOptions": {
"calcs": ["mean"],
"defaults": {
"decimals": null,
"max": 100,
"min": 0,
"unit": "watt"
},
"mappings": [],
"override": {},
"thresholds": [
{
"color": "green",
"index": 0,
"value": null
},
{
"color": "purple",
"index": 1,
"value": 50
},
{
"color": "blue",
"index": 2,
"value": 70
}
],
"values": false
},
"orientation": "horizontal"
},
"pluginVersion": "6.2.0-pre",
"targets": [
{
"refId": "H",
"scenarioId": "csv_metric_values",
"stringInput": "100,100,100"
},
{
"refId": "A",
"scenarioId": "random_walk"
},
{
"refId": "B",
"scenarioId": "random_walk"
},
{
"refId": "C",
"scenarioId": "random_walk"
},
{
"refId": "D",
"scenarioId": "random_walk"
},
{
"refId": "I",
"scenarioId": "random_walk"
},
{
"refId": "J",
"scenarioId": "random_walk"
},
{
"refId": "K",
"scenarioId": "random_walk"
},
{
"refId": "L",
"scenarioId": "random_walk"
},
{
"refId": "M",
"scenarioId": "random_walk"
},
{
"refId": "N",
"scenarioId": "random_walk"
},
{
"refId": "O",
"scenarioId": "random_walk"
},
{
"refId": "P",
"scenarioId": "random_walk"
},
{
"refId": "Q",
"scenarioId": "random_walk"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Usage",
"type": "bargauge"
}
],
"schemaVersion": 18,
"style": "dark",
"tags": ["gdev", "bargauge", "panel-demo"],
"templating": {
"list": []
},
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {
"refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"],
"time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"]
},
"timezone": "",
"title": "Bar Gauge All Modes Demo",
"uid": "zt2f6NgZzaa",
"version": 1
}

View File

@@ -0,0 +1,829 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": "-- Grafana --",
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"gnetId": null,
"graphTooltip": 0,
"links": [],
"panels": [
{
"gridPos": {
"h": 7,
"w": 6,
"x": 0,
"y": 0
},
"id": 6,
"links": [],
"options": {
"displayMode": "gradient",
"fieldOptions": {
"calcs": ["mean"],
"defaults": {
"decimals": null,
"max": 100,
"min": 0,
"unit": "celsius"
},
"mappings": [],
"override": {},
"thresholds": [
{
"color": "blue",
"index": 0,
"value": null
},
{
"color": "green",
"index": 1,
"value": 20
},
{
"color": "orange",
"index": 2,
"value": 40
},
{
"color": "red",
"index": 3,
"value": 80
}
],
"values": false
},
"orientation": "horizontal"
},
"pluginVersion": "6.2.0-pre",
"targets": [
{
"alias": "Inside",
"refId": "H",
"scenarioId": "csv_metric_values",
"stringInput": "100,100,100"
},
{
"alias": "Outhouse",
"refId": "A",
"scenarioId": "random_walk"
},
{
"refId": "F",
"scenarioId": "random_walk"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Title above bar",
"type": "bargauge"
},
{
"gridPos": {
"h": 7,
"w": 5,
"x": 6,
"y": 0
},
"id": 12,
"links": [],
"options": {
"displayMode": "gradient",
"fieldOptions": {
"calcs": ["mean"],
"defaults": {
"decimals": null,
"max": 100,
"min": 0,
"unit": "celsius"
},
"mappings": [],
"override": {},
"thresholds": [
{
"color": "blue",
"index": 0,
"value": null
},
{
"color": "green",
"index": 1,
"value": 20
},
{
"color": "orange",
"index": 2,
"value": 40
},
{
"color": "red",
"index": 3,
"value": 80
}
],
"values": false
},
"orientation": "horizontal"
},
"pluginVersion": "6.2.0-pre",
"targets": [
{
"alias": "Inside",
"refId": "H",
"scenarioId": "csv_metric_values",
"stringInput": "100,100,100"
},
{
"alias": "Outhouse",
"refId": "A",
"scenarioId": "random_walk"
},
{
"refId": "F",
"scenarioId": "random_walk"
},
{
"refId": "B",
"scenarioId": "random_walk"
},
{
"refId": "C",
"scenarioId": "random_walk"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Title to left of bar",
"type": "bargauge"
},
{
"gridPos": {
"h": 7,
"w": 7,
"x": 11,
"y": 0
},
"id": 13,
"links": [],
"options": {
"displayMode": "basic",
"fieldOptions": {
"calcs": ["mean"],
"defaults": {
"decimals": null,
"max": 100,
"min": 0,
"unit": "celsius"
},
"mappings": [],
"override": {},
"thresholds": [
{
"color": "blue",
"index": 0,
"value": null
},
{
"color": "green",
"index": 1,
"value": 20
},
{
"color": "orange",
"index": 2,
"value": 40
},
{
"color": "red",
"index": 3,
"value": 80
}
],
"values": false
},
"orientation": "horizontal"
},
"pluginVersion": "6.2.0-pre",
"targets": [
{
"alias": "Inside",
"refId": "H",
"scenarioId": "csv_metric_values",
"stringInput": "100,100,100"
},
{
"alias": "Outhouse",
"refId": "A",
"scenarioId": "random_walk"
},
{
"refId": "F",
"scenarioId": "random_walk"
},
{
"refId": "B",
"scenarioId": "random_walk"
},
{
"refId": "C",
"scenarioId": "random_walk"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Basic mode",
"type": "bargauge"
},
{
"gridPos": {
"h": 7,
"w": 6,
"x": 18,
"y": 0
},
"id": 14,
"links": [],
"options": {
"displayMode": "lcd",
"fieldOptions": {
"calcs": ["mean"],
"defaults": {
"decimals": null,
"max": 100,
"min": 0,
"unit": "celsius"
},
"mappings": [],
"override": {},
"thresholds": [
{
"color": "blue",
"index": 0,
"value": null
},
{
"color": "green",
"index": 1,
"value": 20
},
{
"color": "orange",
"index": 2,
"value": 40
},
{
"color": "red",
"index": 3,
"value": 80
}
],
"values": false
},
"orientation": "horizontal"
},
"pluginVersion": "6.2.0-pre",
"targets": [
{
"alias": "Inside",
"refId": "H",
"scenarioId": "csv_metric_values",
"stringInput": "100,100,100"
},
{
"alias": "Outhouse",
"refId": "A",
"scenarioId": "random_walk"
},
{
"refId": "F",
"scenarioId": "random_walk"
},
{
"refId": "B",
"scenarioId": "random_walk"
},
{
"refId": "C",
"scenarioId": "random_walk"
}
],
"timeFrom": null,
"timeShift": null,
"title": "LED",
"type": "bargauge"
},
{
"gridPos": {
"h": 9,
"w": 11,
"x": 0,
"y": 7
},
"id": 7,
"links": [],
"options": {
"displayMode": "lcd",
"fieldOptions": {
"calcs": ["mean"],
"defaults": {
"decimals": null,
"max": 100,
"min": 0,
"unit": "watt"
},
"mappings": [],
"override": {},
"thresholds": [
{
"color": "green",
"index": 0,
"value": null
},
{
"color": "orange",
"index": 1,
"value": 40
},
{
"color": "red",
"index": 2,
"value": 80
}
],
"values": false
},
"orientation": "vertical"
},
"pluginVersion": "6.2.0-pre",
"targets": [
{
"refId": "E",
"scenarioId": "csv_metric_values",
"stringInput": "10003,33333"
},
{
"refId": "F",
"scenarioId": "random_walk"
},
{
"refId": "G",
"scenarioId": "random_walk"
},
{
"refId": "H",
"scenarioId": "csv_metric_values",
"stringInput": "100,100,100"
},
{
"refId": "I",
"scenarioId": "random_walk"
},
{
"refId": "J",
"scenarioId": "random_walk"
},
{
"refId": "K",
"scenarioId": "random_walk"
},
{
"refId": "L",
"scenarioId": "random_walk"
},
{
"refId": "M",
"scenarioId": "random_walk"
},
{
"refId": "N",
"scenarioId": "random_walk"
},
{
"refId": "O",
"scenarioId": "random_walk"
},
{
"refId": "P",
"scenarioId": "random_walk"
},
{
"refId": "Q",
"scenarioId": "random_walk"
}
],
"timeFrom": null,
"timeShift": null,
"title": "LED Vertical",
"type": "bargauge"
},
{
"gridPos": {
"h": 9,
"w": 13,
"x": 11,
"y": 7
},
"id": 8,
"links": [],
"options": {
"displayMode": "basic",
"fieldOptions": {
"calcs": ["mean"],
"defaults": {
"decimals": null,
"max": 100,
"min": 0,
"unit": "watt"
},
"mappings": [],
"override": {},
"thresholds": [
{
"color": "green",
"index": 0,
"value": null
},
{
"color": "purple",
"index": 1,
"value": 50
},
{
"color": "blue",
"index": 2,
"value": 70
}
],
"values": false
},
"orientation": "vertical"
},
"pluginVersion": "6.2.0-pre",
"targets": [
{
"refId": "H",
"scenarioId": "csv_metric_values",
"stringInput": "100,100,100"
},
{
"refId": "A",
"scenarioId": "random_walk"
},
{
"refId": "B",
"scenarioId": "random_walk"
},
{
"refId": "C",
"scenarioId": "random_walk"
},
{
"refId": "D",
"scenarioId": "random_walk"
},
{
"refId": "I",
"scenarioId": "random_walk"
},
{
"refId": "J",
"scenarioId": "random_walk"
},
{
"refId": "K",
"scenarioId": "random_walk"
},
{
"refId": "L",
"scenarioId": "random_walk"
},
{
"refId": "M",
"scenarioId": "random_walk"
},
{
"refId": "N",
"scenarioId": "random_walk"
},
{
"refId": "O",
"scenarioId": "random_walk"
},
{
"refId": "P",
"scenarioId": "random_walk"
},
{
"refId": "Q",
"scenarioId": "random_walk"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Basic vertical ",
"type": "bargauge"
},
{
"gridPos": {
"h": 7,
"w": 11,
"x": 0,
"y": 16
},
"id": 16,
"links": [],
"options": {
"displayMode": "lcd",
"fieldOptions": {
"calcs": ["last"],
"defaults": {
"max": 100,
"min": 0
},
"mappings": [],
"override": {},
"thresholds": [
{
"color": "green",
"index": 0,
"value": null
},
{
"color": "blue",
"index": 1,
"value": 40
},
{
"color": "red",
"index": 2,
"value": 80
}
],
"values": false
},
"orientation": "horizontal"
},
"pluginVersion": "6.3.0-pre",
"targets": [
{
"refId": "A",
"scenarioId": "csv_metric_values",
"stringInput": "1,20,90,30,5,0,-100"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Negative value below min",
"type": "bargauge"
},
{
"gridPos": {
"h": 7,
"w": 3,
"x": 11,
"y": 16
},
"id": 17,
"links": [],
"options": {
"displayMode": "lcd",
"fieldOptions": {
"calcs": ["last"],
"defaults": {
"max": 100,
"min": 0
},
"mappings": [],
"override": {},
"thresholds": [
{
"color": "green",
"index": 0,
"value": null
},
{
"color": "blue",
"index": 1,
"value": 40
},
{
"color": "red",
"index": 2,
"value": 80
}
],
"values": false
},
"orientation": "vertical"
},
"pluginVersion": "6.3.0-pre",
"targets": [
{
"refId": "A",
"scenarioId": "csv_metric_values",
"stringInput": "1,20,90,30,5,0,-100"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Negative value below min",
"type": "bargauge"
},
{
"gridPos": {
"h": 7,
"w": 3,
"x": 14,
"y": 16
},
"id": 18,
"links": [],
"options": {
"displayMode": "lcd",
"fieldOptions": {
"calcs": ["last"],
"defaults": {
"max": 100,
"min": -10
},
"mappings": [],
"override": {},
"thresholds": [
{
"color": "green",
"index": 0,
"value": null
},
{
"color": "blue",
"index": 1,
"value": 40
},
{
"color": "red",
"index": 2,
"value": 80
}
],
"values": false
},
"orientation": "vertical"
},
"pluginVersion": "6.3.0-pre",
"targets": [
{
"refId": "A",
"scenarioId": "csv_metric_values",
"stringInput": "1,20,90,30,5,6"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Positive value above min",
"type": "bargauge"
},
{
"gridPos": {
"h": 7,
"w": 3,
"x": 17,
"y": 16
},
"id": 19,
"links": [],
"options": {
"displayMode": "lcd",
"fieldOptions": {
"calcs": ["last"],
"defaults": {
"max": 35,
"min": -20
},
"mappings": [],
"override": {},
"thresholds": [
{
"color": "blue",
"index": 0,
"value": null
},
{
"color": "green",
"index": 1,
"value": 5
},
{
"color": "#EAB839",
"index": 2,
"value": 25
},
{
"color": "red",
"index": 3,
"value": 30
}
],
"values": false
},
"orientation": "vertical"
},
"pluginVersion": "6.3.0-pre",
"targets": [
{
"refId": "A",
"scenarioId": "csv_metric_values",
"stringInput": "1,20,90,30,5,6"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Negative min ",
"type": "bargauge"
},
{
"gridPos": {
"h": 7,
"w": 4,
"x": 20,
"y": 16
},
"id": 20,
"links": [],
"options": {
"displayMode": "gradient",
"fieldOptions": {
"calcs": ["last"],
"defaults": {
"max": 35,
"min": -20
},
"mappings": [],
"override": {},
"thresholds": [
{
"color": "blue",
"index": 0,
"value": null
},
{
"color": "green",
"index": 1,
"value": 5
},
{
"color": "#EAB839",
"index": 2,
"value": 25
},
{
"color": "red",
"index": 3,
"value": 30
}
],
"values": false
},
"orientation": "vertical"
},
"pluginVersion": "6.3.0-pre",
"targets": [
{
"refId": "A",
"scenarioId": "csv_metric_values",
"stringInput": "30,30"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Negative min",
"type": "bargauge"
}
],
"schemaVersion": 18,
"style": "dark",
"tags": ["gdev", "panel-tests"],
"templating": {
"list": []
},
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {
"refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"],
"time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"]
},
"timezone": "",
"title": "Panel Tests - Bar Gauge",
"uid": "O6f11TZWk",
"version": 12
}

View File

@@ -1,400 +0,0 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": "-- Grafana --",
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"gnetId": null,
"graphTooltip": 0,
"links": [],
"panels": [
{
"gridPos": {
"h": 8,
"w": 22,
"x": 0,
"y": 0
},
"id": 7,
"links": [],
"options": {
"displayMode": "lcd",
"fieldOptions": {
"calcs": ["mean"],
"defaults": {
"decimals": null,
"max": 100,
"min": 0,
"unit": "watt"
},
"mappings": [],
"override": {},
"thresholds": [
{
"color": "green",
"index": 0,
"value": null
},
{
"color": "orange",
"index": 1,
"value": 40
},
{
"color": "red",
"index": 2,
"value": 80
}
],
"values": false
},
"orientation": "vertical"
},
"pluginVersion": "6.2.0-pre",
"targets": [
{
"refId": "A",
"scenarioId": "random_walk"
},
{
"refId": "B",
"scenarioId": "random_walk"
},
{
"refId": "C",
"scenarioId": "random_walk"
},
{
"refId": "D",
"scenarioId": "random_walk"
},
{
"refId": "E",
"scenarioId": "csv_metric_values",
"stringInput": "10003,33333"
},
{
"refId": "F",
"scenarioId": "random_walk"
},
{
"refId": "G",
"scenarioId": "random_walk"
},
{
"refId": "H",
"scenarioId": "csv_metric_values",
"stringInput": "100,100,100"
},
{
"refId": "I",
"scenarioId": "random_walk"
},
{
"refId": "J",
"scenarioId": "random_walk"
},
{
"refId": "K",
"scenarioId": "random_walk"
},
{
"refId": "L",
"scenarioId": "random_walk"
},
{
"refId": "M",
"scenarioId": "random_walk"
},
{
"refId": "N",
"scenarioId": "random_walk"
},
{
"refId": "O",
"scenarioId": "random_walk"
},
{
"refId": "P",
"scenarioId": "random_walk"
},
{
"refId": "Q",
"scenarioId": "random_walk"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Usage",
"type": "bargauge"
},
{
"gridPos": {
"h": 21,
"w": 2,
"x": 22,
"y": 0
},
"id": 11,
"links": [],
"options": {
"displayMode": "lcd",
"fieldOptions": {
"calcs": ["mean"],
"defaults": {
"decimals": null,
"max": 100,
"min": 0,
"unit": "percent"
},
"mappings": [],
"override": {},
"thresholds": [
{
"color": "green",
"index": 0,
"value": null
},
{
"color": "red",
"index": 1,
"value": 80
}
],
"values": false
},
"orientation": "vertical"
},
"pluginVersion": "6.2.0-pre",
"targets": [
{
"refId": "A",
"scenarioId": "random_walk"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Progress",
"type": "bargauge"
},
{
"gridPos": {
"h": 13,
"w": 10,
"x": 0,
"y": 8
},
"id": 6,
"links": [],
"options": {
"displayMode": "lcd",
"fieldOptions": {
"calcs": ["mean"],
"defaults": {
"decimals": null,
"max": 100,
"min": 0,
"unit": "celsius"
},
"mappings": [],
"override": {},
"thresholds": [
{
"color": "green",
"index": 0,
"value": null
},
{
"color": "orange",
"index": 1,
"value": 40
},
{
"color": "red",
"index": 2,
"value": 80
}
],
"values": false
},
"orientation": "horizontal"
},
"pluginVersion": "6.2.0-pre",
"targets": [
{
"alias": "Inside",
"refId": "H",
"scenarioId": "csv_metric_values",
"stringInput": "100,100,100"
},
{
"alias": "Outhouse",
"refId": "A",
"scenarioId": "random_walk"
},
{
"alias": "Area B",
"refId": "B",
"scenarioId": "random_walk"
},
{
"alias": "Basement",
"refId": "C",
"scenarioId": "random_walk"
},
{
"alias": "Garage",
"refId": "D",
"scenarioId": "random_walk"
},
{
"alias": "Attic",
"refId": "E",
"scenarioId": "random_walk"
},
{
"refId": "F",
"scenarioId": "random_walk"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Temperature",
"type": "bargauge"
},
{
"gridPos": {
"h": 13,
"w": 12,
"x": 10,
"y": 8
},
"id": 8,
"links": [],
"options": {
"displayMode": "lcd",
"fieldOptions": {
"calcs": ["mean"],
"defaults": {
"decimals": null,
"max": 100,
"min": 0,
"unit": "watt"
},
"mappings": [],
"override": {},
"thresholds": [
{
"color": "green",
"index": 0,
"value": null
},
{
"color": "orange",
"index": 1,
"value": 85
},
{
"color": "red",
"index": 2,
"value": 95
}
],
"values": false
},
"orientation": "horizontal"
},
"pluginVersion": "6.2.0-pre",
"targets": [
{
"refId": "H",
"scenarioId": "csv_metric_values",
"stringInput": "100,100,100"
},
{
"refId": "A",
"scenarioId": "random_walk"
},
{
"refId": "B",
"scenarioId": "random_walk"
},
{
"refId": "C",
"scenarioId": "random_walk"
},
{
"refId": "D",
"scenarioId": "random_walk"
},
{
"refId": "I",
"scenarioId": "random_walk"
},
{
"refId": "J",
"scenarioId": "random_walk"
},
{
"refId": "K",
"scenarioId": "random_walk"
},
{
"refId": "L",
"scenarioId": "random_walk"
},
{
"refId": "M",
"scenarioId": "random_walk"
},
{
"refId": "N",
"scenarioId": "random_walk"
},
{
"refId": "O",
"scenarioId": "random_walk"
},
{
"refId": "P",
"scenarioId": "random_walk"
},
{
"refId": "Q",
"scenarioId": "random_walk"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Usage",
"type": "bargauge"
}
],
"schemaVersion": 18,
"style": "dark",
"tags": ["gdev", "bargauge", "panel-demo"],
"templating": {
"list": []
},
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {
"refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"],
"time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"]
},
"timezone": "",
"title": "Bar Gauge LED Demo",
"uid": "0G3rbkqmkaa",
"version": 1
}

View File

@@ -51,6 +51,36 @@ then the Grafana proxy will transform it into "https://management.azure.com/foo/
The `method` parameter is optional. It can be set to any HTTP verb to provide more fine-grained control.
### Dynamic Routes
When using routes, you can also reference a variable stored in JsonData or SecureJsonData which will be interpolated when connecting to the datasource.
With JsonData:
```json
"routes": [
{
"path": "custom/api/v5/*",
"method": "*",
"url": "{{.JsonData.dynamicUrl}}",
...
},
]
```
With SecureJsonData:
```json
"routes": [{
"path": "custom/api/v5/*",
"method": "*",
"url": "{{.SecureJsonData.dynamicUrl}}",
...
}]
```
In the above example, the app is able to set the value for `dynamicUrl` in JsonData or SecureJsonData and it will be replaced on-demand.
An app using this feature can be found [here](https://github.com/grafana/kentik-app).
## Encrypting Sensitive Data
When a user saves a password or secret with your datasource plugin's Config page, then you can save data to a column in the datasource table called `secureJsonData` that is an encrypted blob. Any data saved in the blob is encrypted by Grafana and can only be decrypted by the Grafana server on the backend. This means once a password is saved, no sensitive data is sent to the browser. If the password is saved in the `jsonData` blob or the `password` field then it is unencrypted and anyone with Admin access (with the help of Chrome Developer Tools) can read it.

View File

@@ -5,7 +5,7 @@
"company": "Grafana Labs"
},
"name": "grafana",
"version": "6.2.0-pre",
"version": "6.2.0",
"repository": {
"type": "git",
"url": "http://github.com/grafana/grafana.git"

View File

@@ -1,6 +1,14 @@
import React from 'react';
import { shallow } from 'enzyme';
import { BarGauge, Props, getValueColor, getBasicAndGradientStyles, getBarGradient, getTitleStyles } from './BarGauge';
import {
BarGauge,
Props,
getValueColor,
getBasicAndGradientStyles,
getBarGradient,
getTitleStyles,
getValuePercent,
} from './BarGauge';
import { VizOrientation, DisplayValue } from '../../types';
import { getTheme } from '../../themes';
@@ -63,6 +71,24 @@ describe('BarGauge', () => {
});
});
describe('Get value percent', () => {
it('0 to 100 and value 40', () => {
expect(getValuePercent(40, 0, 100)).toEqual(0.4);
});
it('50 to 100 and value 75', () => {
expect(getValuePercent(75, 50, 100)).toEqual(0.5);
});
it('-30 to 30 and value 0', () => {
expect(getValuePercent(0, -30, 30)).toEqual(0.5);
});
it('-30 to 30 and value 30', () => {
expect(getValuePercent(30, -30, 30)).toEqual(1);
});
});
describe('Vertical bar without title', () => {
it('should not include title height in height', () => {
const props = getProps({

View File

@@ -161,7 +161,7 @@ export class BarGauge extends PureComponent<Props> {
const cells: JSX.Element[] = [];
for (let i = 0; i < cellCount; i++) {
const currentValue = (valueRange / cellCount) * i;
const currentValue = minValue + (valueRange / cellCount) * i;
const cellColor = this.getCellColor(currentValue);
const cellStyles: CSSProperties = {
borderRadius: '2px',
@@ -345,11 +345,6 @@ function calculateBarAndValueDimensions(props: Props): BarAndValueDimensions {
}
}
// console.log('titleDim', titleDim);
// console.log('valueWidth', valueWidth);
// console.log('width', width);
// console.log('total', titleDim.width + maxBarWidth + valueWidth);
return {
valueWidth,
valueHeight,
@@ -360,6 +355,10 @@ function calculateBarAndValueDimensions(props: Props): BarAndValueDimensions {
};
}
export function getValuePercent(value: number, minValue: number, maxValue: number): number {
return Math.min((value - minValue) / (maxValue - minValue), 1);
}
/**
* Only exported to for unit test
*/
@@ -367,7 +366,7 @@ export function getBasicAndGradientStyles(props: Props): BasicAndGradientStyles
const { displayMode, maxValue, minValue, value } = props;
const { valueWidth, valueHeight, maxBarHeight, maxBarWidth } = calculateBarAndValueDimensions(props);
const valuePercent = Math.min(value.numeric / (maxValue - minValue), 1);
const valuePercent = getValuePercent(value.numeric, minValue, maxValue);
const valueColor = getValueColor(props);
const valueStyles = getValueStyles(value.text, valueColor, valueWidth, valueHeight);
const isBasic = displayMode === 'basic';
@@ -450,7 +449,7 @@ export function getBarGradient(props: Props, maxSize: number): string {
for (let i = 0; i < thresholds.length; i++) {
const threshold = thresholds[i];
const color = getColorFromHexRgbOrName(threshold.color);
const valuePercent = Math.min(threshold.value / (maxValue - minValue), 1);
const valuePercent = getValuePercent(threshold.value, minValue, maxValue);
const pos = valuePercent * maxSize;
const offset = Math.round(pos - (pos - lastpos) / 2);
@@ -499,30 +498,3 @@ function getValueStyles(value: string, color: string, width: number, height: num
fontSize: fontSize.toFixed(2) + 'px',
};
}
// let canvasElement: HTMLCanvasElement | null = null;
//
// interface TextDimensions {
// width: number;
// height: number;
// }
//
// /**
// * Uses canvas.measureText to compute and return the width of the given text of given font in pixels.
// *
// * @param {String} text The text to be rendered.
// * @param {String} font The css font descriptor that text is to be rendered with (e.g. "bold 14px verdana").
// *
// * @see https://stackoverflow.com/questions/118241/calculate-text-width-with-javascript/21015393#21015393
// */
// function getTextWidth(text: string): number {
// // re-use canvas object for better performance
// canvasElement = canvasElement || document.createElement('canvas');
// const context = canvasElement.getContext('2d');
// if (context) {
// context.font = 'normal 16px Roboto';
// const metrics = context.measureText(text);
// return metrics.width;
// }
// return 16;
// }

View File

@@ -42,9 +42,10 @@ export class CustomScrollbar extends Component<Props> {
updateScroll() {
const ref = this.ref.current;
const { scrollTop } = this.props;
if (ref && !isNil(this.props.scrollTop)) {
ref.scrollTop(this.props.scrollTop);
if (ref && !isNil(scrollTop)) {
ref.scrollTop(scrollTop);
}
}
@@ -70,6 +71,44 @@ export class CustomScrollbar extends Component<Props> {
this.updateScroll();
}
renderTrack = (track: 'track-vertical' | 'track-horizontal', hideTrack: boolean | undefined, passedProps: any) => {
return (
<div
{...passedProps}
className={cx(
css`
visibility: ${hideTrack ? 'none' : 'visible'};
`,
track
)}
/>
);
};
renderThumb = (thumb: 'thumb-horizontal' | 'thumb-vertical', passedProps: any) => {
return <div {...passedProps} className={thumb} />;
};
renderTrackHorizontal = (passedProps: any) => {
return this.renderTrack('track-horizontal', this.props.hideHorizontalTrack, passedProps);
};
renderTrackVertical = (passedProps: any) => {
return this.renderTrack('track-vertical', this.props.hideVerticalTrack, passedProps);
};
renderThumbHorizontal = (passedProps: any) => {
return this.renderThumb('thumb-horizontal', passedProps);
};
renderThumbVertical = (passedProps: any) => {
return this.renderThumb('thumb-vertical', passedProps);
};
renderView = (passedProps: any) => {
return <div {...passedProps} className="view" />;
};
render() {
const {
className,
@@ -80,8 +119,6 @@ export class CustomScrollbar extends Component<Props> {
autoHide,
autoHideTimeout,
hideTracksWhenNotNeeded,
hideHorizontalTrack,
hideVerticalTrack,
} = this.props;
return (
@@ -97,31 +134,11 @@ export class CustomScrollbar extends Component<Props> {
// Before these where set to inhert but that caused problems with cut of legends in firefox
autoHeightMax={autoHeightMax}
autoHeightMin={autoHeightMin}
renderTrackHorizontal={props => (
<div
{...props}
className={cx(
css`
visibility: ${hideHorizontalTrack ? 'none' : 'visible'};
`,
'track-horizontal'
)}
/>
)}
renderTrackVertical={props => (
<div
{...props}
className={cx(
css`
visibility: ${hideVerticalTrack ? 'none' : 'visible'};
`,
'track-vertical'
)}
/>
)}
renderThumbHorizontal={props => <div {...props} className="thumb-horizontal" />}
renderThumbVertical={props => <div {...props} className="thumb-vertical" />}
renderView={props => <div {...props} className="view" />}
renderTrackHorizontal={this.renderTrackHorizontal}
renderTrackVertical={this.renderTrackVertical}
renderThumbHorizontal={this.renderThumbHorizontal}
renderThumbVertical={this.renderThumbVertical}
renderView={this.renderView}
>
{children}
</Scrollbars>

View File

@@ -37,7 +37,7 @@ exports[`CustomScrollbar renders correctly 1`] = `
</p>
</div>
<div
className="css-17l4171 track-horizontal"
className="css-52gpmd track-horizontal"
style={
Object {
"display": "none",
@@ -58,7 +58,7 @@ exports[`CustomScrollbar renders correctly 1`] = `
/>
</div>
<div
className="css-17l4171 track-vertical"
className="css-52gpmd track-vertical"
style={
Object {
"display": "none",

View File

@@ -69,14 +69,14 @@ export class Gauge extends PureComponent<Props> {
const backgroundColor = selectThemeVariant(
{
dark: theme.colors.dark3,
light: '#e6e6e6',
dark: theme.colors.dark8,
light: theme.colors.gray6,
},
theme.type
);
const gaugeWidthReduceRatio = showThresholdLabels ? 1.5 : 1;
const gaugeWidth = Math.min(dimension / 6, 40) / gaugeWidthReduceRatio;
const gaugeWidth = Math.min(dimension / 5.5, 40) / gaugeWidthReduceRatio;
const thresholdMarkersWidth = gaugeWidth / 5;
const fontSize = Math.min(dimension / 5.5, 100) * (value.text !== null ? this.getFontScale(value.text.length) : 1);
const thresholdLabelFontSize = fontSize / 2.5;
@@ -181,7 +181,7 @@ function calculateGaugeAutoProps(width: number, height: number, title: string |
const titleFontSize = Math.min((width * 0.15) / 1.5, 20); // 20% of height * line-height, max 40px
const titleHeight = titleFontSize * 1.5;
const availableHeight = showLabel ? height - titleHeight : height;
const gaugeHeight = Math.min(availableHeight * 0.7, width);
const gaugeHeight = Math.min(availableHeight, width);
return {
showLabel,

View File

@@ -4,7 +4,7 @@ import React, { FunctionComponent } from 'react';
interface Props {
title?: string;
onClose?: () => void;
children: JSX.Element | JSX.Element[] | boolean;
children: React.ReactNode;
onAdd?: () => void;
}

View File

@@ -1,116 +1,129 @@
// Libraries
import React, { PureComponent, ChangeEvent } from 'react';
import React, { ChangeEvent, useState, useCallback } from 'react';
// Components
import { PanelOptionsGroup } from '../PanelOptionsGroup/PanelOptionsGroup';
import { FormField } from '../FormField/FormField';
import { FormLabel } from '../FormLabel/FormLabel';
import { UnitPicker } from '../UnitPicker/UnitPicker';
// Types
import { Field } from '../../types/data';
import { toNumberString, toIntegerOrUndefined } from '../../utils';
import { toIntegerOrUndefined } from '../../utils';
import { SelectOptionItem } from '../Select/Select';
import { VAR_SERIES_NAME, VAR_FIELD_NAME, VAR_CALC, VAR_CELL_PREFIX } from '../../utils/fieldDisplay';
import { PanelOptionsGroup } from '../index';
const labelWidth = 6;
export interface Props {
title: string;
options: Partial<Field>;
value: Partial<Field>;
onChange: (fieldProperties: Partial<Field>) => void;
showMinMax: boolean;
}
export class FieldPropertiesEditor extends PureComponent<Props> {
onTitleChange = (event: ChangeEvent<HTMLInputElement>) =>
this.props.onChange({ ...this.props.options, title: event.target.value });
export const FieldPropertiesEditor: React.FC<Props> = ({ value, onChange, showMinMax }) => {
const { unit, title } = value;
// @ts-ignore
onUnitChange = (unit: SelectOptionItem<string>) => this.props.onChange({ ...this.props.value, unit: unit.value });
const [decimals, setDecimals] = useState(
value.decimals !== undefined && value.decimals !== null ? value.decimals.toString() : ''
);
const [min, setMin] = useState(value.min !== undefined && value.min !== null ? value.min.toString() : '');
const [max, setMax] = useState(value.max !== undefined && value.max !== null ? value.max.toString() : '');
onDecimalChange = (event: ChangeEvent<HTMLInputElement>) => {
this.props.onChange({
...this.props.options,
decimals: toIntegerOrUndefined(event.target.value),
});
const onTitleChange = (event: ChangeEvent<HTMLInputElement>) => {
onChange({ ...value, title: event.target.value });
};
onMinChange = (event: ChangeEvent<HTMLInputElement>) => {
this.props.onChange({
...this.props.options,
min: toIntegerOrUndefined(event.target.value),
});
const onDecimalChange = useCallback(
(event: ChangeEvent<HTMLInputElement>) => {
setDecimals(event.target.value);
},
[value.decimals, onChange]
);
const onMinChange = useCallback(
(event: ChangeEvent<HTMLInputElement>) => {
setMin(event.target.value);
},
[value.min, onChange]
);
const onMaxChange = useCallback(
(event: ChangeEvent<HTMLInputElement>) => {
setMax(event.target.value);
},
[value.max, onChange]
);
const onUnitChange = (unit: SelectOptionItem<string>) => {
onChange({ ...value, unit: unit.value });
};
onMaxChange = (event: ChangeEvent<HTMLInputElement>) => {
this.props.onChange({
...this.props.options,
max: toIntegerOrUndefined(event.target.value),
const commitChanges = useCallback(() => {
onChange({
...value,
decimals: toIntegerOrUndefined(decimals),
min: toIntegerOrUndefined(min),
max: toIntegerOrUndefined(max),
});
};
}, [min, max, decimals]);
render() {
const { showMinMax, title } = this.props;
const { unit, decimals, min, max } = this.props.options;
const titleTooltip = (
<div>
Template Variables:
<br />
{'$' + VAR_SERIES_NAME}
<br />
{'$' + VAR_FIELD_NAME}
<br />
{'$' + VAR_CELL_PREFIX + '{N}'} / {'$' + VAR_CALC}
const titleTooltip = (
<div>
Template Variables:
<br />
{'$' + VAR_SERIES_NAME}
<br />
{'$' + VAR_FIELD_NAME}
<br />
{'$' + VAR_CELL_PREFIX + '{N}'} / {'$' + VAR_CALC}
</div>
);
return (
<PanelOptionsGroup title="Field">
<FormField
label="Title"
labelWidth={labelWidth}
onChange={onTitleChange}
value={title}
tooltip={titleTooltip}
placeholder="Auto"
/>
<div className="gf-form">
<FormLabel width={labelWidth}>Unit</FormLabel>
<UnitPicker defaultValue={unit} onChange={onUnitChange} />
</div>
);
return (
<PanelOptionsGroup title={title}>
{showMinMax && (
<>
<FormField
label="Title"
label="Min"
labelWidth={labelWidth}
onChange={this.onTitleChange}
value={this.props.options.title}
tooltip={titleTooltip}
placeholder="Auto"
onChange={onMinChange}
onBlur={commitChanges}
value={min}
type="number"
/>
<div className="gf-form">
<FormLabel width={labelWidth}>Unit</FormLabel>
<UnitPicker defaultValue={unit} onChange={this.onUnitChange} />
</div>
{showMinMax && (
<>
<FormField
label="Min"
labelWidth={labelWidth}
onChange={this.onMinChange}
value={toNumberString(min)}
type="number"
/>
<FormField
label="Max"
labelWidth={labelWidth}
onChange={this.onMaxChange}
value={toNumberString(max)}
type="number"
/>
</>
)}
<FormField
label="Decimals"
label="Max"
labelWidth={labelWidth}
placeholder="auto"
onChange={this.onDecimalChange}
value={toNumberString(decimals)}
onChange={onMaxChange}
onBlur={commitChanges}
value={max}
type="number"
/>
</>
</PanelOptionsGroup>
);
}
}
)}
<FormField
label="Decimals"
labelWidth={labelWidth}
placeholder="auto"
onChange={onDecimalChange}
onBlur={commitChanges}
value={decimals}
type="number"
/>
</PanelOptionsGroup>
);
};

View File

@@ -80,6 +80,13 @@ export interface DataSourcePluginMeta extends PluginMeta {
mixed?: boolean;
hasQueryHelp?: boolean;
queryOptions?: PluginMetaQueryOptions;
sort?: number;
/**
* By default, hidden queries are not passed to the datasource
* Set this to true in plugin.json to have hidden queries passed to the
* DataSource query method
*/
hiddenQueries?: boolean;
}
interface PluginMetaQueryOptions {

View File

@@ -32,6 +32,7 @@ export interface PanelData {
}
export interface PanelProps<T = any> {
id: number; // ID within the current dashboard
data: PanelData;
// TODO: annotation?: PanelData;

View File

@@ -47,8 +47,8 @@ RUN mkdir -p "$GF_PATHS_HOME/.aws" && \
"$GF_PATHS_DATA" && \
cp "$GF_PATHS_HOME/conf/sample.ini" "$GF_PATHS_CONFIG" && \
cp "$GF_PATHS_HOME/conf/ldap.toml" /etc/grafana/ldap.toml && \
chown -R grafana:grafana "$GF_PATHS_DATA" "$GF_PATHS_HOME/.aws" "$GF_PATHS_LOGS" "$GF_PATHS_PLUGINS" && \
chmod 777 "$GF_PATHS_DATA" "$GF_PATHS_HOME/.aws" "$GF_PATHS_LOGS" "$GF_PATHS_PLUGINS"
chown -R grafana:grafana "$GF_PATHS_DATA" "$GF_PATHS_HOME/.aws" "$GF_PATHS_LOGS" "$GF_PATHS_PLUGINS" "$GF_PATHS_PROVISIONING" && \
chmod -R 777 "$GF_PATHS_DATA" "$GF_PATHS_HOME/.aws" "$GF_PATHS_LOGS" "$GF_PATHS_PLUGINS" "$GF_PATHS_PROVISIONING"
EXPOSE 3000

View File

@@ -233,7 +233,7 @@ func (hs *HTTPServer) setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, er
if len(appLink.Children) > 0 && c.OrgRole == m.ROLE_ADMIN {
appLink.Children = append(appLink.Children, &dtos.NavLink{Divider: true})
appLink.Children = append(appLink.Children, &dtos.NavLink{Text: "Plugin Config", Icon: "gicon gicon-cog", Url: setting.AppSubUrl + "/plugins/" + plugin.Id + "/edit"})
appLink.Children = append(appLink.Children, &dtos.NavLink{Text: "Plugin Config", Icon: "gicon gicon-cog", Url: setting.AppSubUrl + "/plugins/" + plugin.Id + "/"})
}
if len(appLink.Children) > 0 {

View File

@@ -67,14 +67,14 @@ func (provider *accessTokenProvider) getAccessToken(data templateData) (string,
}
}
urlInterpolated, err := interpolateString(provider.route.TokenAuth.Url, data)
urlInterpolated, err := InterpolateString(provider.route.TokenAuth.Url, data)
if err != nil {
return "", err
}
params := make(url.Values)
for key, value := range provider.route.TokenAuth.Params {
interpolatedParam, err := interpolateString(value, data)
interpolatedParam, err := InterpolateString(value, data)
if err != nil {
return "", err
}
@@ -119,7 +119,7 @@ func (provider *accessTokenProvider) getJwtAccessToken(ctx context.Context, data
conf := &jwt.Config{}
if val, ok := provider.route.JwtTokenAuth.Params["client_email"]; ok {
interpolatedVal, err := interpolateString(val, data)
interpolatedVal, err := InterpolateString(val, data)
if err != nil {
return "", err
}
@@ -127,7 +127,7 @@ func (provider *accessTokenProvider) getJwtAccessToken(ctx context.Context, data
}
if val, ok := provider.route.JwtTokenAuth.Params["private_key"]; ok {
interpolatedVal, err := interpolateString(val, data)
interpolatedVal, err := InterpolateString(val, data)
if err != nil {
return "", err
}
@@ -135,7 +135,7 @@ func (provider *accessTokenProvider) getJwtAccessToken(ctx context.Context, data
}
if val, ok := provider.route.JwtTokenAuth.Params["token_uri"]; ok {
interpolatedVal, err := interpolateString(val, data)
interpolatedVal, err := InterpolateString(val, data)
if err != nil {
return "", err
}

View File

@@ -1,13 +1,11 @@
package pluginproxy
import (
"bytes"
"context"
"fmt"
"net/http"
"net/url"
"strings"
"text/template"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
@@ -24,7 +22,7 @@ func ApplyRoute(ctx context.Context, req *http.Request, proxyPath string, route
SecureJsonData: ds.SecureJsonData.Decrypt(),
}
interpolatedURL, err := interpolateString(route.Url, data)
interpolatedURL, err := InterpolateString(route.Url, data)
if err != nil {
logger.Error("Error interpolating proxy url", "error", err)
return
@@ -81,24 +79,9 @@ func ApplyRoute(ctx context.Context, req *http.Request, proxyPath string, route
logger.Info("Requesting", "url", req.URL.String())
}
func interpolateString(text string, data templateData) (string, error) {
t, err := template.New("content").Parse(text)
if err != nil {
return "", fmt.Errorf("could not parse template %s", text)
}
var contentBuf bytes.Buffer
err = t.Execute(&contentBuf, data)
if err != nil {
return "", fmt.Errorf("failed to execute template %s", text)
}
return contentBuf.String(), nil
}
func addHeaders(reqHeaders *http.Header, route *plugins.AppPluginRoute, data templateData) error {
for _, header := range route.Headers {
interpolated, err := interpolateString(header.Content, data)
interpolated, err := InterpolateString(header.Content, data)
if err != nil {
return err
}

View File

@@ -14,7 +14,7 @@ func TestDsAuthProvider(t *testing.T) {
},
}
interpolated, err := interpolateString("{{.SecureJsonData.Test}}", data)
interpolated, err := InterpolateString("{{.SecureJsonData.Test}}", data)
So(err, ShouldBeNil)
So(interpolated, ShouldEqual, "0asd+asd")
})

View File

@@ -2,12 +2,13 @@ package pluginproxy
import (
"encoding/json"
"github.com/grafana/grafana/pkg/setting"
"net"
"net/http"
"net/http/httputil"
"net/url"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/log"
m "github.com/grafana/grafana/pkg/models"
@@ -38,6 +39,24 @@ func getHeaders(route *plugins.AppPluginRoute, orgId int64, appID string) (http.
return result, err
}
func updateURL(route *plugins.AppPluginRoute, orgId int64, appID string) (string, error) {
query := m.GetPluginSettingByIdQuery{OrgId: orgId, PluginId: appID}
if err := bus.Dispatch(&query); err != nil {
return "", err
}
data := templateData{
JsonData: query.Result.JsonData,
SecureJsonData: query.Result.SecureJsonData.Decrypt(),
}
interpolated, err := InterpolateString(route.Url, data)
if err != nil {
return "", err
}
return interpolated, err
}
// NewApiPluginProxy create a plugin proxy
func NewApiPluginProxy(ctx *m.ReqContext, proxyPath string, route *plugins.AppPluginRoute, appID string, cfg *setting.Cfg) *httputil.ReverseProxy {
targetURL, _ := url.Parse(route.Url)
@@ -48,7 +67,6 @@ func NewApiPluginProxy(ctx *m.ReqContext, proxyPath string, route *plugins.AppPl
req.Host = targetURL.Host
req.URL.Path = util.JoinURLFragments(targetURL.Path, proxyPath)
// clear cookie headers
req.Header.Del("Cookie")
req.Header.Del("Set-Cookie")
@@ -72,13 +90,13 @@ func NewApiPluginProxy(ctx *m.ReqContext, proxyPath string, route *plugins.AppPl
}
// Create a HTTP header with the context in it.
ctxJson, err := json.Marshal(ctx.SignedInUser)
ctxJSON, err := json.Marshal(ctx.SignedInUser)
if err != nil {
ctx.JsonApiErr(500, "failed to marshal context to json.", err)
return
}
req.Header.Add("X-Grafana-Context", string(ctxJson))
req.Header.Add("X-Grafana-Context", string(ctxJSON))
if cfg.SendUserHeader && !ctx.SignedInUser.IsAnonymous {
req.Header.Add("X-Grafana-User", ctx.SignedInUser.Login)
@@ -97,6 +115,27 @@ func NewApiPluginProxy(ctx *m.ReqContext, proxyPath string, route *plugins.AppPl
}
}
if len(route.Url) > 0 {
interpolatedURL, err := updateURL(route, ctx.OrgId, appID)
if err != nil {
ctx.JsonApiErr(500, "Could not interpolate plugin route url", err)
}
targetURL, err := url.Parse(interpolatedURL)
if err != nil {
ctx.JsonApiErr(500, "Could not parse custom url: %v", err)
return
}
req.URL.Scheme = targetURL.Scheme
req.URL.Host = targetURL.Host
req.Host = targetURL.Host
req.URL.Path = util.JoinURLFragments(targetURL.Path, proxyPath)
if err != nil {
ctx.JsonApiErr(500, "Could not interpolate plugin route url", err)
return
}
}
// reqBytes, _ := httputil.DumpRequestOut(req, true);
// log.Trace("Proxying plugin request: %s", string(reqBytes))
}

View File

@@ -53,6 +53,7 @@ func TestPluginProxy(t *testing.T) {
},
},
&setting.Cfg{SendUserHeader: true},
nil,
)
Convey("Should add header with username", func() {
@@ -69,6 +70,7 @@ func TestPluginProxy(t *testing.T) {
},
},
&setting.Cfg{SendUserHeader: false},
nil,
)
Convey("Should not add header with username", func() {
// Get will return empty string even if header is not set
@@ -82,6 +84,7 @@ func TestPluginProxy(t *testing.T) {
SignedInUser: &m.SignedInUser{IsAnonymous: true},
},
&setting.Cfg{SendUserHeader: true},
nil,
)
Convey("Should not add header with username", func() {
@@ -89,14 +92,59 @@ func TestPluginProxy(t *testing.T) {
So(req.Header.Get("X-Grafana-User"), ShouldEqual, "")
})
})
Convey("When getting templated url", t, func() {
route := &plugins.AppPluginRoute{
Url: "{{.JsonData.dynamicUrl}}",
Method: "GET",
}
bus.AddHandler("test", func(query *m.GetPluginSettingByIdQuery) error {
query.Result = &m.PluginSetting{
JsonData: map[string]interface{}{
"dynamicUrl": "https://dynamic.grafana.com",
},
}
return nil
})
req := getPluginProxiedRequest(
&m.ReqContext{
SignedInUser: &m.SignedInUser{
Login: "test_user",
},
},
&setting.Cfg{SendUserHeader: true},
route,
)
Convey("Headers should be updated", func() {
header, err := getHeaders(route, 1, "my-app")
So(err, ShouldBeNil)
So(header.Get("X-Grafana-User"), ShouldEqual, "")
})
Convey("Should set req.URL to be interpolated value from jsonData", func() {
So(req.URL.String(), ShouldEqual, "https://dynamic.grafana.com")
})
Convey("Route url should not be modified", func() {
So(route.Url, ShouldEqual, "{{.JsonData.dynamicUrl}}")
})
})
}
// getPluginProxiedRequest is a helper for easier setup of tests based on global config and ReqContext.
func getPluginProxiedRequest(ctx *m.ReqContext, cfg *setting.Cfg) *http.Request {
route := &plugins.AppPluginRoute{}
func getPluginProxiedRequest(ctx *m.ReqContext, cfg *setting.Cfg, route *plugins.AppPluginRoute) *http.Request {
// insert dummy route if none is specified
if route == nil {
route = &plugins.AppPluginRoute{
Path: "api/v4/",
Url: "https://www.google.com",
ReqRole: m.ROLE_EDITOR,
}
}
proxy := NewApiPluginProxy(ctx, "", route, "", cfg)
req, err := http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil)
req, err := http.NewRequest(http.MethodGet, route.Url, nil)
So(err, ShouldBeNil)
proxy.Director(req)
return req

View File

@@ -0,0 +1,49 @@
package pluginproxy
import (
"bytes"
"fmt"
"net/url"
"text/template"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
)
// InterpolateString accepts template data and return a string with substitutions
func InterpolateString(text string, data templateData) (string, error) {
t, err := template.New("content").Parse(text)
if err != nil {
return "", fmt.Errorf("could not parse template %s", text)
}
var contentBuf bytes.Buffer
err = t.Execute(&contentBuf, data)
if err != nil {
return "", fmt.Errorf("failed to execute template %s", text)
}
return contentBuf.String(), nil
}
// InterpolateURL accepts template data and return a string with substitutions
func InterpolateURL(anURL *url.URL, route *plugins.AppPluginRoute, orgID int64, appID string) (*url.URL, error) {
query := m.GetPluginSettingByIdQuery{OrgId: orgID, PluginId: appID}
result, err := url.Parse(anURL.String())
if query.Result != nil {
if len(query.Result.JsonData) > 0 {
data := templateData{
JsonData: query.Result.JsonData,
}
interpolatedResult, err := InterpolateString(anURL.String(), data)
if err == nil {
result, err = url.Parse(interpolatedResult)
if err != nil {
return nil, fmt.Errorf("Error parsing plugin route url %v", err)
}
}
}
}
return result, err
}

View File

@@ -0,0 +1,21 @@
package pluginproxy
import (
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestInterpolateString(t *testing.T) {
Convey("When interpolating string", t, func() {
data := templateData{
SecureJsonData: map[string]string{
"Test": "0asd+asd",
},
}
interpolated, err := InterpolateString("{{.SecureJsonData.Test}}", data)
So(err, ShouldBeNil)
So(interpolated, ShouldEqual, "0asd+asd")
})
}

View File

@@ -39,10 +39,14 @@ func (dc *databaseCache) Run(ctx context.Context) error {
}
func (dc *databaseCache) internalRunGC() {
now := getTime().Unix()
sql := `DELETE FROM cache_data WHERE (? - created_at) >= expires AND expires <> 0`
err := dc.SQLStore.WithDbSession(context.Background(), func(session *sqlstore.DBSession) error {
now := getTime().Unix()
sql := `DELETE FROM cache_data WHERE (? - created_at) >= expires AND expires <> 0`
_, err := session.Exec(sql, now)
return err
})
_, err := dc.SQLStore.NewSession().Exec(sql, now)
if err != nil {
dc.log.Error("failed to run garbage collect", "error", err)
}
@@ -80,44 +84,48 @@ func (dc *databaseCache) Get(key string) (interface{}, error) {
}
func (dc *databaseCache) Set(key string, value interface{}, expire time.Duration) error {
item := &cachedItem{Val: value}
data, err := encodeGob(item)
if err != nil {
return dc.SQLStore.WithTransactionalDbSession(context.Background(), func(session *sqlstore.DBSession) error {
item := &cachedItem{Val: value}
data, err := encodeGob(item)
if err != nil {
return err
}
var cacheHit CacheData
has, err := session.Where("cache_key = ?", key).Get(&cacheHit)
if err != nil {
return err
}
var expiresInSeconds int64
if expire != 0 {
expiresInSeconds = int64(expire) / int64(time.Second)
}
// insert or update depending on if item already exist
if has {
sql := `UPDATE cache_data SET data=?, created_at=?, expires=? WHERE cache_key=?`
_, err = session.Exec(sql, data, getTime().Unix(), expiresInSeconds, key)
} else {
sql := `INSERT INTO cache_data (cache_key,data,created_at,expires) VALUES(?,?,?,?)`
_, err = session.Exec(sql, key, data, getTime().Unix(), expiresInSeconds)
}
return err
}
session := dc.SQLStore.NewSession()
var cacheHit CacheData
has, err := session.Where("cache_key = ?", key).Get(&cacheHit)
if err != nil {
return err
}
var expiresInSeconds int64
if expire != 0 {
expiresInSeconds = int64(expire) / int64(time.Second)
}
// insert or update depending on if item already exist
if has {
sql := `UPDATE cache_data SET data=?, created_at=?, expires=? WHERE cache_key=?`
_, err = session.Exec(sql, data, getTime().Unix(), expiresInSeconds, key)
} else {
sql := `INSERT INTO cache_data (cache_key,data,created_at,expires) VALUES(?,?,?,?)`
_, err = session.Exec(sql, key, data, getTime().Unix(), expiresInSeconds)
}
return err
})
}
func (dc *databaseCache) Delete(key string) error {
sql := "DELETE FROM cache_data WHERE cache_key=?"
_, err := dc.SQLStore.NewSession().Exec(sql, key)
return dc.SQLStore.WithDbSession(context.Background(), func(session *sqlstore.DBSession) error {
sql := "DELETE FROM cache_data WHERE cache_key=?"
_, err := session.Exec(sql, key)
return err
})
return err
}
// CacheData is the struct representing the table in the database
type CacheData struct {
CacheKey string
Data []byte

View File

@@ -18,16 +18,17 @@ import (
// DataSourcePlugin contains all metadata about a datasource plugin
type DataSourcePlugin struct {
FrontendPluginBase
Annotations bool `json:"annotations"`
Metrics bool `json:"metrics"`
Alerting bool `json:"alerting"`
Explore bool `json:"explore"`
Table bool `json:"tables"`
Logs bool `json:"logs"`
QueryOptions map[string]bool `json:"queryOptions,omitempty"`
BuiltIn bool `json:"builtIn,omitempty"`
Mixed bool `json:"mixed,omitempty"`
Routes []*AppPluginRoute `json:"routes"`
Annotations bool `json:"annotations"`
Metrics bool `json:"metrics"`
Alerting bool `json:"alerting"`
Explore bool `json:"explore"`
Table bool `json:"tables"`
HiddenQueries bool `json:"hiddenQueries"`
Logs bool `json:"logs"`
QueryOptions map[string]bool `json:"queryOptions,omitempty"`
BuiltIn bool `json:"builtIn,omitempty"`
Mixed bool `json:"mixed,omitempty"`
Routes []*AppPluginRoute `json:"routes"`
Backend bool `json:"backend,omitempty"`
Executable string `json:"executable,omitempty"`

View File

@@ -85,14 +85,17 @@ func (e *AzureMonitorDatasource) buildQueries(queries []*tsdb.Query, timeRange *
azlog.Debug("AzureMonitor", "target", azureMonitorTarget)
urlComponents := map[string]string{}
urlComponents["subscription"] = fmt.Sprintf("%v", query.Model.Get("subscription").MustString())
urlComponents["resourceGroup"] = fmt.Sprintf("%v", azureMonitorTarget["resourceGroup"])
urlComponents["metricDefinition"] = fmt.Sprintf("%v", azureMonitorTarget["metricDefinition"])
urlComponents["resourceName"] = fmt.Sprintf("%v", azureMonitorTarget["resourceName"])
ub := urlBuilder{
ResourceGroup: urlComponents["resourceGroup"],
MetricDefinition: urlComponents["metricDefinition"],
ResourceName: urlComponents["resourceName"],
DefaultSubscription: query.DataSource.JsonData.Get("subscriptionId").MustString(),
Subscription: urlComponents["subscription"],
ResourceGroup: urlComponents["resourceGroup"],
MetricDefinition: urlComponents["metricDefinition"],
ResourceName: urlComponents["resourceName"],
}
azureURL := ub.Build()
@@ -199,8 +202,7 @@ func (e *AzureMonitorDatasource) createRequest(ctx context.Context, dsInfo *mode
}
cloudName := dsInfo.JsonData.Get("cloudName").MustString("azuremonitor")
subscriptionID := dsInfo.JsonData.Get("subscriptionId").MustString()
proxyPass := fmt.Sprintf("%s/subscriptions/%s", cloudName, subscriptionID)
proxyPass := fmt.Sprintf("%s/subscriptions", cloudName)
u, _ := url.Parse(dsInfo.Url)
u.Path = path.Join(u.Path, "render")

View File

@@ -9,6 +9,7 @@ import (
"time"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/tsdb"
. "github.com/smartystreets/goconvey/convey"
@@ -27,7 +28,13 @@ func TestAzureMonitorDatasource(t *testing.T) {
},
Queries: []*tsdb.Query{
{
DataSource: &models.DataSource{
JsonData: simplejson.NewFromAny(map[string]interface{}{
"subscriptionId": "default-subscription",
}),
},
Model: simplejson.NewFromAny(map[string]interface{}{
"subscription": "12345678-aaaa-bbbb-cccc-123456789abc",
"azureMonitor": map[string]interface{}{
"timeGrain": "PT1M",
"aggregation": "Average",
@@ -49,7 +56,7 @@ func TestAzureMonitorDatasource(t *testing.T) {
So(len(queries), ShouldEqual, 1)
So(queries[0].RefID, ShouldEqual, "A")
So(queries[0].URL, ShouldEqual, "resourceGroups/grafanastaging/providers/Microsoft.Compute/virtualMachines/grafana/providers/microsoft.insights/metrics")
So(queries[0].URL, ShouldEqual, "12345678-aaaa-bbbb-cccc-123456789abc/resourceGroups/grafanastaging/providers/Microsoft.Compute/virtualMachines/grafana/providers/microsoft.insights/metrics")
So(queries[0].Target, ShouldEqual, "aggregation=Average&api-version=2018-01-01&interval=PT1M&metricnames=Percentage+CPU&timespan=2018-03-15T13%3A00%3A00Z%2F2018-03-15T13%3A34%3A00Z")
So(len(queries[0].Params), ShouldEqual, 5)
So(queries[0].Params["timespan"][0], ShouldEqual, "2018-03-15T13:00:00Z/2018-03-15T13:34:00Z")

View File

@@ -7,22 +7,30 @@ import (
// urlBuilder builds the URL for calling the Azure Monitor API
type urlBuilder struct {
ResourceGroup string
MetricDefinition string
ResourceName string
DefaultSubscription string
Subscription string
ResourceGroup string
MetricDefinition string
ResourceName string
}
// Build checks the metric definition property to see which form of the url
// should be returned
func (ub *urlBuilder) Build() string {
subscription := ub.Subscription
if ub.Subscription == "" {
subscription = ub.DefaultSubscription
}
if strings.Count(ub.MetricDefinition, "/") > 1 {
rn := strings.Split(ub.ResourceName, "/")
lastIndex := strings.LastIndex(ub.MetricDefinition, "/")
service := ub.MetricDefinition[lastIndex+1:]
md := ub.MetricDefinition[0:lastIndex]
return fmt.Sprintf("resourceGroups/%s/providers/%s/%s/%s/%s/providers/microsoft.insights/metrics", ub.ResourceGroup, md, rn[0], service, rn[1])
return fmt.Sprintf("%s/resourceGroups/%s/providers/%s/%s/%s/%s/providers/microsoft.insights/metrics", subscription, ub.ResourceGroup, md, rn[0], service, rn[1])
}
return fmt.Sprintf("resourceGroups/%s/providers/%s/%s/providers/microsoft.insights/metrics", ub.ResourceGroup, ub.MetricDefinition, ub.ResourceName)
return fmt.Sprintf("%s/resourceGroups/%s/providers/%s/%s/providers/microsoft.insights/metrics", subscription, ub.ResourceGroup, ub.MetricDefinition, ub.ResourceName)
}

View File

@@ -11,35 +11,51 @@ func TestURLBuilder(t *testing.T) {
Convey("when metric definition is in the short form", func() {
ub := &urlBuilder{
ResourceGroup: "rg",
MetricDefinition: "Microsoft.Compute/virtualMachines",
ResourceName: "rn",
DefaultSubscription: "default-sub",
ResourceGroup: "rg",
MetricDefinition: "Microsoft.Compute/virtualMachines",
ResourceName: "rn",
}
url := ub.Build()
So(url, ShouldEqual, "resourceGroups/rg/providers/Microsoft.Compute/virtualMachines/rn/providers/microsoft.insights/metrics")
So(url, ShouldEqual, "default-sub/resourceGroups/rg/providers/Microsoft.Compute/virtualMachines/rn/providers/microsoft.insights/metrics")
})
Convey("when metric definition is in the short form and a subscription is defined", func() {
ub := &urlBuilder{
DefaultSubscription: "default-sub",
Subscription: "specified-sub",
ResourceGroup: "rg",
MetricDefinition: "Microsoft.Compute/virtualMachines",
ResourceName: "rn",
}
url := ub.Build()
So(url, ShouldEqual, "specified-sub/resourceGroups/rg/providers/Microsoft.Compute/virtualMachines/rn/providers/microsoft.insights/metrics")
})
Convey("when metric definition is Microsoft.Storage/storageAccounts/blobServices", func() {
ub := &urlBuilder{
ResourceGroup: "rg",
MetricDefinition: "Microsoft.Storage/storageAccounts/blobServices",
ResourceName: "rn1/default",
DefaultSubscription: "default-sub",
ResourceGroup: "rg",
MetricDefinition: "Microsoft.Storage/storageAccounts/blobServices",
ResourceName: "rn1/default",
}
url := ub.Build()
So(url, ShouldEqual, "resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/rn1/blobServices/default/providers/microsoft.insights/metrics")
So(url, ShouldEqual, "default-sub/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/rn1/blobServices/default/providers/microsoft.insights/metrics")
})
Convey("when metric definition is Microsoft.Storage/storageAccounts/fileServices", func() {
ub := &urlBuilder{
ResourceGroup: "rg",
MetricDefinition: "Microsoft.Storage/storageAccounts/fileServices",
ResourceName: "rn1/default",
DefaultSubscription: "default-sub",
ResourceGroup: "rg",
MetricDefinition: "Microsoft.Storage/storageAccounts/fileServices",
ResourceName: "rn1/default",
}
url := ub.Build()
So(url, ShouldEqual, "resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/rn1/fileServices/default/providers/microsoft.insights/metrics")
So(url, ShouldEqual, "default-sub/resourceGroups/rg/providers/Microsoft.Storage/storageAccounts/rn1/fileServices/default/providers/microsoft.insights/metrics")
})
})
}

View File

@@ -13,8 +13,6 @@ export class HelpCtrl {
{ keys: ['g', 'h'], description: 'Go to Home Dashboard' },
{ keys: ['g', 'p'], description: 'Go to Profile' },
{ keys: ['s', 'o'], description: 'Open search' },
{ keys: ['s', 's'], description: 'Open search with starred filter' },
{ keys: ['s', 't'], description: 'Open search in tags view' },
{ keys: ['esc'], description: 'Exit edit/setting views' },
],
Dashboard: [

View File

@@ -32,6 +32,10 @@ class SearchQueryParser {
}
}
interface OpenSearchParams {
query?: string;
}
export class SearchCtrl {
isOpen: boolean;
query: SearchQuery;
@@ -88,7 +92,7 @@ export class SearchCtrl {
appEvents.emit('search-query');
}
openSearch(evt, payload) {
openSearch(payload: OpenSearchParams = {}) {
if (this.isOpen) {
this.closeSearch();
return;
@@ -99,19 +103,16 @@ export class SearchCtrl {
this.selectedIndex = -1;
this.results = [];
this.query = {
query: evt ? `${evt.query} ` : '',
parsedQuery: this.queryParser.parse(evt && evt.query),
query: payload.query ? `${payload.query} ` : '',
parsedQuery: this.queryParser.parse(payload.query),
tags: [],
starred: false,
};
this.currentSearchId = 0;
this.ignoreClose = true;
this.isLoading = true;
if (payload && payload.starred) {
this.query.starred = true;
}
this.$timeout(() => {
this.ignoreClose = false;
this.giveSearchFocus = true;

View File

@@ -356,7 +356,12 @@ export function seriesDataToLogsModel(seriesData: SeriesData[], intervalMs: numb
return logsModel;
}
return undefined;
return {
hasUniqueLabels: false,
rows: [],
meta: [],
series: [],
};
}
export function logSeriesToLogsModel(logSeries: SeriesData[]): LogsModel {

View File

@@ -43,21 +43,11 @@ export class KeybindingSrv {
this.bind('g h', this.goToHome);
this.bind('g a', this.openAlerting);
this.bind('g p', this.goToProfile);
this.bind('s s', this.openSearchStarred);
this.bind('s o', this.openSearch);
this.bind('s t', this.openSearchTags);
this.bind('f', this.openSearch);
this.bindGlobal('esc', this.exit);
}
openSearchStarred() {
appEvents.emit('show-dash-search', { starred: true });
}
openSearchTags() {
appEvents.emit('show-dash-search', { tagsMode: true });
}
openSearch() {
appEvents.emit('show-dash-search');
}

View File

@@ -333,22 +333,29 @@ describe('LogsParsers', () => {
});
});
const emptyLogsModel = {
hasUniqueLabels: false,
rows: [],
meta: [],
series: [],
};
describe('seriesDataToLogsModel', () => {
it('given empty series should return undefined', () => {
expect(seriesDataToLogsModel([] as SeriesData[], 0)).toBeUndefined();
it('given empty series should return empty logs model', () => {
expect(seriesDataToLogsModel([] as SeriesData[], 0)).toMatchObject(emptyLogsModel);
});
it('given series without correct series name should not be processed', () => {
it('given series without correct series name should return empty logs model', () => {
const series: SeriesData[] = [
{
fields: [],
rows: [],
},
];
expect(seriesDataToLogsModel(series, 0)).toBeUndefined();
expect(seriesDataToLogsModel(series, 0)).toMatchObject(emptyLogsModel);
});
it('given series without a time field should not be processed', () => {
it('given series without a time field should return empty logs model', () => {
const series: SeriesData[] = [
{
fields: [
@@ -360,10 +367,10 @@ describe('seriesDataToLogsModel', () => {
rows: [],
},
];
expect(seriesDataToLogsModel(series, 0)).toBeUndefined();
expect(seriesDataToLogsModel(series, 0)).toMatchObject(emptyLogsModel);
});
it('given series without a string field should not be processed', () => {
it('given series without a string field should return empty logs model', () => {
const series: SeriesData[] = [
{
fields: [
@@ -375,7 +382,7 @@ describe('seriesDataToLogsModel', () => {
rows: [],
},
];
expect(seriesDataToLogsModel(series, 0)).toBeUndefined();
expect(seriesDataToLogsModel(series, 0)).toMatchObject(emptyLogsModel);
});
it('given one series should return expected logs model', () => {

View File

@@ -212,7 +212,7 @@ exports[`ServerStats Should render table with stats 1`] = `
</div>
</div>
<div
className="css-17l4171 track-horizontal"
className="css-52gpmd track-horizontal"
style={
Object {
"display": "none",
@@ -233,7 +233,7 @@ exports[`ServerStats Should render table with stats 1`] = `
/>
</div>
<div
className="css-17l4171 track-vertical"
className="css-52gpmd track-vertical"
style={
Object {
"display": "none",

View File

@@ -60,17 +60,14 @@ export class DashNav extends PureComponent<Props> {
}
}
onOpenSearch = () => {
const { dashboard } = this.props;
const haveFolder = dashboard.meta.folderId > 0;
appEvents.emit(
'show-dash-search',
haveFolder
? {
query: 'folder:current',
}
: null
);
onDahboardNameClick = () => {
appEvents.emit('show-dash-search');
};
onFolderNameClick = () => {
appEvents.emit('show-dash-search', {
query: 'folder:current',
});
};
onClose = () => {
@@ -148,11 +145,20 @@ export class DashNav extends PureComponent<Props> {
return (
<>
<div>
<a className="navbar-page-btn" onClick={this.onOpenSearch}>
<div className="navbar-page-btn">
{!this.isInFullscreenOrSettings && <i className="gicon gicon-dashboard" />}
{haveFolder && <span className="navbar-page-btn--folder">{folderTitle} / </span>}
{dashboard.title} <i className="fa fa-caret-down" />
</a>
{haveFolder && (
<>
<a className="navbar-page-btn__folder" onClick={this.onFolderNameClick}>
{folderTitle}
</a>
<i className="fa fa-chevron-right navbar-page-btn__folder-icon" />
</>
)}
<a onClick={this.onDahboardNameClick}>
{dashboard.title} <i className="fa fa-caret-down navbar-page-btn__search" />
</a>
</div>
</div>
{this.isSettings && <span className="navbar-settings-title">&nbsp;/ Settings</span>}
<div className="navbar__spacer" />

View File

@@ -61,6 +61,7 @@ export interface State {
isFullscreen: boolean;
fullscreenPanel: PanelModel | null;
scrollTop: number;
updateScrollTop: number;
rememberScrollTop: number;
showLoadingState: boolean;
}
@@ -73,6 +74,7 @@ export class DashboardPage extends PureComponent<Props, State> {
showLoadingState: false,
fullscreenPanel: null,
scrollTop: 0,
updateScrollTop: null,
rememberScrollTop: 0,
};
@@ -168,7 +170,7 @@ export class DashboardPage extends PureComponent<Props, State> {
isEditing: false,
isFullscreen: false,
fullscreenPanel: null,
scrollTop: this.state.rememberScrollTop,
updateScrollTop: this.state.rememberScrollTop,
},
this.triggerPanelsRendering.bind(this)
);
@@ -204,7 +206,7 @@ export class DashboardPage extends PureComponent<Props, State> {
setScrollTop = (e: MouseEvent<HTMLElement>): void => {
const target = e.target as HTMLElement;
this.setState({ scrollTop: target.scrollTop });
this.setState({ scrollTop: target.scrollTop, updateScrollTop: null });
};
onAddPanel = () => {
@@ -251,7 +253,7 @@ export class DashboardPage extends PureComponent<Props, State> {
render() {
const { dashboard, editview, $injector, isInitSlow, initError } = this.props;
const { isSettingsOpening, isEditing, isFullscreen, scrollTop } = this.state;
const { isSettingsOpening, isEditing, isFullscreen, scrollTop, updateScrollTop } = this.state;
if (!dashboard) {
if (isInitSlow) {
@@ -285,9 +287,9 @@ export class DashboardPage extends PureComponent<Props, State> {
/>
<div className="scroll-canvas scroll-canvas--dashboard">
<CustomScrollbar
autoHeightMin={'100%'}
autoHeightMin="100%"
setScrollTop={this.setScrollTop}
scrollTop={scrollTop}
scrollTop={updateScrollTop}
updateAfterMountMs={500}
className="custom-scrollbar--page"
>

View File

@@ -111,7 +111,7 @@ exports[`DashboardPage Dashboard init completed Should render dashboard grid 1`
autoHideTimeout={200}
className="custom-scrollbar--page"
hideTracksWhenNotNeeded={false}
scrollTop={0}
scrollTop={null}
setScrollTop={[Function]}
updateAfterMountMs={500}
>
@@ -349,7 +349,7 @@ exports[`DashboardPage When dashboard has editview url state should render setti
autoHideTimeout={200}
className="custom-scrollbar--page"
hideTracksWhenNotNeeded={false}
scrollTop={0}
scrollTop={null}
setScrollTop={[Function]}
updateAfterMountMs={500}
>

View File

@@ -0,0 +1,90 @@
import React from 'react';
import { shallow, ShallowWrapper } from 'enzyme';
import { DashboardGrid, Props } from './DashboardGrid';
import { DashboardModel } from '../state';
interface ScenarioContext {
props: Props;
wrapper?: ShallowWrapper<Props, any, DashboardGrid>;
setup?: (fn: () => void) => void;
setProps: (props: Partial<Props>) => void;
}
function getTestDashboard(overrides?: any, metaOverrides?: any): DashboardModel {
const data = Object.assign(
{
title: 'My dashboard',
panels: [
{
id: 1,
type: 'graph',
title: 'My graph',
gridPos: { x: 0, y: 0, w: 24, h: 10 },
},
{
id: 2,
type: 'graph2',
title: 'My graph2',
gridPos: { x: 0, y: 10, w: 25, h: 10 },
},
{
id: 3,
type: 'graph3',
title: 'My graph3',
gridPos: { x: 0, y: 20, w: 25, h: 100 },
},
{
id: 4,
type: 'graph4',
title: 'My graph4',
gridPos: { x: 0, y: 120, w: 25, h: 10 },
},
],
},
overrides
);
const meta = Object.assign({ canSave: true, canEdit: true }, metaOverrides);
return new DashboardModel(data, meta);
}
function dashboardGridScenario(description, scenarioFn: (ctx: ScenarioContext) => void) {
describe(description, () => {
let setupFn: () => void;
const ctx: ScenarioContext = {
setup: fn => {
setupFn = fn;
},
props: {
isEditing: false,
isFullscreen: false,
scrollTop: null,
dashboard: getTestDashboard(),
},
setProps: (props: Partial<Props>) => {
Object.assign(ctx.props, props);
if (ctx.wrapper) {
ctx.wrapper.setProps(ctx.props);
}
},
};
beforeEach(() => {
setupFn();
ctx.wrapper = shallow(<DashboardGrid {...ctx.props} />);
});
scenarioFn(ctx);
});
}
describe('DashboardGrid', () => {
dashboardGridScenario('Can render dashboard grid', ctx => {
ctx.setup(() => {});
it('Should render', () => {
expect(ctx.wrapper).toMatchSnapshot();
});
});
});

View File

@@ -205,7 +205,7 @@ export class DashboardGrid extends PureComponent<Props> {
return false;
}
const top = parseInt(elem.style.top.replace('px', ''), 10);
const top = elem.offsetTop;
const height = panel.gridPos.h * GRID_CELL_HEIGHT + 40;
const bottom = top + height;

View File

@@ -1,4 +0,0 @@
import { react2AngularDirective } from 'app/core/utils/react2angular';
import DashboardGrid from './DashboardGrid';
react2AngularDirective('dashboardGrid', DashboardGrid, [['dashboard', { watchDepth: 'reference' }]]);

View File

@@ -250,9 +250,10 @@ export class PanelChrome extends PureComponent<Props, State> {
{loading === LoadingState.Loading && this.renderLoadingState()}
<div className="panel-content">
<PanelComponent
id={panel.id}
data={data}
timeRange={data.request ? data.request.range : this.timeSrv.timeRange()}
options={panel.getOptions(plugin.defaults)}
options={panel.getOptions()}
width={width - theme.panelPadding * 2}
height={innerPanelHeight}
renderCounter={renderCounter}

View File

@@ -81,16 +81,21 @@ export class PanelHeader extends Component<Props, State> {
return (
<>
<PanelHeaderCorner
panel={panel}
title={panel.title}
description={panel.description}
scopedVars={panel.scopedVars}
links={panel.links}
error={error}
/>
<div className={panelHeaderClass}>
<div className="panel-title-container" onClick={this.onMenuToggle} onMouseDown={this.onMouseDown}>
<PanelHeaderCorner
panel={panel}
title={panel.title}
description={panel.description}
scopedVars={panel.scopedVars}
links={panel.links}
error={error}
/>
<div
className="panel-title-container"
onClick={this.onMenuToggle}
onMouseDown={this.onMouseDown}
aria-label="Panel Title"
>
<div className="panel-title">
<span className="icon-gf panel-alert-icon" />
<span className="panel-title-text">

View File

@@ -0,0 +1,996 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`DashboardGrid Can render dashboard grid Should render 1`] = `
<SizeMe(GridWrapper)
className="layout"
isDraggable={true}
isFullscreen={false}
isResizable={true}
layout={
Array [
Object {
"h": 10,
"i": "1",
"w": 24,
"x": 0,
"y": 0,
},
Object {
"h": 10,
"i": "2",
"w": 25,
"x": 0,
"y": 10,
},
Object {
"h": 100,
"i": "3",
"w": 25,
"x": 0,
"y": 20,
},
Object {
"h": 10,
"i": "4",
"w": 25,
"x": 0,
"y": 120,
},
]
}
onDragStop={[Function]}
onLayoutChange={[Function]}
onResize={[Function]}
onResizeStop={[Function]}
onWidthChange={[Function]}
>
<div
className=""
id="panel-1"
key="1"
>
<DashboardPanel
dashboard={
DashboardModel {
"annotations": Object {
"list": Array [
Object {
"builtIn": 1,
"datasource": "-- Grafana --",
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard",
},
],
},
"autoUpdate": undefined,
"description": undefined,
"editable": true,
"events": Emitter {
"emitter": EventEmitter {
"_events": Object {
"panel-added": EE {
"context": [Circular],
"fn": [Function],
"once": false,
},
"panel-removed": EE {
"context": [Circular],
"fn": [Function],
"once": false,
},
"repeats-processed": EE {
"context": [Circular],
"fn": [Function],
"once": false,
},
"row-collapsed": EE {
"context": [Circular],
"fn": [Function],
"once": false,
},
"row-expanded": EE {
"context": [Circular],
"fn": [Function],
"once": false,
},
"view-mode-changed": EE {
"context": [Circular],
"fn": [Function],
"once": false,
},
},
"_eventsCount": 6,
},
},
"gnetId": null,
"graphTooltip": 0,
"id": null,
"links": Array [],
"meta": Object {
"canEdit": true,
"canMakeEditable": false,
"canSave": true,
"canShare": true,
"canStar": true,
"fullscreen": false,
"isEditing": false,
"showSettings": true,
},
"originalTemplating": Array [],
"originalTime": Object {
"from": "now-6h",
"to": "now",
},
"panels": Array [
PanelModel {
"cachedPluginOptions": Object {},
"datasource": null,
"events": Emitter {
"emitter": EventEmitter {
"_events": Object {},
"_eventsCount": 0,
},
},
"gridPos": Object {
"h": 10,
"w": 24,
"x": 0,
"y": 0,
},
"id": 1,
"isInView": false,
"targets": Array [
Object {
"refId": "A",
},
],
"title": "My graph",
"transparent": false,
"type": "graph",
},
PanelModel {
"cachedPluginOptions": Object {},
"datasource": null,
"events": Emitter {
"emitter": EventEmitter {
"_events": Object {},
"_eventsCount": 0,
},
},
"gridPos": Object {
"h": 10,
"w": 25,
"x": 0,
"y": 10,
},
"id": 2,
"isInView": false,
"targets": Array [
Object {
"refId": "A",
},
],
"title": "My graph2",
"transparent": false,
"type": "graph2",
},
PanelModel {
"cachedPluginOptions": Object {},
"datasource": null,
"events": Emitter {
"emitter": EventEmitter {
"_events": Object {},
"_eventsCount": 0,
},
},
"gridPos": Object {
"h": 100,
"w": 25,
"x": 0,
"y": 20,
},
"id": 3,
"isInView": false,
"targets": Array [
Object {
"refId": "A",
},
],
"title": "My graph3",
"transparent": false,
"type": "graph3",
},
PanelModel {
"cachedPluginOptions": Object {},
"datasource": null,
"events": Emitter {
"emitter": EventEmitter {
"_events": Object {},
"_eventsCount": 0,
},
},
"gridPos": Object {
"h": 10,
"w": 25,
"x": 0,
"y": 120,
},
"id": 4,
"isInView": false,
"targets": Array [
Object {
"refId": "A",
},
],
"title": "My graph4",
"transparent": false,
"type": "graph4",
},
],
"refresh": undefined,
"revision": undefined,
"schemaVersion": 18,
"snapshot": undefined,
"style": "dark",
"tags": Array [],
"templating": Object {
"list": Array [],
},
"time": Object {
"from": "now-6h",
"to": "now",
},
"timepicker": Object {},
"timezone": "",
"title": "My dashboard",
"uid": null,
"version": 0,
}
}
isInView={false}
panel={
PanelModel {
"cachedPluginOptions": Object {},
"datasource": null,
"events": Emitter {
"emitter": EventEmitter {
"_events": Object {},
"_eventsCount": 0,
},
},
"gridPos": Object {
"h": 10,
"w": 24,
"x": 0,
"y": 0,
},
"id": 1,
"isInView": false,
"targets": Array [
Object {
"refId": "A",
},
],
"title": "My graph",
"transparent": false,
"type": "graph",
}
}
/>
</div>
<div
className=""
id="panel-2"
key="2"
>
<DashboardPanel
dashboard={
DashboardModel {
"annotations": Object {
"list": Array [
Object {
"builtIn": 1,
"datasource": "-- Grafana --",
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard",
},
],
},
"autoUpdate": undefined,
"description": undefined,
"editable": true,
"events": Emitter {
"emitter": EventEmitter {
"_events": Object {
"panel-added": EE {
"context": [Circular],
"fn": [Function],
"once": false,
},
"panel-removed": EE {
"context": [Circular],
"fn": [Function],
"once": false,
},
"repeats-processed": EE {
"context": [Circular],
"fn": [Function],
"once": false,
},
"row-collapsed": EE {
"context": [Circular],
"fn": [Function],
"once": false,
},
"row-expanded": EE {
"context": [Circular],
"fn": [Function],
"once": false,
},
"view-mode-changed": EE {
"context": [Circular],
"fn": [Function],
"once": false,
},
},
"_eventsCount": 6,
},
},
"gnetId": null,
"graphTooltip": 0,
"id": null,
"links": Array [],
"meta": Object {
"canEdit": true,
"canMakeEditable": false,
"canSave": true,
"canShare": true,
"canStar": true,
"fullscreen": false,
"isEditing": false,
"showSettings": true,
},
"originalTemplating": Array [],
"originalTime": Object {
"from": "now-6h",
"to": "now",
},
"panels": Array [
PanelModel {
"cachedPluginOptions": Object {},
"datasource": null,
"events": Emitter {
"emitter": EventEmitter {
"_events": Object {},
"_eventsCount": 0,
},
},
"gridPos": Object {
"h": 10,
"w": 24,
"x": 0,
"y": 0,
},
"id": 1,
"isInView": false,
"targets": Array [
Object {
"refId": "A",
},
],
"title": "My graph",
"transparent": false,
"type": "graph",
},
PanelModel {
"cachedPluginOptions": Object {},
"datasource": null,
"events": Emitter {
"emitter": EventEmitter {
"_events": Object {},
"_eventsCount": 0,
},
},
"gridPos": Object {
"h": 10,
"w": 25,
"x": 0,
"y": 10,
},
"id": 2,
"isInView": false,
"targets": Array [
Object {
"refId": "A",
},
],
"title": "My graph2",
"transparent": false,
"type": "graph2",
},
PanelModel {
"cachedPluginOptions": Object {},
"datasource": null,
"events": Emitter {
"emitter": EventEmitter {
"_events": Object {},
"_eventsCount": 0,
},
},
"gridPos": Object {
"h": 100,
"w": 25,
"x": 0,
"y": 20,
},
"id": 3,
"isInView": false,
"targets": Array [
Object {
"refId": "A",
},
],
"title": "My graph3",
"transparent": false,
"type": "graph3",
},
PanelModel {
"cachedPluginOptions": Object {},
"datasource": null,
"events": Emitter {
"emitter": EventEmitter {
"_events": Object {},
"_eventsCount": 0,
},
},
"gridPos": Object {
"h": 10,
"w": 25,
"x": 0,
"y": 120,
},
"id": 4,
"isInView": false,
"targets": Array [
Object {
"refId": "A",
},
],
"title": "My graph4",
"transparent": false,
"type": "graph4",
},
],
"refresh": undefined,
"revision": undefined,
"schemaVersion": 18,
"snapshot": undefined,
"style": "dark",
"tags": Array [],
"templating": Object {
"list": Array [],
},
"time": Object {
"from": "now-6h",
"to": "now",
},
"timepicker": Object {},
"timezone": "",
"title": "My dashboard",
"uid": null,
"version": 0,
}
}
isInView={false}
panel={
PanelModel {
"cachedPluginOptions": Object {},
"datasource": null,
"events": Emitter {
"emitter": EventEmitter {
"_events": Object {},
"_eventsCount": 0,
},
},
"gridPos": Object {
"h": 10,
"w": 25,
"x": 0,
"y": 10,
},
"id": 2,
"isInView": false,
"targets": Array [
Object {
"refId": "A",
},
],
"title": "My graph2",
"transparent": false,
"type": "graph2",
}
}
/>
</div>
<div
className=""
id="panel-3"
key="3"
>
<DashboardPanel
dashboard={
DashboardModel {
"annotations": Object {
"list": Array [
Object {
"builtIn": 1,
"datasource": "-- Grafana --",
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard",
},
],
},
"autoUpdate": undefined,
"description": undefined,
"editable": true,
"events": Emitter {
"emitter": EventEmitter {
"_events": Object {
"panel-added": EE {
"context": [Circular],
"fn": [Function],
"once": false,
},
"panel-removed": EE {
"context": [Circular],
"fn": [Function],
"once": false,
},
"repeats-processed": EE {
"context": [Circular],
"fn": [Function],
"once": false,
},
"row-collapsed": EE {
"context": [Circular],
"fn": [Function],
"once": false,
},
"row-expanded": EE {
"context": [Circular],
"fn": [Function],
"once": false,
},
"view-mode-changed": EE {
"context": [Circular],
"fn": [Function],
"once": false,
},
},
"_eventsCount": 6,
},
},
"gnetId": null,
"graphTooltip": 0,
"id": null,
"links": Array [],
"meta": Object {
"canEdit": true,
"canMakeEditable": false,
"canSave": true,
"canShare": true,
"canStar": true,
"fullscreen": false,
"isEditing": false,
"showSettings": true,
},
"originalTemplating": Array [],
"originalTime": Object {
"from": "now-6h",
"to": "now",
},
"panels": Array [
PanelModel {
"cachedPluginOptions": Object {},
"datasource": null,
"events": Emitter {
"emitter": EventEmitter {
"_events": Object {},
"_eventsCount": 0,
},
},
"gridPos": Object {
"h": 10,
"w": 24,
"x": 0,
"y": 0,
},
"id": 1,
"isInView": false,
"targets": Array [
Object {
"refId": "A",
},
],
"title": "My graph",
"transparent": false,
"type": "graph",
},
PanelModel {
"cachedPluginOptions": Object {},
"datasource": null,
"events": Emitter {
"emitter": EventEmitter {
"_events": Object {},
"_eventsCount": 0,
},
},
"gridPos": Object {
"h": 10,
"w": 25,
"x": 0,
"y": 10,
},
"id": 2,
"isInView": false,
"targets": Array [
Object {
"refId": "A",
},
],
"title": "My graph2",
"transparent": false,
"type": "graph2",
},
PanelModel {
"cachedPluginOptions": Object {},
"datasource": null,
"events": Emitter {
"emitter": EventEmitter {
"_events": Object {},
"_eventsCount": 0,
},
},
"gridPos": Object {
"h": 100,
"w": 25,
"x": 0,
"y": 20,
},
"id": 3,
"isInView": false,
"targets": Array [
Object {
"refId": "A",
},
],
"title": "My graph3",
"transparent": false,
"type": "graph3",
},
PanelModel {
"cachedPluginOptions": Object {},
"datasource": null,
"events": Emitter {
"emitter": EventEmitter {
"_events": Object {},
"_eventsCount": 0,
},
},
"gridPos": Object {
"h": 10,
"w": 25,
"x": 0,
"y": 120,
},
"id": 4,
"isInView": false,
"targets": Array [
Object {
"refId": "A",
},
],
"title": "My graph4",
"transparent": false,
"type": "graph4",
},
],
"refresh": undefined,
"revision": undefined,
"schemaVersion": 18,
"snapshot": undefined,
"style": "dark",
"tags": Array [],
"templating": Object {
"list": Array [],
},
"time": Object {
"from": "now-6h",
"to": "now",
},
"timepicker": Object {},
"timezone": "",
"title": "My dashboard",
"uid": null,
"version": 0,
}
}
isInView={false}
panel={
PanelModel {
"cachedPluginOptions": Object {},
"datasource": null,
"events": Emitter {
"emitter": EventEmitter {
"_events": Object {},
"_eventsCount": 0,
},
},
"gridPos": Object {
"h": 100,
"w": 25,
"x": 0,
"y": 20,
},
"id": 3,
"isInView": false,
"targets": Array [
Object {
"refId": "A",
},
],
"title": "My graph3",
"transparent": false,
"type": "graph3",
}
}
/>
</div>
<div
className=""
id="panel-4"
key="4"
>
<DashboardPanel
dashboard={
DashboardModel {
"annotations": Object {
"list": Array [
Object {
"builtIn": 1,
"datasource": "-- Grafana --",
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard",
},
],
},
"autoUpdate": undefined,
"description": undefined,
"editable": true,
"events": Emitter {
"emitter": EventEmitter {
"_events": Object {
"panel-added": EE {
"context": [Circular],
"fn": [Function],
"once": false,
},
"panel-removed": EE {
"context": [Circular],
"fn": [Function],
"once": false,
},
"repeats-processed": EE {
"context": [Circular],
"fn": [Function],
"once": false,
},
"row-collapsed": EE {
"context": [Circular],
"fn": [Function],
"once": false,
},
"row-expanded": EE {
"context": [Circular],
"fn": [Function],
"once": false,
},
"view-mode-changed": EE {
"context": [Circular],
"fn": [Function],
"once": false,
},
},
"_eventsCount": 6,
},
},
"gnetId": null,
"graphTooltip": 0,
"id": null,
"links": Array [],
"meta": Object {
"canEdit": true,
"canMakeEditable": false,
"canSave": true,
"canShare": true,
"canStar": true,
"fullscreen": false,
"isEditing": false,
"showSettings": true,
},
"originalTemplating": Array [],
"originalTime": Object {
"from": "now-6h",
"to": "now",
},
"panels": Array [
PanelModel {
"cachedPluginOptions": Object {},
"datasource": null,
"events": Emitter {
"emitter": EventEmitter {
"_events": Object {},
"_eventsCount": 0,
},
},
"gridPos": Object {
"h": 10,
"w": 24,
"x": 0,
"y": 0,
},
"id": 1,
"isInView": false,
"targets": Array [
Object {
"refId": "A",
},
],
"title": "My graph",
"transparent": false,
"type": "graph",
},
PanelModel {
"cachedPluginOptions": Object {},
"datasource": null,
"events": Emitter {
"emitter": EventEmitter {
"_events": Object {},
"_eventsCount": 0,
},
},
"gridPos": Object {
"h": 10,
"w": 25,
"x": 0,
"y": 10,
},
"id": 2,
"isInView": false,
"targets": Array [
Object {
"refId": "A",
},
],
"title": "My graph2",
"transparent": false,
"type": "graph2",
},
PanelModel {
"cachedPluginOptions": Object {},
"datasource": null,
"events": Emitter {
"emitter": EventEmitter {
"_events": Object {},
"_eventsCount": 0,
},
},
"gridPos": Object {
"h": 100,
"w": 25,
"x": 0,
"y": 20,
},
"id": 3,
"isInView": false,
"targets": Array [
Object {
"refId": "A",
},
],
"title": "My graph3",
"transparent": false,
"type": "graph3",
},
PanelModel {
"cachedPluginOptions": Object {},
"datasource": null,
"events": Emitter {
"emitter": EventEmitter {
"_events": Object {},
"_eventsCount": 0,
},
},
"gridPos": Object {
"h": 10,
"w": 25,
"x": 0,
"y": 120,
},
"id": 4,
"isInView": false,
"targets": Array [
Object {
"refId": "A",
},
],
"title": "My graph4",
"transparent": false,
"type": "graph4",
},
],
"refresh": undefined,
"revision": undefined,
"schemaVersion": 18,
"snapshot": undefined,
"style": "dark",
"tags": Array [],
"templating": Object {
"list": Array [],
},
"time": Object {
"from": "now-6h",
"to": "now",
},
"timepicker": Object {},
"timezone": "",
"title": "My dashboard",
"uid": null,
"version": 0,
}
}
isInView={false}
panel={
PanelModel {
"cachedPluginOptions": Object {},
"datasource": null,
"events": Emitter {
"emitter": EventEmitter {
"_events": Object {},
"_eventsCount": 0,
},
},
"gridPos": Object {
"h": 10,
"w": 25,
"x": 0,
"y": 120,
},
"id": 4,
"isInView": false,
"targets": Array [
Object {
"refId": "A",
},
],
"title": "My graph4",
"transparent": false,
"type": "graph4",
}
}
/>
</div>
</SizeMe(GridWrapper)>
`;

View File

@@ -1,5 +1,3 @@
import './dashgrid/DashboardGridDirective';
// Services
import './services/UnsavedChangesSrv';
import './services/DashboardLoaderSrv';

View File

@@ -53,8 +53,8 @@ export class VisualizationTab extends PureComponent<Props, State> {
}
getReactPanelOptions = () => {
const { panel, plugin } = this.props;
return panel.getOptions(plugin.defaults);
const { panel } = this.props;
return panel.getOptions();
};
renderPanelOptions() {

View File

@@ -7,45 +7,70 @@ describe('PanelModel', () => {
describe('when creating new panel model', () => {
let model;
let modelJson;
let persistedOptionsMock;
const defaultOptionsMock = {
fieldOptions: {
thresholds: [
{
color: '#F2495C',
index: 1,
value: 50,
},
{
color: '#73BF69',
index: 0,
value: null,
},
],
},
showThresholds: true,
};
beforeEach(() => {
persistedOptionsMock = {
fieldOptions: {
thresholds: [
{
color: '#F2495C',
index: 1,
value: 50,
},
{
color: '#73BF69',
index: 0,
value: null,
},
],
},
};
modelJson = {
type: 'table',
showColumns: true,
targets: [{ refId: 'A' }, { noRefId: true }],
options: {
fieldOptions: {
thresholds: [
{
color: '#F2495C',
index: 1,
value: 50,
},
{
color: '#73BF69',
index: 0,
value: null,
},
],
},
},
options: persistedOptionsMock,
};
model = new PanelModel(modelJson);
model.pluginLoaded(
getPanelPlugin(
{
id: 'table',
},
null, // react
TablePanelCtrl // angular
)
const panelPlugin = getPanelPlugin(
{
id: 'table',
},
null, // react
TablePanelCtrl // angular
);
panelPlugin.setDefaults(defaultOptionsMock);
model.pluginLoaded(panelPlugin);
});
it('should apply defaults', () => {
expect(model.gridPos.h).toBe(3);
});
it('should apply option defaults', () => {
expect(model.getOptions().showThresholds).toBeTruthy();
});
it('should set model props on instance', () => {
expect(model.showColumns).toBe(true);
});
@@ -89,11 +114,22 @@ describe('PanelModel', () => {
});
describe('when changing panel type', () => {
const newPanelPluginDefaults = {
showThresholdLabels: false,
};
beforeEach(() => {
model.changePlugin(getPanelPlugin({ id: 'graph' }));
const newPlugin = getPanelPlugin({ id: 'graph' });
newPlugin.setDefaults(newPanelPluginDefaults);
model.changePlugin(newPlugin);
model.alert = { id: 2 };
});
it('should apply next panel option defaults', () => {
expect(model.getOptions().showThresholdLabels).toBeFalsy();
expect(model.getOptions().showThresholds).toBeUndefined();
});
it('should remove table properties but keep core props', () => {
expect(model.showColumns).toBe(undefined);
});
@@ -153,19 +189,5 @@ describe('PanelModel', () => {
expect(panelQueryRunner).toBe(sameQueryRunner);
});
});
describe('get panel options', () => {
it('should apply defaults', () => {
model.options = { existingProp: 10 };
const options = model.getOptions({
defaultProp: true,
existingProp: 0,
});
expect(options.defaultProp).toBe(true);
expect(options.existingProp).toBe(10);
expect(model.options).toBe(options);
});
});
});
});

View File

@@ -157,8 +157,8 @@ export class PanelModel {
}
}
getOptions(panelDefaults: any) {
return _.defaultsDeep(this.options || {}, panelDefaults);
getOptions() {
return this.options;
}
updateOptions(options: object) {
@@ -179,7 +179,6 @@ export class PanelModel {
model[property] = _.cloneDeep(this[property]);
}
return model;
}
@@ -247,9 +246,18 @@ export class PanelModel {
});
}
private applyPluginOptionDefaults(plugin: PanelPlugin) {
if (plugin.angularConfigCtrl) {
return;
}
this.options = _.defaultsDeep({}, this.options || {}, plugin.defaults);
}
pluginLoaded(plugin: PanelPlugin) {
this.plugin = plugin;
this.applyPluginOptionDefaults(plugin);
if (plugin.panel && plugin.onPanelMigration) {
const version = getPluginVersion(plugin);
if (version !== this.pluginVersion) {
@@ -284,7 +292,7 @@ export class PanelModel {
// switch
this.type = pluginId;
this.plugin = newPlugin;
this.applyPluginOptionDefaults(newPlugin);
// Let panel plugins inspect options from previous panel and keep any that it can use
if (newPlugin.onPanelTypeChanged) {
this.options = this.options || {};
@@ -324,7 +332,7 @@ export class PanelModel {
}
hasTitle() {
return !!this.title.length;
return this.title && this.title.length > 0;
}
destroy() {

View File

@@ -97,9 +97,6 @@ export class PanelQueryRunner {
delayStateNotification,
} = options;
// filter out hidden queries & deep clone them
const clonedAndFilteredQueries = cloneDeep(queries.filter(q => !q.hide));
const request: DataQueryRequest = {
requestId: getNextRequestId(),
timezone,
@@ -109,7 +106,7 @@ export class PanelQueryRunner {
timeInfo,
interval: '',
intervalMs: 0,
targets: clonedAndFilteredQueries,
targets: cloneDeep(queries),
maxDataPoints: maxDataPoints || widthPixels,
scopedVars: scopedVars || {},
cacheTimeout,
@@ -124,6 +121,10 @@ export class PanelQueryRunner {
try {
const ds = await getDataSource(datasource, request.scopedVars);
if (ds.meta && !ds.meta.hiddenQueries) {
request.targets = request.targets.filter(q => !q.hide);
}
// Attach the datasource name to each query
request.targets = request.targets.map(query => {
if (!query.datasource) {

View File

@@ -275,7 +275,6 @@ export class Explore extends React.PureComponent<ExploreProps> {
<LogsContainer
width={width}
exploreId={exploreId}
onChangeTime={this.onChangeTime}
onClickLabel={this.onClickLabel}
onStartScanning={this.onStartScanning}
onStopScanning={this.onStopScanning}

View File

@@ -1,13 +1,14 @@
import React, { PureComponent } from 'react';
import { hot } from 'react-hot-loader';
import { connect } from 'react-redux';
import moment from 'moment';
import { RawTimeRange, TimeRange, LogLevel, TimeZone, AbsoluteTimeRange } from '@grafana/ui';
import { ExploreId, ExploreItemState } from 'app/types/explore';
import { LogsModel, LogsDedupStrategy } from 'app/core/logs_model';
import { StoreState } from 'app/types';
import { toggleLogs, changeDedupStrategy } from './state/actions';
import { toggleLogs, changeDedupStrategy, changeTime } from './state/actions';
import Logs from './Logs';
import Panel from './Panel';
import { toggleLogLevelAction } from 'app/features/explore/state/actionTypes';
@@ -20,7 +21,6 @@ interface LogsContainerProps {
logsHighlighterExpressions?: string[];
logsResult?: LogsModel;
dedupedResult?: LogsModel;
onChangeTime: (range: AbsoluteTimeRange) => void;
onClickLabel: (key: string, value: string) => void;
onStartScanning: () => void;
onStopScanning: () => void;
@@ -35,9 +35,19 @@ interface LogsContainerProps {
dedupStrategy: LogsDedupStrategy;
hiddenLogLevels: Set<LogLevel>;
width: number;
changeTime: typeof changeTime;
}
export class LogsContainer extends PureComponent<LogsContainerProps> {
onChangeTime = (absRange: AbsoluteTimeRange) => {
const { exploreId, timeZone, changeTime } = this.props;
const range = {
from: timeZone.isUtc ? moment.utc(absRange.from) : moment(absRange.from),
to: timeZone.isUtc ? moment.utc(absRange.to) : moment(absRange.to),
};
changeTime(exploreId, range);
};
onClickLogsButton = () => {
this.props.toggleLogs(this.props.exploreId, this.props.showingLogs);
};
@@ -61,7 +71,6 @@ export class LogsContainer extends PureComponent<LogsContainerProps> {
logsHighlighterExpressions,
logsResult,
dedupedResult,
onChangeTime,
onClickLabel,
onStartScanning,
onStopScanning,
@@ -83,7 +92,7 @@ export class LogsContainer extends PureComponent<LogsContainerProps> {
exploreId={exploreId}
highlighterExpressions={logsHighlighterExpressions}
loading={loading}
onChangeTime={onChangeTime}
onChangeTime={this.onChangeTime}
onClickLabel={onClickLabel}
onStartScanning={onStartScanning}
onStopScanning={onStopScanning}
@@ -130,6 +139,7 @@ const mapDispatchToProps = {
toggleLogs,
changeDedupStrategy,
toggleLogLevelAction,
changeTime,
};
export default hot(module)(

View File

@@ -1,18 +1,29 @@
import React, { FC, useContext } from 'react';
import { css } from 'emotion';
import { PluginState, Tooltip, ThemeContext } from '@grafana/ui';
import { PopperContent } from '@grafana/ui/src/components/Tooltip/PopperController';
interface Props {
state?: PluginState;
}
function getPluginStateInfoText(state?: PluginState): string | null {
function getPluginStateInfoText(state?: PluginState): PopperContent<any> | null {
switch (state) {
case PluginState.alpha:
return 'Plugin in alpha state. Means work in progress and updates may include breaking changes.';
return (
<div>
<h5>Alpha Plugin</h5>
<p>This plugin is a work in progress and updates may include breaking changes.</p>
</div>
);
case PluginState.beta:
return 'Plugin in beta state. Means there could be bugs and minor breaking changes.';
return (
<div>
<h5>Beta Plugin</h5>
<p>There could be bugs and minor breaking changes to this plugin.</p>
</div>
);
}
return null;
}
@@ -34,10 +45,11 @@ const PluginStateinfo: FC<Props> = props => {
font-size: 13px;
padding: 4px 8px;
margin-left: 16px;
cursor: help;
`;
return (
<Tooltip content={text}>
<Tooltip content={text} theme={'info'} placement={'top'}>
<div className={styles}>
<i className="fa fa-warning" /> {props.state}
</div>

View File

@@ -105,7 +105,7 @@ exposeToPlugin('app/core/services/backend_srv', {
});
exposeToPlugin('app/plugins/sdk', sdk);
exposeToPlugin('@grafana/ui/src/utils/datemath', datemath);
exposeToPlugin('app/core/utils/datemath', datemath);
exposeToPlugin('app/core/utils/file_export', fileExport);
exposeToPlugin('app/core/utils/flatten', flatten);
exposeToPlugin('app/core/utils/kbn', kbn);

View File

@@ -3,6 +3,7 @@
"name": "CloudWatch",
"id": "cloudwatch",
"hiddenQueries": true,
"metrics": true,
"alerting": true,
"annotations": true,

View File

@@ -10,7 +10,7 @@
<div class="gf-form" ng-if="ctrl.target.queryType === 'Azure Monitor' || ctrl.target.queryType === 'Azure Log Analytics'">
<label class="gf-form-label query-keyword width-9">Subscription</label>
<gf-form-dropdown model="ctrl.target.subscription" allow-custom="true" lookup-text="true"
get-options="ctrl.subscriptions" on-change="ctrl.onSubscriptionChange()" css-class="min-width-12">
get-options="ctrl.getSubscriptions()" on-change="ctrl.onSubscriptionChange()" css-class="min-width-12">
</gf-form-dropdown>
</div>
<div class="gf-form gf-form--grow">
@@ -108,17 +108,18 @@
<gf-form-dropdown model="ctrl.target.azureLogAnalytics.workspace" allow-custom="true" lookup-text="true"
get-options="ctrl.workspaces" on-change="ctrl.refresh()" css-class="min-width-12">
</gf-form-dropdown>
<div class="gf-form">
<div class="width-1"></div>
</div>
<div class="gf-form">
<button class="btn btn-primary width-10" ng-click="ctrl.refresh()">Run</button>
</div>
<div class="gf-form">
<label class="gf-form-label">(Run Query: Shift+Enter, Trigger Suggestion: Ctrl+Space)</label>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
<div class="gf-form">
<div class="width-1"></div>
</div>
<div class="gf-form">
<button class="btn btn-primary width-10" ng-click="ctrl.refresh()">Run</button>
</div>
<div class="gf-form">
<label class="gf-form-label">(Run Query: Shift+Enter, Trigger Suggestion: Ctrl+Space)</label>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
</div>

View File

@@ -189,11 +189,19 @@ describe('AzureMonitorQueryCtrl', () => {
};
beforeEach(() => {
queryCtrl.target.subscription = 'sub1';
queryCtrl.target.azureMonitor.resourceGroup = 'test';
queryCtrl.target.azureMonitor.metricDefinition = 'Microsoft.Compute/virtualMachines';
queryCtrl.target.azureMonitor.resourceName = 'test';
queryCtrl.target.azureMonitor.metricName = 'Percentage CPU';
queryCtrl.datasource.getMetricMetadata = function(resourceGroup, metricDefinition, resourceName, metricName) {
queryCtrl.datasource.getMetricMetadata = function(
subscription,
resourceGroup,
metricDefinition,
resourceName,
metricName
) {
expect(subscription).toBe('sub1');
expect(resourceGroup).toBe('test');
expect(metricDefinition).toBe('Microsoft.Compute/virtualMachines');
expect(resourceName).toBe('test');

View File

@@ -197,6 +197,8 @@ export class AzureMonitorQueryCtrl extends QueryCtrl {
if (!this.target.subscription && this.subscriptions.length > 0) {
this.target.subscription = this.subscriptions[0].value;
}
return this.subscriptions;
});
}
@@ -204,6 +206,18 @@ export class AzureMonitorQueryCtrl extends QueryCtrl {
if (this.target.queryType === 'Azure Log Analytics') {
return this.getWorkspaces();
}
if (this.target.queryType === 'Azure Monitor') {
this.target.azureMonitor.resourceGroup = this.defaultDropdownValue;
this.target.azureMonitor.metricDefinition = this.defaultDropdownValue;
this.target.azureMonitor.resourceName = this.defaultDropdownValue;
this.target.azureMonitor.metricName = this.defaultDropdownValue;
this.target.azureMonitor.aggregation = '';
this.target.azureMonitor.timeGrains = [];
this.target.azureMonitor.timeGrain = '';
this.target.azureMonitor.dimensions = [];
this.target.azureMonitor.dimension = '';
}
}
/* Azure Monitor Section */
@@ -282,6 +296,9 @@ export class AzureMonitorQueryCtrl extends QueryCtrl {
this.target.azureMonitor.metricDefinition = this.defaultDropdownValue;
this.target.azureMonitor.resourceName = this.defaultDropdownValue;
this.target.azureMonitor.metricName = this.defaultDropdownValue;
this.target.azureMonitor.aggregation = '';
this.target.azureMonitor.timeGrains = [];
this.target.azureMonitor.timeGrain = '';
this.target.azureMonitor.dimensions = [];
this.target.azureMonitor.dimension = '';
}
@@ -289,12 +306,18 @@ export class AzureMonitorQueryCtrl extends QueryCtrl {
onMetricDefinitionChange() {
this.target.azureMonitor.resourceName = this.defaultDropdownValue;
this.target.azureMonitor.metricName = this.defaultDropdownValue;
this.target.azureMonitor.aggregation = '';
this.target.azureMonitor.timeGrains = [];
this.target.azureMonitor.timeGrain = '';
this.target.azureMonitor.dimensions = [];
this.target.azureMonitor.dimension = '';
}
onResourceNameChange() {
this.target.azureMonitor.metricName = this.defaultDropdownValue;
this.target.azureMonitor.aggregation = '';
this.target.azureMonitor.timeGrains = [];
this.target.azureMonitor.timeGrain = '';
this.target.azureMonitor.dimensions = [];
this.target.azureMonitor.dimension = '';
}
@@ -306,6 +329,7 @@ export class AzureMonitorQueryCtrl extends QueryCtrl {
return this.datasource
.getMetricMetadata(
this.replace(this.target.subscription),
this.replace(this.target.azureMonitor.resourceGroup),
this.replace(this.target.azureMonitor.metricDefinition),
this.replace(this.target.azureMonitor.resourceName),
@@ -315,6 +339,7 @@ export class AzureMonitorQueryCtrl extends QueryCtrl {
this.target.azureMonitor.aggOptions = metadata.supportedAggTypes || [metadata.primaryAggType];
this.target.azureMonitor.aggregation = metadata.primaryAggType;
this.target.azureMonitor.timeGrains = [{ text: 'auto', value: 'auto' }].concat(metadata.supportedTimeGrains);
this.target.azureMonitor.timeGrain = 'auto';
this.target.azureMonitor.dimensions = metadata.dimensions;
if (metadata.dimensions.length > 0) {

View File

@@ -5,6 +5,7 @@
"includes": [{ "type": "dashboard", "name": "Graphite Carbon Metrics", "path": "dashboards/carbon_metrics.json" }],
"hiddenQueries": true,
"metrics": true,
"alerting": true,
"annotations": true,

View File

@@ -34,7 +34,7 @@ export default class InfluxDatasource {
this.withCredentials = instanceSettings.withCredentials;
this.interval = (instanceSettings.jsonData || {}).timeInterval;
this.responseParser = new ResponseParser();
this.httpMode = instanceSettings.jsonData.httpMode;
this.httpMode = instanceSettings.jsonData.httpMode || 'GET';
}
query(options) {

View File

@@ -81,7 +81,7 @@ export class BarGaugePanelEditor extends PureComponent<PanelEditorProps<BarGauge
title="Field"
showMinMax={true}
onChange={this.onDefaultsChange}
options={fieldOptions.defaults}
value={fieldOptions.defaults}
/>
<ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={fieldOptions.thresholds} />

View File

@@ -5,7 +5,7 @@ import React, { PureComponent } from 'react';
import { config } from 'app/core/config';
// Components
import { Gauge, FieldDisplay, getFieldDisplayValues } from '@grafana/ui';
import { Gauge, FieldDisplay, getFieldDisplayValues, VizOrientation } from '@grafana/ui';
// Types
import { GaugeOptions } from './types';
@@ -43,7 +43,7 @@ export class GaugePanel extends PureComponent<PanelProps<GaugeOptions>> {
};
render() {
const { height, width, options, data, renderCounter } = this.props;
const { height, width, data, renderCounter } = this.props;
return (
<VizRepeater
getValues={this.getValues}
@@ -52,7 +52,7 @@ export class GaugePanel extends PureComponent<PanelProps<GaugeOptions>> {
height={height}
source={data}
renderCounter={renderCounter}
orientation={options.orientation}
orientation={VizOrientation.Auto}
/>
);
}

View File

@@ -83,7 +83,7 @@ export class GaugePanelEditor extends PureComponent<PanelEditorProps<GaugeOption
title="Field"
showMinMax={true}
onChange={this.onDefaultsChange}
options={fieldOptions.defaults}
value={fieldOptions.defaults}
/>
<ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={fieldOptions.thresholds} />

View File

@@ -0,0 +1,174 @@
// Libraries
import React, { PureComponent } from 'react';
import { PanelProps } from '@grafana/ui/src/types';
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
import { getBackendSrv } from 'app/core/services/backend_srv';
import { contextSrv } from 'app/core/core';
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
interface Step {
title: string;
cta?: string;
icon: string;
href: string;
target?: string;
note?: string;
check: () => Promise<boolean>;
done?: boolean;
}
interface State {
checksDone: boolean;
}
export class GettingStarted extends PureComponent<PanelProps, State> {
stepIndex = 0;
readonly steps: Step[];
constructor(props: PanelProps) {
super(props);
this.state = {
checksDone: false,
};
this.steps = [
{
title: 'Install Grafana',
icon: 'icon-gf icon-gf-check',
href: 'http://docs.grafana.org/',
target: '_blank',
note: 'Review the installation docs',
check: () => Promise.resolve(true),
},
{
title: 'Create your first data source',
cta: 'Add data source',
icon: 'gicon gicon-datasources',
href: 'datasources/new?gettingstarted',
check: () => {
return new Promise(resolve => {
resolve(
getDatasourceSrv()
.getMetricSources()
.filter(item => {
return item.meta.builtIn !== true;
}).length > 0
);
});
},
},
{
title: 'Create your first dashboard',
cta: 'New dashboard',
icon: 'gicon gicon-dashboard',
href: 'dashboard/new?gettingstarted',
check: () => {
return getBackendSrv()
.search({ limit: 1 })
.then(result => {
return result.length > 0;
});
},
},
{
title: 'Invite your team',
cta: 'Add Users',
icon: 'gicon gicon-team',
href: 'org/users?gettingstarted',
check: () => {
return getBackendSrv()
.get('/api/org/users')
.then(res => {
return res.length > 1;
});
},
},
{
title: 'Install apps & plugins',
cta: 'Explore plugin repository',
icon: 'gicon gicon-plugins',
href: 'https://grafana.com/plugins?utm_source=grafana_getting_started',
check: () => {
return getBackendSrv()
.get('/api/plugins', { embedded: 0, core: 0 })
.then(plugins => {
return plugins.length > 0;
});
},
},
];
}
componentDidMount() {
this.stepIndex = -1;
return this.nextStep().then((res: any) => {
this.setState({ checksDone: true });
});
}
nextStep() {
if (this.stepIndex === this.steps.length - 1) {
return Promise.resolve();
}
this.stepIndex += 1;
const currentStep = this.steps[this.stepIndex];
return currentStep.check().then(passed => {
if (passed) {
currentStep.done = true;
return this.nextStep();
}
return Promise.resolve();
});
}
dismiss = () => {
const { id } = this.props;
const dashboard = getDashboardSrv().getCurrent();
const panel = dashboard.getPanelById(id);
dashboard.removePanel(panel);
getBackendSrv()
.request({
method: 'PUT',
url: '/api/user/helpflags/1',
showSuccessAlert: false,
})
.then((res: any) => {
contextSrv.user.helpFlags1 = res.helpFlags1;
});
};
render() {
const { checksDone } = this.state;
if (!checksDone) {
return <div>checking...</div>;
}
return (
<div className="progress-tracker-container">
<button className="progress-tracker-close-btn" onClick={this.dismiss}>
<i className="fa fa-remove" />
</button>
<div className="progress-tracker">
{this.steps.map(step => {
return (
<div className={step.done ? 'progress-step completed' : 'progress-step active'}>
<a className="progress-link" href={step.href} target={step.target} title={step.note}>
<span className="progress-marker" ng-className="step.cssClass">
<i className={step.icon} />
</span>
<span className="progress-text">{step.title}</span>
</a>
<a className="btn-small progress-step-cta" href={step.href} target={step.target}>
{step.cta}
</a>
</div>
);
})}
</div>
</div>
);
}
}

View File

@@ -1,40 +0,0 @@
<div class="gf-form-group">
<div class="gf-form-inline">
<div class="gf-form">
<span class="gf-form-label width-10">Mode</span>
<div class="gf-form-select-wrapper max-width-10">
<select class="gf-form-input" ng-model="ctrl.panel.mode" ng-options="f for f in ctrl.modes" ng-change="ctrl.refresh()"></select>
</div>
</div>
<div class="gf-form" ng-show="ctrl.panel.mode === 'recently viewed'">
<span class="gf-form-label">
<i class="grafana-tip fa fa-question-circle ng-scope" bs-tooltip="'WARNING: This list will be cleared when clearing browser cache'" data-original-title="" title=""></i>
</span>
</div>
</div>
<div class="gf-form-inline" ng-if="ctrl.panel.mode === 'search'">
<div class="gf-form">
<span class="gf-form-label width-10">Search options</span>
<span class="gf-form-label">Query</span>
<input type="text" class="gf-form-input" placeholder="title query"
ng-model="ctrl.panel.query" ng-change="ctrl.refresh()" ng-model-onblur>
</div>
<div class="gf-form">
<span class="gf-form-label">Tags</span>
<bootstrap-tagsinput ng-model="ctrl.panel.tags" tagclass="label label-tag" placeholder="add tags" on-tags-updated="ctrl.refresh()">
</bootstrap-tagsinput>
</div>
</div>
<div class="gf-form-inline">
<div class="gf-form">
<span class="gf-form-label width-10">Limit number to</span>
<input class="gf-form-input" type="number" ng-model="ctrl.panel.limit" ng-model-onblur ng-change="ctrl.refresh()">
</div>
</div>
</div>

View File

@@ -1,16 +0,0 @@
<div class="dashlist" ng-if="ctrl.checksDone">
<div class="dashlist-section">
<button class="dashlist-cta-close-btn" ng-click="ctrl.dismiss()">
<i class="fa fa-remove"></i>
</button>
<ul class="progress-tracker">
<li class="progress-step" ng-repeat="step in ctrl.steps" ng-class="step.cssClass">
<a class="progress-link" ng-href="{{step.href}}" target="{{step.target}}" title="{{step.note}}">
<span class="progress-marker" ng-class="step.cssClass"><i class="{{step.icon}}"></i></span>
<span class="progress-text" ng-href="{{step.href}}" target="{{step.target}}">{{step.title}}</span>
</a>
<a class="btn-small progress-step-cta" ng-href="{{step.href}}" target="{{step.target}}">{{step.cta}}</a>
</li>
</ul>
</div>
</div>

View File

@@ -1,118 +1,5 @@
import { PanelCtrl } from 'app/plugins/sdk';
import { PanelPlugin } from '@grafana/ui';
import { GettingStarted } from './GettingStarted';
import { contextSrv } from 'app/core/core';
class GettingStartedPanelCtrl extends PanelCtrl {
static templateUrl = 'public/app/plugins/panel/gettingstarted/module.html';
checksDone: boolean;
stepIndex: number;
steps: any;
/** @ngInject */
constructor($scope, $injector, private backendSrv, datasourceSrv, private $q) {
super($scope, $injector);
this.stepIndex = 0;
this.steps = [];
this.steps.push({
title: 'Install Grafana',
icon: 'icon-gf icon-gf-check',
href: 'http://docs.grafana.org/',
target: '_blank',
note: 'Review the installation docs',
check: () => $q.when(true),
});
this.steps.push({
title: 'Create your first data source',
cta: 'Add data source',
icon: 'gicon gicon-datasources',
href: 'datasources/new?gettingstarted',
check: () => {
return $q.when(
datasourceSrv.getMetricSources().filter(item => {
return item.meta.builtIn !== true;
}).length > 0
);
},
});
this.steps.push({
title: 'Create your first dashboard',
cta: 'New dashboard',
icon: 'gicon gicon-dashboard',
href: 'dashboard/new?gettingstarted',
check: () => {
return this.backendSrv.search({ limit: 1 }).then(result => {
return result.length > 0;
});
},
});
this.steps.push({
title: 'Invite your team',
cta: 'Add Users',
icon: 'gicon gicon-team',
href: 'org/users?gettingstarted',
check: () => {
return this.backendSrv.get('/api/org/users').then(res => {
return res.length > 1;
});
},
});
this.steps.push({
title: 'Install apps & plugins',
cta: 'Explore plugin repository',
icon: 'gicon gicon-plugins',
href: 'https://grafana.com/plugins?utm_source=grafana_getting_started',
check: () => {
return this.backendSrv.get('/api/plugins', { embedded: 0, core: 0 }).then(plugins => {
return plugins.length > 0;
});
},
});
}
$onInit() {
this.stepIndex = -1;
return this.nextStep().then(res => {
this.checksDone = true;
});
}
nextStep() {
if (this.stepIndex === this.steps.length - 1) {
return this.$q.when();
}
this.stepIndex += 1;
const currentStep = this.steps[this.stepIndex];
return currentStep.check().then(passed => {
if (passed) {
currentStep.cssClass = 'completed';
return this.nextStep();
}
currentStep.cssClass = 'active';
return this.$q.when();
});
}
dismiss() {
this.dashboard.removePanel(this.panel, false);
this.backendSrv
.request({
method: 'PUT',
url: '/api/user/helpflags/1',
showSuccessAlert: false,
})
.then(res => {
contextSrv.user.helpFlags1 = res.helpFlags1;
});
}
}
export { GettingStartedPanelCtrl, GettingStartedPanelCtrl as PanelCtrl };
// Simplest possible panel plugin
export const plugin = new PanelPlugin(GettingStarted);

View File

@@ -46,7 +46,7 @@ export class PieChartPanelEditor extends PureComponent<PanelEditorProps<PieChart
title="Field (default)"
showMinMax={true}
onChange={this.onDefaultsChange}
options={fieldOptions.defaults}
value={fieldOptions.defaults}
/>
<PieChartOptionsBox onOptionsChange={onOptionsChange} options={options} />

View File

@@ -63,7 +63,7 @@ export class SingleStatEditor extends PureComponent<PanelEditorProps<SingleStatO
title="Field (default)"
showMinMax={true}
onChange={this.onDefaultsChange}
options={fieldOptions.defaults}
value={fieldOptions.defaults}
/>
<FontSizeEditor options={options} onChange={this.props.onOptionsChange} />

View File

@@ -67,11 +67,6 @@
min-height: $navbarHeight;
line-height: $navbarHeight;
.fa-caret-down {
font-size: 60%;
padding-left: 6px;
}
.gicon {
top: -2px;
position: relative;
@@ -85,17 +80,32 @@
display: inline-block;
}
}
}
&--folder {
color: $text-color-weak;
display: none;
.navbar-page-btn__folder {
color: $text-color-weak;
display: none;
@include media-breakpoint-up(lg) {
display: inline-block;
}
@include media-breakpoint-up(lg) {
display: inline-block;
}
}
// element is needed here to override font-awesome specificity
i.navbar-page-btn__folder-icon {
font-size: $font-size-sm;
color: $text-color-weak;
padding: 0 $space-sm;
position: relative;
top: -1px;
}
// element is needed here to override font-awesome specificity
i.navbar-page-btn__search {
font-size: $font-size-xs;
padding: 0 $space-xs;
}
.navbar-buttons {
// height: $navbarHeight;
display: flex;

View File

@@ -13,18 +13,22 @@ $marker-size-half: ($marker-size / 2);
$path-height: 2px !default;
$path-position: $marker-size-half - ($path-height / 2);
.dashlist-cta-close-btn {
.progress-tracker-container {
height: 100%;
display: flex;
align-items: center;
}
.progress-tracker-close-btn {
color: $text-color-weak;
float: right;
padding: 0;
margin: 0 2px 0 0;
position: absolute;
z-index: $panel-header-z-index;
top: 8px;
right: 8px;
font-size: $font-size-lg;
background-color: transparent;
border: none;
i {
font-size: 80%;
}
&:hover {
color: $white;
}
@@ -33,9 +37,9 @@ $path-position: $marker-size-half - ($path-height / 2);
// Container element
.progress-tracker {
display: flex;
margin: 0 auto;
width: 100%;
padding: 0;
list-style: none;
align-items: center;
}
// Step container that creates lines between steps
@@ -46,6 +50,7 @@ $path-position: $marker-size-half - ($path-height / 2);
margin: 0;
padding: 0;
color: $text-color-weak;
height: 84px;
// For a flexbox bug in firefox that wont allow the text overflow on the text
min-width: $marker-size;
@@ -54,7 +59,7 @@ $path-position: $marker-size-half - ($path-height / 2);
content: '';
display: block;
position: absolute;
z-index: 1;
z-index: 0;
top: $path-position;
bottom: $path-position;
right: -$marker-size-half;
@@ -134,11 +139,10 @@ $path-position: $marker-size-half - ($path-height / 2);
width: $marker-size;
height: $marker-size;
padding-bottom: 2px; // To align text within the marker
z-index: 20;
z-index: 1;
background-color: $panel-bg;
margin-left: auto;
margin-right: auto;
margin-bottom: $spacer;
color: $text-color-weak;
font-size: 35px;
vertical-align: sub;

View File

@@ -126,6 +126,7 @@ $panel-header-no-title-zindex: 1;
left: 0;
width: $panel-header-height;
height: $panel-header-height;
z-index: $panel-header-no-title-zindex + 1;
top: 0;
.fa {
@@ -138,7 +139,8 @@ $panel-header-no-title-zindex: 1;
&--info {
display: block;
@include panel-corner-color(lighten($panel-corner, 4%));
@include panel-corner-color(lighten($panel-corner, 6%));
.fa:before {
content: '\f129';
}
@@ -146,7 +148,7 @@ $panel-header-no-title-zindex: 1;
&--links {
display: block;
@include panel-corner-color(lighten($panel-corner, 4%));
@include panel-corner-color(lighten($panel-corner, 6%));
.fa {
left: 4px;
}

View File

@@ -6,16 +6,17 @@
}
.singlestat-panel-value-container {
line-height: 1;
// line-height 0 is imporant here as the font-size is on this
// level but overriden one level deeper and but the line-height: is still
// based on the base font size on this level. Using line-height: 0 fixes that
line-height: 0;
display: table-cell;
vertical-align: middle;
text-align: center;
position: relative;
z-index: 1;
font-size: 3em;
font-weight: $font-weight-semi-bold;
// helps make the title feel more centered when there is a panel title
padding-bottom: $panel-padding;
font-size: 38px;
}
// Helps

View File

@@ -123,7 +123,7 @@
}
.table-panel-table-header-inner {
padding: 0.45em 0 0.45em 1.1em;
padding: 0.3em 0 0.45em 1.1em;
text-align: left;
color: $blue;
position: absolute;

View File

@@ -31,6 +31,7 @@
display: flex;
flex-direction: column;
flex-grow: 1;
height: 100%; // Chrome 74 needs this to make the element scrollable
.search-item--indent {
margin-left: 14px;
@@ -258,10 +259,6 @@
align-items: flex-start;
}
.search-dropdown__col_1 {
height: 100%;
}
.search-filter-box {
margin: 0;
}

View File

@@ -16,8 +16,7 @@
}
}
.navbar-button--zoom,
.navbar-button--refresh {
.navbar-button--zoom {
display: none;
}
}

View File

@@ -32,7 +32,7 @@
.panel-alert-icon:before {
content: '\e611';
position: relative;
top: 5px;
top: 1px;
left: -3px;
}
}

View File

@@ -24,5 +24,11 @@ python3 generator/build.py "$@"
chmod a+x /tmp/scratch/*.msi
echo "MSI: Copy to $WORKING_DIRECTORY/dist"
cp /tmp/scratch/*.msi $WORKING_DIRECTORY/dist
echo "MSI: Generate SHA256"
MSI_FILE=`ls $WORKING_DIRECTORY/dist/*.msi`
SHA256SUM=`sha256sum $MSI_FILE | cut -f1 -d' '`
echo $SHA256SUM > $MSI_FILE.sha256
echo "MSI: SHA256 file content:"
cat $MSI_FILE.sha256
echo "MSI: contents of $WORKING_DIRECTORY/dist"
ls -al $WORKING_DIRECTORY/dist

View File

@@ -6,8 +6,8 @@ EXTRA_OPTS="$@"
# Right now we hack this in into the publish script.
# Eventually we might want to keep a list of all previous releases somewhere.
_releaseNoteUrl="https://community.grafana.com/t/release-notes-v6-0-x/14010"
_whatsNewUrl="http://docs.grafana.org/guides/whats-new-in-v6-0/"
_releaseNoteUrl="https://community.grafana.com/t/release-notes-v6-2-x/17037"
_whatsNewUrl="https://grafana.com/docs/guides/whats-new-in-v6-2/"
./scripts/build/release_publisher/release_publisher \
--wn ${_whatsNewUrl} \

View File

@@ -174,6 +174,11 @@ var completeBuildArtifactConfigurations = []buildArtifact{
arch: "amd64",
urlPostfix: ".windows-amd64.zip",
},
{
os: "win-installer",
arch: "amd64",
urlPostfix: ".windows-amd64.msi",
},
}
type artifactFilter struct {

View File

@@ -74,6 +74,34 @@ func TestPreparingReleaseFromRemote(t *testing.T) {
baseArchiveURL: "https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana",
buildArtifacts: []buildArtifact{{"linux", "armv6", "_armhf.deb", "-rpi"}},
},
{
version: "v5.4.0-pre1asdf",
expectedVersion: "5.4.0-pre1asdf",
whatsNewURL: "https://whatsnews.foo/",
relNotesURL: "https://relnotes.foo/",
nightly: true,
expectedBeta: false,
expectedStable: false,
expectedArch: "amd64",
expectedOs: "win-installer",
expectedURL: "https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.4.0-pre1asdf.windows-amd64.msi",
baseArchiveURL: "https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana",
buildArtifacts: []buildArtifact{{"win-installer", "amd64", ".windows-amd64.msi", ""}},
},
{
version: "v5.4.0-pre1asdf",
expectedVersion: "5.4.0-pre1asdf",
whatsNewURL: "https://whatsnews.foo/",
relNotesURL: "https://relnotes.foo/",
nightly: true,
expectedBeta: false,
expectedStable: false,
expectedArch: "amd64",
expectedOs: "win",
expectedURL: "https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-5.4.0-pre1asdf.windows-amd64.zip",
baseArchiveURL: "https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana",
buildArtifacts: []buildArtifact{{"win", "amd64", ".windows-amd64.zip", ""}},
},
}
for _, test := range cases {

View File

@@ -0,0 +1 @@
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

View File

@@ -16,22 +16,32 @@ const cherryPickRunner: TaskRunner<CherryPickOptions> = async () => {
},
});
// sort by closed date
// sort by closed date ASC
res.data.sort(function(a, b) {
return new Date(b.closed_at).getTime() - new Date(a.closed_at).getTime();
return new Date(a.closed_at).getTime() - new Date(b.closed_at).getTime();
});
let commands = '';
console.log('--------------------------------------------------------------------');
console.log('Printing PRs with cherry-pick-needed, in ASC merge date order');
console.log('--------------------------------------------------------------------');
for (const item of res.data) {
if (!item.milestone) {
console.log(item.number + ' missing milestone!');
continue;
}
console.log(`${item.title} (${item.number}) closed_at ${item.closed_at}`);
console.log(`\tURL: ${item.closed_at} ${item.html_url}`);
const issueDetails = await client.get(item.pull_request.url);
console.log(`\tMerge sha: ${issueDetails.data.merge_commit_sha}`);
console.log(`* ${item.title}, (#${item.number}), merge-sha: ${issueDetails.data.merge_commit_sha}`);
commands += `git cherry-pick -x ${issueDetails.data.merge_commit_sha}\n`;
}
console.log('--------------------------------------------------------------------');
console.log('Commands (in order of how they should be executed)');
console.log('--------------------------------------------------------------------');
console.log(commands);
};
export const cherryPickTask = new Task<CherryPickOptions>();