Compare commits

..

299 Commits

Author SHA1 Message Date
Torkel Ödegaard
d9386cc2c3 Updated lastest.json 2014-12-29 11:24:14 +01:00
Torkel Ödegaard
38f348e642 Updated version to 1.9.1 2014-12-29 11:21:25 +01:00
Torkel Ödegaard
656ec9c48f SinglestatPanel: added more fontsize options, Closes #1187 2014-12-29 10:51:59 +01:00
Torkel Ödegaard
f5e8f9334b Fixed test failing in chrome 2014-12-26 11:50:59 +01:00
Torkel Ödegaard
7be2105fd9 Merge pull request #1272 from matschaffer/plugin-spec-support
Allow for plugin testing.
2014-12-26 11:40:54 +01:00
Torkel Ödegaard
49ee388dcf Merge pull request #1274 from matschaffer/panel-alert-tweak
Bring panel alert to front and move tooltip to top.
2014-12-26 11:40:41 +01:00
Mat Schaffer
6cb4b4061c Bring panel alert to front and move tooltip to top.
This makes the panel a lot easier to mouse over without odd visual effects.
2014-12-23 18:12:04 -08:00
Mat Schaffer
85e50ece2e Allow for plugin testing.
With this change in place you can include plugin specs in `grunt test` by creating a `src/config.js` like that includes something like this:

```js
      plugins: {
        // list of plugin panels
        panels: [],
        // requirejs modules in plugins folder that should be loaded
        // for example custom datasources
        dependencies: ['grafana-plugins/atlasDatasource'],
        specs: ['grafana-plugins/specs/atlasDatasource-specs']
      }
```
2014-12-23 12:01:50 -08:00
Torkel Ödegaard
32ae0ea13e Plugins: Custom datasource example adapted to return timestamps in millieseconds, Fixes #1263 2014-12-23 11:32:25 +01:00
Torkel Ödegaard
1b3bddd622 Graph: graph spinner should not stop until annotation request is complete, #1235 2014-12-19 08:39:23 +01:00
Torkel Ödegaard
731bb6ba03 Graph: hover tooltip and axis format units fix, bug introduced last week 2014-12-18 09:12:36 +01:00
Torkel Ödegaard
fed06ef97d Graph: Fix for y axis and scaled units (GiB etc) caused rounding, for example 400 GiB instead of 378 GiB, Fixes #1251 2014-12-18 08:44:47 +01:00
Torkel Ödegaard
a58330f4d8 InfluxDB: Support more than 10 series name segments when using alias patterns, Closes #1126 2014-12-17 14:14:24 +01:00
Torkel Ödegaard
5a46c2397b OpenTSDB: Downsample query field now supports interval template variable, Closes #1242 2014-12-17 13:34:52 +01:00
Torkel Ödegaard
f79588c191 Annotations: mini fix for annotations editor and the list of annotations 2014-12-16 15:47:10 +01:00
Torkel Ödegaard
9f766557f1 Dashboard: fixed minor issue when trying to aborting dashboard delete from search list, Fixes #1233 2014-12-15 17:30:01 +01:00
Torkel Ödegaard
846cf934f5 Graph: New legend option hideEmtpy to hide series with only null values from legend, Closes #1028 2014-12-15 16:13:34 +01:00
Torkel Ödegaard
162eb4ca35 Panel: duplicate now always adds the duplicated panel on the same row instead of creating a new row, Closes #1185 2014-12-15 15:21:29 +01:00
Torkel Ödegaard
2f18444a43 Graphite: alt node suggestions will now not include wildcard or template variables if the node is empty, Closes #1230 2014-12-15 15:16:03 +01:00
Torkel Ödegaard
8a4ff5bddc Merge pull request #1205 from noise/master
fix #1204: add separate datasource parameter withCredentials
2014-12-15 15:05:43 +01:00
Torkel Ödegaard
4ae7648bea Merge pull request #1193 from jklukas/master
Add offsetToZero to gfunc.
2014-12-15 12:37:27 +01:00
Torkel Ödegaard
dc75559758 Merge pull request #1206 from nikut/sumSeriesWithWildcards-params
allow multiple params for sumSeriesWithWildcards
2014-12-15 12:35:19 +01:00
Torkel Ödegaard
acd944a649 Graphite: movingAverage / movingMedian parameter type impovement, now handles int and interval parameter, Fixes #1207 2014-12-15 12:33:04 +01:00
Torkel Ödegaard
3c1b30e3c1 Graphite: parser fix for hard case where ip numbers are used as segments, Fixes #1224 2014-12-15 11:36:08 +01:00
Torkel Ödegaard
5daefc8b8e White theme: css fix for links in annotations tooltips, Fixes #1216 2014-12-15 10:53:37 +01:00
Torkel Ödegaard
715b9cbad0 Small fix, removed test code change 2014-12-11 09:44:45 -08:00
Torkel Ödegaard
7194e91d3b Singlestat: small fix for threshold evalutations, use greater or equal for threshold checks, Fixes #1192 2014-12-11 09:43:36 -08:00
Torkel Ödegaard
69d56b8ed7 Graph: fix for series tooltip when one series is hidden/disabled, #1199 2014-12-11 07:47:04 -08:00
Torkel Ödegaard
982a5b1a39 Panel: drilldown link, moved new params tooltip to params property, #1210 2014-12-11 07:26:00 -08:00
Torkel Ödegaard
6b8cb4ac7f Merge pull request #1210 from falkenbt/patch-1
Extend tooltip for drilldown link
2014-12-11 07:23:07 -08:00
Torkel Ödegaard
397f253180 Merge pull request #1214 from DazWorrall/useseriesabove
Added graphite function 'useSeriesAbove'
2014-12-11 07:21:00 -08:00
Darren Worrall
f93b6f7d85 Add missing default parameters to useSeriesAbove 2014-12-10 15:57:12 +00:00
Darren Worrall
edb2cf2cf2 Added graphite function 'useSeriesAbove' 2014-12-10 15:51:45 +00:00
falkenbt
fb12dd4688 Extend tooltip for drilldown link
It took me a bit to find out that variables need to be passed with var-variableName. 
The behavior is not specific to the drilldown link but I couldn't find it in any documentation elsewhere and it might help others to have it documented right here.
2014-12-09 17:23:44 +01:00
Niku Toivola
0a561e5aeb allow multiple params for sumSeriesWithWildcards 2014-12-09 11:41:01 +02:00
bret barker
c140c8cac9 fix #1204: add separate datasource parameter withCredentials 2014-12-08 09:35:55 -05:00
Jeff Klukas
06ab063671 Add offsetToZero to gfunc. 2014-12-04 09:00:47 -08:00
Torkel Ödegaard
1591a486cc Fixed typo in graphite editor tooltip 2014-12-02 14:51:21 -08:00
Torkel Ödegaard
b53efed1ef formating fix for the changelog 2014-12-02 14:49:42 -08:00
Torkel Ödegaard
7a202db5ad Updated latest.json 2014-12-02 14:24:34 -08:00
Torkel Ödegaard
af1ae7cab4 SinglestatPanel: decimal precision fix for uneven numbers, #1066 2014-12-02 13:55:56 -08:00
Torkel Ödegaard
24519cbf78 Updated version to 1.9.0 2014-12-02 11:21:18 -08:00
Torkel Ödegaard
01305462aa Row: css fix for collapsed rows with empty title 2014-12-02 11:07:51 -08:00
Torkel Ödegaard
7a4077405e Annotations: added html sanitation to prevent markup injection/XSS, Closes #1121 2014-11-27 14:46:01 +01:00
Torkel Ödegaard
9594effb6c Graph: Fix for tooltip series order when series draw order was changed with zindex property, Fixes #1108 2014-11-27 14:30:17 +01:00
Torkel Ödegaard
e750498696 Templating: added validation for allowed characters in variable names, Closes #1106 2014-11-27 14:22:55 +01:00
Torkel Ödegaard
69e18905f5 Templating: added validation to template editor for variable names, Fixes #1133 2014-11-27 14:17:31 +01:00
Torkel Ödegaard
ac4524cf9b Firefox: Workaround for Firefox bug, casued input text fields to not be selectable and not have placeable cursor, Fixes #1123 2014-11-27 13:35:03 +01:00
Torkel Ödegaard
7baad7ff10 Tech: updated angular libs 2014-11-27 13:31:55 +01:00
Torkel Ödegaard
d7ef6daeb8 SinglestatPanel: Fixed absolute drilldown link issue, Fixes #1150 2014-11-27 10:42:05 +01:00
Torkel Ödegaard
93e3908a63 Merge pull request #1140 from johnou/support_negative_thresholds
Support for threshold colouring of zero / negative values.
2014-11-26 12:33:29 +01:00
Torkel Ödegaard
bf5f6ce97c Merge branch 'master' of github.com:grafana/grafana
Conflicts:
	CHANGELOG.md
