mirror of
https://github.com/grafana/grafana.git
synced 2025-12-21 20:24:41 +08:00
Compare commits
56 Commits
docs/add-d
...
v6.2.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
07540dfdce | ||
|
|
8b86d5d1ed | ||
|
|
f26831c8a9 | ||
|
|
6a32c29b92 | ||
|
|
80475fa13a | ||
|
|
2b400ac38e | ||
|
|
c3e5e69e73 | ||
|
|
9e40b07fd7 | ||
|
|
99472f9465 | ||
|
|
5169765db3 | ||
|
|
7b0efb787b | ||
|
|
efd14ac7db | ||
|
|
b0647a4537 | ||
|
|
1f0e94b633 | ||
|
|
eda28131ef | ||
|
|
d385a668a9 | ||
|
|
ef359424a5 | ||
|
|
dce4514f7f | ||
|
|
ad7bd8850f | ||
|
|
5d16da732f | ||
|
|
ee6703fedd | ||
|
|
ec8be5bf61 | ||
|
|
f3c2d03637 | ||
|
|
eb2b31007d | ||
|
|
98f9dbde95 | ||
|
|
7e090ea2a6 | ||
|
|
6a02faeeab | ||
|
|
a6a939cfd3 | ||
|
|
5a10298bd3 | ||
|
|
f52f7c101c | ||
|
|
7824f66cd3 | ||
|
|
71c1e8a731 | ||
|
|
74bc94bcec | ||
|
|
f566da0ff6 | ||
|
|
216aff96fd | ||
|
|
9de11c25a6 | ||
|
|
599e1030d8 | ||
|
|
d62da61d8a | ||
|
|
a2ca973925 | ||
|
|
98da29fd7b | ||
|
|
888ff61d30 | ||
|
|
c1be3adf3b | ||
|
|
ede9d9964d | ||
|
|
5cd69e8d39 | ||
|
|
ceb3672482 | ||
|
|
e519a9d2c4 | ||
|
|
2a3d6604c0 | ||
|
|
9cf0ea5395 | ||
|
|
db37e138bf | ||
|
|
a5a6d43f47 | ||
|
|
d9950aa4f1 | ||
|
|
6e9a395063 | ||
|
|
7374aafb90 | ||
|
|
b54e9880b4 | ||
|
|
09672a287f | ||
|
|
9d877d670e |
@@ -81,7 +81,7 @@ jobs:
|
|||||||
- run:
|
- run:
|
||||||
# Important: all words have to be in lowercase, and separated by "\n".
|
# Important: all words have to be in lowercase, and separated by "\n".
|
||||||
name: exclude known exceptions
|
name: exclude known exceptions
|
||||||
command: 'echo -e "unknwon" > words_to_ignore.txt'
|
command: 'echo -e "unknwon\nreferer\nerrorstring" > words_to_ignore.txt'
|
||||||
- run:
|
- run:
|
||||||
name: check documentation spelling errors
|
name: check documentation spelling errors
|
||||||
command: 'codespell -I ./words_to_ignore.txt docs/'
|
command: 'codespell -I ./words_to_ignore.txt docs/'
|
||||||
@@ -566,6 +566,7 @@ jobs:
|
|||||||
root: .
|
root: .
|
||||||
paths:
|
paths:
|
||||||
- dist/grafana-*.msi
|
- dist/grafana-*.msi
|
||||||
|
- dist/grafana-*.msi.sha256
|
||||||
|
|
||||||
store-build-artifacts:
|
store-build-artifacts:
|
||||||
docker:
|
docker:
|
||||||
@@ -700,7 +701,7 @@ workflows:
|
|||||||
- backend-lint
|
- backend-lint
|
||||||
- mysql-integration-test
|
- mysql-integration-test
|
||||||
- postgres-integration-test
|
- postgres-integration-test
|
||||||
filters: *filter-only-master
|
filters: *filter-only-release
|
||||||
|
|
||||||
build-branches-and-prs:
|
build-branches-and-prs:
|
||||||
jobs:
|
jobs:
|
||||||
|
|||||||
@@ -66,8 +66,8 @@ RUN mkdir -p "$GF_PATHS_HOME/.aws" && \
|
|||||||
"$GF_PATHS_DATA" && \
|
"$GF_PATHS_DATA" && \
|
||||||
cp "$GF_PATHS_HOME/conf/sample.ini" "$GF_PATHS_CONFIG" && \
|
cp "$GF_PATHS_HOME/conf/sample.ini" "$GF_PATHS_CONFIG" && \
|
||||||
cp "$GF_PATHS_HOME/conf/ldap.toml" /etc/grafana/ldap.toml && \
|
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" && \
|
chown -R grafana:grafana "$GF_PATHS_DATA" "$GF_PATHS_HOME/.aws" "$GF_PATHS_LOGS" "$GF_PATHS_PLUGINS" "$GF_PATHS_PROVISIONING" && \
|
||||||
chmod 777 "$GF_PATHS_DATA" "$GF_PATHS_HOME/.aws" "$GF_PATHS_LOGS" "$GF_PATHS_PLUGINS"
|
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=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
|
COPY --from=1 /usr/src/app/public ./public
|
||||||
|
|||||||
1337
devenv/dev-dashboards/datasource-testdata/new_features_in_v62.json
Normal file
1337
devenv/dev-dashboards/datasource-testdata/new_features_in_v62.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -48,28 +48,6 @@
|
|||||||
"y": 0
|
"y": 0
|
||||||
},
|
},
|
||||||
"headings": false,
|
"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,
|
"id": 2,
|
||||||
"limit": 1000,
|
"limit": 1000,
|
||||||
"links": [],
|
"links": [],
|
||||||
@@ -83,6 +61,28 @@
|
|||||||
"title": "tag: panel-tests",
|
"title": "tag: panel-tests",
|
||||||
"type": "dashlist"
|
"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,
|
"folderId": null,
|
||||||
"gridPos": {
|
"gridPos": {
|
||||||
@@ -114,28 +114,6 @@
|
|||||||
"y": 13
|
"y": 13
|
||||||
},
|
},
|
||||||
"headings": false,
|
"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,
|
"id": 4,
|
||||||
"limit": 1000,
|
"limit": 1000,
|
||||||
"links": [],
|
"links": [],
|
||||||
@@ -146,7 +124,7 @@
|
|||||||
"tags": ["templating", "gdev"],
|
"tags": ["templating", "gdev"],
|
||||||
"timeFrom": null,
|
"timeFrom": null,
|
||||||
"timeShift": null,
|
"timeShift": null,
|
||||||
"title": "tag: templating",
|
"title": "tag: templating ",
|
||||||
"type": "dashlist"
|
"type": "dashlist"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -167,5 +145,5 @@
|
|||||||
"timezone": "",
|
"timezone": "",
|
||||||
"title": "Grafana Dev Overview & Home",
|
"title": "Grafana Dev Overview & Home",
|
||||||
"uid": "j6T00KRZz",
|
"uid": "j6T00KRZz",
|
||||||
"version": 1
|
"version": 2
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,26 +15,27 @@
|
|||||||
"editable": true,
|
"editable": true,
|
||||||
"gnetId": null,
|
"gnetId": null,
|
||||||
"graphTooltip": 0,
|
"graphTooltip": 0,
|
||||||
|
"id": 7501,
|
||||||
"links": [],
|
"links": [],
|
||||||
"panels": [
|
"panels": [
|
||||||
{
|
{
|
||||||
|
"datasource": "gdev-testdata",
|
||||||
"gridPos": {
|
"gridPos": {
|
||||||
"h": 7,
|
"h": 7,
|
||||||
"w": 18,
|
"w": 24,
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 0
|
"y": 0
|
||||||
},
|
},
|
||||||
"id": 7,
|
"id": 2,
|
||||||
"links": [],
|
"links": [],
|
||||||
"options": {
|
"options": {
|
||||||
"displayMode": "gradient",
|
"displayMode": "lcd",
|
||||||
"fieldOptions": {
|
"fieldOptions": {
|
||||||
"calcs": ["mean"],
|
"calcs": ["mean"],
|
||||||
"defaults": {
|
"defaults": {
|
||||||
"decimals": null,
|
|
||||||
"max": 100,
|
"max": 100,
|
||||||
"min": 0,
|
"min": 0,
|
||||||
"unit": "watt"
|
"unit": "decgbytes"
|
||||||
},
|
},
|
||||||
"mappings": [],
|
"mappings": [],
|
||||||
"override": {},
|
"override": {},
|
||||||
@@ -47,7 +48,7 @@
|
|||||||
{
|
{
|
||||||
"color": "orange",
|
"color": "orange",
|
||||||
"index": 1,
|
"index": 1,
|
||||||
"value": 40
|
"value": 60
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"color": "red",
|
"color": "red",
|
||||||
@@ -59,95 +60,188 @@
|
|||||||
},
|
},
|
||||||
"orientation": "vertical"
|
"orientation": "vertical"
|
||||||
},
|
},
|
||||||
"pluginVersion": "6.2.0-pre",
|
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
|
"alias": "sda1",
|
||||||
"refId": "A",
|
"refId": "A",
|
||||||
"scenarioId": "random_walk"
|
"scenarioId": "random_walk"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"alias": "sda2",
|
||||||
"refId": "B",
|
"refId": "B",
|
||||||
"scenarioId": "random_walk"
|
"scenarioId": "random_walk"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"alias": "sda3",
|
||||||
"refId": "C",
|
"refId": "C",
|
||||||
"scenarioId": "random_walk"
|
"scenarioId": "random_walk"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"alias": "sda4",
|
||||||
"refId": "D",
|
"refId": "D",
|
||||||
"scenarioId": "random_walk"
|
"scenarioId": "random_walk"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"alias": "sda5",
|
||||||
"refId": "E",
|
"refId": "E",
|
||||||
"scenarioId": "csv_metric_values",
|
"scenarioId": "random_walk"
|
||||||
"stringInput": "10003,33333"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"alias": "sda6",
|
||||||
"refId": "F",
|
"refId": "F",
|
||||||
"scenarioId": "random_walk"
|
"scenarioId": "random_walk"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"alias": "sda7",
|
||||||
"refId": "G",
|
"refId": "G",
|
||||||
"scenarioId": "random_walk"
|
"scenarioId": "random_walk"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"alias": "sda8",
|
||||||
"refId": "H",
|
"refId": "H",
|
||||||
"scenarioId": "csv_metric_values",
|
"scenarioId": "random_walk"
|
||||||
"stringInput": "100,100,100"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"alias": "sda9",
|
||||||
"refId": "I",
|
"refId": "I",
|
||||||
"scenarioId": "random_walk"
|
"scenarioId": "random_walk"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"alias": "sda10",
|
||||||
"refId": "J",
|
"refId": "J",
|
||||||
"scenarioId": "random_walk"
|
"scenarioId": "random_walk"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"alias": "sda11",
|
||||||
"refId": "K",
|
"refId": "K",
|
||||||
"scenarioId": "random_walk"
|
"scenarioId": "random_walk"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"alias": "sda12",
|
||||||
"refId": "L",
|
"refId": "L",
|
||||||
"scenarioId": "random_walk"
|
"scenarioId": "random_walk"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"alias": "sda13",
|
||||||
"refId": "M",
|
"refId": "M",
|
||||||
"scenarioId": "random_walk"
|
"scenarioId": "random_walk"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"alias": "sda14",
|
||||||
"refId": "N",
|
"refId": "N",
|
||||||
"scenarioId": "random_walk"
|
"scenarioId": "random_walk"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"alias": "sda15",
|
||||||
"refId": "O",
|
"refId": "O",
|
||||||
"scenarioId": "random_walk"
|
"scenarioId": "random_walk"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"alias": "sda16",
|
||||||
"refId": "P",
|
"refId": "P",
|
||||||
"scenarioId": "random_walk"
|
"scenarioId": "random_walk"
|
||||||
},
|
|
||||||
{
|
|
||||||
"refId": "Q",
|
|
||||||
"scenarioId": "random_walk"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"timeFrom": null,
|
"timeFrom": null,
|
||||||
"timeShift": null,
|
"timeShift": null,
|
||||||
"title": "Usage",
|
"title": "",
|
||||||
|
"transparent": true,
|
||||||
"type": "bargauge"
|
"type": "bargauge"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"datasource": "gdev-testdata",
|
||||||
"gridPos": {
|
"gridPos": {
|
||||||
"h": 22,
|
"h": 10,
|
||||||
"w": 6,
|
"w": 16,
|
||||||
"x": 18,
|
"x": 0,
|
||||||
"y": 0
|
"y": 7
|
||||||
},
|
},
|
||||||
"id": 8,
|
"id": 4,
|
||||||
"links": [],
|
"links": [],
|
||||||
"options": {
|
"options": {
|
||||||
"displayMode": "gradient",
|
"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": {
|
"fieldOptions": {
|
||||||
"calcs": ["mean"],
|
"calcs": ["mean"],
|
||||||
"defaults": {
|
"defaults": {
|
||||||
@@ -160,19 +254,24 @@
|
|||||||
"override": {},
|
"override": {},
|
||||||
"thresholds": [
|
"thresholds": [
|
||||||
{
|
{
|
||||||
"color": "green",
|
"color": "blue",
|
||||||
"index": 0,
|
"index": 0,
|
||||||
"value": null
|
"value": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"color": "orange",
|
"color": "green",
|
||||||
"index": 1,
|
"index": 1,
|
||||||
"value": 55
|
"value": 42.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "orange",
|
||||||
|
"index": 2,
|
||||||
|
"value": 80
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"color": "red",
|
"color": "red",
|
||||||
"index": 2,
|
"index": 3,
|
||||||
"value": 95
|
"value": 90
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"values": false
|
"values": false
|
||||||
@@ -181,10 +280,6 @@
|
|||||||
},
|
},
|
||||||
"pluginVersion": "6.2.0-pre",
|
"pluginVersion": "6.2.0-pre",
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
|
||||||
"refId": "E",
|
|
||||||
"scenarioId": "random_walk"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"refId": "H",
|
"refId": "H",
|
||||||
"scenarioId": "csv_metric_values",
|
"scenarioId": "csv_metric_values",
|
||||||
@@ -194,22 +289,6 @@
|
|||||||
"refId": "A",
|
"refId": "A",
|
||||||
"scenarioId": "random_walk"
|
"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",
|
"refId": "J",
|
||||||
"scenarioId": "random_walk"
|
"scenarioId": "random_walk"
|
||||||
@@ -241,47 +320,78 @@
|
|||||||
{
|
{
|
||||||
"refId": "Q",
|
"refId": "Q",
|
||||||
"scenarioId": "random_walk"
|
"scenarioId": "random_walk"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"timeFrom": null,
|
||||||
|
"timeShift": null,
|
||||||
|
"title": "Basic",
|
||||||
|
"type": "bargauge"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"refId": "F",
|
"datasource": "gdev-testdata",
|
||||||
"scenarioId": "random_walk"
|
"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
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"refId": "G",
|
"color": "red",
|
||||||
"scenarioId": "random_walk"
|
"index": 1,
|
||||||
|
"value": 90
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"values": false
|
||||||
},
|
},
|
||||||
{
|
"orientation": "vertical"
|
||||||
"refId": "R",
|
|
||||||
"scenarioId": "random_walk"
|
|
||||||
},
|
},
|
||||||
|
"targets": [
|
||||||
{
|
{
|
||||||
"refId": "S",
|
"refId": "A",
|
||||||
"scenarioId": "random_walk"
|
"scenarioId": "random_walk"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"timeFrom": null,
|
"timeFrom": null,
|
||||||
"timeShift": null,
|
"timeShift": null,
|
||||||
"title": "Usage",
|
"title": "Completion",
|
||||||
"type": "bargauge"
|
"type": "bargauge"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"datasource": "gdev-testdata",
|
||||||
"gridPos": {
|
"gridPos": {
|
||||||
"h": 15,
|
"h": 12,
|
||||||
"w": 11,
|
"w": 22,
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 7
|
"y": 17
|
||||||
},
|
},
|
||||||
"id": 6,
|
"id": 10,
|
||||||
"links": [],
|
"links": [],
|
||||||
"options": {
|
"options": {
|
||||||
"displayMode": "gradient",
|
"displayMode": "gradient",
|
||||||
"fieldOptions": {
|
"fieldOptions": {
|
||||||
"calcs": ["mean"],
|
"calcs": ["mean"],
|
||||||
"defaults": {
|
"defaults": {
|
||||||
"decimals": null,
|
|
||||||
"max": 100,
|
"max": 100,
|
||||||
"min": 0,
|
"min": 0,
|
||||||
"unit": "celsius"
|
"unit": "decgbytes"
|
||||||
},
|
},
|
||||||
"mappings": [],
|
"mappings": [],
|
||||||
"override": {},
|
"override": {},
|
||||||
@@ -294,12 +404,12 @@
|
|||||||
{
|
{
|
||||||
"color": "green",
|
"color": "green",
|
||||||
"index": 1,
|
"index": 1,
|
||||||
"value": 20
|
"value": 30
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"color": "orange",
|
"color": "orange",
|
||||||
"index": 2,
|
"index": 2,
|
||||||
"value": 40
|
"value": 60
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"color": "red",
|
"color": "red",
|
||||||
@@ -309,69 +419,113 @@
|
|||||||
],
|
],
|
||||||
"values": false
|
"values": false
|
||||||
},
|
},
|
||||||
"orientation": "horizontal"
|
"orientation": "vertical"
|
||||||
},
|
},
|
||||||
"pluginVersion": "6.2.0-pre",
|
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"alias": "Inside",
|
"alias": "sda1",
|
||||||
"refId": "H",
|
|
||||||
"scenarioId": "csv_metric_values",
|
|
||||||
"stringInput": "100,100,100"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"alias": "Outhouse",
|
|
||||||
"refId": "A",
|
"refId": "A",
|
||||||
"scenarioId": "random_walk"
|
"scenarioId": "random_walk"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"alias": "Area B",
|
"alias": "sda2",
|
||||||
"refId": "B",
|
"refId": "B",
|
||||||
"scenarioId": "random_walk"
|
"scenarioId": "random_walk"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"alias": "Basement",
|
"alias": "sda3",
|
||||||
"refId": "C",
|
"refId": "C",
|
||||||
"scenarioId": "random_walk"
|
"scenarioId": "random_walk"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"alias": "Garage",
|
"alias": "sda4",
|
||||||
"refId": "D",
|
"refId": "D",
|
||||||
"scenarioId": "random_walk"
|
"scenarioId": "random_walk"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"alias": "Attic",
|
"alias": "sda5",
|
||||||
"refId": "E",
|
"refId": "E",
|
||||||
"scenarioId": "random_walk"
|
"scenarioId": "random_walk"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"alias": "sda6",
|
||||||
"refId": "F",
|
"refId": "F",
|
||||||
"scenarioId": "random_walk"
|
"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,
|
"timeFrom": null,
|
||||||
"timeShift": null,
|
"timeShift": null,
|
||||||
"title": "Temperature",
|
"title": "",
|
||||||
"type": "bargauge"
|
"type": "bargauge"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"datasource": "gdev-testdata",
|
||||||
"gridPos": {
|
"gridPos": {
|
||||||
"h": 15,
|
"h": 8,
|
||||||
"w": 7,
|
"w": 24,
|
||||||
"x": 11,
|
"x": 0,
|
||||||
"y": 7
|
"y": 29
|
||||||
},
|
},
|
||||||
"id": 9,
|
"id": 11,
|
||||||
"links": [],
|
"links": [],
|
||||||
"options": {
|
"options": {
|
||||||
"displayMode": "basic",
|
"displayMode": "basic",
|
||||||
"fieldOptions": {
|
"fieldOptions": {
|
||||||
"calcs": ["mean"],
|
"calcs": ["mean"],
|
||||||
"defaults": {
|
"defaults": {
|
||||||
"decimals": null,
|
|
||||||
"max": 100,
|
"max": 100,
|
||||||
"min": 0,
|
"min": 0,
|
||||||
"unit": "celsius"
|
"unit": "decgbytes"
|
||||||
},
|
},
|
||||||
"mappings": [],
|
"mappings": [],
|
||||||
"override": {},
|
"override": {},
|
||||||
@@ -384,12 +538,12 @@
|
|||||||
{
|
{
|
||||||
"color": "green",
|
"color": "green",
|
||||||
"index": 1,
|
"index": 1,
|
||||||
"value": 20
|
"value": 30
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"color": "orange",
|
"color": "orange",
|
||||||
"index": 2,
|
"index": 2,
|
||||||
"value": 40
|
"value": 60
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"color": "red",
|
"color": "red",
|
||||||
@@ -399,89 +553,113 @@
|
|||||||
],
|
],
|
||||||
"values": false
|
"values": false
|
||||||
},
|
},
|
||||||
"orientation": "horizontal"
|
"orientation": "vertical"
|
||||||
},
|
},
|
||||||
"pluginVersion": "6.2.0-pre",
|
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"alias": "Inside",
|
"alias": "sda1",
|
||||||
"refId": "H",
|
|
||||||
"scenarioId": "csv_metric_values",
|
|
||||||
"stringInput": "100,100,100"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"alias": "Outhouse",
|
|
||||||
"refId": "A",
|
"refId": "A",
|
||||||
"scenarioId": "random_walk"
|
"scenarioId": "random_walk"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"alias": "Area B",
|
"alias": "sda2",
|
||||||
"refId": "B",
|
"refId": "B",
|
||||||
"scenarioId": "random_walk"
|
"scenarioId": "random_walk"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"alias": "Basement",
|
"alias": "sda3",
|
||||||
"refId": "C",
|
"refId": "C",
|
||||||
"scenarioId": "random_walk"
|
"scenarioId": "random_walk"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"alias": "Garage",
|
"alias": "sda4",
|
||||||
"refId": "D",
|
"refId": "D",
|
||||||
"scenarioId": "random_walk"
|
"scenarioId": "random_walk"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"alias": "Attic",
|
"alias": "sda5",
|
||||||
"refId": "E",
|
"refId": "E",
|
||||||
"scenarioId": "random_walk"
|
"scenarioId": "random_walk"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"alias": "sda6",
|
||||||
"refId": "F",
|
"refId": "F",
|
||||||
"scenarioId": "random_walk"
|
"scenarioId": "random_walk"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"alias": "sda7",
|
||||||
"refId": "G",
|
"refId": "G",
|
||||||
"scenarioId": "random_walk"
|
"scenarioId": "random_walk"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"alias": "sda8",
|
||||||
|
"refId": "H",
|
||||||
|
"scenarioId": "random_walk"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": "sda9",
|
||||||
"refId": "I",
|
"refId": "I",
|
||||||
"scenarioId": "random_walk"
|
"scenarioId": "random_walk"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"alias": "sda10",
|
||||||
"refId": "J",
|
"refId": "J",
|
||||||
"scenarioId": "random_walk"
|
"scenarioId": "random_walk"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"alias": "sda11",
|
||||||
"refId": "K",
|
"refId": "K",
|
||||||
"scenarioId": "random_walk"
|
"scenarioId": "random_walk"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"alias": "sda12",
|
||||||
"refId": "L",
|
"refId": "L",
|
||||||
"scenarioId": "random_walk"
|
"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,
|
"timeFrom": null,
|
||||||
"timeShift": null,
|
"timeShift": null,
|
||||||
"title": "Temperature",
|
"title": "",
|
||||||
"type": "bargauge"
|
"type": "bargauge"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"refresh": false,
|
"refresh": "10s",
|
||||||
"schemaVersion": 18,
|
"schemaVersion": 18,
|
||||||
"style": "dark",
|
"style": "dark",
|
||||||
"tags": ["gdev", "bargauge", "panel-demo"],
|
"tags": ["gdev", "demo"],
|
||||||
"templating": {
|
"templating": {
|
||||||
"list": []
|
"list": []
|
||||||
},
|
},
|
||||||
"time": {
|
"time": {
|
||||||
"from": "now-30m",
|
"from": "now-6h",
|
||||||
"to": "now"
|
"to": "now"
|
||||||
},
|
},
|
||||||
"timepicker": {
|
"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"]
|
"time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"]
|
||||||
},
|
},
|
||||||
"timezone": "",
|
"timezone": "",
|
||||||
"title": "Bar Gauge Animated Demo",
|
"title": "Bar Gauge Demo",
|
||||||
"uid": "k5IUwQeikaa",
|
"uid": "vmie2cmWz",
|
||||||
"version": 1
|
"version": 3
|
||||||
}
|
}
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
829
devenv/dev-dashboards/panel-bargauge/panel_tests_bar_gauge.json
Normal file
829
devenv/dev-dashboards/panel-bargauge/panel_tests_bar_gauge.json
Normal 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
|
||||||
|
}
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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.
|
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
|
## 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.
|
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.
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
"company": "Grafana Labs"
|
"company": "Grafana Labs"
|
||||||
},
|
},
|
||||||
"name": "grafana",
|
"name": "grafana",
|
||||||
"version": "6.2.0-pre",
|
"version": "6.2.2",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "http://github.com/grafana/grafana.git"
|
"url": "http://github.com/grafana/grafana.git"
|
||||||
@@ -191,7 +191,7 @@
|
|||||||
"eventemitter3": "2.0.3",
|
"eventemitter3": "2.0.3",
|
||||||
"file-saver": "1.3.8",
|
"file-saver": "1.3.8",
|
||||||
"immutable": "3.8.2",
|
"immutable": "3.8.2",
|
||||||
"jquery": "3.4.0",
|
"jquery": "3.4.1",
|
||||||
"lodash": "4.17.11",
|
"lodash": "4.17.11",
|
||||||
"moment": "2.24.0",
|
"moment": "2.24.0",
|
||||||
"mousetrap": "1.6.3",
|
"mousetrap": "1.6.3",
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
"@types/react-color": "2.17.0",
|
"@types/react-color": "2.17.0",
|
||||||
"classnames": "2.2.6",
|
"classnames": "2.2.6",
|
||||||
"d3": "5.9.1",
|
"d3": "5.9.1",
|
||||||
"jquery": "3.4.0",
|
"jquery": "3.4.1",
|
||||||
"lodash": "4.17.11",
|
"lodash": "4.17.11",
|
||||||
"moment": "2.24.0",
|
"moment": "2.24.0",
|
||||||
"papaparse": "4.6.3",
|
"papaparse": "4.6.3",
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { shallow } from 'enzyme';
|
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 { VizOrientation, DisplayValue } from '../../types';
|
||||||
import { getTheme } from '../../themes';
|
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', () => {
|
describe('Vertical bar without title', () => {
|
||||||
it('should not include title height in height', () => {
|
it('should not include title height in height', () => {
|
||||||
const props = getProps({
|
const props = getProps({
|
||||||
|
|||||||
@@ -11,8 +11,9 @@ import { DisplayValue, Themeable, TimeSeriesValue, Threshold, VizOrientation } f
|
|||||||
const MIN_VALUE_HEIGHT = 18;
|
const MIN_VALUE_HEIGHT = 18;
|
||||||
const MAX_VALUE_HEIGHT = 50;
|
const MAX_VALUE_HEIGHT = 50;
|
||||||
const MIN_VALUE_WIDTH = 50;
|
const MIN_VALUE_WIDTH = 50;
|
||||||
const MAX_VALUE_WIDTH = 100;
|
const MAX_VALUE_WIDTH = 150;
|
||||||
const LINE_HEIGHT = 1.5;
|
const TITLE_LINE_HEIGHT = 1.5;
|
||||||
|
const VALUE_LINE_HEIGHT = 1;
|
||||||
|
|
||||||
export interface Props extends Themeable {
|
export interface Props extends Themeable {
|
||||||
height: number;
|
height: number;
|
||||||
@@ -161,7 +162,7 @@ export class BarGauge extends PureComponent<Props> {
|
|||||||
const cells: JSX.Element[] = [];
|
const cells: JSX.Element[] = [];
|
||||||
|
|
||||||
for (let i = 0; i < cellCount; i++) {
|
for (let i = 0; i < cellCount; i++) {
|
||||||
const currentValue = (valueRange / cellCount) * i;
|
const currentValue = minValue + (valueRange / cellCount) * i;
|
||||||
const cellColor = this.getCellColor(currentValue);
|
const cellColor = this.getCellColor(currentValue);
|
||||||
const cellStyles: CSSProperties = {
|
const cellStyles: CSSProperties = {
|
||||||
borderRadius: '2px',
|
borderRadius: '2px',
|
||||||
@@ -227,7 +228,7 @@ function calculateTitleDimensions(props: Props): TitleDimensions {
|
|||||||
return {
|
return {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
width: width,
|
width: width,
|
||||||
height: 14 * LINE_HEIGHT,
|
height: 14 * TITLE_LINE_HEIGHT,
|
||||||
placement: 'below',
|
placement: 'below',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -238,7 +239,7 @@ function calculateTitleDimensions(props: Props): TitleDimensions {
|
|||||||
const titleHeight = Math.max(Math.min(height * maxTitleHeightRatio, MAX_VALUE_HEIGHT), 17);
|
const titleHeight = Math.max(Math.min(height * maxTitleHeightRatio, MAX_VALUE_HEIGHT), 17);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
fontSize: titleHeight / LINE_HEIGHT,
|
fontSize: titleHeight / TITLE_LINE_HEIGHT,
|
||||||
width: 0,
|
width: 0,
|
||||||
height: titleHeight,
|
height: titleHeight,
|
||||||
placement: 'above',
|
placement: 'above',
|
||||||
@@ -251,7 +252,7 @@ function calculateTitleDimensions(props: Props): TitleDimensions {
|
|||||||
const titleHeight = Math.max(height * maxTitleHeightRatio, MIN_VALUE_HEIGHT);
|
const titleHeight = Math.max(height * maxTitleHeightRatio, MIN_VALUE_HEIGHT);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
fontSize: titleHeight / LINE_HEIGHT,
|
fontSize: titleHeight / TITLE_LINE_HEIGHT,
|
||||||
height: 0,
|
height: 0,
|
||||||
width: Math.min(Math.max(width * maxTitleWidthRatio, 50), 200),
|
width: Math.min(Math.max(width * maxTitleWidthRatio, 50), 200),
|
||||||
placement: 'left',
|
placement: 'left',
|
||||||
@@ -345,11 +346,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 {
|
return {
|
||||||
valueWidth,
|
valueWidth,
|
||||||
valueHeight,
|
valueHeight,
|
||||||
@@ -360,6 +356,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
|
* Only exported to for unit test
|
||||||
*/
|
*/
|
||||||
@@ -367,7 +367,7 @@ export function getBasicAndGradientStyles(props: Props): BasicAndGradientStyles
|
|||||||
const { displayMode, maxValue, minValue, value } = props;
|
const { displayMode, maxValue, minValue, value } = props;
|
||||||
const { valueWidth, valueHeight, maxBarHeight, maxBarWidth } = calculateBarAndValueDimensions(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 valueColor = getValueColor(props);
|
||||||
const valueStyles = getValueStyles(value.text, valueColor, valueWidth, valueHeight);
|
const valueStyles = getValueStyles(value.text, valueColor, valueWidth, valueHeight);
|
||||||
const isBasic = displayMode === 'basic';
|
const isBasic = displayMode === 'basic';
|
||||||
@@ -450,7 +450,7 @@ export function getBarGradient(props: Props, maxSize: number): string {
|
|||||||
for (let i = 0; i < thresholds.length; i++) {
|
for (let i = 0; i < thresholds.length; i++) {
|
||||||
const threshold = thresholds[i];
|
const threshold = thresholds[i];
|
||||||
const color = getColorFromHexRgbOrName(threshold.color);
|
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 pos = valuePercent * maxSize;
|
||||||
const offset = Math.round(pos - (pos - lastpos) / 2);
|
const offset = Math.round(pos - (pos - lastpos) / 2);
|
||||||
|
|
||||||
@@ -486,7 +486,7 @@ export function getValueColor(props: Props): string {
|
|||||||
* Only exported to for unit test
|
* Only exported to for unit test
|
||||||
*/
|
*/
|
||||||
function getValueStyles(value: string, color: string, width: number, height: number): CSSProperties {
|
function getValueStyles(value: string, color: string, width: number, height: number): CSSProperties {
|
||||||
const heightFont = height / LINE_HEIGHT;
|
const heightFont = height / VALUE_LINE_HEIGHT;
|
||||||
const guess = width / (value.length * 1.1);
|
const guess = width / (value.length * 1.1);
|
||||||
const fontSize = Math.min(Math.max(guess, 14), heightFont);
|
const fontSize = Math.min(Math.max(guess, 14), heightFont);
|
||||||
|
|
||||||
@@ -496,33 +496,15 @@ function getValueStyles(value: string, color: string, width: number, height: num
|
|||||||
width: `${width}px`,
|
width: `${width}px`,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
fontSize: fontSize.toFixed(2) + 'px',
|
lineHeight: VALUE_LINE_HEIGHT,
|
||||||
|
fontSize: fontSize.toFixed(4) + '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 {
|
// function getTextWidth(text: string): number {
|
||||||
// // re-use canvas object for better performance
|
// const canvas = getTextWidth.canvas || (getTextWidth.canvas = document.createElement("canvas"));
|
||||||
// canvasElement = canvasElement || document.createElement('canvas');
|
// var context = canvas.getContext("2d");
|
||||||
// const context = canvasElement.getContext('2d');
|
// context.font = "'Roboto', 'Helvetica Neue', Arial, sans-serif";
|
||||||
// if (context) {
|
// var metrics = context.measureText(text);
|
||||||
// context.font = 'normal 16px Roboto';
|
|
||||||
// const metrics = context.measureText(text);
|
|
||||||
// return metrics.width;
|
// return metrics.width;
|
||||||
// }
|
// }
|
||||||
// return 16;
|
|
||||||
// }
|
|
||||||
|
|||||||
@@ -18,8 +18,9 @@ exports[`BarGauge Render with basic options should render 1`] = `
|
|||||||
"alignItems": "center",
|
"alignItems": "center",
|
||||||
"color": "#73BF69",
|
"color": "#73BF69",
|
||||||
"display": "flex",
|
"display": "flex",
|
||||||
"fontSize": "27.27px",
|
"fontSize": "27.2727px",
|
||||||
"height": "300px",
|
"height": "300px",
|
||||||
|
"lineHeight": 1,
|
||||||
"paddingLeft": "10px",
|
"paddingLeft": "10px",
|
||||||
"width": "60px",
|
"width": "60px",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,9 +42,10 @@ export class CustomScrollbar extends Component<Props> {
|
|||||||
|
|
||||||
updateScroll() {
|
updateScroll() {
|
||||||
const ref = this.ref.current;
|
const ref = this.ref.current;
|
||||||
|
const { scrollTop } = this.props;
|
||||||
|
|
||||||
if (ref && !isNil(this.props.scrollTop)) {
|
if (ref && !isNil(scrollTop)) {
|
||||||
ref.scrollTop(this.props.scrollTop);
|
ref.scrollTop(scrollTop);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,6 +71,44 @@ export class CustomScrollbar extends Component<Props> {
|
|||||||
this.updateScroll();
|
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() {
|
render() {
|
||||||
const {
|
const {
|
||||||
className,
|
className,
|
||||||
@@ -80,8 +119,6 @@ export class CustomScrollbar extends Component<Props> {
|
|||||||
autoHide,
|
autoHide,
|
||||||
autoHideTimeout,
|
autoHideTimeout,
|
||||||
hideTracksWhenNotNeeded,
|
hideTracksWhenNotNeeded,
|
||||||
hideHorizontalTrack,
|
|
||||||
hideVerticalTrack,
|
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
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
|
// Before these where set to inhert but that caused problems with cut of legends in firefox
|
||||||
autoHeightMax={autoHeightMax}
|
autoHeightMax={autoHeightMax}
|
||||||
autoHeightMin={autoHeightMin}
|
autoHeightMin={autoHeightMin}
|
||||||
renderTrackHorizontal={props => (
|
renderTrackHorizontal={this.renderTrackHorizontal}
|
||||||
<div
|
renderTrackVertical={this.renderTrackVertical}
|
||||||
{...props}
|
renderThumbHorizontal={this.renderThumbHorizontal}
|
||||||
className={cx(
|
renderThumbVertical={this.renderThumbVertical}
|
||||||
css`
|
renderView={this.renderView}
|
||||||
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" />}
|
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</Scrollbars>
|
</Scrollbars>
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ exports[`CustomScrollbar renders correctly 1`] = `
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="css-17l4171 track-horizontal"
|
className="css-52gpmd track-horizontal"
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"display": "none",
|
"display": "none",
|
||||||
@@ -58,7 +58,7 @@ exports[`CustomScrollbar renders correctly 1`] = `
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="css-17l4171 track-vertical"
|
className="css-52gpmd track-vertical"
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"display": "none",
|
"display": "none",
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export class Gauge extends PureComponent<Props> {
|
|||||||
if (length > 12) {
|
if (length > 12) {
|
||||||
return FONT_SCALE - (length * 5) / 110;
|
return FONT_SCALE - (length * 5) / 110;
|
||||||
}
|
}
|
||||||
return FONT_SCALE - (length * 5) / 100;
|
return FONT_SCALE - (length * 5) / 101;
|
||||||
}
|
}
|
||||||
|
|
||||||
draw() {
|
draw() {
|
||||||
@@ -69,16 +69,17 @@ export class Gauge extends PureComponent<Props> {
|
|||||||
|
|
||||||
const backgroundColor = selectThemeVariant(
|
const backgroundColor = selectThemeVariant(
|
||||||
{
|
{
|
||||||
dark: theme.colors.dark3,
|
dark: theme.colors.dark8,
|
||||||
light: '#e6e6e6',
|
light: theme.colors.gray6,
|
||||||
},
|
},
|
||||||
theme.type
|
theme.type
|
||||||
);
|
);
|
||||||
|
|
||||||
const gaugeWidthReduceRatio = showThresholdLabels ? 1.5 : 1;
|
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 thresholdMarkersWidth = gaugeWidth / 5;
|
||||||
const fontSize = Math.min(dimension / 5.5, 100) * (value.text !== null ? this.getFontScale(value.text.length) : 1);
|
const fontSize = Math.min(dimension / 4, 100) * (value.text !== null ? this.getFontScale(value.text.length) : 1);
|
||||||
|
|
||||||
const thresholdLabelFontSize = fontSize / 2.5;
|
const thresholdLabelFontSize = fontSize / 2.5;
|
||||||
|
|
||||||
const options: any = {
|
const options: any = {
|
||||||
@@ -181,7 +182,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 titleFontSize = Math.min((width * 0.15) / 1.5, 20); // 20% of height * line-height, max 40px
|
||||||
const titleHeight = titleFontSize * 1.5;
|
const titleHeight = titleFontSize * 1.5;
|
||||||
const availableHeight = showLabel ? height - titleHeight : height;
|
const availableHeight = showLabel ? height - titleHeight : height;
|
||||||
const gaugeHeight = Math.min(availableHeight * 0.7, width);
|
const gaugeHeight = Math.min(availableHeight, width);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
showLabel,
|
showLabel,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import React, { FunctionComponent } from 'react';
|
|||||||
interface Props {
|
interface Props {
|
||||||
title?: string;
|
title?: string;
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
children: JSX.Element | JSX.Element[] | boolean;
|
children: React.ReactNode;
|
||||||
onAdd?: () => void;
|
onAdd?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,59 +1,74 @@
|
|||||||
// Libraries
|
// Libraries
|
||||||
import React, { PureComponent, ChangeEvent } from 'react';
|
import React, { ChangeEvent, useState, useCallback } from 'react';
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import { PanelOptionsGroup } from '../PanelOptionsGroup/PanelOptionsGroup';
|
|
||||||
import { FormField } from '../FormField/FormField';
|
import { FormField } from '../FormField/FormField';
|
||||||
import { FormLabel } from '../FormLabel/FormLabel';
|
import { FormLabel } from '../FormLabel/FormLabel';
|
||||||
import { UnitPicker } from '../UnitPicker/UnitPicker';
|
import { UnitPicker } from '../UnitPicker/UnitPicker';
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import { Field } from '../../types/data';
|
import { Field } from '../../types/data';
|
||||||
import { toNumberString, toIntegerOrUndefined } from '../../utils';
|
import { toIntegerOrUndefined } from '../../utils';
|
||||||
import { SelectOptionItem } from '../Select/Select';
|
import { SelectOptionItem } from '../Select/Select';
|
||||||
|
|
||||||
import { VAR_SERIES_NAME, VAR_FIELD_NAME, VAR_CALC, VAR_CELL_PREFIX } from '../../utils/fieldDisplay';
|
import { VAR_SERIES_NAME, VAR_FIELD_NAME, VAR_CALC, VAR_CELL_PREFIX } from '../../utils/fieldDisplay';
|
||||||
|
import { PanelOptionsGroup } from '../index';
|
||||||
|
|
||||||
const labelWidth = 6;
|
const labelWidth = 6;
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
title: string;
|
title: string;
|
||||||
options: Partial<Field>;
|
value: Partial<Field>;
|
||||||
onChange: (fieldProperties: Partial<Field>) => void;
|
onChange: (fieldProperties: Partial<Field>) => void;
|
||||||
showMinMax: boolean;
|
showMinMax: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FieldPropertiesEditor extends PureComponent<Props> {
|
export const FieldPropertiesEditor: React.FC<Props> = ({ value, onChange, showMinMax }) => {
|
||||||
onTitleChange = (event: ChangeEvent<HTMLInputElement>) =>
|
const { unit, title } = value;
|
||||||
this.props.onChange({ ...this.props.options, title: event.target.value });
|
|
||||||
|
|
||||||
// @ts-ignore
|
const [decimals, setDecimals] = useState(
|
||||||
onUnitChange = (unit: SelectOptionItem<string>) => this.props.onChange({ ...this.props.value, unit: unit.value });
|
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>) => {
|
const onTitleChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
this.props.onChange({
|
onChange({ ...value, title: event.target.value });
|
||||||
...this.props.options,
|
|
||||||
decimals: toIntegerOrUndefined(event.target.value),
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
onMinChange = (event: ChangeEvent<HTMLInputElement>) => {
|
const onDecimalChange = useCallback(
|
||||||
this.props.onChange({
|
(event: ChangeEvent<HTMLInputElement>) => {
|
||||||
...this.props.options,
|
setDecimals(event.target.value);
|
||||||
min: toIntegerOrUndefined(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>) => {
|
const commitChanges = useCallback(() => {
|
||||||
this.props.onChange({
|
onChange({
|
||||||
...this.props.options,
|
...value,
|
||||||
max: toIntegerOrUndefined(event.target.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 = (
|
const titleTooltip = (
|
||||||
<div>
|
<div>
|
||||||
@@ -66,37 +81,36 @@ export class FieldPropertiesEditor extends PureComponent<Props> {
|
|||||||
{'$' + VAR_CELL_PREFIX + '{N}'} / {'$' + VAR_CALC}
|
{'$' + VAR_CELL_PREFIX + '{N}'} / {'$' + VAR_CALC}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PanelOptionsGroup title={title}>
|
<PanelOptionsGroup title="Field">
|
||||||
<>
|
|
||||||
<FormField
|
<FormField
|
||||||
label="Title"
|
label="Title"
|
||||||
labelWidth={labelWidth}
|
labelWidth={labelWidth}
|
||||||
onChange={this.onTitleChange}
|
onChange={onTitleChange}
|
||||||
value={this.props.options.title}
|
value={title}
|
||||||
tooltip={titleTooltip}
|
tooltip={titleTooltip}
|
||||||
placeholder="Auto"
|
placeholder="Auto"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="gf-form">
|
<div className="gf-form">
|
||||||
<FormLabel width={labelWidth}>Unit</FormLabel>
|
<FormLabel width={labelWidth}>Unit</FormLabel>
|
||||||
<UnitPicker defaultValue={unit} onChange={this.onUnitChange} />
|
<UnitPicker defaultValue={unit} onChange={onUnitChange} />
|
||||||
</div>
|
</div>
|
||||||
{showMinMax && (
|
{showMinMax && (
|
||||||
<>
|
<>
|
||||||
<FormField
|
<FormField
|
||||||
label="Min"
|
label="Min"
|
||||||
labelWidth={labelWidth}
|
labelWidth={labelWidth}
|
||||||
onChange={this.onMinChange}
|
onChange={onMinChange}
|
||||||
value={toNumberString(min)}
|
onBlur={commitChanges}
|
||||||
|
value={min}
|
||||||
type="number"
|
type="number"
|
||||||
/>
|
/>
|
||||||
<FormField
|
<FormField
|
||||||
label="Max"
|
label="Max"
|
||||||
labelWidth={labelWidth}
|
labelWidth={labelWidth}
|
||||||
onChange={this.onMaxChange}
|
onChange={onMaxChange}
|
||||||
value={toNumberString(max)}
|
onBlur={commitChanges}
|
||||||
|
value={max}
|
||||||
type="number"
|
type="number"
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
@@ -105,12 +119,11 @@ export class FieldPropertiesEditor extends PureComponent<Props> {
|
|||||||
label="Decimals"
|
label="Decimals"
|
||||||
labelWidth={labelWidth}
|
labelWidth={labelWidth}
|
||||||
placeholder="auto"
|
placeholder="auto"
|
||||||
onChange={this.onDecimalChange}
|
onChange={onDecimalChange}
|
||||||
value={toNumberString(decimals)}
|
onBlur={commitChanges}
|
||||||
|
value={decimals}
|
||||||
type="number"
|
type="number"
|
||||||
/>
|
/>
|
||||||
</>
|
|
||||||
</PanelOptionsGroup>
|
</PanelOptionsGroup>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
|||||||
@@ -80,6 +80,13 @@ export interface DataSourcePluginMeta extends PluginMeta {
|
|||||||
mixed?: boolean;
|
mixed?: boolean;
|
||||||
hasQueryHelp?: boolean;
|
hasQueryHelp?: boolean;
|
||||||
queryOptions?: PluginMetaQueryOptions;
|
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 {
|
interface PluginMetaQueryOptions {
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ export interface PanelData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface PanelProps<T = any> {
|
export interface PanelProps<T = any> {
|
||||||
|
id: number; // ID within the current dashboard
|
||||||
data: PanelData;
|
data: PanelData;
|
||||||
// TODO: annotation?: PanelData;
|
// TODO: annotation?: PanelData;
|
||||||
|
|
||||||
|
|||||||
@@ -47,8 +47,8 @@ RUN mkdir -p "$GF_PATHS_HOME/.aws" && \
|
|||||||
"$GF_PATHS_DATA" && \
|
"$GF_PATHS_DATA" && \
|
||||||
cp "$GF_PATHS_HOME/conf/sample.ini" "$GF_PATHS_CONFIG" && \
|
cp "$GF_PATHS_HOME/conf/sample.ini" "$GF_PATHS_CONFIG" && \
|
||||||
cp "$GF_PATHS_HOME/conf/ldap.toml" /etc/grafana/ldap.toml && \
|
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" && \
|
chown -R grafana:grafana "$GF_PATHS_DATA" "$GF_PATHS_HOME/.aws" "$GF_PATHS_LOGS" "$GF_PATHS_PLUGINS" "$GF_PATHS_PROVISIONING" && \
|
||||||
chmod 777 "$GF_PATHS_DATA" "$GF_PATHS_HOME/.aws" "$GF_PATHS_LOGS" "$GF_PATHS_PLUGINS"
|
chmod -R 777 "$GF_PATHS_DATA" "$GF_PATHS_HOME/.aws" "$GF_PATHS_LOGS" "$GF_PATHS_PLUGINS" "$GF_PATHS_PROVISIONING"
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
|
|||||||
@@ -233,7 +233,7 @@ func (hs *HTTPServer) setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, er
|
|||||||
|
|
||||||
if len(appLink.Children) > 0 && c.OrgRole == m.ROLE_ADMIN {
|
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{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 {
|
if len(appLink.Children) > 0 {
|
||||||
|
|||||||
@@ -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 {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
params := make(url.Values)
|
params := make(url.Values)
|
||||||
for key, value := range provider.route.TokenAuth.Params {
|
for key, value := range provider.route.TokenAuth.Params {
|
||||||
interpolatedParam, err := interpolateString(value, data)
|
interpolatedParam, err := InterpolateString(value, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -119,7 +119,7 @@ func (provider *accessTokenProvider) getJwtAccessToken(ctx context.Context, data
|
|||||||
conf := &jwt.Config{}
|
conf := &jwt.Config{}
|
||||||
|
|
||||||
if val, ok := provider.route.JwtTokenAuth.Params["client_email"]; ok {
|
if val, ok := provider.route.JwtTokenAuth.Params["client_email"]; ok {
|
||||||
interpolatedVal, err := interpolateString(val, data)
|
interpolatedVal, err := InterpolateString(val, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -127,7 +127,7 @@ func (provider *accessTokenProvider) getJwtAccessToken(ctx context.Context, data
|
|||||||
}
|
}
|
||||||
|
|
||||||
if val, ok := provider.route.JwtTokenAuth.Params["private_key"]; ok {
|
if val, ok := provider.route.JwtTokenAuth.Params["private_key"]; ok {
|
||||||
interpolatedVal, err := interpolateString(val, data)
|
interpolatedVal, err := InterpolateString(val, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -135,7 +135,7 @@ func (provider *accessTokenProvider) getJwtAccessToken(ctx context.Context, data
|
|||||||
}
|
}
|
||||||
|
|
||||||
if val, ok := provider.route.JwtTokenAuth.Params["token_uri"]; ok {
|
if val, ok := provider.route.JwtTokenAuth.Params["token_uri"]; ok {
|
||||||
interpolatedVal, err := interpolateString(val, data)
|
interpolatedVal, err := InterpolateString(val, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
package pluginproxy
|
package pluginproxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
|
||||||
|
|
||||||
m "github.com/grafana/grafana/pkg/models"
|
m "github.com/grafana/grafana/pkg/models"
|
||||||
"github.com/grafana/grafana/pkg/plugins"
|
"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(),
|
SecureJsonData: ds.SecureJsonData.Decrypt(),
|
||||||
}
|
}
|
||||||
|
|
||||||
interpolatedURL, err := interpolateString(route.Url, data)
|
interpolatedURL, err := InterpolateString(route.Url, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Error interpolating proxy url", "error", err)
|
logger.Error("Error interpolating proxy url", "error", err)
|
||||||
return
|
return
|
||||||
@@ -81,24 +79,9 @@ func ApplyRoute(ctx context.Context, req *http.Request, proxyPath string, route
|
|||||||
logger.Info("Requesting", "url", req.URL.String())
|
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 {
|
func addHeaders(reqHeaders *http.Header, route *plugins.AppPluginRoute, data templateData) error {
|
||||||
for _, header := range route.Headers {
|
for _, header := range route.Headers {
|
||||||
interpolated, err := interpolateString(header.Content, data)
|
interpolated, err := InterpolateString(header.Content, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(err, ShouldBeNil)
|
||||||
So(interpolated, ShouldEqual, "0asd+asd")
|
So(interpolated, ShouldEqual, "0asd+asd")
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,12 +2,13 @@ package pluginproxy
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/bus"
|
"github.com/grafana/grafana/pkg/bus"
|
||||||
"github.com/grafana/grafana/pkg/log"
|
"github.com/grafana/grafana/pkg/log"
|
||||||
m "github.com/grafana/grafana/pkg/models"
|
m "github.com/grafana/grafana/pkg/models"
|
||||||
@@ -38,6 +39,24 @@ func getHeaders(route *plugins.AppPluginRoute, orgId int64, appID string) (http.
|
|||||||
return result, err
|
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 {
|
func NewApiPluginProxy(ctx *m.ReqContext, proxyPath string, route *plugins.AppPluginRoute, appID string, cfg *setting.Cfg) *httputil.ReverseProxy {
|
||||||
targetURL, _ := url.Parse(route.Url)
|
targetURL, _ := url.Parse(route.Url)
|
||||||
|
|
||||||
@@ -48,7 +67,6 @@ func NewApiPluginProxy(ctx *m.ReqContext, proxyPath string, route *plugins.AppPl
|
|||||||
req.Host = targetURL.Host
|
req.Host = targetURL.Host
|
||||||
|
|
||||||
req.URL.Path = util.JoinURLFragments(targetURL.Path, proxyPath)
|
req.URL.Path = util.JoinURLFragments(targetURL.Path, proxyPath)
|
||||||
|
|
||||||
// clear cookie headers
|
// clear cookie headers
|
||||||
req.Header.Del("Cookie")
|
req.Header.Del("Cookie")
|
||||||
req.Header.Del("Set-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.
|
// Create a HTTP header with the context in it.
|
||||||
ctxJson, err := json.Marshal(ctx.SignedInUser)
|
ctxJSON, err := json.Marshal(ctx.SignedInUser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.JsonApiErr(500, "failed to marshal context to json.", err)
|
ctx.JsonApiErr(500, "failed to marshal context to json.", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Add("X-Grafana-Context", string(ctxJson))
|
req.Header.Add("X-Grafana-Context", string(ctxJSON))
|
||||||
|
|
||||||
if cfg.SendUserHeader && !ctx.SignedInUser.IsAnonymous {
|
if cfg.SendUserHeader && !ctx.SignedInUser.IsAnonymous {
|
||||||
req.Header.Add("X-Grafana-User", ctx.SignedInUser.Login)
|
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);
|
// reqBytes, _ := httputil.DumpRequestOut(req, true);
|
||||||
// log.Trace("Proxying plugin request: %s", string(reqBytes))
|
// log.Trace("Proxying plugin request: %s", string(reqBytes))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ func TestPluginProxy(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
&setting.Cfg{SendUserHeader: true},
|
&setting.Cfg{SendUserHeader: true},
|
||||||
|
nil,
|
||||||
)
|
)
|
||||||
|
|
||||||
Convey("Should add header with username", func() {
|
Convey("Should add header with username", func() {
|
||||||
@@ -69,6 +70,7 @@ func TestPluginProxy(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
&setting.Cfg{SendUserHeader: false},
|
&setting.Cfg{SendUserHeader: false},
|
||||||
|
nil,
|
||||||
)
|
)
|
||||||
Convey("Should not add header with username", func() {
|
Convey("Should not add header with username", func() {
|
||||||
// Get will return empty string even if header is not set
|
// 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},
|
SignedInUser: &m.SignedInUser{IsAnonymous: true},
|
||||||
},
|
},
|
||||||
&setting.Cfg{SendUserHeader: true},
|
&setting.Cfg{SendUserHeader: true},
|
||||||
|
nil,
|
||||||
)
|
)
|
||||||
|
|
||||||
Convey("Should not add header with username", func() {
|
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, "")
|
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.
|
// 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 {
|
func getPluginProxiedRequest(ctx *m.ReqContext, cfg *setting.Cfg, route *plugins.AppPluginRoute) *http.Request {
|
||||||
route := &plugins.AppPluginRoute{}
|
// 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)
|
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)
|
So(err, ShouldBeNil)
|
||||||
proxy.Director(req)
|
proxy.Director(req)
|
||||||
return req
|
return req
|
||||||
|
|||||||
49
pkg/api/pluginproxy/utils.go
Normal file
49
pkg/api/pluginproxy/utils.go
Normal 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
|
||||||
|
}
|
||||||
21
pkg/api/pluginproxy/utils_test.go
Normal file
21
pkg/api/pluginproxy/utils_test.go
Normal 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")
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -7,14 +7,16 @@ import (
|
|||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
"github.com/grafana/grafana/pkg/bus"
|
"github.com/grafana/grafana/pkg/bus"
|
||||||
|
"github.com/grafana/grafana/pkg/cmd/grafana-cli/commands/datamigrations"
|
||||||
"github.com/grafana/grafana/pkg/cmd/grafana-cli/logger"
|
"github.com/grafana/grafana/pkg/cmd/grafana-cli/logger"
|
||||||
|
"github.com/grafana/grafana/pkg/cmd/grafana-cli/utils"
|
||||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
func runDbCommand(command func(commandLine CommandLine) error) func(context *cli.Context) {
|
func runDbCommand(command func(commandLine utils.CommandLine, sqlStore *sqlstore.SqlStore) error) func(context *cli.Context) {
|
||||||
return func(context *cli.Context) {
|
return func(context *cli.Context) {
|
||||||
cmd := &contextCommandLine{context}
|
cmd := &utils.ContextCommandLine{Context: context}
|
||||||
|
|
||||||
cfg := setting.NewCfg()
|
cfg := setting.NewCfg()
|
||||||
cfg.Load(&setting.CommandLineArgs{
|
cfg.Load(&setting.CommandLineArgs{
|
||||||
@@ -28,7 +30,7 @@ func runDbCommand(command func(commandLine CommandLine) error) func(context *cli
|
|||||||
engine.Bus = bus.GetBus()
|
engine.Bus = bus.GetBus()
|
||||||
engine.Init()
|
engine.Init()
|
||||||
|
|
||||||
if err := command(cmd); err != nil {
|
if err := command(cmd, engine); err != nil {
|
||||||
logger.Errorf("\n%s: ", color.RedString("Error"))
|
logger.Errorf("\n%s: ", color.RedString("Error"))
|
||||||
logger.Errorf("%s\n\n", err)
|
logger.Errorf("%s\n\n", err)
|
||||||
|
|
||||||
@@ -40,10 +42,10 @@ func runDbCommand(command func(commandLine CommandLine) error) func(context *cli
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func runPluginCommand(command func(commandLine CommandLine) error) func(context *cli.Context) {
|
func runPluginCommand(command func(commandLine utils.CommandLine) error) func(context *cli.Context) {
|
||||||
return func(context *cli.Context) {
|
return func(context *cli.Context) {
|
||||||
|
|
||||||
cmd := &contextCommandLine{context}
|
cmd := &utils.ContextCommandLine{Context: context}
|
||||||
if err := command(cmd); err != nil {
|
if err := command(cmd); err != nil {
|
||||||
logger.Errorf("\n%s: ", color.RedString("Error"))
|
logger.Errorf("\n%s: ", color.RedString("Error"))
|
||||||
logger.Errorf("%s %s\n\n", color.RedString("✗"), err)
|
logger.Errorf("%s %s\n\n", color.RedString("✗"), err)
|
||||||
@@ -107,6 +109,17 @@ var adminCommands = []cli.Command{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "data-migration",
|
||||||
|
Usage: "Runs a script that migrates or cleanups data in your db",
|
||||||
|
Subcommands: []cli.Command{
|
||||||
|
{
|
||||||
|
Name: "encrypt-datasource-passwords",
|
||||||
|
Usage: "Migrates passwords from unsecured fields to secure_json_data field. Return ok unless there is an error. Safe to execute multiple times.",
|
||||||
|
Action: runDbCommand(datamigrations.EncryptDatasourcePaswords),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var Commands = []cli.Command{
|
var Commands = []cli.Command{
|
||||||
|
|||||||
@@ -0,0 +1,126 @@
|
|||||||
|
package datamigrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/fatih/color"
|
||||||
|
"github.com/grafana/grafana/pkg/cmd/grafana-cli/logger"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/cmd/grafana-cli/utils"
|
||||||
|
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||||
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
|
"github.com/grafana/grafana/pkg/util"
|
||||||
|
"github.com/grafana/grafana/pkg/util/errutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
datasourceTypes = []string{
|
||||||
|
"mysql",
|
||||||
|
"influxdb",
|
||||||
|
"elasticsearch",
|
||||||
|
"graphite",
|
||||||
|
"prometheus",
|
||||||
|
"opentsdb",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// EncryptDatasourcePaswords migrates un-encrypted secrets on datasources
|
||||||
|
// to the secureJson Column.
|
||||||
|
func EncryptDatasourcePaswords(c utils.CommandLine, sqlStore *sqlstore.SqlStore) error {
|
||||||
|
return sqlStore.WithDbSession(context.Background(), func(session *sqlstore.DBSession) error {
|
||||||
|
passwordsUpdated, err := migrateColumn(session, "password")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
basicAuthUpdated, err := migrateColumn(session, "basic_auth_password")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info("\n")
|
||||||
|
if passwordsUpdated > 0 {
|
||||||
|
logger.Infof("%s Encrypted password field for %d datasources \n", color.GreenString("✔"), passwordsUpdated)
|
||||||
|
}
|
||||||
|
|
||||||
|
if basicAuthUpdated > 0 {
|
||||||
|
logger.Infof("%s Encrypted basic_auth_password field for %d datasources \n", color.GreenString("✔"), basicAuthUpdated)
|
||||||
|
}
|
||||||
|
|
||||||
|
if passwordsUpdated == 0 && basicAuthUpdated == 0 {
|
||||||
|
logger.Infof("%s All datasources secrets are allready encrypted\n", color.GreenString("✔"))
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info("\n")
|
||||||
|
|
||||||
|
logger.Warn("Warning: Datasource provisioning files need to be manually changed to prevent overwriting of " +
|
||||||
|
"the data during provisioning. See https://grafana.com/docs/installation/upgrading/#upgrading-to-v6-2 for " +
|
||||||
|
"details")
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func migrateColumn(session *sqlstore.DBSession, column string) (int, error) {
|
||||||
|
var rows []map[string]string
|
||||||
|
|
||||||
|
session.Cols("id", column, "secure_json_data")
|
||||||
|
session.Table("data_source")
|
||||||
|
session.In("type", datasourceTypes)
|
||||||
|
session.Where(column + " IS NOT NULL AND " + column + " != ''")
|
||||||
|
err := session.Find(&rows)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return 0, errutil.Wrapf(err, "failed to select column: %s", column)
|
||||||
|
}
|
||||||
|
|
||||||
|
rowsUpdated, err := updateRows(session, rows, column)
|
||||||
|
return rowsUpdated, errutil.Wrapf(err, "failed to update column: %s", column)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateRows(session *sqlstore.DBSession, rows []map[string]string, passwordFieldName string) (int, error) {
|
||||||
|
var rowsUpdated int
|
||||||
|
|
||||||
|
for _, row := range rows {
|
||||||
|
newSecureJSONData, err := getUpdatedSecureJSONData(row, passwordFieldName)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := json.Marshal(newSecureJSONData)
|
||||||
|
if err != nil {
|
||||||
|
return 0, errutil.Wrap("marshaling newSecureJsonData failed", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
newRow := map[string]interface{}{"secure_json_data": data, passwordFieldName: ""}
|
||||||
|
session.Table("data_source")
|
||||||
|
session.Where("id = ?", row["id"])
|
||||||
|
// Setting both columns while having value only for secure_json_data should clear the [passwordFieldName] column
|
||||||
|
session.Cols("secure_json_data", passwordFieldName)
|
||||||
|
|
||||||
|
_, err = session.Update(newRow)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rowsUpdated++
|
||||||
|
}
|
||||||
|
return rowsUpdated, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUpdatedSecureJSONData(row map[string]string, passwordFieldName string) (map[string]interface{}, error) {
|
||||||
|
encryptedPassword, err := util.Encrypt([]byte(row[passwordFieldName]), setting.SecretKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var secureJSONData map[string]interface{}
|
||||||
|
|
||||||
|
if err := json.Unmarshal([]byte(row["secure_json_data"]), &secureJSONData); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonFieldName := util.ToCamelCase(passwordFieldName)
|
||||||
|
secureJSONData[jsonFieldName] = encryptedPassword
|
||||||
|
return secureJSONData, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package datamigrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/cmd/grafana-cli/commands/commandstest"
|
||||||
|
"github.com/grafana/grafana/pkg/components/securejsondata"
|
||||||
|
"github.com/grafana/grafana/pkg/models"
|
||||||
|
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPasswordMigrationCommand(t *testing.T) {
|
||||||
|
//setup datasources with password, basic_auth and none
|
||||||
|
sqlstore := sqlstore.InitTestDB(t)
|
||||||
|
session := sqlstore.NewSession()
|
||||||
|
defer session.Close()
|
||||||
|
|
||||||
|
datasources := []*models.DataSource{
|
||||||
|
{Type: "influxdb", Name: "influxdb", Password: "foobar"},
|
||||||
|
{Type: "graphite", Name: "graphite", BasicAuthPassword: "foobar"},
|
||||||
|
{Type: "prometheus", Name: "prometheus", SecureJsonData: securejsondata.GetEncryptedJsonData(map[string]string{})},
|
||||||
|
}
|
||||||
|
|
||||||
|
// set required default values
|
||||||
|
for _, ds := range datasources {
|
||||||
|
ds.Created = time.Now()
|
||||||
|
ds.Updated = time.Now()
|
||||||
|
ds.SecureJsonData = securejsondata.GetEncryptedJsonData(map[string]string{})
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := session.Insert(&datasources)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
//run migration
|
||||||
|
err = EncryptDatasourcePaswords(&commandstest.FakeCommandLine{}, sqlstore)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
//verify that no datasources still have password or basic_auth
|
||||||
|
var dss []*models.DataSource
|
||||||
|
err = session.SQL("select * from data_source").Find(&dss)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, len(dss), 3)
|
||||||
|
|
||||||
|
for _, ds := range dss {
|
||||||
|
sj := ds.SecureJsonData.Decrypt()
|
||||||
|
|
||||||
|
if ds.Name == "influxdb" {
|
||||||
|
assert.Equal(t, ds.Password, "")
|
||||||
|
v, exist := sj["password"]
|
||||||
|
assert.True(t, exist)
|
||||||
|
assert.Equal(t, v, "foobar", "expected password to be moved to securejson")
|
||||||
|
}
|
||||||
|
|
||||||
|
if ds.Name == "graphite" {
|
||||||
|
assert.Equal(t, ds.BasicAuthPassword, "")
|
||||||
|
v, exist := sj["basicAuthPassword"]
|
||||||
|
assert.True(t, exist)
|
||||||
|
assert.Equal(t, v, "foobar", "expected basic_auth_password to be moved to securejson")
|
||||||
|
}
|
||||||
|
|
||||||
|
if ds.Name == "prometheus" {
|
||||||
|
assert.Equal(t, len(sj), 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,13 +14,14 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
|
"github.com/grafana/grafana/pkg/cmd/grafana-cli/utils"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/cmd/grafana-cli/logger"
|
"github.com/grafana/grafana/pkg/cmd/grafana-cli/logger"
|
||||||
m "github.com/grafana/grafana/pkg/cmd/grafana-cli/models"
|
m "github.com/grafana/grafana/pkg/cmd/grafana-cli/models"
|
||||||
s "github.com/grafana/grafana/pkg/cmd/grafana-cli/services"
|
s "github.com/grafana/grafana/pkg/cmd/grafana-cli/services"
|
||||||
)
|
)
|
||||||
|
|
||||||
func validateInput(c CommandLine, pluginFolder string) error {
|
func validateInput(c utils.CommandLine, pluginFolder string) error {
|
||||||
arg := c.Args().First()
|
arg := c.Args().First()
|
||||||
if arg == "" {
|
if arg == "" {
|
||||||
return errors.New("please specify plugin to install")
|
return errors.New("please specify plugin to install")
|
||||||
@@ -46,7 +47,7 @@ func validateInput(c CommandLine, pluginFolder string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func installCommand(c CommandLine) error {
|
func installCommand(c utils.CommandLine) error {
|
||||||
pluginFolder := c.PluginDirectory()
|
pluginFolder := c.PluginDirectory()
|
||||||
if err := validateInput(c, pluginFolder); err != nil {
|
if err := validateInput(c, pluginFolder); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -60,7 +61,7 @@ func installCommand(c CommandLine) error {
|
|||||||
|
|
||||||
// InstallPlugin downloads the plugin code as a zip file from the Grafana.com API
|
// InstallPlugin downloads the plugin code as a zip file from the Grafana.com API
|
||||||
// and then extracts the zip into the plugins directory.
|
// and then extracts the zip into the plugins directory.
|
||||||
func InstallPlugin(pluginName, version string, c CommandLine) error {
|
func InstallPlugin(pluginName, version string, c utils.CommandLine) error {
|
||||||
pluginFolder := c.PluginDirectory()
|
pluginFolder := c.PluginDirectory()
|
||||||
downloadURL := c.PluginURL()
|
downloadURL := c.PluginURL()
|
||||||
if downloadURL == "" {
|
if downloadURL == "" {
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ package commands
|
|||||||
import (
|
import (
|
||||||
"github.com/grafana/grafana/pkg/cmd/grafana-cli/logger"
|
"github.com/grafana/grafana/pkg/cmd/grafana-cli/logger"
|
||||||
s "github.com/grafana/grafana/pkg/cmd/grafana-cli/services"
|
s "github.com/grafana/grafana/pkg/cmd/grafana-cli/services"
|
||||||
|
"github.com/grafana/grafana/pkg/cmd/grafana-cli/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func listremoteCommand(c CommandLine) error {
|
func listremoteCommand(c utils.CommandLine) error {
|
||||||
plugin, err := s.ListAllPlugins(c.RepoDirectory())
|
plugin, err := s.ListAllPlugins(c.RepoDirectory())
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -5,9 +5,10 @@ import (
|
|||||||
|
|
||||||
"github.com/grafana/grafana/pkg/cmd/grafana-cli/logger"
|
"github.com/grafana/grafana/pkg/cmd/grafana-cli/logger"
|
||||||
s "github.com/grafana/grafana/pkg/cmd/grafana-cli/services"
|
s "github.com/grafana/grafana/pkg/cmd/grafana-cli/services"
|
||||||
|
"github.com/grafana/grafana/pkg/cmd/grafana-cli/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func validateVersionInput(c CommandLine) error {
|
func validateVersionInput(c utils.CommandLine) error {
|
||||||
arg := c.Args().First()
|
arg := c.Args().First()
|
||||||
if arg == "" {
|
if arg == "" {
|
||||||
return errors.New("please specify plugin to list versions for")
|
return errors.New("please specify plugin to list versions for")
|
||||||
@@ -16,7 +17,7 @@ func validateVersionInput(c CommandLine) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func listversionsCommand(c CommandLine) error {
|
func listversionsCommand(c utils.CommandLine) error {
|
||||||
if err := validateVersionInput(c); err != nil {
|
if err := validateVersionInput(c); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/cmd/grafana-cli/logger"
|
"github.com/grafana/grafana/pkg/cmd/grafana-cli/logger"
|
||||||
m "github.com/grafana/grafana/pkg/cmd/grafana-cli/models"
|
m "github.com/grafana/grafana/pkg/cmd/grafana-cli/models"
|
||||||
s "github.com/grafana/grafana/pkg/cmd/grafana-cli/services"
|
s "github.com/grafana/grafana/pkg/cmd/grafana-cli/services"
|
||||||
|
"github.com/grafana/grafana/pkg/cmd/grafana-cli/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ls_getPlugins func(path string) []m.InstalledPlugin = s.GetLocalPlugins
|
var ls_getPlugins func(path string) []m.InstalledPlugin = s.GetLocalPlugins
|
||||||
@@ -31,7 +32,7 @@ var validateLsCommand = func(pluginDir string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func lsCommand(c CommandLine) error {
|
func lsCommand(c utils.CommandLine) error {
|
||||||
pluginDir := c.PluginDirectory()
|
pluginDir := c.PluginDirectory()
|
||||||
if err := validateLsCommand(pluginDir); err != nil {
|
if err := validateLsCommand(pluginDir); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -5,12 +5,13 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
services "github.com/grafana/grafana/pkg/cmd/grafana-cli/services"
|
"github.com/grafana/grafana/pkg/cmd/grafana-cli/services"
|
||||||
|
"github.com/grafana/grafana/pkg/cmd/grafana-cli/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
var removePlugin func(pluginPath, id string) error = services.RemoveInstalledPlugin
|
var removePlugin func(pluginPath, id string) error = services.RemoveInstalledPlugin
|
||||||
|
|
||||||
func removeCommand(c CommandLine) error {
|
func removeCommand(c utils.CommandLine) error {
|
||||||
pluginPath := c.PluginDirectory()
|
pluginPath := c.PluginDirectory()
|
||||||
|
|
||||||
plugin := c.Args().First()
|
plugin := c.Args().First()
|
||||||
|
|||||||
@@ -6,13 +6,15 @@ import (
|
|||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
"github.com/grafana/grafana/pkg/bus"
|
"github.com/grafana/grafana/pkg/bus"
|
||||||
"github.com/grafana/grafana/pkg/cmd/grafana-cli/logger"
|
"github.com/grafana/grafana/pkg/cmd/grafana-cli/logger"
|
||||||
|
"github.com/grafana/grafana/pkg/cmd/grafana-cli/utils"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
|
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||||
"github.com/grafana/grafana/pkg/util"
|
"github.com/grafana/grafana/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
const AdminUserId = 1
|
const AdminUserId = 1
|
||||||
|
|
||||||
func resetPasswordCommand(c CommandLine) error {
|
func resetPasswordCommand(c utils.CommandLine, sqlStore *sqlstore.SqlStore) error {
|
||||||
newPassword := c.Args().First()
|
newPassword := c.Args().First()
|
||||||
|
|
||||||
password := models.Password(newPassword)
|
password := models.Password(newPassword)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/cmd/grafana-cli/logger"
|
"github.com/grafana/grafana/pkg/cmd/grafana-cli/logger"
|
||||||
m "github.com/grafana/grafana/pkg/cmd/grafana-cli/models"
|
m "github.com/grafana/grafana/pkg/cmd/grafana-cli/models"
|
||||||
s "github.com/grafana/grafana/pkg/cmd/grafana-cli/services"
|
s "github.com/grafana/grafana/pkg/cmd/grafana-cli/services"
|
||||||
|
"github.com/grafana/grafana/pkg/cmd/grafana-cli/utils"
|
||||||
"github.com/hashicorp/go-version"
|
"github.com/hashicorp/go-version"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -27,7 +28,7 @@ func ShouldUpgrade(installed string, remote m.Plugin) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func upgradeAllCommand(c CommandLine) error {
|
func upgradeAllCommand(c utils.CommandLine) error {
|
||||||
pluginsDir := c.PluginDirectory()
|
pluginsDir := c.PluginDirectory()
|
||||||
|
|
||||||
localPlugins := s.GetLocalPlugins(pluginsDir)
|
localPlugins := s.GetLocalPlugins(pluginsDir)
|
||||||
|
|||||||
@@ -4,9 +4,10 @@ import (
|
|||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
"github.com/grafana/grafana/pkg/cmd/grafana-cli/logger"
|
"github.com/grafana/grafana/pkg/cmd/grafana-cli/logger"
|
||||||
s "github.com/grafana/grafana/pkg/cmd/grafana-cli/services"
|
s "github.com/grafana/grafana/pkg/cmd/grafana-cli/services"
|
||||||
|
"github.com/grafana/grafana/pkg/cmd/grafana-cli/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func upgradeCommand(c CommandLine) error {
|
func upgradeCommand(c utils.CommandLine) error {
|
||||||
pluginsDir := c.PluginDirectory()
|
pluginsDir := c.PluginDirectory()
|
||||||
pluginName := c.Args().First()
|
pluginName := c.Args().First()
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package commands
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
@@ -22,30 +22,30 @@ type CommandLine interface {
|
|||||||
PluginURL() string
|
PluginURL() string
|
||||||
}
|
}
|
||||||
|
|
||||||
type contextCommandLine struct {
|
type ContextCommandLine struct {
|
||||||
*cli.Context
|
*cli.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *contextCommandLine) ShowHelp() {
|
func (c *ContextCommandLine) ShowHelp() {
|
||||||
cli.ShowCommandHelp(c.Context, c.Command.Name)
|
cli.ShowCommandHelp(c.Context, c.Command.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *contextCommandLine) ShowVersion() {
|
func (c *ContextCommandLine) ShowVersion() {
|
||||||
cli.ShowVersion(c.Context)
|
cli.ShowVersion(c.Context)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *contextCommandLine) Application() *cli.App {
|
func (c *ContextCommandLine) Application() *cli.App {
|
||||||
return c.App
|
return c.App
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *contextCommandLine) PluginDirectory() string {
|
func (c *ContextCommandLine) PluginDirectory() string {
|
||||||
return c.GlobalString("pluginsDir")
|
return c.GlobalString("pluginsDir")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *contextCommandLine) RepoDirectory() string {
|
func (c *ContextCommandLine) RepoDirectory() string {
|
||||||
return c.GlobalString("repo")
|
return c.GlobalString("repo")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *contextCommandLine) PluginURL() string {
|
func (c *ContextCommandLine) PluginURL() string {
|
||||||
return c.GlobalString("pluginUrl")
|
return c.GlobalString("pluginUrl")
|
||||||
}
|
}
|
||||||
@@ -39,10 +39,14 @@ func (dc *databaseCache) Run(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (dc *databaseCache) internalRunGC() {
|
func (dc *databaseCache) internalRunGC() {
|
||||||
|
err := dc.SQLStore.WithDbSession(context.Background(), func(session *sqlstore.DBSession) error {
|
||||||
now := getTime().Unix()
|
now := getTime().Unix()
|
||||||
sql := `DELETE FROM cache_data WHERE (? - created_at) >= expires AND expires <> 0`
|
sql := `DELETE FROM cache_data WHERE (? - created_at) >= expires AND expires <> 0`
|
||||||
|
|
||||||
_, err := dc.SQLStore.NewSession().Exec(sql, now)
|
_, err := session.Exec(sql, now)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
dc.log.Error("failed to run garbage collect", "error", err)
|
dc.log.Error("failed to run garbage collect", "error", err)
|
||||||
}
|
}
|
||||||
@@ -80,14 +84,13 @@ func (dc *databaseCache) Get(key string) (interface{}, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (dc *databaseCache) Set(key string, value interface{}, expire time.Duration) error {
|
func (dc *databaseCache) Set(key string, value interface{}, expire time.Duration) error {
|
||||||
|
return dc.SQLStore.WithTransactionalDbSession(context.Background(), func(session *sqlstore.DBSession) error {
|
||||||
item := &cachedItem{Val: value}
|
item := &cachedItem{Val: value}
|
||||||
data, err := encodeGob(item)
|
data, err := encodeGob(item)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
session := dc.SQLStore.NewSession()
|
|
||||||
|
|
||||||
var cacheHit CacheData
|
var cacheHit CacheData
|
||||||
has, err := session.Where("cache_key = ?", key).Get(&cacheHit)
|
has, err := session.Where("cache_key = ?", key).Get(&cacheHit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -109,15 +112,20 @@ func (dc *databaseCache) Set(key string, value interface{}, expire time.Duration
|
|||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dc *databaseCache) Delete(key string) error {
|
func (dc *databaseCache) Delete(key string) error {
|
||||||
|
return dc.SQLStore.WithDbSession(context.Background(), func(session *sqlstore.DBSession) error {
|
||||||
sql := "DELETE FROM cache_data WHERE cache_key=?"
|
sql := "DELETE FROM cache_data WHERE cache_key=?"
|
||||||
_, err := dc.SQLStore.NewSession().Exec(sql, key)
|
_, err := session.Exec(sql, key)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CacheData is the struct representing the table in the database
|
||||||
type CacheData struct {
|
type CacheData struct {
|
||||||
CacheKey string
|
CacheKey string
|
||||||
Data []byte
|
Data []byte
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ func initContextWithAuthProxy(store *remotecache.RemoteCache, ctx *m.ReqContext,
|
|||||||
|
|
||||||
// Check if allowed to continue with this IP
|
// Check if allowed to continue with this IP
|
||||||
if result, err := auth.IsAllowedIP(); result == false {
|
if result, err := auth.IsAllowedIP(); result == false {
|
||||||
|
ctx.Logger.Error("auth proxy: failed to check whitelisted ip addresses", "message", err.Error(), "error", err.DetailsError)
|
||||||
ctx.Handle(407, err.Error(), err.DetailsError)
|
ctx.Handle(407, err.Error(), err.DetailsError)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -38,6 +39,7 @@ func initContextWithAuthProxy(store *remotecache.RemoteCache, ctx *m.ReqContext,
|
|||||||
// Try to get user id from various sources
|
// Try to get user id from various sources
|
||||||
id, err := auth.GetUserID()
|
id, err := auth.GetUserID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
ctx.Logger.Error("auth proxy: failed to login", "message", err.Error(), "error", err.DetailsError)
|
||||||
ctx.Handle(500, err.Error(), err.DetailsError)
|
ctx.Handle(500, err.Error(), err.DetailsError)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -45,6 +47,7 @@ func initContextWithAuthProxy(store *remotecache.RemoteCache, ctx *m.ReqContext,
|
|||||||
// Get full user info
|
// Get full user info
|
||||||
user, err := auth.GetSignedUser(id)
|
user, err := auth.GetSignedUser(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
ctx.Logger.Error("auth proxy: failed to get signed in user", "message", err.Error(), "error", err.DetailsError)
|
||||||
ctx.Handle(500, err.Error(), err.DetailsError)
|
ctx.Handle(500, err.Error(), err.DetailsError)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -54,7 +57,8 @@ func initContextWithAuthProxy(store *remotecache.RemoteCache, ctx *m.ReqContext,
|
|||||||
ctx.IsSignedIn = true
|
ctx.IsSignedIn = true
|
||||||
|
|
||||||
// Remember user data it in cache
|
// Remember user data it in cache
|
||||||
if err := auth.Remember(); err != nil {
|
if err := auth.Remember(id); err != nil {
|
||||||
|
ctx.Logger.Error("auth proxy: failed to store user in cache", "message", err.Error(), "error", err.DetailsError)
|
||||||
ctx.Handle(500, err.Error(), err.DetailsError)
|
ctx.Handle(500, err.Error(), err.DetailsError)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -298,23 +298,24 @@ func (auth *AuthProxy) GetSignedUser(userID int64) (*models.SignedInUser, *Error
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remember user in cache
|
// Remember user in cache
|
||||||
func (auth *AuthProxy) Remember() *Error {
|
func (auth *AuthProxy) Remember(id int64) *Error {
|
||||||
|
key := auth.getKey()
|
||||||
|
|
||||||
// Make sure we do not rewrite the expiration time
|
// Check if user already in cache
|
||||||
if auth.InCache() {
|
userID, err := auth.store.Get(key)
|
||||||
|
if err != nil && err != remotecache.ErrCacheItemNotFound {
|
||||||
|
return newError("failed to lookup user in cache", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if userID != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
expiration := time.Duration(-auth.cacheTTL) * time.Minute
|
||||||
key = auth.getKey()
|
|
||||||
value, _ = auth.GetUserIDViaCache()
|
|
||||||
expiration = time.Duration(-auth.cacheTTL) * time.Minute
|
|
||||||
|
|
||||||
err = auth.store.Set(key, value, expiration)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
err = auth.store.Set(key, id, expiration)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return newError(err.Error(), nil)
|
return newError("failed to store user in cache", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ type DataSourcePlugin struct {
|
|||||||
Alerting bool `json:"alerting"`
|
Alerting bool `json:"alerting"`
|
||||||
Explore bool `json:"explore"`
|
Explore bool `json:"explore"`
|
||||||
Table bool `json:"tables"`
|
Table bool `json:"tables"`
|
||||||
|
HiddenQueries bool `json:"hiddenQueries"`
|
||||||
Logs bool `json:"logs"`
|
Logs bool `json:"logs"`
|
||||||
QueryOptions map[string]bool `json:"queryOptions,omitempty"`
|
QueryOptions map[string]bool `json:"queryOptions,omitempty"`
|
||||||
BuiltIn bool `json:"builtIn,omitempty"`
|
BuiltIn bool `json:"builtIn,omitempty"`
|
||||||
|
|||||||
@@ -45,19 +45,13 @@ func (sb *SqlBuilder) writeDashboardPermissionFilter(user *m.SignedInUser, permi
|
|||||||
sb.sql.WriteString(` AND
|
sb.sql.WriteString(` AND
|
||||||
(
|
(
|
||||||
dashboard.id IN (
|
dashboard.id IN (
|
||||||
SELECT distinct d.id AS DashboardId
|
SELECT distinct DashboardId from (
|
||||||
|
SELECT d.id AS DashboardId
|
||||||
FROM dashboard AS d
|
FROM dashboard AS d
|
||||||
LEFT JOIN dashboard folder on folder.id = d.folder_id
|
LEFT JOIN dashboard AS folder on folder.id = d.folder_id
|
||||||
LEFT JOIN dashboard_acl AS da ON
|
LEFT JOIN dashboard_acl AS da ON
|
||||||
da.dashboard_id = d.id OR
|
da.dashboard_id = d.id OR
|
||||||
da.dashboard_id = d.folder_id OR
|
da.dashboard_id = d.folder_id
|
||||||
(
|
|
||||||
-- include default permissions -->
|
|
||||||
da.org_id = -1 AND (
|
|
||||||
(folder.id IS NOT NULL AND folder.has_acl = ` + falseStr + `) OR
|
|
||||||
(folder.id IS NULL AND d.has_acl = ` + falseStr + `)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
LEFT JOIN team_member as ugm on ugm.team_id = da.team_id
|
LEFT JOIN team_member as ugm on ugm.team_id = da.team_id
|
||||||
WHERE
|
WHERE
|
||||||
d.org_id = ? AND
|
d.org_id = ? AND
|
||||||
@@ -67,9 +61,32 @@ func (sb *SqlBuilder) writeDashboardPermissionFilter(user *m.SignedInUser, permi
|
|||||||
ugm.user_id = ? OR
|
ugm.user_id = ? OR
|
||||||
da.role IN (?` + strings.Repeat(",?", len(okRoles)-1) + `)
|
da.role IN (?` + strings.Repeat(",?", len(okRoles)-1) + `)
|
||||||
)
|
)
|
||||||
|
UNION
|
||||||
|
SELECT d.id AS DashboardId
|
||||||
|
FROM dashboard AS d
|
||||||
|
LEFT JOIN dashboard AS folder on folder.id = d.folder_id
|
||||||
|
LEFT JOIN dashboard_acl AS da ON
|
||||||
|
(
|
||||||
|
-- include default permissions -->
|
||||||
|
da.org_id = -1 AND (
|
||||||
|
(folder.id IS NOT NULL AND folder.has_acl = ` + falseStr + `) OR
|
||||||
|
(folder.id IS NULL AND d.has_acl = ` + falseStr + `)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
WHERE
|
||||||
|
d.org_id = ? AND
|
||||||
|
da.permission >= ? AND
|
||||||
|
(
|
||||||
|
da.user_id = ? OR
|
||||||
|
da.role IN (?` + strings.Repeat(",?", len(okRoles)-1) + `)
|
||||||
|
)
|
||||||
|
) AS a
|
||||||
)
|
)
|
||||||
)`)
|
)`)
|
||||||
|
|
||||||
sb.params = append(sb.params, user.OrgId, permission, user.UserId, user.UserId)
|
sb.params = append(sb.params, user.OrgId, permission, user.UserId, user.UserId)
|
||||||
sb.params = append(sb.params, okRoles...)
|
sb.params = append(sb.params, okRoles...)
|
||||||
|
|
||||||
|
sb.params = append(sb.params, user.OrgId, permission, user.UserId)
|
||||||
|
sb.params = append(sb.params, okRoles...)
|
||||||
}
|
}
|
||||||
|
|||||||
343
pkg/services/sqlstore/sqlbuilder_test.go
Normal file
343
pkg/services/sqlstore/sqlbuilder_test.go
Normal file
@@ -0,0 +1,343 @@
|
|||||||
|
package sqlstore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"math/rand"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||||
|
"github.com/grafana/grafana/pkg/models"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSqlBuilder(t *testing.T) {
|
||||||
|
t.Run("writeDashboardPermissionFilter", func(t *testing.T) {
|
||||||
|
t.Run("user ACL", func(t *testing.T) {
|
||||||
|
test(t,
|
||||||
|
DashboardProps{},
|
||||||
|
&DashboardPermission{User: true, Permission: models.PERMISSION_VIEW},
|
||||||
|
Search{UserFromACL: true, RequiredPermission: models.PERMISSION_VIEW},
|
||||||
|
shouldFind,
|
||||||
|
)
|
||||||
|
|
||||||
|
test(t,
|
||||||
|
DashboardProps{},
|
||||||
|
&DashboardPermission{User: true, Permission: models.PERMISSION_VIEW},
|
||||||
|
Search{UserFromACL: true, RequiredPermission: models.PERMISSION_EDIT},
|
||||||
|
shouldNotFind,
|
||||||
|
)
|
||||||
|
|
||||||
|
test(t,
|
||||||
|
DashboardProps{},
|
||||||
|
&DashboardPermission{User: true, Permission: models.PERMISSION_EDIT},
|
||||||
|
Search{UserFromACL: true, RequiredPermission: models.PERMISSION_EDIT},
|
||||||
|
shouldFind,
|
||||||
|
)
|
||||||
|
|
||||||
|
test(t,
|
||||||
|
DashboardProps{},
|
||||||
|
&DashboardPermission{User: true, Permission: models.PERMISSION_VIEW},
|
||||||
|
Search{RequiredPermission: models.PERMISSION_VIEW},
|
||||||
|
shouldNotFind,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("role ACL", func(t *testing.T) {
|
||||||
|
test(t,
|
||||||
|
DashboardProps{},
|
||||||
|
&DashboardPermission{Role: models.ROLE_VIEWER, Permission: models.PERMISSION_VIEW},
|
||||||
|
Search{UsersOrgRole: models.ROLE_VIEWER, RequiredPermission: models.PERMISSION_VIEW},
|
||||||
|
shouldFind,
|
||||||
|
)
|
||||||
|
|
||||||
|
test(t,
|
||||||
|
DashboardProps{},
|
||||||
|
&DashboardPermission{Role: models.ROLE_VIEWER, Permission: models.PERMISSION_VIEW},
|
||||||
|
Search{UsersOrgRole: models.ROLE_VIEWER, RequiredPermission: models.PERMISSION_EDIT},
|
||||||
|
shouldNotFind,
|
||||||
|
)
|
||||||
|
|
||||||
|
test(t,
|
||||||
|
DashboardProps{},
|
||||||
|
&DashboardPermission{Role: models.ROLE_EDITOR, Permission: models.PERMISSION_VIEW},
|
||||||
|
Search{UsersOrgRole: models.ROLE_VIEWER, RequiredPermission: models.PERMISSION_VIEW},
|
||||||
|
shouldNotFind,
|
||||||
|
)
|
||||||
|
|
||||||
|
test(t,
|
||||||
|
DashboardProps{},
|
||||||
|
&DashboardPermission{Role: models.ROLE_EDITOR, Permission: models.PERMISSION_VIEW},
|
||||||
|
Search{UsersOrgRole: models.ROLE_VIEWER, RequiredPermission: models.PERMISSION_VIEW},
|
||||||
|
shouldNotFind,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("team ACL", func(t *testing.T) {
|
||||||
|
test(t,
|
||||||
|
DashboardProps{},
|
||||||
|
&DashboardPermission{Team: true, Permission: models.PERMISSION_VIEW},
|
||||||
|
Search{UserFromACL: true, RequiredPermission: models.PERMISSION_VIEW},
|
||||||
|
shouldFind,
|
||||||
|
)
|
||||||
|
|
||||||
|
test(t,
|
||||||
|
DashboardProps{},
|
||||||
|
&DashboardPermission{Team: true, Permission: models.PERMISSION_VIEW},
|
||||||
|
Search{UserFromACL: true, RequiredPermission: models.PERMISSION_EDIT},
|
||||||
|
shouldNotFind,
|
||||||
|
)
|
||||||
|
|
||||||
|
test(t,
|
||||||
|
DashboardProps{},
|
||||||
|
&DashboardPermission{Team: true, Permission: models.PERMISSION_EDIT},
|
||||||
|
Search{UserFromACL: true, RequiredPermission: models.PERMISSION_EDIT},
|
||||||
|
shouldFind,
|
||||||
|
)
|
||||||
|
|
||||||
|
test(t,
|
||||||
|
DashboardProps{},
|
||||||
|
&DashboardPermission{Team: true, Permission: models.PERMISSION_EDIT},
|
||||||
|
Search{UserFromACL: false, RequiredPermission: models.PERMISSION_EDIT},
|
||||||
|
shouldNotFind,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("defaults for user ACL", func(t *testing.T) {
|
||||||
|
test(t,
|
||||||
|
DashboardProps{},
|
||||||
|
nil,
|
||||||
|
Search{OrgId: -1, UsersOrgRole: models.ROLE_VIEWER, RequiredPermission: models.PERMISSION_VIEW},
|
||||||
|
shouldNotFind,
|
||||||
|
)
|
||||||
|
|
||||||
|
test(t,
|
||||||
|
DashboardProps{OrgId: -1},
|
||||||
|
nil,
|
||||||
|
Search{OrgId: -1, UsersOrgRole: models.ROLE_VIEWER, RequiredPermission: models.PERMISSION_VIEW},
|
||||||
|
shouldFind,
|
||||||
|
)
|
||||||
|
|
||||||
|
test(t,
|
||||||
|
DashboardProps{OrgId: -1},
|
||||||
|
nil,
|
||||||
|
Search{OrgId: -1, UsersOrgRole: models.ROLE_EDITOR, RequiredPermission: models.PERMISSION_EDIT},
|
||||||
|
shouldFind,
|
||||||
|
)
|
||||||
|
|
||||||
|
test(t,
|
||||||
|
DashboardProps{OrgId: -1},
|
||||||
|
nil,
|
||||||
|
Search{OrgId: -1, UsersOrgRole: models.ROLE_VIEWER, RequiredPermission: models.PERMISSION_EDIT},
|
||||||
|
shouldNotFind,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var shouldFind = true
|
||||||
|
var shouldNotFind = false
|
||||||
|
|
||||||
|
type DashboardProps struct {
|
||||||
|
OrgId int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type DashboardPermission struct {
|
||||||
|
User bool
|
||||||
|
Team bool
|
||||||
|
Role models.RoleType
|
||||||
|
Permission models.PermissionType
|
||||||
|
}
|
||||||
|
|
||||||
|
type Search struct {
|
||||||
|
UsersOrgRole models.RoleType
|
||||||
|
UserFromACL bool
|
||||||
|
RequiredPermission models.PermissionType
|
||||||
|
OrgId int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type dashboardResponse struct {
|
||||||
|
Id int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func test(t *testing.T, dashboardProps DashboardProps, dashboardPermission *DashboardPermission, search Search, shouldFind bool) {
|
||||||
|
// Will also cleanup the db
|
||||||
|
sqlStore := InitTestDB(t)
|
||||||
|
|
||||||
|
dashboard, err := createDummyDashboard(dashboardProps)
|
||||||
|
if !assert.Equal(t, nil, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var aclUserId int64
|
||||||
|
if dashboardPermission != nil {
|
||||||
|
aclUserId, err = createDummyAcl(dashboardPermission, search, dashboard.Id)
|
||||||
|
if !assert.Equal(t, nil, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dashboards, err := getDashboards(sqlStore, search, aclUserId)
|
||||||
|
if !assert.Equal(t, nil, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if shouldFind {
|
||||||
|
if assert.Equal(t, 1, len(dashboards), "Should return one dashboard") {
|
||||||
|
assert.Equal(t, dashboards[0].Id, dashboard.Id, "Should return created dashboard")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
assert.Equal(t, 0, len(dashboards), "Should node return any dashboard")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createDummyUser() (*models.User, error) {
|
||||||
|
uid := rand.Intn(9999999)
|
||||||
|
createUserCmd := &models.CreateUserCommand{
|
||||||
|
Email: string(uid) + "@example.com",
|
||||||
|
Login: string(uid),
|
||||||
|
Name: string(uid),
|
||||||
|
Company: "",
|
||||||
|
OrgName: "",
|
||||||
|
Password: string(uid),
|
||||||
|
EmailVerified: true,
|
||||||
|
IsAdmin: false,
|
||||||
|
SkipOrgSetup: false,
|
||||||
|
DefaultOrgRole: string(models.ROLE_VIEWER),
|
||||||
|
}
|
||||||
|
err := CreateUser(context.Background(), createUserCmd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &createUserCmd.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createDummyTeam() (*models.Team, error) {
|
||||||
|
cmd := &models.CreateTeamCommand{
|
||||||
|
// Does not matter in this tests actually
|
||||||
|
OrgId: 1,
|
||||||
|
Name: "test",
|
||||||
|
Email: "test@example.com",
|
||||||
|
}
|
||||||
|
err := CreateTeam(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &cmd.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createDummyDashboard(dashboardProps DashboardProps) (*models.Dashboard, error) {
|
||||||
|
json, _ := simplejson.NewJson([]byte(`{"schemaVersion":17,"title":"gdev dashboards","uid":"","version":1}`))
|
||||||
|
|
||||||
|
saveDashboardCmd := &models.SaveDashboardCommand{
|
||||||
|
Dashboard: json,
|
||||||
|
UserId: 0,
|
||||||
|
Overwrite: false,
|
||||||
|
Message: "",
|
||||||
|
RestoredFrom: 0,
|
||||||
|
PluginId: "",
|
||||||
|
FolderId: 0,
|
||||||
|
IsFolder: false,
|
||||||
|
UpdatedAt: time.Time{},
|
||||||
|
}
|
||||||
|
if dashboardProps.OrgId != 0 {
|
||||||
|
saveDashboardCmd.OrgId = dashboardProps.OrgId
|
||||||
|
} else {
|
||||||
|
saveDashboardCmd.OrgId = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
err := SaveDashboard(saveDashboardCmd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return saveDashboardCmd.Result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createDummyAcl(dashboardPermission *DashboardPermission, search Search, dashboardId int64) (int64, error) {
|
||||||
|
acl := &models.DashboardAcl{
|
||||||
|
OrgId: 1,
|
||||||
|
Created: time.Now(),
|
||||||
|
Updated: time.Now(),
|
||||||
|
Permission: dashboardPermission.Permission,
|
||||||
|
DashboardId: dashboardId,
|
||||||
|
}
|
||||||
|
|
||||||
|
var user *models.User
|
||||||
|
var err error
|
||||||
|
if dashboardPermission.User {
|
||||||
|
user, err = createDummyUser()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
acl.UserId = user.Id
|
||||||
|
}
|
||||||
|
|
||||||
|
if dashboardPermission.Team {
|
||||||
|
team, err := createDummyTeam()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if search.UserFromACL {
|
||||||
|
user, err = createDummyUser()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
addTeamMemberCmd := &models.AddTeamMemberCommand{
|
||||||
|
UserId: user.Id,
|
||||||
|
OrgId: 1,
|
||||||
|
TeamId: team.Id,
|
||||||
|
}
|
||||||
|
err = AddTeamMember(addTeamMemberCmd)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
acl.TeamId = team.Id
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(string(dashboardPermission.Role)) > 0 {
|
||||||
|
acl.Role = &dashboardPermission.Role
|
||||||
|
}
|
||||||
|
|
||||||
|
updateAclCmd := &models.UpdateDashboardAclCommand{
|
||||||
|
DashboardId: dashboardId,
|
||||||
|
Items: []*models.DashboardAcl{acl},
|
||||||
|
}
|
||||||
|
err = UpdateDashboardAcl(updateAclCmd)
|
||||||
|
if user != nil {
|
||||||
|
return user.Id, err
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDashboards(sqlStore *SqlStore, search Search, aclUserId int64) ([]*dashboardResponse, error) {
|
||||||
|
builder := &SqlBuilder{}
|
||||||
|
signedInUser := &models.SignedInUser{
|
||||||
|
UserId: 9999999999,
|
||||||
|
}
|
||||||
|
|
||||||
|
if search.OrgId == 0 {
|
||||||
|
signedInUser.OrgId = 1
|
||||||
|
} else {
|
||||||
|
signedInUser.OrgId = search.OrgId
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(string(search.UsersOrgRole)) > 0 {
|
||||||
|
signedInUser.OrgRole = search.UsersOrgRole
|
||||||
|
} else {
|
||||||
|
signedInUser.OrgRole = models.ROLE_VIEWER
|
||||||
|
}
|
||||||
|
if search.UserFromACL {
|
||||||
|
signedInUser.UserId = aclUserId
|
||||||
|
}
|
||||||
|
|
||||||
|
var res []*dashboardResponse
|
||||||
|
builder.Write("SELECT * FROM dashboard WHERE true")
|
||||||
|
builder.writeDashboardPermissionFilter(signedInUser, search.RequiredPermission)
|
||||||
|
err := sqlStore.engine.SQL(builder.GetSqlString(), builder.params...).Find(&res)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
@@ -39,6 +39,11 @@ var (
|
|||||||
const ContextSessionName = "db-session"
|
const ContextSessionName = "db-session"
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
// This change will make xorm use an empty default schema for postgres and
|
||||||
|
// by that mimic the functionality of how it was functioning before
|
||||||
|
// xorm's changes above.
|
||||||
|
xorm.DefaultPostgresSchema = ""
|
||||||
|
|
||||||
registry.Register(®istry.Descriptor{
|
registry.Register(®istry.Descriptor{
|
||||||
Name: "SqlStore",
|
Name: "SqlStore",
|
||||||
Instance: &SqlStore{},
|
Instance: &SqlStore{},
|
||||||
@@ -88,12 +93,12 @@ func (ss *SqlStore) inTransactionWithRetryCtx(ctx context.Context, callback dbTr
|
|||||||
|
|
||||||
err = callback(sess)
|
err = callback(sess)
|
||||||
|
|
||||||
// special handling of database locked errors for sqlite, then we can retry 3 times
|
// special handling of database locked errors for sqlite, then we can retry 5 times
|
||||||
if sqlError, ok := err.(sqlite3.Error); ok && retry < 5 {
|
if sqlError, ok := err.(sqlite3.Error); ok && retry < 5 {
|
||||||
if sqlError.Code == sqlite3.ErrLocked {
|
if sqlError.Code == sqlite3.ErrLocked || sqlError.Code == sqlite3.ErrBusy {
|
||||||
sess.Rollback()
|
sess.Rollback()
|
||||||
time.Sleep(time.Millisecond * time.Duration(10))
|
time.Sleep(time.Millisecond * time.Duration(10))
|
||||||
sqlog.Info("Database table locked, sleeping then retrying", "retry", retry)
|
sqlog.Info("Database locked, sleeping then retrying", "error", err, "retry", retry)
|
||||||
return ss.inTransactionWithRetryCtx(ctx, callback, retry+1)
|
return ss.inTransactionWithRetryCtx(ctx, callback, retry+1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,12 +25,12 @@ func (ss *SqlStore) inTransactionWithRetry(ctx context.Context, fn func(ctx cont
|
|||||||
|
|
||||||
err = fn(withValue)
|
err = fn(withValue)
|
||||||
|
|
||||||
// special handling of database locked errors for sqlite, then we can retry 3 times
|
// special handling of database locked errors for sqlite, then we can retry 5 times
|
||||||
if sqlError, ok := err.(sqlite3.Error); ok && retry < 5 {
|
if sqlError, ok := err.(sqlite3.Error); ok && retry < 5 {
|
||||||
if sqlError.Code == sqlite3.ErrLocked {
|
if sqlError.Code == sqlite3.ErrLocked || sqlError.Code == sqlite3.ErrBusy {
|
||||||
sess.Rollback()
|
sess.Rollback()
|
||||||
time.Sleep(time.Millisecond * time.Duration(10))
|
time.Sleep(time.Millisecond * time.Duration(10))
|
||||||
ss.log.Info("Database table locked, sleeping then retrying", "retry", retry)
|
ss.log.Info("Database locked, sleeping then retrying", "error", err, "retry", retry)
|
||||||
return ss.inTransactionWithRetry(ctx, fn, retry+1)
|
return ss.inTransactionWithRetry(ctx, fn, retry+1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -69,12 +69,12 @@ func inTransactionWithRetryCtx(ctx context.Context, callback dbTransactionFunc,
|
|||||||
|
|
||||||
err = callback(sess)
|
err = callback(sess)
|
||||||
|
|
||||||
// special handling of database locked errors for sqlite, then we can retry 3 times
|
// special handling of database locked errors for sqlite, then we can retry 5 times
|
||||||
if sqlError, ok := err.(sqlite3.Error); ok && retry < 5 {
|
if sqlError, ok := err.(sqlite3.Error); ok && retry < 5 {
|
||||||
if sqlError.Code == sqlite3.ErrLocked {
|
if sqlError.Code == sqlite3.ErrLocked || sqlError.Code == sqlite3.ErrBusy {
|
||||||
sess.Rollback()
|
sess.Rollback()
|
||||||
time.Sleep(time.Millisecond * time.Duration(10))
|
time.Sleep(time.Millisecond * time.Duration(10))
|
||||||
sqlog.Info("Database table locked, sleeping then retrying", "retry", retry)
|
sqlog.Info("Database locked, sleeping then retrying", "error", err, "retry", retry)
|
||||||
return inTransactionWithRetry(callback, retry+1)
|
return inTransactionWithRetry(callback, retry+1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,11 +85,14 @@ func (e *AzureMonitorDatasource) buildQueries(queries []*tsdb.Query, timeRange *
|
|||||||
azlog.Debug("AzureMonitor", "target", azureMonitorTarget)
|
azlog.Debug("AzureMonitor", "target", azureMonitorTarget)
|
||||||
|
|
||||||
urlComponents := map[string]string{}
|
urlComponents := map[string]string{}
|
||||||
|
urlComponents["subscription"] = fmt.Sprintf("%v", query.Model.Get("subscription").MustString())
|
||||||
urlComponents["resourceGroup"] = fmt.Sprintf("%v", azureMonitorTarget["resourceGroup"])
|
urlComponents["resourceGroup"] = fmt.Sprintf("%v", azureMonitorTarget["resourceGroup"])
|
||||||
urlComponents["metricDefinition"] = fmt.Sprintf("%v", azureMonitorTarget["metricDefinition"])
|
urlComponents["metricDefinition"] = fmt.Sprintf("%v", azureMonitorTarget["metricDefinition"])
|
||||||
urlComponents["resourceName"] = fmt.Sprintf("%v", azureMonitorTarget["resourceName"])
|
urlComponents["resourceName"] = fmt.Sprintf("%v", azureMonitorTarget["resourceName"])
|
||||||
|
|
||||||
ub := urlBuilder{
|
ub := urlBuilder{
|
||||||
|
DefaultSubscription: query.DataSource.JsonData.Get("subscriptionId").MustString(),
|
||||||
|
Subscription: urlComponents["subscription"],
|
||||||
ResourceGroup: urlComponents["resourceGroup"],
|
ResourceGroup: urlComponents["resourceGroup"],
|
||||||
MetricDefinition: urlComponents["metricDefinition"],
|
MetricDefinition: urlComponents["metricDefinition"],
|
||||||
ResourceName: urlComponents["resourceName"],
|
ResourceName: urlComponents["resourceName"],
|
||||||
@@ -199,8 +202,7 @@ func (e *AzureMonitorDatasource) createRequest(ctx context.Context, dsInfo *mode
|
|||||||
}
|
}
|
||||||
|
|
||||||
cloudName := dsInfo.JsonData.Get("cloudName").MustString("azuremonitor")
|
cloudName := dsInfo.JsonData.Get("cloudName").MustString("azuremonitor")
|
||||||
subscriptionID := dsInfo.JsonData.Get("subscriptionId").MustString()
|
proxyPass := fmt.Sprintf("%s/subscriptions", cloudName)
|
||||||
proxyPass := fmt.Sprintf("%s/subscriptions/%s", cloudName, subscriptionID)
|
|
||||||
|
|
||||||
u, _ := url.Parse(dsInfo.Url)
|
u, _ := url.Parse(dsInfo.Url)
|
||||||
u.Path = path.Join(u.Path, "render")
|
u.Path = path.Join(u.Path, "render")
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||||
|
"github.com/grafana/grafana/pkg/models"
|
||||||
"github.com/grafana/grafana/pkg/tsdb"
|
"github.com/grafana/grafana/pkg/tsdb"
|
||||||
|
|
||||||
. "github.com/smartystreets/goconvey/convey"
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
@@ -27,7 +28,13 @@ func TestAzureMonitorDatasource(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Queries: []*tsdb.Query{
|
Queries: []*tsdb.Query{
|
||||||
{
|
{
|
||||||
|
DataSource: &models.DataSource{
|
||||||
|
JsonData: simplejson.NewFromAny(map[string]interface{}{
|
||||||
|
"subscriptionId": "default-subscription",
|
||||||
|
}),
|
||||||
|
},
|
||||||
Model: simplejson.NewFromAny(map[string]interface{}{
|
Model: simplejson.NewFromAny(map[string]interface{}{
|
||||||
|
"subscription": "12345678-aaaa-bbbb-cccc-123456789abc",
|
||||||
"azureMonitor": map[string]interface{}{
|
"azureMonitor": map[string]interface{}{
|
||||||
"timeGrain": "PT1M",
|
"timeGrain": "PT1M",
|
||||||
"aggregation": "Average",
|
"aggregation": "Average",
|
||||||
@@ -49,7 +56,7 @@ func TestAzureMonitorDatasource(t *testing.T) {
|
|||||||
|
|
||||||
So(len(queries), ShouldEqual, 1)
|
So(len(queries), ShouldEqual, 1)
|
||||||
So(queries[0].RefID, ShouldEqual, "A")
|
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×pan=2018-03-15T13%3A00%3A00Z%2F2018-03-15T13%3A34%3A00Z")
|
So(queries[0].Target, ShouldEqual, "aggregation=Average&api-version=2018-01-01&interval=PT1M&metricnames=Percentage+CPU×pan=2018-03-15T13%3A00%3A00Z%2F2018-03-15T13%3A34%3A00Z")
|
||||||
So(len(queries[0].Params), ShouldEqual, 5)
|
So(len(queries[0].Params), ShouldEqual, 5)
|
||||||
So(queries[0].Params["timespan"][0], ShouldEqual, "2018-03-15T13:00:00Z/2018-03-15T13:34:00Z")
|
So(queries[0].Params["timespan"][0], ShouldEqual, "2018-03-15T13:00:00Z/2018-03-15T13:34:00Z")
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import (
|
|||||||
|
|
||||||
// urlBuilder builds the URL for calling the Azure Monitor API
|
// urlBuilder builds the URL for calling the Azure Monitor API
|
||||||
type urlBuilder struct {
|
type urlBuilder struct {
|
||||||
|
DefaultSubscription string
|
||||||
|
Subscription string
|
||||||
ResourceGroup string
|
ResourceGroup string
|
||||||
MetricDefinition string
|
MetricDefinition string
|
||||||
ResourceName string
|
ResourceName string
|
||||||
@@ -16,13 +18,19 @@ type urlBuilder struct {
|
|||||||
// should be returned
|
// should be returned
|
||||||
func (ub *urlBuilder) Build() string {
|
func (ub *urlBuilder) Build() string {
|
||||||
|
|
||||||
|
subscription := ub.Subscription
|
||||||
|
|
||||||
|
if ub.Subscription == "" {
|
||||||
|
subscription = ub.DefaultSubscription
|
||||||
|
}
|
||||||
|
|
||||||
if strings.Count(ub.MetricDefinition, "/") > 1 {
|
if strings.Count(ub.MetricDefinition, "/") > 1 {
|
||||||
rn := strings.Split(ub.ResourceName, "/")
|
rn := strings.Split(ub.ResourceName, "/")
|
||||||
lastIndex := strings.LastIndex(ub.MetricDefinition, "/")
|
lastIndex := strings.LastIndex(ub.MetricDefinition, "/")
|
||||||
service := ub.MetricDefinition[lastIndex+1:]
|
service := ub.MetricDefinition[lastIndex+1:]
|
||||||
md := ub.MetricDefinition[0:lastIndex]
|
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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,35 +11,51 @@ func TestURLBuilder(t *testing.T) {
|
|||||||
|
|
||||||
Convey("when metric definition is in the short form", func() {
|
Convey("when metric definition is in the short form", func() {
|
||||||
ub := &urlBuilder{
|
ub := &urlBuilder{
|
||||||
|
DefaultSubscription: "default-sub",
|
||||||
ResourceGroup: "rg",
|
ResourceGroup: "rg",
|
||||||
MetricDefinition: "Microsoft.Compute/virtualMachines",
|
MetricDefinition: "Microsoft.Compute/virtualMachines",
|
||||||
ResourceName: "rn",
|
ResourceName: "rn",
|
||||||
}
|
}
|
||||||
|
|
||||||
url := ub.Build()
|
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() {
|
Convey("when metric definition is Microsoft.Storage/storageAccounts/blobServices", func() {
|
||||||
ub := &urlBuilder{
|
ub := &urlBuilder{
|
||||||
|
DefaultSubscription: "default-sub",
|
||||||
ResourceGroup: "rg",
|
ResourceGroup: "rg",
|
||||||
MetricDefinition: "Microsoft.Storage/storageAccounts/blobServices",
|
MetricDefinition: "Microsoft.Storage/storageAccounts/blobServices",
|
||||||
ResourceName: "rn1/default",
|
ResourceName: "rn1/default",
|
||||||
}
|
}
|
||||||
|
|
||||||
url := ub.Build()
|
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() {
|
Convey("when metric definition is Microsoft.Storage/storageAccounts/fileServices", func() {
|
||||||
ub := &urlBuilder{
|
ub := &urlBuilder{
|
||||||
|
DefaultSubscription: "default-sub",
|
||||||
ResourceGroup: "rg",
|
ResourceGroup: "rg",
|
||||||
MetricDefinition: "Microsoft.Storage/storageAccounts/fileServices",
|
MetricDefinition: "Microsoft.Storage/storageAccounts/fileServices",
|
||||||
ResourceName: "rn1/default",
|
ResourceName: "rn1/default",
|
||||||
}
|
}
|
||||||
|
|
||||||
url := ub.Build()
|
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")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,28 @@
|
|||||||
package errutil
|
package errutil
|
||||||
|
|
||||||
import "golang.org/x/xerrors"
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"golang.org/x/xerrors"
|
||||||
|
)
|
||||||
|
|
||||||
// Wrap is a simple wrapper around Errorf that is doing error wrapping. You can read how that works in
|
// Wrap is a simple wrapper around Errorf that is doing error wrapping. You can read how that works in
|
||||||
// https://godoc.org/golang.org/x/xerrors#Errorf but its API is very implicit which is a reason for this wrapper.
|
// https://godoc.org/golang.org/x/xerrors#Errorf but its API is very implicit which is a reason for this wrapper.
|
||||||
// There is also a discussion (https://github.com/golang/go/issues/29934) where many comments make arguments for such
|
// There is also a discussion (https://github.com/golang/go/issues/29934) where many comments make arguments for such
|
||||||
// wrapper so hopefully it will be added in the standard lib later.
|
// wrapper so hopefully it will be added in the standard lib later.
|
||||||
func Wrap(message string, err error) error {
|
func Wrap(message string, err error) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return xerrors.Errorf("%v: %w", message, err)
|
return xerrors.Errorf("%v: %w", message, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wrapf is a simple wrapper around Errorf that is doing error wrapping
|
||||||
|
// Wrapf allows you to send a format and args instead of just a message.
|
||||||
|
func Wrapf(err error, message string, a ...interface{}) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return Wrap(fmt.Sprintf(message, a...), err)
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -66,3 +67,19 @@ func GetAgeString(t time.Time) string {
|
|||||||
|
|
||||||
return "< 1m"
|
return "< 1m"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToCamelCase changes kebab case, snake case or mixed strings to camel case. See unit test for examples.
|
||||||
|
func ToCamelCase(str string) string {
|
||||||
|
var finalParts []string
|
||||||
|
parts := strings.Split(str, "_")
|
||||||
|
|
||||||
|
for _, part := range parts {
|
||||||
|
finalParts = append(finalParts, strings.Split(part, "-")...)
|
||||||
|
}
|
||||||
|
|
||||||
|
for index, part := range finalParts[1:] {
|
||||||
|
finalParts[index+1] = strings.Title(part)
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(finalParts, "")
|
||||||
|
}
|
||||||
|
|||||||
@@ -37,3 +37,12 @@ func TestDateAge(t *testing.T) {
|
|||||||
So(GetAgeString(time.Now().Add(-time.Hour*24*409)), ShouldEqual, "1y")
|
So(GetAgeString(time.Now().Add(-time.Hour*24*409)), ShouldEqual, "1y")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestToCamelCase(t *testing.T) {
|
||||||
|
Convey("ToCamelCase", t, func() {
|
||||||
|
So(ToCamelCase("kebab-case-string"), ShouldEqual, "kebabCaseString")
|
||||||
|
So(ToCamelCase("snake_case_string"), ShouldEqual, "snakeCaseString")
|
||||||
|
So(ToCamelCase("mixed-case_string"), ShouldEqual, "mixedCaseString")
|
||||||
|
So(ToCamelCase("alreadyCamelCase"), ShouldEqual, "alreadyCamelCase")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ export class HelpCtrl {
|
|||||||
{ keys: ['g', 'h'], description: 'Go to Home Dashboard' },
|
{ keys: ['g', 'h'], description: 'Go to Home Dashboard' },
|
||||||
{ keys: ['g', 'p'], description: 'Go to Profile' },
|
{ keys: ['g', 'p'], description: 'Go to Profile' },
|
||||||
{ keys: ['s', 'o'], description: 'Open search' },
|
{ 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' },
|
{ keys: ['esc'], description: 'Exit edit/setting views' },
|
||||||
],
|
],
|
||||||
Dashboard: [
|
Dashboard: [
|
||||||
|
|||||||
@@ -32,6 +32,10 @@ class SearchQueryParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface OpenSearchParams {
|
||||||
|
query?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export class SearchCtrl {
|
export class SearchCtrl {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
query: SearchQuery;
|
query: SearchQuery;
|
||||||
@@ -88,7 +92,7 @@ export class SearchCtrl {
|
|||||||
appEvents.emit('search-query');
|
appEvents.emit('search-query');
|
||||||
}
|
}
|
||||||
|
|
||||||
openSearch(evt, payload) {
|
openSearch(payload: OpenSearchParams = {}) {
|
||||||
if (this.isOpen) {
|
if (this.isOpen) {
|
||||||
this.closeSearch();
|
this.closeSearch();
|
||||||
return;
|
return;
|
||||||
@@ -99,19 +103,16 @@ export class SearchCtrl {
|
|||||||
this.selectedIndex = -1;
|
this.selectedIndex = -1;
|
||||||
this.results = [];
|
this.results = [];
|
||||||
this.query = {
|
this.query = {
|
||||||
query: evt ? `${evt.query} ` : '',
|
query: payload.query ? `${payload.query} ` : '',
|
||||||
parsedQuery: this.queryParser.parse(evt && evt.query),
|
parsedQuery: this.queryParser.parse(payload.query),
|
||||||
tags: [],
|
tags: [],
|
||||||
starred: false,
|
starred: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.currentSearchId = 0;
|
this.currentSearchId = 0;
|
||||||
this.ignoreClose = true;
|
this.ignoreClose = true;
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
|
|
||||||
if (payload && payload.starred) {
|
|
||||||
this.query.starred = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$timeout(() => {
|
this.$timeout(() => {
|
||||||
this.ignoreClose = false;
|
this.ignoreClose = false;
|
||||||
this.giveSearchFocus = true;
|
this.giveSearchFocus = true;
|
||||||
|
|||||||
@@ -356,7 +356,12 @@ export function seriesDataToLogsModel(seriesData: SeriesData[], intervalMs: numb
|
|||||||
return logsModel;
|
return logsModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return {
|
||||||
|
hasUniqueLabels: false,
|
||||||
|
rows: [],
|
||||||
|
meta: [],
|
||||||
|
series: [],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function logSeriesToLogsModel(logSeries: SeriesData[]): LogsModel {
|
export function logSeriesToLogsModel(logSeries: SeriesData[]): LogsModel {
|
||||||
|
|||||||
@@ -43,21 +43,11 @@ export class KeybindingSrv {
|
|||||||
this.bind('g h', this.goToHome);
|
this.bind('g h', this.goToHome);
|
||||||
this.bind('g a', this.openAlerting);
|
this.bind('g a', this.openAlerting);
|
||||||
this.bind('g p', this.goToProfile);
|
this.bind('g p', this.goToProfile);
|
||||||
this.bind('s s', this.openSearchStarred);
|
|
||||||
this.bind('s o', this.openSearch);
|
this.bind('s o', this.openSearch);
|
||||||
this.bind('s t', this.openSearchTags);
|
|
||||||
this.bind('f', this.openSearch);
|
this.bind('f', this.openSearch);
|
||||||
this.bindGlobal('esc', this.exit);
|
this.bindGlobal('esc', this.exit);
|
||||||
}
|
}
|
||||||
|
|
||||||
openSearchStarred() {
|
|
||||||
appEvents.emit('show-dash-search', { starred: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
openSearchTags() {
|
|
||||||
appEvents.emit('show-dash-search', { tagsMode: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
openSearch() {
|
openSearch() {
|
||||||
appEvents.emit('show-dash-search');
|
appEvents.emit('show-dash-search');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ describe('file_export', () => {
|
|||||||
[0x123, 'some string with \n in the middle', 10.01, false],
|
[0x123, 'some string with \n in the middle', 10.01, false],
|
||||||
[0b1011, 'some string with ; in the middle', -12.34, true],
|
[0b1011, 'some string with ; in the middle', -12.34, true],
|
||||||
[123, 'some string with ;; in the middle', -12.34, true],
|
[123, 'some string with ;; in the middle', -12.34, true],
|
||||||
|
[1234, '=a bogus formula ', '-and another', '+another', '@ref'],
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -108,7 +109,8 @@ describe('file_export', () => {
|
|||||||
'501;"some string with "" at the end""";0.01;false\r\n' +
|
'501;"some string with "" at the end""";0.01;false\r\n' +
|
||||||
'291;"some string with \n in the middle";10.01;false\r\n' +
|
'291;"some string with \n in the middle";10.01;false\r\n' +
|
||||||
'11;"some string with ; in the middle";-12.34;true\r\n' +
|
'11;"some string with ; in the middle";-12.34;true\r\n' +
|
||||||
'123;"some string with ;; in the middle";-12.34;true';
|
'123;"some string with ;; in the middle";-12.34;true\r\n' +
|
||||||
|
'1234;"\'=a bogus formula";"\'-and another";"\'+another";"\'@ref"';
|
||||||
|
|
||||||
expect(returnedText).toBe(expectedText);
|
expect(returnedText).toBe(expectedText);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -333,22 +333,29 @@ describe('LogsParsers', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const emptyLogsModel = {
|
||||||
|
hasUniqueLabels: false,
|
||||||
|
rows: [],
|
||||||
|
meta: [],
|
||||||
|
series: [],
|
||||||
|
};
|
||||||
|
|
||||||
describe('seriesDataToLogsModel', () => {
|
describe('seriesDataToLogsModel', () => {
|
||||||
it('given empty series should return undefined', () => {
|
it('given empty series should return empty logs model', () => {
|
||||||
expect(seriesDataToLogsModel([] as SeriesData[], 0)).toBeUndefined();
|
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[] = [
|
const series: SeriesData[] = [
|
||||||
{
|
{
|
||||||
fields: [],
|
fields: [],
|
||||||
rows: [],
|
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[] = [
|
const series: SeriesData[] = [
|
||||||
{
|
{
|
||||||
fields: [
|
fields: [
|
||||||
@@ -360,10 +367,10 @@ describe('seriesDataToLogsModel', () => {
|
|||||||
rows: [],
|
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[] = [
|
const series: SeriesData[] = [
|
||||||
{
|
{
|
||||||
fields: [
|
fields: [
|
||||||
@@ -375,7 +382,7 @@ describe('seriesDataToLogsModel', () => {
|
|||||||
rows: [],
|
rows: [],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
expect(seriesDataToLogsModel(series, 0)).toBeUndefined();
|
expect(seriesDataToLogsModel(series, 0)).toMatchObject(emptyLogsModel);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('given one series should return expected logs model', () => {
|
it('given one series should return expected logs model', () => {
|
||||||
|
|||||||
@@ -17,7 +17,11 @@ function csvEscaped(text) {
|
|||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
return text.split(QUOTE).join(QUOTE + QUOTE);
|
return text
|
||||||
|
.split(QUOTE)
|
||||||
|
.join(QUOTE + QUOTE)
|
||||||
|
.replace(/^([-+=@])/, "'$1")
|
||||||
|
.replace(/\s+$/, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
const domParser = new DOMParser();
|
const domParser = new DOMParser();
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ exports[`ServerStats Should render table with stats 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="css-17l4171 track-horizontal"
|
className="css-52gpmd track-horizontal"
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"display": "none",
|
"display": "none",
|
||||||
@@ -233,7 +233,7 @@ exports[`ServerStats Should render table with stats 1`] = `
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="css-17l4171 track-vertical"
|
className="css-52gpmd track-vertical"
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"display": "none",
|
"display": "none",
|
||||||
|
|||||||
@@ -60,17 +60,14 @@ export class DashNav extends PureComponent<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onOpenSearch = () => {
|
onDahboardNameClick = () => {
|
||||||
const { dashboard } = this.props;
|
appEvents.emit('show-dash-search');
|
||||||
const haveFolder = dashboard.meta.folderId > 0;
|
};
|
||||||
appEvents.emit(
|
|
||||||
'show-dash-search',
|
onFolderNameClick = () => {
|
||||||
haveFolder
|
appEvents.emit('show-dash-search', {
|
||||||
? {
|
|
||||||
query: 'folder:current',
|
query: 'folder:current',
|
||||||
}
|
});
|
||||||
: null
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
onClose = () => {
|
onClose = () => {
|
||||||
@@ -148,11 +145,20 @@ export class DashNav extends PureComponent<Props> {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div>
|
<div>
|
||||||
<a className="navbar-page-btn" onClick={this.onOpenSearch}>
|
<div className="navbar-page-btn">
|
||||||
{!this.isInFullscreenOrSettings && <i className="gicon gicon-dashboard" />}
|
{!this.isInFullscreenOrSettings && <i className="gicon gicon-dashboard" />}
|
||||||
{haveFolder && <span className="navbar-page-btn--folder">{folderTitle} / </span>}
|
{haveFolder && (
|
||||||
{dashboard.title} <i className="fa fa-caret-down" />
|
<>
|
||||||
|
<a className="navbar-page-btn__folder" onClick={this.onFolderNameClick}>
|
||||||
|
{folderTitle}
|
||||||
</a>
|
</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>
|
</div>
|
||||||
{this.isSettings && <span className="navbar-settings-title"> / Settings</span>}
|
{this.isSettings && <span className="navbar-settings-title"> / Settings</span>}
|
||||||
<div className="navbar__spacer" />
|
<div className="navbar__spacer" />
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ export interface State {
|
|||||||
isFullscreen: boolean;
|
isFullscreen: boolean;
|
||||||
fullscreenPanel: PanelModel | null;
|
fullscreenPanel: PanelModel | null;
|
||||||
scrollTop: number;
|
scrollTop: number;
|
||||||
|
updateScrollTop: number;
|
||||||
rememberScrollTop: number;
|
rememberScrollTop: number;
|
||||||
showLoadingState: boolean;
|
showLoadingState: boolean;
|
||||||
}
|
}
|
||||||
@@ -73,6 +74,7 @@ export class DashboardPage extends PureComponent<Props, State> {
|
|||||||
showLoadingState: false,
|
showLoadingState: false,
|
||||||
fullscreenPanel: null,
|
fullscreenPanel: null,
|
||||||
scrollTop: 0,
|
scrollTop: 0,
|
||||||
|
updateScrollTop: null,
|
||||||
rememberScrollTop: 0,
|
rememberScrollTop: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -168,7 +170,7 @@ export class DashboardPage extends PureComponent<Props, State> {
|
|||||||
isEditing: false,
|
isEditing: false,
|
||||||
isFullscreen: false,
|
isFullscreen: false,
|
||||||
fullscreenPanel: null,
|
fullscreenPanel: null,
|
||||||
scrollTop: this.state.rememberScrollTop,
|
updateScrollTop: this.state.rememberScrollTop,
|
||||||
},
|
},
|
||||||
this.triggerPanelsRendering.bind(this)
|
this.triggerPanelsRendering.bind(this)
|
||||||
);
|
);
|
||||||
@@ -204,7 +206,7 @@ export class DashboardPage extends PureComponent<Props, State> {
|
|||||||
|
|
||||||
setScrollTop = (e: MouseEvent<HTMLElement>): void => {
|
setScrollTop = (e: MouseEvent<HTMLElement>): void => {
|
||||||
const target = e.target as HTMLElement;
|
const target = e.target as HTMLElement;
|
||||||
this.setState({ scrollTop: target.scrollTop });
|
this.setState({ scrollTop: target.scrollTop, updateScrollTop: null });
|
||||||
};
|
};
|
||||||
|
|
||||||
onAddPanel = () => {
|
onAddPanel = () => {
|
||||||
@@ -251,7 +253,7 @@ export class DashboardPage extends PureComponent<Props, State> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { dashboard, editview, $injector, isInitSlow, initError } = this.props;
|
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 (!dashboard) {
|
||||||
if (isInitSlow) {
|
if (isInitSlow) {
|
||||||
@@ -285,9 +287,9 @@ export class DashboardPage extends PureComponent<Props, State> {
|
|||||||
/>
|
/>
|
||||||
<div className="scroll-canvas scroll-canvas--dashboard">
|
<div className="scroll-canvas scroll-canvas--dashboard">
|
||||||
<CustomScrollbar
|
<CustomScrollbar
|
||||||
autoHeightMin={'100%'}
|
autoHeightMin="100%"
|
||||||
setScrollTop={this.setScrollTop}
|
setScrollTop={this.setScrollTop}
|
||||||
scrollTop={scrollTop}
|
scrollTop={updateScrollTop}
|
||||||
updateAfterMountMs={500}
|
updateAfterMountMs={500}
|
||||||
className="custom-scrollbar--page"
|
className="custom-scrollbar--page"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ exports[`DashboardPage Dashboard init completed Should render dashboard grid 1`
|
|||||||
autoHideTimeout={200}
|
autoHideTimeout={200}
|
||||||
className="custom-scrollbar--page"
|
className="custom-scrollbar--page"
|
||||||
hideTracksWhenNotNeeded={false}
|
hideTracksWhenNotNeeded={false}
|
||||||
scrollTop={0}
|
scrollTop={null}
|
||||||
setScrollTop={[Function]}
|
setScrollTop={[Function]}
|
||||||
updateAfterMountMs={500}
|
updateAfterMountMs={500}
|
||||||
>
|
>
|
||||||
@@ -349,7 +349,7 @@ exports[`DashboardPage When dashboard has editview url state should render setti
|
|||||||
autoHideTimeout={200}
|
autoHideTimeout={200}
|
||||||
className="custom-scrollbar--page"
|
className="custom-scrollbar--page"
|
||||||
hideTracksWhenNotNeeded={false}
|
hideTracksWhenNotNeeded={false}
|
||||||
scrollTop={0}
|
scrollTop={null}
|
||||||
setScrollTop={[Function]}
|
setScrollTop={[Function]}
|
||||||
updateAfterMountMs={500}
|
updateAfterMountMs={500}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -205,7 +205,7 @@ export class DashboardGrid extends PureComponent<Props> {
|
|||||||
return false;
|
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 height = panel.gridPos.h * GRID_CELL_HEIGHT + 40;
|
||||||
const bottom = top + height;
|
const bottom = top + height;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
import { react2AngularDirective } from 'app/core/utils/react2angular';
|
|
||||||
import DashboardGrid from './DashboardGrid';
|
|
||||||
|
|
||||||
react2AngularDirective('dashboardGrid', DashboardGrid, [['dashboard', { watchDepth: 'reference' }]]);
|
|
||||||
@@ -250,9 +250,10 @@ export class PanelChrome extends PureComponent<Props, State> {
|
|||||||
{loading === LoadingState.Loading && this.renderLoadingState()}
|
{loading === LoadingState.Loading && this.renderLoadingState()}
|
||||||
<div className="panel-content">
|
<div className="panel-content">
|
||||||
<PanelComponent
|
<PanelComponent
|
||||||
|
id={panel.id}
|
||||||
data={data}
|
data={data}
|
||||||
timeRange={data.request ? data.request.range : this.timeSrv.timeRange()}
|
timeRange={data.request ? data.request.range : this.timeSrv.timeRange()}
|
||||||
options={panel.getOptions(plugin.defaults)}
|
options={panel.getOptions()}
|
||||||
width={width - theme.panelPadding * 2}
|
width={width - theme.panelPadding * 2}
|
||||||
height={innerPanelHeight}
|
height={innerPanelHeight}
|
||||||
renderCounter={renderCounter}
|
renderCounter={renderCounter}
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ export class PanelHeader extends Component<Props, State> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<div className={panelHeaderClass}>
|
||||||
<PanelHeaderCorner
|
<PanelHeaderCorner
|
||||||
panel={panel}
|
panel={panel}
|
||||||
title={panel.title}
|
title={panel.title}
|
||||||
@@ -89,8 +90,12 @@ export class PanelHeader extends Component<Props, State> {
|
|||||||
links={panel.links}
|
links={panel.links}
|
||||||
error={error}
|
error={error}
|
||||||
/>
|
/>
|
||||||
<div className={panelHeaderClass}>
|
<div
|
||||||
<div className="panel-title-container" onClick={this.onMenuToggle} onMouseDown={this.onMouseDown}>
|
className="panel-title-container"
|
||||||
|
onClick={this.onMenuToggle}
|
||||||
|
onMouseDown={this.onMouseDown}
|
||||||
|
aria-label="Panel Title"
|
||||||
|
>
|
||||||
<div className="panel-title">
|
<div className="panel-title">
|
||||||
<span className="icon-gf panel-alert-icon" />
|
<span className="icon-gf panel-alert-icon" />
|
||||||
<span className="panel-title-text">
|
<span className="panel-title-text">
|
||||||
|
|||||||
@@ -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)>
|
||||||
|
`;
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
import './dashgrid/DashboardGridDirective';
|
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
import './services/UnsavedChangesSrv';
|
import './services/UnsavedChangesSrv';
|
||||||
import './services/DashboardLoaderSrv';
|
import './services/DashboardLoaderSrv';
|
||||||
|
|||||||
@@ -53,8 +53,8 @@ export class VisualizationTab extends PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getReactPanelOptions = () => {
|
getReactPanelOptions = () => {
|
||||||
const { panel, plugin } = this.props;
|
const { panel } = this.props;
|
||||||
return panel.getOptions(plugin.defaults);
|
return panel.getOptions();
|
||||||
};
|
};
|
||||||
|
|
||||||
renderPanelOptions() {
|
renderPanelOptions() {
|
||||||
|
|||||||
@@ -7,13 +7,8 @@ describe('PanelModel', () => {
|
|||||||
describe('when creating new panel model', () => {
|
describe('when creating new panel model', () => {
|
||||||
let model;
|
let model;
|
||||||
let modelJson;
|
let modelJson;
|
||||||
|
let persistedOptionsMock;
|
||||||
beforeEach(() => {
|
const defaultOptionsMock = {
|
||||||
modelJson = {
|
|
||||||
type: 'table',
|
|
||||||
showColumns: true,
|
|
||||||
targets: [{ refId: 'A' }, { noRefId: true }],
|
|
||||||
options: {
|
|
||||||
fieldOptions: {
|
fieldOptions: {
|
||||||
thresholds: [
|
thresholds: [
|
||||||
{
|
{
|
||||||
@@ -28,24 +23,54 @@ describe('PanelModel', () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
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: persistedOptionsMock,
|
||||||
|
};
|
||||||
|
|
||||||
model = new PanelModel(modelJson);
|
model = new PanelModel(modelJson);
|
||||||
model.pluginLoaded(
|
const panelPlugin = getPanelPlugin(
|
||||||
getPanelPlugin(
|
|
||||||
{
|
{
|
||||||
id: 'table',
|
id: 'table',
|
||||||
},
|
},
|
||||||
null, // react
|
null, // react
|
||||||
TablePanelCtrl // angular
|
TablePanelCtrl // angular
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
panelPlugin.setDefaults(defaultOptionsMock);
|
||||||
|
model.pluginLoaded(panelPlugin);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should apply defaults', () => {
|
it('should apply defaults', () => {
|
||||||
expect(model.gridPos.h).toBe(3);
|
expect(model.gridPos.h).toBe(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should apply option defaults', () => {
|
||||||
|
expect(model.getOptions().showThresholds).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
it('should set model props on instance', () => {
|
it('should set model props on instance', () => {
|
||||||
expect(model.showColumns).toBe(true);
|
expect(model.showColumns).toBe(true);
|
||||||
});
|
});
|
||||||
@@ -89,11 +114,22 @@ describe('PanelModel', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('when changing panel type', () => {
|
describe('when changing panel type', () => {
|
||||||
|
const newPanelPluginDefaults = {
|
||||||
|
showThresholdLabels: false,
|
||||||
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
model.changePlugin(getPanelPlugin({ id: 'graph' }));
|
const newPlugin = getPanelPlugin({ id: 'graph' });
|
||||||
|
newPlugin.setDefaults(newPanelPluginDefaults);
|
||||||
|
model.changePlugin(newPlugin);
|
||||||
model.alert = { id: 2 };
|
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', () => {
|
it('should remove table properties but keep core props', () => {
|
||||||
expect(model.showColumns).toBe(undefined);
|
expect(model.showColumns).toBe(undefined);
|
||||||
});
|
});
|
||||||
@@ -153,19 +189,5 @@ describe('PanelModel', () => {
|
|||||||
expect(panelQueryRunner).toBe(sameQueryRunner);
|
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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -157,8 +157,8 @@ export class PanelModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getOptions(panelDefaults: any) {
|
getOptions() {
|
||||||
return _.defaultsDeep(this.options || {}, panelDefaults);
|
return this.options;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateOptions(options: object) {
|
updateOptions(options: object) {
|
||||||
@@ -179,7 +179,6 @@ export class PanelModel {
|
|||||||
|
|
||||||
model[property] = _.cloneDeep(this[property]);
|
model[property] = _.cloneDeep(this[property]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return model;
|
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) {
|
pluginLoaded(plugin: PanelPlugin) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
|
|
||||||
|
this.applyPluginOptionDefaults(plugin);
|
||||||
|
|
||||||
if (plugin.panel && plugin.onPanelMigration) {
|
if (plugin.panel && plugin.onPanelMigration) {
|
||||||
const version = getPluginVersion(plugin);
|
const version = getPluginVersion(plugin);
|
||||||
if (version !== this.pluginVersion) {
|
if (version !== this.pluginVersion) {
|
||||||
@@ -284,7 +292,7 @@ export class PanelModel {
|
|||||||
// switch
|
// switch
|
||||||
this.type = pluginId;
|
this.type = pluginId;
|
||||||
this.plugin = newPlugin;
|
this.plugin = newPlugin;
|
||||||
|
this.applyPluginOptionDefaults(newPlugin);
|
||||||
// Let panel plugins inspect options from previous panel and keep any that it can use
|
// Let panel plugins inspect options from previous panel and keep any that it can use
|
||||||
if (newPlugin.onPanelTypeChanged) {
|
if (newPlugin.onPanelTypeChanged) {
|
||||||
this.options = this.options || {};
|
this.options = this.options || {};
|
||||||
@@ -324,7 +332,7 @@ export class PanelModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hasTitle() {
|
hasTitle() {
|
||||||
return !!this.title.length;
|
return this.title && this.title.length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
|
|||||||
@@ -97,9 +97,6 @@ export class PanelQueryRunner {
|
|||||||
delayStateNotification,
|
delayStateNotification,
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
// filter out hidden queries & deep clone them
|
|
||||||
const clonedAndFilteredQueries = cloneDeep(queries.filter(q => !q.hide));
|
|
||||||
|
|
||||||
const request: DataQueryRequest = {
|
const request: DataQueryRequest = {
|
||||||
requestId: getNextRequestId(),
|
requestId: getNextRequestId(),
|
||||||
timezone,
|
timezone,
|
||||||
@@ -109,7 +106,7 @@ export class PanelQueryRunner {
|
|||||||
timeInfo,
|
timeInfo,
|
||||||
interval: '',
|
interval: '',
|
||||||
intervalMs: 0,
|
intervalMs: 0,
|
||||||
targets: clonedAndFilteredQueries,
|
targets: cloneDeep(queries),
|
||||||
maxDataPoints: maxDataPoints || widthPixels,
|
maxDataPoints: maxDataPoints || widthPixels,
|
||||||
scopedVars: scopedVars || {},
|
scopedVars: scopedVars || {},
|
||||||
cacheTimeout,
|
cacheTimeout,
|
||||||
@@ -124,6 +121,10 @@ export class PanelQueryRunner {
|
|||||||
try {
|
try {
|
||||||
const ds = await getDataSource(datasource, request.scopedVars);
|
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
|
// Attach the datasource name to each query
|
||||||
request.targets = request.targets.map(query => {
|
request.targets = request.targets.map(query => {
|
||||||
if (!query.datasource) {
|
if (!query.datasource) {
|
||||||
|
|||||||
@@ -275,7 +275,6 @@ export class Explore extends React.PureComponent<ExploreProps> {
|
|||||||
<LogsContainer
|
<LogsContainer
|
||||||
width={width}
|
width={width}
|
||||||
exploreId={exploreId}
|
exploreId={exploreId}
|
||||||
onChangeTime={this.onChangeTime}
|
|
||||||
onClickLabel={this.onClickLabel}
|
onClickLabel={this.onClickLabel}
|
||||||
onStartScanning={this.onStartScanning}
|
onStartScanning={this.onStartScanning}
|
||||||
onStopScanning={this.onStopScanning}
|
onStopScanning={this.onStopScanning}
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import { hot } from 'react-hot-loader';
|
import { hot } from 'react-hot-loader';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
import moment from 'moment';
|
||||||
import { RawTimeRange, TimeRange, LogLevel, TimeZone, AbsoluteTimeRange } from '@grafana/ui';
|
import { RawTimeRange, TimeRange, LogLevel, TimeZone, AbsoluteTimeRange } from '@grafana/ui';
|
||||||
|
|
||||||
import { ExploreId, ExploreItemState } from 'app/types/explore';
|
import { ExploreId, ExploreItemState } from 'app/types/explore';
|
||||||
import { LogsModel, LogsDedupStrategy } from 'app/core/logs_model';
|
import { LogsModel, LogsDedupStrategy } from 'app/core/logs_model';
|
||||||
import { StoreState } from 'app/types';
|
import { StoreState } from 'app/types';
|
||||||
|
|
||||||
import { toggleLogs, changeDedupStrategy } from './state/actions';
|
import { toggleLogs, changeDedupStrategy, changeTime } from './state/actions';
|
||||||
import Logs from './Logs';
|
import Logs from './Logs';
|
||||||
import Panel from './Panel';
|
import Panel from './Panel';
|
||||||
import { toggleLogLevelAction } from 'app/features/explore/state/actionTypes';
|
import { toggleLogLevelAction } from 'app/features/explore/state/actionTypes';
|
||||||
@@ -20,7 +21,6 @@ interface LogsContainerProps {
|
|||||||
logsHighlighterExpressions?: string[];
|
logsHighlighterExpressions?: string[];
|
||||||
logsResult?: LogsModel;
|
logsResult?: LogsModel;
|
||||||
dedupedResult?: LogsModel;
|
dedupedResult?: LogsModel;
|
||||||
onChangeTime: (range: AbsoluteTimeRange) => void;
|
|
||||||
onClickLabel: (key: string, value: string) => void;
|
onClickLabel: (key: string, value: string) => void;
|
||||||
onStartScanning: () => void;
|
onStartScanning: () => void;
|
||||||
onStopScanning: () => void;
|
onStopScanning: () => void;
|
||||||
@@ -35,9 +35,19 @@ interface LogsContainerProps {
|
|||||||
dedupStrategy: LogsDedupStrategy;
|
dedupStrategy: LogsDedupStrategy;
|
||||||
hiddenLogLevels: Set<LogLevel>;
|
hiddenLogLevels: Set<LogLevel>;
|
||||||
width: number;
|
width: number;
|
||||||
|
changeTime: typeof changeTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class LogsContainer extends PureComponent<LogsContainerProps> {
|
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 = () => {
|
onClickLogsButton = () => {
|
||||||
this.props.toggleLogs(this.props.exploreId, this.props.showingLogs);
|
this.props.toggleLogs(this.props.exploreId, this.props.showingLogs);
|
||||||
};
|
};
|
||||||
@@ -61,7 +71,6 @@ export class LogsContainer extends PureComponent<LogsContainerProps> {
|
|||||||
logsHighlighterExpressions,
|
logsHighlighterExpressions,
|
||||||
logsResult,
|
logsResult,
|
||||||
dedupedResult,
|
dedupedResult,
|
||||||
onChangeTime,
|
|
||||||
onClickLabel,
|
onClickLabel,
|
||||||
onStartScanning,
|
onStartScanning,
|
||||||
onStopScanning,
|
onStopScanning,
|
||||||
@@ -83,7 +92,7 @@ export class LogsContainer extends PureComponent<LogsContainerProps> {
|
|||||||
exploreId={exploreId}
|
exploreId={exploreId}
|
||||||
highlighterExpressions={logsHighlighterExpressions}
|
highlighterExpressions={logsHighlighterExpressions}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
onChangeTime={onChangeTime}
|
onChangeTime={this.onChangeTime}
|
||||||
onClickLabel={onClickLabel}
|
onClickLabel={onClickLabel}
|
||||||
onStartScanning={onStartScanning}
|
onStartScanning={onStartScanning}
|
||||||
onStopScanning={onStopScanning}
|
onStopScanning={onStopScanning}
|
||||||
@@ -130,6 +139,7 @@ const mapDispatchToProps = {
|
|||||||
toggleLogs,
|
toggleLogs,
|
||||||
changeDedupStrategy,
|
changeDedupStrategy,
|
||||||
toggleLogLevelAction,
|
toggleLogLevelAction,
|
||||||
|
changeTime,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default hot(module)(
|
export default hot(module)(
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export default class Table extends PureComponent<TableProps> {
|
|||||||
if (e.target) {
|
if (e.target) {
|
||||||
const link = e.target as HTMLElement;
|
const link = e.target as HTMLElement;
|
||||||
if (link.className === 'link') {
|
if (link.className === 'link') {
|
||||||
const columnKey = column.Header;
|
const columnKey = column.Header().props.title;
|
||||||
const rowValue = rowInfo.row[columnKey];
|
const rowValue = rowInfo.row[columnKey];
|
||||||
this.props.onClickCell(columnKey, rowValue);
|
this.props.onClickCell(columnKey, rowValue);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -220,6 +220,7 @@ export interface LoadExploreDataSourcesPayload {
|
|||||||
|
|
||||||
export interface RunQueriesPayload {
|
export interface RunQueriesPayload {
|
||||||
exploreId: ExploreId;
|
exploreId: ExploreId;
|
||||||
|
range: TimeRange;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -559,6 +559,7 @@ export function runQueries(exploreId: ExploreId, ignoreUIState = false): ThunkRe
|
|||||||
supportsTable,
|
supportsTable,
|
||||||
datasourceError,
|
datasourceError,
|
||||||
containerWidth,
|
containerWidth,
|
||||||
|
range,
|
||||||
} = getState().explore[exploreId];
|
} = getState().explore[exploreId];
|
||||||
|
|
||||||
if (datasourceError) {
|
if (datasourceError) {
|
||||||
@@ -576,7 +577,10 @@ export function runQueries(exploreId: ExploreId, ignoreUIState = false): ThunkRe
|
|||||||
// but we're using the datasource interval limit for now
|
// but we're using the datasource interval limit for now
|
||||||
const interval = datasourceInstance.interval;
|
const interval = datasourceInstance.interval;
|
||||||
|
|
||||||
dispatch(runQueriesAction({ exploreId }));
|
const timeZone = getTimeZone(getState().user);
|
||||||
|
const updatedRange = getTimeRange(timeZone, range.raw);
|
||||||
|
|
||||||
|
dispatch(runQueriesAction({ exploreId, range: updatedRange }));
|
||||||
// Keep table queries first since they need to return quickly
|
// Keep table queries first since they need to return quickly
|
||||||
const tableQueriesPromise =
|
const tableQueriesPromise =
|
||||||
(ignoreUIState || showingTable) && supportsTable
|
(ignoreUIState || showingTable) && supportsTable
|
||||||
|
|||||||
@@ -568,8 +568,9 @@ export const itemReducer = reducerFactory<ExploreItemState>({} as ExploreItemSta
|
|||||||
})
|
})
|
||||||
.addMapper({
|
.addMapper({
|
||||||
filter: runQueriesAction,
|
filter: runQueriesAction,
|
||||||
mapper: (state): ExploreItemState => {
|
mapper: (state, action): ExploreItemState => {
|
||||||
const { range, datasourceInstance, containerWidth } = state;
|
const { range } = action.payload;
|
||||||
|
const { datasourceInstance, containerWidth } = state;
|
||||||
let interval = '1s';
|
let interval = '1s';
|
||||||
if (datasourceInstance && datasourceInstance.interval) {
|
if (datasourceInstance && datasourceInstance.interval) {
|
||||||
interval = datasourceInstance.interval;
|
interval = datasourceInstance.interval;
|
||||||
@@ -577,6 +578,7 @@ export const itemReducer = reducerFactory<ExploreItemState>({} as ExploreItemSta
|
|||||||
const queryIntervals = getIntervals(range, interval, containerWidth);
|
const queryIntervals = getIntervals(range, interval, containerWidth);
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
range,
|
||||||
queryIntervals,
|
queryIntervals,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import { PluginHelp } from 'app/core/components/PluginHelp/PluginHelp';
|
|||||||
import { AppConfigCtrlWrapper } from './wrappers/AppConfigWrapper';
|
import { AppConfigCtrlWrapper } from './wrappers/AppConfigWrapper';
|
||||||
import { PluginDashboards } from './PluginDashboards';
|
import { PluginDashboards } from './PluginDashboards';
|
||||||
import { appEvents } from 'app/core/core';
|
import { appEvents } from 'app/core/core';
|
||||||
|
import { config } from 'app/core/config';
|
||||||
|
|
||||||
export function getLoadingNav(): NavModel {
|
export function getLoadingNav(): NavModel {
|
||||||
const node = {
|
const node = {
|
||||||
@@ -93,6 +94,8 @@ class PluginPage extends PureComponent<Props, State> {
|
|||||||
|
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
const { pluginId, path, query } = this.props;
|
const { pluginId, path, query } = this.props;
|
||||||
|
const { appSubUrl } = config;
|
||||||
|
|
||||||
const plugin = await loadPlugin(pluginId);
|
const plugin = await loadPlugin(pluginId);
|
||||||
if (!plugin) {
|
if (!plugin) {
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -109,7 +112,7 @@ class PluginPage extends PureComponent<Props, State> {
|
|||||||
tabs.push({
|
tabs.push({
|
||||||
text: 'Readme',
|
text: 'Readme',
|
||||||
icon: 'fa fa-fw fa-file-text-o',
|
icon: 'fa fa-fw fa-file-text-o',
|
||||||
url: path + '?tab=' + TAB_ID_README,
|
url: `${appSubUrl}${path}?tab=${TAB_ID_README}`,
|
||||||
id: TAB_ID_README,
|
id: TAB_ID_README,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -121,7 +124,7 @@ class PluginPage extends PureComponent<Props, State> {
|
|||||||
tabs.push({
|
tabs.push({
|
||||||
text: 'Config',
|
text: 'Config',
|
||||||
icon: 'gicon gicon-cog',
|
icon: 'gicon gicon-cog',
|
||||||
url: path + '?tab=' + TAB_ID_CONFIG_CTRL,
|
url: `${appSubUrl}${path}?tab=${TAB_ID_CONFIG_CTRL}`,
|
||||||
id: TAB_ID_CONFIG_CTRL,
|
id: TAB_ID_CONFIG_CTRL,
|
||||||
});
|
});
|
||||||
defaultTab = TAB_ID_CONFIG_CTRL;
|
defaultTab = TAB_ID_CONFIG_CTRL;
|
||||||
@@ -146,7 +149,7 @@ class PluginPage extends PureComponent<Props, State> {
|
|||||||
tabs.push({
|
tabs.push({
|
||||||
text: 'Dashboards',
|
text: 'Dashboards',
|
||||||
icon: 'gicon gicon-dashboard',
|
icon: 'gicon gicon-dashboard',
|
||||||
url: path + '?tab=' + TAB_ID_DASHBOARDS,
|
url: `${appSubUrl}${path}?tab=${TAB_ID_DASHBOARDS}`,
|
||||||
id: TAB_ID_DASHBOARDS,
|
id: TAB_ID_DASHBOARDS,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -161,7 +164,7 @@ class PluginPage extends PureComponent<Props, State> {
|
|||||||
img: meta.info.logos.large,
|
img: meta.info.logos.large,
|
||||||
subTitle: meta.info.author.name,
|
subTitle: meta.info.author.name,
|
||||||
breadcrumbs: [{ title: 'Plugins', url: '/plugins' }],
|
breadcrumbs: [{ title: 'Plugins', url: '/plugins' }],
|
||||||
url: path,
|
url: `${appSubUrl}${path}`,
|
||||||
children: this.setActiveTab(query.tab as string, tabs, defaultTab),
|
children: this.setActiveTab(query.tab as string, tabs, defaultTab),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,29 @@
|
|||||||
import React, { FC, useContext } from 'react';
|
import React, { FC, useContext } from 'react';
|
||||||
import { css } from 'emotion';
|
import { css } from 'emotion';
|
||||||
import { PluginState, Tooltip, ThemeContext } from '@grafana/ui';
|
import { PluginState, Tooltip, ThemeContext } from '@grafana/ui';
|
||||||
|
import { PopperContent } from '@grafana/ui/src/components/Tooltip/PopperController';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
state?: PluginState;
|
state?: PluginState;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPluginStateInfoText(state?: PluginState): string | null {
|
function getPluginStateInfoText(state?: PluginState): PopperContent<any> | null {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case PluginState.alpha:
|
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:
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -34,10 +45,11 @@ const PluginStateinfo: FC<Props> = props => {
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
margin-left: 16px;
|
margin-left: 16px;
|
||||||
|
cursor: help;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip content={text}>
|
<Tooltip content={text} theme={'info'} placement={'top'}>
|
||||||
<div className={styles}>
|
<div className={styles}>
|
||||||
<i className="fa fa-warning" /> {props.state}
|
<i className="fa fa-warning" /> {props.state}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ exposeToPlugin('app/core/services/backend_srv', {
|
|||||||
});
|
});
|
||||||
|
|
||||||
exposeToPlugin('app/plugins/sdk', sdk);
|
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/file_export', fileExport);
|
||||||
exposeToPlugin('app/core/utils/flatten', flatten);
|
exposeToPlugin('app/core/utils/flatten', flatten);
|
||||||
exposeToPlugin('app/core/utils/kbn', kbn);
|
exposeToPlugin('app/core/utils/kbn', kbn);
|
||||||
|
|||||||
@@ -141,6 +141,7 @@ export default class CloudWatchDatasource implements DataSourceApi<CloudWatchQue
|
|||||||
if (res.results) {
|
if (res.results) {
|
||||||
for (const query of request.queries) {
|
for (const query of request.queries) {
|
||||||
const queryRes = res.results[query.refId];
|
const queryRes = res.results[query.refId];
|
||||||
|
if (queryRes) {
|
||||||
for (const series of queryRes.series) {
|
for (const series of queryRes.series) {
|
||||||
const s = { target: series.name, datapoints: series.points } as any;
|
const s = { target: series.name, datapoints: series.points } as any;
|
||||||
if (queryRes.meta.unit) {
|
if (queryRes.meta.unit) {
|
||||||
@@ -150,6 +151,7 @@ export default class CloudWatchDatasource implements DataSourceApi<CloudWatchQue
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return { data: data };
|
return { data: data };
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
"name": "CloudWatch",
|
"name": "CloudWatch",
|
||||||
"id": "cloudwatch",
|
"id": "cloudwatch",
|
||||||
|
|
||||||
|
"hiddenQueries": true,
|
||||||
"metrics": true,
|
"metrics": true,
|
||||||
"alerting": true,
|
"alerting": true,
|
||||||
"annotations": true,
|
"annotations": true,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
<div class="gf-form" ng-if="ctrl.target.queryType === 'Azure Monitor' || ctrl.target.queryType === 'Azure Log Analytics'">
|
<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>
|
<label class="gf-form-label query-keyword width-9">Subscription</label>
|
||||||
<gf-form-dropdown model="ctrl.target.subscription" allow-custom="true" lookup-text="true"
|
<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>
|
</gf-form-dropdown>
|
||||||
</div>
|
</div>
|
||||||
<div class="gf-form gf-form--grow">
|
<div class="gf-form gf-form--grow">
|
||||||
@@ -121,6 +121,7 @@
|
|||||||
<div class="gf-form-label gf-form-label--grow"></div>
|
<div class="gf-form-label gf-form-label--grow"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="gf-form gf-form--grow">
|
<div class="gf-form gf-form--grow">
|
||||||
<kusto-editor
|
<kusto-editor
|
||||||
|
|||||||
@@ -189,11 +189,19 @@ describe('AzureMonitorQueryCtrl', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
queryCtrl.target.subscription = 'sub1';
|
||||||
queryCtrl.target.azureMonitor.resourceGroup = 'test';
|
queryCtrl.target.azureMonitor.resourceGroup = 'test';
|
||||||
queryCtrl.target.azureMonitor.metricDefinition = 'Microsoft.Compute/virtualMachines';
|
queryCtrl.target.azureMonitor.metricDefinition = 'Microsoft.Compute/virtualMachines';
|
||||||
queryCtrl.target.azureMonitor.resourceName = 'test';
|
queryCtrl.target.azureMonitor.resourceName = 'test';
|
||||||
queryCtrl.target.azureMonitor.metricName = 'Percentage CPU';
|
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(resourceGroup).toBe('test');
|
||||||
expect(metricDefinition).toBe('Microsoft.Compute/virtualMachines');
|
expect(metricDefinition).toBe('Microsoft.Compute/virtualMachines');
|
||||||
expect(resourceName).toBe('test');
|
expect(resourceName).toBe('test');
|
||||||
|
|||||||
@@ -197,6 +197,8 @@ export class AzureMonitorQueryCtrl extends QueryCtrl {
|
|||||||
if (!this.target.subscription && this.subscriptions.length > 0) {
|
if (!this.target.subscription && this.subscriptions.length > 0) {
|
||||||
this.target.subscription = this.subscriptions[0].value;
|
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') {
|
if (this.target.queryType === 'Azure Log Analytics') {
|
||||||
return this.getWorkspaces();
|
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 */
|
/* Azure Monitor Section */
|
||||||
@@ -282,6 +296,9 @@ export class AzureMonitorQueryCtrl extends QueryCtrl {
|
|||||||
this.target.azureMonitor.metricDefinition = this.defaultDropdownValue;
|
this.target.azureMonitor.metricDefinition = this.defaultDropdownValue;
|
||||||
this.target.azureMonitor.resourceName = this.defaultDropdownValue;
|
this.target.azureMonitor.resourceName = this.defaultDropdownValue;
|
||||||
this.target.azureMonitor.metricName = 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.dimensions = [];
|
||||||
this.target.azureMonitor.dimension = '';
|
this.target.azureMonitor.dimension = '';
|
||||||
}
|
}
|
||||||
@@ -289,12 +306,18 @@ export class AzureMonitorQueryCtrl extends QueryCtrl {
|
|||||||
onMetricDefinitionChange() {
|
onMetricDefinitionChange() {
|
||||||
this.target.azureMonitor.resourceName = this.defaultDropdownValue;
|
this.target.azureMonitor.resourceName = this.defaultDropdownValue;
|
||||||
this.target.azureMonitor.metricName = 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.dimensions = [];
|
||||||
this.target.azureMonitor.dimension = '';
|
this.target.azureMonitor.dimension = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
onResourceNameChange() {
|
onResourceNameChange() {
|
||||||
this.target.azureMonitor.metricName = 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.dimensions = [];
|
||||||
this.target.azureMonitor.dimension = '';
|
this.target.azureMonitor.dimension = '';
|
||||||
}
|
}
|
||||||
@@ -306,6 +329,7 @@ export class AzureMonitorQueryCtrl extends QueryCtrl {
|
|||||||
|
|
||||||
return this.datasource
|
return this.datasource
|
||||||
.getMetricMetadata(
|
.getMetricMetadata(
|
||||||
|
this.replace(this.target.subscription),
|
||||||
this.replace(this.target.azureMonitor.resourceGroup),
|
this.replace(this.target.azureMonitor.resourceGroup),
|
||||||
this.replace(this.target.azureMonitor.metricDefinition),
|
this.replace(this.target.azureMonitor.metricDefinition),
|
||||||
this.replace(this.target.azureMonitor.resourceName),
|
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.aggOptions = metadata.supportedAggTypes || [metadata.primaryAggType];
|
||||||
this.target.azureMonitor.aggregation = metadata.primaryAggType;
|
this.target.azureMonitor.aggregation = metadata.primaryAggType;
|
||||||
this.target.azureMonitor.timeGrains = [{ text: 'auto', value: 'auto' }].concat(metadata.supportedTimeGrains);
|
this.target.azureMonitor.timeGrains = [{ text: 'auto', value: 'auto' }].concat(metadata.supportedTimeGrains);
|
||||||
|
this.target.azureMonitor.timeGrain = 'auto';
|
||||||
|
|
||||||
this.target.azureMonitor.dimensions = metadata.dimensions;
|
this.target.azureMonitor.dimensions = metadata.dimensions;
|
||||||
if (metadata.dimensions.length > 0) {
|
if (metadata.dimensions.length > 0) {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
"includes": [{ "type": "dashboard", "name": "Graphite Carbon Metrics", "path": "dashboards/carbon_metrics.json" }],
|
"includes": [{ "type": "dashboard", "name": "Graphite Carbon Metrics", "path": "dashboards/carbon_metrics.json" }],
|
||||||
|
|
||||||
|
"hiddenQueries": true,
|
||||||
"metrics": true,
|
"metrics": true,
|
||||||
"alerting": true,
|
"alerting": true,
|
||||||
"annotations": true,
|
"annotations": true,
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export default class InfluxDatasource {
|
|||||||
this.withCredentials = instanceSettings.withCredentials;
|
this.withCredentials = instanceSettings.withCredentials;
|
||||||
this.interval = (instanceSettings.jsonData || {}).timeInterval;
|
this.interval = (instanceSettings.jsonData || {}).timeInterval;
|
||||||
this.responseParser = new ResponseParser();
|
this.responseParser = new ResponseParser();
|
||||||
this.httpMode = instanceSettings.jsonData.httpMode;
|
this.httpMode = instanceSettings.jsonData.httpMode || 'GET';
|
||||||
}
|
}
|
||||||
|
|
||||||
query(options) {
|
query(options) {
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ export class BarGaugePanelEditor extends PureComponent<PanelEditorProps<BarGauge
|
|||||||
title="Field"
|
title="Field"
|
||||||
showMinMax={true}
|
showMinMax={true}
|
||||||
onChange={this.onDefaultsChange}
|
onChange={this.onDefaultsChange}
|
||||||
options={fieldOptions.defaults}
|
value={fieldOptions.defaults}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={fieldOptions.thresholds} />
|
<ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={fieldOptions.thresholds} />
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import React, { PureComponent } from 'react';
|
|||||||
import { config } from 'app/core/config';
|
import { config } from 'app/core/config';
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import { Gauge, FieldDisplay, getFieldDisplayValues } from '@grafana/ui';
|
import { Gauge, FieldDisplay, getFieldDisplayValues, VizOrientation } from '@grafana/ui';
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import { GaugeOptions } from './types';
|
import { GaugeOptions } from './types';
|
||||||
@@ -43,7 +43,7 @@ export class GaugePanel extends PureComponent<PanelProps<GaugeOptions>> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { height, width, options, data, renderCounter } = this.props;
|
const { height, width, data, renderCounter } = this.props;
|
||||||
return (
|
return (
|
||||||
<VizRepeater
|
<VizRepeater
|
||||||
getValues={this.getValues}
|
getValues={this.getValues}
|
||||||
@@ -52,7 +52,7 @@ export class GaugePanel extends PureComponent<PanelProps<GaugeOptions>> {
|
|||||||
height={height}
|
height={height}
|
||||||
source={data}
|
source={data}
|
||||||
renderCounter={renderCounter}
|
renderCounter={renderCounter}
|
||||||
orientation={options.orientation}
|
orientation={VizOrientation.Auto}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ export class GaugePanelEditor extends PureComponent<PanelEditorProps<GaugeOption
|
|||||||
title="Field"
|
title="Field"
|
||||||
showMinMax={true}
|
showMinMax={true}
|
||||||
onChange={this.onDefaultsChange}
|
onChange={this.onDefaultsChange}
|
||||||
options={fieldOptions.defaults}
|
value={fieldOptions.defaults}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={fieldOptions.thresholds} />
|
<ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={fieldOptions.thresholds} />
|
||||||
|
|||||||
174
public/app/plugins/panel/gettingstarted/GettingStarted.tsx
Normal file
174
public/app/plugins/panel/gettingstarted/GettingStarted.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user