Compare commits
299 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d9386cc2c3 | ||
|
|
38f348e642 | ||
|
|
656ec9c48f | ||
|
|
f5e8f9334b | ||
|
|
7be2105fd9 | ||
|
|
49ee388dcf | ||
|
|
6cb4b4061c | ||
|
|
85e50ece2e | ||
|
|
32ae0ea13e | ||
|
|
1b3bddd622 | ||
|
|
731bb6ba03 | ||
|
|
fed06ef97d | ||
|
|
a58330f4d8 | ||
|
|
5a46c2397b | ||
|
|
f79588c191 | ||
|
|
9f766557f1 | ||
|
|
846cf934f5 | ||
|
|
162eb4ca35 | ||
|
|
2f18444a43 | ||
|
|
8a4ff5bddc | ||
|
|
4ae7648bea | ||
|
|
dc75559758 | ||
|
|
acd944a649 | ||
|
|
3c1b30e3c1 | ||
|
|
5daefc8b8e | ||
|
|
715b9cbad0 | ||
|
|
7194e91d3b | ||
|
|
69d56b8ed7 | ||
|
|
982a5b1a39 | ||
|
|
6b8cb4ac7f | ||
|
|
397f253180 | ||
|
|
f93b6f7d85 | ||
|
|
edb2cf2cf2 | ||
|
|
fb12dd4688 | ||
|
|
0a561e5aeb | ||
|
|
c140c8cac9 | ||
|
|
06ab063671 | ||
|
|
1591a486cc | ||
|
|
b53efed1ef | ||
|
|
7a202db5ad | ||
|
|
af1ae7cab4 | ||
|
|
24519cbf78 | ||
|
|
01305462aa | ||
|
|
7a4077405e | ||
|
|
9594effb6c | ||
|
|
e750498696 | ||
|
|
69e18905f5 | ||
|
|
ac4524cf9b | ||
|
|
7baad7ff10 | ||
|
|
d7ef6daeb8 | ||
|
|
93e3908a63 | ||
|
|
bf5f6ce97c | ||
|
|
ed2ca5fced | ||
|
|
8dfe85f23e | ||
|
|
846992930c | ||
|
|
a1d652d578 | ||
|
|
d0e057722b | ||
|
|
d1be4e2a90 | ||
|
|
3cde783d1d | ||
|
|
e933369f56 | ||
|
|
e109f8d69c | ||
|
|
d198057eaf | ||
|
|
88c2f18b20 | ||
|
|
a30a604228 | ||
|
|
62b58d8bb0 | ||
|
|
a5951592f7 | ||
|
|
6392d6514e | ||
|
|
91d6641326 | ||
|
|
dd398f73c2 | ||
|
|
de10bd4ef6 | ||
|
|
56321da9c1 | ||
|
|
d0d1c5ea5f | ||
|
|
e16872c864 | ||
|
|
1e425244d2 | ||
|
|
0c6618d2f6 | ||
|
|
a9d7823186 | ||
|
|
6c0f5329aa | ||
|
|
a677a4feff | ||
|
|
4f674c8d19 | ||
|
|
e197163019 | ||
|
|
7ba0099fa9 | ||
|
|
f45797ec4b | ||
|
|
02e1ac12b2 | ||
|
|
bde138177d | ||
|
|
640c558446 | ||
|
|
c11ce99bb3 | ||
|
|
8ad83b8d58 | ||
|
|
873d3d7c4a | ||
|
|
d4adaaaf2b | ||
|
|
104493e725 | ||
|
|
b172e7afdc | ||
|
|
b1efbeb220 | ||
|
|
f2a6657b72 | ||
|
|
4eb4974909 | ||
|
|
3e2c898881 | ||
|
|
2fb176a244 | ||
|
|
53a6a7b305 | ||
|
|
2045380223 | ||
|
|
1c0fc3c924 | ||
|
|
0b966b7a28 | ||
|
|
882a477c0f | ||
|
|
ebcf2c3f68 | ||
|
|
54fafb3a76 | ||
|
|
01bd662046 | ||
|
|
9e26d3e85d | ||
|
|
17114778b7 | ||
|
|
98037ca0c6 | ||
|
|
4ec59e8211 | ||
|
|
1e6a5ff8ec | ||
|
|
c12d830162 | ||
|
|
a49a9b3b64 | ||
|
|
5da3da5962 | ||
|
|
8bb51d47f8 | ||
|
|
381b9ee7ee | ||
|
|
a301c96c9d | ||
|
|
f9c3cdab67 | ||
|
|
27ec0d532e | ||
|
|
3aa619b617 | ||
|
|
ef92272bee | ||
|
|
815ef05daf | ||
|
|
2ab19148c1 | ||
|
|
d12f4a4aee | ||
|
|
81b1939f92 | ||
|
|
834daeecd0 | ||
|
|
6aa0208316 | ||
|
|
aa87d8eb22 | ||
|
|
cd21fa7016 | ||
|
|
7ba4f6b93f | ||
|
|
e16a51ad06 | ||
|
|
6861dc137f | ||
|
|
e530e4d4bc | ||
|
|
d150bc1e52 | ||
|
|
cc8961360a | ||
|
|
c0539e483e | ||
|
|
f0b7099be3 | ||
|
|
ee183d4574 | ||
|
|
fa813024ca | ||
|
|
37176fa42d | ||
|
|
7ff8931def | ||
|
|
2a962bf8fd | ||
|
|
bbbcba8b98 | ||
|
|
ecdcd10612 | ||
|
|
10ea140358 | ||
|
|
74e0309241 | ||
|
|
c88bfbbf82 | ||
|
|
4edb89eeb9 | ||
|
|
cdb4b3cc7d | ||
|
|
ed8dd03fa1 | ||
|
|
e5bb7f7c2d | ||
|
|
a7b0f6dc9f | ||
|
|
c42986c07d | ||
|
|
a982dd1765 | ||
|
|
c3900398fc | ||
|
|
4b79a5e9da | ||
|
|
eed2feea97 | ||
|
|
60a2d9f624 | ||
|
|
3cd33b6ffc | ||
|
|
e3942b3438 | ||
|
|
0bf37b8c00 | ||
|
|
0e5dbf3889 | ||
|
|
785f96aabe | ||
|
|
5cec936128 | ||
|
|
02861142cb | ||
|
|
1cfc4d2f31 | ||
|
|
08e816a539 | ||
|
|
272cf64aac | ||
|
|
ed57a4099b | ||
|
|
79c5d48a3c | ||
|
|
6c70122e55 | ||
|
|
6c83699e6f | ||
|
|
ff254ce08d | ||
|
|
8a80ea26b8 | ||
|
|
b85fe62389 | ||
|
|
31a4d9204c | ||
|
|
69fdfd5cb3 | ||
|
|
16e7980982 | ||
|
|
e3e08cf8e7 | ||
|
|
cae6626b06 | ||
|
|
956d93e871 | ||
|
|
7c4d1b7b01 | ||
|
|
9866e0851b | ||
|
|
0a97a2435b | ||
|
|
5c80f03eae | ||
|
|
dd03a4b011 | ||
|
|
6cd1bc32fe | ||
|
|
dd0193a9a8 | ||
|
|
123faa6f8e | ||
|
|
f743288ce0 | ||
|
|
a1d764bd26 | ||
|
|
61f6bd2c80 | ||
|
|
fe620d8e44 | ||
|
|
22db28d3e7 | ||
|
|
1330488e13 | ||
|
|
22297be3cf | ||
|
|
29d7d6994a | ||
|
|
87e8162a2d | ||
|
|
38b71bf386 | ||
|
|
920689b80e | ||
|
|
e9c7523646 | ||
|
|
88bbc720ca | ||
|
|
1bf1469c80 | ||
|
|
c74eda20dc | ||
|
|
db0a5bd537 | ||
|
|
c6cb01aa3b | ||
|
|
7463f91878 | ||
|
|
482b31298f | ||
|
|
ce46ca2f39 | ||
|
|
de00d18a7e | ||
|
|
b0cf0c558d | ||
|
|
920e5c93e1 | ||
|
|
0aae78c6bd | ||
|
|
7fb048f423 | ||
|
|
e86207bb28 | ||
|
|
75d60ccb69 | ||
|
|
9245cd6aae | ||
|
|
d8183b60c3 | ||
|
|
a24272690d | ||
|
|
40b088d6a2 | ||
|
|
f1125d64de | ||
|
|
0cba818364 | ||
|
|
4285c751b3 | ||
|
|
1b0cddfa72 | ||
|
|
231a599f09 | ||
|
|
7ffc4d388a | ||
|
|
1f24171238 | ||
|
|
24917a6df5 | ||
|
|
67fde17209 | ||
|
|
6abad666db | ||
|
|
2d3f396571 | ||
|
|
51bcbdac75 | ||
|
|
e63889d5c4 | ||
|
|
fe6a7c58bf | ||
|
|
dac3cb15c4 | ||
|
|
ca654ccaf7 | ||
|
|
30512b7032 | ||
|
|
bc8fd62cff | ||
|
|
a9a51ee3c6 | ||
|
|
2d2da7c881 | ||
|
|
d22d8c4905 | ||
|
|
bcdc8eafa6 | ||
|
|
9cf6ace979 | ||
|
|
eef063cec2 | ||
|
|
8b9bdf9054 | ||
|
|
1c7b898b01 | ||
|
|
7ad18da08e | ||
|
|
5530915b49 | ||
|
|
77f380c94b | ||
|
|
c79ab84fdf | ||
|
|
d77448d84e | ||
|
|
8fc5a2785f | ||
|
|
27da2b026f | ||
|
|
2e9cc2a74e | ||
|
|
ffd370176d | ||
|
|
44f2a375f6 | ||
|
|
282c834d9f | ||
|
|
4a6ff9e2aa | ||
|
|
b250d10320 | ||
|
|
f63706d118 | ||
|
|
c41aa64719 | ||
|
|
285d246c65 | ||
|
|
2c85205259 | ||
|
|
2d866b9298 | ||
|
|
7a7629acf7 | ||
|
|
debf820037 | ||
|
|
f908ae8c40 | ||
|
|
022cbdda31 | ||
|
|
ae2523aa59 | ||
|
|
06f382c454 | ||
|
|
5f164d99ac | ||
|
|
2473ae3b47 | ||
|
|
3fb457ccd1 | ||
|
|
51333c9eda | ||
|
|
1aaf3961ff | ||
|
|
af4f3f62e9 | ||
|
|
cc31a12b8c | ||
|
|
bc9989f9be | ||
|
|
7f33bec71c | ||
|
|
3ea94c3484 | ||
|
|
68adaea128 | ||
|
|
4997068a0d | ||
|
|
4c59ec815e | ||
|
|
440ea666d9 | ||
|
|
6f1a6d5a56 | ||
|
|
69e80fd11c | ||
|
|
bef8cc2d70 | ||
|
|
743c95d0f9 | ||
|
|
a08cb52ad9 | ||
|
|
b9604bf3bc | ||
|
|
622c1a1dad | ||
|
|
79fea549ef | ||
|
|
d6f1c379c0 | ||
|
|
6794260e3f | ||
|
|
1be840f19d | ||
|
|
f59bb6461a | ||
|
|
bd3bae3af0 | ||
|
|
139791b0d8 | ||
|
|
6003fee33f | ||
|
|
e78c48620f | ||
|
|
a6fa01f89b |
21
.jsfmtrc
Normal file
21
.jsfmtrc
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"preset" : "default",
|
||||
|
||||
"lineBreak" : {
|
||||
"before" : {
|
||||
"VariableDeclarationWithoutInit" : 0,
|
||||
},
|
||||
|
||||
"after": {
|
||||
"AssignmentOperator": -1,
|
||||
"ArgumentListArrayExpression": ">=1"
|
||||
}
|
||||
},
|
||||
|
||||
"whiteSpace" : {
|
||||
"before" : {
|
||||
},
|
||||
"after" : {
|
||||
}
|
||||
}
|
||||
}
|
||||
66
CHANGELOG.md
66
CHANGELOG.md
@@ -1,5 +1,69 @@
|
||||
# 1.9.0 (unreleased)
|
||||
# 1.9.1 (2014-12-29)
|
||||
|
||||
**Enhancements**
|
||||
- [Issue #1028](https://github.com/grafana/grafana/issues/1028). Graph: New legend option ``hideEmtpy`` to hide series with only null values from legend
|
||||
- [Issue #1242](https://github.com/grafana/grafana/issues/1242). OpenTSDB: Downsample query field now supports interval template variable
|
||||
- [Issue #1126](https://github.com/grafana/grafana/issues/1126). InfluxDB: Support more than 10 series name segments when using alias ``$number`` patterns
|
||||
|
||||
**Fixes**
|
||||
- [Issue #1251](https://github.com/grafana/grafana/issues/1251). Graph: Fix for y axis and scaled units (GiB etc) caused rounding, for example 400 GiB instead of 378 GiB
|
||||
- [Issue #1199](https://github.com/grafana/grafana/issues/1199). Graph: fix for series tooltip when one series is hidden/disabled
|
||||
- [Issue #1207](https://github.com/grafana/grafana/issues/1207). Graphite: movingAverage / movingMedian parameter type impovement, now handles int and interval parameter
|
||||
|
||||
# 1.9.0 (2014-12-02)
|
||||
|
||||
**Enhancements**
|
||||
- [Issue #1130](https://github.com/grafana/grafana/issues/1130). SinglestatPanel: Added null point handling, and value to text mapping
|
||||
|
||||
|
||||
**Fixes**
|
||||
- [Issue #1087](https://github.com/grafana/grafana/issues/1087). Panel: Fixed IE9 crash due to angular drag drop
|
||||
- [Issue #1093](https://github.com/grafana/grafana/issues/1093). SingleStatPanel: Fixed position for drilldown link tooltip when dashboard requires scrolling
|
||||
- [Issue #1095](https://github.com/grafana/grafana/issues/1095). DrilldownLink: template variables in params property was not interpolated
|
||||
- [Issue #1114](https://github.com/grafana/grafana/issues/1114). Graphite: Lexer fix, allow equal sign (=) in metric paths
|
||||
- [Issue #1136](https://github.com/grafana/grafana/issues/1136). Graph: Fix to legend value Max and negative values
|
||||
- [Issue #1150](https://github.com/grafana/grafana/issues/1150). SinglestatPanel: Fixed absolute drilldown link issue
|
||||
- [Issue #1123](https://github.com/grafana/grafana/issues/1123). Firefox: Workaround for Firefox bug, casued input text fields to not be selectable and not have placeable cursor
|
||||
- [Issue #1108](https://github.com/grafana/grafana/issues/1108). Graph: Fix for tooltip series order when series draw order was changed with zindex property
|
||||
|
||||
# 1.9.0-rc1 (2014-11-17)
|
||||
|
||||
**UI Improvements**
|
||||
- [Issue #770](https://github.com/grafana/grafana/issues/770). UI: Panel dropdown menu replaced with a new panel menu
|
||||
|
||||
**Graph**
|
||||
- [Issue #877](https://github.com/grafana/grafana/issues/877). Graph: Smart auto decimal precision when using scaled unit formats
|
||||
- [Issue #850](https://github.com/grafana/grafana/issues/850). Graph: Shared tooltip that shows multiple series & crosshair line, thx @toni-moreno
|
||||
- [Issue #940](https://github.com/grafana/grafana/issues/940). Graph: New series style override option "Fill below to", useful to visualize max & min as a shadow for the mean
|
||||
- [Issue #1030](https://github.com/grafana/grafana/issues/1030). Graph: Legend table display/look changed, now includes column headers for min/max/avg, and full width (unless on right side)
|
||||
- [Issue #861](https://github.com/grafana/grafana/issues/861). Graph: Export graph time series data as csv file
|
||||
|
||||
**New Panels**
|
||||
- [Issue #951](https://github.com/grafana/grafana/issues/951). SingleStat: New singlestat panel
|
||||
|
||||
**Misc**
|
||||
- [Issue #864](https://github.com/grafana/grafana/issues/846). Panel: Share panel feature, get a link to panel with the current time range
|
||||
- [Issue #938](https://github.com/grafana/grafana/issues/938). Panel: Plugin panels now reside outside of app/panels directory
|
||||
- [Issue #952](https://github.com/grafana/grafana/issues/952). Help: Shortcut "?" to open help modal with list of all shortcuts
|
||||
- [Issue #991](https://github.com/grafana/grafana/issues/991). ScriptedDashboard: datasource services are now available in scripted dashboards, you can query datasource for metric keys, generate dashboards, and even save them in a scripted dashboard (see scripted_gen_and_save.js for example)
|
||||
- [Issue #1041](https://github.com/grafana/grafana/issues/1041). Panel: All panels can now have links to other dashboards or absolute links, these links are available in the panel menu.
|
||||
|
||||
**Changes**
|
||||
- [Issue #1007](https://github.com/grafana/grafana/issues/1007). Graph: Series hide/show toggle changed to be default exclusive, so clicking on a series name will show only that series. (SHIFT or meta)+click will toggle hide/show.
|
||||
|
||||
**OpenTSDB**
|
||||
- [Issue #930](https://github.com/grafana/grafana/issues/930). OpenTSDB: Adding counter max and counter reset value to open tsdb query editor, thx @rsimiciuc
|
||||
- [Issue #917](https://github.com/grafana/grafana/issues/917). OpenTSDB: Templating support for OpenTSDB series name and tags, thx @mchataigner
|
||||
|
||||
**InfluxDB**
|
||||
- [Issue #714](https://github.com/grafana/grafana/issues/714). InfluxDB: Support for sub second resolution graphs
|
||||
|
||||
**Fixes**
|
||||
- [Issue #925](https://github.com/grafana/grafana/issues/925). Graph: bar width calculation fix for some edge cases (bars would render on top of each other)
|
||||
- [Issue #505](https://github.com/grafana/grafana/issues/505). Graph: fix for second y axis tick unit labels wrapping on the next line
|
||||
- [Issue #987](https://github.com/grafana/grafana/issues/987). Dashboard: Collapsed rows became invisible when hide controls was enabled
|
||||
|
||||
=======
|
||||
# 1.8.1 (2014-09-30)
|
||||
|
||||
**Fixes**
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
[Grafana](http://grafana.org) [](https://travis-ci.org/grafana/grafana) [](https://coveralls.io/r/grafana/grafana)
|
||||
[Grafana](http://grafana.org) [](https://travis-ci.org/grafana/grafana) [](https://coveralls.io/r/grafana/grafana) [](https://gitter.im/grafana/grafana?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
================
|
||||
[Website](http://grafana.org) |
|
||||
[Twitter](http://twitter.com/grafana) |
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"version": "1.8.1",
|
||||
"url": "http://grafanarel.s3.amazonaws.com/grafana-1.8.1.tar.gz"
|
||||
"version": "1.9.1",
|
||||
"url": "http://grafanarel.s3.amazonaws.com/grafana-1.9.1.tar.gz"
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"company": "Coding Instinct AB"
|
||||
},
|
||||
"name": "grafana",
|
||||
"version": "1.8.1",
|
||||
"version": "1.9.1",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "http://github.com/torkelo/grafana.git"
|
||||
|
||||
@@ -9,6 +9,7 @@ define([
|
||||
'config',
|
||||
'bootstrap',
|
||||
'angular-route',
|
||||
'angular-sanitize',
|
||||
'angular-strap',
|
||||
'angular-dragdrop',
|
||||
'extend-jquery',
|
||||
@@ -57,13 +58,13 @@ function (angular, $, _, appLevelRequire, config) {
|
||||
register_fns.factory = $provide.factory;
|
||||
register_fns.service = $provide.service;
|
||||
register_fns.filter = $filterProvider.register;
|
||||
|
||||
});
|
||||
|
||||
var apps_deps = [
|
||||
'ngRoute',
|
||||
'ngSanitize',
|
||||
'$strap.directives',
|
||||
'ngDragDrop',
|
||||
'ang-drag-drop',
|
||||
'grafana',
|
||||
'pasvaz.bindonce'
|
||||
];
|
||||
@@ -79,6 +80,8 @@ function (angular, $, _, appLevelRequire, config) {
|
||||
});
|
||||
|
||||
var preBootRequires = [
|
||||
'services/all',
|
||||
'features/all',
|
||||
'controllers/all',
|
||||
'directives/all',
|
||||
'filters/all',
|
||||
|
||||
@@ -7,6 +7,7 @@ function($, _, moment) {
|
||||
'use strict';
|
||||
|
||||
var kbn = {};
|
||||
kbn.valueFormats = {};
|
||||
|
||||
kbn.round_interval = function(interval) {
|
||||
switch (true) {
|
||||
@@ -309,241 +310,36 @@ function($, _, moment) {
|
||||
].join(';') + '"></div>';
|
||||
};
|
||||
|
||||
kbn.byteFormat = function(size, decimals) {
|
||||
var ext, steps = 0;
|
||||
|
||||
if(_.isUndefined(decimals)) {
|
||||
decimals = 2;
|
||||
} else if (decimals === 0) {
|
||||
decimals = undefined;
|
||||
}
|
||||
|
||||
while (Math.abs(size) >= 1024) {
|
||||
steps++;
|
||||
size /= 1024;
|
||||
}
|
||||
|
||||
switch (steps) {
|
||||
case 0:
|
||||
ext = " B";
|
||||
break;
|
||||
case 1:
|
||||
ext = " KiB";
|
||||
break;
|
||||
case 2:
|
||||
ext = " MiB";
|
||||
break;
|
||||
case 3:
|
||||
ext = " GiB";
|
||||
break;
|
||||
case 4:
|
||||
ext = " TiB";
|
||||
break;
|
||||
case 5:
|
||||
ext = " PiB";
|
||||
break;
|
||||
case 6:
|
||||
ext = " EiB";
|
||||
break;
|
||||
case 7:
|
||||
ext = " ZiB";
|
||||
break;
|
||||
case 8:
|
||||
ext = " YiB";
|
||||
break;
|
||||
}
|
||||
|
||||
return (size.toFixed(decimals) + ext);
|
||||
kbn.valueFormats.percent = function(size, decimals) {
|
||||
return kbn.toFixed(size, decimals) + '%';
|
||||
};
|
||||
|
||||
kbn.bitFormat = function(size, decimals) {
|
||||
var ext, steps = 0;
|
||||
kbn.formatFuncCreator = function(factor, extArray) {
|
||||
return function(size, decimals, scaledDecimals) {
|
||||
if (size === null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if(_.isUndefined(decimals)) {
|
||||
decimals = 2;
|
||||
} else if (decimals === 0) {
|
||||
decimals = undefined;
|
||||
}
|
||||
var steps = 0;
|
||||
|
||||
while (Math.abs(size) >= 1024) {
|
||||
steps++;
|
||||
size /= 1024;
|
||||
}
|
||||
while (Math.abs(size) >= factor) {
|
||||
steps++;
|
||||
size /= factor;
|
||||
}
|
||||
if (steps > 0) {
|
||||
decimals = scaledDecimals + (3 * steps);
|
||||
}
|
||||
|
||||
switch (steps) {
|
||||
case 0:
|
||||
ext = " b";
|
||||
break;
|
||||
case 1:
|
||||
ext = " Kib";
|
||||
break;
|
||||
case 2:
|
||||
ext = " Mib";
|
||||
break;
|
||||
case 3:
|
||||
ext = " Gib";
|
||||
break;
|
||||
case 4:
|
||||
ext = " Tib";
|
||||
break;
|
||||
case 5:
|
||||
ext = " Pib";
|
||||
break;
|
||||
case 6:
|
||||
ext = " Eib";
|
||||
break;
|
||||
case 7:
|
||||
ext = " Zib";
|
||||
break;
|
||||
case 8:
|
||||
ext = " Yib";
|
||||
break;
|
||||
}
|
||||
|
||||
return (size.toFixed(decimals) + ext);
|
||||
return kbn.toFixed(size, decimals) + extArray[steps];
|
||||
};
|
||||
};
|
||||
|
||||
kbn.bpsFormat = function(size, decimals) {
|
||||
var ext, steps = 0;
|
||||
|
||||
if(_.isUndefined(decimals)) {
|
||||
decimals = 2;
|
||||
} else if (decimals === 0) {
|
||||
decimals = undefined;
|
||||
kbn.toFixed = function(value, decimals) {
|
||||
if (value === null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
while (Math.abs(size) >= 1000) {
|
||||
steps++;
|
||||
size /= 1000;
|
||||
}
|
||||
|
||||
switch (steps) {
|
||||
case 0:
|
||||
ext = " bps";
|
||||
break;
|
||||
case 1:
|
||||
ext = " Kbps";
|
||||
break;
|
||||
case 2:
|
||||
ext = " Mbps";
|
||||
break;
|
||||
case 3:
|
||||
ext = " Gbps";
|
||||
break;
|
||||
case 4:
|
||||
ext = " Tbps";
|
||||
break;
|
||||
case 5:
|
||||
ext = " Pbps";
|
||||
break;
|
||||
case 6:
|
||||
ext = " Ebps";
|
||||
break;
|
||||
case 7:
|
||||
ext = " Zbps";
|
||||
break;
|
||||
case 8:
|
||||
ext = " Ybps";
|
||||
break;
|
||||
}
|
||||
|
||||
return (size.toFixed(decimals) + ext);
|
||||
};
|
||||
|
||||
kbn.shortFormat = function(size, decimals) {
|
||||
var ext, steps = 0;
|
||||
|
||||
if(_.isUndefined(decimals)) {
|
||||
decimals = 2;
|
||||
} else if (decimals === 0) {
|
||||
decimals = undefined;
|
||||
}
|
||||
|
||||
while (Math.abs(size) >= 1000) {
|
||||
steps++;
|
||||
size /= 1000;
|
||||
}
|
||||
|
||||
switch (steps) {
|
||||
case 0:
|
||||
ext = "";
|
||||
break;
|
||||
case 1:
|
||||
ext = " K";
|
||||
break;
|
||||
case 2:
|
||||
ext = " Mil";
|
||||
break;
|
||||
case 3:
|
||||
ext = " Bil";
|
||||
break;
|
||||
case 4:
|
||||
ext = " Tri";
|
||||
break;
|
||||
case 5:
|
||||
ext = " Quadr";
|
||||
break;
|
||||
case 6:
|
||||
ext = " Quint";
|
||||
break;
|
||||
case 7:
|
||||
ext = " Sext";
|
||||
break;
|
||||
case 8:
|
||||
ext = " Sept";
|
||||
break;
|
||||
}
|
||||
|
||||
return (size.toFixed(decimals) + ext);
|
||||
};
|
||||
|
||||
kbn.getFormatFunction = function(formatName, decimals) {
|
||||
switch(formatName) {
|
||||
case 'short':
|
||||
return function(val) {
|
||||
return kbn.shortFormat(val, decimals);
|
||||
};
|
||||
case 'bytes':
|
||||
return function(val) {
|
||||
return kbn.byteFormat(val, decimals);
|
||||
};
|
||||
case 'bits':
|
||||
return function(val) {
|
||||
return kbn.bitFormat(val, decimals);
|
||||
};
|
||||
case 'bps':
|
||||
return function(val) {
|
||||
return kbn.bpsFormat(val, decimals);
|
||||
};
|
||||
case 's':
|
||||
return function(val) {
|
||||
return kbn.sFormat(val, decimals);
|
||||
};
|
||||
case 'ms':
|
||||
return function(val) {
|
||||
return kbn.msFormat(val, decimals);
|
||||
};
|
||||
case 'µs':
|
||||
return function(val) {
|
||||
return kbn.microsFormat(val, decimals);
|
||||
};
|
||||
case 'ns':
|
||||
return function(val) {
|
||||
return kbn.nanosFormat(val, decimals);
|
||||
};
|
||||
case 'percent':
|
||||
return function(val, axis) {
|
||||
return kbn.noneFormat(val, axis ? axis.tickDecimals : null) + ' %';
|
||||
};
|
||||
default:
|
||||
return function(val, axis) {
|
||||
return kbn.noneFormat(val, axis ? axis.tickDecimals : null);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
kbn.noneFormat = function(value, decimals) {
|
||||
var factor = decimals ? Math.pow(10, decimals) : 1;
|
||||
var factor = decimals ? Math.pow(10, Math.max(0, decimals)) : 1;
|
||||
var formatted = String(Math.round(value * factor) / factor);
|
||||
|
||||
// if exponent return directly
|
||||
@@ -553,7 +349,6 @@ function($, _, moment) {
|
||||
|
||||
// If tickDecimals was specified, ensure that we have exactly that
|
||||
// much precision; otherwise default to the value's own precision.
|
||||
|
||||
if (decimals != null) {
|
||||
var decimalPos = formatted.indexOf(".");
|
||||
var precision = decimalPos === -1 ? 0 : formatted.length - decimalPos - 1;
|
||||
@@ -565,97 +360,95 @@ function($, _, moment) {
|
||||
return formatted;
|
||||
};
|
||||
|
||||
kbn.msFormat = function(size, decimals) {
|
||||
// Less than 1 milli, downscale to micro
|
||||
if (size !== 0 && Math.abs(size) < 1) {
|
||||
return kbn.microsFormat(size * 1000, decimals);
|
||||
}
|
||||
else if (Math.abs(size) < 1000) {
|
||||
return size.toFixed(decimals) + " ms";
|
||||
kbn.valueFormats.bits = kbn.formatFuncCreator(1024, [' b', ' Kib', ' Mib', ' Gib', ' Tib', ' Pib', ' Eib', ' Zib', ' Yib']);
|
||||
kbn.valueFormats.bytes = kbn.formatFuncCreator(1024, [' B', ' KiB', ' MiB', ' GiB', ' TiB', ' PiB', ' EiB', ' ZiB', ' YiB']);
|
||||
kbn.valueFormats.bps = kbn.formatFuncCreator(1000, [' bps', ' Kbps', ' Mbps', ' Gbps', ' Tbps', ' Pbps', ' Ebps', ' Zbps', ' Ybps']);
|
||||
kbn.valueFormats.short = kbn.formatFuncCreator(1000, ['', ' K', ' Mil', ' Bil', ' Tri', ' Qaudr', ' Quint', ' Sext', ' Sept']);
|
||||
kbn.valueFormats.none = kbn.toFixed;
|
||||
|
||||
kbn.valueFormats.ms = function(size, decimals, scaledDecimals) {
|
||||
if (size === null) { return ""; }
|
||||
|
||||
if (Math.abs(size) < 1000) {
|
||||
return kbn.toFixed(size, decimals) + " ms";
|
||||
}
|
||||
// Less than 1 min
|
||||
else if (Math.abs(size) < 60000) {
|
||||
return (size / 1000).toFixed(decimals) + " s";
|
||||
return kbn.toFixed(size / 1000, scaledDecimals + 3) + " s";
|
||||
}
|
||||
// Less than 1 hour, devide in minutes
|
||||
else if (Math.abs(size) < 3600000) {
|
||||
return (size / 60000).toFixed(decimals) + " min";
|
||||
return kbn.toFixed(size / 60000, scaledDecimals + 5) + " min";
|
||||
}
|
||||
// Less than one day, devide in hours
|
||||
else if (Math.abs(size) < 86400000) {
|
||||
return (size / 3600000).toFixed(decimals) + " hour";
|
||||
return kbn.toFixed(size / 3600000, scaledDecimals + 7) + " hour";
|
||||
}
|
||||
// Less than one year, devide in days
|
||||
else if (Math.abs(size) < 31536000000) {
|
||||
return (size / 86400000).toFixed(decimals) + " day";
|
||||
return kbn.toFixed(size / 86400000, scaledDecimals + 8) + " day";
|
||||
}
|
||||
|
||||
return (size / 31536000000).toFixed(decimals) + " year";
|
||||
return kbn.toFixed(size / 31536000000, scaledDecimals + 10) + " year";
|
||||
};
|
||||
|
||||
kbn.sFormat = function(size, decimals) {
|
||||
// Less than 1 sec, downscale to milli
|
||||
if (size !== 0 && Math.abs(size) < 1) {
|
||||
return kbn.msFormat(size * 1000, decimals);
|
||||
}
|
||||
// Less than 10 min, use seconds
|
||||
else if (Math.abs(size) < 600) {
|
||||
return size.toFixed(decimals) + " s";
|
||||
kbn.valueFormats.s = function(size, decimals, scaledDecimals) {
|
||||
if (size === null) { return ""; }
|
||||
|
||||
if (Math.abs(size) < 600) {
|
||||
return kbn.toFixed(size, decimals) + " s";
|
||||
}
|
||||
// Less than 1 hour, devide in minutes
|
||||
else if (Math.abs(size) < 3600) {
|
||||
return (size / 60).toFixed(decimals) + " min";
|
||||
return kbn.toFixed(size / 60, scaledDecimals + 1) + " min";
|
||||
}
|
||||
// Less than one day, devide in hours
|
||||
else if (Math.abs(size) < 86400) {
|
||||
return (size / 3600).toFixed(decimals) + " hour";
|
||||
return kbn.toFixed(size / 3600, scaledDecimals + 4) + " hour";
|
||||
}
|
||||
// Less than one week, devide in days
|
||||
else if (Math.abs(size) < 604800) {
|
||||
return (size / 86400).toFixed(decimals) + " day";
|
||||
return kbn.toFixed(size / 86400, scaledDecimals + 5) + " day";
|
||||
}
|
||||
// Less than one year, devide in week
|
||||
else if (Math.abs(size) < 31536000) {
|
||||
return (size / 604800).toFixed(decimals) + " week";
|
||||
return kbn.toFixed(size / 604800, scaledDecimals + 6) + " week";
|
||||
}
|
||||
|
||||
return (size / 3.15569e7).toFixed(decimals) + " year";
|
||||
return kbn.toFixed(size / 3.15569e7, scaledDecimals + 7) + " year";
|
||||
};
|
||||
|
||||
kbn.microsFormat = function(size, decimals) {
|
||||
// Less than 1 micro, downscale to nano
|
||||
if (size !== 0 && Math.abs(size) < 1) {
|
||||
return kbn.nanosFormat(size * 1000, decimals);
|
||||
}
|
||||
else if (Math.abs(size) < 1000) {
|
||||
return size.toFixed(decimals) + " µs";
|
||||
kbn.valueFormats['µs'] = function(size, decimals, scaledDecimals) {
|
||||
if (size === null) { return ""; }
|
||||
|
||||
if (Math.abs(size) < 1000) {
|
||||
return kbn.toFixed(size, decimals) + " µs";
|
||||
}
|
||||
else if (Math.abs(size) < 1000000) {
|
||||
return (size / 1000).toFixed(decimals) + " ms";
|
||||
return kbn.toFixed(size / 1000, scaledDecimals + 3) + " ms";
|
||||
}
|
||||
else {
|
||||
return (size / 1000000).toFixed(decimals) + " s";
|
||||
return kbn.toFixed(size / 1000000, scaledDecimals + 6) + " s";
|
||||
}
|
||||
};
|
||||
|
||||
kbn.nanosFormat = function(size, decimals) {
|
||||
if (Math.abs(size) < 1) {
|
||||
return size.toFixed(decimals) + " ns";
|
||||
}
|
||||
else if (Math.abs(size) < 1000) {
|
||||
return size.toFixed(0) + " ns";
|
||||
kbn.valueFormats.ns = function(size, decimals, scaledDecimals) {
|
||||
if (size === null) { return ""; }
|
||||
|
||||
if (Math.abs(size) < 1000) {
|
||||
return kbn.toFixed(size, decimals) + " ns";
|
||||
}
|
||||
else if (Math.abs(size) < 1000000) {
|
||||
return (size / 1000).toFixed(decimals) + " µs";
|
||||
return kbn.toFixed(size / 1000, scaledDecimals + 3) + " µs";
|
||||
}
|
||||
else if (Math.abs(size) < 1000000000) {
|
||||
return (size / 1000000).toFixed(decimals) + " ms";
|
||||
return kbn.toFixed(size / 1000000, scaledDecimals + 6) + " ms";
|
||||
}
|
||||
else if (Math.abs(size) < 60000000000){
|
||||
return (size / 1000000000).toFixed(decimals) + " s";
|
||||
return kbn.toFixed(size / 1000000000, scaledDecimals + 9) + " s";
|
||||
}
|
||||
else {
|
||||
return (size / 60000000000).toFixed(decimals) + " m";
|
||||
return kbn.toFixed(size / 60000000000, scaledDecimals + 12) + " m";
|
||||
}
|
||||
};
|
||||
|
||||
@@ -666,6 +459,17 @@ function($, _, moment) {
|
||||
.replace(/ +/g,'-');
|
||||
};
|
||||
|
||||
kbn.exportSeriesListToCsv = function(seriesList) {
|
||||
var text = 'Series;Time;Value\n';
|
||||
_.each(seriesList, function(series) {
|
||||
_.each(series.datapoints, function(dp) {
|
||||
text += series.alias + ';' + new Date(dp[1]).toISOString() + ';' + dp[0] + '\n';
|
||||
});
|
||||
});
|
||||
var blob = new Blob([text], { type: "text/csv;charset=utf-8" });
|
||||
window.saveAs(blob, 'grafana_data_export.csv');
|
||||
};
|
||||
|
||||
kbn.stringToJsRegex = function(str) {
|
||||
if (str[0] !== '/') {
|
||||
return new RegExp(str);
|
||||
|
||||
44
src/app/components/panelmeta.js
Normal file
44
src/app/components/panelmeta.js
Normal file
@@ -0,0 +1,44 @@
|
||||
define([
|
||||
],
|
||||
function () {
|
||||
"use strict";
|
||||
|
||||
function PanelMeta(options) {
|
||||
this.description = options.description;
|
||||
this.fullscreen = options.fullscreen;
|
||||
this.menu = [];
|
||||
this.editorTabs = [];
|
||||
this.extendedMenu = [];
|
||||
|
||||
if (options.fullscreen) {
|
||||
this.addMenuItem('view', 'icon-eye-open', 'toggleFullscreen(false); dismiss();');
|
||||
}
|
||||
|
||||
this.addMenuItem('edit', 'icon-cog', 'editPanel(); dismiss();');
|
||||
this.addMenuItem('duplicate', 'icon-copy', 'duplicatePanel()');
|
||||
this.addMenuItem('share', 'icon-share', 'sharePanel(); dismiss();');
|
||||
|
||||
this.addEditorTab('General', 'app/partials/panelgeneral.html');
|
||||
|
||||
if (options.metricsEditor) {
|
||||
this.addEditorTab('Metrics', 'app/partials/metrics.html');
|
||||
}
|
||||
|
||||
this.addExtendedMenuItem('Panel JSON', '', 'editPanelJson(); dismiss();');
|
||||
}
|
||||
|
||||
PanelMeta.prototype.addMenuItem = function(text, icon, click) {
|
||||
this.menu.push({text: text, icon: icon, click: click});
|
||||
};
|
||||
|
||||
PanelMeta.prototype.addExtendedMenuItem = function(text, icon, click) {
|
||||
this.extendedMenu.push({text: text, icon: icon, click: click});
|
||||
};
|
||||
|
||||
PanelMeta.prototype.addEditorTab = function(title, src) {
|
||||
this.editorTabs.push({title: title, src: src});
|
||||
};
|
||||
|
||||
return PanelMeta;
|
||||
|
||||
});
|
||||
@@ -3,6 +3,7 @@
|
||||
*/
|
||||
require.config({
|
||||
baseUrl: 'app',
|
||||
urlArgs: 'bust=' + (new Date().getTime()),
|
||||
|
||||
paths: {
|
||||
config: ['../config', '../config.sample'],
|
||||
@@ -16,6 +17,7 @@ require.config({
|
||||
filesaver: '../vendor/filesaver',
|
||||
angular: '../vendor/angular/angular',
|
||||
'angular-route': '../vendor/angular/angular-route',
|
||||
'angular-sanitize': '../vendor/angular/angular-sanitize',
|
||||
'angular-dragdrop': '../vendor/angular/angular-dragdrop',
|
||||
'angular-strap': '../vendor/angular/angular-strap',
|
||||
timepicker: '../vendor/angular/timepicker',
|
||||
@@ -29,7 +31,6 @@ require.config({
|
||||
bootstrap: '../vendor/bootstrap/bootstrap',
|
||||
|
||||
jquery: '../vendor/jquery/jquery-2.1.1.min',
|
||||
'jquery-ui': '../vendor/jquery/jquery-ui-1.10.3',
|
||||
|
||||
'extend-jquery': 'components/extend-jquery',
|
||||
|
||||
@@ -40,6 +41,8 @@ require.config({
|
||||
'jquery.flot.stack': '../vendor/jquery/jquery.flot.stack',
|
||||
'jquery.flot.stackpercent':'../vendor/jquery/jquery.flot.stackpercent',
|
||||
'jquery.flot.time': '../vendor/jquery/jquery.flot.time',
|
||||
'jquery.flot.crosshair': '../vendor/jquery/jquery.flot.crosshair',
|
||||
'jquery.flot.fillbelow': '../vendor/jquery/jquery.flot.fillbelow',
|
||||
|
||||
modernizr: '../vendor/modernizr-2.6.1',
|
||||
|
||||
@@ -75,7 +78,6 @@ require.config({
|
||||
|
||||
// simple dependency declaration
|
||||
//
|
||||
'jquery-ui': ['jquery'],
|
||||
'jquery.flot': ['jquery'],
|
||||
'jquery.flot.pie': ['jquery', 'jquery.flot'],
|
||||
'jquery.flot.events': ['jquery', 'jquery.flot'],
|
||||
@@ -83,15 +85,14 @@ require.config({
|
||||
'jquery.flot.stack': ['jquery', 'jquery.flot'],
|
||||
'jquery.flot.stackpercent':['jquery', 'jquery.flot'],
|
||||
'jquery.flot.time': ['jquery', 'jquery.flot'],
|
||||
'angular-cookies': ['angular'],
|
||||
'angular-dragdrop': ['jquery','jquery-ui','angular'],
|
||||
'angular-loader': ['angular'],
|
||||
'jquery.flot.crosshair':['jquery', 'jquery.flot'],
|
||||
'jquery.flot.fillbelow':['jquery', 'jquery.flot'],
|
||||
'angular-dragdrop': ['jquery', 'angular'],
|
||||
'angular-mocks': ['angular'],
|
||||
'angular-resource': ['angular'],
|
||||
'angular-sanitize': ['angular'],
|
||||
'angular-route': ['angular'],
|
||||
'angular-touch': ['angular'],
|
||||
'bindonce': ['angular'],
|
||||
'angular-strap': ['angular', 'bootstrap','timepicker', 'datepicker'],
|
||||
'bindonce': ['angular'],
|
||||
|
||||
timepicker: ['jquery', 'bootstrap'],
|
||||
datepicker: ['jquery', 'bootstrap'],
|
||||
|
||||
@@ -15,12 +15,16 @@ function (_, crypto) {
|
||||
var defaults = {
|
||||
datasources : {},
|
||||
window_title_prefix : 'Grafana - ',
|
||||
panels : ['graph', 'text'],
|
||||
panels : {
|
||||
'graph': { path: 'panels/graph' },
|
||||
'singlestat': { path: 'panels/singlestat' },
|
||||
'text': { path: 'panels/text' }
|
||||
},
|
||||
plugins : {},
|
||||
default_route : '/dashboard/file/default.json',
|
||||
playlist_timespan : "1m",
|
||||
unsaved_changes_warning : true,
|
||||
search : { max_results: 16 },
|
||||
search : { max_results: 100 },
|
||||
admin : {}
|
||||
};
|
||||
|
||||
@@ -76,7 +80,7 @@ function (_, crypto) {
|
||||
});
|
||||
|
||||
if (settings.plugins.panels) {
|
||||
settings.panels = _.union(settings.panels, settings.plugins.panels);
|
||||
_.extend(settings.panels, settings.plugins.panels);
|
||||
}
|
||||
|
||||
if (!settings.plugins.dependencies) {
|
||||
|
||||
@@ -7,8 +7,12 @@ function (_, kbn) {
|
||||
|
||||
function TimeSeries(opts) {
|
||||
this.datapoints = opts.datapoints;
|
||||
this.info = opts.info;
|
||||
this.label = opts.info.alias;
|
||||
this.label = opts.alias;
|
||||
this.id = opts.alias;
|
||||
this.alias = opts.alias;
|
||||
this.color = opts.color;
|
||||
this.valueFormater = kbn.valueFormats.none;
|
||||
this.stats = {};
|
||||
}
|
||||
|
||||
function matchSeriesOverride(aliasOrRegex, seriesAlias) {
|
||||
@@ -30,13 +34,13 @@ function (_, kbn) {
|
||||
this.lines = {};
|
||||
this.points = {};
|
||||
this.bars = {};
|
||||
this.info.yaxis = 1;
|
||||
this.yaxis = 1;
|
||||
this.zindex = 0;
|
||||
delete this.stack;
|
||||
|
||||
for (var i = 0; i < overrides.length; i++) {
|
||||
var override = overrides[i];
|
||||
if (!matchSeriesOverride(override.alias, this.info.alias)) {
|
||||
if (!matchSeriesOverride(override.alias, this.alias)) {
|
||||
continue;
|
||||
}
|
||||
if (override.lines !== void 0) { this.lines.show = override.lines; }
|
||||
@@ -48,21 +52,23 @@ function (_, kbn) {
|
||||
if (override.pointradius !== void 0) { this.points.radius = override.pointradius; }
|
||||
if (override.steppedLine !== void 0) { this.lines.steps = override.steppedLine; }
|
||||
if (override.zindex !== void 0) { this.zindex = override.zindex; }
|
||||
if (override.fillBelowTo !== void 0) { this.fillBelowTo = override.fillBelowTo; }
|
||||
|
||||
if (override.yaxis !== void 0) {
|
||||
this.info.yaxis = override.yaxis;
|
||||
this.yaxis = override.yaxis;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TimeSeries.prototype.getFlotPairs = function (fillStyle, yFormats) {
|
||||
TimeSeries.prototype.getFlotPairs = function (fillStyle) {
|
||||
var result = [];
|
||||
|
||||
this.color = this.info.color;
|
||||
this.yaxis = this.info.yaxis;
|
||||
|
||||
this.info.total = 0;
|
||||
this.info.max = -212312321312;
|
||||
this.info.min = 212312321312;
|
||||
this.stats.total = 0;
|
||||
this.stats.max = -Number.MAX_VALUE;
|
||||
this.stats.min = Number.MAX_VALUE;
|
||||
this.stats.avg = null;
|
||||
this.stats.current = null;
|
||||
this.allIsNull = true;
|
||||
|
||||
var ignoreNulls = fillStyle === 'connected';
|
||||
var nullAsZero = fillStyle === 'null as zero';
|
||||
@@ -81,40 +87,49 @@ function (_, kbn) {
|
||||
}
|
||||
|
||||
if (_.isNumber(currentValue)) {
|
||||
this.info.total += currentValue;
|
||||
this.stats.total += currentValue;
|
||||
this.allIsNull = false;
|
||||
}
|
||||
|
||||
if (currentValue > this.info.max) {
|
||||
this.info.max = currentValue;
|
||||
if (currentValue > this.stats.max) {
|
||||
this.stats.max = currentValue;
|
||||
}
|
||||
|
||||
if (currentValue < this.info.min) {
|
||||
this.info.min = currentValue;
|
||||
if (currentValue < this.stats.min) {
|
||||
this.stats.min = currentValue;
|
||||
}
|
||||
|
||||
result.push([currentTime * 1000, currentValue]);
|
||||
result.push([currentTime, currentValue]);
|
||||
}
|
||||
|
||||
if (result.length > 2) {
|
||||
this.info.timeStep = result[1][0] - result[0][0];
|
||||
if (this.datapoints.length >= 2) {
|
||||
this.stats.timeStep = this.datapoints[1][1] - this.datapoints[0][1];
|
||||
}
|
||||
|
||||
if (this.stats.max === -Number.MAX_VALUE) { this.stats.max = null; }
|
||||
if (this.stats.min === Number.MAX_VALUE) { this.stats.min = null; }
|
||||
|
||||
if (result.length) {
|
||||
|
||||
this.info.avg = (this.info.total / result.length);
|
||||
this.info.current = result[result.length-1][1];
|
||||
|
||||
var formater = kbn.getFormatFunction(yFormats[this.yaxis - 1], 2);
|
||||
this.info.avg = this.info.avg != null ? formater(this.info.avg) : null;
|
||||
this.info.current = this.info.current != null ? formater(this.info.current) : null;
|
||||
this.info.min = this.info.min != null ? formater(this.info.min) : null;
|
||||
this.info.max = this.info.max != null ? formater(this.info.max) : null;
|
||||
this.info.total = this.info.total != null ? formater(this.info.total) : null;
|
||||
this.stats.avg = (this.stats.total / result.length);
|
||||
this.stats.current = result[result.length-1][1];
|
||||
if (this.stats.current === null && result.length > 1) {
|
||||
this.stats.current = result[result.length-2][1];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
TimeSeries.prototype.updateLegendValues = function(formater, decimals, scaledDecimals) {
|
||||
this.valueFormater = formater;
|
||||
this.decimals = decimals;
|
||||
this.scaledDecimals = scaledDecimals;
|
||||
};
|
||||
|
||||
TimeSeries.prototype.formatValue = function(value) {
|
||||
return this.valueFormater(value, this.decimals, this.scaledDecimals);
|
||||
};
|
||||
|
||||
return TimeSeries;
|
||||
|
||||
});
|
||||
|
||||
@@ -15,5 +15,6 @@ define([
|
||||
'./opentsdbTargetCtrl',
|
||||
'./annotationsEditorCtrl',
|
||||
'./templateEditorCtrl',
|
||||
'./sharePanelCtrl',
|
||||
'./jsonEditorCtrl',
|
||||
], function () {});
|
||||
|
||||
@@ -3,7 +3,6 @@ define([
|
||||
'jquery',
|
||||
'config',
|
||||
'lodash',
|
||||
'services/all',
|
||||
],
|
||||
function (angular, $, config, _) {
|
||||
"use strict";
|
||||
@@ -18,11 +17,10 @@ function (angular, $, config, _) {
|
||||
templateValuesSrv,
|
||||
dashboardSrv,
|
||||
dashboardViewStateSrv,
|
||||
panelMoveSrv,
|
||||
$timeout) {
|
||||
|
||||
$scope.editor = { index: 0 };
|
||||
$scope.panelNames = config.panels;
|
||||
$scope.panelNames = _.map(config.panels, function(value, key) { return key; });
|
||||
var resizeEventTimeout;
|
||||
|
||||
this.init = function(dashboardData) {
|
||||
@@ -51,14 +49,13 @@ function (angular, $, config, _) {
|
||||
// init services
|
||||
timeSrv.init($scope.dashboard);
|
||||
templateValuesSrv.init($scope.dashboard, $scope.dashboardViewState);
|
||||
panelMoveSrv.init($scope.dashboard, $scope);
|
||||
|
||||
$scope.checkFeatureToggles();
|
||||
dashboardKeybindings.shortcuts($scope);
|
||||
|
||||
$scope.setWindowTitleAndTheme();
|
||||
|
||||
$scope.emitAppEvent("dashboard-loaded", $scope.dashboard);
|
||||
$scope.appEvent("dashboard-loaded", $scope.dashboard);
|
||||
};
|
||||
|
||||
$scope.setWindowTitleAndTheme = function() {
|
||||
@@ -92,40 +89,42 @@ function (angular, $, config, _) {
|
||||
};
|
||||
};
|
||||
|
||||
$scope.edit_path = function(type) {
|
||||
var p = $scope.panel_path(type);
|
||||
if(p) {
|
||||
return p+'/editor.html';
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
$scope.panelEditorPath = function(type) {
|
||||
return 'app/' + config.panels[type].path + '/editor.html';
|
||||
};
|
||||
|
||||
$scope.panel_path =function(type) {
|
||||
if(type) {
|
||||
return 'app/panels/'+type.replace(".","/");
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
$scope.pulldownEditorPath = function(type) {
|
||||
return 'app/panels/'+type+'/editor.html';
|
||||
};
|
||||
|
||||
$scope.showJsonEditor = function(evt, options) {
|
||||
var editScope = $rootScope.$new();
|
||||
editScope.object = options.object;
|
||||
editScope.updateHandler = options.updateHandler;
|
||||
$scope.emitAppEvent('show-dash-editor', { src: 'app/partials/edit_json.html', scope: editScope });
|
||||
$scope.appEvent('show-dash-editor', { src: 'app/partials/edit_json.html', scope: editScope });
|
||||
};
|
||||
|
||||
$scope.checkFeatureToggles = function() {
|
||||
$scope.submenuEnabled = $scope.dashboard.templating.enable || $scope.dashboard.annotations.enable;
|
||||
};
|
||||
|
||||
$scope.setEditorTabs = function(panelMeta) {
|
||||
$scope.editorTabs = ['General','Panel'];
|
||||
if(!_.isUndefined(panelMeta.editorTabs)) {
|
||||
$scope.editorTabs = _.union($scope.editorTabs,_.pluck(panelMeta.editorTabs,'title'));
|
||||
$scope.onDrop = function(panelId, row, dropTarget) {
|
||||
var info = $scope.dashboard.getPanelInfoById(panelId);
|
||||
if (dropTarget) {
|
||||
var dropInfo = $scope.dashboard.getPanelInfoById(dropTarget.id);
|
||||
dropInfo.row.panels[dropInfo.index] = info.panel;
|
||||
info.row.panels[info.index] = dropTarget;
|
||||
var dragSpan = info.panel.span;
|
||||
info.panel.span = dropTarget.span;
|
||||
dropTarget.span = dragSpan;
|
||||
}
|
||||
return $scope.editorTabs;
|
||||
else {
|
||||
info.row.panels.splice(info.index, 1);
|
||||
info.panel.span = 12 - $scope.dashboard.rowSpan(row);
|
||||
row.panels.push(info.panel);
|
||||
}
|
||||
|
||||
$rootScope.$broadcast('render');
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
@@ -69,7 +69,7 @@ function (angular, _, moment, config, store) {
|
||||
};
|
||||
|
||||
$scope.openSearch = function() {
|
||||
$scope.emitAppEvent('show-dash-editor', { src: 'app/partials/search.html' });
|
||||
$scope.appEvent('show-dash-editor', { src: 'app/partials/search.html' });
|
||||
};
|
||||
|
||||
$scope.saveDashboard = function() {
|
||||
@@ -78,7 +78,7 @@ function (angular, _, moment, config, store) {
|
||||
var clone = angular.copy($scope.dashboard);
|
||||
$scope.db.saveDashboard(clone)
|
||||
.then(function(result) {
|
||||
alertSrv.set('Dashboard Saved', 'Saved as "' + result.title + '"','success', 3000);
|
||||
$scope.appEvent('alert-success', ['Dashboard saved', 'Saved as ' + result.title]);
|
||||
|
||||
if (result.url !== $location.path()) {
|
||||
$location.search({});
|
||||
@@ -88,22 +88,29 @@ function (angular, _, moment, config, store) {
|
||||
$rootScope.$emit('dashboard-saved', $scope.dashboard);
|
||||
|
||||
}, function(err) {
|
||||
alertSrv.set('Save failed', err, 'error', 5000);
|
||||
$scope.appEvent('alert-error', ['Save failed', err]);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.deleteDashboard = function(evt, options) {
|
||||
if (!confirm('Do you want to delete dashboard ' + options.title + ' ?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$scope.isAdmin()) { return false; }
|
||||
|
||||
$scope.appEvent('confirm-modal', {
|
||||
title: 'Delete dashboard',
|
||||
text: 'Do you want to delete dashboard ' + options.title + '?',
|
||||
onConfirm: function() {
|
||||
$scope.deleteDashboardConfirmed(options);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.deleteDashboardConfirmed = function(options) {
|
||||
var id = options.id;
|
||||
$scope.db.deleteDashboard(id).then(function(id) {
|
||||
alertSrv.set('Dashboard Deleted', id + ' has been deleted', 'success', 5000);
|
||||
}, function() {
|
||||
alertSrv.set('Dashboard Not Deleted', 'An error occurred deleting the dashboard', 'error', 5000);
|
||||
$scope.appEvent('dashboard-deleted', id);
|
||||
$scope.appEvent('alert-success', ['Dashboard Deleted', id + ' has been deleted']);
|
||||
}, function(err) {
|
||||
$scope.appEvent('alert-error', ['Deleted failed', err]);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -138,7 +145,7 @@ function (angular, _, moment, config, store) {
|
||||
};
|
||||
|
||||
$scope.editJson = function() {
|
||||
$scope.emitAppEvent('show-json-editor', { object: $scope.dashboard });
|
||||
$scope.appEvent('show-json-editor', { object: $scope.dashboard });
|
||||
};
|
||||
|
||||
$scope.openSaveDropdown = function() {
|
||||
|
||||
@@ -10,19 +10,19 @@ function (angular, config, _, $, store) {
|
||||
|
||||
var module = angular.module('grafana.controllers');
|
||||
|
||||
module.controller('GrafanaCtrl', function($scope, alertSrv, grafanaVersion, $rootScope, $controller) {
|
||||
module.controller('GrafanaCtrl', function($scope, alertSrv, utilSrv, grafanaVersion, $rootScope, $controller) {
|
||||
|
||||
$scope.grafanaVersion = grafanaVersion[0] === '@' ? 'master' : grafanaVersion;
|
||||
$scope.consoleEnabled = store.getBool('grafanaConsole');
|
||||
|
||||
$scope._ = _;
|
||||
$rootScope.profilingEnabled = store.getBool('profilingEnabled');
|
||||
$rootScope.performance = { loadStart: new Date().getTime() };
|
||||
|
||||
$scope.init = function() {
|
||||
$scope._ = _;
|
||||
|
||||
if ($rootScope.profilingEnabled) { $scope.initProfiling(); }
|
||||
|
||||
alertSrv.init();
|
||||
utilSrv.init();
|
||||
|
||||
$scope.dashAlerts = alertSrv;
|
||||
$scope.grafana = { style: 'dark' };
|
||||
};
|
||||
@@ -41,7 +41,7 @@ function (angular, config, _, $, store) {
|
||||
this.$on('$destroy', unbind);
|
||||
};
|
||||
|
||||
$rootScope.emitAppEvent = function(name, payload) {
|
||||
$rootScope.appEvent = function(name, payload) {
|
||||
$rootScope.$emit(name, payload);
|
||||
};
|
||||
|
||||
@@ -81,12 +81,8 @@ function (angular, config, _, $, store) {
|
||||
$scope.initProfiling = function() {
|
||||
var count = 0;
|
||||
|
||||
$scope.$watch(function digestCounter() {
|
||||
count++;
|
||||
}, function() {
|
||||
});
|
||||
|
||||
$scope.onAppEvent('setup-dashboard', function() {
|
||||
$scope.$watch(function digestCounter() { count++; }, function() { });
|
||||
$scope.onAppEvent('dashboard-loaded', function() {
|
||||
count = 0;
|
||||
|
||||
setTimeout(function() {
|
||||
|
||||
@@ -162,6 +162,11 @@ function (angular, _, config, gfunc, Parser) {
|
||||
return new MetricSegment({ value: segment.text, expandable: segment.expandable });
|
||||
});
|
||||
|
||||
if ($scope.altSegments.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// add template variables
|
||||
_.each(templateSrv.variables, function(variable) {
|
||||
$scope.altSegments.unshift(new MetricSegment({
|
||||
type: 'template',
|
||||
@@ -170,6 +175,7 @@ function (angular, _, config, gfunc, Parser) {
|
||||
}));
|
||||
});
|
||||
|
||||
// add wildcard option
|
||||
$scope.altSegments.unshift(new MetricSegment('*'));
|
||||
})
|
||||
.then(null, function(err) {
|
||||
@@ -201,7 +207,7 @@ function (angular, _, config, gfunc, Parser) {
|
||||
|
||||
$scope.targetTextChanged = function() {
|
||||
parseTarget();
|
||||
$scope.$parent.get_data();
|
||||
$scope.get_data();
|
||||
};
|
||||
|
||||
$scope.targetChanged = function() {
|
||||
@@ -275,6 +281,10 @@ function (angular, _, config, gfunc, Parser) {
|
||||
}
|
||||
};
|
||||
|
||||
$scope.moveMetricQuery = function(fromIndex, toIndex) {
|
||||
_.move($scope.panel.targets, fromIndex, toIndex);
|
||||
};
|
||||
|
||||
$scope.duplicate = function() {
|
||||
var clone = angular.copy($scope.target);
|
||||
$scope.panel.targets.push(clone);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
define([
|
||||
'angular'
|
||||
'angular',
|
||||
'lodash'
|
||||
],
|
||||
function (angular) {
|
||||
function (angular, _) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.controllers');
|
||||
@@ -83,10 +84,11 @@ function (angular) {
|
||||
};
|
||||
|
||||
$scope.listSeries = function(query, callback) {
|
||||
if (!seriesList || query === '') {
|
||||
if (query !== '') {
|
||||
seriesList = [];
|
||||
$scope.datasource.listSeries().then(function(series) {
|
||||
$scope.datasource.listSeries(query).then(function(series) {
|
||||
seriesList = series;
|
||||
console.log(series);
|
||||
callback(seriesList);
|
||||
});
|
||||
}
|
||||
@@ -95,6 +97,10 @@ function (angular) {
|
||||
}
|
||||
};
|
||||
|
||||
$scope.moveMetricQuery = function(fromIndex, toIndex) {
|
||||
_.move($scope.panel.targets, fromIndex, toIndex);
|
||||
};
|
||||
|
||||
$scope.duplicate = function() {
|
||||
var clone = angular.copy($scope.target);
|
||||
$scope.panel.targets.push(clone);
|
||||
|
||||
@@ -23,6 +23,11 @@ function (angular, app, _) {
|
||||
$scope.reset_panel();
|
||||
};
|
||||
|
||||
$scope.togglePanelMenu = function(posX) {
|
||||
$scope.showPanelMenu = !$scope.showPanelMenu;
|
||||
$scope.panelMenuPos = posX;
|
||||
};
|
||||
|
||||
$scope.toggle_row = function(row) {
|
||||
row.collapse = row.collapse ? false : true;
|
||||
if (!row.collapse) {
|
||||
@@ -42,9 +47,13 @@ function (angular, app, _) {
|
||||
};
|
||||
|
||||
$scope.delete_row = function() {
|
||||
if (confirm("Are you sure you want to delete this row?")) {
|
||||
$scope.dashboard.rows = _.without($scope.dashboard.rows, $scope.row);
|
||||
}
|
||||
$scope.appEvent('confirm-modal', {
|
||||
title: 'Delete row',
|
||||
text: 'Are you sure you want to delete this row?',
|
||||
onConfirm: function() {
|
||||
$scope.dashboard.rows = _.without($scope.dashboard.rows, $scope.row);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.move_row = function(direction) {
|
||||
@@ -71,9 +80,13 @@ function (angular, app, _) {
|
||||
};
|
||||
|
||||
$scope.remove_panel_from_row = function(row, panel) {
|
||||
if (confirm('Are you sure you want to remove this ' + panel.type + ' panel?')) {
|
||||
row.panels = _.without(row.panels,panel);
|
||||
}
|
||||
$scope.appEvent('confirm-modal', {
|
||||
title: 'Remove panel',
|
||||
text: 'Are you sure you want to remove this panel?',
|
||||
onConfirm: function() {
|
||||
row.panels = _.without(row.panels, panel);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.replacePanel = function(newPanel, oldPanel) {
|
||||
@@ -89,15 +102,12 @@ function (angular, app, _) {
|
||||
});
|
||||
};
|
||||
|
||||
$scope.duplicatePanel = function(panel, row) {
|
||||
$scope.dashboard.duplicatePanel(panel, row || $scope.row);
|
||||
};
|
||||
|
||||
$scope.reset_panel = function(type) {
|
||||
var defaultSpan = 12;
|
||||
var _as = 12 - $scope.dashboard.rowSpan($scope.row);
|
||||
|
||||
$scope.panel = {
|
||||
title: 'no title (click here)',
|
||||
error : false,
|
||||
span : _as < defaultSpan && _as > 0 ? _as : defaultSpan,
|
||||
editable: true,
|
||||
@@ -139,13 +149,18 @@ function (angular, app, _) {
|
||||
|
||||
module.directive('panelDropZone', function() {
|
||||
return function(scope, element) {
|
||||
scope.$watch('dashboard.$$panelDragging', function(newVal) {
|
||||
if (newVal && scope.dashboard.rowSpan(scope.row) < 10) {
|
||||
scope.$on("ANGULAR_DRAG_START", function() {
|
||||
var dropZoneSpan = 12 - scope.dashboard.rowSpan(scope.row);
|
||||
|
||||
if (dropZoneSpan > 0) {
|
||||
element.find('.panel-container').css('height', scope.row.height);
|
||||
element[0].style.width = ((dropZoneSpan / 1.2) * 10) + '%';
|
||||
element.show();
|
||||
}
|
||||
else {
|
||||
element.hide();
|
||||
}
|
||||
});
|
||||
|
||||
scope.$on("ANGULAR_DRAG_END", function() {
|
||||
element.hide();
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
@@ -19,6 +19,9 @@ function (angular, _, config, $) {
|
||||
$scope.db = datasourceSrv.getGrafanaDB();
|
||||
$scope.currentSearchId = 0;
|
||||
|
||||
// events
|
||||
$scope.onAppEvent('dashboard-deleted', $scope.dashboardDeleted);
|
||||
|
||||
$timeout(function() {
|
||||
$scope.giveSearchFocus = $scope.giveSearchFocus + 1;
|
||||
$scope.query.query = 'title:';
|
||||
@@ -29,7 +32,7 @@ function (angular, _, config, $) {
|
||||
|
||||
$scope.keyDown = function (evt) {
|
||||
if (evt.keyCode === 27) {
|
||||
$scope.emitAppEvent('hide-dash-editor');
|
||||
$scope.appEvent('hide-dash-editor');
|
||||
}
|
||||
if (evt.keyCode === 40) {
|
||||
$scope.moveSelection(1);
|
||||
@@ -62,6 +65,7 @@ function (angular, _, config, $) {
|
||||
};
|
||||
|
||||
$scope.goToDashboard = function(id) {
|
||||
$location.search({});
|
||||
$location.path("/dashboard/db/" + id);
|
||||
};
|
||||
|
||||
@@ -121,7 +125,11 @@ function (angular, _, config, $) {
|
||||
|
||||
$scope.deleteDashboard = function(dash, evt) {
|
||||
evt.stopPropagation();
|
||||
$scope.emitAppEvent('delete-dashboard', { id: dash.id, title: dash.title });
|
||||
$scope.appEvent('delete-dashboard', { id: dash.id, title: dash.title });
|
||||
};
|
||||
|
||||
$scope.dashboardDeleted = function(evt, id) {
|
||||
var dash = _.findWhere($scope.results.dashboards, {id: id});
|
||||
$scope.results.dashboards = _.without($scope.results.dashboards, dash);
|
||||
};
|
||||
|
||||
|
||||
85
src/app/controllers/sharePanelCtrl.js
Normal file
85
src/app/controllers/sharePanelCtrl.js
Normal file
@@ -0,0 +1,85 @@
|
||||
define([
|
||||
'angular',
|
||||
'lodash'
|
||||
],
|
||||
function (angular, _) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.controllers');
|
||||
|
||||
module.controller('SharePanelCtrl', function($scope, $location, $timeout, timeSrv, $element, templateSrv) {
|
||||
|
||||
$scope.init = function() {
|
||||
$scope.editor = { index: 0 };
|
||||
$scope.forCurrent = true;
|
||||
$scope.toPanel = true;
|
||||
$scope.includeTemplateVars = true;
|
||||
|
||||
$scope.buildUrl();
|
||||
};
|
||||
|
||||
$scope.buildUrl = function() {
|
||||
var baseUrl = $location.absUrl();
|
||||
var queryStart = baseUrl.indexOf('?');
|
||||
|
||||
if (queryStart !== -1) {
|
||||
baseUrl = baseUrl.substring(0, queryStart);
|
||||
}
|
||||
|
||||
var panelId = $scope.panel.id;
|
||||
var params = angular.copy($location.search());
|
||||
|
||||
var range = timeSrv.timeRangeForUrl();
|
||||
params.from = range.from;
|
||||
params.to = range.to;
|
||||
|
||||
if ($scope.includeTemplateVars) {
|
||||
_.each(templateSrv.variables, function(variable) {
|
||||
params['var-' + variable.name] = variable.current.text;
|
||||
});
|
||||
}
|
||||
else {
|
||||
_.each(templateSrv.variables, function(variable) {
|
||||
delete params['var-' + variable.name];
|
||||
});
|
||||
}
|
||||
|
||||
if (!$scope.forCurrent) {
|
||||
delete params.from;
|
||||
delete params.to;
|
||||
}
|
||||
|
||||
if ($scope.toPanel) {
|
||||
params.panelId = panelId;
|
||||
params.fullscreen = true;
|
||||
} else {
|
||||
delete params.panelId;
|
||||
delete params.fullscreen;
|
||||
}
|
||||
|
||||
var paramsArray = [];
|
||||
_.each(params, function(value, key) {
|
||||
if (value === null) { return; }
|
||||
if (value === true) {
|
||||
paramsArray.push(key);
|
||||
} else {
|
||||
key += '=' + encodeURIComponent(value);
|
||||
paramsArray.push(key);
|
||||
}
|
||||
});
|
||||
|
||||
$scope.shareUrl = baseUrl + "?" + paramsArray.join('&') ;
|
||||
|
||||
$timeout(function() {
|
||||
var input = $element.find('[data-share-panel-url]');
|
||||
input.focus();
|
||||
input.select();
|
||||
}, 10);
|
||||
|
||||
};
|
||||
|
||||
$scope.init();
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,9 +1,8 @@
|
||||
define([
|
||||
'angular',
|
||||
'app',
|
||||
'lodash'
|
||||
],
|
||||
function (angular, app, _) {
|
||||
function (angular, _) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.controllers');
|
||||
|
||||
@@ -33,8 +33,30 @@ function (angular, _) {
|
||||
};
|
||||
|
||||
$scope.add = function() {
|
||||
$scope.variables.push($scope.current);
|
||||
$scope.update();
|
||||
if ($scope.isValid()) {
|
||||
$scope.variables.push($scope.current);
|
||||
$scope.update();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.isValid = function() {
|
||||
if (!$scope.current.name) {
|
||||
$scope.appEvent('alert-warning', ['Validation', 'Template variable requires a name']);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$scope.current.name.match(/^\w+$/)) {
|
||||
$scope.appEvent('alert-warning', ['Validation', 'Only word and digit characters are allowed in variable names']);
|
||||
return false;
|
||||
}
|
||||
|
||||
var sameName = _.findWhere($scope.variables, { name: $scope.current.name });
|
||||
if (sameName && sameName !== $scope.current) {
|
||||
$scope.appEvent('alert-warning', ['Validation', 'Variable with the same name already exists']);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
$scope.runQuery = function() {
|
||||
@@ -57,10 +79,12 @@ function (angular, _) {
|
||||
};
|
||||
|
||||
$scope.update = function() {
|
||||
$scope.runQuery().then(function() {
|
||||
$scope.reset();
|
||||
$scope.editor.index = 0;
|
||||
});
|
||||
if ($scope.isValid()) {
|
||||
$scope.runQuery().then(function() {
|
||||
$scope.reset();
|
||||
$scope.editor.index = 0;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.reset = function() {
|
||||
@@ -72,6 +96,9 @@ function (angular, _) {
|
||||
if ($scope.current.type === 'interval') {
|
||||
$scope.current.query = '1m,10m,30m,1h,6h,12h,1d,7d,14d,30d';
|
||||
}
|
||||
if ($scope.current.type === 'query') {
|
||||
$scope.current.query = '';
|
||||
}
|
||||
};
|
||||
|
||||
$scope.removeVariable = function(variable) {
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"editable": true,
|
||||
"type": "text",
|
||||
"mode": "html",
|
||||
"content": "<div class=\"text-center\" style=\"padding-top: 15px\">\n<img src=\"//grafana.org/assets/img/logo_transparent_200x75.png\"> \n</div>",
|
||||
"content": "<div class=\"text-center\" style=\"padding-top: 15px\">\n<img src=\"img/logo_transparent_200x.png\"> \n</div>",
|
||||
"style": {},
|
||||
"title": "Welcome to"
|
||||
}
|
||||
@@ -101,7 +101,6 @@
|
||||
"legend_counts": true,
|
||||
"timezone": "browser",
|
||||
"percentage": false,
|
||||
"zerofill": true,
|
||||
"nullPointMode": "connected",
|
||||
"steppedLine": false,
|
||||
"tooltip": {
|
||||
|
||||
@@ -17,14 +17,11 @@
|
||||
var window, document, ARGS, $, jQuery, moment, kbn;
|
||||
|
||||
// Setup some variables
|
||||
var dashboard, timspan;
|
||||
var dashboard;
|
||||
|
||||
// All url parameters are available via the ARGS object
|
||||
var ARGS;
|
||||
|
||||
// Set a default timespan if one isn't specified
|
||||
timspan = '1d';
|
||||
|
||||
// Intialize a skeleton with nothing but a rows array and service object
|
||||
dashboard = {
|
||||
rows : [],
|
||||
@@ -32,8 +29,12 @@ dashboard = {
|
||||
|
||||
// Set a title
|
||||
dashboard.title = 'Scripted dash';
|
||||
|
||||
// Set default time
|
||||
// time can be overriden in the url using from/to parameteres, but this is
|
||||
// handled automatically in grafana core during dashboard initialization
|
||||
dashboard.time = {
|
||||
from: "now-" + (ARGS.from || timspan),
|
||||
from: "now-6h",
|
||||
to: "now"
|
||||
};
|
||||
|
||||
@@ -68,6 +69,17 @@ for (var i = 0; i < rows; i++) {
|
||||
'target': "randomWalk('random walk2')"
|
||||
}
|
||||
],
|
||||
seriesOverrides: [
|
||||
{
|
||||
alias: '/random/',
|
||||
yaxis: 2,
|
||||
fill: 0,
|
||||
linewidth: 5
|
||||
}
|
||||
],
|
||||
tooltip: {
|
||||
shared: true
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
@@ -22,10 +22,7 @@ var window, document, ARGS, $, jQuery, moment, kbn;
|
||||
return function(callback) {
|
||||
|
||||
// Setup some variables
|
||||
var dashboard, timspan;
|
||||
|
||||
// Set a default timespan if one isn't specified
|
||||
timspan = '1d';
|
||||
var dashboard;
|
||||
|
||||
// Intialize a skeleton with nothing but a rows array and service object
|
||||
dashboard = {
|
||||
@@ -35,9 +32,13 @@ return function(callback) {
|
||||
|
||||
// Set a title
|
||||
dashboard.title = 'Scripted dash';
|
||||
|
||||
// Set default time
|
||||
// time can be overriden in the url using from/to parameteres, but this is
|
||||
// handled automatically in grafana core during dashboard initialization
|
||||
dashboard.time = {
|
||||
from: "now-" + (ARGS.from || timspan),
|
||||
to: "now"
|
||||
from: "now-6h",
|
||||
to: "now"
|
||||
};
|
||||
|
||||
var rows = 1;
|
||||
|
||||
95
src/app/dashboards/scripted_gen_and_save.js
Normal file
95
src/app/dashboards/scripted_gen_and_save.js
Normal file
@@ -0,0 +1,95 @@
|
||||
/* global _ */
|
||||
|
||||
/*
|
||||
* Complex scripted dashboard
|
||||
* This script generates a dashboard object that Grafana can load. It also takes a number of user
|
||||
* supplied URL parameters (int ARGS variable)
|
||||
*
|
||||
* Return a dashboard object, or a function
|
||||
*
|
||||
* For async scripts, return a function, this function must take a single callback function as argument,
|
||||
* call this callback function with the dashboard object (look at scripted_async.js for an example)
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// accessable variables in this scope
|
||||
var window, document, ARGS, $, jQuery, moment, kbn, services, _;
|
||||
|
||||
// default datasource
|
||||
var datasource = services.datasourceSrv.default;
|
||||
// get datasource used for saving dashboards
|
||||
var dashboardDB = services.datasourceSrv.getGrafanaDB();
|
||||
|
||||
var targets = [];
|
||||
|
||||
function getTargets(path) {
|
||||
return datasource.metricFindQuery(path + '.*').then(function(result) {
|
||||
if (!result) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (targets.length === 10) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var promises = _.map(result, function(metric) {
|
||||
if (metric.expandable) {
|
||||
return getTargets(path + "." + metric.text);
|
||||
}
|
||||
else {
|
||||
targets.push(path + '.' + metric.text);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
return services.$q.all(promises);
|
||||
});
|
||||
}
|
||||
|
||||
function createDashboard(target, index) {
|
||||
// Intialize a skeleton with nothing but a rows array and service object
|
||||
var dashboard = { rows : [] };
|
||||
dashboard.title = 'Scripted dash ' + index;
|
||||
dashboard.time = {
|
||||
from: "now-6h",
|
||||
to: "now"
|
||||
};
|
||||
|
||||
dashboard.rows.push({
|
||||
title: 'Chart',
|
||||
height: '300px',
|
||||
panels: [
|
||||
{
|
||||
title: 'Events',
|
||||
type: 'graph',
|
||||
span: 12,
|
||||
targets: [ {target: target} ]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
return dashboard;
|
||||
}
|
||||
|
||||
function saveDashboard(dashboard) {
|
||||
var model = services.dashboardSrv.create(dashboard);
|
||||
dashboardDB.saveDashboard(model);
|
||||
}
|
||||
|
||||
return function(callback) {
|
||||
|
||||
getTargets('apps').then(function() {
|
||||
console.log('targets: ', targets);
|
||||
_.each(targets, function(target, index) {
|
||||
var dashboard = createDashboard(target, index);
|
||||
saveDashboard(dashboard);
|
||||
|
||||
if (index === targets.length - 1) {
|
||||
callback(dashboard);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
@@ -17,25 +17,27 @@
|
||||
var window, document, ARGS, $, jQuery, moment, kbn;
|
||||
|
||||
// Setup some variables
|
||||
var dashboard, timspan;
|
||||
var dashboard;
|
||||
|
||||
// All url parameters are available via the ARGS object
|
||||
var ARGS;
|
||||
|
||||
// Set a default timespan if one isn't specified
|
||||
timspan = '1d';
|
||||
|
||||
// Intialize a skeleton with nothing but a rows array and service object
|
||||
dashboard = {
|
||||
rows : [],
|
||||
};
|
||||
|
||||
// Set a title
|
||||
dashboard.title = 'Scripted dash';
|
||||
dashboard.title = 'Scripted and templated dash';
|
||||
|
||||
// Set default time
|
||||
// time can be overriden in the url using from/to parameteres, but this is
|
||||
// handled automatically in grafana core during dashboard initialization
|
||||
dashboard.time = {
|
||||
from: "now-" + (ARGS.from || timspan),
|
||||
from: "now-6h",
|
||||
to: "now"
|
||||
};
|
||||
|
||||
dashboard.templating = {
|
||||
enable: true,
|
||||
list: [
|
||||
|
||||
@@ -65,7 +65,6 @@
|
||||
"avg": false
|
||||
},
|
||||
"percentage": false,
|
||||
"zerofill": true,
|
||||
"nullPointMode": "connected",
|
||||
"steppedLine": false,
|
||||
"tooltip": {
|
||||
|
||||
@@ -68,13 +68,12 @@ function (angular, app, _, $, gfunc) {
|
||||
});
|
||||
|
||||
$input.blur(function() {
|
||||
$input.hide();
|
||||
$input.val('');
|
||||
$button.show();
|
||||
$button.focus();
|
||||
// clicking the function dropdown menu wont
|
||||
// work if you remove class at once
|
||||
setTimeout(function() {
|
||||
$input.val('');
|
||||
$input.hide();
|
||||
$button.show();
|
||||
elem.removeClass('open');
|
||||
}, 200);
|
||||
});
|
||||
|
||||
@@ -10,7 +10,6 @@ define([
|
||||
'./confirmClick',
|
||||
'./configModal',
|
||||
'./spectrumPicker',
|
||||
'./grafanaGraph',
|
||||
'./bootstrap-tagsinput',
|
||||
'./bodyClass',
|
||||
'./addGraphiteFunc',
|
||||
@@ -18,5 +17,6 @@ define([
|
||||
'./templateParamSelector',
|
||||
'./graphiteSegment',
|
||||
'./grafanaVersionCheck',
|
||||
'./dropdown.typeahead',
|
||||
'./influxdbFuncEditor'
|
||||
], function () {});
|
||||
|
||||
@@ -16,7 +16,7 @@ function (angular, $) {
|
||||
elem.bind('click',function() {
|
||||
$timeout(function() {
|
||||
var editorScope = attrs.editorScope === 'isolated' ? null : scope;
|
||||
scope.emitAppEvent('show-dash-editor', { src: partial, scope: editorScope });
|
||||
scope.appEvent('show-dash-editor', { src: partial, scope: editorScope });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -16,7 +16,13 @@ function (angular, kbn) {
|
||||
var readerOnload = function() {
|
||||
return function(e) {
|
||||
scope.$apply(function() {
|
||||
window.grafanaImportDashboard = JSON.parse(e.target.result);
|
||||
try {
|
||||
window.grafanaImportDashboard = JSON.parse(e.target.result);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
scope.appEvent('alert-error', ['Import failed', 'JSON -> JS Serialization failed: ' + err.message]);
|
||||
return;
|
||||
}
|
||||
var title = kbn.slugifyForUrl(window.grafanaImportDashboard.title);
|
||||
$location.path('/dashboard/import/' + title);
|
||||
});
|
||||
|
||||
105
src/app/directives/dropdown.typeahead.js
Normal file
105
src/app/directives/dropdown.typeahead.js
Normal file
@@ -0,0 +1,105 @@
|
||||
define([
|
||||
'angular',
|
||||
'app',
|
||||
'lodash',
|
||||
'jquery',
|
||||
],
|
||||
function (angular, app, _, $) {
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
.module('grafana.directives')
|
||||
.directive('dropdownTypeahead', function($compile) {
|
||||
|
||||
var inputTemplate = '<input type="text"'+
|
||||
' class="grafana-target-segment-input input-medium grafana-target-segment-input"' +
|
||||
' spellcheck="false" style="display:none"></input>';
|
||||
|
||||
var buttonTemplate = '<a class="grafana-target-segment grafana-target-function dropdown-toggle"' +
|
||||
' tabindex="1" gf-dropdown="menuItems" data-toggle="dropdown"' +
|
||||
' data-placement="top"><i class="icon-plus"></i></a>';
|
||||
|
||||
return {
|
||||
scope: {
|
||||
"menuItems": "=dropdownTypeahead",
|
||||
"dropdownTypeaheadOnSelect": "&dropdownTypeaheadOnSelect"
|
||||
},
|
||||
link: function($scope, elem) {
|
||||
var $input = $(inputTemplate);
|
||||
var $button = $(buttonTemplate);
|
||||
$input.appendTo(elem);
|
||||
$button.appendTo(elem);
|
||||
|
||||
var typeaheadValues = _.reduce($scope.menuItems, function(memo, value) {
|
||||
_.each(value.submenu, function(item) {
|
||||
memo.push(value.text + ' ' + item.text);
|
||||
});
|
||||
return memo;
|
||||
}, []);
|
||||
|
||||
$scope.menuItemSelected = function(optionIndex, valueIndex) {
|
||||
var option = $scope.menuItems[optionIndex];
|
||||
var result = {
|
||||
$item: option.submenu[valueIndex],
|
||||
$optionIndex: optionIndex,
|
||||
$valueIndex: valueIndex
|
||||
};
|
||||
|
||||
$scope.dropdownTypeaheadOnSelect(result);
|
||||
};
|
||||
|
||||
$input.attr('data-provide', 'typeahead');
|
||||
$input.typeahead({
|
||||
source: typeaheadValues,
|
||||
minLength: 1,
|
||||
items: 10,
|
||||
updater: function (value) {
|
||||
var result = {};
|
||||
_.each($scope.menuItems, function(menuItem, optionIndex) {
|
||||
_.each(menuItem.submenu, function(submenuItem, valueIndex) {
|
||||
if (value === (menuItem.text + ' ' + submenuItem.text)) {
|
||||
result.$item = submenuItem;
|
||||
result.$optionIndex = optionIndex;
|
||||
result.$valueIndex = valueIndex;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (result.$item) {
|
||||
$scope.$apply(function() {
|
||||
$scope.dropdownTypeaheadOnSelect(result);
|
||||
});
|
||||
}
|
||||
|
||||
$input.trigger('blur');
|
||||
return '';
|
||||
}
|
||||
});
|
||||
|
||||
$button.click(function() {
|
||||
$button.hide();
|
||||
$input.show();
|
||||
$input.focus();
|
||||
});
|
||||
|
||||
$input.keyup(function() {
|
||||
elem.toggleClass('open', $input.val() === '');
|
||||
});
|
||||
|
||||
$input.blur(function() {
|
||||
$input.hide();
|
||||
$input.val('');
|
||||
$button.show();
|
||||
$button.focus();
|
||||
// clicking the function dropdown menu wont
|
||||
// work if you remove class at once
|
||||
setTimeout(function() {
|
||||
elem.removeClass('open');
|
||||
}, 200);
|
||||
});
|
||||
|
||||
$compile(elem.contents())($scope);
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
@@ -1,9 +1,10 @@
|
||||
define([
|
||||
'angular',
|
||||
'jquery',
|
||||
'lodash',
|
||||
'config',
|
||||
'./panelMenu',
|
||||
],
|
||||
function (angular, $) {
|
||||
function (angular, $, config) {
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
@@ -15,37 +16,19 @@ function (angular, $) {
|
||||
|
||||
var panelHeader =
|
||||
'<div class="panel-header">'+
|
||||
'<div class="row-fluid panel-extra">' +
|
||||
'<div class="panel-extra-container">' +
|
||||
'<span class="alert-error panel-error small pointer"' +
|
||||
'config-modal="app/partials/inspector.html" ng-if="panelMeta.error">' +
|
||||
'<span data-placement="right" bs-tooltip="panelMeta.error">' +
|
||||
'<i class="icon-exclamation-sign"></i><span class="panel-error-arrow"></span>' +
|
||||
'</span>' +
|
||||
'<span class="alert-error panel-error small pointer"' +
|
||||
'config-modal="app/partials/inspector.html" ng-if="panelMeta.error">' +
|
||||
'<span data-placement="top" bs-tooltip="panelMeta.error">' +
|
||||
'<i class="icon-exclamation-sign"></i><span class="panel-error-arrow"></span>' +
|
||||
'</span>' +
|
||||
'</span>' +
|
||||
|
||||
'<span class="panel-loading" ng-show="panelMeta.loading">' +
|
||||
'<i class="icon-spinner icon-spin icon-large"></i>' +
|
||||
'</span>' +
|
||||
'<span class="panel-loading" ng-show="panelMeta.loading">' +
|
||||
'<i class="icon-spinner icon-spin icon-large"></i>' +
|
||||
'</span>' +
|
||||
|
||||
'<span class="dropdown">' +
|
||||
'<span class="panel-text panel-title pointer" gf-dropdown="panelMeta.menu" tabindex="1" ' +
|
||||
'data-drag=true data-jqyoui-options="kbnJqUiDraggableOptions"'+
|
||||
' jqyoui-draggable="'+
|
||||
'{'+
|
||||
'animate:false,'+
|
||||
'mutate:false,'+
|
||||
'index:{{$index}},'+
|
||||
'onStart:\'panelMoveStart\','+
|
||||
'onStop:\'panelMoveStop\''+
|
||||
'}" ng-model="panel" ' +
|
||||
'>' +
|
||||
'{{panel.title | interpolateTemplateVars}}' +
|
||||
'</span>' +
|
||||
'</span>'+
|
||||
|
||||
'</div>'+
|
||||
'</div>\n'+
|
||||
'<div class="panel-title-container drag-handle" panel-menu></div>' +
|
||||
'</div>'+
|
||||
'</div>';
|
||||
|
||||
return {
|
||||
@@ -86,10 +69,12 @@ function (angular, $) {
|
||||
|
||||
elem.addClass('ng-cloak');
|
||||
|
||||
var panelPath = config.panels[panelType].path;
|
||||
|
||||
$scope.require([
|
||||
'jquery',
|
||||
'text!panels/'+panelType+'/module.html',
|
||||
'panels/' + panelType + "/module",
|
||||
'text!'+panelPath+'/module.html',
|
||||
panelPath + "/module",
|
||||
], function ($, moduleTemplate) {
|
||||
var $module = $(moduleTemplate);
|
||||
$module.prepend(panelHeader);
|
||||
|
||||
@@ -206,6 +206,7 @@ function (angular, _, $) {
|
||||
if ($target.hasClass('icon-arrow-left')) {
|
||||
$scope.$apply(function() {
|
||||
_.move($scope.functions, $scope.$index, $scope.$index - 1);
|
||||
$scope.targetChanged();
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -213,6 +214,7 @@ function (angular, _, $) {
|
||||
if ($target.hasClass('icon-arrow-right')) {
|
||||
$scope.$apply(function() {
|
||||
_.move($scope.functions, $scope.$index, $scope.$index + 1);
|
||||
$scope.targetChanged();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
155
src/app/directives/panelMenu.js
Normal file
155
src/app/directives/panelMenu.js
Normal file
@@ -0,0 +1,155 @@
|
||||
define([
|
||||
'angular',
|
||||
'jquery',
|
||||
'lodash',
|
||||
],
|
||||
function (angular, $, _) {
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
.module('grafana.directives')
|
||||
.directive('panelMenu', function($compile, linkSrv) {
|
||||
var linkTemplate =
|
||||
'<span class="panel-title drag-handle pointer">' +
|
||||
'<span class="panel-title-text drag-handle">{{panel.title | interpolateTemplateVars}}</span>' +
|
||||
'<span class="panel-links-icon"></span>' +
|
||||
'</span>';
|
||||
|
||||
function createMenuTemplate($scope) {
|
||||
var template = '<div class="panel-menu small">';
|
||||
template += '<div class="panel-menu-inner">';
|
||||
template += '<div class="panel-menu-row">';
|
||||
template += '<a class="panel-menu-icon pull-left" ng-click="updateColumnSpan(-1)"><i class="icon-minus"></i></a>';
|
||||
template += '<a class="panel-menu-icon pull-left" ng-click="updateColumnSpan(1)"><i class="icon-plus"></i></a>';
|
||||
template += '<a class="panel-menu-icon pull-right" ng-click="remove_panel_from_row(row, panel)"><i class="icon-remove"></i></a>';
|
||||
template += '<div class="clearfix"></div>';
|
||||
template += '</div>';
|
||||
|
||||
template += '<div class="panel-menu-row">';
|
||||
template += '<a class="panel-menu-link" gf-dropdown="extendedMenu"><i class="icon-th-list"></i></a>';
|
||||
|
||||
_.each($scope.panelMeta.menu, function(item) {
|
||||
template += '<a class="panel-menu-link" ';
|
||||
if (item.click) { template += ' ng-click="' + item.click + '"'; }
|
||||
if (item.editorLink) { template += ' dash-editor-link="' + item.editorLink + '"'; }
|
||||
template += '>';
|
||||
template += item.text + '</a>';
|
||||
});
|
||||
|
||||
template += '</div>';
|
||||
template += '</div>';
|
||||
template += '</div>';
|
||||
return template;
|
||||
}
|
||||
|
||||
function getExtendedMenu($scope) {
|
||||
var menu = angular.copy($scope.panelMeta.extendedMenu);
|
||||
|
||||
if ($scope.panel.links) {
|
||||
_.each($scope.panel.links, function(link) {
|
||||
var info = linkSrv.getPanelLinkAnchorInfo(link);
|
||||
menu.push({text: info.title, href: info.href, target: info.target });
|
||||
});
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function($scope, elem) {
|
||||
var $link = $(linkTemplate);
|
||||
var $panelContainer = elem.parents(".panel-container");
|
||||
var menuWidth = $scope.panelMeta.menu.length === 4 ? 236 : 191;
|
||||
var menuScope = null;
|
||||
var timeout = null;
|
||||
var $menu = null;
|
||||
|
||||
elem.append($link);
|
||||
|
||||
$scope.$watchCollection('panel.links', function(newValue) {
|
||||
var showIcon = (newValue ? newValue.length > 0 : false) && $scope.panel.title !== '';
|
||||
$link.toggleClass('has-panel-links', showIcon);
|
||||
});
|
||||
|
||||
function dismiss(time, force) {
|
||||
clearTimeout(timeout);
|
||||
timeout = null;
|
||||
|
||||
if (time) {
|
||||
timeout = setTimeout(dismiss, time);
|
||||
return;
|
||||
}
|
||||
|
||||
// if hovering or draging pospone close
|
||||
if (force !== true) {
|
||||
if ($menu.is(':hover') || $scope.dashboard.$$panelDragging) {
|
||||
dismiss(2200);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (menuScope) {
|
||||
$menu.unbind();
|
||||
$menu.remove();
|
||||
menuScope.$destroy();
|
||||
menuScope = null;
|
||||
$menu = null;
|
||||
$panelContainer.removeClass('panel-highlight');
|
||||
}
|
||||
}
|
||||
|
||||
var showMenu = function(e) {
|
||||
// if menu item is clicked and menu was just removed from dom ignore this event
|
||||
if (!$.contains(document, e.target)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($menu) {
|
||||
dismiss();
|
||||
return;
|
||||
}
|
||||
|
||||
var windowWidth = $(window).width();
|
||||
var panelLeftPos = $(elem).offset().left;
|
||||
var panelWidth = $(elem).width();
|
||||
var menuLeftPos = (panelWidth / 2) - (menuWidth/2);
|
||||
var stickingOut = panelLeftPos + menuLeftPos + menuWidth - windowWidth;
|
||||
if (stickingOut > 0) {
|
||||
menuLeftPos -= stickingOut + 10;
|
||||
}
|
||||
if (panelLeftPos + menuLeftPos < 0) {
|
||||
menuLeftPos = 0;
|
||||
}
|
||||
|
||||
var menuTemplate = createMenuTemplate($scope);
|
||||
$menu = $(menuTemplate);
|
||||
$menu.css('left', menuLeftPos);
|
||||
$menu.mouseleave(function() {
|
||||
dismiss(1000);
|
||||
});
|
||||
|
||||
menuScope = $scope.$new();
|
||||
menuScope.extendedMenu = getExtendedMenu($scope);
|
||||
menuScope.dismiss = function() {
|
||||
dismiss(null, true);
|
||||
};
|
||||
|
||||
$('.panel-menu').remove();
|
||||
elem.append($menu);
|
||||
$scope.$apply(function() {
|
||||
$compile($menu.contents())(menuScope);
|
||||
});
|
||||
|
||||
$(".panel-container").removeClass('panel-highlight');
|
||||
$panelContainer.toggleClass('panel-highlight');
|
||||
|
||||
dismiss(2200);
|
||||
};
|
||||
|
||||
elem.click(showMenu);
|
||||
$compile(elem.contents())($scope);
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
@@ -32,7 +32,11 @@ function (angular) {
|
||||
};
|
||||
|
||||
input.spectrum(options);
|
||||
|
||||
scope.$on('$destroy', function() {
|
||||
input.spectrum('destroy');
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,4 +17,27 @@ function (angular, kbn) {
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
angular
|
||||
.module('grafana.directives')
|
||||
.directive('editorOptBool', function($compile) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
link: function(scope, elem, attrs) {
|
||||
var ngchange = attrs.change ? (' ng-change="' + attrs.change + '"') : '';
|
||||
var tip = attrs.tip ? (' <tip>' + attrs.tip + '</tip>') : '';
|
||||
var showIf = attrs.showIf ? (' ng-show="' + attrs.showIf + '" ') : '';
|
||||
|
||||
var template = '<div class="editor-option text-center"' + showIf + '>' +
|
||||
' <label for="' + attrs.model + '" class="small">' +
|
||||
attrs.text + tip + '</label>' +
|
||||
'<input class="cr1" id="' + attrs.model + '" type="checkbox" ' +
|
||||
' ng-model="' + attrs.model + '"' + ngchange +
|
||||
' ng-checked="' + attrs.model + '"></input>' +
|
||||
' <label for="' + attrs.model + '" class="cr1"></label>';
|
||||
elem.replaceWith($compile(angular.element(template))(scope));
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
3
src/app/features/all.js
Normal file
3
src/app/features/all.js
Normal file
@@ -0,0 +1,3 @@
|
||||
define([
|
||||
'./panellinkeditor/module',
|
||||
], function () {});
|
||||
39
src/app/features/panellinkeditor/linkSrv.js
Normal file
39
src/app/features/panellinkeditor/linkSrv.js
Normal file
@@ -0,0 +1,39 @@
|
||||
define([
|
||||
'angular',
|
||||
'kbn',
|
||||
],
|
||||
function (angular, kbn) {
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
.module('grafana.services')
|
||||
.service('linkSrv', function(templateSrv, timeSrv) {
|
||||
|
||||
this.getPanelLinkAnchorInfo = function(link) {
|
||||
var info = {};
|
||||
if (link.type === 'absolute') {
|
||||
info.target = '_blank';
|
||||
info.href = templateSrv.replace(link.url || '');
|
||||
info.title = templateSrv.replace(link.title || '');
|
||||
info.href += '?';
|
||||
|
||||
}
|
||||
else {
|
||||
info.title = templateSrv.replace(link.title || '');
|
||||
var slug = kbn.slugifyForUrl(link.dashboard || '');
|
||||
info.href = '#dashboard/db/' + slug + '?';
|
||||
}
|
||||
|
||||
var range = timeSrv.timeRangeForUrl();
|
||||
info.href += 'from=' + range.from;
|
||||
info.href += '&to=' + range.to;
|
||||
|
||||
if (link.params) {
|
||||
info.href += "&" + templateSrv.replace(link.params);
|
||||
}
|
||||
|
||||
return info;
|
||||
};
|
||||
|
||||
});
|
||||
});
|
||||
51
src/app/features/panellinkeditor/module.html
Normal file
51
src/app/features/panellinkeditor/module.html
Normal file
@@ -0,0 +1,51 @@
|
||||
<div class="editor-row">
|
||||
<div class="section">
|
||||
<h5>Drilldown / detail link<tip>These links appear in the dropdown menu in the panel menu. </tip></h5>
|
||||
|
||||
<div class="grafana-target" ng-repeat="link in panel.links"j>
|
||||
<div class="grafana-target-inner">
|
||||
<ul class="grafana-segment-list">
|
||||
<li class="grafana-target-segment">
|
||||
<i class="icon-remove pointer" ng-click="deleteLink(link)"></i>
|
||||
</li>
|
||||
|
||||
<li class="grafana-target-segment">title</li>
|
||||
<li>
|
||||
<input type="text" ng-model="link.title" class="input-medium grafana-target-segment-input">
|
||||
</li>
|
||||
|
||||
<li class="grafana-target-segment">type</li>
|
||||
<li>
|
||||
<select class="input-medium grafana-target-segment-input" style="width: 101px;" ng-model="link.type" ng-options="f for f in ['dashboard','absolute']"></select>
|
||||
</li>
|
||||
|
||||
<li class="grafana-target-segment" ng-show="link.type === 'dashboard'">dashboard</li>
|
||||
<li ng-show="link.type === 'dashboard'">
|
||||
<input type="text"
|
||||
ng-model="link.dashboard"
|
||||
bs-typeahead="searchDashboards"
|
||||
class="input-large grafana-target-segment-input">
|
||||
</li>
|
||||
|
||||
<li class="grafana-target-segment" ng-show="link.type === 'absolute'">url</li>
|
||||
<li ng-show="link.type === 'absolute'">
|
||||
<input type="text" ng-model="link.url" class="input-large grafana-target-segment-input">
|
||||
</li>
|
||||
|
||||
<li class="grafana-target-segment">params
|
||||
<tip>Use var-variableName=value to pass templating variables.</tip>
|
||||
</li>
|
||||
<li>
|
||||
<input type="text" ng-model="link.params" class="input-medium grafana-target-segment-input">
|
||||
</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="editor-row">
|
||||
<br>
|
||||
<button class="btn btn-success" ng-click="addLink()">Add link</button>
|
||||
</div>
|
||||
51
src/app/features/panellinkeditor/module.js
Normal file
51
src/app/features/panellinkeditor/module.js
Normal file
@@ -0,0 +1,51 @@
|
||||
define([
|
||||
'angular',
|
||||
'lodash',
|
||||
'./linkSrv',
|
||||
],
|
||||
function (angular, _) {
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
.module('grafana.directives')
|
||||
.directive('panelLinkEditor', function() {
|
||||
return {
|
||||
scope: {
|
||||
panel: "="
|
||||
},
|
||||
restrict: 'E',
|
||||
controller: 'PanelLinkEditorCtrl',
|
||||
templateUrl: 'app/features/panellinkeditor/module.html',
|
||||
link: function() {
|
||||
}
|
||||
};
|
||||
}).controller('PanelLinkEditorCtrl', function($scope, datasourceSrv) {
|
||||
|
||||
$scope.panel.links = $scope.panel.links || [];
|
||||
|
||||
$scope.addLink = function() {
|
||||
$scope.panel.links.push({
|
||||
type: 'dashboard',
|
||||
name: 'Drilldown dashboard'
|
||||
});
|
||||
};
|
||||
|
||||
$scope.searchDashboards = function(query, callback) {
|
||||
var ds = datasourceSrv.getGrafanaDB();
|
||||
if (ds === null) { return; }
|
||||
|
||||
ds.searchDashboards(query).then(function(result) {
|
||||
var dashboards = _.map(result.dashboards, function(dash) {
|
||||
return dash.title;
|
||||
});
|
||||
|
||||
callback(dashboards);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.deleteLink = function(link) {
|
||||
$scope.panel.links = _.without($scope.panel.links, link);
|
||||
};
|
||||
|
||||
});
|
||||
});
|
||||
@@ -56,9 +56,13 @@ define(['angular', 'jquery', 'lodash', 'moment'], function (angular, $, _, momen
|
||||
});
|
||||
|
||||
module.filter('interpolateTemplateVars', function(templateSrv) {
|
||||
return function(text) {
|
||||
function interpolateTemplateVars(text) {
|
||||
return templateSrv.replaceWithText(text);
|
||||
};
|
||||
}
|
||||
|
||||
interpolateTemplateVars.$stateful = true;
|
||||
|
||||
return interpolateTemplateVars;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -40,42 +40,20 @@
|
||||
<div class="editor-row">
|
||||
<div class="section">
|
||||
<h5>Legend styles</h5>
|
||||
<div class="editor-option">
|
||||
<label class="small">Show Legend</label><input type="checkbox" ng-model="panel.legend.show" ng-checked="panel.legend.show" ng-change="render();">
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Include Values</label><input type="checkbox" ng-model="panel.legend.values" ng-checked="panel.legend.values" ng-change="render();">
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Align as table</label><input type="checkbox" ng-model="panel.legend.alignAsTable" ng-checked="panel.legend.alignAsTable">
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Right side</label><input type="checkbox" ng-model="panel.legend.rightSide" ng-change="render();" ng-checked="panel.legend.rightSide">
|
||||
</div>
|
||||
</div>
|
||||
<editor-opt-bool text="Show" model="panel.legend.show" change="get_data();"></editor-opt-bool>
|
||||
<editor-opt-bool text="Values" model="panel.legend.values" change="render()"></editor-opt-bool>
|
||||
<editor-opt-bool text="Table" model="panel.legend.alignAsTable" change="render()"></editor-opt-bool>
|
||||
<editor-opt-bool text="Right side" model="panel.legend.rightSide" change="render()"></editor-opt-bool>
|
||||
<editor-opt-bool text="Hide empty" model="panel.legend.hideEmpty" tip="Hides series with only null values" change="render()"></editor-opt-bool>
|
||||
</div>
|
||||
|
||||
<div class="section" ng-if="panel.legend.values">
|
||||
<h5>Legend values</h5>
|
||||
<div class="editor-option">
|
||||
<label class="small">Min</label><input type="checkbox" ng-model="panel.legend.min" ng-checked="panel.legend.min" ng-change="render();">
|
||||
</div>
|
||||
|
||||
<div class="editor-option">
|
||||
<label class="small">Max</label><input type="checkbox" ng-model="panel.legend.max" ng-checked="panel.legend.max" ng-change="render();">
|
||||
</div>
|
||||
|
||||
<div class="editor-option">
|
||||
<label class="small">Current</label><input type="checkbox" ng-model="panel.legend.current" ng-checked="panel.legend.current" ng-change="render();">
|
||||
</div>
|
||||
|
||||
<div class="editor-option">
|
||||
<label class="small">Total</label><input type="checkbox" ng-model="panel.legend.total" ng-checked="panel.legend.total" ng-change="render();">
|
||||
</div>
|
||||
|
||||
<div class="editor-option">
|
||||
<label class="small">Avg</label><input type="checkbox" ng-model="panel.legend.avg" ng-checked="panel.legend.avg" ng-change="render();">
|
||||
</div>
|
||||
|
||||
<editor-opt-bool text="Min" model="panel.legend.min" change="render()"></editor-opt-bool>
|
||||
<editor-opt-bool text="Max" model="panel.legend.max" change="render()"></editor-opt-bool>
|
||||
<editor-opt-bool text="Current" model="panel.legend.current" change="render()"></editor-opt-bool>
|
||||
<editor-opt-bool text="Total" model="panel.legend.total" change="render()"></editor-opt-bool>
|
||||
<editor-opt-bool text="Avg" model="panel.legend.avg" change="render()"></editor-opt-bool>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
@@ -96,19 +74,13 @@
|
||||
<label class="small">Color</label>
|
||||
<spectrum-picker ng-model="panel.grid.threshold2Color" ng-change="render()" ></spectrum-picker>
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Line mode</label><input type="checkbox" ng-model="panel.grid.thresholdLine" ng-checked="panel.grid.thresholdLine" ng-change="render();">
|
||||
</div>
|
||||
<editor-opt-bool text="Line mode" model="panel.grid.thresholdLine" change="render()"></editor-opt-bool>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h5>Show Axes</h5>
|
||||
<div class="editor-option">
|
||||
<label class="small">X-Axis</label><input type="checkbox" ng-model="panel['x-axis']" ng-checked="panel['x-axis']" ng-change="render()">
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Y-Axis</label><input type="checkbox" ng-model="panel['y-axis']" ng-checked="panel['y-axis']" ng-change="render()">
|
||||
</div>
|
||||
<editor-opt-bool text="X-Axis" model="panel['x-axis']" change="render()"></editor-opt-bool>
|
||||
<editor-opt-bool text="Y-axis" model="panel['y-axis']" change="render()"></editor-opt-bool>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -3,9 +3,18 @@ define([
|
||||
'jquery',
|
||||
'kbn',
|
||||
'moment',
|
||||
'lodash'
|
||||
'lodash',
|
||||
'./graph.tooltip',
|
||||
'jquery.flot',
|
||||
'jquery.flot.events',
|
||||
'jquery.flot.selection',
|
||||
'jquery.flot.time',
|
||||
'jquery.flot.stack',
|
||||
'jquery.flot.stackpercent',
|
||||
'jquery.flot.fillbelow',
|
||||
'jquery.flot.crosshair'
|
||||
],
|
||||
function (angular, $, kbn, moment, _) {
|
||||
function (angular, $, kbn, moment, _, GraphTooltip) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.directives');
|
||||
@@ -15,16 +24,35 @@ function (angular, $, kbn, moment, _) {
|
||||
restrict: 'A',
|
||||
template: '<div> </div>',
|
||||
link: function(scope, elem) {
|
||||
var data, annotations;
|
||||
var dashboard = scope.dashboard;
|
||||
var data, annotations;
|
||||
var sortedSeries;
|
||||
var legendSideLastValue = null;
|
||||
scope.crosshairEmiter = false;
|
||||
|
||||
scope.$on('refresh',function() {
|
||||
scope.get_data();
|
||||
scope.onAppEvent('setCrosshair', function(event, info) {
|
||||
// do not need to to this if event is from this panel
|
||||
if (info.scope === scope) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(dashboard.sharedCrosshair) {
|
||||
var plot = elem.data().plot;
|
||||
if (plot) {
|
||||
plot.setCrosshair({ x: info.pos.x, y: info.pos.y });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
scope.$on('toggleLegend', function() {
|
||||
render_panel();
|
||||
scope.onAppEvent('clearCrosshair', function() {
|
||||
var plot = elem.data().plot;
|
||||
if (plot) {
|
||||
plot.clearCrosshair();
|
||||
}
|
||||
});
|
||||
|
||||
scope.$on('refresh', function() {
|
||||
scope.get_data();
|
||||
});
|
||||
|
||||
// Receive render events
|
||||
@@ -45,10 +73,11 @@ function (angular, $, kbn, moment, _) {
|
||||
height = parseInt(height.replace('px', ''), 10);
|
||||
}
|
||||
|
||||
height = height - 32; // subtract panel title bar
|
||||
height -= 5; // padding
|
||||
height -= scope.panel.title ? 24 : 9; // subtract panel title bar
|
||||
|
||||
if (scope.panel.legend.show && !scope.panel.legend.rightSide) {
|
||||
height = height - 21; // subtract one line legend
|
||||
height = height - 26; // subtract one line legend
|
||||
}
|
||||
|
||||
elem.css('height', height + 'px');
|
||||
@@ -80,6 +109,23 @@ function (angular, $, kbn, moment, _) {
|
||||
}
|
||||
}
|
||||
|
||||
function updateLegendValues(plot) {
|
||||
var yaxis = plot.getYAxes();
|
||||
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
var series = data[i];
|
||||
var axis = yaxis[series.yaxis - 1];
|
||||
var formater = kbn.valueFormats[scope.panel.y_formats[series.yaxis - 1]];
|
||||
|
||||
// legend and tooltip gets one more decimal precision
|
||||
// than graph legend ticks
|
||||
var tickDecimals = (axis.tickDecimals || -1) + 1;
|
||||
|
||||
series.updateLegendValues(formater, tickDecimals, axis.scaledDecimals + 2);
|
||||
if(!scope.$$phase) { scope.$digest(); }
|
||||
}
|
||||
}
|
||||
|
||||
// Function for rendering panel
|
||||
function render_panel() {
|
||||
if (shouldAbortRender()) {
|
||||
@@ -91,6 +137,7 @@ function (angular, $, kbn, moment, _) {
|
||||
|
||||
// Populate element
|
||||
var options = {
|
||||
hooks: { draw: [updateLegendValues] },
|
||||
legend: { show: false },
|
||||
series: {
|
||||
stackpercent: panel.stack ? panel.percentage : false,
|
||||
@@ -113,7 +160,8 @@ function (angular, $, kbn, moment, _) {
|
||||
show: panel.points,
|
||||
fill: 1,
|
||||
fillColor: false,
|
||||
radius: panel.pointradius
|
||||
radius: panel.points ? panel.pointradius : 2
|
||||
// little points when highlight points
|
||||
},
|
||||
shadowSize: 1
|
||||
},
|
||||
@@ -130,6 +178,9 @@ function (angular, $, kbn, moment, _) {
|
||||
selection: {
|
||||
mode: "x",
|
||||
color: '#666'
|
||||
},
|
||||
crosshair: {
|
||||
mode: panel.tooltip.shared || dashboard.sharedCrosshair ? "x" : null
|
||||
}
|
||||
};
|
||||
|
||||
@@ -137,15 +188,16 @@ function (angular, $, kbn, moment, _) {
|
||||
var series = data[i];
|
||||
series.applySeriesOverrides(panel.seriesOverrides);
|
||||
series.data = series.getFlotPairs(panel.nullPointMode, panel.y_formats);
|
||||
|
||||
// if hidden remove points and disable stack
|
||||
if (scope.hiddenSeries[series.info.alias]) {
|
||||
if (scope.hiddenSeries[series.alias]) {
|
||||
series.data = [];
|
||||
series.stack = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (data.length && data[0].info.timeStep) {
|
||||
options.series.bars.barWidth = data[0].info.timeStep / 1.5;
|
||||
if (data.length && data[0].stats.timeStep) {
|
||||
options.series.bars.barWidth = data[0].stats.timeStep / 1.5;
|
||||
}
|
||||
|
||||
addTimeAxis(options);
|
||||
@@ -153,7 +205,7 @@ function (angular, $, kbn, moment, _) {
|
||||
addAnnotations(options);
|
||||
configureAxisOptions(data, options);
|
||||
|
||||
var sortedSeries = _.sortBy(data, function(series) { return series.zindex; });
|
||||
sortedSeries = _.sortBy(data, function(series) { return series.zindex; });
|
||||
|
||||
function callPlot() {
|
||||
try {
|
||||
@@ -166,6 +218,8 @@ function (angular, $, kbn, moment, _) {
|
||||
}
|
||||
|
||||
if (shouldDelayDraw(panel)) {
|
||||
// temp fix for legends on the side, need to render twice to get dimensions right
|
||||
callPlot();
|
||||
setTimeout(callPlot, 50);
|
||||
legendSideLastValue = panel.legend.rightSide;
|
||||
}
|
||||
@@ -299,7 +353,9 @@ function (angular, $, kbn, moment, _) {
|
||||
}
|
||||
|
||||
function configureAxisMode(axis, format) {
|
||||
axis.tickFormatter = kbn.getFormatFunction(format, 1);
|
||||
axis.tickFormatter = function(val, axis) {
|
||||
return kbn.valueFormats[format](val, axis.tickDecimals, axis.scaledDecimals);
|
||||
};
|
||||
}
|
||||
|
||||
function time_format(interval, ticks, min, max) {
|
||||
@@ -324,40 +380,6 @@ function (angular, $, kbn, moment, _) {
|
||||
return "%H:%M";
|
||||
}
|
||||
|
||||
var $tooltip = $('<div id="tooltip">');
|
||||
|
||||
elem.bind("plothover", function (event, pos, item) {
|
||||
var group, value, timestamp, seriesInfo, format;
|
||||
|
||||
if (item) {
|
||||
seriesInfo = item.series.info;
|
||||
format = scope.panel.y_formats[seriesInfo.yaxis - 1];
|
||||
|
||||
if (seriesInfo.alias) {
|
||||
group = '<small style="font-size:0.9em;">' +
|
||||
'<i class="icon-circle" style="color:'+item.series.color+';"></i>' + ' ' +
|
||||
seriesInfo.alias +
|
||||
'</small><br>';
|
||||
} else {
|
||||
group = kbn.query_color_dot(item.series.color, 15) + ' ';
|
||||
}
|
||||
|
||||
if (scope.panel.stack && scope.panel.tooltip.value_type === 'individual') {
|
||||
value = item.datapoint[1] - item.datapoint[2];
|
||||
}
|
||||
else {
|
||||
value = item.datapoint[1];
|
||||
}
|
||||
|
||||
value = kbn.getFormatFunction(format, 2)(value, item.series.yaxis);
|
||||
timestamp = dashboard.formatDate(item.datapoint[0]);
|
||||
|
||||
$tooltip.html(group + value + " @ " + timestamp).place_tt(pos.pageX, pos.pageY);
|
||||
} else {
|
||||
$tooltip.detach();
|
||||
}
|
||||
});
|
||||
|
||||
function render_panel_as_graphite_png(url) {
|
||||
url += '&width=' + elem.width();
|
||||
url += '&height=' + elem.css('height').replace('px', '');
|
||||
@@ -408,6 +430,10 @@ function (angular, $, kbn, moment, _) {
|
||||
elem.html('<img src="' + url + '"></img>');
|
||||
}
|
||||
|
||||
new GraphTooltip(elem, dashboard, scope, function() {
|
||||
return sortedSeries;
|
||||
});
|
||||
|
||||
elem.bind("plotselected", function (event, ranges) {
|
||||
scope.$apply(function() {
|
||||
timeSrv.setTime({
|
||||
200
src/app/panels/graph/graph.tooltip.js
Normal file
200
src/app/panels/graph/graph.tooltip.js
Normal file
@@ -0,0 +1,200 @@
|
||||
define([
|
||||
'jquery',
|
||||
],
|
||||
function ($) {
|
||||
'use strict';
|
||||
|
||||
function GraphTooltip(elem, dashboard, scope, getSeriesFn) {
|
||||
var self = this;
|
||||
|
||||
var $tooltip = $('<div id="tooltip">');
|
||||
|
||||
this.findHoverIndexFromDataPoints = function(posX, series,last) {
|
||||
var ps = series.datapoints.pointsize;
|
||||
var initial = last*ps;
|
||||
var len = series.datapoints.points.length;
|
||||
for (var j = initial; j < len; j += ps) {
|
||||
if (series.datapoints.points[j] > posX) {
|
||||
return Math.max(j - ps, 0)/ps;
|
||||
}
|
||||
}
|
||||
return j/ps - 1;
|
||||
};
|
||||
|
||||
this.findHoverIndexFromData = function(posX, series) {
|
||||
var len = series.data.length;
|
||||
for (var j = 0; j < len; j++) {
|
||||
if (series.data[j][0] > posX) {
|
||||
return Math.max(j - 1, 0);
|
||||
}
|
||||
}
|
||||
return j - 1;
|
||||
};
|
||||
|
||||
this.showTooltip = function(title, innerHtml, pos) {
|
||||
var body = '<div class="graph-tooltip small"><div class="graph-tooltip-time">'+ title + '</div> ' ;
|
||||
body += innerHtml + '</div>';
|
||||
$tooltip.html(body).place_tt(pos.pageX + 20, pos.pageY);
|
||||
};
|
||||
|
||||
this.getMultiSeriesPlotHoverInfo = function(seriesList, pos) {
|
||||
var value, i, series, hoverIndex, seriesTmp;
|
||||
var results = [];
|
||||
|
||||
var pointCount;
|
||||
for (i = 0; i < seriesList.length; i++) {
|
||||
seriesTmp = seriesList[i];
|
||||
if (!seriesTmp.data.length) { continue; }
|
||||
|
||||
if (!pointCount) {
|
||||
series = seriesTmp;
|
||||
pointCount = series.data.length;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (seriesTmp.data.length !== pointCount) {
|
||||
results.pointCountMismatch = true;
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
hoverIndex = this.findHoverIndexFromData(pos.x, series);
|
||||
var lasthoverIndex = 0;
|
||||
if(!scope.panel.steppedLine) {
|
||||
lasthoverIndex = hoverIndex;
|
||||
}
|
||||
|
||||
//now we know the current X (j) position for X and Y values
|
||||
results.time = series.data[hoverIndex][0];
|
||||
var last_value = 0; //needed for stacked values
|
||||
|
||||
for (i = 0; i < seriesList.length; i++) {
|
||||
series = seriesList[i];
|
||||
|
||||
if (!series.data.length) {
|
||||
results.push({ hidden: true });
|
||||
continue;
|
||||
}
|
||||
|
||||
if (scope.panel.stack) {
|
||||
if (scope.panel.tooltip.value_type === 'individual') {
|
||||
value = series.data[hoverIndex][1];
|
||||
} else {
|
||||
last_value += series.data[hoverIndex][1];
|
||||
value = last_value;
|
||||
}
|
||||
} else {
|
||||
value = series.data[hoverIndex][1];
|
||||
}
|
||||
|
||||
// Highlighting multiple Points depending on the plot type
|
||||
if (scope.panel.steppedLine || (scope.panel.stack && scope.panel.nullPointMode == "null")) {
|
||||
// stacked and steppedLine plots can have series with different length.
|
||||
// Stacked series can increase its length on each new stacked serie if null points found,
|
||||
// to speed the index search we begin always on the las found hoverIndex.
|
||||
var newhoverIndex = this.findHoverIndexFromDataPoints(pos.x, series,lasthoverIndex);
|
||||
// update lasthoverIndex depends also on the plot type.
|
||||
if(!scope.panel.steppedLine) {
|
||||
// on stacked graphs new will be always greater than last
|
||||
lasthoverIndex = newhoverIndex;
|
||||
} else {
|
||||
// if steppeLine, not always series increases its length, so we should begin
|
||||
// to search correct index from the original hoverIndex on each serie.
|
||||
lasthoverIndex = hoverIndex;
|
||||
}
|
||||
|
||||
results.push({ value: value, hoverIndex: newhoverIndex});
|
||||
} else {
|
||||
results.push({ value: value, hoverIndex: hoverIndex});
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
};
|
||||
|
||||
elem.mouseleave(function () {
|
||||
if (scope.panel.tooltip.shared || dashboard.sharedCrosshair) {
|
||||
var plot = elem.data().plot;
|
||||
if (plot) {
|
||||
$tooltip.detach();
|
||||
plot.unhighlight();
|
||||
scope.appEvent('clearCrosshair');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
elem.bind("plothover", function (event, pos, item) {
|
||||
var plot = elem.data().plot;
|
||||
var plotData = plot.getData();
|
||||
var seriesList = getSeriesFn();
|
||||
var group, value, timestamp, hoverInfo, i, series, seriesHtml;
|
||||
|
||||
if(dashboard.sharedCrosshair){
|
||||
scope.appEvent('setCrosshair', { pos: pos, scope: scope });
|
||||
}
|
||||
|
||||
if (seriesList.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (scope.panel.tooltip.shared) {
|
||||
plot.unhighlight();
|
||||
|
||||
var seriesHoverInfo = self.getMultiSeriesPlotHoverInfo(plotData, pos);
|
||||
if (seriesHoverInfo.pointCountMismatch) {
|
||||
self.showTooltip('Shared tooltip error', '<ul>' +
|
||||
'<li>Series point counts are not the same</li>' +
|
||||
'<li>Set null point mode to null or null as zero</li>' +
|
||||
'<li>For influxdb users set fill(0) in your query</li></ul>', pos);
|
||||
return;
|
||||
}
|
||||
|
||||
seriesHtml = '';
|
||||
timestamp = dashboard.formatDate(seriesHoverInfo.time);
|
||||
|
||||
for (i = 0; i < seriesHoverInfo.length; i++) {
|
||||
hoverInfo = seriesHoverInfo[i];
|
||||
|
||||
if (hoverInfo.hidden) {
|
||||
continue;
|
||||
}
|
||||
|
||||
series = seriesList[i];
|
||||
value = series.formatValue(hoverInfo.value);
|
||||
|
||||
seriesHtml += '<div class="graph-tooltip-list-item"><div class="graph-tooltip-series-name">';
|
||||
seriesHtml += '<i class="icon-minus" style="color:' + series.color +';"></i> ' + series.label + ':</div>';
|
||||
seriesHtml += '<div class="graph-tooltip-value">' + value + '</div></div>';
|
||||
plot.highlight(i, hoverInfo.hoverIndex);
|
||||
}
|
||||
|
||||
self.showTooltip(timestamp, seriesHtml, pos);
|
||||
}
|
||||
// single series tooltip
|
||||
else if (item) {
|
||||
series = seriesList[item.seriesIndex];
|
||||
group = '<div class="graph-tooltip-list-item"><div class="graph-tooltip-series-name">';
|
||||
group += '<i class="icon-minus" style="color:' + item.series.color +';"></i> ' + series.label + ':</div>';
|
||||
|
||||
if (scope.panel.stack && scope.panel.tooltip.value_type === 'individual') {
|
||||
value = item.datapoint[1] - item.datapoint[2];
|
||||
}
|
||||
else {
|
||||
value = item.datapoint[1];
|
||||
}
|
||||
|
||||
value = series.formatValue(value);
|
||||
timestamp = dashboard.formatDate(item.datapoint[0]);
|
||||
group += '<div class="graph-tooltip-value">' + value + '</div>';
|
||||
|
||||
self.showTooltip(timestamp, group, pos);
|
||||
}
|
||||
// no hit
|
||||
else {
|
||||
$tooltip.detach();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return GraphTooltip;
|
||||
});
|
||||
@@ -1,58 +0,0 @@
|
||||
<section class="graph-legend" ng-class="{'graph-legend-table': panel.legend.alignAsTable}">
|
||||
|
||||
<div class="graph-legend-series"
|
||||
ng-repeat='series in legend'
|
||||
ng-class="{'pull-right': series.yaxis === 2, 'graph-legend-series-hidden': hiddenSeries[series.alias]}"
|
||||
>
|
||||
<div class="graph-legend-icon">
|
||||
<i class='icon-minus pointer' ng-style="{color: series.color}" bs-popover="'colorPopup.html'" data-placement="bottom">
|
||||
</i>
|
||||
</div>
|
||||
<div class="graph-legend-alias small">
|
||||
<a ng-click="toggleSeries(series, $event)" data-unique="1" data-placement="{{series.yaxis === 2 ? 'bottomRight' : 'bottomLeft'}}">
|
||||
{{series.alias}}
|
||||
</a>
|
||||
</div>
|
||||
<div class="graph-legend-value current small" ng-show="panel.legend.values && panel.legend.current" ng-bind="series.current">
|
||||
</div>
|
||||
<div class="graph-legend-value min small" ng-show="panel.legend.values && panel.legend.min" ng-bind="series.min">
|
||||
</div>
|
||||
<div class="graph-legend-value max small" ng-show="panel.legend.values && panel.legend.max" ng-bind="series.max">
|
||||
</div>
|
||||
<div class="graph-legend-value total small" ng-show="panel.legend.values && panel.legend.total" ng-bind="series.total">
|
||||
</div>
|
||||
<div class="graph-legend-value avg small" ng-show="panel.legend.values && panel.legend.avg" ng-bind="series.avg">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
<script type="text/ng-template" id="colorPopup.html">
|
||||
<div class="graph-legend-popover">
|
||||
<a class="close" ng-click="dismiss();" href="">×</a>
|
||||
|
||||
<div class="editor-row small" style="padding-bottom: 0;">
|
||||
<label>Axis:</label>
|
||||
<button ng-click="toggleYAxis(series);dismiss();"
|
||||
class="btn btn-mini"
|
||||
ng-class="{'btn-success': series.yaxis === 1 }">
|
||||
Left
|
||||
</button>
|
||||
<button ng-click="toggleYAxis(series);dismiss();"
|
||||
class="btn btn-mini"
|
||||
ng-class="{'btn-success': series.yaxis === 2 }">
|
||||
Right
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="editor-row">
|
||||
<i ng-repeat="color in colors"
|
||||
class="pointer"
|
||||
ng-class="{'icon-circle-blank': color === series.color,'icon-circle': color !== series.color}"
|
||||
ng-style="{color:color}"
|
||||
ng-click="changeSeriesColor(series, color);dismiss();">
|
||||
</i>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
168
src/app/panels/graph/legend.js
Normal file
168
src/app/panels/graph/legend.js
Normal file
@@ -0,0 +1,168 @@
|
||||
define([
|
||||
'angular',
|
||||
'app',
|
||||
'lodash',
|
||||
'kbn',
|
||||
'jquery',
|
||||
'jquery.flot',
|
||||
'jquery.flot.time',
|
||||
],
|
||||
function (angular, app, _, kbn, $) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.panels.graph');
|
||||
|
||||
module.directive('graphLegend', function(popoverSrv) {
|
||||
|
||||
return {
|
||||
link: function(scope, elem) {
|
||||
var $container = $('<section class="graph-legend"></section>');
|
||||
var firstRender = true;
|
||||
var panel = scope.panel;
|
||||
var data;
|
||||
var seriesList;
|
||||
var i;
|
||||
|
||||
scope.$on('render', function() {
|
||||
data = scope.seriesList;
|
||||
if (data) {
|
||||
render();
|
||||
}
|
||||
});
|
||||
|
||||
function getSeriesIndexForElement(el) {
|
||||
return el.parents('[data-series-index]').data('series-index');
|
||||
}
|
||||
|
||||
function openColorSelector(e) {
|
||||
var el = $(e.currentTarget);
|
||||
var index = getSeriesIndexForElement(el);
|
||||
var seriesInfo = seriesList[index];
|
||||
var popoverScope = scope.$new();
|
||||
popoverScope.series = seriesInfo;
|
||||
popoverSrv.show({
|
||||
element: $(':first-child', el),
|
||||
templateUrl: 'app/panels/graph/legend.popover.html',
|
||||
scope: popoverScope
|
||||
});
|
||||
}
|
||||
|
||||
function toggleSeries(e) {
|
||||
var el = $(e.currentTarget);
|
||||
var index = getSeriesIndexForElement(el);
|
||||
var seriesInfo = seriesList[index];
|
||||
scope.toggleSeries(seriesInfo, e);
|
||||
}
|
||||
|
||||
function sortLegend(e) {
|
||||
var el = $(e.currentTarget);
|
||||
var stat = el.data('stat');
|
||||
|
||||
if (stat !== panel.legend.sort) { panel.legend.sortDesc = null; }
|
||||
|
||||
// if already sort ascending, disable sorting
|
||||
if (panel.legend.sortDesc === false) {
|
||||
panel.legend.sort = null;
|
||||
panel.legend.sortDesc = null;
|
||||
render();
|
||||
return;
|
||||
}
|
||||
|
||||
panel.legend.sortDesc = !panel.legend.sortDesc;
|
||||
panel.legend.sort = stat;
|
||||
render();
|
||||
}
|
||||
|
||||
function getTableHeaderHtml(statName) {
|
||||
if (!panel.legend[statName]) { return ""; }
|
||||
var html = '<th class="pointer" data-stat="' + statName + '">' + statName;
|
||||
|
||||
if (panel.legend.sort === statName) {
|
||||
var cssClass = panel.legend.sortDesc ? 'icon-caret-down' : 'icon-caret-up' ;
|
||||
html += ' <span class="' + cssClass + '"></span>';
|
||||
}
|
||||
|
||||
return html + '</th>';
|
||||
}
|
||||
|
||||
function render() {
|
||||
if (firstRender) {
|
||||
elem.append($container);
|
||||
$container.on('click', '.graph-legend-icon', openColorSelector);
|
||||
$container.on('click', '.graph-legend-alias', toggleSeries);
|
||||
$container.on('click', 'th', sortLegend);
|
||||
firstRender = false;
|
||||
}
|
||||
|
||||
seriesList = data;
|
||||
|
||||
$container.empty();
|
||||
|
||||
$container.toggleClass('graph-legend-table', panel.legend.alignAsTable === true);
|
||||
|
||||
if (panel.legend.alignAsTable) {
|
||||
var header = '<tr>';
|
||||
header += '<th colspan="2" style="text-align:left"></th>';
|
||||
if (panel.legend.values) {
|
||||
header += getTableHeaderHtml('min');
|
||||
header += getTableHeaderHtml('max');
|
||||
header += getTableHeaderHtml('avg');
|
||||
header += getTableHeaderHtml('current');
|
||||
header += getTableHeaderHtml('total');
|
||||
}
|
||||
header += '</tr>';
|
||||
$container.append($(header));
|
||||
}
|
||||
|
||||
if (panel.legend.sort) {
|
||||
seriesList = _.sortBy(seriesList, function(series) {
|
||||
return series.stats[panel.legend.sort];
|
||||
});
|
||||
if (panel.legend.sortDesc) {
|
||||
seriesList = seriesList.reverse();
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < seriesList.length; i++) {
|
||||
var series = seriesList[i];
|
||||
|
||||
// ignore empty series
|
||||
if (panel.legend.hideEmpty && series.allIsNull) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var html = '<div class="graph-legend-series';
|
||||
if (series.yaxis === 2) { html += ' pull-right'; }
|
||||
if (scope.hiddenSeries[series.alias]) { html += ' graph-legend-series-hidden'; }
|
||||
html += '" data-series-index="' + i + '">';
|
||||
html += '<div class="graph-legend-icon">';
|
||||
html += '<i class="icon-minus pointer" style="color:' + series.color + '"></i>';
|
||||
html += '</div>';
|
||||
|
||||
html += '<div class="graph-legend-alias">';
|
||||
html += '<a>' + series.label + '</a>';
|
||||
html += '</div>';
|
||||
|
||||
var avg = series.formatValue(series.stats.avg);
|
||||
var current = series.formatValue(series.stats.current);
|
||||
var min = series.formatValue(series.stats.min);
|
||||
var max = series.formatValue(series.stats.max);
|
||||
var total = series.formatValue(series.stats.total);
|
||||
|
||||
if (panel.legend.values) {
|
||||
if (panel.legend.min) { html += '<div class="graph-legend-value min">' + min + '</div>'; }
|
||||
if (panel.legend.max) { html += '<div class="graph-legend-value max">' + max + '</div>'; }
|
||||
if (panel.legend.avg) { html += '<div class="graph-legend-value avg">' + avg + '</div>'; }
|
||||
if (panel.legend.current) { html += '<div class="graph-legend-value current">' + current + '</div>'; }
|
||||
if (panel.legend.total) { html += '<div class="graph-legend-value total">' + total + '</div>'; }
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
$container.append($(html));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
});
|
||||
26
src/app/panels/graph/legend.popover.html
Normal file
26
src/app/panels/graph/legend.popover.html
Normal file
@@ -0,0 +1,26 @@
|
||||
<div class="graph-legend-popover">
|
||||
<a class="close" ng-click="dismiss();" href="">×</a>
|
||||
|
||||
<div class="editor-row small" style="padding-bottom: 0;">
|
||||
<label>Axis:</label>
|
||||
<button ng-click="toggleYAxis(series);dismiss();"
|
||||
class="btn btn-mini"
|
||||
ng-class="{'btn-success': series.yaxis === 1 }">
|
||||
Left
|
||||
</button>
|
||||
<button ng-click="toggleYAxis(series);dismiss();"
|
||||
class="btn btn-mini"
|
||||
ng-class="{'btn-success': series.yaxis === 2 }">
|
||||
Right
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="editor-row">
|
||||
<i ng-repeat="color in colors"
|
||||
class="pointer"
|
||||
ng-class="{'icon-circle-blank': color === series.color,'icon-circle': color !== series.color}"
|
||||
ng-style="{color:color}"
|
||||
ng-click="changeSeriesColor(series, color);dismiss();"> </i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,25 +1,24 @@
|
||||
<div ng-controller='GraphCtrl'>
|
||||
<div ng-controller='GraphCtrl'>
|
||||
|
||||
<div class="graph-wrapper" ng-class="{'graph-legend-rightside': panel.legend.rightSide}">
|
||||
<div class="graph-canvas-wrapper">
|
||||
<div class="graph-wrapper" ng-class="{'graph-legend-rightside': panel.legend.rightSide}">
|
||||
<div class="graph-canvas-wrapper">
|
||||
|
||||
<div ng-if="datapointsWarning" class="datapoints-warning">
|
||||
<span class="small" ng-show="!datapointsCount">No datapoints <tip>Can be caused by timezone mismatch between browser and graphite server</tip></span>
|
||||
<span class="small" ng-show="datapointsOutside">Datapoints outside time range <tip>Can be caused by timezone mismatch between browser and graphite server</tip></span>
|
||||
</div>
|
||||
<div ng-if="datapointsWarning" class="datapoints-warning">
|
||||
<span class="small" ng-show="!datapointsCount">
|
||||
No datapoints <tip>No datapoints returned from metric query</tip>
|
||||
</span>
|
||||
<span class="small" ng-show="datapointsOutside">Datapoints outside time range <tip>Can be caused by timezone mismatch between browser and graphite server</tip></span>
|
||||
</div>
|
||||
|
||||
<div grafana-graph class="pointer histogram-chart">
|
||||
</div>
|
||||
<div grafana-graph class="histogram-chart">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="graph-legend-wrapper"
|
||||
ng-if="panel.legend.show"
|
||||
ng-include="'app/panels/graph/legend.html'">
|
||||
</div>
|
||||
</div>
|
||||
<div class="graph-legend-wrapper" ng-if="panel.legend.show" graph-legend></div>
|
||||
</div>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
<div class="clearfix"></div>
|
||||
|
||||
<div style="margin-top: 30px" ng-if="editMode">
|
||||
<div class="dashboard-editor-header">
|
||||
@@ -29,13 +28,13 @@
|
||||
</div>
|
||||
|
||||
<div ng-model="editor.index" bs-tabs>
|
||||
<div ng-repeat="tab in editorTabs" data-title="{{tab}}">
|
||||
<div ng-repeat="tab in panelMeta.editorTabs" data-title="{{tab.title}}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-editor-body">
|
||||
<div ng-repeat="tab in panelMeta.fullEditorTabs" ng-if="editorTabs[editor.index] == tab.title">
|
||||
<div ng-repeat="tab in panelMeta.editorTabs" ng-if="editor.index === $index">
|
||||
<div ng-include src="tab.src"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,81 +6,46 @@ define([
|
||||
'kbn',
|
||||
'moment',
|
||||
'components/timeSeries',
|
||||
'./seriesOverridesCtrl',
|
||||
'components/panelmeta',
|
||||
'services/panelSrv',
|
||||
'services/annotationsSrv',
|
||||
'services/datasourceSrv',
|
||||
'jquery.flot',
|
||||
'jquery.flot.events',
|
||||
'jquery.flot.selection',
|
||||
'jquery.flot.time',
|
||||
'jquery.flot.stack',
|
||||
'jquery.flot.stackpercent'
|
||||
'./seriesOverridesCtrl',
|
||||
'./graph',
|
||||
'./legend',
|
||||
],
|
||||
function (angular, app, $, _, kbn, moment, TimeSeries) {
|
||||
function (angular, app, $, _, kbn, moment, TimeSeries, PanelMeta) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.panels.graph');
|
||||
app.useModule(module);
|
||||
|
||||
module.controller('GraphCtrl', function($scope, $rootScope, panelSrv, annotationsSrv, timeSrv) {
|
||||
|
||||
$scope.panelMeta = {
|
||||
modals : [],
|
||||
editorTabs: [],
|
||||
fullEditorTabs : [
|
||||
{
|
||||
title: 'General',
|
||||
src:'app/partials/panelgeneral.html'
|
||||
},
|
||||
{
|
||||
title: 'Metrics',
|
||||
src:'app/partials/metrics.html'
|
||||
},
|
||||
{
|
||||
title:'Axes & Grid',
|
||||
src:'app/panels/graph/axisEditor.html'
|
||||
},
|
||||
{
|
||||
title:'Display Styles',
|
||||
src:'app/panels/graph/styleEditor.html'
|
||||
}
|
||||
],
|
||||
fullscreenEdit: true,
|
||||
fullscreenView: true,
|
||||
description : "Graphing"
|
||||
};
|
||||
$scope.panelMeta = new PanelMeta({
|
||||
description: 'Graph panel',
|
||||
fullscreen: true,
|
||||
metricsEditor: true
|
||||
});
|
||||
|
||||
$scope.panelMeta.addEditorTab('Axes & Grid', 'app/panels/graph/axisEditor.html');
|
||||
$scope.panelMeta.addEditorTab('Display Styles', 'app/panels/graph/styleEditor.html');
|
||||
|
||||
$scope.panelMeta.addExtendedMenuItem('Export CSV', '', 'exportCsv()');
|
||||
$scope.panelMeta.addExtendedMenuItem('Toggle legend', '', 'toggleLegend()');
|
||||
|
||||
// Set and populate defaults
|
||||
var _d = {
|
||||
|
||||
// datasource name, null = default datasource
|
||||
datasource: null,
|
||||
|
||||
/** @scratch /panels/histogram/3
|
||||
* renderer:: sets client side (flot) or native graphite png renderer (png)
|
||||
*/
|
||||
// sets client side (flot) or native graphite png renderer (png)
|
||||
renderer: 'flot',
|
||||
/** @scratch /panels/histogram/3
|
||||
* x-axis:: Show the x-axis
|
||||
*/
|
||||
// Show/hide the x-axis
|
||||
'x-axis' : true,
|
||||
/** @scratch /panels/histogram/3
|
||||
* y-axis:: Show the y-axis
|
||||
*/
|
||||
// Show/hide y-axis
|
||||
'y-axis' : true,
|
||||
/** @scratch /panels/histogram/3
|
||||
* scale:: Scale the y-axis by this factor
|
||||
*/
|
||||
scale : 1,
|
||||
/** @scratch /panels/histogram/3
|
||||
* y_formats :: 'none','bytes','bits','bps','short', 's', 'ms'
|
||||
*/
|
||||
// y axis formats, [left axis,right axis]
|
||||
y_formats : ['short', 'short'],
|
||||
/** @scratch /panels/histogram/5
|
||||
* grid object:: Min and max y-axis values
|
||||
* grid.min::: Minimum y-axis value
|
||||
* grid.ma1::: Maximum y-axis value
|
||||
*/
|
||||
// grid options
|
||||
grid : {
|
||||
leftMax: null,
|
||||
rightMax: null,
|
||||
@@ -91,48 +56,23 @@ function (angular, app, $, _, kbn, moment, TimeSeries) {
|
||||
threshold1Color: 'rgba(216, 200, 27, 0.27)',
|
||||
threshold2Color: 'rgba(234, 112, 112, 0.22)'
|
||||
},
|
||||
|
||||
annotate : {
|
||||
enable : false,
|
||||
},
|
||||
|
||||
/** @scratch /panels/histogram/3
|
||||
* resolution:: If auto_int is true, shoot for this many bars.
|
||||
*/
|
||||
resolution : 100,
|
||||
|
||||
/** @scratch /panels/histogram/3
|
||||
* ==== Drawing options
|
||||
* lines:: Show line chart
|
||||
*/
|
||||
// show/hide lines
|
||||
lines : true,
|
||||
/** @scratch /panels/histogram/3
|
||||
* fill:: Area fill factor for line charts, 1-10
|
||||
*/
|
||||
// fill factor
|
||||
fill : 0,
|
||||
/** @scratch /panels/histogram/3
|
||||
* linewidth:: Weight of lines in pixels
|
||||
*/
|
||||
// line width in pixels
|
||||
linewidth : 1,
|
||||
/** @scratch /panels/histogram/3
|
||||
* points:: Show points on chart
|
||||
*/
|
||||
// show hide points
|
||||
points : false,
|
||||
/** @scratch /panels/histogram/3
|
||||
* pointradius:: Size of points in pixels
|
||||
*/
|
||||
// point radius in pixels
|
||||
pointradius : 5,
|
||||
/** @scratch /panels/histogram/3
|
||||
* bars:: Show bars on chart
|
||||
*/
|
||||
// show hide bars
|
||||
bars : false,
|
||||
/** @scratch /panels/histogram/3
|
||||
* stack:: Stack multiple series
|
||||
*/
|
||||
// enable/disable stacking
|
||||
stack : false,
|
||||
/** @scratch /panels/histogram/3
|
||||
* legend:: Display the legend
|
||||
*/
|
||||
// stack percentage mode
|
||||
percentage : false,
|
||||
// legend options
|
||||
legend: {
|
||||
show: true, // disable/enable legend
|
||||
values: false, // disable/enable legend values
|
||||
@@ -142,31 +82,20 @@ function (angular, app, $, _, kbn, moment, TimeSeries) {
|
||||
total: false,
|
||||
avg: false
|
||||
},
|
||||
/** @scratch /panels/histogram/3
|
||||
* ==== Transformations
|
||||
/** @scratch /panels/histogram/3
|
||||
* percentage:: Show the y-axis as a percentage of the axis total. Only makes sense for multiple
|
||||
* queries
|
||||
*/
|
||||
percentage : false,
|
||||
/** @scratch /panels/histogram/3
|
||||
* zerofill:: Improves the accuracy of line charts at a small performance cost.
|
||||
*/
|
||||
zerofill : true,
|
||||
|
||||
// how null points should be handled
|
||||
nullPointMode : 'connected',
|
||||
|
||||
// staircase line mode
|
||||
steppedLine: false,
|
||||
|
||||
// tooltip options
|
||||
tooltip : {
|
||||
value_type: 'cumulative',
|
||||
query_as_alias: true
|
||||
shared: false,
|
||||
},
|
||||
|
||||
// metric queries
|
||||
targets: [{}],
|
||||
|
||||
// series color overrides
|
||||
aliasColors: {},
|
||||
|
||||
// other style overrides
|
||||
seriesOverrides: [],
|
||||
};
|
||||
|
||||
@@ -177,11 +106,17 @@ function (angular, app, $, _, kbn, moment, TimeSeries) {
|
||||
_.defaults($scope.panel.legend, _d.legend);
|
||||
|
||||
$scope.hiddenSeries = {};
|
||||
$scope.seriesList = [];
|
||||
|
||||
$scope.updateTimeRange = function () {
|
||||
$scope.range = timeSrv.timeRange();
|
||||
$scope.rangeUnparsed = timeSrv.timeRange(false);
|
||||
$scope.resolution = Math.ceil($(window).width() * ($scope.panel.span / 12));
|
||||
if ($scope.panel.maxDataPoints) {
|
||||
$scope.resolution = $scope.panel.maxDataPoints;
|
||||
}
|
||||
else {
|
||||
$scope.resolution = Math.ceil($(window).width() * ($scope.panel.span / 12));
|
||||
}
|
||||
$scope.interval = kbn.calculateInterval($scope.range, $scope.resolution, $scope.panel.interval);
|
||||
};
|
||||
|
||||
@@ -205,16 +140,15 @@ function (angular, app, $, _, kbn, moment, TimeSeries) {
|
||||
$scope.panelMeta.loading = false;
|
||||
$scope.panelMeta.error = err.message || "Timeseries data request error";
|
||||
$scope.inspector.error = err;
|
||||
$scope.seriesList = [];
|
||||
$scope.render([]);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.dataHandler = function(results) {
|
||||
$scope.panelMeta.loading = false;
|
||||
$scope.legend = [];
|
||||
|
||||
// png renderer returns just a url
|
||||
if (_.isString(results)) {
|
||||
$scope.panelMeta.loading = false;
|
||||
$scope.render(results);
|
||||
return;
|
||||
}
|
||||
@@ -223,16 +157,18 @@ function (angular, app, $, _, kbn, moment, TimeSeries) {
|
||||
$scope.datapointsCount = 0;
|
||||
$scope.datapointsOutside = false;
|
||||
|
||||
var data = _.map(results.data, $scope.seriesHandler);
|
||||
$scope.seriesList = _.map(results.data, $scope.seriesHandler);
|
||||
|
||||
$scope.datapointsWarning = $scope.datapointsCount === 0 || $scope.datapointsOutside;
|
||||
|
||||
$scope.annotationsPromise
|
||||
.then(function(annotations) {
|
||||
data.annotations = annotations;
|
||||
$scope.render(data);
|
||||
$scope.panelMeta.loading = false;
|
||||
$scope.seriesList.annotations = annotations;
|
||||
$scope.render($scope.seriesList);
|
||||
}, function() {
|
||||
$scope.render(data);
|
||||
$scope.panelMeta.loading = false;
|
||||
$scope.render($scope.seriesList);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -241,20 +177,14 @@ function (angular, app, $, _, kbn, moment, TimeSeries) {
|
||||
var alias = seriesData.target;
|
||||
var color = $scope.panel.aliasColors[alias] || $rootScope.colors[index];
|
||||
|
||||
var seriesInfo = {
|
||||
alias: alias,
|
||||
color: color,
|
||||
};
|
||||
|
||||
$scope.legend.push(seriesInfo);
|
||||
|
||||
var series = new TimeSeries({
|
||||
datapoints: datapoints,
|
||||
info: seriesInfo,
|
||||
alias: alias,
|
||||
color: color,
|
||||
});
|
||||
|
||||
if (datapoints && datapoints.length > 0) {
|
||||
var last = moment.utc(datapoints[datapoints.length - 1][1] * 1000);
|
||||
var last = moment.utc(datapoints[datapoints.length - 1][1]);
|
||||
var from = moment.utc($scope.range.from);
|
||||
if (last - from < -10000) {
|
||||
$scope.datapointsOutside = true;
|
||||
@@ -267,7 +197,7 @@ function (angular, app, $, _, kbn, moment, TimeSeries) {
|
||||
};
|
||||
|
||||
$scope.render = function(data) {
|
||||
$scope.$emit('render', data);
|
||||
$scope.$broadcast('render', data);
|
||||
};
|
||||
|
||||
$scope.changeSeriesColor = function(series, color) {
|
||||
@@ -277,18 +207,18 @@ function (angular, app, $, _, kbn, moment, TimeSeries) {
|
||||
};
|
||||
|
||||
$scope.toggleSeries = function(serie, event) {
|
||||
if ($scope.hiddenSeries[serie.alias]) {
|
||||
delete $scope.hiddenSeries[serie.alias];
|
||||
}
|
||||
else {
|
||||
$scope.hiddenSeries[serie.alias] = true;
|
||||
}
|
||||
|
||||
if (event.ctrlKey || event.metaKey || event.shiftKey) {
|
||||
if ($scope.hiddenSeries[serie.alias]) {
|
||||
delete $scope.hiddenSeries[serie.alias];
|
||||
}
|
||||
else {
|
||||
$scope.hiddenSeries[serie.alias] = true;
|
||||
}
|
||||
} else {
|
||||
$scope.toggleSeriesExclusiveMode(serie);
|
||||
}
|
||||
|
||||
$scope.$emit('toggleLegend', $scope.legend);
|
||||
$scope.render();
|
||||
};
|
||||
|
||||
$scope.toggleSeriesExclusiveMode = function(serie) {
|
||||
@@ -299,7 +229,7 @@ function (angular, app, $, _, kbn, moment, TimeSeries) {
|
||||
}
|
||||
|
||||
// check if every other series is hidden
|
||||
var alreadyExclusive = _.every($scope.legend, function(value) {
|
||||
var alreadyExclusive = _.every($scope.seriesList, function(value) {
|
||||
if (value.alias === serie.alias) {
|
||||
return true;
|
||||
}
|
||||
@@ -309,13 +239,13 @@ function (angular, app, $, _, kbn, moment, TimeSeries) {
|
||||
|
||||
if (alreadyExclusive) {
|
||||
// remove all hidden series
|
||||
_.each($scope.legend, function(value) {
|
||||
_.each($scope.seriesList, function(value) {
|
||||
delete $scope.hiddenSeries[value.alias];
|
||||
});
|
||||
}
|
||||
else {
|
||||
// hide all but this serie
|
||||
_.each($scope.legend, function(value) {
|
||||
_.each($scope.seriesList, function(value) {
|
||||
if (value.alias === serie.alias) {
|
||||
return;
|
||||
}
|
||||
@@ -340,8 +270,8 @@ function (angular, app, $, _, kbn, moment, TimeSeries) {
|
||||
$scope.render();
|
||||
};
|
||||
|
||||
$scope.addSeriesOverride = function() {
|
||||
$scope.panel.seriesOverrides.push({});
|
||||
$scope.addSeriesOverride = function(override) {
|
||||
$scope.panel.seriesOverrides.push(override || {});
|
||||
};
|
||||
|
||||
$scope.removeSeriesOverride = function(override) {
|
||||
@@ -349,12 +279,14 @@ function (angular, app, $, _, kbn, moment, TimeSeries) {
|
||||
$scope.render();
|
||||
};
|
||||
|
||||
$scope.toggleEditorHelp = function(index) {
|
||||
if ($scope.editorHelpIndex === index) {
|
||||
$scope.editorHelpIndex = null;
|
||||
return;
|
||||
}
|
||||
$scope.editorHelpIndex = index;
|
||||
// Called from panel menu
|
||||
$scope.toggleLegend = function() {
|
||||
$scope.panel.legend.show = !$scope.panel.legend.show;
|
||||
$scope.get_data();
|
||||
};
|
||||
|
||||
$scope.exportCsv = function() {
|
||||
kbn.exportSeriesListToCsv($scope.seriesList);
|
||||
};
|
||||
|
||||
panelSrv.init($scope);
|
||||
|
||||
@@ -23,7 +23,7 @@ define([
|
||||
option.submenu = _.map(values, function(value, index) {
|
||||
return {
|
||||
text: String(value),
|
||||
click: 'setOverride(' + option.index + ',' + index + ')'
|
||||
click: 'menuItemSelected(' + option.index + ',' + index + ')'
|
||||
};
|
||||
});
|
||||
|
||||
@@ -34,6 +34,14 @@ define([
|
||||
var option = $scope.overrideMenu[optionIndex];
|
||||
var value = option.values[valueIndex];
|
||||
$scope.override[option.propertyName] = value;
|
||||
|
||||
// automatically disable lines for this series and the fill bellow to series
|
||||
// can be removed by the user if they still want lines
|
||||
if (option.propertyName === 'fillBelowTo') {
|
||||
$scope.override['lines'] = false;
|
||||
$scope.addSeriesOverride({ alias: value, lines: false });
|
||||
}
|
||||
|
||||
$scope.updateCurrentOverrides();
|
||||
$scope.render();
|
||||
};
|
||||
@@ -45,8 +53,8 @@ define([
|
||||
};
|
||||
|
||||
$scope.getSeriesNames = function() {
|
||||
return _.map($scope.legend, function(info) {
|
||||
return info.alias;
|
||||
return _.map($scope.seriesList, function(series) {
|
||||
return series.alias;
|
||||
});
|
||||
};
|
||||
|
||||
@@ -67,6 +75,7 @@ define([
|
||||
$scope.addOverrideOption('Lines', 'lines', [true, false]);
|
||||
$scope.addOverrideOption('Line fill', 'fill', [0,1,2,3,4,5,6,7,8,9,10]);
|
||||
$scope.addOverrideOption('Line width', 'linewidth', [0,1,2,3,4,5,6,7,8,9,10]);
|
||||
$scope.addOverrideOption('Fill below to', 'fillBelowTo', $scope.getSeriesNames());
|
||||
$scope.addOverrideOption('Staircase line', 'steppedLine', [true, false]);
|
||||
$scope.addOverrideOption('Points', 'points', [true, false]);
|
||||
$scope.addOverrideOption('Points Radius', 'pointradius', [1,2,3,4,5]);
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
<div class="editor-row">
|
||||
<div class="section">
|
||||
<h5>Chart Options</h5>
|
||||
<div class="editor-option">
|
||||
<label class="small">Bars</label><input type="checkbox" ng-model="panel.bars" ng-checked="panel.bars" ng-change="render()">
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Lines</label><input type="checkbox" ng-model="panel.lines" ng-checked="panel.lines" ng-change="render()">
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Points</label><input type="checkbox" ng-model="panel.points" ng-checked="panel.points" ng-change="render()">
|
||||
</div>
|
||||
<editor-opt-bool text="Bars" model="panel.bars" change="render()"></editor-opt-bool>
|
||||
<editor-opt-bool text="Lines" model="panel.lines" change="render()"></editor-opt-bool>
|
||||
<editor-opt-bool text="Points" model="panel.points" change="render()"></editor-opt-bool>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
@@ -30,24 +24,16 @@
|
||||
<label class="small">Null point mode<tip>Define how null values should be drawn</tip></label>
|
||||
<select class="input-medium" ng-model="panel.nullPointMode" ng-options="f for f in ['connected', 'null', 'null as zero']" ng-change="render()"></select>
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Staircase line</label><input type="checkbox" ng-model="panel.steppedLine" ng-checked="panel.steppedLine" ng-change="render()">
|
||||
</div>
|
||||
|
||||
<editor-opt-bool text="Staircase line" model="panel.steppedLine" change="render()"></editor-opt-bool>
|
||||
</div>
|
||||
<div class="section">
|
||||
<h5>Multiple Series</h5>
|
||||
<div class="editor-option">
|
||||
<label class="small">Stack</label><input type="checkbox" ng-model="panel.stack" ng-checked="panel.stack" ng-change="render()">
|
||||
</div>
|
||||
<div class="editor-option" ng-show="panel.stack">
|
||||
<label style="white-space:nowrap" class="small">Percent <tip>Stack as a percentage of total</tip></label>
|
||||
<input type="checkbox" ng-model="panel.percentage" ng-checked="panel.percentage" ng-change="render()">
|
||||
</div>
|
||||
<div class="editor-option" ng-show="panel.stack">
|
||||
<label class="small">Stacked Values <tip>How should the values in stacked charts to be calculated?</tip></label>
|
||||
<select class="input-small" ng-model="panel.tooltip.value_type" ng-options="f for f in ['cumulative','individual']" ng-change="render()"></select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<editor-opt-bool text="Stack" model="panel.stack" change="render()"></editor-opt-bool>
|
||||
<editor-opt-bool text="Percent" model="panel.percentage" change="render()" tip="Stack as a percentage of total"></editor-opt-bool>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="section">
|
||||
@@ -61,8 +47,21 @@
|
||||
<input type="radio" class="input-small" ng-model="panel.renderer" value="png" ng-change="get_data()" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h5>Tooltip</h5>
|
||||
<editor-opt-bool
|
||||
text="All series" model="panel.tooltip.shared" change="render()"
|
||||
tip="Show all series on same tooltip and a x croshair to help follow all series">
|
||||
</editor-opt-bool>
|
||||
<div class="editor-option" ng-show="panel.stack">
|
||||
<label class="small">Stacked Values <tip>How should the values in stacked charts to be calculated?</tip></label>
|
||||
<select class="input-small" ng-model="panel.tooltip.value_type" ng-options="f for f in ['cumulative','individual']" ng-change="render()"></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="editor-row">
|
||||
<div class="section">
|
||||
<h5>Series specific overrides <tip>Regex match example: /server[0-3]/i </tip></h5>
|
||||
@@ -89,11 +88,10 @@
|
||||
<i class="pointer icon-remove" ng-click="removeOverride(option)"></i>
|
||||
{{option.name}}: {{option.value}}
|
||||
</li>
|
||||
<li class="dropdown">
|
||||
<a class="dropdown-toggle grafana-target-segment" data-toggle="dropdown" gf-dropdown="overrideMenu" bs-tooltip="'set option to override'" data-placement="top">
|
||||
<i class="icon-plus"></i>
|
||||
</a>
|
||||
|
||||
<li class="dropdown" dropdown-typeahead="overrideMenu" dropdown-typeahead-on-select="setOverride($optionIndex, $valueIndex)">
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
115
src/app/panels/singlestat/editor.html
Normal file
115
src/app/panels/singlestat/editor.html
Normal file
@@ -0,0 +1,115 @@
|
||||
<div class="editor-row">
|
||||
<div class="section">
|
||||
<h5>Big value</h5>
|
||||
<div class="editor-option">
|
||||
<label class="small">Prefix</label>
|
||||
<input type="text" class="input-small" ng-model="panel.prefix" ng-blur="render()"></input>
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Value</label>
|
||||
<select class="input-small" ng-model="panel.valueName" ng-options="f for f in ['min','max','avg', 'current', 'total']" ng-change="render()"></select>
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Postfix</label>
|
||||
<input type="text" class="input-small" ng-model="panel.postfix" ng-blur="render()" ng-trim="false"></input>
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Null point mode<tip>Define how null values should handled, connected = ignored</tip></label>
|
||||
<select class="input-medium" ng-model="panel.nullPointMode" ng-options="f for f in ['connected', 'null', 'null as zero']" ng-change="get_data()"></select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h5>Big value font size</h5>
|
||||
<div class="editor-option">
|
||||
<label class="small">Prefix</label>
|
||||
<select class="input-mini" style="width: 75px;" ng-model="panel.prefixFontSize" ng-options="f for f in fontSizes" ng-change="render()"></select>
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Value</label>
|
||||
<select class="input-mini" style="width: 75px;" ng-model="panel.valueFontSize" ng-options="f for f in fontSizes" ng-change="render()"></select>
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Postfix</label>
|
||||
<select class="input-mini" style="width: 75px;" ng-model="panel.postfixFontSize" ng-options="f for f in fontSizes" ng-change="render()"></select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h5>Formats</h5>
|
||||
<div class="editor-option">
|
||||
<label class="small">Unit format</label>
|
||||
<select class="input-small" ng-model="panel.format" ng-options="f for f in ['none','short','bytes', 'bits', 'bps', 's', 'ms', 'µs', 'ns', 'percent']" ng-change="render()"></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="editor-row">
|
||||
<div class="section">
|
||||
<h5>Coloring</h5>
|
||||
<editor-opt-bool text="Background" model="panel.colorBackground" change="setColoring({background: true})"></editor-opt-bool>
|
||||
<editor-opt-bool text="Value" model="panel.colorValue" change="setColoring({value: true})"></editor-opt-bool>
|
||||
<div class="editor-option" ng-show="panel.colorBackground || panel.colorValue">
|
||||
<label class="small">Thresholds<tip>Comma seperated values</tip></label>
|
||||
<input type="text" class="input-large" ng-model="panel.thresholds" ng-blur="render()" placeholder="0,50,80"></input>
|
||||
</div>
|
||||
<div class="editor-option" ng-show="panel.colorBackground || panel.colorValue">
|
||||
<label class="small">Colors</label>
|
||||
<spectrum-picker ng-model="panel.colors[0]" ng-change="render()" ></spectrum-picker>
|
||||
<spectrum-picker ng-model="panel.colors[1]" ng-change="render()" ></spectrum-picker>
|
||||
<spectrum-picker ng-model="panel.colors[2]" ng-change="render()" ></spectrum-picker>
|
||||
<a class="pointer" ng-click="invertColorOrder()">invert order</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h5>Spark lines</h5>
|
||||
<editor-opt-bool text="Spark line" model="panel.sparkline.show" change="render()"></editor-opt-bool>
|
||||
<editor-opt-bool text="Background mode" model="panel.sparkline.full" change="render()"></editor-opt-bool>
|
||||
<div class="editor-option">
|
||||
<label class="small">Line color</label>
|
||||
<spectrum-picker ng-model="panel.sparkline.lineColor" ng-change="render()" ></spectrum-picker>
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Fill color</label>
|
||||
<spectrum-picker ng-model="panel.sparkline.fillColor" ng-change="render()" ></spectrum-picker>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="editor-row">
|
||||
<div class="section">
|
||||
<h5>Value to text mapping</h5>
|
||||
<div class="editor-option">
|
||||
<label class="small">Specify mappings</label>
|
||||
<div class="grafana-target">
|
||||
<div class="grafana-target-inner">
|
||||
<ul class="grafana-segment-list">
|
||||
<li class="grafana-target-segment" ng-repeat-start="map in panel.valueMaps">
|
||||
<i class="icon-remove pointer" ng-click="removeValueMap(map)"></i>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<input type="text" ng-model="map.value" placeholder="value" class="input-mini grafana-target-segment-input" ng-blur="render()">
|
||||
</li>
|
||||
<li class="grafana-target-segment">
|
||||
<i class="icon-arrow-right"></i>
|
||||
</li>
|
||||
<li ng-repeat-end>
|
||||
<input type="text" placeholder="text" ng-model="map.text" class="input-mini grafana-target-segment-input" ng-blur="render()">
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a class="pointer grafana-target-segment" ng-click="addValueMap();">
|
||||
<i class="icon-plus"></i>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
26
src/app/panels/singlestat/module.html
Normal file
26
src/app/panels/singlestat/module.html
Normal file
@@ -0,0 +1,26 @@
|
||||
<div ng-controller='SingleStatCtrl'>
|
||||
|
||||
<div class="singlestat-panel" singlestat-panel></div>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
|
||||
<div style="margin-top: 30px" ng-if="editMode">
|
||||
<div class="dashboard-editor-header">
|
||||
<div class="dashboard-editor-title">
|
||||
<i class="icon icon-dashboard"></i>
|
||||
Singlestat
|
||||
</div>
|
||||
|
||||
<div ng-model="editor.index" bs-tabs>
|
||||
<div ng-repeat="tab in panelMeta.editorTabs" data-title="{{tab.title}}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-editor-body">
|
||||
<div ng-repeat="tab in panelMeta.editorTabs" ng-if="editor.index === $index">
|
||||
<div ng-include src="tab.src"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
233
src/app/panels/singlestat/module.js
Normal file
233
src/app/panels/singlestat/module.js
Normal file
@@ -0,0 +1,233 @@
|
||||
define([
|
||||
'angular',
|
||||
'app',
|
||||
'lodash',
|
||||
'components/timeSeries',
|
||||
'kbn',
|
||||
'components/panelmeta',
|
||||
'services/panelSrv',
|
||||
'./singleStatPanel',
|
||||
],
|
||||
function (angular, app, _, TimeSeries, kbn, PanelMeta) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.panels.singlestat');
|
||||
app.useModule(module);
|
||||
|
||||
module.controller('SingleStatCtrl', function($scope, panelSrv, timeSrv) {
|
||||
|
||||
$scope.panelMeta = new PanelMeta({
|
||||
description: 'Singlestat panel',
|
||||
fullscreen: true,
|
||||
metricsEditor: true
|
||||
});
|
||||
|
||||
$scope.fontSizes = ['20%', '30%','50%','70%','80%','100%', '110%', '120%', '150%', '170%', '200%'];
|
||||
|
||||
$scope.panelMeta.addEditorTab('Options', 'app/panels/singlestat/editor.html');
|
||||
|
||||
// Set and populate defaults
|
||||
var _d = {
|
||||
links: [],
|
||||
maxDataPoints: 100,
|
||||
interval: null,
|
||||
targets: [{}],
|
||||
cacheTimeout: null,
|
||||
format: 'none',
|
||||
prefix: '',
|
||||
postfix: '',
|
||||
nullText: null,
|
||||
valueMaps: [
|
||||
{ value: 'null', op: '=', text: 'N/A' }
|
||||
],
|
||||
nullPointMode: 'connected',
|
||||
valueName: 'avg',
|
||||
prefixFontSize: '50%',
|
||||
valueFontSize: '80%',
|
||||
postfixFontSize: '50%',
|
||||
thresholds: '',
|
||||
colorBackground: false,
|
||||
colorValue: false,
|
||||
colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"],
|
||||
sparkline: {
|
||||
show: false,
|
||||
full: false,
|
||||
lineColor: 'rgb(31, 120, 193)',
|
||||
fillColor: 'rgba(31, 118, 189, 0.18)',
|
||||
}
|
||||
};
|
||||
|
||||
_.defaults($scope.panel, _d);
|
||||
|
||||
$scope.init = function() {
|
||||
panelSrv.init($scope);
|
||||
$scope.$on('refresh', $scope.get_data);
|
||||
};
|
||||
|
||||
$scope.updateTimeRange = function () {
|
||||
$scope.range = timeSrv.timeRange();
|
||||
$scope.rangeUnparsed = timeSrv.timeRange(false);
|
||||
$scope.resolution = $scope.panel.maxDataPoints;
|
||||
$scope.interval = kbn.calculateInterval($scope.range, $scope.resolution, $scope.panel.interval);
|
||||
};
|
||||
|
||||
$scope.get_data = function() {
|
||||
$scope.updateTimeRange();
|
||||
|
||||
var metricsQuery = {
|
||||
range: $scope.rangeUnparsed,
|
||||
interval: $scope.interval,
|
||||
targets: $scope.panel.targets,
|
||||
maxDataPoints: $scope.resolution,
|
||||
cacheTimeout: $scope.panel.cacheTimeout
|
||||
};
|
||||
|
||||
return $scope.datasource.query(metricsQuery)
|
||||
.then($scope.dataHandler)
|
||||
.then(null, function(err) {
|
||||
console.log("err");
|
||||
$scope.panelMeta.loading = false;
|
||||
$scope.panelMeta.error = err.message || "Timeseries data request error";
|
||||
$scope.inspector.error = err;
|
||||
$scope.render();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.dataHandler = function(results) {
|
||||
$scope.panelMeta.loading = false;
|
||||
$scope.series = _.map(results.data, $scope.seriesHandler);
|
||||
$scope.render();
|
||||
};
|
||||
|
||||
$scope.seriesHandler = function(seriesData) {
|
||||
var series = new TimeSeries({
|
||||
datapoints: seriesData.datapoints,
|
||||
alias: seriesData.target,
|
||||
});
|
||||
|
||||
series.flotpairs = series.getFlotPairs($scope.panel.nullPointMode);
|
||||
|
||||
return series;
|
||||
};
|
||||
|
||||
$scope.setColoring = function(options) {
|
||||
if (options.background) {
|
||||
$scope.panel.colorValue = false;
|
||||
$scope.panel.colors = ['rgba(71, 212, 59, 0.4)', 'rgba(245, 150, 40, 0.73)', 'rgba(225, 40, 40, 0.59)'];
|
||||
}
|
||||
else {
|
||||
$scope.panel.colorBackground = false;
|
||||
$scope.panel.colors = ['rgba(50, 172, 45, 0.97)', 'rgba(237, 129, 40, 0.89)', 'rgba(245, 54, 54, 0.9)'];
|
||||
}
|
||||
$scope.render();
|
||||
};
|
||||
|
||||
$scope.invertColorOrder = function() {
|
||||
var tmp = $scope.panel.colors[0];
|
||||
$scope.panel.colors[0] = $scope.panel.colors[2];
|
||||
$scope.panel.colors[2] = tmp;
|
||||
$scope.render();
|
||||
};
|
||||
|
||||
$scope.getDecimalsForValue = function(value) {
|
||||
|
||||
var delta = value / 2;
|
||||
var dec = -Math.floor(Math.log(delta) / Math.LN10);
|
||||
|
||||
var magn = Math.pow(10, -dec),
|
||||
norm = delta / magn, // norm is between 1.0 and 10.0
|
||||
size;
|
||||
|
||||
if (norm < 1.5) {
|
||||
size = 1;
|
||||
} else if (norm < 3) {
|
||||
size = 2;
|
||||
// special case for 2.5, requires an extra decimal
|
||||
if (norm > 2.25) {
|
||||
size = 2.5;
|
||||
++dec;
|
||||
}
|
||||
} else if (norm < 7.5) {
|
||||
size = 5;
|
||||
} else {
|
||||
size = 10;
|
||||
}
|
||||
|
||||
size *= magn;
|
||||
|
||||
// reduce starting decimals if not needed
|
||||
if (Math.floor(value) === value) { dec = 0; }
|
||||
|
||||
var result = {};
|
||||
result.decimals = Math.max(0, dec);
|
||||
result.scaledDecimals = result.decimals - Math.floor(Math.log(size) / Math.LN10) + 2;
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
$scope.render = function() {
|
||||
var data = {};
|
||||
|
||||
if (!$scope.series || $scope.series.length === 0) {
|
||||
data.flotpairs = [];
|
||||
data.mainValue = Number.NaN;
|
||||
data.mainValueFormated = $scope.getFormatedValue(null);
|
||||
}
|
||||
else {
|
||||
var series = $scope.series[0];
|
||||
data.mainValue = series.stats[$scope.panel.valueName];
|
||||
data.mainValueFormated = $scope.getFormatedValue(data.mainValue);
|
||||
data.flotpairs = series.flotpairs;
|
||||
}
|
||||
|
||||
data.thresholds = $scope.panel.thresholds.split(',').map(function(strVale) {
|
||||
return Number(strVale.trim());
|
||||
});
|
||||
|
||||
data.colorMap = $scope.panel.colors;
|
||||
|
||||
$scope.data = data;
|
||||
$scope.$emit('render');
|
||||
};
|
||||
|
||||
$scope.getFormatedValue = function(mainValue) {
|
||||
|
||||
// first check value to text mappings
|
||||
for(var i = 0; i < $scope.panel.valueMaps.length; i++) {
|
||||
var map = $scope.panel.valueMaps[i];
|
||||
// special null case
|
||||
if (map.value === 'null') {
|
||||
if (mainValue === null || mainValue === void 0) {
|
||||
return map.text;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// value/number to text mapping
|
||||
var value = parseFloat(map.value);
|
||||
if (value === mainValue) {
|
||||
return map.text;
|
||||
}
|
||||
}
|
||||
|
||||
if (mainValue === null || mainValue === void 0) {
|
||||
return "no value";
|
||||
}
|
||||
|
||||
var decimalInfo = $scope.getDecimalsForValue(mainValue);
|
||||
var formatFunc = kbn.valueFormats[$scope.panel.format];
|
||||
return formatFunc(mainValue, decimalInfo.decimals, decimalInfo.scaledDecimals);
|
||||
};
|
||||
|
||||
$scope.removeValueMap = function(map) {
|
||||
var index = _.indexOf($scope.panel.valueMaps, map);
|
||||
$scope.panel.valueMaps.splice(index, 1);
|
||||
$scope.render();
|
||||
};
|
||||
|
||||
$scope.addValueMap = function() {
|
||||
$scope.panel.valueMaps.push({value: '', op: '=', text: '' });
|
||||
};
|
||||
|
||||
$scope.init();
|
||||
});
|
||||
});
|
||||
211
src/app/panels/singlestat/singleStatPanel.js
Normal file
211
src/app/panels/singlestat/singleStatPanel.js
Normal file
@@ -0,0 +1,211 @@
|
||||
define([
|
||||
'angular',
|
||||
'app',
|
||||
'lodash',
|
||||
'jquery',
|
||||
'jquery.flot',
|
||||
],
|
||||
function (angular, app, _, $) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.panels.singlestat', []);
|
||||
app.useModule(module);
|
||||
|
||||
module.directive('singlestatPanel', function($location, linkSrv, $timeout) {
|
||||
|
||||
return {
|
||||
link: function(scope, elem) {
|
||||
var data, panel;
|
||||
var $panelContainer = elem.parents('.panel-container');
|
||||
|
||||
scope.$on('render', function() {
|
||||
render();
|
||||
});
|
||||
|
||||
function setElementHeight() {
|
||||
try {
|
||||
var height = scope.height || panel.height || scope.row.height;
|
||||
if (_.isString(height)) {
|
||||
height = parseInt(height.replace('px', ''), 10);
|
||||
}
|
||||
|
||||
height -= 5; // padding
|
||||
height -= panel.title ? 24 : 9; // subtract panel title bar
|
||||
|
||||
elem.css('height', height + 'px');
|
||||
|
||||
return true;
|
||||
} catch(e) { // IE throws errors sometimes
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function applyColoringThresholds(value, valueString) {
|
||||
if (!panel.colorValue) {
|
||||
return valueString;
|
||||
}
|
||||
|
||||
var color = getColorForValue(value);
|
||||
if (color) {
|
||||
return '<span style="color:' + color + '">'+ valueString + '</span>';
|
||||
}
|
||||
|
||||
return valueString;
|
||||
}
|
||||
|
||||
function getColorForValue(value) {
|
||||
for (var i = data.thresholds.length - 1; i >= 0 ; i--) {
|
||||
if (value >= data.thresholds[i]) {
|
||||
return data.colorMap[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getSpan(className, fontSize, value) {
|
||||
return '<span class="' + className + '" style="font-size:' + fontSize + '">' +
|
||||
value + '</span>';
|
||||
}
|
||||
|
||||
function getBigValueHtml() {
|
||||
var body = '<div class="singlestat-panel-value-container">';
|
||||
|
||||
if (panel.prefix) { body += getSpan('singlestat-panel-prefix', panel.prefixFontSize, scope.panel.prefix); }
|
||||
|
||||
var value = applyColoringThresholds(data.mainValue, data.mainValueFormated);
|
||||
body += getSpan('singlestat-panel-value', panel.valueFontSize, value);
|
||||
|
||||
if (panel.postfix) { body += getSpan('singlestat-panel-postfix', panel.postfixFontSize, panel.postfix); }
|
||||
|
||||
body += '</div>';
|
||||
|
||||
return body;
|
||||
}
|
||||
|
||||
function addSparkline() {
|
||||
var panel = scope.panel;
|
||||
var width = elem.width() + 20;
|
||||
var height = elem.height() || 100;
|
||||
|
||||
var plotCanvas = $('<div></div>');
|
||||
var plotCss = {};
|
||||
plotCss.position = 'absolute';
|
||||
|
||||
if (panel.sparkline.full) {
|
||||
plotCss.bottom = '5px';
|
||||
plotCss.left = '-5px';
|
||||
plotCss.width = (width - 10) + 'px';
|
||||
plotCss.height = (height - 45) + 'px';
|
||||
}
|
||||
else {
|
||||
plotCss.bottom = "0px";
|
||||
plotCss.left = "-5px";
|
||||
plotCss.width = (width - 10) + 'px';
|
||||
plotCss.height = Math.floor(height * 0.25) + "px";
|
||||
}
|
||||
|
||||
plotCanvas.css(plotCss);
|
||||
|
||||
var options = {
|
||||
legend: { show: false },
|
||||
series: {
|
||||
lines: {
|
||||
show: true,
|
||||
fill: 1,
|
||||
lineWidth: 1,
|
||||
fillColor: panel.sparkline.fillColor,
|
||||
},
|
||||
},
|
||||
yaxes: { show: false },
|
||||
xaxis: {
|
||||
show: false,
|
||||
mode: "time",
|
||||
min: scope.range.from.getTime(),
|
||||
max: scope.range.to.getTime(),
|
||||
},
|
||||
grid: { hoverable: false, show: false },
|
||||
};
|
||||
|
||||
elem.append(plotCanvas);
|
||||
|
||||
var plotSeries = {
|
||||
data: data.flotpairs,
|
||||
color: panel.sparkline.lineColor
|
||||
};
|
||||
|
||||
setTimeout(function() {
|
||||
$.plot(plotCanvas, [plotSeries], options);
|
||||
}, 10);
|
||||
}
|
||||
|
||||
function render() {
|
||||
if (!scope.data) { return; }
|
||||
|
||||
data = scope.data;
|
||||
panel = scope.panel;
|
||||
|
||||
setElementHeight();
|
||||
|
||||
var body = getBigValueHtml();
|
||||
|
||||
if (panel.colorBackground && !isNaN(data.mainValue)) {
|
||||
var color = getColorForValue(data.mainValue);
|
||||
if (color) {
|
||||
$panelContainer.css('background-color', color);
|
||||
if (scope.fullscreen) {
|
||||
elem.css('background-color', color);
|
||||
} else {
|
||||
elem.css('background-color', '');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$panelContainer.css('background-color', '');
|
||||
elem.css('background-color', '');
|
||||
}
|
||||
|
||||
elem.html(body);
|
||||
|
||||
if (panel.sparkline.show) {
|
||||
addSparkline();
|
||||
}
|
||||
|
||||
elem.toggleClass('pointer', panel.links.length > 0);
|
||||
}
|
||||
|
||||
// drilldown link tooltip
|
||||
var drilldownTooltip = $('<div id="tooltip" class="">gello</div>"');
|
||||
|
||||
elem.mouseleave(function() {
|
||||
if (panel.links.length === 0) { return;}
|
||||
drilldownTooltip.detach();
|
||||
});
|
||||
|
||||
elem.click(function() {
|
||||
if (panel.links.length === 0) { return; }
|
||||
|
||||
var linkInfo = linkSrv.getPanelLinkAnchorInfo(panel.links[0]);
|
||||
if (linkInfo.href[0] === '#') { linkInfo.href = linkInfo.href.substring(1); }
|
||||
|
||||
if (linkInfo.href.indexOf('http') === 0) {
|
||||
window.location.href = linkInfo.href;
|
||||
} else {
|
||||
$timeout(function() {
|
||||
$location.url(linkInfo.href);
|
||||
});
|
||||
}
|
||||
|
||||
drilldownTooltip.detach();
|
||||
});
|
||||
|
||||
elem.mousemove(function(e) {
|
||||
if (panel.links.length === 0) { return;}
|
||||
|
||||
drilldownTooltip.text('click to go to: ' + panel.links[0].title);
|
||||
|
||||
drilldownTooltip.place_tt(e.pageX+20, e.pageY-15);
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
});
|
||||
@@ -3,8 +3,9 @@ define([
|
||||
'app',
|
||||
'lodash',
|
||||
'require',
|
||||
'components/panelmeta',
|
||||
],
|
||||
function (angular, app, _, require) {
|
||||
function (angular, app, _, require, PanelMeta) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.panels.text', []);
|
||||
@@ -14,12 +15,15 @@ function (angular, app, _, require) {
|
||||
|
||||
module.controller('text', function($scope, templateSrv, $sce, panelSrv) {
|
||||
|
||||
$scope.panelMeta = {
|
||||
$scope.panelMeta = new PanelMeta({
|
||||
description : "A static text panel that can use plain text, markdown, or (sanitized) HTML"
|
||||
};
|
||||
});
|
||||
|
||||
$scope.panelMeta.addEditorTab('Edit text', 'app/panels/text/editor.html');
|
||||
|
||||
// Set and populate defaults
|
||||
var _d = {
|
||||
title : 'default title',
|
||||
mode : "markdown", // 'html', 'markdown', 'text'
|
||||
content : "",
|
||||
style: {},
|
||||
@@ -28,7 +32,7 @@ function (angular, app, _, require) {
|
||||
_.defaults($scope.panel, _d);
|
||||
|
||||
$scope.init = function() {
|
||||
panelSrv.init(this);
|
||||
panelSrv.init($scope);
|
||||
$scope.ready = false;
|
||||
$scope.$on('refresh', $scope.render);
|
||||
$scope.render();
|
||||
|
||||
@@ -9,22 +9,27 @@
|
||||
border: 0px !important;
|
||||
}
|
||||
</style>
|
||||
<!-- This is a complete hack. The form actually exists in the modal, but due to transclusion
|
||||
$scope.input isn't available on the controller unless the form element is in this file -->
|
||||
<form name="input" style="margin:3px 0 0 0">
|
||||
<ul class="nav nav-pills timepicker-dropdown">
|
||||
<li class="dropdown">
|
||||
<form name="input" style="margin:0">
|
||||
<ul class="nav timepicker-dropdown">
|
||||
|
||||
<a class="dropdown-toggle timepicker-dropdown" data-toggle="dropdown" href="" bs-tooltip="time.tooltip" data-placement="bottom" ng-click="dismiss();">
|
||||
<span ng-bind="time.rangeString"></span>
|
||||
<span ng-show="dashboard.refresh" class="text-warning">refreshed every {{dashboard.refresh}} </span>
|
||||
<i class="icon-caret-down"></i>
|
||||
</a>
|
||||
<li class="grafana-menu-zoom-out">
|
||||
<a class='small' ng-click='zoom(2)'>
|
||||
Zoom Out
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<ul class="dropdown-menu">
|
||||
<!-- Relative time options -->
|
||||
<li bindonce ng-repeat='timespan in panel.time_options track by $index'>
|
||||
<a ng-click="setRelativeFilter(timespan)" bo-text="'Last ' + timespan"></a>
|
||||
<li class="dropdown">
|
||||
|
||||
<a class="dropdown-toggle timepicker-dropdown" data-toggle="dropdown" href="" bs-tooltip="time.tooltip" data-placement="bottom" ng-click="dismiss();">
|
||||
<span ng-bind="time.rangeString"></span>
|
||||
<span ng-show="dashboard.refresh" class="text-warning">refreshed every {{dashboard.refresh}} </span>
|
||||
<i class="icon-caret-down"></i>
|
||||
</a>
|
||||
|
||||
<ul class="dropdown-menu">
|
||||
<!-- Relative time options -->
|
||||
<li bindonce ng-repeat='timespan in panel.time_options track by $index'>
|
||||
<a ng-click="setRelativeFilter(timespan)" bo-text="'Last ' + timespan"></a>
|
||||
</li>
|
||||
|
||||
<!-- Auto refresh submenu -->
|
||||
@@ -47,6 +52,5 @@
|
||||
<a ng-click="timeSrv.refreshDashboard()"><i class="icon-refresh"></i></a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -75,11 +75,11 @@ function (angular, app, _, moment, kbn) {
|
||||
|
||||
// Date picker needs the date to be at the start of the day
|
||||
if(new Date().getTimezoneOffset() < 0) {
|
||||
$scope.temptime.from.date = moment($scope.temptime.from.date).add('days',1).toDate();
|
||||
$scope.temptime.to.date = moment($scope.temptime.to.date).add('days',1).toDate();
|
||||
$scope.temptime.from.date = moment($scope.temptime.from.date).add(1, 'days').toDate();
|
||||
$scope.temptime.to.date = moment($scope.temptime.to.date).add(1, 'days').toDate();
|
||||
}
|
||||
|
||||
$scope.emitAppEvent('show-dash-editor', {src: 'app/panels/timepicker/custom.html', scope: $scope });
|
||||
$scope.appEvent('show-dash-editor', {src: 'app/panels/timepicker/custom.html', scope: $scope });
|
||||
};
|
||||
|
||||
// Constantly validate the input of the fields. This function does not change any date variables
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<div class="dashboard-editor-body">
|
||||
<div class="editor-row row" ng-if="editor.index == 0">
|
||||
<div class="span6">
|
||||
<div ng-if="variables.length === 0">
|
||||
<div ng-if="annotations.length === 0">
|
||||
<em>No annotations defined</em>
|
||||
</div>
|
||||
<table class="grafana-options-table">
|
||||
@@ -61,10 +61,7 @@
|
||||
<label class="small">Icon size</label>
|
||||
<select class="input-mini" ng-model="currentAnnotation.iconSize" ng-options="f for f in [7,8,9,10,13,15,17,20,25,30]"></select>
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Grid line</label>
|
||||
<input type="checkbox" ng-model="currentAnnotation.showLine" ng-checked="currentAnnotation.showLine">
|
||||
</div>
|
||||
<editor-opt-bool text="Grid line" model="currentAnnotation.showLine"></editor-opt-bool>
|
||||
<div class="editor-option">
|
||||
<label class="small">Line color</label>
|
||||
<spectrum-picker ng-model="currentAnnotation.lineColor"></spectrum-picker>
|
||||
|
||||
23
src/app/partials/confirm_modal.html
Normal file
23
src/app/partials/confirm_modal.html
Normal file
@@ -0,0 +1,23 @@
|
||||
<div class="modal-body">
|
||||
<div class="dashboard-editor-header">
|
||||
<div class="dashboard-editor-title">
|
||||
<i class="icon icon-ok"></i>
|
||||
{{title}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-editor-body">
|
||||
<p class="row-fluid text-center large">
|
||||
{{text}}
|
||||
<br>
|
||||
<br>
|
||||
</p>
|
||||
<div class="row-fluid">
|
||||
<span class="span4"></span>
|
||||
<button type="button" class="btn btn-success span2" ng-click="dismiss()">No</button>
|
||||
<button type="button" class="btn btn-danger span2" ng-click="onConfirm();dismiss();">Yes</button>
|
||||
<span class="span4"></span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -14,14 +14,14 @@
|
||||
<div class="main-view-container">
|
||||
<div class="grafana-row" ng-controller="RowCtrl" ng-repeat="(row_name, row) in dashboard.rows" row-height>
|
||||
<div class="row-control">
|
||||
<div class="row-control-inner" style="padding:0px;margin:0px;position:relative;">
|
||||
<div class="row-control-inner">
|
||||
<div class="row-close" ng-show="row.collapse" data-placement="bottom" >
|
||||
<div class="row-close-buttons">
|
||||
<span class="row-button bgPrimary" ng-click="toggle_row(row)">
|
||||
<i bs-tooltip="'Expand row'" data-placement="right" class="icon-caret-left pointer" ></i>
|
||||
</span>
|
||||
</div>
|
||||
<span class="row-text pointer" ng-click="toggle_row(row)" ng-bind="row.title"></span>
|
||||
<div class="row-text pointer" ng-click="toggle_row(row)" ng-bind="row.title"></div>
|
||||
</div>
|
||||
<div class="row-open" ng-show="!row.collapse">
|
||||
<div class='row-tab bgSuccess dropdown' ng-show="row.editable">
|
||||
@@ -74,24 +74,27 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="padding-top:0px" ng-if="!row.collapse">
|
||||
<div class="panels-wrapper" ng-if="!row.collapse">
|
||||
<div class="row-text pointer" ng-click="toggle_row(row)" ng-if="row.showTitle" ng-bind="row.title">
|
||||
</div>
|
||||
|
||||
<!-- Panels -->
|
||||
<!-- Panels, draggable needs to be disabled in fullscreen because Firefox bug -->
|
||||
<div ng-repeat="(name, panel) in row.panels"
|
||||
class="panel nospace"
|
||||
style="position:relative"
|
||||
data-drop="true"
|
||||
panel-width
|
||||
ng-model="panel"
|
||||
data-jqyoui-options
|
||||
jqyoui-droppable="{index:$index,mutate:false,onDrop:'panelMoveDrop',onOver:'panelMoveOver(true)',onOut:'panelMoveOut'}"
|
||||
ng-class="{'dragInProgress':dashboard.$$panelDragging}">
|
||||
class="panel"
|
||||
ui-draggable="{{!dashboardViewState.fullscreen}}" drag="panel.id"
|
||||
ui-on-Drop="onDrop($data, row, panel)"
|
||||
drag-handle-class="drag-handle" panel-width ng-model="panel">
|
||||
<grafana-panel type="panel.type" ng-cloak></grafana-panel>
|
||||
</div>
|
||||
|
||||
<div panel-drop-zone class="panel dragInProgress" style="margin:5px;width:30%;background:rgba(100,100,100,0.50)" ng-style="{height:row.height}" data-drop="true" ng-model="row.panels" data-jqyoui-options jqyoui-droppable="{index:row.panels.length,mutate:false,onDrop:'panelMoveDrop',onOver:'panelMoveOver',onOut:'panelMoveOut'}">
|
||||
<div panel-drop-zone class="panel panel-drop-zone"
|
||||
ui-on-drop="onDrop($data, row)"
|
||||
data-drop="true">
|
||||
<div class="panel-container" style="background: transparent">
|
||||
<div style="text-align: center">
|
||||
<em>Drop here</em>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
|
||||
@@ -13,12 +13,6 @@
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="grafana-menu-zoom-out">
|
||||
<a class='small' ng-click='zoom(2)'>
|
||||
Zoom Out
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li ng-repeat="pulldown in dashboard.nav" ng-controller="PulldownCtrl" ng-show="pulldown.enable">
|
||||
<grafana-simple-panel type="pulldown.type" ng-cloak>
|
||||
</grafana-simple-panel>
|
||||
|
||||
@@ -28,10 +28,7 @@
|
||||
<label class="small">Time correction</label>
|
||||
<select ng-model="dashboard.timezone" class='input-small' ng-options="f for f in ['browser','utc']"></select>
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Hide controls (CTRL+H)</label>
|
||||
<input type="checkbox" ng-model="dashboard.hideControls" ng-checked="dashboard.hideControls">
|
||||
</div>
|
||||
<editor-opt-bool text="Hide controls (CTRL+H)" model="dashboard.hideControls"></editor-opt-bool>
|
||||
</div>
|
||||
</div>
|
||||
<div class="editor-row">
|
||||
@@ -42,7 +39,6 @@
|
||||
</bootstrap-tagsinput>
|
||||
<tip>Press enter to a add tag</tip>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -71,17 +67,14 @@
|
||||
<div ng-if="editor.index == 2">
|
||||
<div class="editor-row">
|
||||
<div class="section">
|
||||
<div class="editor-option">
|
||||
<label class="small">Templating</label>
|
||||
<input type="checkbox" ng-model="dashboard.templating.enable" ng-checked="dashboard.templating.enable" ng-change="checkFeatureToggles()"x >
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Annotations</label>
|
||||
<input type="checkbox" ng-model="dashboard.annotations.enable" ng-checked="dashboard.annotations.enable" ng-change="checkFeatureToggles()">
|
||||
</div>
|
||||
<div class="editor-option" ng-repeat="pulldown in dashboard.nav">
|
||||
<label class="small" style="text-transform:capitalize;">{{pulldown.type}}</label><input type="checkbox" ng-model="pulldown.enable" ng-checked="pulldown.enable">
|
||||
<editor-opt-bool text="Templating" model="dashboard.templating.enable" change="checkFeatureToggles()"></editor-opt-bool>
|
||||
<editor-opt-bool text="Annotations" model="dashboard.annotations.enable" change="checkFeatureToggles()"></editor-opt-bool>
|
||||
<div class="editor-option text-center" ng-repeat="pulldown in dashboard.nav">
|
||||
<label class="small" style="text-transform:capitalize;">{{pulldown.type}}</label>
|
||||
<input class="cr1" id="pulldown{{pulldown.type}}" type="checkbox" ng-model="pulldown.enable" ng-checked="pulldown.enable">
|
||||
<label for="pulldown{{pulldown.type}}" class="cr1"></label>
|
||||
</div>
|
||||
<editor-opt-bool text="Shared Crosshair (CTRL+O)" model="dashboard.sharedCrosshair"></editor-opt-bool>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -91,7 +84,7 @@
|
||||
</div>
|
||||
|
||||
<div ng-repeat="pulldown in dashboard.nav" ng-controller="SubmenuCtrl" ng-show="editor.index == 4+$index">
|
||||
<ng-include ng-show="pulldown.enable" src="edit_path(pulldown.type)"></ng-include>
|
||||
<ng-include ng-show="pulldown.enable" src="pulldownEditorPath(pulldown.type)"></ng-include>
|
||||
<button ng-hide="pulldown.enable" class="btn" ng-click="pulldown.enable = true">Enable the {{pulldown.type}}</button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -30,6 +30,19 @@
|
||||
ng-click="duplicate()">
|
||||
Duplicate
|
||||
</a>
|
||||
</li>
|
||||
<li role="menuitem">
|
||||
<a tabindex="1"
|
||||
ng-click="moveMetricQuery($index, $index-1)">
|
||||
Move up
|
||||
</a>
|
||||
</li>
|
||||
<li role="menuitem">
|
||||
<a tabindex="1"
|
||||
ng-click="moveMetricQuery($index, $index+1)">
|
||||
Move down
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
@@ -83,16 +96,29 @@
|
||||
<i class="icon-wrench"></i>
|
||||
</li>
|
||||
<li class="grafana-target-segment">
|
||||
cacheTimeout
|
||||
Cache timeout
|
||||
</li>
|
||||
<li>
|
||||
<input type="text"
|
||||
class="input-mini grafana-target-segment-input"
|
||||
ng-model="panel.cacheTimeout"
|
||||
bs-tooltip="'Graphite parameter to overwride memcache default timeout (unit is seconds)'"
|
||||
data-placement="right"
|
||||
spellcheck='false'
|
||||
placeholder="60">
|
||||
class="input-mini grafana-target-segment-input"
|
||||
ng-model="panel.cacheTimeout"
|
||||
bs-tooltip="'Graphite parameter to override memcache default timeout (unit is seconds)'"
|
||||
data-placement="right"
|
||||
spellcheck='false'
|
||||
placeholder="60">
|
||||
</li>
|
||||
<li class="grafana-target-segment">
|
||||
Max data points
|
||||
</li>
|
||||
<li>
|
||||
<input type="text"
|
||||
class="input-mini grafana-target-segment-input"
|
||||
ng-model="panel.maxDataPoints"
|
||||
bs-tooltip="'Override max data points, automatically set to graph width in pixels.'"
|
||||
data-placement="right"
|
||||
ng-model-onblur ng-change="get_data()"
|
||||
spellcheck='false'
|
||||
placeholder="auto">
|
||||
</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
@@ -122,6 +148,11 @@
|
||||
templating
|
||||
</a>
|
||||
</li>
|
||||
<li class="grafana-target-segment">
|
||||
<a ng-click="toggleEditorHelp(5)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
|
||||
max data points
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
@@ -177,7 +208,18 @@
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="grafana-info-box span6" ng-if="editorHelpIndex === 5">
|
||||
<h5>Max data points</h5>
|
||||
<ul>
|
||||
<li>Every graphite request is issued with a maxDataPoints parameter</li>
|
||||
<li>Graphite uses this parameter to consolidate the real number of values down to this number</li>
|
||||
<li>If there are more real values, then by default they will be consolidated using averages</li>
|
||||
<li>This could hide real peaks and max values in your series</li>
|
||||
<li>You can change how point consolidation is made using the consolidateBy graphite function</li>
|
||||
<li>Point consolidation will effect series legend values (min,max,total,current)</li>
|
||||
<li>If you override maxDataPoint and set a high value performance can be severely effected</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
50
src/app/partials/help_modal.html
Normal file
50
src/app/partials/help_modal.html
Normal file
@@ -0,0 +1,50 @@
|
||||
<div class="modal-body">
|
||||
<div class="dashboard-editor-header">
|
||||
<div class="dashboard-editor-title">
|
||||
<i class="icon icon-keyboard"></i>
|
||||
Keyboard shutcuts
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-editor-body">
|
||||
<table class="shortcut-table">
|
||||
<tr>
|
||||
<th></th>
|
||||
<th style="text-align: left;">Dashboard wide shortcuts</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="text-align: right;"><span class="label label-info">ESC</span></td>
|
||||
<td>Exit fullscreen edit/view mode, close search or any editor view</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="label label-info">CTRL+F</span></td>
|
||||
<td>Open dashboard search view (also contains import/playlist controls)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="label label-info">CTRL+S</span></td>
|
||||
<td>Save dashboard</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="label label-info">CTRL+H</span></td>
|
||||
<td>Hide row controls</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="label label-info">CTRL+Z</span></td>
|
||||
<td>Zoom out</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="label label-info">CTRL+R</span></td>
|
||||
<td>Refresh (Fetches new data and rerenders panels)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="label label-info">CTRL+O</span></td>
|
||||
<td>Enable/Disable shared graph crosshair</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-info" ng-click="dismiss()">Close</button>
|
||||
</div>
|
||||
@@ -15,26 +15,26 @@
|
||||
tabindex="1">
|
||||
<i class="icon icon-cog"></i>
|
||||
</a>
|
||||
<ul class="dropdown-menu pull-right" role="menu">
|
||||
<li role="menuitem">
|
||||
<a tabindex="1" ng-click="duplicate()">Duplicate</a>
|
||||
<a tabindex="2" ng-click="showQuery()" ng-hide="target.rawQuery">Raw query mode</a>
|
||||
<a tabindex="2" ng-click="hideQuery()" ng-show="target.rawQuery">Query editor mode</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a class="pointer" tabindex="1" ng-click="removeDataQuery(target)">
|
||||
<i class="icon icon-remove"></i>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="dropdown-menu pull-right" role="menu">
|
||||
<li role="menuitem"><a tabindex="1" ng-click="duplicate()">Duplicate</a></li>
|
||||
<li role="menuitem"><a tabindex="1" ng-click="showQuery()" ng-hide="target.rawQuery">Raw query mode</a></li>
|
||||
<li role="menuitem"><a tabindex="1" ng-click="hideQuery()" ng-show="target.rawQuery">Query editor mode</a></li>
|
||||
<li role="menuitem"><a tabindex="1" ng-click="moveMetricQuery($index, $index-1)">Move up </a></li>
|
||||
<li role="menuitem"><a tabindex="1" ng-click="moveMetricQuery($index, $index+1)">Move down</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a class="pointer" tabindex="1" ng-click="removeDataQuery(target)">
|
||||
<i class="icon icon-remove"></i>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="grafana-segment-list">
|
||||
<li>
|
||||
<a class="grafana-target-segment" ng-click="target.hide = !target.hide; get_data();" role="menuitem">
|
||||
<i class="icon-eye-open"></i>
|
||||
</a>
|
||||
<ul class="grafana-segment-list">
|
||||
<li>
|
||||
<a class="grafana-target-segment" ng-click="target.hide = !target.hide; get_data();" role="menuitem">
|
||||
<i class="icon-eye-open"></i>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -64,6 +64,8 @@
|
||||
ng-model="target.series"
|
||||
spellcheck='false'
|
||||
bs-typeahead="listSeries"
|
||||
match-all="true"
|
||||
min-length="3"
|
||||
placeholder="series name"
|
||||
data-min-length=0 data-items=100
|
||||
ng-blur="seriesBlur()">
|
||||
|
||||
@@ -14,4 +14,5 @@
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
@@ -89,6 +89,32 @@
|
||||
ng-model="target.isCounter"
|
||||
ng-change="targetBlur()">
|
||||
</li>
|
||||
<li class="grafana-target-segment" ng-hide="!target.isCounter">
|
||||
Counter Max:
|
||||
</li>
|
||||
<li ng-hide="!target.isCounter">
|
||||
<input type="text"
|
||||
class="grafana-target-segment-input input-medium"
|
||||
ng-disabled="!target.shouldComputeRate"
|
||||
ng-model="target.counterMax"
|
||||
spellcheck='false'
|
||||
placeholder="Counter max value"
|
||||
ng-blur="targetBlur()"
|
||||
/>
|
||||
</li>
|
||||
<li class="grafana-target-segment" ng-hide="!target.isCounter">
|
||||
Counter Reset Value:
|
||||
</li>
|
||||
<li ng-hide="!target.isCounter">
|
||||
<input type="text"
|
||||
class="grafana-target-segment-input input-medium"
|
||||
ng-disabled="!target.shouldComputeRate"
|
||||
ng-model="target.counterResetValue"
|
||||
spellcheck='false'
|
||||
placeholder="Counter reset value"
|
||||
ng-blur="targetBlur()"
|
||||
/>
|
||||
</li>
|
||||
<li class="grafana-target-segment">
|
||||
Alias:
|
||||
</li>
|
||||
|
||||
@@ -5,22 +5,14 @@
|
||||
</div>
|
||||
|
||||
<div ng-model="editor.index" bs-tabs style="text-transform:capitalize;">
|
||||
<div ng-repeat="tab in setEditorTabs(panelMeta)" data-title="{{tab}}">
|
||||
<div ng-repeat="tab in panelMeta.editorTabs" data-title="{{tab.title}}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="dashboard-editor-body">
|
||||
<div ng-show="editorTabs[editor.index] == 'General'">
|
||||
<div ng-include src="'app/partials/panelgeneral.html'"></div>
|
||||
</div>
|
||||
|
||||
<div ng-show="editorTabs[editor.index] == 'Panel'">
|
||||
<div ng-include src="edit_path(panel.type)"></div>
|
||||
</div>
|
||||
|
||||
<div ng-repeat="tab in panelMeta.editorTabs" ng-show="editorTabs[editor.index] == tab.title">
|
||||
<div ng-repeat="tab in panelMeta.editorTabs" ng-show="editor.index == $index">
|
||||
<div ng-include src="tab.src"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -12,3 +12,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<panel-link-editor panel="panel"></panel-link-editor>
|
||||
|
||||
|
||||
|
||||
@@ -22,7 +22,8 @@
|
||||
{{dashboard.title}}
|
||||
</td>
|
||||
<td style="text-align: center">
|
||||
<input type="checkbox" ng-model="dashboard.include" ng-checked="dashboard.include" />
|
||||
<input id="dash-{{$index}}" class="cr1" type="checkbox" ng-model="dashboard.include" ng-checked="dashboard.include" />
|
||||
<label for="dash-{{$index}}" class="cr1"></label>
|
||||
</td>
|
||||
<td style="text-align: center">
|
||||
<i class="icon-remove pointer" ng-click="removeAsFavorite(dashboard)"></i>
|
||||
|
||||
@@ -20,12 +20,8 @@
|
||||
<div class="editor-option">
|
||||
<label class="small">Height</label><input type="text" class="input-mini" ng-model='row.height'></input>
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small"> Editable </label><input type="checkbox" ng-model="row.editable" ng-checked="row.editable" />
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small"> Show title </label><input type="checkbox" ng-model="row.showTitle" ng-checked="row.showTitle" />
|
||||
</div>
|
||||
<editor-opt-bool text="Editable" model="row.editable"></editor-opt-bool>
|
||||
<editor-opt-bool text="Show title" model="row.showTitle"></editor-opt-bool>
|
||||
</div>
|
||||
<div class="row-fluid" ng-if="editor.index == 1">
|
||||
<div class="span12">
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
</button>
|
||||
<span style="position: relative;">
|
||||
<input type="text" placeholder="search dashboards, metrics, or graphs" xng-focus="giveSearchFocus"
|
||||
ng-keydown="keyDown($event)" ng-model="query.query" spellcheck='false' ng-change="search()" />
|
||||
ng-keydown="keyDown($event)" ng-model="query.query" ng-model-options="{ debounce: 500 }" spellcheck='false' ng-change="search()" />
|
||||
<a class="search-tagview-switch" href="javascript:void(0);" ng-class="{'active': tagsOnly}" ng-click="showTags($event)">tags</a>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
33
src/app/partials/share-panel.html
Normal file
33
src/app/partials/share-panel.html
Normal file
@@ -0,0 +1,33 @@
|
||||
<div ng-controller="SharePanelCtrl">
|
||||
<div class="modal-header">
|
||||
<div class="dashboard-editor-header">
|
||||
<div class="dashboard-editor-title">
|
||||
<i class="icon icon-share"></i>
|
||||
Share
|
||||
</div>
|
||||
|
||||
<div ng-model="editor.index" bs-tabs style="text-transform:capitalize;">
|
||||
<div ng-repeat="tab in ['Link']" data-title="{{tab}}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
|
||||
<div class="editor-row">
|
||||
<editor-opt-bool text="Current time range" model="forCurrent" change="buildUrl()"></editor-opt-bool>
|
||||
<editor-opt-bool text="To this panel only" model="toPanel" change="buildUrl()"></editor-opt-bool>
|
||||
<editor-opt-bool text="Include template variables" model="includeTemplateVars" change="buildUrl()"></editor-opt-bool>
|
||||
</div>
|
||||
|
||||
<div class="editor-row" style="margin-top: 20px;">
|
||||
<input type="text" data-share-panel-url class="input input-fluid" ng-model='shareUrl'></input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-success pull-right" ng-click="dismiss();">close</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -47,7 +47,6 @@
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div ng-if="editor.index == 1 || (editor.index == 2 && !currentIsNew)">
|
||||
@@ -56,7 +55,7 @@
|
||||
<div class="editor-row">
|
||||
<div class="editor-option">
|
||||
<label class="small">Variable name</label>
|
||||
<input type="text" class="input-medium" ng-model='current.name' placeholder="name"></input>
|
||||
<input type="text" class="input-medium" ng-model='current.name' placeholder="name" required></input>
|
||||
</div>
|
||||
<div class="editor-option">
|
||||
<label class="small">Type</label>
|
||||
@@ -66,10 +65,10 @@
|
||||
<label class="small">Datasource</label>
|
||||
<select class="input input-medium" ng-model="current.datasource" ng-options="f.value as f.name for f in datasources"></select>
|
||||
</div>
|
||||
<div class="editor-option text-center" ng-show="current.type === 'query'">
|
||||
<label class="small">Refresh on load <tip>Check if you want values to be updated on dashboard load, will slow down dashboard load time.</tip></label>
|
||||
<input type="checkbox" ng-model="current.refresh" ng-checked="current.refresh">
|
||||
</div>
|
||||
|
||||
<editor-opt-bool text="Refresh on load" show-if="current.type === 'query'"
|
||||
tip="Check if you want values to be updated on dashboard load, will slow down dashboard load time"
|
||||
model="current.refresh"></editor-opt-bool>
|
||||
</div>
|
||||
|
||||
<div ng-show="current.type === 'interval'">
|
||||
@@ -80,10 +79,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="editor-row">
|
||||
<div class="editor-option text-center">
|
||||
<label class="small">Include auto interval</label>
|
||||
<input type="checkbox" ng-model="current.auto" ng-checked="current.auto" ng-change="runQuery()">
|
||||
</div>
|
||||
<editor-opt-bool text="Include auto interval" model="current.auto" change="runQuery()"></editor-opt-bool>
|
||||
<div class="editor-option" ng-show="current.auto">
|
||||
<label class="small">Auto interval steps <tip>How many steps, roughly, the interval is rounded and will not always match this count<tip></label>
|
||||
<select class="input-mini" ng-model="current.auto_count" ng-options="f for f in [3,5,10,30,50,100,200]" ng-change="runQuery()"></select>
|
||||
@@ -118,10 +114,7 @@
|
||||
</div>
|
||||
|
||||
<div class="editor-row" style="margin: 15px 0">
|
||||
<div class="editor-option text-center">
|
||||
<label class="small">All option</label>
|
||||
<input type="checkbox" ng-model="current.includeAll" ng-checked="current.includeAll" ng-change="runQuery()">
|
||||
</div>
|
||||
<editor-opt-bool text="All option" model="current.includeAll" change="runQuery()"></editor-opt-bool>
|
||||
<div class="editor-option" ng-show="current.includeAll">
|
||||
<label class="small">All format</label>
|
||||
<select class="input-medium" ng-model="current.allFormat" ng-change="runQuery()" ng-options="f for f in ['glob', 'wildcard', 'regex wildcard', 'regex values']"></select>
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
<div class="modal-header">
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
<h3 class="text-center"><i class="icon-warning-sign"></i> Unsaved changes</h3>
|
||||
<div class="modal-body">
|
||||
<h4 class="text-center"><i class="icon-warning-sign"></i> Unsaved changes</h4>
|
||||
<div class="row-fluid">
|
||||
<span class="span3">
|
||||
{{changes}}
|
||||
|
||||
</span>
|
||||
<button type="button" class="btn btn-success span2" ng-click="dismiss()">Cancel</button>
|
||||
<button type="button" class="btn btn-success span2" ng-click="save();dismiss();">Save</button>
|
||||
<button type="button" class="btn btn-warning span2" ng-click="ignore();dismiss();">Ignore</button>
|
||||
<span class="span3"></span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
</div>
|
||||
|
||||
@@ -31,7 +31,7 @@ function (angular) {
|
||||
|
||||
});
|
||||
|
||||
module.controller('DashFromDBProvider', function($scope, $rootScope, datasourceSrv, $routeParams, alertSrv) {
|
||||
module.controller('DashFromDBProvider', function($scope, $rootScope, datasourceSrv, $routeParams) {
|
||||
|
||||
var db = datasourceSrv.getGrafanaDB();
|
||||
var isTemp = window.location.href.indexOf('dashboard/temp') !== -1;
|
||||
@@ -41,14 +41,14 @@ function (angular) {
|
||||
$scope.initDashboard(dashboard, $scope);
|
||||
}).then(null, function(error) {
|
||||
$scope.initDashboard({ title: 'Grafana'}, $scope);
|
||||
alertSrv.set('Error', error, 'error');
|
||||
$scope.appEvent('alert-error', ['Dashboard load failed', error]);
|
||||
});
|
||||
});
|
||||
|
||||
module.controller('DashFromImportCtrl', function($scope, $location, alertSrv) {
|
||||
module.controller('DashFromImportCtrl', function($scope, $location) {
|
||||
|
||||
if (!window.grafanaImportDashboard) {
|
||||
alertSrv.set('Not found', 'Cannot reload page with unsaved imported dashboard', 'warning', 7000);
|
||||
$scope.appEvent('alert-warning', ['Dashboard load failed', 'Cannot reload unsaved imported dashboard']);
|
||||
$location.path('');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -16,21 +16,28 @@ function (angular, $, config, _, kbn, moment) {
|
||||
.when('/dashboard/script/:jsFile', {
|
||||
templateUrl: 'app/partials/dashboard.html',
|
||||
controller : 'DashFromScriptProvider',
|
||||
reloadOnSearch: false,
|
||||
});
|
||||
});
|
||||
|
||||
module.controller('DashFromScriptProvider', function($scope, $rootScope, $http, $routeParams, alertSrv, $q) {
|
||||
module.controller('DashFromScriptProvider', function($scope, $rootScope, $http, $routeParams, $q, dashboardSrv, datasourceSrv, $timeout) {
|
||||
|
||||
var execute_script = function(result) {
|
||||
var services = {
|
||||
dashboardSrv: dashboardSrv,
|
||||
datasourceSrv: datasourceSrv,
|
||||
$q: $q,
|
||||
};
|
||||
|
||||
/*jshint -W054 */
|
||||
var script_func = new Function('ARGS','kbn','_','moment','window','document','$','jQuery', result.data);
|
||||
var script_result = script_func($routeParams, kbn, _ , moment, window, document, $, $);
|
||||
var script_func = new Function('ARGS','kbn','_','moment','window','document','$','jQuery', 'services', result.data);
|
||||
var script_result = script_func($routeParams, kbn, _ , moment, window, document, $, $, services);
|
||||
|
||||
// Handle async dashboard scripts
|
||||
if (_.isFunction(script_result)) {
|
||||
var deferred = $q.defer();
|
||||
script_result(function(dashboard) {
|
||||
$rootScope.$apply(function() {
|
||||
$timeout(function() {
|
||||
deferred.resolve({ data: dashboard });
|
||||
});
|
||||
});
|
||||
@@ -47,7 +54,7 @@ function (angular, $, config, _, kbn, moment) {
|
||||
.then(execute_script)
|
||||
.then(null,function(err) {
|
||||
console.log('Script dashboard error '+ err);
|
||||
alertSrv.set('Error', "Could not load <i>scripts/"+file+"</i>. Please make sure it exists and returns a valid dashboard", 'error');
|
||||
$scope.appEvent('alert-error', ["Script Error", "Please make sure it exists and returns a valid dashboard"]);
|
||||
return false;
|
||||
});
|
||||
};
|
||||
|
||||
@@ -7,9 +7,22 @@ function (angular, _) {
|
||||
|
||||
var module = angular.module('grafana.services');
|
||||
|
||||
module.service('alertSrv', function($timeout, $sce) {
|
||||
module.service('alertSrv', function($timeout, $sce, $rootScope, $modal, $q) {
|
||||
var self = this;
|
||||
|
||||
this.init = function() {
|
||||
$rootScope.onAppEvent('alert-error', function(e, alert) {
|
||||
self.set(alert[0], alert[1], 'error');
|
||||
});
|
||||
$rootScope.onAppEvent('alert-warning', function(e, alert) {
|
||||
self.set(alert[0], alert[1], 'warning', 5000);
|
||||
});
|
||||
$rootScope.onAppEvent('alert-success', function(e, alert) {
|
||||
self.set(alert[0], alert[1], 'success', 3000);
|
||||
});
|
||||
$rootScope.onAppEvent('confirm-modal', this.showConfirmModal);
|
||||
};
|
||||
|
||||
// List of all alert objects
|
||||
this.list = [];
|
||||
|
||||
@@ -45,5 +58,28 @@ function (angular, _) {
|
||||
this.clearAll = function() {
|
||||
self.list = [];
|
||||
};
|
||||
|
||||
this.showConfirmModal = function(e, payload) {
|
||||
var scope = $rootScope.$new();
|
||||
|
||||
scope.title = payload.title;
|
||||
scope.text = payload.text;
|
||||
scope.onConfirm = payload.onConfirm;
|
||||
|
||||
var confirmModal = $modal({
|
||||
template: './app/partials/confirm_modal.html',
|
||||
persist: true,
|
||||
modalClass: 'confirm-modal',
|
||||
show: false,
|
||||
scope: scope,
|
||||
keyboard: false
|
||||
});
|
||||
|
||||
$q.when(confirmModal).then(function(modalEl) {
|
||||
modalEl.modal('show');
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
define([
|
||||
'./alertSrv',
|
||||
'./utilSrv',
|
||||
'./datasourceSrv',
|
||||
'./timeSrv',
|
||||
'./templateSrv',
|
||||
'./templateValuesSrv',
|
||||
'./panelSrv',
|
||||
'./timer',
|
||||
'./panelMove',
|
||||
'./keyboardManager',
|
||||
'./annotationsSrv',
|
||||
'./popoverSrv',
|
||||
'./playlistSrv',
|
||||
'./unsavedChangesSrv',
|
||||
'./dashboard/dashboardKeyBindings',
|
||||
|
||||
@@ -7,7 +7,7 @@ define([
|
||||
|
||||
var module = angular.module('grafana.services');
|
||||
|
||||
module.service('annotationsSrv', function(datasourceSrv, $q, alertSrv, $rootScope) {
|
||||
module.service('annotationsSrv', function(datasourceSrv, $q, alertSrv, $rootScope, $sanitize) {
|
||||
var promiseCached;
|
||||
var list = [];
|
||||
var timezone;
|
||||
@@ -58,14 +58,16 @@ define([
|
||||
|
||||
function errorHandler(err) {
|
||||
console.log('Annotation error: ', err);
|
||||
var message = err.message || "Aannotation query failed";
|
||||
var message = err.message || "Annotation query failed";
|
||||
alertSrv.set('Annotations error', message,'error');
|
||||
}
|
||||
|
||||
function addAnnotation(options) {
|
||||
var tooltip = "<small><b>" + options.title + "</b><br/>";
|
||||
var title = $sanitize(options.title);
|
||||
var tooltip = "<small><b>" + title + "</b><br/>";
|
||||
if (options.tags) {
|
||||
tooltip += '<span class="tag label label-tag">' + (options.tags || '') + '</span><br/>';
|
||||
var tags = $sanitize(options.tags);
|
||||
tooltip += '<span class="tag label label-tag">' + (tags || '') + '</span><br/>';
|
||||
}
|
||||
|
||||
if (timezone === 'browser') {
|
||||
@@ -76,7 +78,8 @@ define([
|
||||
}
|
||||
|
||||
if (options.text) {
|
||||
tooltip += options.text.replace(/\n/g, '<br/>');
|
||||
var text = $sanitize(options.text);
|
||||
tooltip += text.replace(/\n/g, '<br/>');
|
||||
}
|
||||
|
||||
tooltip += "</small>";
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
define([
|
||||
'angular',
|
||||
'jquery',
|
||||
'services/all'
|
||||
],
|
||||
function(angular, $) {
|
||||
"use strict";
|
||||
|
||||
var module = angular.module('grafana.services');
|
||||
|
||||
module.service('dashboardKeybindings', function($rootScope, keyboardManager) {
|
||||
module.service('dashboardKeybindings', function($rootScope, keyboardManager, $modal, $q) {
|
||||
|
||||
this.shortcuts = function(scope) {
|
||||
|
||||
@@ -18,11 +17,40 @@ function(angular, $) {
|
||||
keyboardManager.unbind('ctrl+s');
|
||||
keyboardManager.unbind('ctrl+r');
|
||||
keyboardManager.unbind('ctrl+z');
|
||||
keyboardManager.unbind('ctrl+o');
|
||||
keyboardManager.unbind('esc');
|
||||
});
|
||||
|
||||
var helpModalScope = null;
|
||||
keyboardManager.bind('shift+?', function() {
|
||||
if (helpModalScope) { return; }
|
||||
|
||||
helpModalScope = $rootScope.$new();
|
||||
var helpModal = $modal({
|
||||
template: './app/partials/help_modal.html',
|
||||
persist: false,
|
||||
show: false,
|
||||
scope: helpModalScope,
|
||||
keyboard: false
|
||||
});
|
||||
|
||||
helpModalScope.$on('$destroy', function() { helpModalScope = null; });
|
||||
$q.when(helpModal).then(function(modalEl) { modalEl.modal('show'); });
|
||||
|
||||
}, { inputDisabled: true });
|
||||
|
||||
keyboardManager.bind('ctrl+f', function() {
|
||||
scope.emitAppEvent('show-dash-editor', { src: 'app/partials/search.html' });
|
||||
scope.appEvent('show-dash-editor', { src: 'app/partials/search.html' });
|
||||
}, { inputDisabled: true });
|
||||
|
||||
keyboardManager.bind('ctrl+o', function() {
|
||||
var current = scope.dashboard.sharedCrosshair;
|
||||
scope.dashboard.sharedCrosshair = !current;
|
||||
scope.dashboard.emit_refresh('refresh');
|
||||
}, { inputDisabled: true });
|
||||
|
||||
keyboardManager.bind('ctrl+l', function() {
|
||||
scope.$broadcast('toggle-all-legends');
|
||||
}, { inputDisabled: true });
|
||||
|
||||
keyboardManager.bind('ctrl+h', function() {
|
||||
@@ -31,7 +59,7 @@ function(angular, $) {
|
||||
}, { inputDisabled: true });
|
||||
|
||||
keyboardManager.bind('ctrl+s', function(evt) {
|
||||
scope.emitAppEvent('save-dashboard', evt);
|
||||
scope.appEvent('save-dashboard', evt);
|
||||
}, { inputDisabled: true });
|
||||
|
||||
keyboardManager.bind('ctrl+r', function() {
|
||||
@@ -39,7 +67,7 @@ function(angular, $) {
|
||||
}, { inputDisabled: true });
|
||||
|
||||
keyboardManager.bind('ctrl+z', function(evt) {
|
||||
scope.emitAppEvent('zoom-out', evt);
|
||||
scope.appEvent('zoom-out', evt);
|
||||
}, { inputDisabled: true });
|
||||
|
||||
keyboardManager.bind('esc', function() {
|
||||
@@ -53,7 +81,7 @@ function(angular, $) {
|
||||
modalData.$scope.dismiss();
|
||||
}
|
||||
|
||||
scope.emitAppEvent('hide-dash-editor');
|
||||
scope.appEvent('hide-dash-editor');
|
||||
|
||||
scope.exitFullscreen();
|
||||
}, { inputDisabled: true });
|
||||
|
||||
@@ -27,23 +27,31 @@ function (angular, $, kbn, _, moment) {
|
||||
this.timezone = data.timezone || 'browser';
|
||||
this.editable = data.editable === false ? false : true;
|
||||
this.hideControls = data.hideControls || false;
|
||||
this.sharedCrosshair = data.sharedCrosshair || false;
|
||||
this.rows = data.rows || [];
|
||||
this.nav = data.nav || [];
|
||||
this.time = data.time || { from: 'now-6h', to: 'now' };
|
||||
this.templating = data.templating || { list: [], enable: false };
|
||||
this.annotations = data.annotations || { list: [], enable: false};
|
||||
this.templating = this._ensureListExist(data.templating);
|
||||
this.annotations = this._ensureListExist(data.annotations);
|
||||
this.refresh = data.refresh;
|
||||
this.version = data.version || 0;
|
||||
this.hideAllLegends = data.hideAllLegends || false;
|
||||
|
||||
if (this.nav.length === 0) {
|
||||
this.nav.push({ type: 'timepicker' });
|
||||
}
|
||||
|
||||
this.updateSchema(data);
|
||||
this._updateSchema(data);
|
||||
}
|
||||
|
||||
var p = DashboardModel.prototype;
|
||||
|
||||
p._ensureListExist = function (data) {
|
||||
if (!data) { data = {}; }
|
||||
if (!data.list) { data.list = []; }
|
||||
return data;
|
||||
};
|
||||
|
||||
p.getNextPanelId = function() {
|
||||
var i, j, row, panel, max = 0;
|
||||
for (i = 0; i < this.rows.length; i++) {
|
||||
@@ -84,23 +92,33 @@ function (angular, $, kbn, _, moment) {
|
||||
row.panels.push(panel);
|
||||
};
|
||||
|
||||
p.getPanelInfoById = function(panelId) {
|
||||
var result = {};
|
||||
_.each(this.rows, function(row) {
|
||||
_.each(row.panels, function(panel, index) {
|
||||
if (panel.id === panelId) {
|
||||
result.panel = panel;
|
||||
result.row = row;
|
||||
result.index = index;
|
||||
return;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (!result.panel) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
p.duplicatePanel = function(panel, row) {
|
||||
var rowIndex = _.indexOf(this.rows, row);
|
||||
var newPanel = angular.copy(panel);
|
||||
newPanel.id = this.getNextPanelId();
|
||||
|
||||
while(rowIndex < this.rows.length) {
|
||||
var currentRow = this.rows[rowIndex];
|
||||
if (this.rowSpan(currentRow) <= 9) {
|
||||
currentRow.panels.push(newPanel);
|
||||
return;
|
||||
}
|
||||
rowIndex++;
|
||||
}
|
||||
|
||||
var newRow = angular.copy(row);
|
||||
newRow.panels = [newPanel];
|
||||
this.rows.push(newRow);
|
||||
var currentRow = this.rows[rowIndex];
|
||||
currentRow.panels.push(newPanel);
|
||||
};
|
||||
|
||||
p.formatDate = function(date, format) {
|
||||
@@ -115,7 +133,7 @@ function (angular, $, kbn, _, moment) {
|
||||
$rootScope.$broadcast('refresh');
|
||||
};
|
||||
|
||||
p.updateSchema = function(old) {
|
||||
p._updateSchema = function(old) {
|
||||
var i, j, k;
|
||||
var oldVersion = this.version;
|
||||
var panelUpgrades = [];
|
||||
|
||||
@@ -32,33 +32,34 @@ function (angular, _, $) {
|
||||
});
|
||||
|
||||
this.update(this.getQueryStringState(), true);
|
||||
this.expandRowForPanel();
|
||||
}
|
||||
|
||||
DashboardViewState.prototype.expandRowForPanel = function() {
|
||||
if (!this.state.panelId) { return; }
|
||||
|
||||
var panelInfo = this.$scope.dashboard.getPanelInfoById(this.state.panelId);
|
||||
if (panelInfo) {
|
||||
panelInfo.row.collapse = false;
|
||||
}
|
||||
};
|
||||
|
||||
DashboardViewState.prototype.needsSync = function(urlState) {
|
||||
return _.isEqual(this.state, urlState) === false;
|
||||
};
|
||||
|
||||
DashboardViewState.prototype.getQueryStringState = function() {
|
||||
var queryParams = $location.search();
|
||||
var urlState = {
|
||||
panelId: parseInt(queryParams.panelId) || null,
|
||||
fullscreen: queryParams.fullscreen ? true : false,
|
||||
edit: queryParams.edit ? true : false,
|
||||
};
|
||||
|
||||
_.each(queryParams, function(value, key) {
|
||||
if (key.indexOf('var-') !== 0) { return; }
|
||||
urlState[key] = value;
|
||||
});
|
||||
|
||||
return urlState;
|
||||
var state = $location.search();
|
||||
state.panelId = parseInt(state.panelId) || null;
|
||||
state.fullscreen = state.fullscreen ? true : null;
|
||||
state.edit = (state.edit === "true" || state.edit === true) || null;
|
||||
return state;
|
||||
};
|
||||
|
||||
DashboardViewState.prototype.serializeToUrl = function() {
|
||||
var urlState = _.clone(this.state);
|
||||
urlState.fullscreen = this.state.fullscreen ? true : null,
|
||||
urlState.edit = this.state.edit ? true : null;
|
||||
|
||||
return urlState;
|
||||
};
|
||||
|
||||
@@ -68,7 +69,8 @@ function (angular, _, $) {
|
||||
|
||||
if (!this.state.fullscreen) {
|
||||
this.state.panelId = null;
|
||||
this.state.edit = false;
|
||||
this.state.fullscreen = null;
|
||||
this.state.edit = null;
|
||||
}
|
||||
|
||||
if (!skipUrlSync) {
|
||||
|
||||
@@ -224,7 +224,7 @@ function (angular, _, config, kbn, moment) {
|
||||
var endsInOpen = function(string, opener, closer) {
|
||||
var character;
|
||||
var count = 0;
|
||||
for (var i=0; i<string.length; i++) {
|
||||
for (var i = 0, len = string.length; i < len; i++) {
|
||||
character = string[i];
|
||||
|
||||
if (character === opener) {
|
||||
@@ -279,18 +279,20 @@ function (angular, _, config, kbn, moment) {
|
||||
return { dashboards: [], tags: [] };
|
||||
}
|
||||
|
||||
var hits = { dashboards: [], tags: results.facets.tags.terms || [] };
|
||||
var resultsHits = results.hits.hits;
|
||||
var displayHits = { dashboards: [], tags: results.facets.tags.terms || [] };
|
||||
|
||||
for (var i = 0; i < results.hits.hits.length; i++) {
|
||||
hits.dashboards.push({
|
||||
id: results.hits.hits[i]._id,
|
||||
title: results.hits.hits[i]._source.title,
|
||||
tags: results.hits.hits[i]._source.tags
|
||||
for (var i = 0, len = resultsHits.length; i < len; i++) {
|
||||
var hit = resultsHits[i];
|
||||
displayHits.dashboards.push({
|
||||
id: hit._id,
|
||||
title: hit._source.title,
|
||||
tags: hit._source.tags
|
||||
});
|
||||
}
|
||||
|
||||
hits.tagsOnly = tagsOnly;
|
||||
return hits;
|
||||
displayHits.tagsOnly = tagsOnly;
|
||||
return displayHits;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
define([
|
||||
'lodash'
|
||||
'lodash',
|
||||
'jquery'
|
||||
],
|
||||
function (_) {
|
||||
function (_, $) {
|
||||
'use strict';
|
||||
|
||||
var index = [];
|
||||
@@ -39,6 +40,13 @@ function (_) {
|
||||
defaultParams: [1],
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'perSecond',
|
||||
category: categories.Transform,
|
||||
params: [],
|
||||
defaultParams: [],
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: "holtWintersForecast",
|
||||
category: categories.Calculate,
|
||||
@@ -93,6 +101,27 @@ function (_) {
|
||||
category: categories.Combine,
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'mapSeries',
|
||||
shortName: 'map',
|
||||
params: [{ name: "node", type: 'int' }],
|
||||
defaultParams: [3],
|
||||
category: categories.Combine,
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'reduceSeries',
|
||||
shortName: 'reduce',
|
||||
params: [
|
||||
{ name: "function", type: 'string', options: ['asPercent', 'diffSeries', 'divideSeries'] },
|
||||
{ name: "reduceNode", type: 'int', options: [0,1,2,3,4,5,6,7,8,9,10,11,12,13] },
|
||||
{ name: "reduceMatchers", type: 'string' },
|
||||
{ name: "reduceMatchers", type: 'string' },
|
||||
],
|
||||
defaultParams: ['asPercent', 2, 'used_bytes', 'total_bytes'],
|
||||
category: categories.Combine,
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'sumSeries',
|
||||
shortName: 'sum',
|
||||
@@ -129,7 +158,12 @@ function (_) {
|
||||
addFuncDef({
|
||||
name: 'sumSeriesWithWildcards',
|
||||
category: categories.Combine,
|
||||
params: [{ name: "node", type: "int" }],
|
||||
params: [
|
||||
{ name: "node", type: "int" },
|
||||
{ name: "node", type: "int", optional: true },
|
||||
{ name: "node", type: "int", optional: true },
|
||||
{ name: "node", type: "int", optional: true }
|
||||
],
|
||||
defaultParams: [3]
|
||||
});
|
||||
|
||||
@@ -148,7 +182,10 @@ function (_) {
|
||||
addFuncDef({
|
||||
name: 'averageSeriesWithWildcards',
|
||||
category: categories.Combine,
|
||||
params: [{ name: "node", type: "int" }],
|
||||
params: [
|
||||
{ name: "node", type: "int" },
|
||||
{ name: "node", type: "int", optional: true },
|
||||
],
|
||||
defaultParams: [3]
|
||||
});
|
||||
|
||||
@@ -163,7 +200,7 @@ function (_) {
|
||||
name: "aliasSub",
|
||||
category: categories.Special,
|
||||
params: [{ name: "search", type: 'string' }, { name: "replace", type: 'string' }],
|
||||
defaultParams: ['', '']
|
||||
defaultParams: ['', '\\1']
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
@@ -193,7 +230,7 @@ function (_) {
|
||||
{
|
||||
name: "node",
|
||||
type: "int",
|
||||
options: [1,2,3,4,5,6,7,8,9,10,12]
|
||||
options: [0,1,2,3,4,5,6,7,8,9,10,12]
|
||||
},
|
||||
{
|
||||
name: "function",
|
||||
@@ -210,6 +247,8 @@ function (_) {
|
||||
params: [
|
||||
{ name: "node", type: "int", options: [0,1,2,3,4,5,6,7,8,9,10,12] },
|
||||
{ name: "node", type: "int", options: [0,-1,-2,-3,-4,-5,-6,-7], optional: true },
|
||||
{ name: "node", type: "int", options: [0,-1,-2,-3,-4,-5,-6,-7], optional: true },
|
||||
{ name: "node", type: "int", options: [0,-1,-2,-3,-4,-5,-6,-7], optional: true },
|
||||
],
|
||||
defaultParams: [3]
|
||||
});
|
||||
@@ -295,6 +334,11 @@ function (_) {
|
||||
defaultParams: [10]
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'offsetToZero',
|
||||
category: categories.Transform,
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'transformNull',
|
||||
category: categories.Transform,
|
||||
@@ -326,11 +370,26 @@ function (_) {
|
||||
defaultParams: ['1d']
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'timeStack',
|
||||
category: categories.Transform,
|
||||
params: [
|
||||
{ name: "timeShiftUnit", type: "select", options: ['1h', '6h', '12h', '1d', '2d', '7d', '14d', '30d'] },
|
||||
{ name: "timeShiftStart", type: "int" },
|
||||
{ name: "timeShiftEnd", type: "int" }
|
||||
],
|
||||
defaultParams: ['1d', 0, 7]
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'summarize',
|
||||
category: categories.Transform,
|
||||
params: [{ name: "interval", type: "string" }, { name: "func", type: "select", options: ['sum', 'avg', 'min', 'max', 'last'] }],
|
||||
defaultParams: ['1h', 'sum']
|
||||
params: [
|
||||
{ name: "interval", type: "string" },
|
||||
{ name: "func", type: "select", options: ['sum', 'avg', 'min', 'max', 'last'] },
|
||||
{ name: "alignToFrom", type: "boolean", optional: true, options: ['false', 'true'] },
|
||||
],
|
||||
defaultParams: ['1h', 'sum', 'false']
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
@@ -453,15 +512,15 @@ function (_) {
|
||||
addFuncDef({
|
||||
name: 'movingAverage',
|
||||
category: categories.Filter,
|
||||
params: [{ name: "window size", type: "int" }],
|
||||
params: [{ name: "windowSize", type: "int_or_interval", options: ['5', '7', '10', '5min', '10min', '30min', '1hour'] }],
|
||||
defaultParams: [10]
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'movingMedian',
|
||||
category: categories.Filter,
|
||||
params: [{ name: "windowSize", type: "select", options: ['1min', '5min', '15min', '30min', '1hour'] }],
|
||||
defaultParams: ['1min']
|
||||
params: [{ name: "windowSize", type: "int_or_interval", options: ['5', '7', '10', '5min', '10min', '30min', '1hour'] }],
|
||||
defaultParams: ['5']
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
@@ -513,6 +572,17 @@ function (_) {
|
||||
defaultParams: [5]
|
||||
});
|
||||
|
||||
addFuncDef({
|
||||
name: 'useSeriesAbove',
|
||||
category: categories.Filter,
|
||||
params: [
|
||||
{ name: "value", type: "int" },
|
||||
{ name: "search", type: "string" },
|
||||
{ name: "replace", type: "string" }
|
||||
],
|
||||
defaultParams: [0, 'search', 'replace']
|
||||
});
|
||||
|
||||
_.each(categories, function(funcList, catName) {
|
||||
categories[catName] = _.sortBy(funcList, 'name');
|
||||
});
|
||||
@@ -533,7 +603,10 @@ function (_) {
|
||||
var parameters = _.map(this.params, function(value, index) {
|
||||
|
||||
var paramType = this.def.params[index].type;
|
||||
if (paramType === 'int' || paramType === 'value_or_series') {
|
||||
if (paramType === 'int' || paramType === 'value_or_series' || paramType === 'boolean') {
|
||||
return value;
|
||||
}
|
||||
else if (paramType === 'int_or_interval' && $.isNumeric(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ function (angular, _, $, config, kbn, moment) {
|
||||
this.supportMetrics = true;
|
||||
this.annotationEditorSrc = 'app/partials/graphite/annotation_editor.html';
|
||||
this.cacheTimeout = datasource.cacheTimeout;
|
||||
this.withCredentials = datasource.withCredentials;
|
||||
}
|
||||
|
||||
GraphiteDatasource.prototype.query = function(options) {
|
||||
@@ -53,13 +54,24 @@ function (angular, _, $, config, kbn, moment) {
|
||||
httpOptions.headers = { 'Content-Type': 'application/x-www-form-urlencoded' };
|
||||
}
|
||||
|
||||
return this.doGraphiteRequest(httpOptions);
|
||||
return this.doGraphiteRequest(httpOptions).then(this.convertDataPointsToMs);
|
||||
}
|
||||
catch(err) {
|
||||
return $q.reject(err);
|
||||
}
|
||||
};
|
||||
|
||||
GraphiteDatasource.prototype.convertDataPointsToMs = function(result) {
|
||||
if (!result || !result.data) { return []; }
|
||||
for (var i = 0; i < result.data.length; i++) {
|
||||
var series = result.data[i];
|
||||
for (var y = 0; y < series.datapoints.length; y++) {
|
||||
series.datapoints[y][1] *= 1000;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
GraphiteDatasource.prototype.annotationQuery = function(annotation, rangeUnparsed) {
|
||||
// Graphite metric as annotation
|
||||
if (annotation.target) {
|
||||
@@ -84,7 +96,7 @@ function (angular, _, $, config, kbn, moment) {
|
||||
|
||||
list.push({
|
||||
annotation: annotation,
|
||||
time: datapoint[1] * 1000,
|
||||
time: datapoint[1],
|
||||
title: target.target
|
||||
});
|
||||
}
|
||||
@@ -198,8 +210,10 @@ function (angular, _, $, config, kbn, moment) {
|
||||
};
|
||||
|
||||
GraphiteDatasource.prototype.doGraphiteRequest = function(options) {
|
||||
if (this.basicAuth) {
|
||||
if (this.basicAuth || this.withCredentials) {
|
||||
options.withCredentials = true;
|
||||
}
|
||||
if (this.basicAuth) {
|
||||
options.headers = options.headers || {};
|
||||
options.headers.Authorization = 'Basic ' + this.basicAuth;
|
||||
}
|
||||
|
||||
@@ -129,6 +129,7 @@ define([
|
||||
i === 63 || // ?
|
||||
i === 37 || // %
|
||||
i === 35 || // #
|
||||
i === 61 || // =
|
||||
i >= 97 && i <= 122; // a-z
|
||||
}
|
||||
|
||||
|
||||
@@ -67,9 +67,16 @@ define([
|
||||
}
|
||||
|
||||
if (this.match('identifier') || this.match('number')) {
|
||||
// hack to handle float numbers in metric segments
|
||||
var parts = this.consumeToken().value.split('.');
|
||||
if (parts.length === 2) {
|
||||
this.tokens.splice(this.index, 0, { type: '.' });
|
||||
this.tokens.splice(this.index + 1, 0, { type: 'number', value: parts[1] });
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'segment',
|
||||
value: this.consumeToken().value
|
||||
value: parts[0]
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ function () {
|
||||
var query = 'select ';
|
||||
var seriesName = target.series;
|
||||
|
||||
if(!seriesName.match('^/.*/')) {
|
||||
if(!seriesName.match('^/.*/') && !seriesName.match(/^merge\(.*\)/)) {
|
||||
seriesName = '"' + seriesName+ '"';
|
||||
}
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ function (_) {
|
||||
_.each(series.points, function (point) {
|
||||
var data = {
|
||||
annotation: self.annotation,
|
||||
time: point[timeCol] * 1000,
|
||||
time: point[timeCol],
|
||||
title: point[titleCol],
|
||||
tags: point[tagsCol],
|
||||
text: point[textCol]
|
||||
@@ -106,21 +106,23 @@ function (_) {
|
||||
};
|
||||
|
||||
p.createNameForSeries = function(seriesName, groupByColValue) {
|
||||
var name = this.alias
|
||||
.replace('$s', seriesName);
|
||||
|
||||
var regex = /\$(\w+)/g;
|
||||
var segments = seriesName.split('.');
|
||||
for (var i = 0; i < segments.length; i++) {
|
||||
if (segments[i].length > 0) {
|
||||
name = name.replace('$' + i, segments[i]);
|
||||
|
||||
return this.alias.replace(regex, function(match, group) {
|
||||
if (group === 's') {
|
||||
return seriesName;
|
||||
}
|
||||
}
|
||||
else if (group === 'g') {
|
||||
return groupByColValue;
|
||||
}
|
||||
var index = parseInt(group);
|
||||
if (_.isNumber(index) && index < segments.length) {
|
||||
return segments[index];
|
||||
}
|
||||
return match;
|
||||
});
|
||||
|
||||
if (this.groupByField) {
|
||||
name = name.replace('$g', groupByColValue);
|
||||
}
|
||||
|
||||
return name;
|
||||
};
|
||||
|
||||
return InfluxSeries;
|
||||
|
||||
@@ -44,7 +44,7 @@ function (angular, _, kbn, InfluxSeries, InfluxQueryBuilder) {
|
||||
|
||||
// replace grafana variables
|
||||
query = query.replace('$timeFilter', timeFilter);
|
||||
query = query.replace('$interval', (target.interval || options.interval));
|
||||
query = query.replace(/\$interval/g, (target.interval || options.interval));
|
||||
|
||||
// replace templated variables
|
||||
query = templateSrv.replace(query);
|
||||
@@ -85,8 +85,13 @@ function (angular, _, kbn, InfluxSeries, InfluxQueryBuilder) {
|
||||
});
|
||||
};
|
||||
|
||||
InfluxDatasource.prototype.listSeries = function() {
|
||||
return this._seriesQuery('list series').then(function(data) {
|
||||
InfluxDatasource.prototype.listSeries = function(query) {
|
||||
// wrap in regex
|
||||
if (query && query.length > 0 && query[0] !== '/') {
|
||||
query = '/' + query + '/';
|
||||
}
|
||||
|
||||
return this._seriesQuery('list series ' + query).then(function(data) {
|
||||
if (!data || data.length === 0) {
|
||||
return [];
|
||||
}
|
||||
@@ -141,7 +146,6 @@ function (angular, _, kbn, InfluxSeries, InfluxQueryBuilder) {
|
||||
InfluxDatasource.prototype._seriesQuery = function(query) {
|
||||
return this._influxRequest('GET', '/series', {
|
||||
q: query,
|
||||
time_precision: 's',
|
||||
});
|
||||
};
|
||||
|
||||
@@ -368,8 +372,9 @@ function (angular, _, kbn, InfluxSeries, InfluxQueryBuilder) {
|
||||
function getTimeFilter(options) {
|
||||
var from = getInfluxTime(options.range.from);
|
||||
var until = getInfluxTime(options.range.to);
|
||||
var fromIsAbsolute = from[from.length-1] === 's';
|
||||
|
||||
if (until === 'now()') {
|
||||
if (until === 'now()' && !fromIsAbsolute) {
|
||||
return 'time > now() - ' + from;
|
||||
}
|
||||
|
||||
|
||||
@@ -61,6 +61,7 @@ function (angular) {
|
||||
else if (e.which) {
|
||||
code = e.which;
|
||||
}
|
||||
|
||||
var character = String.fromCharCode(code).toLowerCase();
|
||||
|
||||
if (code === 188) {
|
||||
@@ -93,6 +94,9 @@ function (angular) {
|
||||
",": "<",
|
||||
".": ">",
|
||||
"/": "?",
|
||||
"»": "?",
|
||||
"«": "?",
|
||||
"¿": "?",
|
||||
"\\": "|"
|
||||
};
|
||||
// Special Keys - and their codes
|
||||
@@ -277,4 +281,4 @@ function (angular) {
|
||||
return keyboardManagerService;
|
||||
}]);
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
define([
|
||||
'angular',
|
||||
'lodash',
|
||||
'kbn'
|
||||
'kbn',
|
||||
'moment'
|
||||
],
|
||||
function (angular, _, kbn) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.services');
|
||||
|
||||
module.factory('OpenTSDBDatasource', function($q, $http) {
|
||||
module.factory('OpenTSDBDatasource', function($q, $http, templateSrv) {
|
||||
|
||||
function OpenTSDBDatasource(datasource) {
|
||||
this.type = 'opentsdb';
|
||||
@@ -99,7 +100,7 @@ function (angular, _, kbn) {
|
||||
// TSDB returns datapoints has a hash of ts => value.
|
||||
// Can't use _.pairs(invert()) because it stringifies keys/values
|
||||
_.each(md.dps, function (v, k) {
|
||||
dps.push([v, k]);
|
||||
dps.push([v, k * 1000]);
|
||||
});
|
||||
|
||||
return { target: metricLabel, datapoints: dps };
|
||||
@@ -123,12 +124,12 @@ function (angular, _, kbn) {
|
||||
}
|
||||
|
||||
var query = {
|
||||
metric: target.metric,
|
||||
metric: templateSrv.replace(target.metric),
|
||||
aggregator: "avg"
|
||||
};
|
||||
|
||||
if (target.aggregator) {
|
||||
query.aggregator = target.aggregator;
|
||||
query.aggregator = templateSrv.replace(target.aggregator);
|
||||
}
|
||||
|
||||
if (target.shouldComputeRate) {
|
||||
@@ -136,13 +137,26 @@ function (angular, _, kbn) {
|
||||
query.rateOptions = {
|
||||
counter: !!target.isCounter
|
||||
};
|
||||
|
||||
if (target.counterMax && target.counterMax.length) {
|
||||
query.rateOptions.counterMax = parseInt(target.counterMax);
|
||||
}
|
||||
|
||||
if (target.counterResetValue && target.counterResetValue.length) {
|
||||
query.rateOptions.resetValue = parseInt(target.counterResetValue);
|
||||
}
|
||||
}
|
||||
|
||||
if (target.shouldDownsample) {
|
||||
query.downsample = target.downsampleInterval + "-" + target.downsampleAggregator;
|
||||
query.downsample = templateSrv.replace(target.downsampleInterval) + "-" + target.downsampleAggregator;
|
||||
}
|
||||
|
||||
query.tags = angular.copy(target.tags);
|
||||
if(query.tags){
|
||||
for(var key in query.tags){
|
||||
query.tags[key] = templateSrv.replace(query.tags[key]);
|
||||
}
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
define([
|
||||
'angular',
|
||||
'lodash'
|
||||
],
|
||||
function (angular, _) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.services');
|
||||
|
||||
module.service('panelMoveSrv', function($rootScope) {
|
||||
|
||||
function PanelMoveSrv(dashboard) {
|
||||
this.dashboard = dashboard;
|
||||
_.bindAll(this, 'onStart', 'onOver', 'onOut', 'onDrop', 'onStop', 'cleanup');
|
||||
}
|
||||
|
||||
var p = PanelMoveSrv.prototype;
|
||||
|
||||
/* each of these can take event,ui,data parameters */
|
||||
p.onStart = function() {
|
||||
this.dashboard.$$panelDragging = true;
|
||||
$rootScope.$apply();
|
||||
};
|
||||
|
||||
p.onOver = function() {
|
||||
$rootScope.$apply();
|
||||
};
|
||||
|
||||
p.onOut = function() {
|
||||
$rootScope.$apply();
|
||||
};
|
||||
|
||||
/*
|
||||
Use our own drop logic. the $parent.$parent this is ugly.
|
||||
*/
|
||||
p.onDrop = function(event,ui,data) {
|
||||
var
|
||||
dragRow = data.draggableScope.$parent.$parent.row.panels,
|
||||
dropRow = data.droppableScope.$parent.$parent.row.panels,
|
||||
dragIndex = data.dragSettings.index,
|
||||
dropIndex = data.dropSettings.index;
|
||||
|
||||
// Remove panel from source row
|
||||
dragRow.splice(dragIndex,1);
|
||||
|
||||
// Add to destination row
|
||||
if (!_.isUndefined(dropRow)) {
|
||||
dropRow.splice(dropIndex,0,data.dragItem);
|
||||
}
|
||||
|
||||
this.dashboard.$$panelDragging = false;
|
||||
// Cleanup nulls/undefined left behind
|
||||
this.cleanup();
|
||||
$rootScope.$apply();
|
||||
$rootScope.$broadcast('render');
|
||||
};
|
||||
|
||||
p.onStop = function() {
|
||||
this.dashboard.$$panelDragging = false;
|
||||
this.cleanup();
|
||||
$rootScope.$apply();
|
||||
};
|
||||
|
||||
p.cleanup = function () {
|
||||
_.each(this.dashboard.rows, function(row) {
|
||||
row.panels = _.without(row.panels,{});
|
||||
row.panels = _.compact(row.panels);
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
init: function(dashboard, scope) {
|
||||
var panelMove = new PanelMoveSrv(dashboard);
|
||||
|
||||
scope.panelMoveDrop = panelMove.onDrop;
|
||||
scope.panelMoveStart = panelMove.onStart;
|
||||
scope.panelMoveStop = panelMove.onStop;
|
||||
scope.panelMoveOver = panelMove.onOver;
|
||||
scope.panelMoveOut = panelMove.onOut;
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
@@ -10,70 +10,35 @@ function (angular, _) {
|
||||
|
||||
this.init = function($scope) {
|
||||
if (!$scope.panel.span) { $scope.panel.span = 12; }
|
||||
if (!$scope.panel.title) { $scope.panel.title = 'No title'; }
|
||||
|
||||
var menu = [
|
||||
{
|
||||
text: 'Edit',
|
||||
configModal: "app/partials/paneleditor.html",
|
||||
condition: !$scope.panelMeta.fullscreenEdit
|
||||
},
|
||||
{
|
||||
text: 'Edit',
|
||||
click: "toggleFullscreen(true)",
|
||||
condition: $scope.panelMeta.fullscreenEdit
|
||||
},
|
||||
{
|
||||
text: "Fullscreen",
|
||||
click: 'toggleFullscreen(false)',
|
||||
condition: $scope.panelMeta.fullscreenView
|
||||
},
|
||||
{
|
||||
text: 'Duplicate',
|
||||
click: 'duplicatePanel(panel)',
|
||||
condition: true
|
||||
},
|
||||
{
|
||||
text: 'Span',
|
||||
submenu: [
|
||||
{ text: '1', click: 'updateColumnSpan(1)' },
|
||||
{ text: '2', click: 'updateColumnSpan(2)' },
|
||||
{ text: '3', click: 'updateColumnSpan(3)' },
|
||||
{ text: '4', click: 'updateColumnSpan(4)' },
|
||||
{ text: '5', click: 'updateColumnSpan(5)' },
|
||||
{ text: '6', click: 'updateColumnSpan(6)' },
|
||||
{ text: '7', click: 'updateColumnSpan(7)' },
|
||||
{ text: '8', click: 'updateColumnSpan(8)' },
|
||||
{ text: '9', click: 'updateColumnSpan(9)' },
|
||||
{ text: '10', click: 'updateColumnSpan(10)' },
|
||||
{ text: '11', click: 'updateColumnSpan(11)' },
|
||||
{ text: '12', click: 'updateColumnSpan(12)' },
|
||||
],
|
||||
condition: true
|
||||
},
|
||||
{
|
||||
text: 'Advanced',
|
||||
submenu: [
|
||||
{ text: 'Panel JSON', click: 'editPanelJson()' },
|
||||
],
|
||||
condition: true
|
||||
},
|
||||
{
|
||||
text: 'Remove',
|
||||
click: 'remove_panel_from_row(row, panel)',
|
||||
condition: true
|
||||
}
|
||||
];
|
||||
|
||||
$scope.inspector = {};
|
||||
$scope.panelMeta.menu = _.where(menu, { condition: true });
|
||||
|
||||
$scope.editPanel = function() {
|
||||
if ($scope.panelMeta.fullscreen) {
|
||||
$scope.toggleFullscreen(true);
|
||||
}
|
||||
else {
|
||||
$scope.appEvent('show-dash-editor', { src: 'app/partials/paneleditor.html', scope: $scope });
|
||||
}
|
||||
};
|
||||
|
||||
$scope.sharePanel = function() {
|
||||
$scope.appEvent('show-modal', {
|
||||
src: './app/partials/share-panel.html',
|
||||
scope: $scope.$new()
|
||||
});
|
||||
};
|
||||
|
||||
$scope.editPanelJson = function() {
|
||||
$scope.emitAppEvent('show-json-editor', { object: $scope.panel, updateHandler: $scope.replacePanel });
|
||||
$scope.appEvent('show-json-editor', { object: $scope.panel, updateHandler: $scope.replacePanel });
|
||||
};
|
||||
|
||||
$scope.duplicatePanel = function() {
|
||||
$scope.dashboard.duplicatePanel($scope.panel, $scope.row);
|
||||
};
|
||||
|
||||
$scope.updateColumnSpan = function(span) {
|
||||
$scope.panel.span = span;
|
||||
$scope.panel.span = Math.min(Math.max($scope.panel.span + span, 1), 12);
|
||||
|
||||
$timeout(function() {
|
||||
$scope.$emit('render');
|
||||
@@ -104,6 +69,14 @@ function (angular, _) {
|
||||
$scope.get_data();
|
||||
};
|
||||
|
||||
$scope.toggleEditorHelp = function(index) {
|
||||
if ($scope.editorHelpIndex === index) {
|
||||
$scope.editorHelpIndex = null;
|
||||
return;
|
||||
}
|
||||
$scope.editorHelpIndex = index;
|
||||
};
|
||||
|
||||
$scope.toggleFullscreen = function(edit) {
|
||||
$scope.dashboardViewState.update({ fullscreen: true, edit: edit, panelId: $scope.panel.id });
|
||||
};
|
||||
@@ -115,9 +88,6 @@ function (angular, _) {
|
||||
// Post init phase
|
||||
$scope.fullscreen = false;
|
||||
$scope.editor = { index: 1 };
|
||||
if ($scope.panelMeta.fullEditorTabs) {
|
||||
$scope.editorTabs = _.pluck($scope.panelMeta.fullEditorTabs, 'title');
|
||||
}
|
||||
|
||||
$scope.datasources = datasourceSrv.getMetricSources();
|
||||
$scope.setDatasource($scope.panel.datasource);
|
||||
|
||||
46
src/app/services/popoverSrv.js
Normal file
46
src/app/services/popoverSrv.js
Normal file
@@ -0,0 +1,46 @@
|
||||
define([
|
||||
'angular',
|
||||
'lodash',
|
||||
],
|
||||
function (angular, _) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.services');
|
||||
|
||||
module.service('popoverSrv', function($templateCache, $timeout, $q, $http, $compile) {
|
||||
|
||||
this.getTemplate = function(url) {
|
||||
return $q.when($templateCache.get(url) || $http.get(url, {cache: true}));
|
||||
};
|
||||
|
||||
this.show = function(options) {
|
||||
var popover = options.element.data('popover');
|
||||
if (popover) {
|
||||
popover.scope.$destroy();
|
||||
popover.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
this.getTemplate(options.templateUrl).then(function(result) {
|
||||
var template = _.isString(result) ? result : result.data;
|
||||
|
||||
options.element.popover({
|
||||
content: template,
|
||||
placement: 'bottom',
|
||||
html: true
|
||||
});
|
||||
|
||||
popover = options.element.data('popover');
|
||||
popover.hasContent = function () {
|
||||
return template;
|
||||
};
|
||||
|
||||
popover.toggle();
|
||||
popover.scope = options.scope;
|
||||
$compile(popover.$tip)(popover.scope);
|
||||
});
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
@@ -30,6 +30,7 @@ function (angular, _, kbn) {
|
||||
var option = _.findWhere(variable.options, { text: urlValue });
|
||||
option = option || { text: urlValue, value: urlValue };
|
||||
this.setVariableValue(variable, option, true);
|
||||
this.updateAutoInterval(variable);
|
||||
}
|
||||
else if (variable.refresh) {
|
||||
this.updateOptions(variable);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user