2014-11-26 09:36:11 +01:00
Torkel Ödegaard
ed2ca5fced Graph: Fix to legend value Max and negative values, Fixes #1136 2014-11-26 09:34:21 +01:00
Johno Crawford
8dfe85f23e Support for negative thresholds. 2014-11-25 19:31:35 +00:00
Torkel Ödegaard
846992930c Merge pull request #1109 from jklukas/master
Purge timspan variable from scripted templates.
2014-11-23 15:43:46 +01:00
Torkel Ödegaard
a1d652d578 SinglestatPanel: Added null point handling, and value to text mapping, Closes #1130, Fixes #1120, #951 2014-11-22 15:26:23 +01:00
Torkel Ödegaard
d0e057722b Graphite: added more optional parameters to aliasByNode function definition, #1124 2014-11-21 17:48:49 +01:00
Torkel Ödegaard
d1be4e2a90 Light theme: tweaks to background color and table highlight rows, Closes #1119 2014-11-21 10:44:37 +01:00
Torkel Ödegaard
3cde783d1d Graphite: Lexer fix, allow equal sign (=) in metric paths, Fixes #1114 2014-11-21 10:25:36 +01:00
Torkel Ödegaard
e933369f56 Singlestat: changed singlestat title to centered position, Closes #1117 2014-11-21 10:17:08 +01:00
Torkel Ödegaard
e109f8d69c Graphite: added timeStack function definition, Closes #1118 2014-11-21 10:11:21 +01:00
Torkel Ödegaard
d198057eaf InfluxDB: fixed issue with using custom/absolute from time to now(), Fixes #1113 2014-11-21 10:06:42 +01:00
Torkel Ödegaard
88c2f18b20 Graphite: fixed aliasSub graphite func definition 2014-11-21 08:24:13 +01:00
Jeff Klukas
a30a604228 Purge timspan variable from scripted templates. 2014-11-20 09:32:19 -06:00
Torkel Ödegaard
62b58d8bb0 Updated scripted dashboard example 2014-11-20 09:09:59 +01:00
Torkel Ödegaard
a5951592f7 Merge branch 'master' of github.com:jklukas/grafana into jklukas-master 2014-11-20 09:08:00 +01:00
Torkel Ödegaard
6392d6514e Graphite: fixed: moving graphite function left/right did not update query, Fixes #1100 2014-11-20 09:00:30 +01:00
Torkel Ödegaard
91d6641326 Graph: fixed issue with shared tooltip when one or more series is hidden, Fixes #1094 2014-11-19 16:01:04 +01:00
Torkel Ödegaard
dd398f73c2 Css tweak to dark theme, mark submenu carets (ie arrows) white, Fixes #1098 2014-11-19 15:41:16 +01:00
Torkel Ödegaard
de10bd4ef6 DrilldownLink: template variables in params property was not interpolated, Fixes #1095 2014-11-19 15:33:00 +01:00
Torkel Ödegaard
56321da9c1 Bug: Fixed position for drilldown link tooltip when dashboard requires scrolling, Fixes #1093 2014-11-19 15:26:14 +01:00
Torkel Ödegaard
d0d1c5ea5f updated changelog 2014-11-19 11:57:00 +01:00
Torkel Ödegaard
e16872c864 Updated angular-dragdrop to fix issue with IE9, Fixes #1087 2014-11-19 11:55:18 +01:00
Jeff Klukas
1e425244d2 Refactor timspan default in scripted examples. 2014-11-18 14:24:55 -06:00
Torkel Ödegaard
0c6618d2f6 Small tweaks 2014-11-18 16:58:26 +01:00
Torkel Ödegaard
a9d7823186 Singlestat: only show thresholds options when coloring option is checked, Fixes #1077 2014-11-18 08:27:43 +01:00
Torkel Ödegaard
6c0f5329aa Graph: improved decimal precision in legend and graph hover when graph ticks use single decimal point, now graph legend and tooltip always use one more decimal precision than axis ticks (if axis ticks has decimals), Fixes #1072 2014-11-17 19:20:27 +01:00
Torkel Ödegaard
a677a4feff Updated changelog with share panel feature, #864, it was missing 2014-11-17 16:13:47 +01:00
Torkel Ödegaard
4f674c8d19 Updated changelog with RC1 release date 2014-11-17 15:21:43 +01:00
Torkel Ödegaard
e197163019 Changed version to 1.9-rc1 2014-11-17 11:19:17 +01:00
Torkel Ödegaard
7ba0099fa9 Panel: fixes to panel menu when in edito/fullscreen mode, Fixes #1069 2014-11-17 09:59:37 +01:00
Torkel Ödegaard
f45797ec4b trying out panel actions buttons 2014-11-17 09:11:54 +01:00
Torkel Ödegaard
02e1ac12b2 InfluxDB: fixed annotations, broken after switch to millisecond resolution, Fixes #1061 2014-11-17 07:09:21 +01:00
Torkel Ödegaard
bde138177d Singlestat: fixed decimal issue when value was 1, Fixes #1066 2014-11-15 10:38:01 +01:00
Torkel Ödegaard
640c558446 Graph: added export graph time series data as csv file feature, accessed from panel menu dropdown, #861 2014-11-14 12:12:09 +01:00
Torkel Ödegaard
c11ce99bb3 SinglestatPanel: another small fix for drilldown link click while fullscreen mode, #1041 2014-11-14 11:41:36 +01:00
Torkel Ödegaard
8ad83b8d58 SinglestatPanel: small fix for drilldown link click while fullscreen mode, #1041 2014-11-14 11:36:20 +01:00
Torkel Ödegaard
873d3d7c4a SinglestatPanel: Added integration with drilldown link feature, if a drilldown link is present the entire singlestat panel will act as a link, with hover tooltip containing the link name, #951, #1041 2014-11-14 09:33:03 +01:00
Torkel Ödegaard
d4adaaaf2b Graphite: annotations stop working after recent change to millisecond resolution, Fixes #1061 2014-11-14 08:19:49 +01:00
Torkel Ödegaard
104493e725 SharePanel: fix for interval variables and auto value, Fixes #1056 2014-11-13 15:23:34 +01:00
Torkel Ödegaard
b172e7afdc SinglestatPanel: mini fix 2014-11-13 15:23:34 +01:00
Torkel Ödegaard
b1efbeb220 Merge pull request #1057 from kaos/master
panelmeta: fix metrics tab title.
2014-11-13 15:06:31 +01:00
Andreas Stenius
f2a6657b72 panelmeta: fix metrics tab title. 2014-11-13 14:29:42 +01:00
Torkel Ödegaard
4eb4974909 Graph: change to current legend value handling, if last value is null, current will pick next to last value, Closes #190 2014-11-12 13:47:06 +01:00
Torkel Ödegaard
3e2c898881 Graph: legend fix for current legend value 2014-11-12 13:40:22 +01:00
Torkel Ödegaard
2fb176a244 Graphite: fix for graphite query editor, when adding functions and the function selection dropdown get push down the selection did not work properly, Fixes #1038 2014-11-12 12:57:22 +01:00
Torkel Ödegaard
53a6a7b305 Annotations: fixed spelling issue in annoation error handling code 2014-11-12 12:41:53 +01:00
Torkel Ödegaard
2045380223 Graph: moved graph and graph tooltip directive into panels/graph folder 2014-11-12 11:58:04 +01:00
Torkel Ödegaard
1c0fc3c924 Graph: small fix to legend sorting, #1030 2014-11-12 10:19:33 +01:00
Torkel Ödegaard
0b966b7a28 Graph: legend sorting while in table mode implemented, panel persisted property, Closes #1030 2014-11-12 10:11:01 +01:00
Torkel Ödegaard
882a477c0f Graph: increase decimal precision in tooltip and legend values when unit scaling is applied, #1043 2014-11-12 09:09:05 +01:00
Torkel Ödegaard
ebcf2c3f68 InfluxDB: Support for sub second resolution graphs, Closes #714, #728, #752 2014-11-12 08:39:04 +01:00
Torkel Ödegaard
54fafb3a76 Graph: fix to typeahead for series overrides, (bug introduced in recent commit), Fixes #1046 2014-11-11 21:09:50 +01:00
Torkel Ödegaard
01bd662046 Graph: legend fix for bug when running optimized build, Fixes #1045 2014-11-11 21:06:01 +01:00
Torkel Ödegaard
9e26d3e85d SinglestatPanel: increased scaled decimal offset by 2 for, improves decimal precision when units gets scaled, #1043, #951 2014-11-11 16:30:20 +01:00
Torkel Ödegaard
17114778b7 Panel: fixed bug introduced in recent commit that caused blank title in optimized build, #1041 2014-11-11 15:00:31 +01:00
Torkel Ödegaard
98037ca0c6 SinglestatPanel: fixed issue when value is zero, #1039 2014-11-11 14:55:23 +01:00
Torkel Ödegaard
4ec59e8211 Tech: cleanup unused angular-strap directives, Closes #1029 2014-11-11 13:57:39 +01:00
Torkel Ödegaard
1e6a5ff8ec Updated changelog with #1041 2014-11-11 13:44:25 +01:00
Torkel Ödegaard
c12d830162 Panel: added search typeahead for dashboard links, #1041 2014-11-11 13:38:34 +01:00
Torkel Ödegaard
a49a9b3b64 Panel: more work on panel links, #1041 2014-11-11 12:51:57 +01:00
Torkel Ödegaard
5da3da5962 PanelLinks: began work on drilldown panel links feature, #1041 2014-11-11 11:48:27 +01:00
Torkel Ödegaard
8bb51d47f8 DashboardViewState: small fix for bug that caused issue for singlestat panel 2014-11-11 08:07:05 +01:00
Torkel Ödegaard
381b9ee7ee Graph: legend css tweaks 2014-11-11 07:51:52 +01:00
Torkel Ödegaard
a301c96c9d SharePanel: fix to share feature, Closes #1035 2014-11-10 16:18:59 +01:00
Torkel Ödegaard
f9c3cdab67 UI: replaced native confirm dialogs with nicer custom ones 2014-11-10 15:01:30 +01:00
Torkel Ödegaard
27ec0d532e began work on custom confirm modal 2014-11-10 10:40:45 +01:00
Torkel Ödegaard
3aa619b617 Panel: fixed duplicate panel, broken after yesterdays panel menu model refactoring 2014-11-09 09:44:16 +01:00
Torkel Ödegaard
ef92272bee Panel: fixed menu position after moving json to new dropdown 2014-11-08 19:28:16 +01:00
Torkel Ödegaard
815ef05daf Graph: refactoring some stuff with legend values 2014-11-08 19:16:22 +01:00
Torkel Ödegaard
2ab19148c1 Graph: toggle legend on/off from panel menu, #941, no keyboard shortcut yet, but could come 2014-11-08 18:12:05 +01:00
Torkel Ödegaard
d12f4a4aee Panels: refactoring panel meta model & menu, will open up panel specific menu actions 2014-11-08 16:27:49 +01:00
Torkel Ödegaard
81b1939f92 Graph: small fix to legend table mode 2014-11-08 11:44:23 +01:00
Torkel Ödegaard
834daeecd0 OpenTSDB: updated change log with templating support PR #917 2014-11-08 11:35:42 +01:00
Torkel Ödegaard
6aa0208316 Updated changelog with #1030 2014-11-07 14:23:18 +01:00
Torkel Ödegaard
aa87d8eb22 Graph: changed style of legend table mode to full width table and with headers, #1030 2014-11-07 14:20:58 +01:00
Torkel Ödegaard
cd21fa7016 Graph: new legend table display style, #1030 2014-11-07 13:57:52 +01:00
Torkel Ödegaard
7ba4f6b93f Singlestat: removed debug console.log 2014-11-07 13:56:28 +01:00
Torkel Ödegaard
e16a51ad06 Singlestat: fixed decimal precision bug, #951 2014-11-07 13:54:19 +01:00
Torkel Ödegaard
6861dc137f Graph: fix for legend show/hide toggle, broken by recent legend rewrite 2014-11-07 13:39:47 +01:00
Torkel Ödegaard
e530e4d4bc Tech: rewrite of how the legend is implement, performance increase, and will make future legend enhancements easier 2014-11-07 12:15:34 +01:00
Torkel Ödegaard
d150bc1e52 Graphite: added mapSeries and reduceSeries functions, Closes #1018 2014-11-07 06:34:47 +01:00
Torkel Ödegaard
cc8961360a Graphite: added maxDataPoints override option and a help section that describes how graphite point consolidation work, Closes #5 2014-11-06 16:25:46 +01:00
Torkel Ödegaard
c0539e483e Added singlestat panel to changelog 2014-11-06 14:10:25 +01:00
Torkel Ödegaard
f0b7099be3 SingleStatPanel: renamed panel to singlestat 2014-11-06 14:07:32 +01:00
Torkel Ödegaard
ee183d4574 SingleStatPanel: some more tweaks and polish 2014-11-06 13:57:16 +01:00
Torkel Ödegaard
fa813024ca SingleStatPanel: various fixes 2014-11-06 12:30:42 +01:00
Torkel Ödegaard
37176fa42d SingleStatPanel: Finnaly solved automatic decimal precision calculation for singlestat panel, #951 2014-11-06 12:17:46 +01:00
Torkel Ödegaard
7ff8931def SingleStatPanel: added font size options for value, prefix and postfix, #951 2014-11-06 10:47:46 +01:00
Torkel Ödegaard
2a962bf8fd Singlestat: progress on singlestat panel 2014-11-06 09:56:50 +01:00
Torkel Ödegaard
bbbcba8b98 Merge branch 'master' into valuepanel 2014-11-06 08:54:01 +01:00
Torkel Ödegaard
ecdcd10612 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. Closes #1007 2014-11-03 10:21:16 +01:00
Torkel Ödegaard
10ea140358 MetricEditors: Ability to reorder metric queries in metrics tag, Closes #716, Closes #856 2014-11-03 08:56:13 +01:00
Torkel Ödegaard
74e0309241 Fixes #912, URL parameters are lost when switching to fullscreen/edit mode 2014-11-02 11:56:51 +01:00
Torkel Ödegaard
c88bfbbf82 SingleStatPanel: editor cleanup 2014-11-02 11:36:11 +01:00
Torkel Ödegaard
4edb89eeb9 Merge branch 'summarize-alignToFrom' of github.com:adriensamson/grafana into adriensamson-summarize-alignToFrom 2014-10-30 09:24:27 +01:00
Torkel Ödegaard
cdb4b3cc7d Panel: fix fullscreen/edit view and page refresh when panel is in collapsed/hidden row, Fixes #992 2014-10-29 09:51:44 +01:00
Torkel Ödegaard
ed8dd03fa1 Merge branch 'master' into valuepanel 2014-10-29 09:24:49 +01:00
Torkel Ödegaard
e5bb7f7c2d CustomDatasource: fixed build optimization issue with custom datasources, Fixes #994 2014-10-28 19:00:46 +01:00
Torkel Ödegaard
a7b0f6dc9f 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), Closes #991 2014-10-28 16:16:52 +01:00
Torkel Ödegaard
c42986c07d Merge branch 'services_in_scripted_dashboard' 2014-10-28 16:14:41 +01:00
Torkel Ödegaard
a982dd1765 ScriptedDashboard: using grafana services in scripted dashboard 2014-10-28 16:14:28 +01:00
Torkel Ödegaard
c3900398fc Merge pull request #917 from mchataigner/master
adding templating in opentsdb graphs
2014-10-28 15:39:14 +01:00
Torkel Ödegaard
4b79a5e9da OpenTSDB: merged PR #930 adding counter max and counter reset options to OpenTSDB query editor 2014-10-28 12:41:17 +01:00
Torkel Ödegaard
eed2feea97 Merge branch 'opentsdb-counterMax' of github.com:rsimiciuc/grafana into rsimiciuc-opentsdb-counterMax 2014-10-28 12:36:15 +01:00
Torkel Ödegaard
60a2d9f624 Dashboard: fix for collapsed rows became invisible when hide controls was enabled, Fixes #987 2014-10-27 10:42:13 +01:00
Torkel Ödegaard
3cd33b6ffc Tech: added cache busting for requirejs fetches, will only affect config.js for optimized builds, should solve user issues where browsers cache config.js which causes initial confusing setup/config issues. 2014-10-26 08:45:42 +01:00
Torkel Ödegaard
e3942b3438 Graph: multi series tooltip fix for long series names, and metric value row alignment, Fixes #983, #984 2014-10-25 12:01:31 +02:00
Torkel Ödegaard
0bf37b8c00 Merge pull request #981 from swoop-inc/mb_value_update_bug
When get_data fails the legend should be cleared
2014-10-24 21:10:07 +02:00
Torkel Ödegaard
0e5dbf3889 Merge pull request #980 from maxstepanov/patch-1
Replace all $interval occurrences in query string
2014-10-24 21:07:38 +02:00
Torkel Ödegaard
785f96aabe Merge pull request #975 from swoop-inc/mb_support_influx_regex_merge
Add support for regex based series merging in InfluxDB
2014-10-24 21:01:52 +02:00
Mark Bell
5cec936128 When get_data fails the legend should be cleared 2014-10-24 14:51:45 -04:00
Max Stepanov
02861142cb Replace all $interval occurrences in query string
because template
2014-10-24 20:06:39 +03:00
Torkel Ödegaard
1cfc4d2f31 ScriptedDashboard: bugfix for scripted dashboards and collapsed rows, Fixes #965 2014-10-24 11:48:08 +02:00
Mark Bell
08e816a539 Add support for regex based series merging in InfluxDB 2014-10-23 10:35:57 -04:00
Torkel Ödegaard
272cf64aac SinglsStatPanel: more work on sparkline option for single stat panel 2014-10-23 13:44:15 +02:00
Torkel Ödegaard
ed57a4099b Merge branch 'master' into valuepanel 2014-10-23 13:35:54 +02:00
Torkel Ödegaard
79c5d48a3c Timepicker: Fix for Zoom out top menu link still visible when timepicker was disabled, Fixes #963 2014-10-23 12:36:25 +02:00
Adrien Samson
6c70122e55 Add alignToFrom param to summarize 2014-10-22 11:31:41 +02:00
Torkel Ödegaard
6c83699e6f Panel: css fix for panel fullscreen/edit mode for latest chrome 38 and Firefox 33, #954 2014-10-20 12:14:58 -04:00
Torkel Ödegaard
ff254ce08d Help: fix for help modal shortcut, and added missing shortcut, #952 2014-10-20 11:33:07 -04:00
Torkel Ödegaard
8a80ea26b8 Graph: fix for series override controller unit test 2014-10-20 10:10:18 -04:00
Torkel Ödegaard
b85fe62389 Graph: series override dropdown menu select did not work after recent commit that added typeahead to this dropdown 2014-10-20 10:06:34 -04:00
Torkel Ödegaard
31a4d9204c SingleStatPanel: Added graph to single stat panel, #951 2014-10-19 19:36:59 -04:00
Torkel Ödegaard
69fdfd5cb3 Merge branch 'master' into valuepanel 2014-10-19 12:34:01 -04:00
Torkel Ödegaard
16e7980982 Help: added help modal, accessed by shortcut '?', the help modal only contains a list of all shortcuts right now but will be extended in the future, #952 2014-10-19 12:30:41 -04:00
Torkel Ödegaard
e3e08cf8e7 Graph: fix for second y axis tick unit labels wrapping on the next line, Fixes #505 2014-10-18 13:31:15 -04:00
Torkel Ödegaard
cae6626b06 StatsPanel: small change to stats panel, #951 2014-10-17 10:38:15 -04:00
Torkel Ödegaard
956d93e871 StatsPanel: fine tuning colors 2014-10-17 10:36:04 -04:00
Torkel Ödegaard
7c4d1b7b01 StatsPanel: made big values template based 2014-10-16 13:44:52 -04:00
Torkel Ödegaard
9866e0851b Graph: single series tooltip and unit format change fix, Closes #946 2014-10-16 11:21:56 -04:00
Torkel Ödegaard
0a97a2435b StatsPanel: more progress 2014-10-16 11:16:20 -04:00
Torkel Ödegaard
5c80f03eae StatsPanel: small progress on stats panel 2014-10-15 18:16:04 -04:00
Torkel Ödegaard
dd03a4b011 Dashboard: fix for fullscreen mode and small gap sometimes showing the underlying dashboard 2014-10-15 17:37:08 -04:00
Torkel Ödegaard
6cd1bc32fe StatsPanel: more work on stats panel 2014-10-15 17:29:47 -04:00
Torkel Ödegaard
dd0193a9a8 Dashboard: removed wip stats panel from settings.js, accidental commit 2014-10-15 15:48:57 -04:00
Torkel Ödegaard
123faa6f8e Merge branch 'master' into valuepanel 2014-10-15 15:48:09 -04:00
Torkel Ödegaard
f743288ce0 Graph: fix for legends on the side and graph dimensions adaptation 2014-10-15 15:47:59 -04:00
Torkel Ödegaard
a1d764bd26 Merge branch 'master' into valuepanel
Conflicts:
	src/app/components/settings.js
2014-10-15 15:41:58 -04:00
Torkel Ödegaard
61f6bd2c80 Graph: added typehead dropdown menu combination for the series override selection 2014-10-15 14:13:53 -04:00
Torkel Ödegaard
fe620d8e44 Graph: fill below to series override option not automatically adds lines=false, as overrides, if you want lines just for the fill below series just remove the overrides, #940 2014-10-15 11:07:51 -04:00
Torkel Ödegaard
22db28d3e7 Graph: New series style override option 'Fill below to', useful to visualize max & min as shadow for the mean, #940 2014-10-15 10:55:46 -04:00
Torkel Ödegaard
1330488e13 Panel: plugins panels can now reside outsude the app/panels directory, added example plugin panel 2014-10-14 16:57:33 -04:00
Torkel Ödegaard
22297be3cf Merge branch 'master' into valuepanel 2014-10-13 12:30:34 -04:00
Raul Simiciuc
29d7d6994a added reset value 2014-10-13 13:43:25 +01:00
Raul Simiciuc
87e8162a2d fixed identation 2014-10-13 13:07:46 +01:00
Raul Simiciuc
38b71bf386 adding counterMax option to opentsdb 2014-10-13 12:36:26 +01:00
Torkel Ödegaard
920689b80e InfluxDB: series lookup & typeahead is now handled by influxdb list series regex queries, Closes #888 2014-10-12 12:47:50 -04:00
Torkel Ödegaard
e9c7523646 Graphite: added second optional parameter to averageSeriesWithWildcards, Closes #926 2014-10-11 06:24:44 -04:00
Torkel Ödegaard
88bbc720ca Graph: removed console logs 2014-10-11 06:08:33 -04:00
Torkel Ödegaard
1bf1469c80 Merge branch 'master' of github.com:grafana/grafana 2014-10-10 13:37:43 -04:00
Torkel Ödegaard
c74eda20dc Graph: fix for legend values min & max, avg & current when series only has null values, Closes #923 2014-10-10 13:37:33 -04:00
Torkel Ödegaard
db0a5bd537 Graph: fix for legend values min & max, avg & current when series only has null values, Closes #923 2014-10-10 10:45:34 -04:00
Torkel Ödegaard
c6cb01aa3b Graph: series tooltip length check 2014-10-10 10:05:12 -04:00
Torkel Ödegaard
7463f91878 Tech: removed jquery-ui and jquiery-ui-angular-drag-drop, replaced with native drag drop for angular, saved ~50kb in js libs, much cleaner & simpler usage, Also changed some behavior for drag drop, now panels are replaced when droped on (switch places), Closes #920 2014-10-09 20:30:23 -04:00
Mathieu Chataigner
482b31298f fixing unused var 2014-10-09 19:12:56 +02:00
Torkel Ödegaard
ce46ca2f39 Search: fixed bug introduced in recent PR #900, related to comment on #909 2014-10-09 12:31:51 -04:00
Mathieu Chataigner
de00d18a7e adding templating in opentsdb graphs 2014-10-09 16:59:29 +02:00
Torkel Ödegaard
b0cf0c558d Merge branch 'master' into valuepanel 2014-10-09 09:55:49 -04:00
Torkel Ödegaard
920e5c93e1 Graph: tooltip fix for single series tooltip and right floated value being pushed down 2014-10-08 17:00:07 -04:00
Torkel Ödegaard
0aae78c6bd Graph: fix for bars not displaying, caused by change in recent commit, added unit test so it does not happen again 2014-10-08 16:24:58 -04:00
Torkel Ödegaard
7fb048f423 Began work on experimental new stats panel 2014-10-08 11:43:51 -04:00
Torkel Ödegaard
e86207bb28 Fixed default panel title when creating new panel, tweaks to panel height calculation, Closes #910 2014-10-08 06:44:46 -04:00
Torkel Ödegaard
75d60ccb69 Graphite: added perSecond function to the func def list 2014-10-07 06:40:45 -04:00
Torkel Ödegaard
9245cd6aae Merge branch 'inline_styles' of github.com:mikhailov/grafana into mikhailov-inline_styles 2014-10-07 06:32:13 -04:00
Torkel Ödegaard
d8183b60c3 Merge branch 'mikhailov-patch-1' 2014-10-07 06:31:05 -04:00
Torkel Ödegaard
a24272690d Merge branch 'patch-1' of github.com:mikhailov/grafana into mikhailov-patch-1 2014-10-07 06:30:53 -04:00
Torkel Ödegaard
40b088d6a2 Merge pull request #900 from mikhailov/patch-2
Speed search result parsing
2014-10-07 06:19:52 -04:00
Torkel Ödegaard
f1125d64de Updated default max search results to 100, #909 2014-10-07 06:17:33 -04:00
Torkel Ödegaard
0cba818364 Graph: legend and digest phase fix 2014-10-06 12:37:51 -04:00
Torkel Ödegaard
4285c751b3 Graph: fixed value formating for tooltip, need original data. flotcharts copys the data, so changes to value formats func after plot call does not affect the plot.getData() series 2014-10-06 12:17:48 -04:00
Torkel Ödegaard
1b0cddfa72 Graph: Tooltip refactoring for testability 2014-10-06 11:34:51 -04:00
Torkel Ödegaard
231a599f09 legend html markup cleanup 2014-10-06 11:34:51 -04:00
Torkel Ödegaard
7ffc4d388a Merge pull request #895 from ngmlabs/master
second parameter of groupByNode() should be 0 indexed
2014-10-06 09:50:19 -04:00
Torkel Ödegaard
1f24171238 Merge pull request #902 from toni-moreno/fix_multiple_highlight_points
Fix #901: correct positioning for multiple highlighted stacked and staircase points
2014-10-06 09:27:33 -04:00
toni-moreno
24917a6df5 correct positioning for multiple highlighted stacked and staircase points, fix to #901 2014-10-06 06:22:55 +02:00
Anatoly Mikhailov
67fde17209 Speed searching up a bit
Reuse ElasticSearch hits length via hits.total and cache it rahter than calculation length every iteration.
2014-10-05 23:24:41 +01:00
Anatoly Mikhailov
6abad666db Debounce/Throttling Searching event
New backend call to each keydown may hit the back-end performance.
500ms is reasonable delay to avoid too many requests while user is
typing a search query
2014-10-05 20:54:53 +01:00
mikhailov
2d3f396571 Extract repetitive CSS inline styles
Inline styles slow down rendering proportionally to containers number
2014-10-05 18:30:22 +01:00
George Negoita
51bcbdac75 second parameter of groupByNode() is 0 indexed 2014-10-04 02:33:36 +03:00
Torkel Ödegaard
e63889d5c4 Css fix for metrics tab, help boxes did extend page height so could not scroll if they extended below screen, #891 2014-10-03 07:31:27 +02:00
Torkel Ödegaard
fe6a7c58bf Fixed issue casued by recent angular upgrade and stateful filter, casued template vars in graph titles no update properly, angular was not updated in 1.8.1 so this does affect any released version, Closes #892 2014-10-02 21:48:00 +02:00
Torkel Ödegaard
dac3cb15c4 Merge branch 'v1.8.x' 2014-10-02 12:17:37 +02:00
Torkel Ödegaard
ca654ccaf7 fixed stupid unit test mistake 2014-10-02 12:17:10 +02:00
Torkel Ödegaard
30512b7032 Merge fix from 'v1.8.x' branch
Conflicts:
	CHANGELOG.md
2014-10-02 12:15:21 +02:00
Torkel Ödegaard
bc8fd62cff Dashboard: fix for dashboard schema upgrade during load, ensure that annotation and templating object as a list array, Closes #890 2014-10-02 12:13:18 +02:00
Torkel Ödegaard
a9a51ee3c6 Fix to new checkboxes in features toggles tab 2014-10-02 11:54:06 +02:00
Torkel Ödegaard
2d2da7c881 Small tweak to shared tooltip edit option in Display styles tab 2014-10-02 10:55:08 +02:00
Torkel Ödegaard
d22d8c4905 Added grafana logo to repo instead of fetching it from grafana.org, Fixes #881 2014-10-02 10:24:18 +02:00
Torkel Ödegaard
bcdc8eafa6 Updated gitter badge 2014-10-02 10:13:23 +02:00
Torkel Ödegaard
9cf6ace979 Updated gitter badge 2014-10-02 10:09:16 +02:00
Torkel Ödegaard
eef063cec2 Merge pull request #889 from gitter-badger/gitter-badge
Add a Gitter chat badge to README.md
2014-10-02 10:05:01 +02:00
The Gitter Badger
8b9bdf9054 Added Gitter badge 2014-10-02 07:59:43 +00:00
Torkel Ödegaard
1c7b898b01 Dashboard: show error when importing dashboard json file with broken/incorrect json syntax, Closes #878 2014-10-01 13:56:57 +02:00
Torkel Ödegaard
7ad18da08e update to test helper 2014-10-01 13:49:36 +02:00
Torkel Ödegaard
5530915b49 Updated AngularJS from 1.3-beta-17 to 1.3-RC3 2014-10-01 13:44:45 +02:00
Torkel Ödegaard
77f380c94b Graph: shared tooltip improvements, info tooltip when point counts are not the same, #850 2014-10-01 10:30:24 +02:00
Torkel Ödegaard
c79ab84fdf More fixes related to shared crosshair #880 2014-10-01 09:42:42 +02:00
Torkel Ödegaard
d77448d84e Fixed unit tests for grafanaGraph component 2014-10-01 09:10:49 +02:00
Torkel Ödegaard
8fc5a2785f Refactoring and fixes for PR #880 2014-10-01 09:08:11 +02:00
Torkel Ödegaard
27da2b026f Merge branch 'add_shared_croshair_to_dashboards' of github.com:toni-moreno/grafana into toni-moreno-add_shared_croshair_to_dashboards 2014-10-01 08:00:20 +02:00
Torkel Ödegaard
2e9cc2a74e Fix for SharePanelCtrl-specs and travis-ci timezone issue 2014-10-01 07:58:31 +02:00
toni-moreno
ffd370176d fix tooltip test expecting for stacked data 2014-10-01 07:15:20 +02:00
toni-moreno
44f2a375f6 Added shared crosshair to dashboards to track better all graphs, and little Fix for #884 2014-10-01 07:03:59 +02:00
Torkel Ödegaard
282c834d9f updated package.json 2014-09-30 17:58:02 +02:00
Torkel Ödegaard
4a6ff9e2aa Merge branch 'v1.8.x' 2014-09-30 17:57:30 +02:00
Torkel Ödegaard
b250d10320 Trying to fox unit tests for sharePanelCtrl 2014-09-30 17:55:07 +02:00
Torkel Ödegaard
f63706d118 Small update to share panel 2014-09-30 15:31:18 +02:00
Torkel Ödegaard
c41aa64719 ShareModal: Added template variables to share url, and an option for it, #864 2014-09-30 15:19:48 +02:00
Torkel Ödegaard
285d246c65 PanelMenu: fixed interpolate template vars for new panel title/menu 2014-09-30 14:48:36 +02:00
Torkel Ödegaard
2c85205259 SharePanelModal: working on share feature, #864 2014-09-30 14:42:59 +02:00
Torkel Ödegaard
2d866b9298 Updated changelog with the new panel menu, Closes #770 2014-09-30 10:49:59 +02:00
Torkel Ödegaard
7a7629acf7 Panel-menu: small tweeks to css for cursor types 2014-09-30 10:43:13 +02:00
Torkel Ödegaard
debf820037 New checkbox change is complete 2014-09-30 10:27:56 +02:00
Torkel Ödegaard
f908ae8c40 added checkbox image for white theme 2014-09-30 09:50:01 +02:00
Torkel Ödegaard
022cbdda31 Merge branch 'master' into panel_edit_menu_poc 2014-09-30 09:25:19 +02:00
Torkel Ödegaard
ae2523aa59 Merge branch 'v1.8.x' 2014-09-30 09:23:07 +02:00
Torkel Ödegaard
06f382c454 Merge branch 'master' of github.com:grafana/grafana 2014-09-29 16:59:54 +02:00
Torkel Ödegaard
5f164d99ac Updated changelog with merged PR #850, shared multi series graph tooltip & crosshair 2014-09-29 16:59:17 +02:00
Torkel Ödegaard
2473ae3b47 Graph: shared multi series tooltip, refactoring PR #850 2014-09-29 16:57:05 +02:00
Torkel Ödegaard
3fb457ccd1 Merge branch 'master' into toni-moreno-add_shared_tooltips_to_graphs 2014-09-29 14:25:25 +02:00
toni-moreno
51333c9eda improved tooltip styles, add multiple highlight points, and changed highligth size when graph plotted without points. 2014-09-29 12:48:08 +02:00
Torkel Ödegaard
1aaf3961ff Merge pull request #867 from tobym/patch-1
Fix typo
2014-09-29 12:43:48 +02:00
Torkel Ödegaard
af4f3f62e9 Merged yaxis_precision branch, #877, updated changelog 2014-09-29 12:38:52 +02:00
Torkel Ödegaard
cc31a12b8c Smart decimal precision when using scaled unit format, Closes #877 2014-09-29 12:29:53 +02:00
Torkel Ödegaard
bc9989f9be replaced checkbox options with the new editor-opt-bool directive 2014-09-27 10:47:48 +02:00
toni-moreno
7f33bec71c fixing the previous fix, now working fine 2014-09-25 15:45:18 +02:00
toni-moreno
3ea94c3484 little fix when searching x index time 2014-09-25 06:35:49 +02:00
Toby Matejovsky
68adaea128 Fix typo 2014-09-24 19:01:23 -04:00
Torkel Ödegaard
4997068a0d Added directive element for the new checkboxes 2014-09-24 19:10:00 +02:00
Torkel Ödegaard
4c59ec815e work on share panel view, and better look for checkbox 2014-09-24 18:55:55 +02:00
Torkel Ödegaard
440ea666d9 more work on share panel, #864 2014-09-24 17:15:58 +02:00
Torkel Ödegaard
6f1a6d5a56 Changed name of emitAppEvent to just appEvent 2014-09-24 16:26:39 +02:00
Torkel Ödegaard
69e80fd11c working on share feature, and refactorings 2014-09-24 16:20:55 +02:00
Torkel Ödegaard
bef8cc2d70 Merge branch 'master' into panel_edit_menu_poc 2014-09-24 14:00:55 +02:00
Torkel Ödegaard
743c95d0f9 small fix to panel menu positioning 2014-09-24 12:45:03 +02:00
Torkel Ödegaard
a08cb52ad9 tweaks to new panel menu, now how top area of panel is clickable, and if no title a 5px high area can be clicked, this will enable panels without title which is something I have wanted 2014-09-24 12:41:55 +02:00
Torkel Ödegaard
b9604bf3bc Merge branch 'master' into panel_edit_menu_poc 2014-09-24 12:23:04 +02:00
Torkel Ödegaard
622c1a1dad small tweak to new panel edit menu 2014-09-24 11:58:02 +02:00
Torkel Ödegaard
79fea549ef Merge branch 'master' into panel_edit_menu_poc 2014-09-24 11:41:25 +02:00
Torkel Ödegaard
d6f1c379c0 fixed text editor & scope issue 2014-09-23 22:30:01 +02:00
Torkel Ödegaard
6794260e3f Lots of progress on new panel edit menu, very tricky to get this right but think I am getting close to something that is good and will work long term 2014-09-23 22:10:10 +02:00
Torkel Ödegaard
1be840f19d More wort on panel edit menu 2014-09-23 16:11:31 +02:00
toni-moreno
f59bb6461a added shared tooltips to graphs 2014-09-23 13:51:59 +02:00
Torkel Ödegaard
bd3bae3af0 another attempt at improving panel edit menu 2014-09-23 10:52:31 +02:00
Torkel Ödegaard
139791b0d8 Merge branch 'master' into panel_edit_menu_poc 2014-09-22 18:11:52 +02:00
Torkel Ödegaard
6003fee33f Merge branch 'master' into panel_edit_menu_poc 2014-09-20 13:37:02 +02:00
Torkel Ödegaard
e78c48620f Trying to improve yaxis precision 2014-09-19 13:24:15 +02:00
Torkel Ödegaard
a6fa01f89b POC of panel edit menu 2014-09-06 14:05:07 +02:00
159 changed files with 11988 additions and 11236 deletions

21
.jsfmtrc Normal file
View File

@@ -0,0 +1,21 @@
{
"preset" : "default",
"lineBreak" : {
"before" : {
"VariableDeclarationWithoutInit" : 0,
},
"after": {
"AssignmentOperator": -1,
"ArgumentListArrayExpression": ">=1"
}
},
"whiteSpace" : {
"before" : {
},
"after" : {
}
}
}

View File

@@ -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**

View File

@@ -1,4 +1,4 @@
[Grafana](http://grafana.org) [![Build Status](https://api.travis-ci.org/grafana/grafana.svg)](https://travis-ci.org/grafana/grafana) [![Coverage Status](https://coveralls.io/repos/grafana/grafana/badge.png)](https://coveralls.io/r/grafana/grafana)
[Grafana](http://grafana.org) [![Build Status](https://api.travis-ci.org/grafana/grafana.svg)](https://travis-ci.org/grafana/grafana) [![Coverage Status](https://coveralls.io/repos/grafana/grafana/badge.png)](https://coveralls.io/r/grafana/grafana) [![Gitter](https://badges.gitter.im/Join Chat.svg)](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) |

View File

@@ -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"
}

View File

@@ -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"

View File

@@ -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',

View File

@@ -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);

View 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;
});

View File

@@ -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'],

View File

@@ -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) {

View File

@@ -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;
});

View File

@@ -15,5 +15,6 @@ define([
'./opentsdbTargetCtrl',
'./annotationsEditorCtrl',
'./templateEditorCtrl',
'./sharePanelCtrl',
'./jsonEditorCtrl',
], function () {});

View File

@@ -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');
};
});

View File

@@ -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() {

View File

@@ -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() {

View File

@@ -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);

View File

@@ -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);

View File

@@ -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();
});
};
});

View File

@@ -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);
};

View 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();
});
});

View File

@@ -1,9 +1,8 @@
define([
'angular',
'app',
'lodash'
],
function (angular, app, _) {
function (angular, _) {
'use strict';
var module = angular.module('grafana.controllers');

View File

@@ -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) {

View File

@@ -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": {

View File

@@ -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
}
}
]
});

View File

@@ -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;

View 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);
}
});
});
};

View File

@@ -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: [

View File

@@ -65,7 +65,6 @@
"avg": false
},
"percentage": false,
"zerofill": true,
"nullPointMode": "connected",
"steppedLine": false,
"tooltip": {

View File

@@ -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);
});

View File

@@ -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 () {});

View File

@@ -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 });
});
});
}

View File

@@ -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);
});

View 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);
}
};
});
});

View File

@@ -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);

View File

@@ -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;
}

View 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);
}
};
});
});

View File

@@ -32,7 +32,11 @@ function (angular) {
};
input.spectrum(options);
scope.$on('$destroy', function() {
input.spectrum('destroy');
});
}
};
});
});
});

View File

@@ -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
View File

@@ -0,0 +1,3 @@
define([
'./panellinkeditor/module',
], function () {});

View 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;
};
});
});

View 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>

View 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);
};
});
});

View File

@@ -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;
});
});

View File

@@ -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>

View File

@@ -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({

View 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;
});

View File

@@ -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>

View 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));
}
}
}
};
});
});

View 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();">&nbsp;</i>
</div>
</div>

View File

@@ -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>

View File

@@ -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);

View File

@@ -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]);

View File

@@ -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>

View 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>

View 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>

View 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();
});
});

View 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);
});
}
};
});
});

View File

@@ -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();

View File

@@ -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>

View File

@@ -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

View File

@@ -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>

View 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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View 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>

View File

@@ -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()">

View File

@@ -14,4 +14,5 @@
</ul>
</div>
<div class="clearfix"></div>
</div>

View File

@@ -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>

View File

@@ -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>

View File

@@ -12,3 +12,7 @@
</div>
</div>
</div>
<panel-link-editor panel="panel"></panel-link-editor>

View File

@@ -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>

View File

@@ -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">

View File

@@ -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>

View 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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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;
}

View File

@@ -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;
});
};

View File

@@ -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');
});
};
});
});

View File

@@ -1,14 +1,15 @@
define([
'./alertSrv',
'./utilSrv',
'./datasourceSrv',
'./timeSrv',
'./templateSrv',
'./templateValuesSrv',
'./panelSrv',
'./timer',
'./panelMove',
'./keyboardManager',
'./annotationsSrv',
'./popoverSrv',
'./playlistSrv',
'./unsavedChangesSrv',
'./dashboard/dashboardKeyBindings',

View File

@@ -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>";

View File

@@ -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 });

View File

@@ -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 = [];

View File

@@ -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) {

View File

@@ -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;
});
};

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -129,6 +129,7 @@ define([
i === 63 || // ?
i === 37 || // %
i === 35 || // #
i === 61 || // =
i >= 97 && i <= 122; // a-z
}

View File

@@ -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]
};
}

View File

@@ -18,7 +18,7 @@ function () {
var query = 'select ';
var seriesName = target.series;
if(!seriesName.match('^/.*/')) {
if(!seriesName.match('^/.*/') && !seriesName.match(/^merge\(.*\)/)) {
seriesName = '"' + seriesName+ '"';
}

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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;
}]);
});
});

View File

@@ -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;
}

View File

@@ -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;
}
};
});
});

View File

@@ -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);

View 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);
});
};
});
});

View File

@@ -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