Compare commits

...

779 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
05cb97819c Updated changelog 2014-09-30 18:21:19 +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
ce972d4f19 Graph: css & scroll fix for dropdown menus in graph edit mode, Closes #855 2014-09-30 17:56:43 +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
bf361d2b02 Updated package.json version and latest.json, preparation for 1.8.1 release 2014-09-30 09:03:14 +02:00
Torkel Ödegaard
64f3303711 InfluxDB: save dashboard issue, another fix for #859 2014-09-30 09:02:25 +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
eaa899e9cf Small fix to fullscreen mode where scrolling would scroll the background dashboard & page header 2014-09-24 14:00:44 +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
978a345ad8 Dashboard: When deleting dashboard show dashboard title in confirmation popup, Closes #860 2014-09-24 12:20:27 +02:00
Torkel Ödegaard
d5ffe6acef White theme: Fixes for hidden series legend text and disabled annotations color, Closes #852 2014-09-24 12:14:20 +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
bce6e75cfa InfluxDB: Fix for bug when saving dashboard where title is the same as slugified url id, Fixes #859 2014-09-24 11:35:08 +02:00
Torkel Ödegaard
34f36fff5c small fix for graphite-web import 2014-09-24 11:17:34 +02:00
Torkel Ödegaard
f4e24038fe Import: Fixes to import from json file and import from graphite. Issues was lingering state from previous dashboard. Closes #840, Closes #853 2014-09-24 10:51:20 +02:00
Torkel Ödegaard
81747e1623 Annotations: Fix for annotations not reloaded when switching between 2 dashboards with annotations, Fixes #851 2014-09-24 09:03:04 +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
0fbace7285 Row: fix for row editor and scroll pos, Fixes #846 2014-09-23 08:32:04 +02:00
Torkel Ödegaard
af8fec941c Graph: Fix for series draw order not being the same after hiding/unhiding series, Fixes #847 2014-09-23 08:18:59 +02:00
Torkel Ödegaard
139791b0d8 Merge branch 'master' into panel_edit_menu_poc 2014-09-22 18:11:52 +02:00
Torkel Ödegaard
7fe76d32d0 updated latest.json 2014-09-22 13:06:52 +02:00
Torkel Ödegaard
352ad3385a Updated changelog and package.json to new version 1.8.0 2014-09-22 12:59:21 +02:00
Torkel Ödegaard
010baad532 Dashboard: fixed init of editable setting, #837 2014-09-22 12:54:02 +02:00
Torkel Ödegaard
af52b20c4a Merge branch 'master' of github.com:grafana/grafana 2014-09-21 08:21:31 +02:00
Torkel Ödegaard
e82d171041 Dashboard: when opening search or dashboard settings, click the icon again will now hide the view, Closes #836 2014-09-21 08:19:41 +02:00
Torkel Ödegaard
4f261389db changed placement of color selector popup 2014-09-20 16:55:02 +02:00
Torkel Ödegaard
6003fee33f Merge branch 'master' into panel_edit_menu_poc 2014-09-20 13:37:02 +02:00
Torkel Ödegaard
b56c3eb035 Changed color for warning alert 2014-09-20 13:32:26 +02:00
Torkel Ödegaard
a19a2c70ab Fixed spelling in config.sample.js 2014-09-20 08:33:16 +02:00
Torkel Ödegaard
40a491a80b Annotations: Elasticsearch annotation and field mapping fixes, small changes for PR #830 2014-09-20 08:30:59 +02:00
Gregory Becker
06ec91c899 Give maximum width & height constraint to tooltip boxes
Extreme values that go beyond the screen resolution are very likely to be misplaced. This is a simple workaround. A better solution would be to improve the code placing the tooltip and make it handle tooltips containing more content than they can safely display.
2014-09-19 14:16:53 +01:00
Gregory Becker
10f9022d7c Support fields from nested objects pulled from Elasticsearch 2014-09-19 14:11:09 +01:00
Torkel Ödegaard
e78c48620f Trying to improve yaxis precision 2014-09-19 13:24:15 +02:00
Torkel Ödegaard
563dd898c1 Graph: fix for downscaling y-axis format, never downscale when value is zero, Fixes #826 2014-09-19 12:16:00 +02:00
Torkel Ödegaard
a97bcc3ca7 Elasticsearch: fix for issue when saving dashboard with title equal to slugified url, would cause the backward compatible fix to delete it, Closes #828 2014-09-19 10:37:43 +02:00
Torkel Ödegaard
fa31fc046e Merge pull request #823 from beevee/elasticsearch_basic_auth
enable withCredentials in elasticsearch basic auth
2014-09-18 15:54:58 +02:00
Torkel Ödegaard
a68a179c1e Small improvements to dashboard alerts, less intrusive, do not push down page anymore, Closes #822 2014-09-18 15:07:49 +02:00
Alexey Kirpichnikov
77b0d36b55 enable withCredentials in elasticsearch basic auth 2014-09-18 17:50:59 +06:00
Torkel Ödegaard
f2a6fc4d5a Merge pull request #808 from lento/multiple-stacks
add override options to allow multiple stacks
2014-09-18 09:36:48 +02:00
Torkel Ödegaard
c9501d1119 Merge pull request #820 from jozog/master
Handle 'group' graphite method
2014-09-18 09:35:37 +02:00
Torkel Ödegaard
323ff3d491 Merge pull request #821 from linkslice/master
Update playlist.html
2014-09-18 09:35:13 +02:00
Bryan Irvine
98b3126e32 Update playlist.html 2014-09-17 11:04:06 -07:00
Julien Ozog
88ea524f44 Handle 'group' graphite method 2014-09-17 17:12:43 +02:00
Torkel Ödegaard
a0ab9113fc Graph: added percent y-axis format, Closes #818 2014-09-17 15:39:45 +02:00
Torkel Ödegaard
6dae8f44b9 Small fix for favicon 2014-09-17 13:58:32 +02:00
Torkel Ödegaard
bba3f3000f Search: remove dashboard from search result after dashboard deletion, Closes #753 2014-09-17 13:00:35 +02:00
Torkel Ödegaard
d40e21a7e0 Chrome: Fix for display issue in chrome beta & chrome canary when entering edit mode, Closes #795 2014-09-17 09:29:51 +02:00
Torkel Ödegaard
94d2ae2a6a Merge branch 'master' of github.com:grafana/grafana 2014-09-17 09:06:37 +02:00
Torkel Ödegaard
3099198e47 Fixed default dashboard grafana logo when using https 2014-09-17 09:03:34 +02:00
Torkel Ödegaard
48e9b5f4be Merge pull request #810 from marcusoftnet/“AddingFavIcon”
Added a favicon. This will resolve issue #796
2014-09-16 13:38:32 +02:00
Torkel Ödegaard
430e2e439b Small fix to schemaUpgrade, Closes #807 2014-09-16 13:32:27 +02:00
Torkel Ödegaard
81a21c03b2 Merge branch 'favicon' 2014-09-16 13:20:42 +02:00
Torkel Odegaard
064a97e734 added favicon, Closes #796 2014-09-16 13:19:52 +02:00
Marcus Hammarberg
5e9ed95684 Added a favicon. This will resolve issue #796 2014-09-16 17:45:43 +07:00
Torkel Ödegaard
017d5617a5 Merge pull request #809 from alxrem/master
fix typos
2014-09-16 11:12:48 +02:00
Alexey Remizov
26bb2e0193 fix typos 2014-09-16 13:08:28 +04:00
Torkel Odegaard
ff91430fcc added favicons 2014-09-16 08:01:18 +02:00
Lorenzo Pierfederici
32a41a8422 add override options to allow multiple stacks 2014-09-15 18:12:20 -07:00
Torkel Ödegaard
8b93e20a0b Merge pull request #806 from starshayayord/master
Disable annoying Google Translate plugin
2014-09-15 19:15:57 +02:00
starshayayord
92bec31ccb Update index.html
disable google translate plugin
2014-09-15 18:51:58 +06:00
Torkel Ödegaard
96a0d0aefa fixed changelog 2014-09-13 16:38:01 +02:00
Torkel Ödegaard
15f2b2cf9a Annotations: fixed InfluxDB annotation query, added unit test for annotation query, Fixes #802 2014-09-13 16:19:33 +02:00
Torkel Ödegaard
bf9eaea334 Updated lastest.json 2014-09-12 13:19:17 +02:00
Torkel Ödegaard
7b45a2ec51 Small fix to scripted async dashboard example 2014-09-12 13:15:06 +02:00
Torkel Ödegaard
48eb2083f2 Fix for graphite query letter assignment 2014-09-11 17:25:59 +02:00
Torkel Ödegaard
2c6ea276c1 Fixed small bug in graphite target controller when having variable for single parameter function 2014-09-11 17:19:39 +02:00
Torkel Ödegaard
5a3db0505f Small fix to elasticsearch save error handling 2014-09-11 16:00:59 +02:00
Torkel Ödegaard
6ca73f6df0 Do not render graph when width is zero, avoids plot errors 2014-09-11 14:25:20 +02:00
Torkel Ödegaard
762dab618a Small change to datasourceSrv, if datasource is not found, return default datasource 2014-09-11 14:07:27 +02:00
Torkel Ödegaard
a65c61442e minifix for spacing of question sign tooltips when html is minified 2014-09-11 14:01:37 +02:00
Torkel Ödegaard
4883b2a296 Fixed issue with using template variables in panel titles, and text panel, when selecting All option in variable 2014-09-11 13:54:59 +02:00
Torkel Ödegaard
b1abe72ab6 small update to text panel editor 2014-09-11 11:34:32 +02:00
Torkel Ödegaard
a640d55297 Added informatio & help blocks to graphite metric editor 2014-09-11 09:27:49 +02:00
Torkel Ödegaard
99009a11ed Graphite: added divideSeries function, #177 2014-09-11 08:03:00 +02:00
Torkel Ödegaard
2150bbf191 removed console.log from templateValuesSrv 2014-09-10 13:35:54 +02:00
Torkel Ödegaard
6f8cb743b5 Fixed default welcome to grafana dashboard, rows were not marked as editable 2014-09-10 11:53:58 +02:00
Torkel Ödegaard
682d2a1675 Dashboard: time range can now be read from URL parameters, will override dashboard saved time range, Closes #787, Closes #761 2014-09-10 10:46:04 +02:00
Torkel Ödegaard
6022784e42 InfluxDB: support for basic authorization (PR #782) 2014-09-10 09:08:25 +02:00
Torkel Ödegaard
4d4478d969 Added PR #785 to changelog 2014-09-10 09:07:14 +02:00
Ed Dawley
c0935c84ee Fixing some grunt errors with the elasticsearch grammar changes 2014-09-09 15:54:05 -04:00
Ed Dawley
17e040abe4 The elasticsearch datasource will now better handle language specifics when making the search partial (ie for search as you type). This means the search field will now support significantly more complex searches such as:
title:foo AND title:bar OR baz
    title:mysql AND [3306 TO 3308]
    web~
    title:foo AND -bar
2014-09-09 15:17:13 -04:00
Ed Dawley
6152a5e3c2 Adding in global search id counter so that async search responses can be discarded if a newer search is being processed. This prevents older search results from clobbering a newer search that happened to complete faster. 2014-09-09 15:16:47 -04:00
Daniel Shir
4558486cbd Added basic authorization for influxdb if needed 2014-09-09 15:23:40 +03:00
Torkel Ödegaard
3df592c702 Dashboard: elasticsearch dashboard storage now slugifies urls, #781 2014-09-09 13:42:46 +02:00
Torkel Ödegaard
05fabc73c2 Dashboard: elasticsearch dashboard storage now slugifies urls, #781 2014-09-09 13:04:07 +02:00
Torkel Ödegaard
e949761107 Made unsaved changes service ignore template variable options and selection changes 2014-09-09 11:40:37 +02:00
Torkel Ödegaard
4311a20c5f Fixed issue with editing text panel, removed a function from dashboard controller that I though was not used, turned out it was 2014-09-09 11:35:29 +02:00
Torkel Ödegaard
1440d1a147 Fix when changing templated vars, should update child template vars All value 2014-09-09 11:30:00 +02:00
Torkel Ödegaard
4b170ca9a3 Corrected error handling for influxdb datasource when loading/deleting dashboard 2014-09-09 11:14:57 +02:00
Torkel Ödegaard
d6e844c67c Fixes for opentsdb, (broken during 1.8 development) 2014-09-09 10:29:59 +02:00
Torkel Ödegaard
71a307270a Fixed text color in json text area for white theme, #735 2014-09-09 09:16:00 +02:00
Torkel Ödegaard
0f88b470e8 Fix for elasticsearch annotations when timestamp is a field and not in source, Fixes #777 2014-09-09 08:50:01 +02:00
Torkel Ödegaard
4798aa4789 Fixes to requirejs build task to include all modules, Fixes #779 2014-09-09 08:24:04 +02:00
Torkel Ödegaard
a9d96ccc8c Fixed ids for panels in default.json welcome to grafana dashboard 2014-09-08 18:03:10 +02:00
Torkel Ödegaard
e0c9ddbfba Worked on variable initilization and sync to from url, #772 2014-09-08 11:03:14 +02:00
Torkel Ödegaard
bbc5dae1d2 Working on better handling of variables and url init and state 2014-09-07 11:55:26 +02:00
Torkel Ödegaard
9e7c55728f small cleanup of unused code 2014-09-06 18:05:54 +02:00
Torkel Ödegaard
a6fa01f89b POC of panel edit menu 2014-09-06 14:05:07 +02:00
Torkel Ödegaard
6c71754e51 Changed template variable typeahead/autocomplete list limit from 10 to 1000, Fixes #767, Fixes #768 2014-09-06 10:12:28 +02:00
Torkel Ödegaard
e729b3734d Fix for selecting template variable value from typeahead using enter key, Closes #765 2014-09-06 10:05:58 +02:00
Torkel Ödegaard
6337c77532 Rename of edit label on graph panel 2014-09-06 09:49:21 +02:00
Torkel Ödegaard
fb08b71884 Small fix to graphite target controller to still revert to text box for expressions with multiple series that do not use a series reference 2014-09-05 17:44:54 +02:00
Torkel Ödegaard
d749549135 Templating: Full support for InfluxDB, filter by part of series names, extract series substrings, nested queries, multipe where clauses! Closes #613 2014-09-05 15:46:29 +02:00
Torkel Ödegaard
58a2ab4fbd Templating: Can now use template variables in panel titles, Closes #312 2014-09-05 15:17:19 +02:00
Torkel Ödegaard
cc96cfe0c7 Templating: Ability to use template variables for function parameters via custom variable type, can be used as parameter for movingAverage or scaleToSeconds for example, Closes #262 2014-09-05 14:03:36 +02:00
Torkel Ödegaard
656b3e53a8 Templating: Interval variable type for time intervals summarize/group by parameter, included auto option, and auto step counts option.
Closes #243
2014-09-05 13:31:34 +02:00
Torkel Ödegaard
4e5dcafa1b working on auto interval template variable support 2014-09-05 12:07:48 +02:00
Torkel Ödegaard
afc8380f23 Work on getting template variables to work well with functions that take integers, #262 2014-09-05 09:11:50 +02:00
Torkel Ödegaard
0319051891 Extend template variable syntax to include , Closes #760 2014-09-05 08:26:50 +02:00
Torkel Ödegaard
4805a3bc23 Merge remote-tracking branch 'oss/template_variable_syntax' 2014-09-05 07:06:00 +02:00
Torkel Ödegaard
dc63f0ddd0 Fixed so white theme looks good with new search and editor panes, Closes #735, and other small fixes and polish 2014-09-05 07:02:59 +02:00
Torkel Ödegaard
6ff188e4d9 test for adding syntax in addition to [[variable]] 2014-09-04 17:34:36 +02:00
Torkel Ödegaard
44edaad19d Fixed scroll issue with firefox, Fixes #754 2014-09-04 15:25:59 +02:00
Torkel Ödegaard
5304221e46 fixed spelling in changelog concerning InfluxDB breaking changes 2014-09-04 15:09:07 +02:00
Torkel Ödegaard
c6b1fe5349 updated change log with info about InfluxDB breaking changes, and fixes for stacked series and missing values, Fixes #534, Closes #743, Fixes #673,Fixes #674, Closes #756 2014-09-04 14:57:50 +02:00
Torkel Ödegaard
80574334cf Changed variable replacement works for InfluxDB, now , and 2014-09-04 14:41:27 +02:00
Torkel Ödegaard
dd4eaa0758 fixes to target segment markup 2014-09-04 14:17:05 +02:00
Torkel Ödegaard
93550e9ea5 Work on fixing stacking issues with InfluxdB, added fill(0) and fill(null) option to InfluxDB query editor, also a panel wide group by time option that supports setting a low limit, Fixes issues #673, #674, #534, #743 2014-09-04 14:08:31 +02:00
Torkel Ödegaard
3157fc651d Fixed dashboard import view, did not hide search results 2014-09-04 09:58:08 +02:00
Torkel Ödegaard
dd4f27e3fa Fixed issue where a metric request error would set panel error flag, which would cause unsaved changes service to detect change, and prompt the unsaved changes warning. The panel error state is now moved to the panelMeta object that is not part of the dashboard / panel model, Closes #745 2014-09-04 09:44:42 +02:00
Torkel Ödegaard
455e80513b Fixes delete link in search result, broken after recent changes to search results, Closes #749 2014-09-04 08:56:50 +02:00
Torkel Ödegaard
65af872ec6 Small fix to graphiteDatasource and sending cacheTimeout undefined, reintroduced this bug yesterday, added unit test so it should not appear again 2014-09-03 16:48:48 +02:00
Torkel Ödegaard
917cd35005 Dashboard: View dashboard json, edit/update any panel using json editor, makes it possible to quickly copy a graph from one dashboard to another.
Closes #304
2014-09-03 11:15:44 +02:00
Torkel Ödegaard
b989183fce Removed collapse row button, added to row menu, made small change to text pannel to allow smaller height, Closes #727 2014-09-03 09:52:36 +02:00
Torkel Ödegaard
0845d5d451 Removed configure row button when row is collapsed 2014-09-03 09:33:32 +02:00
Torkel Ödegaard
f002ef105e Small fix to influxdb query builder, should update raw query after building query 2014-09-03 09:20:39 +02:00
Torkel Ödegaard
3d202c2ef9 Fixed small issue where dashboard search did not work after loading dashboard and it does not exist, now shows a valid dashboard 2014-09-03 09:12:59 +02:00
Torkel Ödegaard
953eec7326 Fixes and polish for the graphite query editor, #117 2014-09-03 08:53:08 +02:00
Torkel Ödegaard
c3956b4d6f fixed jshint warning 2014-09-03 07:58:00 +02:00
Torkel Ödegaard
d2421bb216 Added better match when using graphite function autocomplete, hit tab key and the first match will be used, you no longer need to use the down arrow to select the typeahead match you want, just hit tab key 2014-09-03 07:41:43 +02:00
Torkel Ödegaard
afdc19ce9d Updated sumSeries, averageSeries to support other series reference arguments, #117 2014-09-03 07:35:14 +02:00
Torkel Ödegaard
4a9380cc95 added limit checks to up/down arrow key selection of search results 2014-09-03 07:24:28 +02:00
Torkel Ödegaard
9f60745e57 Graphite: Graphite query builder can now handle functions that multiple series as arguments! #117 2014-09-02 20:59:54 +02:00
Torkel Ödegaard
666d640216 Graphite: Metric node/segment selection is now a textbox with autocomplete dropdown, allow for custom glob expression for single node segment without enter text editor mode, Closes #281 2014-09-02 12:55:45 +02:00
Torkel Ödegaard
cb479d737b Graphite: Fix for nonNegativeDerivative function, now possible to not include optional first parameter maxValue, Closes #702 2014-09-02 07:58:29 +02:00
Torkel Ödegaard
4ee455fad2 Fixed failing influxdb query builder unit test 2014-09-02 07:21:41 +02:00
Torkel Ödegaard
2da04e72f5 More progress on influxdb query editor, templating, refactoring, unit tests, #740, #507, #586 2014-09-02 07:05:36 +02:00
Torkel Ödegaard
141ea7ba91 More work InfluxDB templated queries and required changes to editor and datasource 2014-09-01 16:55:54 +02:00
Torkel Ödegaard
5ae0239c26 small UI changes to InfluxDB query editor, made each query two lines to give more space to series name 2014-09-01 13:40:34 +02:00
Torkel Ödegaard
2dc4434a49 Progress on influxdb and templated queries/variables, #613 2014-09-01 11:13:18 +02:00
Torkel Ödegaard
39c068bd53 Graph: Fix for tooltip current value decimal precision when 'none' axis format was selected, Closes #733 2014-08-29 12:54:42 +02:00
Torkel Ödegaard
b26dfd8246 Updated changelog with progress on filtering/template overhaul, and UI change 2014-08-29 12:45:58 +02:00
Torkel Ödegaard
3185db9609 new template system is starting to work 100%, not all features are in, like regex selection, influxdb support, and other stuff 2014-08-29 12:34:04 +02:00
Torkel Ödegaard
61a618e473 fixes to templating 2014-08-29 10:17:00 +02:00
Torkel Ödegaard
c4a4ecfc81 Merge branch 'master' into filtering_overhaul
Conflicts:
	src/app/controllers/dashboardNavCtrl.js
2014-08-29 10:04:30 +02:00
Torkel Ödegaard
f9b0ce0f75 Fixes to annotations editor 2014-08-29 09:59:18 +02:00
Torkel Ödegaard
20607c00b1 Fixed playlist pane not showing after modal removal change 2014-08-29 08:44:38 +02:00
Torkel Ödegaard
12e2bf2f85 Trying to restore broken features and some polishing 2014-08-28 16:44:16 +02:00
Torkel Ödegaard
685a2fec6c trying to get new templating / filtering to work 2014-08-28 16:03:13 +02:00
Torkel Ödegaard
f9cd4a4470 More work on templating, added an embry of a dashboard json edit view 2014-08-28 12:44:01 +02:00
Torkel Ödegaard
b761aad903 More work on filter/templating overhaul 2014-08-27 21:47:41 +02:00
Torkel Ödegaard
9ee4fcb36c continued large refactoring of filterSrv, timeSrv and templating 2014-08-27 17:58:49 +02:00
Torkel Ödegaard
1929490deb Renamed filterSrv to timeSrv and made it a service again 2014-08-27 16:29:48 +02:00
Torkel Ödegaard
bb3b31829f Progress on template editor & new templating features 2014-08-27 15:54:30 +02:00
Torkel Ödegaard
e0a58dd1fe More work on template editor 2014-08-27 10:41:27 +02:00
Torkel Ödegaard
7d6e04ac77 Small fixes to dasheditor and firefox fixes for search 2014-08-27 09:01:50 +02:00
Torkel Ödegaard
6502cff8fe Moved search results from fixed dropdown to edit pane 2014-08-26 16:42:15 +02:00
Torkel Ödegaard
fdffb03eba Css polish and tweaks 2014-08-26 15:02:25 +02:00
Torkel Ödegaard
d4d3ae7530 Dashboard: Fix for zoom out causing right hand to range to be set in the future. Closes #724 2014-08-26 14:42:32 +02:00
Torkel Ödegaard
647feb7b33 Progress on new filter/templating editor 2014-08-26 14:17:46 +02:00
Torkel Ödegaard
625781c7f4 Merge branch 'master' into filtering_overhaul 2014-08-26 11:18:30 +02:00
Torkel Ödegaard
02ef6c4e07 Merge branch 'master' of github.com:grafana/grafana 2014-08-26 11:18:08 +02:00
Torkel Ödegaard
43eba6cc31 Dashboard: fix for hideControls setting not used/initlaized on dashboard load, Closes #723 2014-08-26 11:17:56 +02:00
Torkel Ödegaard
3775991ac8 Modals to edit pane work almost done, need to work on light theme 2014-08-26 11:12:26 +02:00
Torkel Ödegaard
08d2492839 tweaks to edit & tabs look, removed add row from dasheditor 2014-08-26 10:49:09 +02:00
Torkel Ödegaard
d0d0e8349f moved custom timepicker from modal to edit pane 2014-08-26 10:16:21 +02:00
Torkel Ödegaard
1a97f79d54 changed playlist modal to edit pane 2014-08-26 09:46:15 +02:00
Torkel Ödegaard
9fc6c4888f converting more modals to edit panels 2014-08-26 09:32:30 +02:00
Torkel Ödegaard
f2de18508a More work on removing modals 2014-08-26 07:27:43 +02:00
Torkel Ödegaard
6342571afe Trying get rid of modals, new better design for dashboard settings and modals 2014-08-25 22:39:40 +02:00
Torkel Ödegaard
00e5bb61fc Trying out an alternative to modals 2014-08-25 17:27:19 +02:00
Torkel Ödegaard
b506e7c267 fixed submenu in fullscreen mode 2014-08-25 16:44:33 +02:00
Torkel Ödegaard
061d1262d4 Small html markup/css simplification 2014-08-25 16:14:47 +02:00
Torkel Ödegaard
8b3c89e267 Starting work on new templating editor 2014-08-25 15:55:42 +02:00
Torkel Ödegaard
c634ee81fc Refactoring the pulldown (filtering/annotations), changing the ui slightly 2014-08-25 15:36:44 +02:00
Torkel Ödegaard
6969a4121c removing pulldowns and simplifying submenu controls code 2014-08-25 13:31:31 +02:00
Torkel Ödegaard
4f8b2ad245 began work on filtering overhaul 2014-08-25 12:55:42 +02:00
Torkel Ödegaard
03353cb652 Merge branch 'famousgarkin-master' 2014-08-24 14:46:50 +02:00
Torkel Ödegaard
6f80862517 Dashboard: new config.js option to change/remove window title prefix from 'Grafana -' to anything, PR #685 2014-08-24 14:46:13 +02:00
Torkel Ödegaard
edd8d63caf Merge branch 'master' of github.com:famousgarkin/grafana into famousgarkin-master 2014-08-24 14:29:35 +02:00
Torkel Ödegaard
ffbdea78ee Fix for plot dimension error when resizing window 2014-08-23 20:23:33 +02:00
Torkel Ödegaard
47a20e6a2f Dashboard: fix for adding rows from dashboard settings modal, Closes #699 2014-08-23 10:45:07 +02:00
Torkel Ödegaard
8a80623d0c Merged dashboard tag colors feature branch, updated changelog #634 2014-08-22 09:40:11 +02:00
Torkel Ödegaard
adbe8142b6 Merge branch 'dashboard_tag_colors' 2014-08-22 09:34:19 +02:00
Torkel Ödegaard
3579a18da8 Css fix for panel-error position 2014-08-22 09:34:05 +02:00
Torkel Ödegaard
36882ea2a4 More work on dashboard tag colors 2014-08-22 09:04:28 +02:00
Torkel Ödegaard
3fec2cdfd6 Merge branch 'master' into dashboard_tag_colors 2014-08-22 07:14:19 +02:00
Torkel Ödegaard
28de5cbd97 Merge branch 'master' of github.com:grafana/grafana 2014-08-21 21:59:36 +02:00
Torkel Ödegaard
39cdf85788 small refactoring for search result and dashboard id/title handling 2014-08-21 21:59:23 +02:00
Torkel Odegaard
7c3046e011 Fix for reloadOnSearch missing on all dashboard routes, caused dashboard to be reloaded when entering/exiting edit mode (settings were lost), related to recent fullscreen/edit state present in url, #672, #425 2014-08-21 10:36:18 +02:00
Torkel Ödegaard
1bc8526640 Reverted change, default edit tab should be metrics tab 2014-08-21 09:29:56 +02:00
Torkel Ödegaard
0795af8d42 Fix for search result list and double link to dashboard that caused navigation start recursion when unsaved changed dialog was displayed 2014-08-21 09:28:51 +02:00
Torkel Ödegaard
bd7c045b1c Small fix for dashboard settings dialog controls tab was empty 2014-08-21 09:01:04 +02:00
Torkel Ödegaard
c6812f569f Small js warning fix for firefox 2014-08-21 08:42:09 +02:00
Torkel Ödegaard
17ffb167e2 Small fixes for firefox 2014-08-20 22:34:51 +02:00
Torkel Ödegaard
019d077f5a Added missing max-height to search results container 2014-08-20 19:56:32 +02:00
Torkel Ödegaard
c000f438ae added unit tests for grid thresholds 2014-08-20 15:03:10 +02:00
Torkel Ödegaard
c0d7ddf1fb Updated changelog with new display styles per series option feature, Closes #425, Closes #700 2014-08-20 12:11:14 +02:00
Torkel Ödegaard
5bf794e24e Merge branch 'series_style_overrides' 2014-08-20 11:48:13 +02:00
Torkel Ödegaard
a9cfb160c9 Added typeahead to series overrides, #425 2014-08-20 11:48:00 +02:00
Torkel Ödegaard
468c9a9061 Fix for zindex override removal, should restore series order 2014-08-20 11:31:40 +02:00
Torkel Ödegaard
da1279aa5b Added zindex per series override option, #425 2014-08-20 10:50:26 +02:00
Torkel Ödegaard
3ec053bea7 Moved series override code to TimeSeries 2014-08-20 10:27:30 +02:00
Torkel Ödegaard
b2f9f81eaf Moved yaxis override from aliasYAxis map to the new seriesOverride array 2014-08-20 09:31:22 +02:00
Torkel Ödegaard
939e957fda Made regex match work for per series overrides, #425, #700 2014-08-20 08:35:17 +02:00
Torkel Ödegaard
062fe72030 More options can now be set on pre series basis, this is awesome! 2014-08-19 17:24:37 +02:00
Torkel Ödegaard
cdcbb872d5 options per series is starting to work nicely 2014-08-19 16:57:33 +02:00
Torkel Ödegaard
048763053c Began work on applying per series options to flot options 2014-08-19 16:22:27 +02:00
Torkel Ödegaard
c6489d9b01 Lots of progress on per series overrides 2014-08-19 15:22:03 +02:00
Torkel Ödegaard
299053f2d5 Fix for utc in timepicker, Closes #713 2014-08-19 11:12:48 +02:00
Torkel Ödegaard
937ac84538 Began work on per series style overrides, #425 2014-08-18 21:48:01 +02:00
Torkel Ödegaard
5c0d1355a5 Second take on dashboard tags search result colors 2014-08-18 19:33:38 +02:00
Torkel Ödegaard
a64604de6b UI improvements to search result list (larger click are for dashboard title link, plus UI look polish), Closes #709 2014-08-18 16:38:04 +02:00
Torkel Ödegaard
1a3dac0c17 Fix for timepicker dates and tooltip when UTC timzone is selected,
custom date modal is still local time, Closes #277
2014-08-18 13:43:26 +02:00
Torkel Ödegaard
ffd73e8bfb Fix for graphite queries with glob syntax ([1-9] and ?) that made
the graphite parser / query editor bail and fallback to text edit mode.
2014-08-18 12:17:48 +02:00
Torkel Ödegaard
27c536b1a1 Small fix to 'none' axis formats and zero value when axis tickDecimals is high, Closes #707 2014-08-18 09:03:25 +02:00
Torkel Ödegaard
dc5973a0f3 small css fix for alignment of legend values when shown in table style 2014-08-17 11:53:58 +02:00
Torkel Ödegaard
b812b1c579 Fixed link to playlist docs in readme.md 2014-08-16 19:02:50 +02:00
Torkel Ödegaard
b89480a284 refactored use of localStorage 2014-08-16 13:13:26 +02:00
Torkel Ödegaard
5846c71095 Changed name of some partials and controllers 2014-08-16 08:55:14 +02:00
Torkel Ödegaard
142f081d1e fixed small issue with the recent change to the 'none' axis format,
Closes #703
2014-08-15 21:31:53 +02:00
Torkel Ödegaard
ec2b4f584c some initial work on making it easy to add a custom datasource without modifing the original source, #701, #553 2014-08-15 19:12:25 +02:00
Torkel Ödegaard
88d991ef45 Another angular binding/watcher optimization 2014-08-15 17:23:27 +02:00
Torkel Ödegaard
dc382a6df7 Drag drop binding expression watcher was expensive, removed watcher after first eval, seems to still work 2014-08-15 13:35:17 +02:00
Torkel Ödegaard
c6e57d64d7 moved binding expression for panel width to directive 2014-08-15 13:19:11 +02:00
Torkel Ödegaard
dc3cd430c8 moved binding expression in panels to directive and watchGroup 2014-08-15 12:57:12 +02:00
Torkel Ödegaard
9848600335 Changed panel error icon from ng-show to ng-if 2014-08-15 12:09:04 +02:00
Torkel Ödegaard
cd79b73cb0 moved an expensive binding expression into a directive and a groupWatch 2014-08-15 11:15:24 +02:00
Torkel Ödegaard
21aa1b43fd Save dropdown and search bindings and scope is now not loaded on dashboard load, small performance improvement 2014-08-15 09:35:07 +02:00
Torkel Ödegaard
2f3a96f7a7 Removed add-panel tab from row editor, only add panels from the row menu now, do not want to ways to do the same thing 2014-08-15 08:46:32 +02:00
Torkel Ödegaard
b761dcde45 Switch from watch to watchCollection for bodyclass directive 2014-08-15 08:14:15 +02:00
Torkel Ödegaard
b6cdb0f885 Moved some expensive bindings from timepicker to controller 2014-08-15 08:02:16 +02:00
Torkel Ödegaard
472969ae2a Merge branch 'master' of github.com:grafana/grafana 2014-08-14 22:32:17 +02:00
Torkel Ödegaard
9e3514a993 More small performance tweaks, trying to clean up watcher & scope counts 2014-08-14 22:30:19 +02:00
Torkel Ödegaard
aee3ddd06a Simplified panel bootstrapping, limiting digest cycles during dashboard boot 2014-08-14 15:32:15 +02:00
Torkel Ödegaard
9558d404fa Merge pull request #698 from Topface/http-update
Checking for new version over https
2014-08-14 12:39:54 +02:00
Torkel Ödegaard
0ca6b67132 Added some performance profiling code 2014-08-14 12:26:06 +02:00
Torkel Ödegaard
9390b1eef5 Small cleanup of dashboard partial, removed unused stuff 2014-08-14 12:15:46 +02:00
Ian Babrou
0198167b27 checking for new version over https 2014-08-14 14:13:03 +04:00
Torkel Ödegaard
83e9bc4816 Graph: Fix for axis format none broken for numbers in exponential notation, Closes #696 2014-08-14 12:05:29 +02:00
Torkel Ödegaard
87bf6b3800 Updated changelog and added fix for #695 2014-08-14 10:29:47 +02:00
Torkel Ödegaard
7b3df02640 Performance enhancements 2014-08-13 21:26:33 +02:00
Torkel Ödegaard
499246abae Tech: upgraded jquery from 1.8.0 to 2.1.1, Closes #694 2014-08-13 19:51:27 +02:00
Torkel Ödegaard
71f78cc895 removed comments 2014-08-13 19:26:53 +02:00
Torkel Ödegaard
5896eee693 Dashboard: tooltip fixes for flickering tooltips that sometimes do now want to show on hover, and for tooltips that get stuck after exiting modal, Closes #691 2014-08-13 19:11:46 +02:00
Torkel Ödegaard
ce6b60653c Updated changelog with #672 2014-08-13 17:23:29 +02:00
Torkel Ödegaard
2b865e3505 Merge branch 'panel_id_permalink' 2014-08-13 17:20:59 +02:00
Torkel Ödegaard
dee0e5fce7 final fixes for fullscreen url state, #672 2014-08-13 17:20:54 +02:00
Torkel Ödegaard
56269758c4 refactoring of panel manipulation, moved to dashboard model 2014-08-13 16:35:34 +02:00
Torkel Ödegaard
436f6bda3e Fixed unit tests for dashboardViewStateSrv 2014-08-13 15:17:13 +02:00
Torkel Ödegaard
4d1102db0b Panel edit state is working pretty good now, #672 2014-08-13 15:02:57 +02:00
Torkel Ödegaard
4987a2158e Lots of complicated code for dealing with panel state 2014-08-13 14:22:21 +02:00
Torkel Ödegaard
435a5a67bc Refactoring fullscreen / edit handling 2014-08-13 12:16:50 +02:00
Torkel Ödegaard
fc686ca618 Fullscreen & edit state persisted to url is nearing completion, refactored fullscreen state management to a special dashboardViewState object, Issue #672 2014-08-13 10:07:32 +02:00
Torkel Ödegaard
ec99096d52 experimenting with url and dashboard state 2014-08-13 07:47:36 +02:00
Torkel Ödegaard
68e520fa2d small css tweak to legend and padding between edit tabs 2014-08-12 19:59:20 +02:00
Torkel Ödegaard
d46e612cb1 Working on linking to panels, #576, #672 2014-08-12 18:21:48 +02:00
Torkel Ödegaard
c48df8522a updated readme.md again 2014-08-12 09:24:58 +02:00
Torkel Ödegaard
b0a2c26b22 updated readme.md with a run from master section 2014-08-12 09:23:25 +02:00
Torkel Ödegaard
f865da6d6a removed generated css that came back after merge 2014-08-11 18:52:34 +02:00
Torkel Ödegaard
58e4dc1b6c updated readme with correct coveralls badge 2014-08-11 16:03:27 +02:00
Torkel Ödegaard
17c03bed21 Added some unit tests for RowCtrl 2014-08-11 15:59:03 +02:00
Torkel Ödegaard
4bfc5355db removed sublime project 2014-08-11 15:32:21 +02:00
Torkel Ödegaard
d865618051 Dashboard: Row option to display row title even when the row is visible, Closes #578 2014-08-11 15:25:36 +02:00
Torkel Ödegaard
a995857cca changed placement of panel error tooltip to the right of icon, works better in when in edit mode 2014-08-11 13:47:58 +02:00
Torkel Ödegaard
fdbdebac32 Merge branch 'master' of github.com:grafana/grafana 2014-08-11 13:44:11 +02:00
Torkel Ödegaard
a242c40b23 Merge branch '1.7.x' 2014-08-11 13:43:05 +02:00
Torkel Ödegaard
f82b84eac7 updated changelog 2014-08-11 13:42:39 +02:00
Torkel Ödegaard
70be333691 Last miniute fix for issue in influxdb http request calling 2014-08-11 13:39:02 +02:00
Torkel Ödegaard
3d9a4dcbf3 Merge branch '1.7.x'
Conflicts:
	src/css/bootstrap.dark.min.css
	src/css/bootstrap.light.min.css
	src/css/default.min.css
2014-08-11 13:35:15 +02:00
Torkel Ödegaard
577efbf0c2 fixed changelog issue link 2014-08-11 12:49:17 +02:00
Torkel Ödegaard
a3fca638ee updated version to 1.7.0 (removed rc1 pre-release tag) 2014-08-11 12:23:00 +02:00
Torkel Ödegaard
027855f891 Small fix for light theme 2014-08-11 12:16:19 +02:00
Torkel Ödegaard
b9b04fd932 Dashboard: Panel error are less intrusive, panel error bar replaced with small indicator, hover for short details, click to open inspector, Closes #681 2014-08-11 12:11:24 +02:00
unknown
2b1dcaf5e3 added global page title prefix setting 2014-08-11 11:25:30 +02:00
Torkel Ödegaard
9e32a5613d Merge pull request #682 from PeterDaveHello/patch-1
Use svg instead of png to get better image quality
2014-08-11 11:18:05 +02:00
Torkel Ödegaard
61b43a0828 Merge pull request #683 from PeterDaveHello/patch-2
make CI build faster
2014-08-11 11:17:14 +02:00
Peter Dave Hello
7fc1fed91e make CI build faster 2014-08-11 17:03:17 +08:00
Peter Dave Hello
1fd97f8732 Use svg instead of png to get better image quality 2014-08-11 16:46:20 +08:00
Torkel Ödegaard
aa03de8e52 More work on integrated console, with request details 2014-08-10 21:25:24 +02:00
Torkel Ödegaard
eb9a7267bd began work on inspection console to visualize metric requests, and other useful troubleshooting info and inspection 2014-08-10 14:35:56 +02:00
Torkel Ödegaard
21b7c6a2c0 Moved css files, removed generated css files from source control, consolidated (concat) css more, now only two css files, dark and light 2014-08-10 09:46:55 +02:00
Torkel Ödegaard
4cd53ce119 Merge branch 'cleanup-generated-files' of github.com:clkao/grafana into clkao-cleanup-generated-files 2014-08-10 09:23:44 +02:00
Torkel Ödegaard
e2283e53b6 Merge branch 'master' into develop 2014-08-10 09:12:18 +02:00
Chia-liang Kao
d51d5af992 Cleanup legend value by using css content 2014-08-10 09:09:45 +02:00
Torkel Ödegaard
b98a8ee83a updated changelog for PR #666, Closes #660 2014-08-10 09:09:45 +02:00
Christophe Furmaniak
21cf1f6c47 fix #660 by checking if options is undefined
- a complete fix with a consistent support of alias in all use cases is not obvious (see #660 for explanations)
2014-08-10 09:09:45 +02:00
Torkel Ödegaard
918ea5d12f Annotation: filter field is not interpreting in elasticsearch query, Fixes #661 2014-08-10 09:09:45 +02:00
Torkel Ödegaard
3b7551e1e4 added coveralls badge 2014-08-10 09:09:45 +02:00
Torkel Ödegaard
5dbc0aedcc General: Fix for refresh icon in IE browsers, Fixes #657 2014-08-10 09:09:45 +02:00
Chia-liang Kao
8819d559b7 move vendor css intor appropriate places and remove generated files from version control 2014-08-10 02:21:05 +08:00
Torkel Ödegaard
448a5c00fd Merge pull request #669 from clkao/release-lodash
Fix release build rules following 23c9f97 and #659
2014-08-09 13:08:42 +02:00
Chia-liang Kao
8194c62f4e Fix release build rules following 23c9f97 and #659 2014-08-09 18:40:40 +08:00
Torkel Ödegaard
e75debbf81 Fixes to text panel, and alert related to angularjs upgrade 2014-08-09 12:18:21 +02:00
Torkel Ödegaard
5b475a05ef Added another unit test for influxdb datasource, fixed angular dragdrop/mocks file mixup 2014-08-09 10:02:47 +02:00
Torkel Ödegaard
f69dcf38ef added unit test for influxdb query 2014-08-08 17:33:16 +02:00
Torkel Ödegaard
966ba97b2c updated changelog for PR #666, Closes #660 2014-08-08 15:24:10 +02:00
Christophe Furmaniak
675688cb80 fix #660 by checking if options is undefined
- a complete fix with a consistent support of alias in all use cases is not obvious (see #660 for explanations)
2014-08-08 14:58:03 +02:00
Torkel Ödegaard
59c7edfd90 fixed unit test that broke after moving colors array 2014-08-08 14:10:35 +02:00
Torkel Ödegaard
660fbfd73c Moved colors array away from dash controller 2014-08-08 14:03:05 +02:00
Torkel Ödegaard
7b011c1d96 Changed name of dashboard service to dashboardSrv 2014-08-08 13:45:52 +02:00
Torkel Ödegaard
3fffd08ae4 Annotation: filter field is not interpreting in elasticsearch query, Fixes #661 2014-08-08 07:19:03 +02:00
Torkel Ödegaard
abc8077a96 added some unit tests for graph panel controller 2014-08-07 18:17:26 +02:00
Torkel Ödegaard
5a125c7fe5 added coveralls badge 2014-08-07 15:05:37 +02:00
Torkel Ödegaard
02fb2baf62 Added code coverage, and sending reports to coveralls 2014-08-07 14:58:57 +02:00
Torkel Ödegaard
23c9f973cc Switch from underscore to lodash, #659 2014-08-07 14:35:28 +02:00
Torkel Ödegaard
db90fa71d4 General: Fix for refresh icon in IE browsers, Fixes #657 2014-08-07 14:16:54 +02:00
Torkel Ödegaard
c3a6ae1622 added more graphite target controller tests 2014-08-07 13:44:09 +02:00
Torkel Ödegaard
76aab2a2ac added graphiteTargetCtrl specs 2014-08-07 10:42:05 +02:00
Torkel Ödegaard
a02effc32e Merge branch 'master' into develop 2014-08-07 09:01:09 +02:00
Torkel Ödegaard
965c1f0353 Chart: Possible fix for stuck tooltip (annotation or time series point hover tooltip would not disappear), Fixes #450 2014-08-07 09:00:52 +02:00
Torkel Ödegaard
e7086cf6df Timepicker: Fix for setting custom To date with low refresh interval, Fixes #652 2014-08-07 07:58:14 +02:00
Torkel Ödegaard
af073dad46 Fix for auto refresh not being started after loading dashboard, Fixes #655 2014-08-07 07:20:56 +02:00
Torkel Ödegaard
b79e8b8130 fixed jshint error in test-main 2014-08-06 16:11:17 +02:00
Torkel Ödegaard
3b25200868 Refactoring base panel features, trying to get controller unit tests to work 2014-08-06 16:00:43 +02:00
Torkel Ödegaard
3985e52a3a Fixed unit tests broken after angular 1.3 upgrade 2014-08-06 10:53:45 +02:00
Torkel Ödegaard
fee44d83c8 Small timepicker angular binding perf improvement 2014-08-06 10:39:27 +02:00
Torkel Ödegaard
d70c81f03b more angular 1.3 upgrade fixes 2014-08-06 09:10:18 +02:00
Torkel Ödegaard
387ec89b95 more angular 1.3 upgrade changes 2014-08-06 09:05:03 +02:00
Torkel Ödegaard
60dd68490c working on angular upgrade 2014-08-06 08:16:54 +02:00
Torkel Ödegaard
378e55ed0c small changelog update 2014-08-05 15:37:09 +02:00
Torkel Ödegaard
16900ad421 Small fixes for 1.7 release 2014-08-05 13:22:54 +02:00
Torkel Ödegaard
0bf1e8f252 Readme fixes 2014-08-05 12:29:58 +02:00
Torkel Ödegaard
0aa5505d7f Updated readme, and other small changes for 1.7.0-rc1 release 2014-08-05 12:27:03 +02:00
Torkel Ödegaard
60f68abd31 Dashboard schema simplifications, moved schema updates to dashboard model creation, removes irritating 'unsaved changes' dialogs that show for dashboard schema changes, Closes #532 2014-08-05 10:15:51 +02:00
Torkel Ödegaard
0a677449dc Fixed issue in dashboard settings modal and timepicker options 2014-08-04 15:52:41 +02:00
Torkel Ödegaard
6e9723325f Fixed issue span set to zero. Removed zero option from row editor. Closes #645 2014-08-04 12:26:53 +02:00
Torkel Ödegaard
e9a046e74d Small annotation fix when leaving edit mode and having series hidden 2014-08-04 07:18:26 +02:00
Torkel Ödegaard
44f0c749d5 Fix for InfluxDB temp dashboards, seperate series name prefix so they do not show up in dashboard search, #633 2014-08-03 12:20:42 +02:00
Torkel Ödegaard
082d2c739e updated changelog with #641 change 2014-08-03 12:12:38 +02:00
Torkel Ödegaard
6354b1aec0 Merge branch 'jordanrinke-master' 2014-08-03 12:08:17 +02:00
Torkel Ödegaard
512dbf1980 Refactoring temp dashboard settings, and handling, moved from dashboard to config.js, defaults are enabled, and ttl of 30 days, #641, #638 2014-08-03 12:07:50 +02:00
Torkel Ödegaard
ed491b0caf Merge branch 'master' of github.com:jordanrinke/grafana into jordanrinke-master 2014-08-01 15:29:29 +02:00
Torkel Ödegaard
d6814587ad Update changelog and config sample 2014-08-01 11:34:57 +02:00
Torkel Ödegaard
586399a814 Graphite: Fix for graphite expressions parser failure when metric expressions starts with curly brace segment, Fixes #528 2014-08-01 09:28:57 +02:00
Torkel Ödegaard
867186fd66 Filtering: Fix for nested filters, changing a child filter could result in infinite recursion in some cases, Fixes #628 2014-08-01 08:54:22 +02:00
Jordan Rinke
48c18ee8d1 fixed 2 coding errors noted by travis 2014-07-31 13:48:28 -07:00
Jordan Rinke
f5d5d9a504 fixed 2 coding errors noted by travis 2014-07-31 13:45:17 -07:00
Jordan Rinke
f958924b79 added saving and showing temp searches also trailing comman in default.json was causing the default to fail to load for me, #633 2014-07-31 12:37:35 -07:00
Torkel Ödegaard
67582aaee4 cleanup of 'loader' settings, removed loader.save_elasticsearch, loader.load_elasticsearch. Save/Load is default enabled and will use any datasource marked with grafanaDB: true property 2014-07-31 14:20:53 +02:00
Torkel Ödegaard
8ebe260628 Added support for deleting dashboards to influxdb datasource, #633 2014-07-31 12:39:49 +02:00
Torkel Ödegaard
a6d2590834 Merge PR #618 2014-07-31 12:28:11 +02:00
Torkel Ödegaard
923cd045cd Changed opentsdb metric option chartLabel to Alias to better conform to grafana naming, updated changelog with PR #618 info 2014-07-31 12:27:49 +02:00
Torkel Ödegaard
f64bcf0d08 Merge branch 'master' of github.com:heldr/grafana into heldr-master 2014-07-31 12:16:07 +02:00
Torkel Ödegaard
c48b6e23eb Updated changelog with PR #626 2014-07-31 11:22:20 +02:00
Torkel Ödegaard
b2eabda5b6 Merge branch 'master' of github.com:kamaradclimber/grafana into kamaradclimber-master 2014-07-31 11:17:08 +02:00
Torkel Ödegaard
305e12be1d Updated changelog and config.sample.js with info about #633 2014-07-31 11:11:33 +02:00
Torkel Ödegaard
30ad784d95 Changed dashboard urls from /dashboard/elasticsearch/<title> to dashboard/db/<title>, old urls will still work 2014-07-31 11:06:02 +02:00
Torkel Ödegaard
7be7b07c19 Changed search result model to be more datasource agnostic 2014-07-31 10:54:36 +02:00
Torkel Ödegaard
88c46f4612 Merge branch 'influxdb_dashstore' 2014-07-31 10:37:17 +02:00
Torkel Ödegaard
1c1b9b5c9d InfluxDB: save/load and search works, tag facets still to be done, but is not critical, #633 2014-07-31 10:36:45 +02:00
Gregoire Seux
f5f3256824 Downscale y axis to more precise unit
y axis unit is already upscaled automatically, this commit adds
automatic downscale.
It also fixes the number of decimals displayed (0 -> decimals)
2014-07-31 09:40:23 +02:00
Torkel Ödegaard
d056b1f1e1 Search: max_results config.js option & scroll in search results (To show more or all dashboards), Closes #631 2014-07-31 09:17:37 +02:00
Torkel Ödegaard
c86a30921f Save/load dashboard from/to influxdb works, #633 2014-07-30 15:38:09 +02:00
Torkel Ödegaard
14f09e3787 Added filtering support for graphite events/metrics, Closes #402 2014-07-30 13:09:23 +02:00
Torkel Ödegaard
d2a342a94e Merge branch 'elastic_annotations'
Conflicts:
	src/css/bootstrap.dark.min.css
	src/css/bootstrap.light.min.css
	src/css/default.min.css
2014-07-30 11:36:47 +02:00
Torkel Ödegaard
c61e4c02bd further work on unifying datasources, #630 2014-07-30 11:34:09 +02:00
Torkel Ödegaard
b8ce61ae45 General architectural changes around datasources, unifying dashboard loading behind datasource abstraction, #630 2014-07-30 10:52:02 +02:00
Torkel Ödegaard
5a25b0885c added datasource filtering based on datasource abilities 2014-07-30 08:34:58 +02:00
Torkel Ödegaard
337cbb2844 Ctrl+H (hide controls) issue introduced in recent commit, Closes #625 2014-07-29 17:50:09 +02:00
Torkel Ödegaard
fa3b84a615 Elastic search annotations are working, need to refactor and unify datasource abstraction more, #201 2014-07-29 17:24:42 +02:00
Torkel Ödegaard
4e47447dec began work on ES annotation datasource, #201 2014-07-29 11:26:05 +02:00
Torkel Ödegaard
a1772d26b5 New global option in config.js to specify admin password (useful to hinder some users from accidentally making changes), Closes #606 2014-07-29 09:45:07 +02:00
Torkel Ödegaard
77e5e75b2f Fixed ngmin build issue introduced in route refactoring, Fixes #622 2014-07-29 08:13:23 +02:00
Torkel Ödegaard
77bfd85e9e Changed all kibana words to grafana 2014-07-28 18:11:52 +02:00
Torkel Ödegaard
272ea9fe17 Added global datasource config option cacheTimeout for graphite datasource, #266 2014-07-28 17:54:32 +02:00
Torkel Ödegaard
8aed1aa634 Fix for cacheTimeout undefined value, #266 2014-07-28 17:01:48 +02:00
Torkel Ödegaard
2bec41b80e Graphite: new option available in metrics view to set cacheTimeout, will override default memcache timeout, Closes #266 2014-07-28 15:01:00 +02:00
Torkel Ödegaard
38633b6db4 Merge branch 'develop' 2014-07-28 12:41:51 +02:00
Torkel Odegaard
e62dc00d7b Fix for build issues on windows, Fixes #574 2014-07-28 11:08:19 +02:00
Torkel Ödegaard
f619fc3e7e merge with master 2014-07-25 12:37:39 +02:00
Torkel Ödegaard
6c7d74c43b updated changelog with PR #604 2014-07-25 12:36:23 +02:00
Torkel Ödegaard
268cead331 Merge pull request #604 from floored1585/add_bps
Adding bps unit type for network gear
2014-07-25 12:30:13 +02:00
Torkel Ödegaard
70521f0756 InfluxDB: support for InfluxDB v0.8 'list series' response schema, Fixes #610 2014-07-25 12:14:15 +02:00
Helder Santana
1f283d93ca add opentsdb chart label field 2014-07-23 14:19:21 -04:00
Torkel Ödegaard
7dc422887c Merge pull request #615 from lathan/english_syntax
english syntax fix
2014-07-23 13:36:34 +02:00
Torkel Ödegaard
7dea0dcfc4 Merge pull request #616 from guequierre/master
'list series' instead of 'select * from /.*/ limit 1'
2014-07-23 13:34:51 +02:00
guequierre
ab11604bfb 'list series' instead of 'select * from /.*/ limit 1'
'list series' response is much faster than 'select * from /.*/ limit 1' for the auto-complete option. Especially noticeable on large datasets.
2014-07-23 12:37:11 +02:00
George Angelopoulos
0e8c026854 english syntax fix
Add a comma to clearly separate the two clauses of the conditional sentence.
Otherwise, it's unclear whether it is referring to "the panel below" or
"the panel below the browser".
2014-07-23 12:47:52 +03:00
Torkel Ödegaard
3b1cc1cc34 removed old timezoneOffset setting 2014-07-22 08:56:12 +02:00
Torkel Ödegaard
85a8f2f147 another small fix for timezone and annotations, #611 2014-07-21 18:52:19 +02:00
Torkel Ödegaard
37c43199ca Fix for annotations hover tooltip and timestamp when timezone set to utc, Fixes #611, #394 2014-07-21 18:49:30 +02:00
Torkel Ödegaard
f22fcc2e59 updated changelog 2014-07-20 18:12:54 +02:00
Torkel Ödegaard
05d7d58cd5 Merge branch 'master' into develop 2014-07-20 18:09:01 +02:00
Torkel Ödegaard
10e89f6802 small tweek to plot hover and annotation hover tooltip css 2014-07-20 18:08:05 +02:00
Torkel Ödegaard
551b802d89 Annotation datasource redesign is done, Closes #608 2014-07-20 16:32:09 +02:00
Torkel Ödegaard
5ae8607771 Fixed dashboard import, broken by dashboard loading redesign #609 2014-07-19 19:06:28 +02:00
Torkel Ödegaard
7b47f40979 better formating of changelog 2014-07-19 18:41:25 +02:00
Torkel Ödegaard
a9a76b9010 annotation redesign is almost done, #608 2014-07-19 13:16:47 +02:00
Torkel Ödegaard
cf68725c89 influxdb annotation column mapping is working 2014-07-18 19:19:30 +02:00
Ian Clark
989d703d1d Adding bps unit type for network gear 2014-07-17 16:52:12 -07:00
Torkel Ödegaard
cf2ef0955d influxdb annotations working, need to figure out how to know which columns to use for title, tags, and data 2014-07-17 10:24:30 +02:00
Torkel Ödegaard
2fe3b0de55 influxdb annoations starting to work 2014-07-16 18:51:51 +02:00
Torkel Ödegaard
b47047a91d Merge branch 'master' into annotations_redesign 2014-07-16 13:18:21 +02:00
Torkel Ödegaard
1c56ac7e48 changed css concat order 2014-07-16 13:17:55 +02:00
Torkel Ödegaard
47fb553d38 Merge pull request #593 from Kixeye/master-fixfilterqueries
influxdbDatasource.js - fix find query
2014-07-16 07:47:24 +02:00
Dave Ertel
bd6362f09f influxdbDatasource.js - fix find query 2014-07-16 14:03:44 +10:00
Torkel Ödegaard
9cc735bdf2 fixed small css bug with the 'no datapoints' warning introduced by legends makeover 2014-07-15 16:57:23 +02:00
Torkel Ödegaard
f5d992f609 InfluxDB: Support for alias & alias patterns when using raw query mode, #584 2014-07-15 16:46:17 +02:00
Torkel Ödegaard
25407fb5f0 updated readme.md 2014-07-15 11:32:03 +02:00
Torkel Ödegaard
9eb9bd8488 moved annotations graphite query to graphite datasource 2014-07-14 18:19:41 +02:00
Torkel Ödegaard
7d6eafb2f2 Merge branch 'dashboard_loading_refactoring' into annotations_redesign 2014-07-14 17:28:19 +02:00
Torkel Ödegaard
bfdf25a162 Merge branch 'master' into dashboard_loading_refactoring 2014-07-14 17:28:00 +02:00
Torkel Ödegaard
6e0e6f5ec4 updated changelog with PR #581 2014-07-14 17:26:16 +02:00
Torkel Ödegaard
dce8b15937 Merge branch 'feature/influxdb-expose-continuous-query-in-series' of github.com:mavimo/grafana into mavimo-feature/influxdb-expose-continuous-query-in-series 2014-07-14 17:20:28 +02:00
Marco Vito Moscaritolo
25e2e07631 ADd contiuous query in series results. 2014-07-13 19:57:09 +02:00
Torkel Ödegaard
b5fb8b6d82 fixed jscs errors 2014-07-13 15:15:10 +02:00
Torkel Ödegaard
171c5aa50c began work on annotations redesign to easier support more annotation sources, #133, #394, #403 2014-07-13 15:01:20 +02:00
Torkel Ödegaard
b5d378c425 Merge branch 'master' into dashboard_loading_refactoring 2014-07-13 12:51:22 +02:00
Torkel Ödegaard
145d65fd60 removed commented out html in graph module 2014-07-13 12:49:54 +02:00
Torkel Ödegaard
5c78fe1070 fixed jshint error 2014-07-08 08:36:59 +02:00
Torkel Ödegaard
31b1203317 fixed failing unit test 2014-07-07 19:04:22 +02:00
Torkel Ödegaard
eaa200a766 Fix for Max legend value when max value is zero (Issue #460) 2014-07-04 11:50:11 +02:00
Torkel Ödegaard
f422c84414 extra fix for percent sign in aliases and plothover, #506 2014-07-03 16:28:38 +02:00
Torkel Ödegaard
4562f31b6b merged with master 2014-07-03 16:25:07 +02:00
Torkel Ödegaard
6911e184f9 New legend display option 'Right side', will show legend to the right of the graph (Closes #556) 2014-07-03 12:38:20 +02:00
Torkel Ödegaard
9627212510 fixed failing unit test 2014-07-03 09:30:44 +02:00
Torkel Ödegaard
0fc8c4e071 small changes to influxdb aliasing, and help text, #525 2014-07-03 09:27:11 +02:00
Torkel Ödegaard
6c93dc6a4c Some changes to influxdb alias pattern expansions, removed as it was to confusion, added help text to influxdb metric editor view, need to polish a litle more, #525 2014-07-02 15:50:57 +02:00
Torkel Ödegaard
1dbbfbbba6 Enhanced InfluxDB series aliasing (legend names) with pattern replacements (Issue #525) 2014-07-02 15:21:29 +02:00
Torkel Ödegaard
88ab36c45b added y2 text after right y axis series, when legend is in table display mode #136 2014-07-02 13:00:33 +02:00
Torkel Ödegaard
14247ddabb New legend display option 'Align as table' (Issue #136) 2014-07-02 12:13:42 +02:00
Torkel Ödegaard
fd8561ac55 added missing file from last commit 2014-07-01 20:01:56 +02:00
Torkel Ödegaard
e1e6ba36ca Refactoring influxdb datasource, split out response handling 2014-07-01 15:55:56 +02:00
Torkel Ödegaard
435a50de4b Merge pull request #550 from Fuitad/master
Fixed invalid references to grid.min and grid.max in function render_panel_as_graphite_png()
2014-06-30 20:45:36 +02:00
Pierre-Luc Brunet
93b2b9b7b0 Fixed invalid references to grid.min and grid.max in function render_panel_as_graphite_png() 2014-06-30 12:13:39 -04:00
Torkel Ödegaard
faa5199a9a removed gist loading/saving 2014-06-30 09:46:34 +02:00
Torkel Ödegaard
5768d7a247 Merge branch 'master' into dashboard_loading_refactoring
Conflicts:
	src/app/partials/dashboard.html
	src/app/partials/dasheditor.html
	src/app/services/graphite/graphiteDatasource.js
2014-06-30 09:21:16 +02:00
Torkel Ödegaard
d9f2fca66d Merge branch 'dashboard_loading_refactoring' of github.com:torkelo/grafana-private into dashboard_loading_refactoring 2014-06-30 09:20:07 +02:00
Torkel Ödegaard
91b48258f0 Refactoring PR #511, Allow filter notation [[..]] in text panels 2014-06-30 09:10:32 +02:00
Torkel Ödegaard
2ac7b9dabf Merge branch 'master' of github.com:Akeru/grafana into Akeru-master 2014-06-30 08:05:48 +02:00
Torkel Ödegaard
505f0f65d0 updated change log with PR #545 2014-06-30 07:56:35 +02:00
Torkel Ödegaard
8262a8dea2 Merge pull request #545 from Akeru/fix-format
Fix formatting negative values
2014-06-30 07:32:11 +02:00
Torkel Ödegaard
406286b970 Merge pull request #544 from Akeru/diffseries
Add diffSeries function support
2014-06-30 07:29:44 +02:00
Akeru
6346f835af Fix formatting negative values 2014-06-27 15:37:11 +02:00
Akeru
73fc437c7d Add diffSeries function support 2014-06-27 15:24:06 +02:00
Torkel Ödegaard
af66739207 Merge pull request #543 from Akeru/html-fix
Fix HTML code
2014-06-27 14:03:56 +02:00
Akeru
65bb6a8b73 Fix HTML code 2014-06-27 13:52:43 +02:00
Torkel Ödegaard
8bda5aa2a7 Merge pull request #539 from looztra/opentsdb-fix-tag-display
Display tag values whenever a tag is part of the query (opentsdb)
2014-06-26 06:59:23 +02:00
Christophe Furmaniak
dd2b43dc73 Display tag values whenever a tag is part of the query (opentsdb datasource) 2014-06-25 23:56:25 +02:00
Torkel Ödegaard
c925014bb5 Use unix epoch for Graphite from/to for absolute time ranges, #536 2014-06-25 10:09:19 -04:00
Torkel Ödegaard
32b11b104f updated version to 1.6.1 2014-06-24 15:06:51 +02:00
Torkel Ödegaard
e25a73f9af updated default dashboard info text 2014-06-24 13:57:46 +02:00
Torkel Ödegaard
15c1b48b25 merged with master 2014-06-22 19:01:04 +02:00
Torkel Ödegaard
d14a86069a Auto-refresh caused 2 refreshes (and hence mulitple queries) each time (at least in firefox) (Fixes #342) 2014-06-22 18:51:27 +02:00
Torkel Ödegaard
186f753aec style changing now works again 2014-06-22 18:21:38 +02:00
Torkel Ödegaard
f180707b6f Merge branch 'master' into dashboard_loading_refactoring 2014-06-22 18:04:37 +02:00
Torkel Ödegaard
9a53779b6c Default property that marks which datasource is default in config.js is now optional (Fixes #526) 2014-06-22 18:02:43 +02:00
Torkel Ödegaard
551771c6cf ixed influxdb issue with raw query that caused wrong value column detection (Fixes #504) 2014-06-22 12:01:41 +02:00
Torkel Ödegaard
a3aca0bae4 restored influxdb series naming default to series.value_func 2014-06-22 11:53:58 +02:00
Torkel Ödegaard
ed0c71fa56 Series names and column name typeahead cache fix (Fixes #522) 2014-06-22 11:27:36 +02:00
Torkel Ödegaard
574ecdb512 fixed column typehead, introduced in PR #500 2014-06-22 11:27:36 +02:00
Torkel Ödegaard
7848a35941 Merge pull request #524 from acedrew/master
Added nginx config examples to docs.
2014-06-21 22:07:04 +02:00
Andrew Rodgers
dfe0314ba0 Added nginx config examples for CORS headers, and CORS selective reflection 2014-06-21 16:17:45 +00:00
Andrew Rodgers
4618ef0cbf Merge branch 'docs-improvement'
Added nginx config examples, including CORS header reflection.
2014-06-21 16:11:53 +00:00
Andrew Rodgers
bb281649fc prepare for master branch after all sorts of git shenanigans 2014-06-21 16:11:36 +00:00
Torkel Ödegaard
012ffcf6f6 Bug in when using % sign in legends (aliases), fixed by removing url decoding of metric names (Fixes #506) 2014-06-21 17:27:57 +02:00
Torkel Ödegaard
bef61cf019 Ability to set y min/max for right y-axis (RR #519, Closes #360) 2014-06-21 17:18:24 +02:00
Andrew Rodgers
5ce6464461 Revert "added nginx conf examples for graphite CORS configuration"
This reverts commit c37496f2b0.
2014-06-20 03:01:13 +00:00
Andrew Rodgers
a0c3f99d80 Revert "updated nginx conf examples for graphite CORS configuration"
This reverts commit be03f6adb6.
2014-06-20 03:00:50 +00:00
Andrew Rodgers
f6ba577cf4 Revert "updated nginx conf examples for graphite CORS configuration"
This reverts commit 9edaa407f2.
2014-06-20 03:00:31 +00:00
Andrew Rodgers
6ef03b7c6c Revert "Revert "Added left and right min and max for y-axis""
This reverts commit 05303dab45.
2014-06-20 02:59:52 +00:00
Andrew Rodgers
05303dab45 Revert "Added left and right min and max for y-axis"
This reverts commit 8743ba2886.
2014-06-20 02:54:34 +00:00
Andrew Rodgers
9edaa407f2 updated nginx conf examples for graphite CORS configuration 2014-06-20 02:44:45 +00:00
Andrew Rodgers
be03f6adb6 updated nginx conf examples for graphite CORS configuration 2014-06-20 02:42:18 +00:00
Andrew Rodgers
c37496f2b0 added nginx conf examples for graphite CORS configuration 2014-06-20 02:40:01 +00:00
Andrew Rodgers
8743ba2886 Added left and right min and max for y-axis 2014-06-19 21:49:49 +00:00
Akeru
c100054d68 Allow [[..]] filter notation in all text panels
Based on #408 with fixed filter service method. Supports html, text and
markdown panels.
2014-06-18 17:28:40 +02:00
Torkel Ödegaard
2fbb87e17a fix jshint and updated changelog with PR #500 2014-06-17 18:09:10 +02:00
Pauly Myjavec
1eadf52f5e Fixes regex InfluxDB queries intoduced in 1.6.0
Do not encapsulate regex with quotes in query string
Fix the way series names are displayed, do not use column name but
rather series names
2014-06-17 22:38:52 +10:00
Torkel Ödegaard
9f3681642a Merge branch 'dashboard_loading_refactoring' of github.com:torkelo/grafana-private into dashboard_loading_refactoring 2014-06-14 14:55:29 +02:00
Torkel Ödegaard
a4ca679b65 Merge branch 'master' of github.com:torkelo/grafana-private into dashboard_loading_refactoring 2014-06-14 14:54:55 +02:00
Torkel Ödegaard
d198095d7a Merge branch 'master' into dashboard_loading_refactoring 2014-06-14 14:51:09 +02:00
Torkel Ödegaard
438455bc8c finally removed elasticjs library 2014-06-13 13:58:34 +02:00
Torkel Ödegaard
7914f65f68 removed redundant load/save options 2014-06-13 13:50:09 +02:00
Torkel Ödegaard
d0b79ad335 now dashboard deleting works 2014-06-12 17:40:37 +02:00
Torkel Ödegaard
7dc4484f6a Renamed dashboard service to dashboardModel, fixed saving default dashboard feature 2014-06-12 17:03:52 +02:00
Torkel Ödegaard
257ea391da fixed issues with unsaved changes srv 2014-06-12 16:35:58 +02:00
Torkel Ödegaard
4c64bcfae7 keyboard binding now work again 2014-06-12 13:37:40 +02:00
Torkel Ödegaard
99d2f537c2 sharing temp dashboard is now working again 2014-06-11 20:22:05 +02:00
Torkel Ödegaard
6c32365e00 more work on refactoring ES usage 2014-06-11 08:19:52 +02:00
Torkel Ödegaard
95925aafb1 removed dependence on elasticsearch js client from search, will try to remove this depdence in other places and just use $http, elasticsearch js client is to big and does not provide enough value 2014-06-11 08:11:13 +02:00
Torkel Ödegaard
0a3d4a5ab0 elasticsearch saving does now work, still have to do ttl and temp dashboard 2014-06-10 21:32:38 +02:00
Torkel Ödegaard
2328c60d12 small refactoring progress 2014-06-08 20:46:30 +02:00
Torkel Ödegaard
61cd5cf4e6 fixed jshint and jscs checks 2014-06-08 16:09:36 +02:00
Torkel Ödegaard
1eb9efe2d5 fixing broken things 2014-06-08 16:08:12 +02:00
Torkel Ödegaard
d5882f2efe ES and file loading is working 2014-06-08 15:28:50 +02:00
Torkel Ödegaard
5f3991127b refactoring progress 2014-06-08 14:40:44 +02:00
Torkel Ödegaard
b995dc54f1 further refactoring 2014-06-07 21:00:05 +02:00
Torkel Ödegaard
79404e754e started on some big refactoring of how the app starts and how dashboard object is loaded, created. This should make it easier to add other dashboard storage backends and other views 2014-06-07 19:43:15 +02:00
268 changed files with 43036 additions and 50638 deletions

3
.gitignore vendored
View File

@@ -1,11 +1,14 @@
node_modules
coverage/
.aws-config.json
dist
# locally required config files
web.config
config.js
src/css/*.min.css
# Editor junk
*.sublime-workspace
*.swp
.idea/

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,9 @@
language: node_js
node_js:
- "0.10"
git:
depth: 1
before_script:
- npm install -g grunt-cli
- npm install -g grunt-cli
after_script:
- npm run coveralls

View File

@@ -1,88 +1,309 @@
# vNext
# 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**
- [Issue #855](https://github.com/grafana/grafana/issues/855). Graph: Fix for scroll issue in graph edit mode when dropdown goes below screen
- [Issue #847](https://github.com/grafana/grafana/issues/847). Graph: Fix for series draw order not being the same after hiding/unhiding series
- [Issue #851](https://github.com/grafana/grafana/issues/851). Annotations: Fix for annotations not reloaded when switching between 2 dashboards with annotations
- [Issue #846](https://github.com/grafana/grafana/issues/846). Edit panes: Issue when open row or json editor when scrolled down the page, unable to scroll and you did not see editor
- [Issue #840](https://github.com/grafana/grafana/issues/840). Import: Fixes to import from json file and import from graphite. Issues was lingering state from previous dashboard.
- [Issue #859](https://github.com/grafana/grafana/issues/859). InfluxDB: Fix for bug when saving dashboard where title is the same as slugified url id
- [Issue #852](https://github.com/grafana/grafana/issues/852). White theme: Fixes for hidden series legend text and disabled annotations color
# 1.8.0 (2014-09-22)
Read this [blog post](http://grafana.org/blog/2014/09/11/grafana-1-8-0-rc1-released.html) for an overview of all improvements.
**Fixes**
- [Issue #802](https://github.com/grafana/grafana/issues/802). Annotations: Fix when using InfluxDB datasource
- [Issue #795](https://github.com/grafana/grafana/issues/795). Chrome: Fix for display issue in chrome beta & chrome canary when entering edit mode
- [Issue #818](https://github.com/grafana/grafana/issues/818). Graph: Added percent y-axis format
- [Issue #828](https://github.com/grafana/grafana/issues/828). Elasticsearch: saving new dashboard with title equal to slugified url would cause it to deleted.
- [Issue #830](https://github.com/grafana/grafana/issues/830). Annotations: Fix for elasticsearch annotations and mapping nested fields
# 1.8.0-RC1 (2014-09-12)
**UI polish / changes**
- [Issue #725](https://github.com/grafana/grafana/issues/725). UI: All modal editors are removed and replaced by an edit pane under menu. The look of editors is also updated and polished. Search dropdown is also shown as pane under menu and has seen some UI polish.
**Filtering/Templating feature overhaul**
- Filtering renamed to Templating, and filter items to variables
- Filter editing has gotten its own edit pane with much improved UI and options
- [Issue #296](https://github.com/grafana/grafana/issues/296). Templating: Can now retrieve variable values from a non-default data source
- [Issue #219](https://github.com/grafana/grafana/issues/219). Templating: Template variable value selection is now a typeahead autocomplete dropdown
- [Issue #760](https://github.com/grafana/grafana/issues/760). Templating: Extend template variable syntax to include $variable syntax replacement
- [Issue #234](https://github.com/grafana/grafana/issues/234). Templating: Interval variable type for time intervals summarize/group by parameter, included "auto" option, and auto step counts option.
- [Issue #262](https://github.com/grafana/grafana/issues/262). Templating: Ability to use template variables for function parameters via custom variable type, can be used as parameter for movingAverage or scaleToSeconds for example
- [Issue #312](https://github.com/grafana/grafana/issues/312). Templating: Can now use template variables in panel titles
- [Issue #613](https://github.com/grafana/grafana/issues/613). Templating: Full support for InfluxDB, filter by part of series names, extract series substrings, nested queries, multipe where clauses!
- Template variables can be initialized from url, with var-my_varname=value, breaking change, before it was just my_varname.
- Templating and url state sync has some issues that are not solved for this release, see [Issue #772](https://github.com/grafana/grafana/issues/772) for more details.
**InfluxDB Breaking changes**
- To better support templating, fill(0) and group by time low limit some changes has been made to the editor and query model schema
- Currently some of these changes are breaking
- If you used custom condition filter you need to open the graph in edit mode, the editor will update the schema, and the queries should work again
- If you used a raw query you need to remove the time filter and replace it with $timeFilter (this is done automatically when you switch from query editor to raw query, but old raw queries needs to updated)
- If you used group by and later removed the group by the graph could break, open in editor and should correct it
- InfluxDB annotation queries that used [[timeFilter]] should be updated to use $timeFilter syntax instead
- Might write an upgrade tool to update dashboards automatically, but right now master (1.8) includes the above breaking changes
**InfluxDB query editor enhancements**
- [Issue #756](https://github.com/grafana/grafana/issues/756). InfluxDB: Add option for fill(0) and fill(null), integrated help in editor for why this option is important when stacking series
- [Issue #743](https://github.com/grafana/grafana/issues/743). InfluxDB: A group by time option for all queries in graph panel that supports a low limit for auto group by time, very important for stacking and fill(0)
- The above to enhancements solves the problems associated with stacked bars and lines when points are missing, these issues are solved:
- [Issue #673](https://github.com/grafana/grafana/issues/673). InfluxDB: stacked bars missing intermediate data points, unless lines also enabled
- [Issue #674](https://github.com/grafana/grafana/issues/674). InfluxDB: stacked chart ignoring series without latest values
- [Issue #534](https://github.com/grafana/grafana/issues/534). InfluxDB: No order in stacked bars mode
**New features and improvements**
- [Issue #117](https://github.com/grafana/grafana/issues/117). Graphite: Graphite query builder can now handle functions that multiple series as arguments!
- [Issue #281](https://github.com/grafana/grafana/issues/281). Graphite: Metric node/segment selection is now a textbox with autocomplete dropdown, allow for custom glob expression for single node segment without entering text editor mode.
- [Issue #304](https://github.com/grafana/grafana/issues/304). Dashboard: View dashboard json, edit/update any panel using json editor, makes it possible to quickly copy a graph from one dashboard to another.
- [Issue #578](https://github.com/grafana/grafana/issues/578). Dashboard: Row option to display row title even when the row is visible
- [Issue #672](https://github.com/grafana/grafana/issues/672). Dashboard: panel fullscreen & edit state is present in url, can now link to graph in edit & fullscreen mode.
- [Issue #709](https://github.com/grafana/grafana/issues/709). Dashboard: Small UI look polish to search results, made dashboard title link are larger
- [Issue #425](https://github.com/grafana/grafana/issues/425). Graph: New section in 'Display Styles' tab to override any display setting on per series bases (mix and match lines, bars, points, fill, stack, line width etc)
- [Issue #634](https://github.com/grafana/grafana/issues/634). Dashboard: Dashboard tags now in different colors (from fixed palette) determined by tag name.
- [Issue #685](https://github.com/grafana/grafana/issues/685). Dashboard: New config.js option to change/remove window title prefix.
- [Issue #781](https://github.com/grafana/grafana/issues/781). Dashboard: Title URL is now slugified for greater URL readability, works with both ES & InfluxDB storage, is backward compatible
- [Issue #785](https://github.com/grafana/grafana/issues/785). Elasticsearch: Support for full elasticsearch lucene search grammar when searching for dashboards, better async search
- [Issue #787](https://github.com/grafana/grafana/issues/787). Dashboard: time range can now be read from URL parameters, will override dashboard saved time range
**Fixes**
- [Issue #696](https://github.com/grafana/grafana/issues/696). Graph: Fix for y-axis format 'none' when values are in scientific notation (ex 2.3e-13)
- [Issue #733](https://github.com/grafana/grafana/issues/733). Graph: Fix for tooltip current value decimal precision when 'none' axis format was selected
- [Issue #697](https://github.com/grafana/grafana/issues/697). Graphite: Fix for Glob syntax in graphite queries ([1-9] and ?) that made the query editor / parser bail and fallback to a text box.
- [Issue #702](https://github.com/grafana/grafana/issues/702). Graphite: Fix for nonNegativeDerivative function, now possible to not include optional first parameter maxValue
- [Issue #277](https://github.com/grafana/grafana/issues/277). Dashboard: Fix for timepicker date & tooltip when UTC timezone selected.
- [Issue #699](https://github.com/grafana/grafana/issues/699). Dashboard: Fix for bug when adding rows from dashboard settings dialog.
- [Issue #723](https://github.com/grafana/grafana/issues/723). Dashboard: Fix for hide controls setting not used/initialized on dashboard load
- [Issue #724](https://github.com/grafana/grafana/issues/724). Dashboard: Fix for zoom out causing right hand "to" range to be set in the future.
**Tech**
- Upgraded from angularjs 1.1.5 to 1.3 beta 17;
- Switch from underscore to lodash
- helpers to easily unit test angularjs controllers and services
- Test coverage through coveralls
- Upgrade from jquery 1.8.0 to 2.1.1 (**Removes support for IE7 & IE8**)
# 1.7.1 (unreleased)
**Fixes**
- [Issue #691](https://github.com/grafana/grafana/issues/691). Dashboard: Tooltip fixes, sometimes they would not show, and sometimes they would get stuck.
- [Issue #695](https://github.com/grafana/grafana/issues/695). Dashboard: Tooltip on goto home menu icon would get stuck after clicking on it
# 1.7.0 (2014-08-11)
**Fixes**
- [Issue #652](https://github.com/grafana/grafana/issues/652). Timepicker: Entering custom date range impossible when refresh is low (now is constantly reset)
- [Issue #450](https://github.com/grafana/grafana/issues/450). Graph: Tooltip does not disappear sometimes and would get stuck
- [Issue #655](https://github.com/grafana/grafana/issues/655). General: Auto refresh not initiated / started after dashboard loading
- [Issue #657](https://github.com/grafana/grafana/issues/657). General: Fix for refresh icon in IE browsers
- [Issue #661](https://github.com/grafana/grafana/issues/661). Annotations: Elasticsearch querystring with filter template replacements was not interpolated
- [Issue #660](https://github.com/grafana/grafana/issues/660). OpenTSDB: fix opentsdb queries that returned more than one series
**Change**
- [Issue #681](https://github.com/grafana/grafana/issues/681). Dashboard: The panel error bar has been replaced with a small error indicator, this indicator does not change panel height and is a lot less intrusive. Hover over it for short details, click on it for more details.
# 1.7.0-rc1 (2014-08-05)
**New features or improvements**
- [Issue #581](https://github.com/grafana/grafana/issues/581). InfluxDB: Add continuous query in series results (series typeahead).
- [Issue #584](https://github.com/grafana/grafana/issues/584). InfluxDB: Support for alias & alias patterns when using raw query mode
- [Issue #394](https://github.com/grafana/grafana/issues/394). InfluxDB: Annotation support
- [Issue #633](https://github.com/grafana/grafana/issues/633). InfluxDB: InfluxDB can now act as a datastore for dashboards
- [Issue #610](https://github.com/grafana/grafana/issues/610). InfluxDB: Support for InfluxdB v0.8 list series response schemea (series typeahead)
- [Issue #525](https://github.com/grafana/grafana/issues/525). InfluxDB: Enhanced series aliasing (legend names) with pattern replacements
- [Issue #266](https://github.com/grafana/grafana/issues/266). Graphite: New option cacheTimeout to override graphite default memcache timeout
- [Issue #606](https://github.com/grafana/grafana/issues/606). General: New global option in config.js to specify admin password (useful to hinder users from accidentally make changes)
- [Issue #201](https://github.com/grafana/grafana/issues/201). Annotations: Elasticsearch datasource support for events
- [Issue #344](https://github.com/grafana/grafana/issues/344). Annotations: Annotations can now be fetched from non default datasources
- [Issue #631](https://github.com/grafana/grafana/issues/631). Search: max_results config.js option & scroll in search results (To show more or all dashboards)
- [Issue #511](https://github.com/grafana/grafana/issues/511). Text panel: Allow [[..]] filter notation in all text panels (markdown/html/text)
- [Issue #136](https://github.com/grafana/grafana/issues/136). Graph: New legend display option "Align as table"
- [Issue #556](https://github.com/grafana/grafana/issues/556). Graph: New legend display option "Right side", will show legend to the right of the graph
- [Issue #604](https://github.com/grafana/grafana/issues/604). Graph: New axis format, 'bps' (SI unit in steps of 1000) useful for network gear metics
- [Issue #626](https://github.com/grafana/grafana/issues/626). Graph: Downscale y axis to more precise unit, value of 0.1 for seconds format will be formated as 100 ms. Thanks @kamaradclimber
- [Issue #618](https://github.com/grafana/grafana/issues/618). OpenTSDB: Series alias option to override metric name returned from opentsdb. Thanks @heldr
**Documentation**
- [Issue #635](https://github.com/grafana/grafana/issues/635). Docs for features and changes in v1.7, new troubleshooting guide, new Getting started guide, improved install & config guide.
**Changes**
- [Issue #536](https://github.com/grafana/grafana/issues/536). Graphite: Use unix epoch for Graphite from/to for absolute time ranges
- [Issue #641](https://github.com/grafana/grafana/issues/536). General: Dashboard save temp copy feature settings moved from dashboard to config.js, default is enabled, and ttl to 30 days
- [Issue #532](https://github.com/grafana/grafana/issues/532). Schema: Dashboard schema changes, "Unsaved changes" should not appear for schema changes. All changes are backward compatible with old schema.
**Fixes**
- [Issue #545](https://github.com/grafana/grafana/issues/545). Graph: Fix formatting negative values (axis formats, legend values)
- [Issue #460](https://github.com/grafana/grafana/issues/460). Graph: fix for max legend value when max value is zero
- [Issue #628](https://github.com/grafana/grafana/issues/628). Filtering: Fix for nested filters, changing a child filter could result in infinite recursion in some cases
- [Issue #528](https://github.com/grafana/grafana/issues/528). Graphite: Fix for graphite expressions parser failure when metric expressions starts with curly brace segment
# 1.6.1 (2014-06-24)
**New features or improvements**
- [Issue #360](https://github.com/grafana/grafana/issues/360). Ability to set y min/max for right y-axis (RR #519)
**Fixes**
- [Issue #500](https://github.com/grafana/grafana/issues/360). Fixes regex InfluxDB queries intoduced in 1.6.0
- [Issue #506](https://github.com/grafana/grafana/issues/506). Bug in when using % sign in legends (aliases), fixed by removing url decoding of metric names
- [Issue #522](https://github.com/grafana/grafana/issues/522). Series names and column name typeahead cache fix
- [Issue #504](https://github.com/grafana/grafana/issues/504). Fixed influxdb issue with raw query that caused wrong value column detection
- [Issue #526](https://github.com/grafana/grafana/issues/526). Default property that marks which datasource is default in config.js is now optional
- [Issue #342](https://github.com/grafana/grafana/issues/342). Auto-refresh caused 2 refreshes (and hence mulitple queries) each time (at least in firefox)
# 1.6.0 (2014-06-16)
#### New features or improvements
- New Y-axis formater for metric values that represent seconds (Issue #427) - thx @jippi
- Allow special characters in serie names (influxdb datasource), PR #390 - thx @majst01
- Refactoring of filterSrv (Issue #428), thx @Tetha
- New config for playlist feature. Set playlist_timespan to set default playlist interval (Issue #445) - thx @rmca
- New graphite function definition added isNonNull (PR #461), - thx @tmonk42
- New InfluxDB function difference add to function dropdown (PR #455)
- Added parameter to keepLastValue graphite function definition (default 100), Closes #459
- [Issue #427](https://github.com/grafana/grafana/issues/427). New Y-axis formater for metric values that represent seconds, Thanks @jippi
- [Issue #390](https://github.com/grafana/grafana/issues/390). Allow special characters in serie names (influxdb datasource), Thanks @majst01
- [Issue #428](https://github.com/grafana/grafana/issues/428). Refactoring of filterSrv, Thanks @Tetha
- [Issue #445](https://github.com/grafana/grafana/issues/445). New config for playlist feature. Set playlist_timespan to set default playlist interval, Thanks @rmca
- [Issue #461](https://github.com/grafana/grafana/issues/461). New graphite function definition added isNonNull, Thanks @tmonk42
- [Issue #455](https://github.com/grafana/grafana/issues/455). New InfluxDB function difference add to function dropdown
- [Issue #459](https://github.com/grafana/grafana/issues/459). Added parameter to keepLastValue graphite function definition (default 100)
[Issue #418](https://github.com/grafana/grafana/issues/418). to the browser cache when upgrading grafana and improve load performance
- [Issue #327](https://github.com/grafana/grafana/issues/327). Partial support for url encoded metrics when using Graphite datasource. Thanks @axe-felix
- [Issue #473](https://github.com/grafana/grafana/issues/473). Improvement to InfluxDB query editor and function/value column selection
- [Issue #375](https://github.com/grafana/grafana/issues/375). Initial support for filtering (templated queries) for InfluxDB. Thanks @mavimo
- [Issue #475](https://github.com/grafana/grafana/issues/475). Row editing and adding new panel is now a lot quicker and easier with the new row menu
- [Issue #211](https://github.com/grafana/grafana/issues/211). New datasource! Initial support for OpenTSDB, Thanks @mpage
- [Issue #492](https://github.com/grafana/grafana/issues/492). Improvement and polish to the OpenTSDB query editor
- [Issue #441](https://github.com/grafana/grafana/issues/441). Influxdb group by support, Thanks @piis3
- improved asset (css/js) build pipeline, added revision to css and js. Will remove issues related
to the browser cache when upgrading grafana and improve load performance (Fixes #418)
- Partial support for url encoded metrics when using Graphite datasource (PR #327) - thx @axe-felix
- Improvement to InfluxDB query editor and function/value column selection (Issue #473)
- Initial support for filtering (templated queries) for InfluxDB (PR #375) - thx @mavimo
- Row editing and adding new panel is now a lot quicker and easier with the new row menu (Issue #475)
- New datasource! Initial support for OpenTSDB (PR #211) - thx @mpage
- Improvement and polish to the OpenTSDB query editor (Issue #492)
- Influxdb group by support (Issue #441) thx @piis3
#### Changes
- Graphite panel is now renamed graph (Existing dashboards will still work)
- Add panel icon and Row edit button is replaced by the Row edit menu (Issue #475)
- [Issue #475](https://github.com/grafana/grafana/issues/475). Add panel icon and Row edit button is replaced by the Row edit menu
- New graphs now have a default empty query
- Add Row button now creates a row with default height of 250px (no longer opens dashboard settings modal)
- Clean up of config.sample.js, graphiteUrl removed (still works, but depricated, removed in future)
Use datasources config instead. panel_names removed from config.js. Use plugins.panels to add custom panels
- Graphite panel is now renamed graph (Existing dashboards will still work)
#### Fixes
- Graphite query lexer change, can now handle regex parameters for aliasSub function (Fixes #126)
- Filter option loading when having muliple nested filters now works better.
Options are now reloaded correctly and there are no multiple renders/refresh inbetween (#447),
After an option is changed and a nested template param is also reloaded, if the current value
exists after the options are reloaded the current selected value is kept (Closes #447, Closes #412)
- Legend Current value did not display when value was zero, Fixes #460
- Fix to series toggling bug that caused annotations to be hidden when toggling (hiding) series. Fixes #328
- Fix for graphite function selection menu that some times draws outside screen. It now displays upward (Fixes #293)
- Fix for exclusive series toggling (hold down CTRL, SHIFT or META key) and left click a series for exclusive toggling
CTRL does not work on MAC OSX but SHIFT or META should (depending on browser) (Closes #350, Fixes #472)
- [Issue #126](https://github.com/grafana/grafana/issues/126). Graphite query lexer change, can now handle regex parameters for aliasSub function
- [Issue #447](https://github.com/grafana/grafana/issues/447). Filter option loading when having muliple nested filters now works better. Options are now reloaded correctly and there are no multiple renders/refresh inbetween.
- [Issue #412](https://github.com/grafana/grafana/issues/412). After a filter option is changed and a nested template param is reloaded, if the current value exists after the options are reloaded the current selected value is kept.
- [Issue #460](https://github.com/grafana/grafana/issues/460). Legend Current value did not display when value was zero
- [Issue #328](https://github.com/grafana/grafana/issues/328). Fix to series toggling bug that caused annotations to be hidden when toggling/hiding series.
- [Issue #293](https://github.com/grafana/grafana/issues/293). Fix for graphite function selection menu that some times draws outside screen. It now displays upward
- [Issue #350](https://github.com/grafana/grafana/issues/350). Fix for exclusive series toggling (hold down CTRL, SHIFT or META key) and left click a series for exclusive toggling
- [Issue #472](https://github.com/grafana/grafana/issues/472). CTRL does not work on MAC OSX but SHIFT or META should (depending on browser)
# 1.5.4 (2014-05-13)
### New features and improvements
- InfluxDB enhancement: support for multiple hosts (with retries) and raw queries (Issue #318, thx @toddboom)
- InfluxDB enhancement: support for multiple hosts (with retries) and raw queries ([Issue #318](https://github.com/grafana/grafana/issues/318), thx @toddboom)
- Added rounding for graphites from and to time range filters
for very short absolute ranges (Issue #320)
- Increased resolution for graphite datapoints (maxDataPoints), now equal to panel pixel width. (Closes #5)
- Improvement to influxdb query editor, can now add where clause and alias (Issue #331, thanks @mavimo)
- New config setting for graphite datasource to control if json render request is POST or GET (Issue #345)
- Unsaved changes warning feature (Issue #324)
- Improvement to series toggling, CTRL+MouseClick on series name will now hide all others (Issue #350)
for very short absolute ranges ([Issue #320](https://github.com/grafana/grafana/issues/320))
- Increased resolution for graphite datapoints (maxDataPoints), now equal to panel pixel width. ([Issue #5](https://github.com/grafana/grafana/issues/5))
- Improvement to influxdb query editor, can now add where clause and alias ([Issue #331](https://github.com/grafana/grafana/issues/331), thanks @mavimo)
- New config setting for graphite datasource to control if json render request is POST or GET ([Issue #345](https://github.com/grafana/grafana/issues/345))
- Unsaved changes warning feature ([Issue #324](https://github.com/grafana/grafana/issues/324))
- Improvement to series toggling, CTRL+MouseClick on series name will now hide all others ([Issue #350](https://github.com/grafana/grafana/issues/350))
### Changes
- Graph default setting for Y-Min changed from zero to auto scalling (will not effect existing dashboards). (Issue #386) - thx @kamaradclimber
- Graph default setting for Y-Min changed from zero to auto scalling (will not effect existing dashboards). ([Issue #386](https://github.com/grafana/grafana/issues/386)) - thx @kamaradclimber
### Fixes
- Fixes to filters and "All" option. It now never uses "*" as value, but all options in a {node1, node2, node3} expression (Issue #228, #359)
- Fix for InfluxDB query generation with columns containing dots or dashes (Issue #369, #348) - Thanks to @jbripley
- Fixes to filters and "All" option. It now never uses "*" as value, but all options in a {node1, node2, node3} expression ([Issue #228](https://github.com/grafana/grafana/issues/228), #359)
- Fix for InfluxDB query generation with columns containing dots or dashes ([Issue #369](https://github.com/grafana/grafana/issues/369), #348) - Thanks to @jbripley
# 1.5.3 (2014-04-17)
- Add support for async scripted dashboards (Issue #274)
- Text panel now accepts html (for links to other dashboards, etc) (Issue #236)
- Fix for Text panel, now changes take effect directly (Issue #251)
- Fix when adding functions without params that did not cause graph to update (Issue #267)
- Graphite errors are now much easier to see and troubleshoot with the new inspector (Issue #265)
- Use influxdb aliases to distinguish between multiple columns (Issue #283)
- Correction to ms axis formater, now formats days correctly. (Issue #189)
- Css fix for Firefox and using top menu dropdowns in panel fullscren / edit mode (Issue #106)
- Browser page title is now Grafana - {{dashboard title}} (Issue #294)
- Disable auto refresh zooming in (every time you change to an absolute time range), refresh will be restored when you change time range back to relative (Issue #282)
- Add support for async scripted dashboards ([Issue #274](https://github.com/grafana/grafana/issues/274))
- Text panel now accepts html (for links to other dashboards, etc) ([Issue #236](https://github.com/grafana/grafana/issues/236))
- Fix for Text panel, now changes take effect directly ([Issue #251](https://github.com/grafana/grafana/issues/251))
- Fix when adding functions without params that did not cause graph to update ([Issue #267](https://github.com/grafana/grafana/issues/267))
- Graphite errors are now much easier to see and troubleshoot with the new inspector ([Issue #265](https://github.com/grafana/grafana/issues/265))
- Use influxdb aliases to distinguish between multiple columns ([Issue #283](https://github.com/grafana/grafana/issues/283))
- Correction to ms axis formater, now formats days correctly. ([Issue #189](https://github.com/grafana/grafana/issues/189))
- Css fix for Firefox and using top menu dropdowns in panel fullscren / edit mode ([Issue #106](https://github.com/grafana/grafana/issues/106))
- Browser page title is now Grafana - {{dashboard title}} ([Issue #294](https://github.com/grafana/grafana/issues/294))
- Disable auto refresh zooming in (every time you change to an absolute time range), refresh will be restored when you change time range back to relative ([Issue #282](https://github.com/grafana/grafana/issues/282))
- More graphite functions
# 1.5.2 (2014-03-24)
### New Features and improvements
- Support for second optional params for functions like aliasByNode (Issue #167). Read the wiki on the [Function Editor](https://github.com/torkelo/grafana/wiki/Graphite-Function-Editor) for more info.
- More functions added to InfluxDB query editor (Issue #218)
- Filters can now be used inside other filters (templated segments) (Issue #128)
- Support for second optional params for functions like aliasByNode ([Issue #167](https://github.com/grafana/grafana/issues/167)). Read the wiki on the [Function Editor](https://github.com/torkelo/grafana/wiki/Graphite-Function-Editor) for more info.
- More functions added to InfluxDB query editor ([Issue #218](https://github.com/grafana/grafana/issues/218))
- Filters can now be used inside other filters (templated segments) ([Issue #128](https://github.com/grafana/grafana/issues/128))
- More graphite functions added
### Fixes
- Float arguments now work for functions like scale (Issue #223)
- Float arguments now work for functions like scale ([Issue #223](https://github.com/grafana/grafana/issues/223))
- Fix for graphite function editor, the graph & target was not updated after adding a function and leaving default params as is #191
The zip files now contains a sub folder with project name and version prefix. (Issue #209)
The zip files now contains a sub folder with project name and version prefix. ([Issue #209](https://github.com/grafana/grafana/issues/209))
# 1.5.1 (2014-03-10)
### Fixes
@@ -95,22 +316,22 @@ Read this for more info:
# 1.5.0 (2014-03-09)
### New Features and improvements
- New function editor [video demo](http://youtu.be/I90WHRwE1ZM) (Issue #178)
- Links to function documentation from function editor (Issue #3)
- Reorder functions (Issue #130)
- New function editor [video demo](http://youtu.be/I90WHRwE1ZM) ([Issue #178](https://github.com/grafana/grafana/issues/178))
- Links to function documentation from function editor ([Issue #3](https://github.com/grafana/grafana/issues/3))
- Reorder functions ([Issue #130](https://github.com/grafana/grafana/issues/130))
- [Initial support for InfluxDB](https://github.com/torkelo/grafana/wiki/InfluxDB) as metric datasource (#103), need feedback!
- [Dashboard playlist](https://github.com/torkelo/grafana/wiki/Dashboard-playlist) (Issue #36)
- When adding aliasByNode smartly set node number (Issue #175)
- Support graphite identifiers with embedded colons (Issue #173)
- Typeahead & autocomplete when adding new function (Issue #164)
- [Dashboard playlist](https://github.com/torkelo/grafana/wiki/Dashboard-playlist) ([Issue #36](https://github.com/grafana/grafana/issues/36))
- When adding aliasByNode smartly set node number ([Issue #175](https://github.com/grafana/grafana/issues/175))
- Support graphite identifiers with embedded colons ([Issue #173](https://github.com/grafana/grafana/issues/173))
- Typeahead & autocomplete when adding new function ([Issue #164](https://github.com/grafana/grafana/issues/164))
- More graphite function definitions
- Make "ms" axis format include hour, day, weeks, month and year (Issue #149)
- Microsecond axis format (Issue #146)
- Specify template paramaters in URL (Issue #123)
- Make "ms" axis format include hour, day, weeks, month and year ([Issue #149](https://github.com/grafana/grafana/issues/149))
- Microsecond axis format ([Issue #146](https://github.com/grafana/grafana/issues/146))
- Specify template parameters in URL ([Issue #123](https://github.com/grafana/grafana/issues/123))
### Fixes
- Basic Auth fix (Issue #152)
- Fix to annotations with graphite source & null values (Issue #138)
- Basic Auth fix ([Issue #152](https://github.com/grafana/grafana/issues/152))
- Fix to annotations with graphite source & null values ([Issue #138](https://github.com/grafana/grafana/issues/138))
# 1.4.0 (2014-02-21)
### New Features
@@ -176,7 +397,7 @@ Read this for more info:
Thanks to everyone who contributed fixes and provided feedback :+1:
# 1.0.4 (2014-01-24)
- Fixes #28 - Relative time range caused 500 graphite error in some cases (thx rsommer for the fix)
- [Issue #28](https://github.com/grafana/grafana/issues/28) - Relative time range caused 500 graphite error in some cases (thx rsommer for the fix)
# 1.0.3 (2014-01-23)
- #9 Add Y-axis format for milliseconds
@@ -184,7 +405,7 @@ Thanks to everyone who contributed fixes and provided feedback :+1:
- #13 Relative time ranges now uses relative time ranges when issuing graphite query
# 1.0.2 (2014-01-21)
- Fixes #12, should now work ok without ElasticSearch
- [Issue #12](https://github.com/grafana/grafana/issues/12), should now work ok without ElasticSearch
# 1.0.1 (2014-01-21)
- Resize fix

135
README.md
View File

@@ -1,19 +1,22 @@
[Grafana](http://grafana.org) [![Build Status](https://api.travis-ci.org/grafana/grafana.png)](https://travis-ci.org/grafana/grafana)
=================
A beautiful, easy to use and feature rich Graphite dashboard replacement and graph editor. Visit [grafana.org](http://grafana.org) for screenshots, videos and feature descriptions.
[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) |
[IRC](http://webchat.freenode.net/?channels=grafana) |
[Email](mailto:contact@grafana.org)
![](http://grafana.org/assets/img/edit_dashboards.png)
Grafana is An open source, feature rich metrics dashboard and graph editor for
Graphite, InfluxDB & OpenTSDB.
![](http://grafana.org/assets/img/start_page_bg.png)
## Features
### Graphite Target Editor
- Graphite target expression parser
- Quickly add / edit / remove function ([video demo](http://youtu.be/I90WHRwE1ZM))
- Function parameters can be easily changed
- Quickly navigate graphite metric structure
- Templating
- Integrated links to function documentation
- Rearrange function order
- Native Graphite PNG render support
- Feature rich query composer
- Quickly add and edit functions & parameters
- Templated queries
- [See it in action](http://grafana.org/docs/features/graphite)
### Graphing
- Fast rendering, even over large timespans.
@@ -22,90 +25,62 @@ A beautiful, easy to use and feature rich Graphite dashboard replacement and gra
- Bars, Lines, Points.
- Smart Y-axis formating
- Series toggles & color selector
- Axis labels
- Legend values, and formating options
- Grid thresholds, axis labels
- [Annotations] (https://github.com/grafana/grafana/wiki/Annotations)
- [Annotations](http://grafana.org/docs/features/annotations)
### Dashboards
- Create and edit dashboards
- Drag and drop graphs to rearrange
- Set column spans and row heights
- Save & [search dashboards](https://github.com/grafana/grafana/wiki/Search-features)
- Create, edit, save & search dashboards
- Change column spans and row heights
- Drag and drop panels to rearrange
- Use InfluxDB or Elasticsearch as dashboard storage
- Import & export dashboard (json file)
- Import dashboard from Graphite
- Templating
- [Scripted dashboards](https://github.com/grafana/grafana/wiki/Scripted-dashboards) (generate from js script and url parameters)
- Flexible [time range controls](https://github.com/grafana/grafana/wiki/Time-range-controls)
- [Dashboard playlists](https://github.com/grafana/grafana/wiki/Dashboard-playlist)
- [Scripted dashboards](http://grafana.org/docs/features/scripted_dashboards)
- [Dashboard playlists](http://grafana.org/docs/features/playlist)
- [Time range controls](http://grafana.org/docs/features/time_range)
### InfluxDB
- [Use InfluxDB](https://github.com/grafana/grafana/wiki/InfluxDB) as metric datasource
- Use InfluxDB as a metric data source, annotation source and for dashboard storage
- Query editor with series and column typeahead, easy group by and function selection
# Requirements
Grafana is very easy to install. It is a client side web app with no backend. Any webserver will do. Optionally you will need ElasticSearch if you want to be able to save and load dashboards quickly instead of json files or local storage.
### OpenTSDB
- Use as metric data source
- Query editor with metric name typeahead and tag filtering
# Installation
- Download and extract the [latest release](https://github.com/grafana/grafana/releases).
- Rename `config.sample.js` to `config.js`, then change `graphiteUrl` and `elasticsearch` to point to the correct urls. The urls entered here must be reachable by your browser.
- Point your browser to the installation.
## Requirements
There are no dependencies, Grafana is a client side application that runs in your browser. It only needs a time series store where it can fetch metrics. If you use InfluxDB Grafana can use it to store dashboards. If you use Graphite or OpenTSDB you can use Elasticsearch to store dashboards or just use json files stored on disk.
To run from master:
- Clone this repository
- Start a web server in src folder
- Or create a optimized & minified build:
- npm install (requires nodejs)
- grunt build (requires grunt-cli)
## Installation
Head to [grafana.org](http://grafana.org) and [download](http://grafana.org/download/)
the latest release.
If you use ansible for provisioning and deployment [ansible-grafana](https://github.com/bobrik/ansible-grafana) should get you started.
Then follow the quick [setup & config guide](http://grafana.org/docs/). If you have any problems please
read the [troubleshooting guide](http://grafana.org/docs/troubleshooting).
When you have Grafana up an running, read the [Getting started](https://github.com/grafana/grafana/wiki/Getting-started) guide for
an introduction on how to use Grafana and/or watch [this video](https://www.youtube.com/watch?v=OUvJamHeMpw) for a guide in creating a new dashboard and for creating
templated dashboards.
## Documentation & Support
Be sure to read the [getting started guide](http://grafana.org/docs/features/intro) and the other
feature guides.
# Graphite server config
If you haven't used an alternative dashboard for graphite before you need to enable cross-domain origin request. For Apache 2.x:
```
Header set Access-Control-Allow-Origin "*"
Header set Access-Control-Allow-Methods "GET, OPTIONS"
Header set Access-Control-Allow-Headers "origin, authorization, accept"
```
Note that using "\*" leaves your graphite instance quite open so you might want to consider using "http://my.graphite-dom.ain" in place of "\*"
## Run from master
Grafana uses nodejs and grunt for asset management (css & javascript), unit test runner and javascript syntax verification.
- clone repository
- install nodejs
- npm install (in project root)
- npm install -g grunt-cli
- grunt (runt default task that will generate css files)
- grunt build (creates optimized & minified release)
- grunt release (same as grunt build but will also create tar & zip package)
- grunt test (executes jshint and unit tests)
If your Graphite web is proteced by basic authentication, you have to enable the HTTP verb OPTIONS, origin
(no wildcards are allowed in this case) and add Access-Control-Allow-Credentials. This looks like the following for Apache:
```
Header set Access-Control-Allow-Origin "http://mygrafana.com:5656"
Header set Access-Control-Allow-Credentials true
## Contribute
If you have any idea for an improvement or found a bug do not hesitate to open an issue.
And if you have time clone this repo and submit a pull request and help me make Grafana
the kickass metrics & devops dashboard we all dream about!
<Location />
AuthName "graphs restricted"
AuthType Basic
AuthUserFile /etc/apache2/htpasswd
<LimitExcept OPTIONS>
require valid-user
</LimitExcept>
</Location>
```
Before creating a pull request be sure that "grunt test" runs without any style or unit test errors, also
please [sign the CLA](http://grafana.org/docs/contributing/cla.html)
# Roadmap
- Improve and refine the target parser and editing
- Improve graphite import feature
- Refine and simplify common tasks
- More panel types (not just graphs)
- Use elasticsearch to search for metrics
- Improve template support
- Annotate graph by querying ElasticSearch for events (or other event sources)
# Contribute
If you have any idea for an improvement or found a bug do not hesitate to open an issue. And if you have time clone this repo and submit a pull request and help me make Grafana the kickass metrics & devops dashboard we all dream about!
Clone repository:
- npm install
- grunt server (starts development web server in src folder)
- grunt (runs jshint and less -> css compilation)
# Notice
This software is based on the great log dashboard [kibana](https://github.com/elasticsearch/kibana).
# License
## License
Grafana is distributed under Apache 2.0 License.

View File

@@ -1,18 +0,0 @@
{
"folders":
[
{
"follow_symlinks": true,
"path": ".",
"folder_exclude_patterns": [
"node_modules"
]
}
],
"settings":
{
"tab_size": 2,
"translate_tabs_to_spaces": true,
"trim_trailing_white_space_on_save": true
}
}

View File

@@ -1,4 +1,4 @@
{
"version": "1.5.4",
"url": "http://grafanarel.s3.amazonaws.com/grafana-1.5.4.tar.gz"
}
"version": "1.9.1",
"url": "http://grafanarel.s3.amazonaws.com/grafana-1.9.1.tar.gz"
}

View File

@@ -4,60 +4,66 @@
"company": "Coding Instinct AB"
},
"name": "grafana",
"version": "1.6.0",
"version": "1.9.1",
"repository": {
"type": "git",
"url": "http://github.com/torkelo/grafana.git"
},
"devDependencies": {
"rjs-build-analysis": "0.0.3",
"grunt": "~0.4.0",
"grunt-ngmin": "0.0.3",
"grunt-contrib-less": "~0.7.0",
"grunt-contrib-copy": "~0.4.1",
"grunt-git-describe": "~2.3.2",
"grunt-contrib-clean": "~0.5.0",
"grunt-contrib-cssmin": "~0.6.1",
"grunt-contrib-jshint": "~0.10.0",
"grunt-string-replace": "~0.2.4",
"grunt-contrib-htmlmin": "~0.1.3",
"grunt-contrib-requirejs": "~0.4.1",
"grunt-angular-templates": "^0.5.5",
"grunt-contrib-compress": "~0.5.2",
"grunt-contrib-uglify": "~0.2.4",
"load-grunt-tasks": "~0.2.0",
"glob": "~3.2.7",
"grunt-contrib-connect": "~0.5.0",
"mocha": "~1.16.1",
"expect.js": "~0.2.0",
"karma-script-launcher": "~0.1.0",
"karma-firefox-launcher": "~0.1.3",
"glob": "~3.2.7",
"grunt": "~0.4.0",
"grunt-angular-templates": "^0.5.5",
"grunt-cli": "~0.1.13",
"grunt-contrib-clean": "~0.5.0",
"grunt-contrib-compress": "~0.5.2",
"grunt-contrib-concat": "^0.4.0",
"grunt-contrib-connect": "~0.5.0",
"grunt-contrib-copy": "~0.5.0",
"grunt-contrib-cssmin": "~0.6.1",
"grunt-contrib-htmlmin": "~0.1.3",
"grunt-contrib-jshint": "~0.10.0",
"grunt-contrib-less": "~0.7.0",
"grunt-contrib-requirejs": "~0.4.1",
"grunt-contrib-uglify": "~0.2.4",
"grunt-contrib-watch": "^0.6.1",
"grunt-filerev": "^0.2.1",
"grunt-git-describe": "~2.3.2",
"grunt-karma": "~0.8.3",
"grunt-ngmin": "0.0.3",
"grunt-string-replace": "~0.2.4",
"grunt-usemin": "^2.1.1",
"jshint-stylish": "~0.1.5",
"karma": "~0.12.21",
"karma-chrome-launcher": "~0.1.4",
"karma-coffee-preprocessor": "~0.1.2",
"karma-coverage": "^0.2.5",
"karma-coveralls": "^0.1.4",
"karma-expect": "~1.1.0",
"karma-firefox-launcher": "~0.1.3",
"karma-html2js-preprocessor": "~0.1.0",
"karma-jasmine": "~0.2.2",
"requirejs": "~2.1.9",
"karma-requirejs": "~0.2.1",
"karma-coffee-preprocessor": "~0.1.2",
"karma-phantomjs-launcher": "~0.1.1",
"karma": "~0.12.16",
"grunt-karma": "~0.8.3",
"karma-mocha": "~0.1.4",
"karma-expect": "~1.1.0",
"grunt-cli": "~0.1.13",
"jshint-stylish": "~0.1.5",
"grunt-contrib-concat": "^0.4.0",
"grunt-usemin": "^2.1.1",
"grunt-filerev": "^0.2.1"
"karma-phantomjs-launcher": "~0.1.1",
"karma-requirejs": "~0.2.1",
"karma-script-launcher": "~0.1.0",
"load-grunt-tasks": "~0.2.0",
"mocha": "~1.16.1",
"requirejs": "~2.1.14",
"rjs-build-analysis": "0.0.3"
},
"engines": {
"node": "0.10.x",
"npm": "1.2.x"
},
"scripts": {
"test": "grunt test"
"test": "grunt test",
"coveralls": "grunt karma:coveralls && rm -rf ./coverage"
},
"license": "Apache License",
"dependencies": {
"grunt-jscs-checker": "^0.4.4"
"grunt-jscs-checker": "^0.4.4",
"karma-sinon": "^1.0.3",
"sinon": "^1.10.3"
}
}

View File

@@ -0,0 +1,55 @@
<br/>
<div class="row-fluid">
<div class="span6">
<ul>
<li>
<a href="http://grafana.org/docs#configuration" target="_blank">Configuration</a>
</li>
<li>
<a href="http://grafana.org/docs/troubleshooting" target="_blank">Troubleshooting</a>
</li>
<li>
<a href="http://grafana.org/docs/support" target="_blank">Support</a>
</li>
<li>
<a href="http://grafana.org/docs/features/intro" target="_blank">Getting started</a> (Must read!)
</li>
</ul>
</div>
<div class="span6">
<ul>
<li>
<a href="http://grafana.org/docs/features/graphing" target="_blank">Graphing</a>
</li>
<li>
<a href="http://grafana.org/docs/features/annotations" target="_blank">Annotations</a>
</li>
<li>
<a href="http://grafana.org/docs/features/graphite" target="_blank">Graphite</a>
</li>
<li>
<a href="http://grafana.org/docs/features/influxdb" target="_blank">InfluxDB</a>
</li>
<li>
<a href="http://grafana.org/docs/features/opentsdb" target="_blank">OpenTSDB</a>
</li>
</ul>
</div>
</div>
<br/>
<div class="row-fluid">
<div class="span12">
<ul>
<li>Ctrl+S saves the current dashboard</li>
<li>Ctrl+F Opens the dashboard finder</li>
<li>Ctrl+H Hide/show row controls</li>
<li>Click and drag graph title to move panel</li>
<li>Hit Escape to exit graph when in fullscreen or edit mode</li>
<li>Click the colored icon in the legend to change series color</li>
<li>Ctrl or Shift + Click legend name to hide other series</li>
</ul>
</div>
</div>

View File

@@ -4,21 +4,22 @@
define([
'angular',
'jquery',
'underscore',
'lodash',
'require',
'elasticjs',
'config',
'bootstrap',
'angular-route',
'angular-sanitize',
'angular-strap',
'angular-dragdrop',
'extend-jquery',
'bindonce'
'bindonce',
],
function (angular, $, _, appLevelRequire) {
function (angular, $, _, appLevelRequire, config) {
"use strict";
var app = angular.module('kibana', []),
var app = angular.module('grafana', []),
// we will keep a reference to each module defined before boot, so that we can
// go back and allow it to define new features later. Once we boot, this will be false
pre_boot_modules = [],
@@ -48,39 +49,9 @@ function (angular, $, _, appLevelRequire) {
return module;
};
app.safeApply = function ($scope, fn) {
switch($scope.$$phase) {
case '$apply':
// $digest hasn't started, we should be good
$scope.$eval(fn);
break;
case '$digest':
// waiting to $apply the changes
setTimeout(function () { app.safeApply($scope, fn); }, 10);
break;
default:
// clear to begin an $apply $$phase
$scope.$apply(fn);
break;
}
};
app.config(function ($routeProvider, $controllerProvider, $compileProvider, $filterProvider, $provide) {
$routeProvider
.when('/dashboard', {
templateUrl: 'app/partials/dashboard.html',
})
.when('/dashboard/:kbnType/:kbnId', {
templateUrl: 'app/partials/dashboard.html',
})
.when('/dashboard/:kbnType/:kbnId/:params', {
templateUrl: 'app/partials/dashboard.html'
})
.otherwise({
redirectTo: 'dashboard'
});
$routeProvider.otherwise({ redirectTo: config.default_route });
// this is how the internet told me to dynamically add modules :/
register_fns.controller = $controllerProvider.register;
register_fns.directive = $compileProvider.directive;
@@ -90,60 +61,72 @@ function (angular, $, _, appLevelRequire) {
});
var apps_deps = [
'elasticjs.service',
'$strap.directives',
'ngRoute',
'ngSanitize',
'ngDragDrop',
'kibana',
'$strap.directives',
'ang-drag-drop',
'grafana',
'pasvaz.bindonce'
];
var module_types = ['controllers', 'directives', 'factories', 'services', 'services.dashboard', 'filters'];
var module_types = ['controllers', 'directives', 'factories', 'services', 'filters', 'routes'];
_.each(module_types, function (type) {
var module_name = 'kibana.'+type;
var module_name = 'grafana.'+type;
// create the module
app.useModule(angular.module(module_name, []));
// push it into the apps dependencies
apps_deps.push(module_name);
});
// load the core components
require([
var preBootRequires = [
'services/all',
'features/all',
'controllers/all',
'directives/all',
'filters/all',
'components/partials',
], function () {
'routes/all',
];
// bootstrap the app
angular
.element(document)
.ready(function() {
$('body').attr('ng-controller', 'DashCtrl');
angular.bootstrap(document, apps_deps)
.invoke(['$rootScope', function ($rootScope) {
_.each(pre_boot_modules, function (module) {
_.extend(module, register_fns);
});
pre_boot_modules = false;
$rootScope.requireContext = appLevelRequire;
$rootScope.require = function (deps, fn) {
var $scope = this;
$scope.requireContext(deps, function () {
var deps = _.toArray(arguments);
// Check that this is a valid scope.
if($scope.$id) {
$scope.$apply(function () {
fn.apply($scope, deps);
});
}
});
};
}]);
});
_.each(config.plugins.dependencies, function(dep) {
preBootRequires.push('../plugins/' + dep);
});
app.boot = function() {
require(preBootRequires, function () {
// disable tool tip animation
$.fn.tooltip.defaults.animation = false;
// bootstrap the app
angular
.element(document)
.ready(function() {
angular.bootstrap(document, apps_deps)
.invoke(['$rootScope', function ($rootScope) {
_.each(pre_boot_modules, function (module) {
_.extend(module, register_fns);
});
pre_boot_modules = false;
$rootScope.requireContext = appLevelRequire;
$rootScope.require = function (deps, fn) {
var $scope = this;
$scope.requireContext(deps, function () {
var deps = _.toArray(arguments);
// Check that this is a valid scope.
if($scope.$id) {
$scope.$apply(function () {
fn.apply($scope, deps);
});
}
});
};
}]);
});
});
};
return app;
});

View File

@@ -10,18 +10,6 @@ function ($) {
$.fn.place_tt = (function () {
var defaults = {
offset: 5,
css: {
position : 'absolute',
top : -1000,
left : 0,
color : "#c8c8c8",
padding : '10px',
'font-size': '11pt',
'font-weight' : 200,
'background-color': '#1f1f1f',
'border-radius': '5px',
'z-index': 9999
}
};
return function (x, y, opts) {
@@ -29,10 +17,10 @@ function ($) {
return this.each(function () {
var $tooltip = $(this), width, height;
$tooltip.css(opts.css);
if (!$.contains(document.body, $tooltip[0])) {
$tooltip.appendTo(document.body);
}
$tooltip.addClass('grafana-tooltip');
$("#tooltip").remove();
$tooltip.appendTo(document.body);
width = $tooltip.outerWidth(true);
height = $tooltip.outerHeight(true);
@@ -44,4 +32,4 @@ function ($) {
})();
return $;
});
});

View File

@@ -1,27 +1,13 @@
define(['jquery','underscore','moment'],
define([
'jquery',
'lodash',
'moment'
],
function($, _, moment) {
'use strict';
var kbn = {};
/**
* Calculate a graph interval
*
* from:: Date object containing the start time
* to:: Date object containing the finish time
* size:: Calculate to approximately this many bars
* user_interval:: User specified histogram interval
*
*/
kbn.calculate_interval = function(from,to,size,user_interval) {
if(_.isObject(from)) {
from = from.valueOf();
}
if(_.isObject(to)) {
to = to.valueOf();
}
return user_interval === 0 ? kbn.round_interval((to - from)/size) : user_interval;
};
kbn.valueFormats = {};
kbn.round_interval = function(interval) {
switch (true) {
@@ -127,6 +113,28 @@ function($, _, moment) {
s: 1
};
kbn.calculateInterval = function(range, resolution, userInterval) {
var lowLimitMs = 1; // 1 millisecond default low limit
var intervalMs, lowLimitInterval;
if (userInterval) {
if (userInterval[0] === '>') {
lowLimitInterval = userInterval.slice(1);
lowLimitMs = kbn.interval_to_ms(lowLimitInterval);
}
else {
return userInterval;
}
}
intervalMs = kbn.round_interval((range.to.valueOf() - range.from.valueOf()) / resolution);
if (lowLimitMs > intervalMs) {
intervalMs = lowLimitMs;
}
return kbn.secondsToHms(intervalMs / 1000);
};
kbn.describe_interval = function (string) {
var matches = string.match(kbn.interval_regex);
if (!matches || !_.has(kbn.intervals_in_seconds, matches[2])) {
@@ -227,36 +235,36 @@ function($, _, moment) {
if (type === 0) {
roundUp ? dateTime.endOf('year') : dateTime.startOf('year');
} else if (type === 1) {
dateTime.add('years',num);
dateTime.add(num, 'years');
} else if (type === 2) {
dateTime.subtract('years',num);
dateTime.subtract(num, 'years');
}
break;
case 'M':
if (type === 0) {
roundUp ? dateTime.endOf('month') : dateTime.startOf('month');
} else if (type === 1) {
dateTime.add('months',num);
dateTime.add(num, 'months');
} else if (type === 2) {
dateTime.subtract('months',num);
dateTime.subtract(num, 'months');
}
break;
case 'w':
if (type === 0) {
roundUp ? dateTime.endOf('week') : dateTime.startOf('week');
} else if (type === 1) {
dateTime.add('weeks',num);
dateTime.add(num, 'weeks');
} else if (type === 2) {
dateTime.subtract('weeks',num);
dateTime.subtract(num, 'weeks');
}
break;
case 'd':
if (type === 0) {
roundUp ? dateTime.endOf('day') : dateTime.startOf('day');
} else if (type === 1) {
dateTime.add('days',num);
dateTime.add(num, 'days');
} else if (type === 2) {
dateTime.subtract('days',num);
dateTime.subtract(num, 'days');
}
break;
case 'h':
@@ -264,27 +272,27 @@ function($, _, moment) {
if (type === 0) {
roundUp ? dateTime.endOf('hour') : dateTime.startOf('hour');
} else if (type === 1) {
dateTime.add('hours',num);
dateTime.add(num, 'hours');
} else if (type === 2) {
dateTime.subtract('hours',num);
dateTime.subtract(num,'hours');
}
break;
case 'm':
if (type === 0) {
roundUp ? dateTime.endOf('minute') : dateTime.startOf('minute');
} else if (type === 1) {
dateTime.add('minutes',num);
dateTime.add(num, 'minutes');
} else if (type === 2) {
dateTime.subtract('minutes',num);
dateTime.subtract(num, 'minutes');
}
break;
case 's':
if (type === 0) {
roundUp ? dateTime.endOf('second') : dateTime.startOf('second');
} else if (type === 1) {
dateTime.add('seconds',num);
dateTime.add(num, 'seconds');
} else if (type === 2) {
dateTime.subtract('seconds',num);
dateTime.subtract(num, 'seconds');
}
break;
default:
@@ -302,262 +310,174 @@ 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.shortFormat = 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;
var factor = decimals ? Math.pow(10, Math.max(0, decimals)) : 1;
var formatted = String(Math.round(value * factor) / factor);
// if exponent return directly
if (formatted.indexOf('e') !== -1 || value === 0) {
return formatted;
}
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;
// 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;
if (precision < decimals) {
return (precision ? formatted : formatted + ".") + (String(factor)).substr(1, decimals - precision);
}
}
return (size.toFixed(decimals) + ext);
return formatted;
};
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 '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);
};
default:
return function(val) {
return val % 1 === 0 ? val : val.toFixed(decimals);
};
}
};
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.msFormat = function(size, decimals) {
if (size < 1000) {
return size.toFixed(0) + " ms";
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 (size < 60000) {
return (size / 1000).toFixed(decimals) + " s";
else if (Math.abs(size) < 60000) {
return kbn.toFixed(size / 1000, scaledDecimals + 3) + " s";
}
// Less than 1 hour, devide in minutes
else if (size < 3600000) {
return (size / 60000).toFixed(decimals) + " min";
else if (Math.abs(size) < 3600000) {
return kbn.toFixed(size / 60000, scaledDecimals + 5) + " min";
}
// Less than one day, devide in hours
else if (size < 86400000) {
return (size / 3600000).toFixed(decimals) + " hour";
else if (Math.abs(size) < 86400000) {
return kbn.toFixed(size / 3600000, scaledDecimals + 7) + " hour";
}
// Less than one year, devide in days
else if (size < 31536000000) {
return (size / 86400000).toFixed(decimals) + " day";
else if (Math.abs(size) < 31536000000) {
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 10 min, use seconds
if (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 (size < 3600) {
return (size / 60).toFixed(decimals) + " min";
else if (Math.abs(size) < 3600) {
return kbn.toFixed(size / 60, scaledDecimals + 1) + " min";
}
// Less than one day, devide in hours
else if (size < 86400) {
return (size / 3600).toFixed(decimals) + " hour";
else if (Math.abs(size) < 86400) {
return kbn.toFixed(size / 3600, scaledDecimals + 4) + " hour";
}
// Less than one week, devide in days
else if (size < 604800) {
return (size / 86400).toFixed(decimals) + " day";
else if (Math.abs(size) < 604800) {
return kbn.toFixed(size / 86400, scaledDecimals + 5) + " day";
}
// Less than one year, devide in week
else if (size < 31536000) {
return (size / 604800).toFixed(decimals) + " week";
else if (Math.abs(size) < 31536000) {
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) {
if (size < 1000) {
return size.toFixed(0) + " µ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 (size < 1000000) {
return (size / 1000).toFixed(decimals) + " ms";
else if (Math.abs(size) < 1000000) {
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 (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 (size < 1000000) {
return (size / 1000).toFixed(decimals) + " µs";
else if (Math.abs(size) < 1000000) {
return kbn.toFixed(size / 1000, scaledDecimals + 3) + " µs";
}
else if (size < 1000000000) {
return (size / 1000000).toFixed(decimals) + " ms";
else if (Math.abs(size) < 1000000000) {
return kbn.toFixed(size / 1000000, scaledDecimals + 6) + " ms";
}
else if (size < 60000000000){
return (size / 1000000000).toFixed(decimals) + " s";
else if (Math.abs(size) < 60000000000){
return kbn.toFixed(size / 1000000000, scaledDecimals + 9) + " s";
}
else {
return (size / 60000000000).toFixed(decimals) + " m";
return kbn.toFixed(size / 60000000000, scaledDecimals + 12) + " m";
}
};
kbn.slugifyForUrl = function(str) {
return str
.toLowerCase()
.replace(/[^\w ]+/g,'')
.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);
}
var match = str.match(new RegExp('^/(.*?)/(g?i?m?y?)$'));
return new RegExp(match[1], match[2]);
};
return kbn;
});

View File

@@ -1,5 +1,5 @@
define([
'underscore-src'
'lodash-src'
],
function () {
'use strict';
@@ -33,4 +33,4 @@ function () {
});
return _;
});
});

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,32 +3,34 @@
*/
require.config({
baseUrl: 'app',
urlArgs: 'bust=' + (new Date().getTime()),
paths: {
config: ['../config', '../config.sample'],
settings: 'components/settings',
kbn: 'components/kbn',
store: 'components/store',
css: '../vendor/require/css',
text: '../vendor/require/text',
moment: '../vendor/moment',
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',
'angular-sanitize': '../vendor/angular/angular-sanitize',
timepicker: '../vendor/angular/timepicker',
datepicker: '../vendor/angular/datepicker',
bindonce: '../vendor/angular/bindonce',
crypto: '../vendor/crypto.min',
spectrum: '../vendor/spectrum',
underscore: 'components/underscore.extended',
'underscore-src': '../vendor/underscore',
lodash: 'components/lodash.extended',
'lodash-src': '../vendor/lodash',
bootstrap: '../vendor/bootstrap/bootstrap',
jquery: '../vendor/jquery/jquery-1.8.0',
'jquery-ui': '../vendor/jquery/jquery-ui-1.10.3',
jquery: '../vendor/jquery/jquery-2.1.1.min',
'extend-jquery': 'components/extend-jquery',
@@ -39,18 +41,15 @@ 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.byte': '../vendor/jquery/jquery.flot.byte',
'jquery.flot.crosshair': '../vendor/jquery/jquery.flot.crosshair',
'jquery.flot.fillbelow': '../vendor/jquery/jquery.flot.fillbelow',
modernizr: '../vendor/modernizr-2.6.1',
elasticjs: '../vendor/elasticjs/elastic-angular-client',
'bootstrap-tagsinput': '../vendor/tagsinput/bootstrap-tagsinput',
},
shim: {
underscore: {
exports: '_'
},
spectrum: {
deps: ['jquery']
@@ -79,32 +78,25 @@ require.config({
// simple dependency declaration
//
'jquery-ui': ['jquery'],
'jquery.flot': ['jquery'],
'jquery.flot.byte': ['jquery', 'jquery.flot'],
'jquery.flot.pie': ['jquery', 'jquery.flot'],
'jquery.flot.events': ['jquery', 'jquery.flot'],
'jquery.flot.selection':['jquery', 'jquery.flot'],
'jquery.flot.stack': ['jquery', 'jquery.flot'],
'jquery.flot.stackpercent':['jquery', 'jquery.flot'],
'jquery.flot.time': ['jquery', 'jquery.flot'],
'angular-sanitize': ['angular'],
'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'],
elasticjs: ['angular', '../vendor/elasticjs/elastic'],
'bootstrap-tagsinput': ['jquery'],
},
waitSeconds: 60,

View File

@@ -1,5 +1,5 @@
define([
'underscore',
'lodash',
'crypto',
],
function (_, crypto) {
@@ -13,21 +13,19 @@ function (_, crypto) {
* @type {Object}
*/
var defaults = {
elasticsearch : "http://"+window.location.hostname+":9200",
datasources : {
default: {
url: "http://"+window.location.hostname+":8080",
default: true
}
datasources : {},
window_title_prefix : 'Grafana - ',
panels : {
'graph': { path: 'panels/graph' },
'singlestat': { path: 'panels/singlestat' },
'text': { path: 'panels/text' }
},
panels : ['graph', 'text'],
plugins : {},
default_route : '/dashboard/file/default.json',
grafana_index : 'grafana-dash',
elasticsearch_all_disabled : false,
timezoneOffset : null,
playlist_timespan : "1m",
unsaved_changes_warning : true
unsaved_changes_warning : true,
search : { max_results: 100 },
admin : {}
};
// This initializes a new hash on purpose, to avoid adding parameters to
@@ -57,28 +55,36 @@ function (_, crypto) {
return datasource;
};
// backward compatible with old config
if (options.graphiteUrl) {
settings.datasources = {
graphite: {
type: 'graphite',
url: options.graphiteUrl,
default: true
}
settings.datasources.graphite = {
type: 'graphite',
url: options.graphiteUrl,
default: true
};
}
if (options.elasticsearch) {
settings.datasources.elasticsearch = {
type: 'elasticsearch',
url: options.elasticsearch,
index: options.grafana_index,
grafanaDB: true
};
}
_.each(settings.datasources, function(datasource, key) {
datasource.name = key;
parseBasicAuth(datasource);
if (datasource.url) { parseBasicAuth(datasource); }
if (datasource.type === 'influxdb') { parseMultipleHosts(datasource); }
});
var elasticParsed = parseBasicAuth({ url: settings.elasticsearch });
settings.elasticsearchBasicAuth = elasticParsed.basicAuth;
settings.elasticsearch = elasticParsed.url;
if (settings.plugins.panels) {
settings.panels = _.union(settings.panels, settings.plugins.panels);
_.extend(settings.panels, settings.plugins.panels);
}
if (!settings.plugins.dependencies) {
settings.plugins.dependencies = [];
}
return settings;

View File

@@ -0,0 +1,20 @@
define([], function() {
'use strict';
return {
get: function(key) {
return window.localStorage[key];
},
set: function(key, value) {
window.localStorage[key] = value;
},
getBool: function(key) {
return window.localStorage[key] === 'true' ? true : false;
},
delete: function(key) {
window.localStorage.removeItem(key);
}
};
});

View File

@@ -0,0 +1,135 @@
define([
'lodash',
'kbn'
],
function (_, kbn) {
'use strict';
function TimeSeries(opts) {
this.datapoints = opts.datapoints;
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) {
if (!aliasOrRegex) { return false; }
if (aliasOrRegex[0] === '/') {
var regex = kbn.stringToJsRegex(aliasOrRegex);
return seriesAlias.match(regex) != null;
}
return aliasOrRegex === seriesAlias;
}
function translateFillOption(fill) {
return fill === 0 ? 0.001 : fill/10;
}
TimeSeries.prototype.applySeriesOverrides = function(overrides) {
this.lines = {};
this.points = {};
this.bars = {};
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.alias)) {
continue;
}
if (override.lines !== void 0) { this.lines.show = override.lines; }
if (override.points !== void 0) { this.points.show = override.points; }
if (override.bars !== void 0) { this.bars.show = override.bars; }
if (override.fill !== void 0) { this.lines.fill = translateFillOption(override.fill); }
if (override.stack !== void 0) { this.stack = override.stack; }
if (override.linewidth !== void 0) { this.lines.lineWidth = override.linewidth; }
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.yaxis = override.yaxis;
}
}
};
TimeSeries.prototype.getFlotPairs = function (fillStyle) {
var result = [];
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';
var currentTime;
var currentValue;
for (var i = 0; i < this.datapoints.length; i++) {
currentValue = this.datapoints[i][0];
currentTime = this.datapoints[i][1];
if (currentValue === null) {
if (ignoreNulls) { continue; }
if (nullAsZero) {
currentValue = 0;
}
}
if (_.isNumber(currentValue)) {
this.stats.total += currentValue;
this.allIsNull = false;
}
if (currentValue > this.stats.max) {
this.stats.max = currentValue;
}
if (currentValue < this.stats.min) {
this.stats.min = currentValue;
}
result.push([currentTime, currentValue]);
}
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.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

@@ -1,6 +1,7 @@
define([
'./dash',
'./dashLoader',
'./grafanaCtrl',
'./dashboardCtrl',
'./dashboardNavCtrl',
'./row',
'./submenuCtrl',
'./pulldown',
@@ -12,4 +13,8 @@ define([
'./playlistCtrl',
'./inspectCtrl',
'./opentsdbTargetCtrl',
'./annotationsEditorCtrl',
'./templateEditorCtrl',
'./sharePanelCtrl',
'./jsonEditorCtrl',
], function () {});

View File

@@ -0,0 +1,76 @@
define([
'angular',
'lodash',
'jquery'
],
function (angular, _, $) {
'use strict';
var module = angular.module('grafana.controllers');
module.controller('AnnotationsEditorCtrl', function($scope, datasourceSrv) {
var annotationDefaults = {
name: '',
datasource: null,
showLine: true,
iconColor: '#C0C6BE',
lineColor: 'rgba(255, 96, 96, 0.592157)',
iconSize: 13,
enable: true
};
$scope.init = function() {
$scope.editor = { index: 0 };
$scope.datasources = datasourceSrv.getAnnotationSources();
$scope.annotations = $scope.dashboard.annotations.list;
$scope.reset();
$scope.$watch('editor.index', function(newVal) {
if (newVal !== 2) {
$scope.reset();
}
});
};
$scope.datasourceChanged = function() {
$scope.currentDatasource = _.findWhere($scope.datasources, { name: $scope.currentAnnotation.datasource });
if (!$scope.currentDatasource) {
$scope.currentDatasource = $scope.datasources[0];
}
};
$scope.edit = function(annotation) {
$scope.currentAnnotation = annotation;
$scope.currentIsNew = false;
$scope.datasourceChanged();
$scope.editor.index = 2;
$(".tooltip.in").remove();
};
$scope.reset = function() {
$scope.currentAnnotation = angular.copy(annotationDefaults);
$scope.currentIsNew = true;
$scope.datasourceChanged();
$scope.currentAnnotation.datasource = $scope.currentDatasource.name;
};
$scope.update = function() {
$scope.reset();
$scope.editor.index = 0;
};
$scope.add = function() {
$scope.annotations.push($scope.currentAnnotation);
$scope.reset();
$scope.editor.index = 0;
};
$scope.removeAnnotation = function(annotation) {
var index = _.indexOf($scope.annotations, annotation);
$scope.annotations.splice(index, 1);
};
});
});

View File

@@ -0,0 +1,108 @@
define([
'angular',
'lodash',
'moment',
'store'
],
function (angular, _, moment, store) {
'use strict';
var module = angular.module('grafana.controllers');
var consoleEnabled = store.getBool('grafanaConsole');
if (!consoleEnabled) {
return;
}
var events = [];
function ConsoleEvent(type, title, data) {
this.type = type;
this.title = title;
this.data = data;
this.time = moment().format('hh:mm:ss');
if (data.config) {
this.method = data.config.method;
this.elapsed = (new Date().getTime() - data.config.$grafana_timestamp) + ' ms';
if (data.config.params && data.config.params.q) {
this.field2 = data.config.params.q;
}
if (_.isString(data.config.data)) {
this.field2 = data.config.data;
}
if (data.status !== 200) {
this.error = true;
this.field3 = data.data;
}
if (_.isArray(data.data)) {
this.extractTimeseriesInfo(data.data);
}
}
}
ConsoleEvent.prototype.extractTimeseriesInfo = function(series) {
if (series.length === 0) {
return;
}
var points = 0;
var ok = false;
if (series[0].datapoints) {
points = _.reduce(series, function(memo, val) {
return memo + val.datapoints.length;
}, 0);
ok = true;
}
if (series[0].columns) {
points = _.reduce(series, function(memo, val) {
return memo + val.points.length;
}, 0);
ok = true;
}
if (ok) {
this.field1 = '(' + series.length + ' series';
this.field1 += ', ' + points + ' points)';
}
};
module.config(function($provide, $httpProvider) {
$provide.factory('mupp', function($q) {
return {
'request': function(config) {
if (config.inspect) {
config.$grafana_timestamp = new Date().getTime();
}
return config;
},
'response': function(response) {
if (response.config.inspect) {
events.push(new ConsoleEvent(response.config.inspect.type, response.config.url, response));
}
return response;
},
'requestError': function(rejection) {
console.log('requestError', rejection);
return $q.reject(rejection);
},
'responseError': function (rejection) {
var inspect = rejection.config.inspect || { type: 'error' };
events.push(new ConsoleEvent(inspect.type, rejection.config.url, rejection));
return $q.reject(rejection);
}
};
});
$httpProvider.interceptors.push('mupp');
});
module.controller('ConsoleCtrl', function($scope) {
$scope.events = events;
});
});

View File

@@ -1,152 +0,0 @@
/** @scratch /index/0
* = Kibana
*
* // Why can't I have a preamble here?
*
* == Introduction
*
* Kibana is an open source (Apache Licensed), browser based analytics and search dashboard for
* ElasticSearch. Kibana is a snap to setup and start using. Written entirely in HTML and Javascript
* it requires only a plain webserver, Kibana requires no fancy server side components.
* Kibana strives to be easy to get started with, while also being flexible and powerful, just like
* Elasticsearch.
*
* include::configuration/config.js.asciidoc[]
*
* include::panels.asciidoc[]
*
*/
define([
'angular',
'jquery',
'config',
'underscore',
'services/all',
'services/dashboard/all'
],
function (angular, $, config, _) {
"use strict";
var module = angular.module('kibana.controllers');
module.controller('DashCtrl', function(
$scope, $rootScope, $timeout, ejsResource, dashboard, filterSrv, dashboardKeybindings,
alertSrv, panelMove, keyboardManager, grafanaVersion) {
$scope.requiredElasticSearchVersion = ">=0.90.3";
$scope.editor = {
index: 0
};
$scope.grafanaVersion = grafanaVersion[0] === '@' ? 'master' : grafanaVersion;
// For moving stuff around the dashboard.
$scope.panelMoveDrop = panelMove.onDrop;
$scope.panelMoveStart = panelMove.onStart;
$scope.panelMoveStop = panelMove.onStop;
$scope.panelMoveOver = panelMove.onOver;
$scope.panelMoveOut = panelMove.onOut;
$scope.init = function() {
$scope.config = config;
// Make stuff, including underscore.js available to views
$scope._ = _;
$scope.dashboard = dashboard;
$scope.dashAlerts = alertSrv;
$scope.filter = filterSrv;
$scope.filter.init(dashboard.current);
$rootScope.$on("dashboard-loaded", function(event, dashboard) {
$scope.filter.init(dashboard);
});
// Clear existing alerts
alertSrv.clearAll();
$scope.reset_row();
$scope.ejs = ejsResource(config.elasticsearch, config.elasticsearchBasicAuth);
$scope.bindKeyboardShortcuts();
};
$scope.bindKeyboardShortcuts = dashboardKeybindings.shortcuts;
$scope.isPanel = function(obj) {
if(!_.isNull(obj) && !_.isUndefined(obj) && !_.isUndefined(obj.type)) {
return true;
} else {
return false;
}
};
$scope.add_row = function(dash, row) {
dash.rows.push(row);
};
$scope.add_row_default = function() {
$scope.reset_row();
$scope.row.title = 'New row';
$scope.add_row(dashboard.current, $scope.row);
};
$scope.reset_row = function() {
$scope.row = {
title: '',
height: '250px',
editable: true,
};
};
$scope.row_style = function(row) {
return { 'min-height': row.collapse ? '5px' : row.height };
};
$scope.panel_path =function(type) {
if(type) {
return 'app/panels/'+type.replace(".","/");
} else {
return false;
}
};
$scope.edit_path = function(type) {
var p = $scope.panel_path(type);
if(p) {
return p+'/editor.html';
} else {
return false;
}
};
$scope.setEditorTabs = function(panelMeta) {
$scope.editorTabs = ['General','Panel'];
if(!_.isUndefined(panelMeta.editorTabs)) {
$scope.editorTabs = _.union($scope.editorTabs,_.pluck(panelMeta.editorTabs,'title'));
}
return $scope.editorTabs;
};
// This is whoafully incomplete, but will do for now
$scope.parse_error = function(data) {
var _error = data.match("nested: (.*?);");
return _.isNull(_error) ? data : _error[1];
};
$scope.colors = [
"#7EB26D","#EAB839","#6ED0E0","#EF843C","#E24D42","#1F78C1","#BA43A9","#705DA0", //1
"#508642","#CCA300","#447EBC","#C15C17","#890F02","#0A437C","#6D1F62","#584477", //2
"#B7DBAB","#F4D598","#70DBED","#F9BA8F","#F29191","#82B5D8","#E5A8E2","#AEA2E0", //3
"#629E51","#E5AC0E","#64B0C8","#E0752D","#BF1B00","#0A50A1","#962D82","#614D93", //4
"#9AC48A","#F2C96D","#65C5DB","#F9934E","#EA6460","#5195CE","#D683CE","#806EB7", //5
"#3F6833","#967302","#2F575E","#99440A","#58140C","#052B51","#511749","#3F2B5B", //6
"#E0F9D7","#FCEACA","#CFFAFF","#F9E2D2","#FCE2DE","#BADFF4","#F9D9F9","#DEDAF7" //7
];
$scope.init();
});
});

View File

@@ -1,172 +0,0 @@
define([
'angular',
'underscore',
'moment'
],
function (angular, _, moment) {
'use strict';
var module = angular.module('kibana.controllers');
module.controller('dashLoader', function($scope, $rootScope, $http, dashboard, alertSrv, $location, playlistSrv) {
$scope.loader = dashboard.current.loader;
$scope.init = function() {
$scope.gist_pattern = /(^\d{5,}$)|(^[a-z0-9]{10,}$)|(gist.github.com(\/*.*)\/[a-z0-9]{5,}\/*$)/;
$scope.gist = $scope.gist || {};
$scope.elasticsearch = $scope.elasticsearch || {};
$rootScope.$on('save-dashboard', function() {
$scope.elasticsearch_save('dashboard', false);
});
$rootScope.$on('zoom-out', function() {
$scope.zoom(2);
});
};
$scope.exitFullscreen = function() {
$rootScope.$emit('panel-fullscreen-exit');
};
$scope.showDropdown = function(type) {
if(_.isUndefined(dashboard.current.loader)) {
return true;
}
var _l = dashboard.current.loader;
if(type === 'load') {
return (_l.load_elasticsearch || _l.load_gist || _l.load_local);
}
if(type === 'save') {
return (_l.save_elasticsearch || _l.save_gist || _l.save_local || _l.save_default);
}
if(type === 'share') {
return (_l.save_temp);
}
return false;
};
$scope.set_default = function() {
if(dashboard.set_default($location.path())) {
alertSrv.set('Home Set','This page has been set as your default dashboard','success',5000);
} else {
alertSrv.set('Incompatible Browser','Sorry, your browser is too old for this feature','error',5000);
}
};
$scope.purge_default = function() {
if(dashboard.purge_default()) {
alertSrv.set('Local Default Clear','Your default dashboard has been reset to the default',
'success',5000);
} else {
alertSrv.set('Incompatible Browser','Sorry, your browser is too old for this feature','error',5000);
}
};
$scope.elasticsearch_save = function(type,ttl) {
dashboard.elasticsearch_save(type, dashboard.current.title, ttl)
.then(function(result) {
if(_.isUndefined(result._id)) {
alertSrv.set('Save failed','Dashboard could not be saved to Elasticsearch','error',5000);
return;
}
alertSrv.set('Dashboard Saved', 'Dashboard has been saved to Elasticsearch as "' + result._id + '"','success', 5000);
if(type === 'temp') {
$scope.share = dashboard.share_link(dashboard.current.title,'temp',result._id);
}
$rootScope.$emit('dashboard-saved', dashboard.current);
});
};
$scope.elasticsearch_delete = function(id) {
if (!confirm('Are you sure you want to delete dashboard?')) {
return;
}
dashboard.elasticsearch_delete(id).then(
function(result) {
if(!_.isUndefined(result)) {
if(result.found) {
alertSrv.set('Dashboard Deleted',id+' has been deleted','success',5000);
// Find the deleted dashboard in the cached list and remove it
var toDelete = _.where($scope.elasticsearch.dashboards,{_id:id})[0];
$scope.elasticsearch.dashboards = _.without($scope.elasticsearch.dashboards,toDelete);
} else {
alertSrv.set('Dashboard Not Found','Could not find '+id+' in Elasticsearch','warning',5000);
}
} else {
alertSrv.set('Dashboard Not Deleted','An error occurred deleting the dashboard','error',5000);
}
}
);
};
$scope.save_gist = function() {
dashboard.save_gist($scope.gist.title).then(function(link) {
if (!_.isUndefined(link)) {
$scope.gist.last = link;
alertSrv.set('Gist saved','You will be able to access your exported dashboard file at '+
'<a href="'+link+'">'+link+'</a> in a moment','success');
} else {
alertSrv.set('Save failed','Gist could not be saved','error',5000);
}
});
};
$scope.gist_dblist = function(id) {
dashboard.gist_list(id).then(function(files) {
if (files && files.length > 0) {
$scope.gist.files = files;
} else {
alertSrv.set('Gist Failed','Could not retrieve dashboard list from gist','error',5000);
}
});
};
// function $scope.zoom
// factor :: Zoom factor, so 0.5 = cuts timespan in half, 2 doubles timespan
$scope.zoom = function(factor) {
var _range = this.filter.timeRange();
var _timespan = (_range.to.valueOf() - _range.from.valueOf());
var _center = _range.to.valueOf() - _timespan/2;
var _to = (_center + (_timespan*factor)/2);
var _from = (_center - (_timespan*factor)/2);
// If we're not already looking into the future, don't.
if(_to > Date.now() && _range.to < Date.now()) {
var _offset = _to - Date.now();
_from = _from - _offset;
_to = Date.now();
}
this.filter.setTime({
from:moment.utc(_from).toDate(),
to:moment.utc(_to).toDate(),
});
};
$scope.openSaveDropdown = function() {
$scope.isFavorite = playlistSrv.isCurrentFavorite();
};
$scope.markAsFavorite = function() {
playlistSrv.markAsFavorite();
$scope.isFavorite = true;
};
$scope.removeAsFavorite = function() {
playlistSrv.removeAsFavorite(dashboard.current);
$scope.isFavorite = false;
};
$scope.stopPlaylist = function() {
playlistSrv.stop(1);
};
});
});

View File

@@ -0,0 +1,131 @@
define([
'angular',
'jquery',
'config',
'lodash',
],
function (angular, $, config, _) {
"use strict";
var module = angular.module('grafana.controllers');
module.controller('DashboardCtrl', function(
$scope,
$rootScope,
dashboardKeybindings,
timeSrv,
templateValuesSrv,
dashboardSrv,
dashboardViewStateSrv,
$timeout) {
$scope.editor = { index: 0 };
$scope.panelNames = _.map(config.panels, function(value, key) { return key; });
var resizeEventTimeout;
this.init = function(dashboardData) {
$scope.availablePanels = config.panels;
$scope.reset_row();
$scope.registerWindowResizeEvent();
$scope.onAppEvent('show-json-editor', $scope.showJsonEditor);
$scope.setupDashboard(dashboardData);
};
$scope.registerWindowResizeEvent = function() {
angular.element(window).bind('resize', function() {
$timeout.cancel(resizeEventTimeout);
resizeEventTimeout = $timeout(function() { $scope.$broadcast('render'); }, 200);
});
};
$scope.setupDashboard = function(dashboardData) {
$rootScope.performance.dashboardLoadStart = new Date().getTime();
$rootScope.performance.panelsInitialized = 0;
$rootScope.performance.panelsRendered = 0;
$scope.dashboard = dashboardSrv.create(dashboardData);
$scope.dashboardViewState = dashboardViewStateSrv.create($scope);
// init services
timeSrv.init($scope.dashboard);
templateValuesSrv.init($scope.dashboard, $scope.dashboardViewState);
$scope.checkFeatureToggles();
dashboardKeybindings.shortcuts($scope);
$scope.setWindowTitleAndTheme();
$scope.appEvent("dashboard-loaded", $scope.dashboard);
};
$scope.setWindowTitleAndTheme = function() {
window.document.title = config.window_title_prefix + $scope.dashboard.title;
$scope.grafana.style = $scope.dashboard.style;
};
$scope.isPanel = function(obj) {
if(!_.isNull(obj) && !_.isUndefined(obj) && !_.isUndefined(obj.type)) {
return true;
} else {
return false;
}
};
$scope.add_row = function(dash, row) {
dash.rows.push(row);
};
$scope.add_row_default = function() {
$scope.reset_row();
$scope.row.title = 'New row';
$scope.add_row($scope.dashboard, $scope.row);
};
$scope.reset_row = function() {
$scope.row = {
title: '',
height: '250px',
editable: true,
};
};
$scope.panelEditorPath = function(type) {
return 'app/' + config.panels[type].path + '/editor.html';
};
$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.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.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;
}
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

@@ -0,0 +1,172 @@
define([
'angular',
'lodash',
'moment',
'config',
'store',
'filesaver'
],
function (angular, _, moment, config, store) {
'use strict';
var module = angular.module('grafana.controllers');
module.controller('DashboardNavCtrl', function($scope, $rootScope, alertSrv, $location, playlistSrv, datasourceSrv, timeSrv) {
$scope.init = function() {
$scope.db = datasourceSrv.getGrafanaDB();
$scope.onAppEvent('save-dashboard', $scope.saveDashboard);
$scope.onAppEvent('delete-dashboard', $scope.deleteDashboard);
$scope.onAppEvent('zoom-out', function() {
$scope.zoom(2);
});
};
$scope.set_default = function() {
store.set('grafanaDashboardDefault', $location.path());
alertSrv.set('Home Set','This page has been set as your default dashboard','success',5000);
};
$scope.purge_default = function() {
store.delete('grafanaDashboardDefault');
alertSrv.set('Local Default Clear','Your default dashboard has been reset to the default','success', 5000);
};
$scope.saveForSharing = function() {
var clone = angular.copy($scope.dashboard);
clone.temp = true;
$scope.db.saveDashboard(clone)
.then(function(result) {
$scope.share = { url: result.url, title: result.title };
}, function(err) {
alertSrv.set('Save for sharing failed', err, 'error',5000);
});
};
$scope.passwordCache = function(pwd) {
if (!window.sessionStorage) { return null; }
if (!pwd) { return window.sessionStorage["grafanaAdminPassword"]; }
window.sessionStorage["grafanaAdminPassword"] = pwd;
};
$scope.isAdmin = function() {
if (!config.admin || !config.admin.password) { return true; }
if ($scope.passwordCache() === config.admin.password) { return true; }
var password = window.prompt("Admin password", "");
$scope.passwordCache(password);
if (password === config.admin.password) { return true; }
alertSrv.set('Save failed', 'Password incorrect', 'error');
return false;
};
$scope.openSearch = function() {
$scope.appEvent('show-dash-editor', { src: 'app/partials/search.html' });
};
$scope.saveDashboard = function() {
if (!$scope.isAdmin()) { return false; }
var clone = angular.copy($scope.dashboard);
$scope.db.saveDashboard(clone)
.then(function(result) {
$scope.appEvent('alert-success', ['Dashboard saved', 'Saved as ' + result.title]);
if (result.url !== $location.path()) {
$location.search({});
$location.path(result.url);
}
$rootScope.$emit('dashboard-saved', $scope.dashboard);
}, function(err) {
$scope.appEvent('alert-error', ['Save failed', err]);
});
};
$scope.deleteDashboard = function(evt, options) {
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) {
$scope.appEvent('dashboard-deleted', id);
$scope.appEvent('alert-success', ['Dashboard Deleted', id + ' has been deleted']);
}, function(err) {
$scope.appEvent('alert-error', ['Deleted failed', err]);
});
};
$scope.exportDashboard = function() {
var blob = new Blob([angular.toJson($scope.dashboard, true)], { type: "application/json;charset=utf-8" });
window.saveAs(blob, $scope.dashboard.title + '-' + new Date().getTime());
};
$scope.zoom = function(factor) {
var range = timeSrv.timeRange();
var timespan = (range.to.valueOf() - range.from.valueOf());
var center = range.to.valueOf() - timespan/2;
var to = (center + (timespan*factor)/2);
var from = (center - (timespan*factor)/2);
if(to > Date.now() && range.to <= Date.now()) {
var offset = to - Date.now();
from = from - offset;
to = Date.now();
}
timeSrv.setTime({
from: moment.utc(from).toDate(),
to: moment.utc(to).toDate(),
});
};
$scope.styleUpdated = function() {
$scope.grafana.style = $scope.dashboard.style;
};
$scope.editJson = function() {
$scope.appEvent('show-json-editor', { object: $scope.dashboard });
};
$scope.openSaveDropdown = function() {
$scope.isFavorite = playlistSrv.isCurrentFavorite($scope.dashboard);
$scope.saveDropdownOpened = true;
};
$scope.markAsFavorite = function() {
playlistSrv.markAsFavorite($scope.dashboard);
$scope.isFavorite = true;
};
$scope.removeAsFavorite = function() {
playlistSrv.removeAsFavorite($scope.dashboard);
$scope.isFavorite = false;
};
$scope.stopPlaylist = function() {
playlistSrv.stop(1);
};
});
});

View File

@@ -0,0 +1,112 @@
define([
'angular',
'config',
'lodash',
'jquery',
'store',
],
function (angular, config, _, $, store) {
"use strict";
var module = angular.module('grafana.controllers');
module.controller('GrafanaCtrl', function($scope, alertSrv, utilSrv, grafanaVersion, $rootScope, $controller) {
$scope.grafanaVersion = grafanaVersion[0] === '@' ? 'master' : grafanaVersion;
$scope._ = _;
$rootScope.profilingEnabled = store.getBool('profilingEnabled');
$rootScope.performance = { loadStart: new Date().getTime() };
$scope.init = function() {
if ($rootScope.profilingEnabled) { $scope.initProfiling(); }
alertSrv.init();
utilSrv.init();
$scope.dashAlerts = alertSrv;
$scope.grafana = { style: 'dark' };
};
$scope.toggleConsole = function() {
$scope.consoleEnabled = !$scope.consoleEnabled;
store.set('grafanaConsole', $scope.consoleEnabled);
};
$scope.initDashboard = function(dashboardData, viewScope) {
$controller('DashboardCtrl', { $scope: viewScope }).init(dashboardData);
};
$rootScope.onAppEvent = function(name, callback) {
var unbind = $rootScope.$on(name, callback);
this.$on('$destroy', unbind);
};
$rootScope.appEvent = function(name, payload) {
$rootScope.$emit(name, payload);
};
$rootScope.colors = [
"#7EB26D","#EAB839","#6ED0E0","#EF843C","#E24D42","#1F78C1","#BA43A9","#705DA0", //1
"#508642","#CCA300","#447EBC","#C15C17","#890F02","#0A437C","#6D1F62","#584477", //2
"#B7DBAB","#F4D598","#70DBED","#F9BA8F","#F29191","#82B5D8","#E5A8E2","#AEA2E0", //3
"#629E51","#E5AC0E","#64B0C8","#E0752D","#BF1B00","#0A50A1","#962D82","#614D93", //4
"#9AC48A","#F2C96D","#65C5DB","#F9934E","#EA6460","#5195CE","#D683CE","#806EB7", //5
"#3F6833","#967302","#2F575E","#99440A","#58140C","#052B51","#511749","#3F2B5B", //6
"#E0F9D7","#FCEACA","#CFFAFF","#F9E2D2","#FCE2DE","#BADFF4","#F9D9F9","#DEDAF7" //7
];
$scope.getTotalWatcherCount = function() {
var count = 0;
var scopes = 0;
var root = $(document.getElementsByTagName('body'));
var f = function (element) {
if (element.data().hasOwnProperty('$scope')) {
scopes++;
angular.forEach(element.data().$scope.$$watchers, function () {
count++;
});
}
angular.forEach(element.children(), function (childElement) {
f($(childElement));
});
};
f(root);
$rootScope.performance.scopeCount = scopes;
return count;
};
$scope.initProfiling = function() {
var count = 0;
$scope.$watch(function digestCounter() { count++; }, function() { });
$scope.onAppEvent('dashboard-loaded', function() {
count = 0;
setTimeout(function() {
console.log("Dashboard::Performance Total Digests: " + count);
console.log("Dashboard::Performance Total Watchers: " + $scope.getTotalWatcherCount());
console.log("Dashboard::Performance Total ScopeCount: " + $rootScope.performance.scopeCount);
var timeTaken = $rootScope.performance.allPanelsInitialized - $rootScope.performance.dashboardLoadStart;
console.log("Dashboard::Performance - All panels initialized in " + timeTaken + " ms");
// measure digest performance
var rootDigestStart = window.performance.now();
for (var i = 0; i < 30; i++) {
$rootScope.$apply();
}
console.log("Dashboard::Performance Root Digest " + ((window.performance.now() - rootDigestStart) / 30));
}, 3000);
});
};
$scope.init();
});
});

View File

@@ -1,18 +1,18 @@
define([
'angular',
'app',
'underscore'
'lodash',
'kbn'
],
function (angular, app, _) {
function (angular, app, _, kbn) {
'use strict';
var module = angular.module('kibana.controllers');
var module = angular.module('grafana.controllers');
module.controller('GraphiteImportCtrl', function($scope, $rootScope, $timeout, datasourceSrv, dashboard) {
module.controller('GraphiteImportCtrl', function($scope, $rootScope, $timeout, datasourceSrv, $location) {
$scope.init = function() {
console.log('hej!');
$scope.datasources = datasourceSrv.listOptions();
$scope.datasources = datasourceSrv.getMetricSources();
$scope.setDatasource(null);
};
@@ -68,23 +68,24 @@ function (angular, app, _) {
currentRow = angular.copy(rowTemplate);
var newDashboard = angular.copy(dashboard.current);
var newDashboard = angular.copy($scope.dashboard);
newDashboard.rows = [];
newDashboard.title = state.name;
newDashboard.rows.push(currentRow);
_.each(state.graphs, function(graph) {
_.each(state.graphs, function(graph, index) {
if (currentRow.panels.length === graphsPerRow) {
currentRow = angular.copy(rowTemplate);
newDashboard.rows.push(currentRow);
}
panel = {
type: 'graphite',
type: 'graph',
span: 12 / graphsPerRow,
title: graph[1].title,
targets: [],
datasource: datasource
datasource: datasource,
id: index + 1
};
_.each(graph[1].target, function(target) {
@@ -96,7 +97,10 @@ function (angular, app, _) {
currentRow.panels.push(panel);
});
dashboard.dash_load(newDashboard);
window.grafanaImportDashboard = newDashboard;
$location.path('/dashboard/import/' + kbn.slugifyForUrl(newDashboard.title));
$scope.dismiss();
}
});

View File

@@ -1,6 +1,6 @@
define([
'angular',
'underscore',
'lodash',
'config',
'../services/graphite/gfunc',
'../services/graphite/parser'
@@ -8,12 +8,14 @@ define([
function (angular, _, config, gfunc, Parser) {
'use strict';
var module = angular.module('kibana.controllers');
var module = angular.module('grafana.controllers');
var targetLetters = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O'];
module.controller('GraphiteTargetCtrl', function($scope) {
module.controller('GraphiteTargetCtrl', function($scope, $sce, templateSrv) {
$scope.init = function() {
$scope.target.target = $scope.target.target || '';
$scope.targetLetters = targetLetters;
parseTarget();
};
@@ -52,6 +54,13 @@ function (angular, _, config, gfunc, Parser) {
checkOtherSegments($scope.segments.length - 1);
}
function addFunctionParameter(func, value, index, shiftBack) {
if (shiftBack) {
index = Math.max(index - 1, 0);
}
func.params[index] = value;
}
function parseTargeRecursive(astNode, func, index) {
if (astNode === null) {
return null;
@@ -59,7 +68,7 @@ function (angular, _, config, gfunc, Parser) {
switch(astNode.type) {
case 'function':
var innerFunc = gfunc.createFuncInstance(astNode.name);
var innerFunc = gfunc.createFuncInstance(astNode.name, { withDefaultParams: false });
_.each(astNode.params, function(param, index) {
parseTargeRecursive(param, innerFunc, index);
@@ -69,40 +78,27 @@ function (angular, _, config, gfunc, Parser) {
$scope.functions.push(innerFunc);
break;
case 'series-ref':
addFunctionParameter(func, astNode.value, index, $scope.segments.length > 0);
break;
case 'string':
case 'number':
if ((index-1) >= func.def.params.length) {
throw { message: 'invalid number of parameters to method ' + func.def.name };
}
if (index === 0) {
func.params[index] = astNode.value;
}
else {
func.params[index - 1] = astNode.value;
}
addFunctionParameter(func, astNode.value, index, true);
break;
case 'metric':
if ($scope.segments.length > 0) {
throw { message: 'Multiple metric params not supported, use text editor.' };
if (astNode.segments.length !== 1) {
throw { message: 'Multiple metric params not supported, use text editor.' };
}
addFunctionParameter(func, astNode.segments[0].value, index, true);
break;
}
$scope.segments = _.map(astNode.segments, function(segment) {
var node = {
type: segment.type,
val: segment.value,
html: segment.value
};
if (segment.value === '*') {
node.html = '<i class="icon-asterisk"><i>';
}
if (segment.type === 'template') {
node.val = node.html = '[[' + segment.value + ']]';
node.html = "<span style='color: #ECEC09'>" + node.html + "</span>";
}
return node;
return new MetricSegment(segment);
});
}
}
@@ -111,27 +107,29 @@ function (angular, _, config, gfunc, Parser) {
var arr = $scope.segments.slice(0, index);
return _.reduce(arr, function(result, segment) {
return result ? (result + "." + segment.val) : segment.val;
return result ? (result + "." + segment.value) : segment.value;
}, "");
}
function checkOtherSegments(fromIndex) {
if (fromIndex === 0) {
$scope.segments.push({html: 'select metric'});
$scope.segments.push(new MetricSegment('select metric'));
return;
}
var path = getSegmentPathUpTo(fromIndex + 1);
return $scope.datasource.metricFindQuery($scope.filter, path)
return $scope.datasource.metricFindQuery(path)
.then(function(segments) {
if (segments.length === 0) {
$scope.segments = $scope.segments.splice(0, fromIndex);
$scope.segments.push({html: 'select metric'});
if (path !== '') {
$scope.segments = $scope.segments.splice(0, fromIndex);
$scope.segments.push(new MetricSegment('select metric'));
}
return;
}
if (segments[0].expandable) {
if ($scope.segments.length === fromIndex) {
$scope.segments.push({html: 'select metric'});
$scope.segments.push(new MetricSegment('select metric'));
}
else {
return checkOtherSegments(fromIndex + 1);
@@ -156,39 +154,43 @@ function (angular, _, config, gfunc, Parser) {
$scope.getAltSegments = function (index) {
$scope.altSegments = [];
var query = index === 0 ?
'*' : getSegmentPathUpTo(index) + '.*';
var query = index === 0 ? '*' : getSegmentPathUpTo(index) + '.*';
return $scope.datasource.metricFindQuery($scope.filter, query)
return $scope.datasource.metricFindQuery(query)
.then(function(segments) {
_.each(segments, function(segment) {
segment.html = segment.val = segment.text;
$scope.altSegments = _.map(segments, function(segment) {
return new MetricSegment({ value: segment.text, expandable: segment.expandable });
});
_.each($scope.filter.templateParameters, function(templateParameter) {
segments.unshift({
if ($scope.altSegments.length === 0) {
return;
}
// add template variables
_.each(templateSrv.variables, function(variable) {
$scope.altSegments.unshift(new MetricSegment({
type: 'template',
html: '[[' + templateParameter.name + ']]',
val: '[[' + templateParameter.name + ']]',
value: '$' + variable.name,
expandable: true,
});
}));
});
segments.unshift({val: '*', html: '<i class="icon-asterisk"></i>', expandable: true });
$scope.altSegments = segments;
// add wildcard option
$scope.altSegments.unshift(new MetricSegment('*'));
})
.then(null, function(err) {
$scope.parserError = err.message || 'Failed to issue metric query';
});
};
$scope.setSegment = function (altIndex, segmentIndex) {
$scope.segmentValueChanged = function (segment, segmentIndex) {
delete $scope.parserError;
$scope.segments[segmentIndex].val = $scope.altSegments[altIndex].val;
$scope.segments[segmentIndex].html = $scope.altSegments[altIndex].html;
if ($scope.functions.length > 0 && $scope.functions[0].def.fake) {
$scope.functions = [];
}
if ($scope.altSegments[altIndex].expandable) {
if (segment.expandable) {
return checkOtherSegments(segmentIndex + 1)
.then(function () {
setSegmentFocus(segmentIndex + 1);
@@ -205,7 +207,7 @@ function (angular, _, config, gfunc, Parser) {
$scope.targetTextChanged = function() {
parseTarget();
$scope.$parent.get_data();
$scope.get_data();
};
$scope.targetChanged = function() {
@@ -229,13 +231,17 @@ function (angular, _, config, gfunc, Parser) {
};
$scope.addFunction = function(funcDef) {
var newFunc = gfunc.createFuncInstance(funcDef);
var newFunc = gfunc.createFuncInstance(funcDef, { withDefaultParams: true });
newFunc.added = true;
$scope.functions.push(newFunc);
$scope.moveAliasFuncLast();
$scope.smartlyHandleNewAliasByNode(newFunc);
if ($scope.segments.length === 1 && $scope.segments[0].value === 'select metric') {
$scope.segments = [];
}
if (!newFunc.params.length && newFunc.added) {
$scope.targetChanged();
}
@@ -259,7 +265,7 @@ function (angular, _, config, gfunc, Parser) {
return;
}
for(var i = 0; i < $scope.segments.length; i++) {
if ($scope.segments[i].val.indexOf('*') >= 0) {
if ($scope.segments[i].value.indexOf('*') >= 0) {
func.params[0] = i;
func.added = false;
$scope.targetChanged();
@@ -268,11 +274,42 @@ function (angular, _, config, gfunc, Parser) {
}
};
$scope.toggleMetricOptions = function() {
$scope.panel.metricOptionsEnabled = !$scope.panel.metricOptionsEnabled;
if (!$scope.panel.metricOptionsEnabled) {
delete $scope.panel.cacheTimeout;
}
};
$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);
};
function MetricSegment(options) {
if (options === '*' || options.value === '*') {
this.value = '*';
this.html = $sce.trustAsHtml('<i class="icon-asterisk"><i>');
this.expandable = true;
return;
}
if (_.isString(options)) {
this.value = options;
this.html = $sce.trustAsHtml(this.value);
return;
}
this.value = options.value;
this.type = options.type;
this.expandable = options.expandable;
this.html = $sce.trustAsHtml(templateSrv.highlightVariablesAsHtml(this.value));
}
});
module.directive('focusMe', function($timeout, $parse) {

View File

@@ -1,18 +1,34 @@
define([
'angular'
'angular',
'lodash'
],
function (angular) {
function (angular, _) {
'use strict';
var module = angular.module('kibana.controllers');
var module = angular.module('grafana.controllers');
var seriesList = null;
module.controller('InfluxTargetCtrl', function($scope, $timeout) {
$scope.init = function() {
$scope.target.function = $scope.target.function || 'mean';
$scope.target.column = $scope.target.column || 'value';
var target = $scope.target;
target.function = target.function || 'mean';
target.column = target.column || 'value';
// backward compatible correction of schema
if (target.condition_value) {
target.condition = target.condition_key + ' ' + target.condition_op + ' ' + target.condition_value;
delete target.condition_key;
delete target.condition_op;
delete target.condition_value;
}
if (target.groupby_field_add === false) {
target.groupby_field = '';
delete target.groupby_field_add;
}
$scope.rawQuery = false;
@@ -24,7 +40,7 @@ function (angular) {
];
$scope.operators = ['=', '=~', '>', '<', '!~', '<>'];
$scope.oldSeries = $scope.target.series;
$scope.oldSeries = target.series;
$scope.$on('typeahead-updated', function() {
$timeout($scope.get_data);
});
@@ -42,6 +58,7 @@ function (angular) {
$scope.seriesBlur = function() {
if ($scope.oldSeries !== $scope.target.series) {
$scope.oldSeries = $scope.target.series;
$scope.columnList = null;
$scope.get_data();
}
};
@@ -67,10 +84,11 @@ function (angular) {
};
$scope.listSeries = function(query, callback) {
if (!seriesList) {
if (query !== '') {
seriesList = [];
$scope.datasource.listSeries().then(function(series) {
$scope.datasource.listSeries(query).then(function(series) {
seriesList = series;
console.log(series);
callback(seriesList);
});
}
@@ -79,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

@@ -1,10 +1,11 @@
define([
'angular'
'angular',
'lodash'
],
function (angular) {
function (angular, _) {
'use strict';
var module = angular.module('kibana.controllers');
var module = angular.module('grafana.controllers');
module.controller('InspectCtrl', function($scope) {
var model = $scope.inspector;
@@ -28,6 +29,16 @@ function (angular) {
return;
}
if (_.isString(model.error.data)) {
$scope.response = model.error.data;
}
if (model.error.config && model.error.config.params) {
$scope.request_parameters = _.map(model.error.config.params, function(value, key) {
return { key: key, value: value};
});
}
if (model.error.stack) {
$scope.editor.index = 2;
$scope.stack_trace = model.error.stack;
@@ -47,7 +58,7 @@ function (angular) {
});
angular
.module('kibana.directives')
.module('grafana.directives')
.directive('iframeContent', function($parse) {
return {
restrict: 'A',
@@ -72,4 +83,4 @@ function (angular) {
};
});
});
});

View File

@@ -0,0 +1,22 @@
define([
'angular',
'lodash'
],
function (angular) {
'use strict';
var module = angular.module('grafana.controllers');
module.controller('JsonEditorCtrl', function($scope) {
$scope.json = angular.toJson($scope.object, true);
$scope.canUpdate = $scope.updateHandler !== void 0;
$scope.update = function () {
var newObject = angular.fromJson($scope.json);
$scope.updateHandler(newObject, $scope.object);
};
});
});

View File

@@ -1,12 +1,12 @@
define([
'angular',
'underscore',
'lodash',
'config'
],
function (angular, _, config) {
'use strict';
var module = angular.module('kibana.controllers');
var module = angular.module('grafana.controllers');
module.controller('MetricKeysCtrl', function($scope, $http, $q) {
var elasticSearchUrlForMetricIndex = config.elasticsearch + '/' + config.grafana_metrics_index + '/';

View File

@@ -1,12 +1,12 @@
define([
'angular',
'underscore',
'lodash',
'kbn'
],
function (angular, _, kbn) {
'use strict';
var module = angular.module('kibana.controllers');
var module = angular.module('grafana.controllers');
module.controller('OpenTSDBTargetCtrl', function($scope, $timeout) {

View File

@@ -1,134 +0,0 @@
define([
'angular',
'underscore',
'jquery'
],
function (angular, _, $) {
'use strict';
// This function needs $inject annotations, update below
// when changing arguments to this function
function PanelBaseCtrl($scope, $rootScope, $timeout) {
var menu = [
{
text: 'Edit',
configModal: "app/partials/paneleditor.html",
condition: !$scope.panelMeta.fullscreenEdit
},
{
text: 'Edit',
click: "toggleFullscreenEdit()",
condition: $scope.panelMeta.fullscreenEdit
},
{
text: "Fullscreen",
click: 'toggleFullscreen()',
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: 'Remove',
click: 'remove_panel_from_row(row, panel)',
condition: true
}
];
$scope.inspector = {};
$scope.panelMeta.menu = _.where(menu, { condition: true });
$scope.updateColumnSpan = function(span) {
$scope.panel.span = span;
$timeout(function() {
$scope.$emit('render');
});
};
$scope.enterFullscreenMode = function(options) {
var docHeight = $(window).height();
var editHeight = Math.floor(docHeight * 0.3);
var fullscreenHeight = Math.floor(docHeight * 0.7);
var oldTimeRange = $scope.range;
$scope.height = options.edit ? editHeight : fullscreenHeight;
$scope.editMode = options.edit;
if (!$scope.fullscreen) {
var closeEditMode = $rootScope.$on('panel-fullscreen-exit', function() {
$scope.editMode = false;
$scope.fullscreen = false;
delete $scope.height;
closeEditMode();
$timeout(function() {
if (oldTimeRange !== $scope.range) {
$scope.dashboard.refresh();
}
else {
$scope.$emit('render');
}
});
});
}
$(window).scrollTop(0);
$scope.fullscreen = true;
$rootScope.$emit('panel-fullscreen-enter');
$timeout(function() {
$scope.$emit('render');
});
};
$scope.toggleFullscreenEdit = function() {
if ($scope.editMode) {
$rootScope.$emit('panel-fullscreen-exit');
return;
}
$scope.enterFullscreenMode({edit: true});
};
$scope.toggleFullscreen = function() {
if ($scope.fullscreen && !$scope.editMode) {
$rootScope.$emit('panel-fullscreen-exit');
return;
}
$scope.enterFullscreenMode({ edit: false });
};
}
PanelBaseCtrl['$inject'] = ['$scope', '$rootScope', '$timeout'];
return PanelBaseCtrl;
});

View File

@@ -1,19 +1,18 @@
define([
'angular',
'underscore',
'lodash',
'config'
],
function (angular, _, config) {
'use strict';
var module = angular.module('kibana.controllers');
var module = angular.module('grafana.controllers');
module.controller('PlaylistCtrl', function($scope, playlistSrv) {
$scope.init = function() {
$scope.timespan = config.playlist_timespan;
$scope.loadFavorites();
$scope.$on('modal-opened', $scope.loadFavorites);
};
$scope.loadFavorites = function() {

View File

@@ -1,12 +1,12 @@
define([
'angular',
'app',
'underscore'
'lodash'
],
function (angular, app, _) {
'use strict';
var module = angular.module('kibana.controllers');
var module = angular.module('grafana.controllers');
module.controller('PulldownCtrl', function($scope, $rootScope, $timeout) {
var _d = {

View File

@@ -1,22 +1,20 @@
define([
'angular',
'app',
'underscore'
'lodash'
],
function (angular, app, _) {
'use strict';
var module = angular.module('kibana.controllers');
var module = angular.module('grafana.controllers');
module.controller('RowCtrl', function($scope, $rootScope, $timeout) {
var _d = {
title: "Row",
height: "150px",
collapse: false,
collapsable: true,
editable: true,
panels: [],
notice: false
};
_.defaults($scope.row,_d);
@@ -25,63 +23,41 @@ function (angular, app, _) {
$scope.reset_panel();
};
$scope.togglePanelMenu = function(posX) {
$scope.showPanelMenu = !$scope.showPanelMenu;
$scope.panelMenuPos = posX;
};
$scope.toggle_row = function(row) {
if(!row.collapsable) {
return;
}
row.collapse = row.collapse ? false : true;
if (!row.collapse) {
$timeout(function() {
$scope.$broadcast('render');
});
} else {
row.notice = false;
}
};
$scope.rowSpan = function(row) {
var panels = _.filter(row.panels, function(p) {
return $scope.isPanel(p);
});
return _.reduce(_.pluck(panels,'span'), function(p,v) {
return p+v;
},0);
};
// This can be overridden by individual panels
$scope.close_edit = function() {
$scope.$broadcast('render');
};
$scope.add_panel = function(panel) {
var rowSpan = $scope.rowSpan($scope.row);
var panelCount = $scope.row.panels.length;
var space = (12 - rowSpan) - panel.span;
// try to make room of there is no space left
if (space <= 0) {
if (panelCount === 1) {
$scope.row.panels[0].span = 6;
panel.span = 6;
}
else if (panelCount === 2) {
$scope.row.panels[0].span = 4;
$scope.row.panels[1].span = 4;
panel.span = 4;
}
}
$scope.row.panels.push(panel);
$scope.dashboard.add_panel(panel, $scope.row);
};
$scope.delete_row = function() {
if (confirm("Are you sure you want to delete this row?")) {
$scope.dashboard.current.rows = _.without($scope.dashboard.current.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) {
var rowsList = $scope.dashboard.current.rows;
var rowsList = $scope.dashboard.rows;
var rowIndex = _.indexOf(rowsList, $scope.row);
var newIndex = rowIndex + direction;
if (newIndex >= 0 && newIndex <= (rowsList.length - 1)) {
@@ -104,70 +80,37 @@ 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.duplicatePanel = function(panel, row) {
row = row || $scope.row;
var currentRowSpan = $scope.rowSpan(row);
if (currentRowSpan <= 9) {
row.panels.push(angular.copy(panel));
}
else {
var rowsList = $scope.dashboard.current.rows;
var rowIndex = _.indexOf(rowsList, row);
if (rowIndex === rowsList.length - 1) {
var newRow = angular.copy($scope.row);
newRow.panels = [];
$scope.dashboard.current.rows.push(newRow);
$scope.duplicatePanel(panel, newRow);
}
else {
$scope.duplicatePanel(panel, rowsList[rowIndex+1]);
}
}
};
$scope.replacePanel = function(newPanel, oldPanel) {
var row = $scope.row;
var index = _.indexOf(row.panels, oldPanel);
row.panels.splice(index, 1);
/** @scratch /panels/0
* [[panels]]
* = Panels
*
* [partintro]
* --
* *Kibana* dashboards are made up of blocks called +panels+. Panels are organized into rows
* and can serve many purposes, though most are designed to provide the results of a query or
* multiple queries as a visualization. Other panels may show collections of documents or
* allow you to insert instructions for your users.
*
* Panels can be configured easily via the Kibana web interface. For more advanced usage, such
* as templated or scripted dashboards, documentation of panel properties is available in this
* section. You may find settings here which are not exposed via the web interface.
*
* Each panel type has its own properties, hover there are several that are shared.
*
*/
// adding it back needs to be done in next digest
$timeout(function() {
newPanel.id = oldPanel.id;
newPanel.span = oldPanel.span;
row.panels.splice(index, 0, newPanel);
});
};
$scope.reset_panel = function(type) {
var
defaultSpan = 4,
_as = 12-$scope.rowSpan($scope.row);
var defaultSpan = 12;
var _as = 12 - $scope.dashboard.rowSpan($scope.row);
$scope.panel = {
title: 'no title (click here)',
error : false,
/** @scratch /panels/1
* span:: A number, 1-12, that describes the width of the panel.
*/
span : _as < defaultSpan && _as > 0 ? _as : defaultSpan,
/** @scratch /panels/1
* editable:: Enable or disable the edit button the the panel
*/
editable: true,
/** @scratch /panels/1
* type:: The type of panel this object contains. Each panel type will require additional
* properties. See the panel types list to the right.
*/
type : type
};
@@ -184,12 +127,42 @@ function (angular, app, _) {
$scope.row.height = fixRowHeight($scope.row.height);
};
/** @scratch /panels/2
* --
*/
$scope.init();
});
});
module.directive('rowHeight', function() {
return function(scope, element) {
scope.$watchGroup(['row.collapse', 'row.height'], function() {
element[0].style.minHeight = scope.row.collapse ? '5px' : scope.row.height;
});
};
});
module.directive('panelWidth', function() {
return function(scope, element) {
scope.$watch('panel.span', function() {
element[0].style.width = ((scope.panel.span / 1.2) * 10) + '%';
});
};
});
module.directive('panelDropZone', function() {
return function(scope, element) {
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();
}
});
scope.$on("ANGULAR_DRAG_END", function() {
element.hide();
});
};
});
});

View File

@@ -1,33 +1,44 @@
define([
'angular',
'underscore',
'lodash',
'config',
'jquery'
],
function (angular, _, config, $) {
'use strict';
var module = angular.module('kibana.controllers');
var module = angular.module('grafana.controllers');
module.controller('SearchCtrl', function($scope, $rootScope, dashboard, $element, $location) {
module.controller('SearchCtrl', function($scope, $rootScope, $element, $location, datasourceSrv, $timeout) {
$scope.init = function() {
$scope.giveSearchFocus = 0;
$scope.selectedIndex = -1;
$scope.results = {dashboards: [], tags: [], metrics: []};
$scope.query = { query: 'title:' };
$rootScope.$on('open-search', $scope.openSearch);
$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:';
$scope.search();
}, 100);
};
$scope.keyDown = function (evt) {
if (evt.keyCode === 27) {
$element.find('.dropdown-toggle').dropdown('toggle');
$scope.appEvent('hide-dash-editor');
}
if (evt.keyCode === 40) {
$scope.selectedIndex++;
$scope.moveSelection(1);
}
if (evt.keyCode === 38) {
$scope.selectedIndex--;
$scope.moveSelection(-1);
}
if (evt.keyCode === 13) {
if ($scope.tagsOnly) {
@@ -40,7 +51,8 @@ function (angular, _, config, $) {
var selectedDash = $scope.results.dashboards[$scope.selectedIndex];
if (selectedDash) {
$location.path("/dashboard/elasticsearch/" + encodeURIComponent(selectedDash._id));
$location.search({});
$location.path("/dashboard/db/" + selectedDash.id);
setTimeout(function() {
$('body').click(); // hack to force dropdown to close;
});
@@ -48,39 +60,42 @@ function (angular, _, config, $) {
}
};
$scope.searchDasboards = function(query) {
var request = $scope.ejs.Request().indices(config.grafana_index).types('dashboard');
var tagsOnly = query.indexOf('tags!:') === 0;
if (tagsOnly) {
var tagsQuery = query.substring(6, query.length);
query = 'tags:' + tagsQuery + '*';
}
else {
if (query.length === 0) {
query = 'title:';
}
$scope.moveSelection = function(direction) {
$scope.selectedIndex = Math.max(Math.min($scope.selectedIndex + direction, $scope.resultCount - 1), 0);
};
if (query[query.length - 1] !== '*') {
query += '*';
}
}
$scope.goToDashboard = function(id) {
$location.search({});
$location.path("/dashboard/db/" + id);
};
return request
.query($scope.ejs.QueryStringQuery(query))
.sort('_uid')
.facet($scope.ejs.TermsFacet("tags").field("tags").order('term').size(50))
.size(20).doSearch()
$scope.shareDashboard = function(title, id, $event) {
$event.stopPropagation();
var baseUrl = window.location.href.replace(window.location.hash,'');
$scope.share = {
title: title,
url: baseUrl + '#dashboard/db/' + encodeURIComponent(id)
};
};
$scope.searchDashboards = function(queryString) {
// bookeeping for determining stale search requests
var searchId = $scope.currentSearchId + 1;
$scope.currentSearchId = searchId > $scope.currentSearchId ? searchId : $scope.currentSearchId;
return $scope.db.searchDashboards(queryString)
.then(function(results) {
if(_.isUndefined(results.hits)) {
$scope.results.dashboards = [];
$scope.results.tags = [];
// since searches are async, it's possible that these results are not for the latest search. throw
// them away if so
if (searchId < $scope.currentSearchId) {
return;
}
$scope.tagsOnly = tagsOnly;
$scope.results.dashboards = results.hits.hits;
$scope.results.tags = results.facets.tags.terms;
$scope.tagsOnly = results.tagsOnly;
$scope.results.dashboards = results.dashboards;
$scope.results.tags = results.tags;
$scope.resultCount = results.tagsOnly ? results.tags.length : results.dashboards.length;
});
};
@@ -94,8 +109,7 @@ function (angular, _, config, $) {
}
};
$scope.showTags = function(evt) {
evt.stopPropagation();
$scope.showTags = function() {
$scope.tagsOnly = !$scope.tagsOnly;
$scope.query.query = $scope.tagsOnly ? "tags!:" : "";
$scope.giveSearchFocus = $scope.giveSearchFocus + 1;
@@ -105,55 +119,22 @@ function (angular, _, config, $) {
$scope.search = function() {
$scope.showImport = false;
$scope.selectedIndex = -1;
var queryStr = $scope.query.query.toLowerCase();
if (queryStr.indexOf('m:') !== 0) {
queryStr = queryStr.replace(' and ', ' AND ');
$scope.searchDasboards(queryStr);
return;
}
queryStr = queryStr.substring(2, queryStr.length);
var words = queryStr.split(' ');
var query = $scope.ejs.BoolQuery();
var terms = _.map(words, function(word) {
return $scope.ejs.MatchQuery('metricPath_ng', word).boost(1.2);
});
var ngramQuery = $scope.ejs.BoolQuery();
ngramQuery.must(terms);
var fieldMatchQuery = $scope.ejs.FieldQuery('metricPath', queryStr + "*").boost(1.2);
query.should([ngramQuery, fieldMatchQuery]);
var request = $scope.ejs.Request().indices(config.grafana_index).types('metricKey');
var results = request.query(query).size(20).doSearch();
results.then(function(results) {
if (results && results.hits && results.hits.hits.length > 0) {
$scope.results.metrics = { metrics: results.hits.hits };
}
else {
$scope.results.metrics = { metric: [] };
}
});
$scope.selectedIndex = 0;
$scope.searchDashboards($scope.query.query);
};
$scope.openSearch = function (evt) {
if (evt) {
$element.find('.dropdown-toggle').dropdown('toggle');
}
$scope.deleteDashboard = function(dash, evt) {
evt.stopPropagation();
$scope.appEvent('delete-dashboard', { id: dash.id, title: dash.title });
};
$scope.giveSearchFocus = $scope.giveSearchFocus + 1;
$scope.query.query = 'title:';
$scope.search();
$scope.dashboardDeleted = function(evt, id) {
var dash = _.findWhere($scope.results.dashboards, {id: id});
$scope.results.dashboards = _.without($scope.results.dashboards, dash);
};
$scope.addMetricToCurrentDashboard = function (metricId) {
dashboard.current.rows.push({
$scope.dashboard.rows.push({
title: '',
height: '250px',
editable: true,
@@ -168,8 +149,7 @@ function (angular, _, config, $) {
});
};
$scope.toggleImport = function ($event) {
$event.stopPropagation();
$scope.toggleImport = function () {
$scope.showImport = !$scope.showImport;
};
@@ -181,16 +161,48 @@ function (angular, _, config, $) {
module.directive('xngFocus', function() {
return function(scope, element, attrs) {
$(element).click(function(e) {
element.click(function(e) {
e.stopPropagation();
});
scope.$watch(attrs.xngFocus,function (newValue) {
if (!newValue) {
return;
}
setTimeout(function() {
newValue && element.focus();
element.focus();
var pos = element.val().length * 2;
element[0].setSelectionRange(pos, pos);
}, 200);
},true);
};
});
});
module.directive('tagColorFromName', function() {
function djb2(str) {
var hash = 5381;
for (var i = 0; i < str.length; i++) {
hash = ((hash << 5) + hash) + str.charCodeAt(i); /* hash * 33 + c */
}
return hash;
}
return function (scope, element) {
var name = _.isString(scope.tag) ? scope.tag : scope.tag.term;
var hash = djb2(name.toLowerCase());
var colors = [
"#E24D42","#1F78C1","#BA43A9","#705DA0","#466803",
"#508642","#447EBC","#C15C17","#890F02","#757575",
"#0A437C","#6D1F62","#584477","#629E51","#2F4F4F",
"#BF1B00","#806EB7","#8a2eb8", "#699e00","#000000",
"#3F6833","#2F575E","#99440A","#E0752D","#0E4AB4",
"#58140C","#052B51","#511749","#3F2B5B",
];
var color = colors[Math.abs(hash % colors.length)];
element.css("background-color", color);
};
});
});

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,14 +1,13 @@
define([
'angular',
'app',
'underscore'
'lodash'
],
function (angular, app, _) {
function (angular, _) {
'use strict';
var module = angular.module('kibana.controllers');
var module = angular.module('grafana.controllers');
module.controller('SubmenuCtrl', function($scope) {
module.controller('SubmenuCtrl', function($scope, $q, $rootScope, templateValuesSrv) {
var _d = {
enable: true
};
@@ -18,10 +17,20 @@ function (angular, app, _) {
$scope.init = function() {
$scope.panel = $scope.pulldown;
$scope.row = $scope.pulldown;
$scope.variables = $scope.dashboard.templating.list;
};
$scope.disableAnnotation = function (annotation) {
annotation.enable = !annotation.enable;
$rootScope.$broadcast('refresh');
};
$scope.setVariableValue = function(param, option) {
templateValuesSrv.setVariableValue(param, option);
};
$scope.init();
});
});
});

View File

@@ -0,0 +1,111 @@
define([
'angular',
'lodash',
],
function (angular, _) {
'use strict';
var module = angular.module('grafana.controllers');
module.controller('TemplateEditorCtrl', function($scope, datasourceSrv, templateSrv, templateValuesSrv, alertSrv) {
var replacementDefaults = {
type: 'query',
datasource: null,
refresh_on_load: false,
name: '',
options: [],
includeAll: false,
allFormat: 'glob',
};
$scope.init = function() {
$scope.editor = { index: 0 };
$scope.datasources = datasourceSrv.getMetricSources();
$scope.variables = templateSrv.variables;
$scope.reset();
$scope.$watch('editor.index', function(index) {
if ($scope.currentIsNew === false && index === 1) {
$scope.reset();
}
});
};
$scope.add = function() {
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() {
return templateValuesSrv.updateOptions($scope.current).then(function() {
}, function(err) {
alertSrv.set('Templating', 'Failed to run query for variable values: ' + err.message, 'error');
});
};
$scope.edit = function(variable) {
$scope.current = variable;
$scope.currentIsNew = false;
$scope.editor.index = 2;
if ($scope.current.datasource === void 0) {
$scope.current.datasource = null;
$scope.current.type = 'query';
$scope.current.allFormat = 'Glob';
}
};
$scope.update = function() {
if ($scope.isValid()) {
$scope.runQuery().then(function() {
$scope.reset();
$scope.editor.index = 0;
});
}
};
$scope.reset = function() {
$scope.currentIsNew = true;
$scope.current = angular.copy(replacementDefaults);
};
$scope.typeChanged = function () {
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) {
var index = _.indexOf($scope.variables, variable);
$scope.variables.splice(index, 1);
};
});
});

View File

@@ -1,54 +1,82 @@
{
"title": "Welcome to Grafana!",
"services": {
"filter": {
"list": [],
"time": {
"from": "now-6h",
"to": "now"
}
}
},
"title": "Grafana",
"tags": [],
"style": "dark",
"timezone": "browser",
"editable": true,
"rows": [
{
"title": "Welcome to Grafana",
"title": "New row",
"height": "150px",
"editable": true,
"collapse": false,
"collapsable": true,
"editable": true,
"panels": [
{
"error": false,
"id": 1,
"span": 12,
"editable": true,
"type": "text",
"loadingEditor": false,
"mode": "markdown",
"content": "####Thank you for trying out Grafana! \n\nGeneral documentation is found in the readme and in the wiki section of the [Github Project](https://github.com/torkelo/grafana). If you encounter any problem or have an idea for an improvement do not hesitate to open a github issue. \n\nTips: \n\n- Ctrl+S saves the current dashboard\n- Ctrl+F Opens the dashboard finder (searches elastic search)\n- Ctrl+H Hide/show row controls \n- Click and drag graph title to move panel (only works when row controls are enabled)\n\nIf you do not see a graph in the panel bellow the browser cannot access your graphite installation. Please make sure that the graphiteUrl property in config.js is correctly set and accessible.",
"mode": "html",
"content": "<div class=\"text-center\" style=\"padding-top: 15px\">\n<img src=\"img/logo_transparent_200x.png\"> \n</div>",
"style": {},
"title": "Welcome to Grafana"
"title": "Welcome to"
}
],
"notice": false
]
},
{
"title": "Welcome to Grafana",
"height": "210px",
"collapse": false,
"editable": true,
"panels": [
{
"id": 2,
"span": 6,
"type": "text",
"mode": "html",
"content": "<br/>\n\n<div class=\"row-fluid\">\n <div class=\"span6\">\n <ul>\n <li>\n <a href=\"http://grafana.org/docs#configuration\" target=\"_blank\">Configuration</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/troubleshooting\" target=\"_blank\">Troubleshooting</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/support\" target=\"_blank\">Support</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/features/intro\" target=\"_blank\">Getting started</a> (Must read!)\n </li>\n </ul>\n </div>\n <div class=\"span6\">\n <ul>\n <li>\n <a href=\"http://grafana.org/docs/features/graphing\" target=\"_blank\">Graphing</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/features/annotations\" target=\"_blank\">Annotations</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/features/graphite\" target=\"_blank\">Graphite</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/features/influxdb\" target=\"_blank\">InfluxDB</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/features/opentsdb\" target=\"_blank\">OpenTSDB</a>\n </li>\n </ul>\n </div>\n</div>",
"style": {},
"title": "Documentation Links"
},
{
"id": 3,
"span": 6,
"type": "text",
"mode": "html",
"content": "<br/>\n\n<div class=\"row-fluid\">\n <div class=\"span12\">\n <ul>\n <li>Ctrl+S saves the current dashboard</li>\n <li>Ctrl+F Opens the dashboard finder</li>\n <li>Ctrl+H Hide/show row controls</li>\n <li>Click and drag graph title to move panel</li>\n <li>Hit Escape to exit graph when in fullscreen or edit mode</li>\n <li>Click the colored icon in the legend to change series color</li>\n <li>Ctrl or Shift + Click legend name to hide other series</li>\n </ul>\n </div>\n</div>\n",
"style": {},
"title": "Tips & Shortcuts"
}
]
},
{
"title": "test",
"height": "250px",
"editable": true,
"collapse": false,
"collapsable": true,
"panels": [
{
"id": 4,
"span": 12,
"editable": true,
"type": "graphite",
"type": "graph",
"x-axis": true,
"y-axis": true,
"scale": 1,
"y_formats": ["short", "short"],
"y_formats": [
"short",
"short"
],
"grid": {
"max": null,
"min": null
"min": null,
"leftMax": null,
"rightMax": null,
"leftMin": null,
"rightMin": null,
"threshold1": null,
"threshold2": null,
"threshold1Color": "rgba(216, 200, 27, 0.27)",
"threshold2Color": "rgba(234, 112, 112, 0.22)"
},
"resolution": 100,
"lines": true,
@@ -60,53 +88,48 @@
"stack": true,
"spyable": true,
"options": false,
"legend": true,
"legend": {
"show": true,
"values": false,
"min": false,
"max": false,
"current": false,
"total": false,
"avg": false
},
"interactive": true,
"legend_counts": true,
"timezone": "browser",
"percentage": false,
"zerofill": true,
"nullPointMode": "connected",
"steppedLine": false,
"tooltip": {
"value_type": "cumulative"
"value_type": "cumulative",
"query_as_alias": true
},
"targets": [
{
"target": "randomWalk('random walk')"
},
{
"target": "randomWalk('random walk2')"
},
{
"target": "randomWalk('random walk3')"
"target": "randomWalk('random walk')",
"function": "mean",
"column": "value"
}
],
"aliasColors": {},
"aliasYAxis": {},
"title": "Graphite test"
"title": "First Graph (click title to edit)",
"datasource": "graphite",
"renderer": "flot",
"annotate": {
"enable": false
}
}
],
"notice": false
}
],
"editable": true,
"failover": false,
"panel_hints": true,
"style": "dark",
"pulldowns": [
{
"type": "filtering",
"collapse": false,
"notice": false,
"enable": false
]
}
],
"nav": [
{
"type": "timepicker",
"collapse": false,
"notice": false,
"enable": true,
"status": "Stable",
"time_options": [
@@ -135,19 +158,12 @@
"now": true
}
],
"loader": {
"save_gist": false,
"save_elasticsearch": true,
"save_local": true,
"save_default": true,
"save_temp": true,
"save_temp_ttl_enable": true,
"save_temp_ttl": "30d",
"load_gist": false,
"load_elasticsearch": true,
"load_elasticsearch_size": 20,
"load_local": false,
"hide": false
"time": {
"from": "now-6h",
"to": "now"
},
"refresh": false
"templating": {
"list": []
},
"version": 5
}

View File

@@ -1,13 +1,11 @@
{
"title": "New Dashboard",
"services": {
"filter": {
"list": [],
"time": {
"from": "now-6h",
"to": "now"
}
}
"time": {
"from": "now-6h",
"to": "now"
},
"templating": {
"list": []
},
"rows": [
{
@@ -15,28 +13,14 @@
"height": "250px",
"editable": true,
"collapse": false,
"collapsable": true,
"panels": [],
"notice": false
"panels": []
}
],
"editable": true,
"failover": false,
"panel_hints": true,
"style": "dark",
"pulldowns": [
{
"type": "filtering",
"collapse": false,
"notice": false,
"enable": false
}
],
"nav": [
{
"type": "timepicker",
"collapse": false,
"notice": false,
"enable": true,
"status": "Stable",
"time_options": [
@@ -65,19 +49,5 @@
"now": true
}
],
"loader": {
"save_gist": false,
"save_elasticsearch": true,
"save_local": true,
"save_default": true,
"save_temp": true,
"save_temp_ttl_enable": true,
"save_temp_ttl": "30d",
"load_gist": false,
"load_elasticsearch": true,
"load_elasticsearch_size": 20,
"load_local": false,
"hide": false
},
"refresh": false
}
}

View File

@@ -17,27 +17,25 @@
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 : [],
services : {}
};
// Set a title
dashboard.title = 'Scripted dash';
dashboard.services.filter = {
time: {
from: "now-" + (ARGS.from || timspan),
to: "now"
}
// 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-6h",
to: "now"
};
var rows = 1;
@@ -59,7 +57,7 @@ for (var i = 0; i < rows; i++) {
panels: [
{
title: 'Events',
type: 'graphite',
type: 'graph',
span: 12,
fill: 1,
linewidth: 2,
@@ -71,10 +69,21 @@ for (var i = 0; i < rows; i++) {
'target': "randomWalk('random walk2')"
}
],
seriesOverrides: [
{
alias: '/random/',
yaxis: 2,
fill: 0,
linewidth: 5
}
],
tooltip: {
shared: true
}
}
]
});
}
return dashboard;
return dashboard;

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,11 +32,13 @@ return function(callback) {
// Set a title
dashboard.title = 'Scripted dash';
dashboard.services.filter = {
time: {
from: "now-" + (ARGS.from || timspan),
// 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-6h",
to: "now"
}
};
var rows = 1;
@@ -78,4 +77,4 @@ return function(callback) {
callback(dashboard);
});
}
}

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

@@ -0,0 +1,98 @@
/* 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;
// Setup some variables
var dashboard;
// All url parameters are available via the ARGS object
var ARGS;
// Intialize a skeleton with nothing but a rows array and service object
dashboard = {
rows : [],
};
// Set a title
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-6h",
to: "now"
};
dashboard.templating = {
enable: true,
list: [
{
name: 'test',
query: 'apps.backend.*',
refresh: true,
options: [],
current: null,
},
{
name: 'test2',
query: '*',
refresh: true,
options: [],
current: null,
}
]
};
var rows = 1;
var seriesName = 'argName';
if(!_.isUndefined(ARGS.rows)) {
rows = parseInt(ARGS.rows, 10);
}
if(!_.isUndefined(ARGS.name)) {
seriesName = ARGS.name;
}
for (var i = 0; i < rows; i++) {
dashboard.rows.push({
title: 'Chart',
height: '300px',
panels: [
{
title: 'Events',
type: 'graph',
span: 12,
fill: 1,
linewidth: 2,
targets: [
{
'target': "randomWalk('" + seriesName + "')"
},
{
'target': "randomWalk('[[test2]]')"
}
],
}
]
});
}
return dashboard;

View File

@@ -0,0 +1,263 @@
{
"id": null,
"title": "Templated Graphs Nested",
"originalTitle": "Templated Graphs Nested",
"tags": [
"showcase",
"templated"
],
"style": "dark",
"timezone": "browser",
"editable": true,
"hideControls": false,
"rows": [
{
"title": "Row1",
"height": "350px",
"editable": true,
"collapse": false,
"collapsable": true,
"panels": [
{
"span": 12,
"editable": true,
"type": "graph",
"loadingEditor": false,
"datasource": null,
"renderer": "flot",
"x-axis": true,
"y-axis": true,
"scale": 1,
"y_formats": [
"short",
"short"
],
"grid": {
"max": null,
"min": 0,
"threshold1": null,
"threshold2": null,
"threshold1Color": "rgba(216, 200, 27, 0.27)",
"threshold2Color": "rgba(234, 112, 112, 0.22)",
"leftMax": null,
"rightMax": null,
"leftMin": null,
"rightMin": null
},
"annotate": {
"enable": false
},
"resolution": 100,
"lines": true,
"fill": 1,
"linewidth": 1,
"points": false,
"pointradius": 5,
"bars": false,
"stack": true,
"legend": {
"show": true,
"values": false,
"min": false,
"max": false,
"current": false,
"total": false,
"avg": false
},
"percentage": false,
"nullPointMode": "connected",
"steppedLine": false,
"tooltip": {
"value_type": "cumulative",
"query_as_alias": true
},
"targets": [
{
"target": "aliasByNode(apps.$app.$server.counters.requests.count, 2)",
"function": "mean",
"column": "value"
}
],
"aliasColors": {
"highres.test": "#1F78C1",
"scale(highres.test,3)": "#6ED0E0",
"mobile": "#6ED0E0",
"tablet": "#EAB839"
},
"title": "Traffic [[period]]",
"id": 1,
"seriesOverrides": []
}
],
"notice": false
},
{
"title": "Row1",
"height": "350px",
"editable": true,
"collapse": false,
"collapsable": true,
"panels": [
{
"span": 12,
"editable": true,
"type": "graph",
"loadingEditor": false,
"datasource": null,
"renderer": "flot",
"x-axis": true,
"y-axis": true,
"scale": 1,
"y_formats": [
"short",
"short"
],
"grid": {
"max": null,
"min": 0,
"threshold1": null,
"threshold2": null,
"threshold1Color": "rgba(216, 200, 27, 0.27)",
"threshold2Color": "rgba(234, 112, 112, 0.22)",
"leftMax": null,
"rightMax": null,
"leftMin": null,
"rightMin": null
},
"annotate": {
"enable": false
},
"resolution": 100,
"lines": true,
"fill": 1,
"linewidth": 1,
"points": false,
"pointradius": 5,
"bars": false,
"stack": true,
"legend": {
"show": true,
"values": false,
"min": false,
"max": false,
"current": false,
"total": false,
"avg": false
},
"percentage": false,
"zerofill": true,
"nullPointMode": "connected",
"steppedLine": false,
"tooltip": {
"value_type": "cumulative",
"query_as_alias": true
},
"targets": [
{
"target": "aliasByNode(apps.$app.$server.counters.requests.count, 2)"
}
],
"aliasColors": {
"highres.test": "#1F78C1",
"scale(highres.test,3)": "#6ED0E0",
"mobile": "#6ED0E0",
"tablet": "#EAB839"
},
"title": "Second pannel",
"id": 2,
"seriesOverrides": []
}
],
"notice": false
}
],
"nav": [
{
"type": "timepicker",
"collapse": false,
"notice": false,
"enable": true,
"status": "Stable",
"time_options": [
"5m",
"15m",
"1h",
"6h",
"12h",
"24h",
"2d",
"7d",
"30d"
],
"refresh_intervals": [
"5s",
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
],
"now": true
}
],
"time": {
"from": "now-15m",
"to": "now"
},
"templating": {
"list": [
{
"type": "query",
"name": "app",
"query": "apps.*",
"includeAll": true,
"options": [],
"current": {
"text": "All",
"value": "*"
},
"datasource": null,
"allFormat": "wildcard",
"refresh": true
},
{
"type": "query",
"name": "server",
"query": "apps.$app.*",
"includeAll": true,
"options": [],
"current": {
"text": "All",
"value": "*"
},
"datasource": null,
"allFormat": "Glob",
"refresh": false
},
{
"type": "query",
"datasource": null,
"refresh_on_load": false,
"name": "metric",
"options": [],
"includeAll": true,
"allFormat": "glob",
"query": "apps.$app.$server.*",
"current": {
"text": "counters",
"value": "counters"
}
}
],
"enable": true
},
"annotations": {
"enable": false
},
"refresh": false,
"version": 6
}

View File

@@ -1,7 +1,7 @@
define([
'angular',
'app',
'underscore',
'lodash',
'jquery',
'../services/graphite/gfunc',
],
@@ -9,7 +9,7 @@ function (angular, app, _, $, gfunc) {
'use strict';
angular
.module('kibana.directives')
.module('grafana.directives')
.directive('graphiteAddFunc', function($compile) {
var inputTemplate = '<input type="text"'+
' class="grafana-target-segment-input input-medium grafana-target-segment-input"' +
@@ -38,6 +38,15 @@ function (angular, app, _, $, gfunc) {
items: 10,
updater: function (value) {
var funcDef = gfunc.getFuncDef(value);
if (!funcDef) {
// try find close match
value = value.toLowerCase();
funcDef = _.find(allFunctions, function(funcName) {
return funcName.toLowerCase().indexOf(value) === 0;
});
if (!funcDef) { return; }
}
$scope.$apply(function() {
$scope.addFunction(funcDef);
@@ -59,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);
});
@@ -97,4 +105,4 @@ function (angular, app, _, $, gfunc) {
};
});
}
});
});

View File

@@ -1,35 +0,0 @@
define([
'angular',
'app',
'underscore'
],
function (angular, app, _) {
'use strict';
angular
.module('kibana.directives')
.directive('addPanel', function($compile) {
return {
restrict: 'A',
link: function($scope, elem) {
$scope.$on("$destroy",function() {
elem.remove();
});
$scope.$watch('panel.type', function() {
var _type = $scope.panel.type;
$scope.reset_panel(_type);
if(!_.isUndefined($scope.panel.type)) {
$scope.panel.loadingEditor = true;
$scope.require(['panels/'+$scope.panel.type.replace(".","/") +'/module'], function () {
var template = '<div ng-controller="'+$scope.panel.type+'" ng-include="\'app/partials/paneladd.html\'"></div>';
elem.html($compile(angular.element(template))($scope));
$scope.panel.loadingEditor = false;
});
}
});
}
};
});
});

View File

@@ -1,20 +1,22 @@
define([
'./addPanel',
'./arrayJoin',
'./dashUpload',
'./kibanaPanel',
'./kibanaSimplePanel',
'./grafanaPanel',
'./grafanaSimplePanel',
'./ngBlur',
'./dashEditLink',
'./ngModelOnBlur',
'./tip',
'./confirmClick',
'./configModal',
'./spectrumPicker',
'./grafanaGraph',
'./bootstrap-tagsinput',
'./bodyClass',
'./addGraphiteFunc',
'./graphiteFuncEditor',
'./templateParamSelector',
'./graphiteSegment',
'./grafanaVersionCheck',
'./dropdown.typeahead',
'./influxdbFuncEditor'
], function () {});
], function () {});

View File

@@ -1,13 +1,13 @@
define([
'angular',
'app',
'underscore'
'lodash'
],
function (angular, app, _) {
'use strict';
angular
.module('kibana.directives')
.module('grafana.directives')
.directive('arrayJoin', function() {
return {
restrict: 'A',

View File

@@ -1,31 +1,33 @@
define([
'angular',
'app',
'underscore'
'lodash'
],
function (angular, app, _) {
function (angular) {
'use strict';
angular
.module('kibana.directives')
.module('grafana.directives')
.directive('bodyClass', function() {
return {
link: function($scope, elem) {
var lastPulldownVal;
var lastHideControlsVal;
$scope.$watch('dashboard.current.pulldowns', function() {
var panel = _.find($scope.dashboard.current.pulldowns, function(pulldown) { return pulldown.enable; });
var panelEnabled = panel ? panel.enable : false;
if (lastPulldownVal !== panelEnabled) {
elem.toggleClass('submenu-controls-visible', panelEnabled);
lastPulldownVal = panelEnabled;
$scope.$watch('submenuEnabled', function() {
if (!$scope.dashboard) {
return;
}
}, true);
$scope.$watch('dashboard.current.hideControls', function() {
var hideControls = $scope.dashboard.current.hideControls || $scope.playlist_active;
elem.toggleClass('submenu-controls-visible', $scope.submenuEnabled);
});
$scope.$watch('dashboard.hideControls', function() {
if (!$scope.dashboard) {
return;
}
var hideControls = $scope.dashboard.hideControls || $scope.playlist_active;
if (lastHideControlsVal !== hideControls) {
elem.toggleClass('hide-controls', hideControls);
@@ -41,4 +43,4 @@ function (angular, app, _) {
};
});
});
});

View File

@@ -7,7 +7,7 @@ function (angular, $) {
'use strict';
angular
.module('kibana.directives')
.module('grafana.directives')
.directive('bootstrapTagsinput', function() {
function getItemProperty(scope, property) {
@@ -84,7 +84,7 @@ function (angular, $) {
});
angular
.module('kibana.directives')
.module('grafana.directives')
.directive('gfDropdown', function ($parse, $compile, $timeout) {
function buildTemplate(items, placement) {
@@ -102,7 +102,7 @@ function (angular, $) {
var li = '<li' + (item.submenu && item.submenu.length ? ' class="dropdown-submenu"' : '') + '>' +
'<a tabindex="-1" ng-href="' + (item.href || '') + '"' + (item.click ? ' ng-click="' + item.click + '"' : '') +
(item.target ? ' target="' + item.target + '"' : '') + (item.method ? ' data-method="' + item.method + '"' : '') +
(item.configModal ? ' config-modal="' + item.configModal + '"' : "") +
(item.configModal ? ' dash-editor-link="' + item.configModal + '"' : "") +
'>' + (item.text || '') + '</a>';
if (item.submenu && item.submenu.length) {
@@ -131,4 +131,4 @@ function (angular, $) {
}
};
});
});
});

View File

@@ -1,13 +1,13 @@
define([
'angular',
'underscore',
'lodash',
'jquery'
],
function (angular, _, $) {
'use strict';
angular
.module('kibana.directives')
.module('grafana.directives')
.directive('configModal', function($modal, $q, $timeout) {
return {
restrict: 'A',
@@ -45,4 +45,4 @@ function (angular, _, $) {
}
};
});
});
});

View File

@@ -5,7 +5,7 @@ define([
function (angular) {
'use strict';
var module = angular.module('kibana.directives');
var module = angular.module('grafana.directives');
module.directive('confirmClick', function() {
return {

View File

@@ -0,0 +1,88 @@
define([
'angular',
'jquery'
],
function (angular, $) {
'use strict';
angular
.module('grafana.directives')
.directive('dashEditorLink', function($timeout) {
return {
restrict: 'A',
link: function(scope, elem, attrs) {
var partial = attrs.dashEditorLink;
elem.bind('click',function() {
$timeout(function() {
var editorScope = attrs.editorScope === 'isolated' ? null : scope;
scope.appEvent('show-dash-editor', { src: partial, scope: editorScope });
});
});
}
};
});
angular
.module('grafana.directives')
.directive('dashEditorView', function($compile) {
return {
restrict: 'A',
link: function(scope, elem) {
var editorScope;
var lastEditor;
function hideScrollbars(value) {
if (value) {
window.scrollTo(0,0);
document.documentElement.style.overflow = 'hidden'; // firefox, chrome
document.body.scroll = "no"; // ie only
} else {
document.documentElement.style.overflow = 'auto';
document.body.scroll = "yes";
}
}
function hideEditorPane() {
hideScrollbars(false);
if (editorScope) { editorScope.dismiss(); }
}
scope.onAppEvent("dashboard-loaded", hideEditorPane);
scope.onAppEvent('hide-dash-editor', hideEditorPane);
scope.onAppEvent('show-dash-editor', function(evt, payload) {
if (lastEditor === payload.src) {
hideEditorPane();
return;
}
hideEditorPane();
scope.exitFullscreen();
lastEditor = payload.src;
editorScope = payload.scope ? payload.scope.$new() : scope.$new();
editorScope.dismiss = function() {
editorScope.$destroy();
elem.empty();
lastEditor = null;
editorScope = null;
hideScrollbars(false);
};
// hide page scrollbars while edit pane is visible
hideScrollbars(true);
var src = "'" + payload.src + "'";
var view = $('<div class="dashboard-edit-view" ng-include="' + src + '"></div>');
elem.append(view);
$compile(elem.contents())(editorScope);
});
}
};
});
});

View File

@@ -1,12 +1,13 @@
define([
'angular'
'angular',
'kbn'
],
function (angular) {
function (angular, kbn) {
'use strict';
var module = angular.module('kibana.directives');
var module = angular.module('grafana.directives');
module.directive('dashUpload', function(timer, dashboard, alertSrv) {
module.directive('dashUpload', function(timer, alertSrv, $location) {
return {
restrict: 'A',
link: function(scope) {
@@ -14,8 +15,17 @@ function (angular) {
var files = evt.target.files; // FileList object
var readerOnload = function() {
return function(e) {
dashboard.dash_load(JSON.parse(e.target.result));
scope.$apply();
scope.$apply(function() {
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);
});
};
};
for (var i = 0, f; f = files[i]; i++) {
@@ -34,4 +44,4 @@ function (angular) {
}
};
});
});
});

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

@@ -0,0 +1,89 @@
define([
'angular',
'jquery',
'config',
'./panelMenu',
],
function (angular, $, config) {
'use strict';
angular
.module('grafana.directives')
.directive('grafanaPanel', function($compile, $parse) {
var container = '<div class="panel-container"></div>';
var content = '<div class="panel-content"></div>';
var panelHeader =
'<div class="panel-header">'+
'<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>' +
'<div class="panel-title-container drag-handle" panel-menu></div>' +
'</div>'+
'</div>';
return {
restrict: 'E',
link: function($scope, elem, attr) {
var getter = $parse(attr.type), panelType = getter($scope);
var newScope = $scope.$new();
$scope.kbnJqUiDraggableOptions = {
revert: 'invalid',
helper: function() {
return $('<div style="width:200px;height:100px;background: rgba(100,100,100,0.50);"/>');
},
placeholder: 'keep'
};
// compile the module and uncloack. We're done
function loadModule($module) {
$module.appendTo(elem);
elem.wrap(container);
/* jshint indent:false */
$compile(elem.contents())(newScope);
elem.removeClass("ng-cloak");
var panelCtrlElem = $(elem.children()[0]);
var panelCtrlScope = panelCtrlElem.data().$scope;
panelCtrlScope.$watchGroup(['fullscreen', 'panel.height', 'row.height'], function() {
panelCtrlElem.css({ minHeight: panelCtrlScope.panel.height || panelCtrlScope.row.height });
panelCtrlElem.toggleClass('panel-fullscreen', panelCtrlScope.fullscreen ? true : false);
});
}
newScope.$on('$destroy',function() {
elem.unbind();
elem.remove();
});
elem.addClass('ng-cloak');
var panelPath = config.panels[panelType].path;
$scope.require([
'jquery',
'text!'+panelPath+'/module.html',
panelPath + "/module",
], function ($, moduleTemplate) {
var $module = $(moduleTemplate);
$module.prepend(panelHeader);
$module.first().find('.panel-header').nextAll().wrapAll(content);
loadModule($module);
});
}
};
});
});

View File

@@ -1,13 +1,12 @@
define([
'angular',
'underscore'
],
function (angular, _) {
function (angular) {
'use strict';
angular
.module('kibana.directives')
.directive('kibanaSimplePanel', function($compile) {
.module('grafana.directives')
.directive('grafanaSimplePanel', function($compile) {
var panelLoading = '<span ng-show="panelMeta.loading == true">' +
'<span style="font-size:72px;font-weight:200">'+
'<i class="icon-spinner icon-spin"></i> loading ...' +
@@ -60,18 +59,8 @@ function (angular, _) {
loadController(name);
});
if(attr.panel) {
$scope.$watch(attr.panel, function (panel) {
// If the panel attribute is specified, create a new scope. This ruins configuration
// so don't do it with anything that needs to use editor.html
if(!_.isUndefined(panel)) {
$scope = $scope.$new();
$scope.panel = angular.fromJson(panel);
}
});
}
}
};
});
});
});

View File

@@ -5,7 +5,7 @@ function (angular) {
'use strict';
angular
.module('kibana.directives')
.module('grafana.directives')
.directive('grafanaVersionCheck', function($http, grafanaVersion) {
return {
restrict: 'A',
@@ -14,7 +14,7 @@ function (angular) {
return;
}
$http({ method: 'GET', url: 'http://grafanarel.s3.amazonaws.com/latest.json' })
$http({ method: 'GET', url: 'https://grafanarel.s3.amazonaws.com/latest.json' })
.then(function(response) {
if (!response.data || !response.data.version) {
return;
@@ -30,4 +30,4 @@ function (angular) {
}
};
});
});
});

View File

@@ -1,14 +1,14 @@
define([
'angular',
'underscore',
'lodash',
'jquery',
],
function (angular, _, $) {
'use strict';
angular
.module('kibana.directives')
.directive('graphiteFuncEditor', function($compile) {
.module('grafana.directives')
.directive('graphiteFuncEditor', function($compile, templateSrv) {
var funcSpanTemplate = '<a ng-click="">{{func.def.name}}</a><span>(</span>';
var paramTemplate = '<input type="text" style="display:none"' +
@@ -69,12 +69,12 @@ function (angular, _, $) {
function inputBlur(paramIndex) {
/*jshint validthis:true */
var $input = $(this);
var $link = $input.prev();
var newValue = $input.val();
if ($input.val() !== '' || func.def.params[paramIndex].optional) {
$link.text($input.val());
if (newValue !== '' || func.def.params[paramIndex].optional) {
$link.html(templateSrv.highlightVariablesAsHtml(newValue));
func.updateParam($input.val(), paramIndex);
scheduledRelinkIfNeeded();
@@ -88,7 +88,6 @@ function (angular, _, $) {
function inputKeyPress(paramIndex, e) {
/*jshint validthis:true */
if(e.which === 13) {
inputBlur.call(this, paramIndex);
}
@@ -147,7 +146,7 @@ function (angular, _, $) {
$funcLink.appendTo(elem);
_.each(funcDef.params, function(param, index) {
if (param.optional && !func.params[index]) {
if (param.optional && func.params.length <= index) {
return;
}
@@ -155,7 +154,8 @@ function (angular, _, $) {
$('<span>, </span>').appendTo(elem);
}
var $paramLink = $('<a ng-click="" class="graphite-func-param-link">' + func.params[index] + '</a>');
var paramValue = templateSrv.highlightVariablesAsHtml(func.params[index]);
var $paramLink = $('<a ng-click="" class="graphite-func-param-link">' + paramValue + '</a>');
var $input = $(paramTemplate);
paramCountAtLink++;
@@ -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;
}
@@ -239,4 +241,4 @@ function (angular, _, $) {
});
});
});

View File

@@ -0,0 +1,134 @@
define([
'angular',
'app',
'lodash',
'jquery',
],
function (angular, app, _, $) {
'use strict';
angular
.module('grafana.directives')
.directive('graphiteSegment', function($compile, $sce) {
var inputTemplate = '<input type="text" data-provide="typeahead" ' +
' class="grafana-target-text-input input-medium"' +
' spellcheck="false" style="display:none"></input>';
var buttonTemplate = '<a class="grafana-target-segment" tabindex="1" focus-me="segment.focus" ng-bind-html="segment.html"></a>';
return {
link: function($scope, elem) {
var $input = $(inputTemplate);
var $button = $(buttonTemplate);
var segment = $scope.segment;
var options = null;
var cancelBlur = null;
$input.appendTo(elem);
$button.appendTo(elem);
$scope.updateVariableValue = function(value) {
if (value === '' || segment.value === value) {
return;
}
$scope.$apply(function() {
var selected = _.findWhere($scope.altSegments, { value: value });
if (selected) {
segment.value = selected.value;
segment.html = selected.html;
segment.expandable = selected.expandable;
}
else {
segment.value = value;
segment.html = $sce.trustAsHtml(value);
segment.expandable = true;
}
$scope.segmentValueChanged(segment, $scope.$index);
});
};
$scope.switchToLink = function(now) {
if (now === true || cancelBlur) {
clearTimeout(cancelBlur);
cancelBlur = null;
$input.hide();
$button.show();
$scope.updateVariableValue($input.val());
}
else {
// need to have long delay because the blur
// happens long before the click event on the typeahead options
cancelBlur = setTimeout($scope.switchToLink, 350);
}
};
$scope.source = function(query, callback) {
if (options) { return options; }
$scope.$apply(function() {
$scope.getAltSegments($scope.$index).then(function() {
options = _.map($scope.altSegments, function(alt) { return alt.value; });
// add custom values
if (segment.value !== 'select metric' && _.indexOf(options, segment.value) === -1) {
options.unshift(segment.value);
}
callback(options);
});
});
};
$scope.updater = function(value) {
if (value === segment.value) {
clearTimeout(cancelBlur);
$input.focus();
return value;
}
$input.val(value);
$scope.switchToLink(true);
return value;
};
$input.attr('data-provide', 'typeahead');
$input.typeahead({ source: $scope.source, minLength: 0, items: 10000, updater: $scope.updater });
var typeahead = $input.data('typeahead');
typeahead.lookup = function () {
this.query = this.$element.val() || '';
var items = this.source(this.query, $.proxy(this.process, this));
return items ? this.process(items) : items;
};
$button.keydown(function(evt) {
// trigger typeahead on down arrow or enter key
if (evt.keyCode === 40 || evt.keyCode === 13) {
$button.click();
}
});
$button.click(function() {
options = null;
$input.css('width', ($button.width() + 16) + 'px');
$button.hide();
$input.show();
$input.focus();
var typeahead = $input.data('typeahead');
if (typeahead) {
$input.val('');
typeahead.lookup();
}
});
$input.blur($scope.switchToLink);
$compile(elem.contents())($scope);
}
};
});
});

View File

@@ -1,13 +1,13 @@
define([
'angular',
'underscore',
'lodash',
'jquery',
],
function (angular, _, $) {
'use strict';
angular
.module('kibana.directives')
.module('grafana.directives')
.directive('influxdbFuncEditor', function($compile) {
var funcSpanTemplate = '<a gf-dropdown="functionMenu" class="dropdown-toggle" ' +

View File

@@ -1,118 +0,0 @@
define([
'angular',
'jquery',
'underscore',
'../controllers/panelBaseCtrl'
],
function (angular, $, _, PanelBaseCtrl) {
'use strict';
angular
.module('kibana.directives')
.directive('kibanaPanel', function($compile, $timeout, $rootScope, $injector) {
var container = '<div class="panel-container"></div>';
var content = '<div class="panel-content"></div>';
var panelHeader =
'<div class="panel-header">'+
'<div class="row-fluid">' +
'<div class="span12 alert-error panel-error small" ng-show="panel.error">' +
'<a class="close" ng-click="panel.error=false">&times;</a>' +
'<span><i class="icon-exclamation-sign"></i> <strong>Oops!</strong> {{panel.error}} </span>' +
'<span class="pointer panel-error-inspector-link" config-modal="app/partials/inspector.html">View details</span>' +
'</div>' +
'</div>\n' +
'<div class="row-fluid panel-extra">' +
'<div class="panel-extra-container">' +
'<span class="panel-loading" ng-show="panelMeta.loading == true">' +
'<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="row.panels" ' +
'>' +
'{{panel.title || "No title"}}' +
'</span>' +
'</span>'+
'</div>'+
'</div>\n'+
'</div>';
return {
restrict: 'E',
link: function($scope, elem, attr) {
// once we have the template, scan it for controllers and
// load the module.js if we have any
var newScope = $scope.$new();
$scope.kbnJqUiDraggableOptions = {
revert: 'invalid',
helper: function() {
return $('<div style="width:200px;height:100px;background: rgba(100,100,100,0.50);"/>');
},
placeholder: 'keep'
};
// compile the module and uncloack. We're done
function loadModule($module) {
$module.appendTo(elem);
elem.wrap(container);
/* jshint indent:false */
$compile(elem.contents())(newScope);
elem.removeClass("ng-cloak");
}
newScope.$on('$destroy',function() {
elem.unbind();
elem.remove();
});
newScope.initBaseController = function(self, scope) {
$injector.invoke(PanelBaseCtrl, self, { $scope: scope });
};
$scope.$watch(attr.type, function (name) {
elem.addClass("ng-cloak");
// load the panels module file, then render it in the dom.
var nameAsPath = name.replace(".", "/");
$scope.require([
'jquery',
'text!panels/'+nameAsPath+'/module.html'
], function ($, moduleTemplate) {
var $module = $(moduleTemplate);
// top level controllers
var $controllers = $module.filter('ngcontroller, [ng-controller], .ng-controller');
// add child controllers
$controllers = $controllers.add($module.find('ngcontroller, [ng-controller], .ng-controller'));
if ($controllers.length) {
$controllers.first().prepend(panelHeader);
$controllers.first().find('.panel-header').nextAll().wrapAll(content);
$scope.require(['panels/' + nameAsPath + '/module'], function() {
loadModule($module);
});
} else {
loadModule($module);
}
});
});
}
};
});
});

View File

@@ -5,7 +5,7 @@ function (angular) {
'use strict';
angular
.module('kibana.directives')
.module('grafana.directives')
.directive('ngBlur', ['$parse', function($parse) {
return function(scope, element, attr) {
var fn = $parse(attr['ngBlur']);

View File

@@ -3,17 +3,18 @@ function (angular) {
'use strict';
angular
.module('kibana.directives')
.module('grafana.directives')
.directive('ngModelOnblur', function() {
return {
restrict: 'A',
priority: 1,
require: 'ngModel',
link: function(scope, elm, attr, ngModelCtrl) {
if (attr.type === 'radio' || attr.type === 'checkbox') {
return;
}
elm.unbind('input').unbind('keydown').unbind('change');
elm.off('input keydown change');
elm.bind('blur', function() {
scope.$apply(function() {
ngModelCtrl.$setViewValue(elm.val());
@@ -22,4 +23,4 @@ function (angular) {
}
};
});
});
});

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

@@ -6,7 +6,7 @@ function (angular) {
'use strict';
angular
.module('kibana.directives')
.module('grafana.directives')
.directive('spectrumPicker', function() {
return {
restrict: 'E',
@@ -32,7 +32,11 @@ function (angular) {
};
input.spectrum(options);
scope.$on('$destroy', function() {
input.spectrum('destroy');
});
}
};
});
});
});

View File

@@ -0,0 +1,82 @@
define([
'angular',
'app',
'lodash',
'jquery',
],
function (angular, app, _, $) {
'use strict';
angular
.module('grafana.directives')
.directive('templateParamSelector', function($compile) {
var inputTemplate = '<input type="text" data-provide="typeahead" ' +
' class="grafana-target-text-input input-medium"' +
' spellcheck="false" style="display:none"></input>';
var buttonTemplate = '<a class="grafana-target-segment tabindex="1">{{variable.current.text}}</a>';
return {
link: function($scope, elem) {
var $input = $(inputTemplate);
var $button = $(buttonTemplate);
var variable = $scope.variable;
$input.appendTo(elem);
$button.appendTo(elem);
function updateVariableValue(value) {
$scope.$apply(function() {
var selected = _.findWhere(variable.options, { text: value });
if (!selected) {
selected = { text: value, value: value };
}
$scope.setVariableValue($scope.variable, selected);
});
}
$input.attr('data-provide', 'typeahead');
$input.typeahead({
minLength: 0,
items: 1000,
updater: function(value) {
$input.val(value);
$input.trigger('blur');
return value;
}
});
var typeahead = $input.data('typeahead');
typeahead.lookup = function () {
var options = _.map(variable.options, function(option) { return option.text; });
this.query = this.$element.val() || '';
return this.process(options);
};
$button.click(function() {
$input.css('width', ($button.width() + 16) + 'px');
$button.hide();
$input.show();
$input.focus();
var typeahead = $input.data('typeahead');
if (typeahead) {
$input.val('');
typeahead.lookup();
}
});
$input.blur(function() {
if ($input.val() !== '') { updateVariableValue($input.val()); }
$input.hide();
$button.show();
$button.focus();
});
$compile(elem.contents())($scope);
}
};
});
});

View File

@@ -6,15 +6,38 @@ function (angular, kbn) {
'use strict';
angular
.module('kibana.directives')
.module('grafana.directives')
.directive('tip', function($compile) {
return {
restrict: 'E',
link: function(scope, elem, attrs) {
var _t = '<i class="icon-'+(attrs.icon||'question-sign')+'" bs-tooltip="\''+
var _t = '<i class="grafana-tip icon-'+(attrs.icon||'question-sign')+'" bs-tooltip="\''+
kbn.addslashes(elem.text())+'\'"></i>';
elem.replaceWith($compile(angular.element(_t))(scope));
}
};
});
});
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

@@ -1,7 +1,7 @@
define(['angular', 'jquery', 'underscore', 'moment'], function (angular, $, _, moment) {
define(['angular', 'jquery', 'lodash', 'moment'], function (angular, $, _, moment) {
'use strict';
var module = angular.module('kibana.filters');
var module = angular.module('grafana.filters');
module.filter('stringSort', function() {
return function(input) {
@@ -9,18 +9,6 @@ define(['angular', 'jquery', 'underscore', 'moment'], function (angular, $, _, m
};
});
/*
Filter an array of objects by elasticsearch version requirements
*/
module.filter('esVersion', function(esVersion) {
return function(items, require) {
var ret = _.filter(items,function(qt) {
return esVersion.is(qt[require]) ? true : false;
});
return ret;
};
});
module.filter('slice', function() {
return function(arr, start, end) {
if(!_.isUndefined(arr)) {
@@ -67,57 +55,14 @@ define(['angular', 'jquery', 'underscore', 'moment'], function (angular, $, _, m
};
});
module.filter('urlLink', function() {
var //URLs starting with http://, https://, or ftp://
r1 = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim,
//URLs starting with "www." (without // before it, or it'd re-link the ones done above).
r2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim,
//Change email addresses to mailto:: links.
r3 = /(\w+@[a-zA-Z_]+?\.[a-zA-Z]{2,6})/gim;
module.filter('interpolateTemplateVars', function(templateSrv) {
function interpolateTemplateVars(text) {
return templateSrv.replaceWithText(text);
}
var urlLink = function(text) {
var t1,t2,t3;
if(!_.isString(text)) {
return text;
} else {
_.each(text.match(r1), function() {
t1 = text.replace(r1, "<a href=\"$1\" target=\"_blank\">$1</a>");
});
text = t1 || text;
_.each(text.match(r2), function() {
t2 = text.replace(r2, "$1<a href=\"http://$2\" target=\"_blank\">$2</a>");
});
text = t2 || text;
_.each(text.match(r3), function() {
t3 = text.replace(r3, "<a href=\"mailto:$1\">$1</a>");
});
text = t3 || text;
return text;
}
};
return function(text) {
return _.isArray(text)
? _.map(text, urlLink)
: urlLink(text);
};
interpolateTemplateVars.$stateful = true;
return interpolateTemplateVars;
});
module.filter('gistid', function() {
var gist_pattern = /(\d{5,})|([a-z0-9]{10,})|(gist.github.com(\/*.*)\/[a-z0-9]{5,}\/*$)/;
return function(input) {
if(!(_.isUndefined(input))) {
var output = input.match(gist_pattern);
if(!_.isNull(output) && !_.isUndefined(output)) {
return output[0].replace(/.*\//, '');
}
}
};
});
module.filter('urlDecode', function() {
return function(input) {
return decodeURIComponent(input);
};
});
});
});

View File

@@ -1,76 +0,0 @@
<div bindonce class="modal-body">
<div class="pull-right editor-title">Annotations</div>
<div class="editor-row">
<table class="table table-striped annotation-editor-table" style="width: 700px">
<thead>
<th width="90%">Name</th>
<th width="1%"></th>
<th width="1%"></th>
<th width="1%"></th>
</thead>
<tr ng-repeat="annotation in panel.annotations">
<td>
<a ng-click="edit(annotation)" bs-tooltip="'Click to edit'">
<i class="icon-cog"></i>
{{annotation.name}}
</a>
</td>
<td><i ng-click="_.move(panel.annotations,$index,$index-1)" ng-hide="$first" class="pointer icon-arrow-up"></i></td>
<td><i ng-click="_.move(panel.annotations,$index,$index+1)" ng-hide="$last" class="pointer icon-arrow-down"></i></td>
<td><i ng-click="panel.annotations = _.without(panel.annotations, annotation)" class="pointer icon-remove"></i></td>
</tr>
</table>
</div>
<div class="editor-row">
<h4 ng-show="currentIsNew">Add Annotation</h4>
<h4 ng-show="!currentIsNew">Edit Annotation</h4>
<div class="editor-option">
<label class="small">Name</label>
<input type="text" class="input-medium" ng-model='currentAnnnotation.name' placeholder="name"></input>
</div>
<div class="editor-option">
<label class="small">Type</label>
<select ng-model="currentAnnnotation.type" ng-options="f for f in ['graphite metric', 'graphite events']"></select>
</div>
<div class="editor-option">
<label class="small">Icon color</label>
<spectrum-picker ng-model="currentAnnnotation.iconColor"></spectrum-picker>
</div>
<div class="editor-option">
<label class="small">Icon size</label>
<select class="input-mini" ng-model="currentAnnnotation.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="currentAnnnotation.showLine" ng-checked="currentAnnnotation.showLine">
</div>
<div class="editor-option">
<label class="small">Line color</label>
<spectrum-picker ng-model="currentAnnnotation.lineColor"></spectrum-picker>
</div>
</div>
<div class="editor-row" ng-if="currentAnnnotation.type === 'graphite metric'">
<div class="editor-option">
<label class="small">Graphite target expression</label>
<input type="text" class="span10" ng-model='currentAnnnotation.target' placeholder=""></input>
</div>
</div>
<div class="editor-row" ng-if="currentAnnnotation.type === 'graphite events'">
<div class="editor-option">
<label class="small">Graphite event tags</label>
<input type="text" ng-model='currentAnnnotation.tags' placeholder=""></input>
</div>
</div>
</div>
<div class="modal-footer">
<button ng-show="currentIsNew" type="button" class="btn btn-success" ng-click="add()">Add annotation</button>
<button ng-show="!currentIsNew" type="button" class="btn btn-success" ng-click="update()">Update</button>
<button type="button" class="btn btn-danger" ng-click="close_edit();dismiss();dashboard.refresh();">Close</button>
</div>

View File

@@ -1,12 +0,0 @@
<div ng-controller='AnnotationsCtrl' ng-init="init()">
<div class="submenu-toggle" ng-repeat="annotation in panel.annotations" ng-class="{'annotation-disabled': !annotation.enable }">
<i class="annotation-color-icon icon-minus"></i>
<a ng-click="hide(annotation)" class="small">{{annotation.name}}</a>
</div>
<div class="submenu-control-edit">
<i class="icon-cog pointer" config-modal="app/panels/annotations/editor.html" bs-tooltip="'Edit annotations'" ></i>
</div>
</div>

View File

@@ -1,67 +0,0 @@
/*
## annotations
*/
define([
'angular',
'app',
'underscore'
],
function (angular, app, _) {
'use strict';
var module = angular.module('kibana.panels.annotations', []);
app.useModule(module);
module.controller('AnnotationsCtrl', function($scope, dashboard, $rootScope) {
$scope.panelMeta = {
status : "Stable",
description : "Annotations"
};
// Set and populate defaults
var _d = {
annotations: []
};
var annotationDefaults = {
name: '',
type: 'graphite metric',
showLine: true,
iconColor: '#C0C6BE',
lineColor: 'rgba(255, 96, 96, 0.592157)',
iconSize: 13,
enable: true
};
_.defaults($scope.panel,_d);
$scope.init = function() {
$scope.currentAnnnotation = angular.copy(annotationDefaults);
$scope.currentIsNew = true;
};
$scope.edit = function(annotation) {
$scope.currentAnnnotation = annotation;
$scope.currentIsNew = false;
};
$scope.update = function() {
$scope.currentAnnnotation = angular.copy(annotationDefaults);
$scope.currentIsNew = true;
};
$scope.add = function() {
$scope.panel.annotations.push($scope.currentAnnnotation);
$scope.currentAnnnotation = angular.copy(annotationDefaults);
};
$scope.hide = function (annotation) {
annotation.enable = !annotation.enable;
$rootScope.$broadcast('refresh');
};
});
});

View File

@@ -1,50 +0,0 @@
<div ng-controller='filtering' ng-init="init()">
<div class='filtering-container'>
<div ng-repeat="filter in filter.templateParameters" class="small filter-panel-filter">
<div>
<i class="filter-action pointer icon-remove" bs-tooltip="'Remove'" ng-click="remove(filter)"></i>
<i class="filter-action pointer icon-edit" ng-hide="filter.editing" bs-tooltip="'Edit'" ng-click="filter.editing = true"></i>
</div>
<div ng-hide="filter.editing" style="margin-right: 45px;">
<ul class="unstyled">
<li ng-if="filter.name" class="dropdown">
{{filter.name}} :
<a class="dropdown-toggle" data-toggle="dropdown">
{{filter.current.text}}
</a>
<ul class="dropdown-menu">
<li ng-repeat="option in filter.options">
<a ng-click="filterOptionSelected(filter, option)">{{option.text | urlDecode}}</a>
</li>
</ul>
</li>
</ul>
</div>
<form ng-show="filter.editing">
<ul class="unstyled">
<li>
<strong>name</strong>:<br/>
<input type='text' ng-model="filter.name">
</li>
<li>
<strong>filter.query</strong>:<br/>
<input type='text' ng-model="filter.query">
</li>
<li>
<label for="includeAll">Include all:</label>
<input id="includeAll" type='checkbox' ng-model="filter.includeAll">
</li>
</ul>
<div>
<input type="submit" value="Update" ng-click="applyFilter(filter)" class="filter-apply btn btn-success btn-mini" bs-tooltip="'Update and refresh'"/>
<button ng-click="filter.editing=undefined" class="filter-apply btn btn-mini" bs-tooltip="'Save without refresh'">Close</button>
</div>
</form>
</div>
<i class="pointer icon-plus-sign add-filter-action" ng-click="add()" bs-tooltip="'Add metric filter / param'" data-placement="right"></i>
</div>
</div>

View File

@@ -1,104 +0,0 @@
/*
## filtering
*/
define([
'angular',
'app',
'underscore'
],
function (angular, app, _) {
'use strict';
var module = angular.module('kibana.panels.filtering', []);
app.useModule(module);
module.controller('filtering', function($scope, datasourceSrv, $rootScope, $timeout, $q) {
$scope.panelMeta = {
status : "Stable",
description : "graphite target filters"
};
// Set and populate defaults
var _d = {
};
_.defaults($scope.panel,_d);
$scope.init = function() {
// empty. Don't know if I need the function then.
};
$scope.remove = function(templateParameter) {
$scope.filter.removeTemplateParameter(templateParameter);
};
$scope.filterOptionSelected = function(templateParameter, option, recursive) {
templateParameter.current = option;
$scope.filter.updateTemplateData();
return $scope.applyFilterToOtherFilters(templateParameter)
.then(function() {
// only refresh in the outermost call
if (!recursive) {
$scope.dashboard.refresh();
}
});
};
$scope.applyFilterToOtherFilters = function(updatedTemplatedParam) {
var promises = _.map($scope.filter.templateParameters, function(templateParam) {
if (templateParam === updatedTemplatedParam) {
return;
}
if (templateParam.query.indexOf(updatedTemplatedParam.name) !== -1) {
return $scope.applyFilter(templateParam);
}
});
return $q.all(promises);
};
$scope.applyFilter = function(templateParam) {
return datasourceSrv.default.metricFindQuery($scope.filter, templateParam.query)
.then(function (results) {
templateParam.editing = undefined;
templateParam.options = _.map(results, function(node) {
return { text: node.text, value: node.text };
});
if (templateParam.includeAll) {
var allExpr = '{';
_.each(templateParam.options, function(option) {
allExpr += option.text + ',';
});
allExpr = allExpr.substring(0, allExpr.length - 1) + '}';
templateParam.options.unshift({text: 'All', value: allExpr});
}
// if parameter has current value
// if it exists in options array keep value
if (templateParam.current) {
var currentExists = _.findWhere(templateParam.options, { value: templateParam.current.value });
if (currentExists) {
return $scope.filterOptionSelected(templateParam, templateParam.current, true);
}
}
return $scope.filterOptionSelected(templateParam, templateParam.options[0], true);
});
};
$scope.add = function() {
$scope.filter.addTemplateParameter({
type : 'filter',
name : 'filter name',
editing : true,
query : 'metric.path.query.*',
});
};
});
});

View File

@@ -1,48 +1,59 @@
<div class="editor-row">
<div class="section">
<h5>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>
<div class="editor-option">
<label class="small">Left Y Format <tip>Y-axis formatting</tip></label>
<select class="input-small" ng-model="panel.y_formats[0]" ng-options="f for f in ['none','short','bytes', 'bits', 's', 'ms', 'µs', 'ns']" ng-change="render()"></select>
</div>
<div class="editor-option">
<label class="small">Right Y Format <tip>Y-axis formatting</tip></label>
<select class="input-small" ng-model="panel.y_formats[1]" ng-options="f for f in ['none','short','bytes', 'bits', 's', 'ms', 'µs', 'ns']" ng-change="render()"></select>
</div>
<div class="editor-option">
<label class="small">Left Y-axis label</label>
<input ng-change="get_data()" ng-model-onblur placeholder="" type="text" class="input-medium" ng-model="panel.leftYAxisLabel">
</div>
<div class="editor-option">
<label class="small">Right Y-axis label</label>
<input ng-change="get_data()" ng-model-onblur placeholder="" type="text" class="input-medium" ng-model="panel.rightYAxisLabel">
</div>
<h5>Left Y Axis</h5>
<div class="editor-option">
<label class="small">Format <tip>Y-axis formatting</tip></label>
<select class="input-small" ng-model="panel.y_formats[0]" ng-options="f for f in ['none','short','bytes', 'bits', 'bps', 's', 'ms', 'µs', 'ns', 'percent']" ng-change="render()"></select>
</div>
<div class="editor-option">
<label class="small">Min / <a ng-click="toggleGridMinMax('leftMin')">Auto <i class="icon-star" ng-show="_.isNull(panel.grid.leftMin)"></i></a></label>
<input type="number" class="input-small" ng-model="panel.grid.leftMin" ng-change="render()" ng-model-onblur />
</div>
<div class="editor-option">
<label class="small">Max / <a ng-click="toggleGridMinMax('leftMax')">Auto <i class="icon-star" ng-show="_.isNull(panel.grid.leftMax)"></i></a></label>
<input type="number" class="input-small" ng-model="panel.grid.leftMax" ng-change="render()" ng-model-onblur />
</div>
<div class="editor-option">
<label class="small">Label</label>
<input ng-change="get_data()" ng-model-onblur placeholder="" type="text" class="input-medium" ng-model="panel.leftYAxisLabel">
</div>
</div>
<div class="section">
<h5>Right Y Axis</h5>
<div class="editor-option">
<label class="small">Format <tip>Y-axis formatting</tip></label>
<select class="input-small" ng-model="panel.y_formats[1]" ng-options="f for f in ['none','short','bytes', 'bits', 'bps', 's', 'ms', 'µs', 'ns', 'percent']" ng-change="render()"></select>
</div>
<div class="editor-option">
<label class="small">Min / <a ng-click="toggleGridMinMax('rightMin')">Auto <i class="icon-star" ng-show="_.isNull(panel.grid.rightMin)"></i></a></label>
<input type="number" class="input-small" ng-model="panel.grid.rightMin" ng-change="render()" ng-model-onblur />
</div>
<div class="editor-option">
<label class="small">Max / <a ng-click="toggleGridMinMax('rightMax')">Auto <i class="icon-star" ng-show="_.isNull(panel.grid.rightMax)"></i></a></label>
<input type="number" class="input-small" ng-model="panel.grid.rightMax" ng-change="render()" ng-model-onblur />
</div>
</div>
</div>
<div class="editor-row">
<div class="editor-row">
<div class="section">
<h5>Grid</h5>
<div class="editor-option">
<label class="small">Min / <a ng-click="toggleGridMinMax('min')">Auto <i class="icon-star" ng-show="_.isNull(panel.grid.min)"></i></a></label>
<input type="number" class="input-small" ng-model="panel.grid.min" ng-change="render()" ng-model-onblur />
</div>
<div class="editor-option">
<label class="small">Max / <a ng-click="toggleGridMinMax('max')">Auto <i class="icon-star" ng-show="_.isNull(panel.grid.max)"></i></a></label>
<input type="number" class="input-small" ng-model="panel.grid.max" ng-change="render()" ng-model-onblur />
</div>
<h5>Legend styles</h5>
<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>
<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">
@@ -63,43 +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>Legend</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>
<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>
<h5>Show Axes</h5>
<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,46 +3,66 @@ define([
'jquery',
'kbn',
'moment',
'underscore'
'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('kibana.directives');
var module = angular.module('grafana.directives');
module.directive('grafanaGraph', function($rootScope, dashboard) {
module.directive('grafanaGraph', function($rootScope, timeSrv) {
return {
restrict: 'A',
template: '<div> </div>',
link: function(scope, elem) {
var data, plot, annotations;
var hiddenData = {};
var dashboard = scope.dashboard;
var data, annotations;
var sortedSeries;
var legendSideLastValue = null;
scope.crosshairEmiter = false;
scope.$on('refresh',function() {
if (scope.otherPanelInFullscreenMode()) { return; }
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(e, series) {
_.each(series, function(serie) {
if (hiddenData[serie.alias]) {
data.push(hiddenData[serie.alias]);
delete hiddenData[serie.alias];
}
});
scope.onAppEvent('clearCrosshair', function() {
var plot = elem.data().plot;
if (plot) {
plot.clearCrosshair();
}
});
render_panel();
scope.$on('refresh', function() {
scope.get_data();
});
// Receive render events
scope.$on('render',function(event, renderData) {
data = renderData || data;
annotations = data.annotations;
render_panel();
});
// Re-render if the window is resized
angular.element(window).bind('resize', function() {
if (!data) {
scope.get_data();
return;
}
annotations = data.annotations || annotations;
render_panel();
});
@@ -53,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) {
height = height - 21; // subtract one line legend
if (scope.panel.legend.show && !scope.panel.legend.rightSide) {
height = height - 26; // subtract one line legend
}
elem.css('height', height + 'px');
@@ -82,6 +103,27 @@ function (angular, $, kbn, moment, _) {
render_panel_as_graphite_png(data);
return true;
}
if (elem.width() === 0) {
return;
}
}
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
@@ -91,21 +133,11 @@ function (angular, $, kbn, moment, _) {
}
var panel = scope.panel;
_.each(_.keys(scope.hiddenSeries), function(seriesAlias) {
var dataSeries = _.find(data, function(series) {
return series.info.alias === seriesAlias;
});
if (dataSeries) {
hiddenData[dataSeries.info.alias] = dataSeries;
data = _.without(data, dataSeries);
}
});
var stack = panel.stack ? true : null;
// Populate element
var options = {
hooks: { draw: [updateLegendValues] },
legend: { show: false },
series: {
stackpercent: panel.stack ? panel.percentage : false,
@@ -113,7 +145,7 @@ function (angular, $, kbn, moment, _) {
lines: {
show: panel.lines,
zero: false,
fill: panel.fill === 0 ? 0.001 : panel.fill/10,
fill: translateFillOption(panel.fill),
lineWidth: panel.linewidth,
steps: panel.steppedLine
},
@@ -128,13 +160,15 @@ 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
},
yaxes: [],
xaxis: {},
grid: {
minBorderMargin: 0,
markings: [],
backgroundColor: null,
borderWidth: 0,
@@ -144,16 +178,26 @@ function (angular, $, kbn, moment, _) {
selection: {
mode: "x",
color: '#666'
},
crosshair: {
mode: panel.tooltip.shared || dashboard.sharedCrosshair ? "x" : null
}
};
for (var i = 0; i < data.length; i++) {
var _d = data[i].getFlotPairs(panel.nullPointMode, panel.y_formats);
data[i].data = _d;
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.alias]) {
series.data = [];
series.stack = false;
}
}
if (panel.bars && 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);
@@ -161,9 +205,41 @@ function (angular, $, kbn, moment, _) {
addAnnotations(options);
configureAxisOptions(data, options);
plot = $.plot(elem, data, options);
sortedSeries = _.sortBy(data, function(series) { return series.zindex; });
addAxisLabels();
function callPlot() {
try {
$.plot(elem, sortedSeries, options);
} catch (e) {
console.log('flotcharts error', e);
}
addAxisLabels();
}
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;
}
else {
callPlot();
}
}
function translateFillOption(fill) {
return fill === 0 ? 0.001 : fill/10;
}
function shouldDelayDraw(panel) {
if (panel.legend.rightSide) {
return true;
}
if (legendSideLastValue !== null && panel.legend.rightSide !== legendSideLastValue) {
return true;
}
return false;
}
function addTimeAxis(options) {
@@ -172,7 +248,7 @@ function (angular, $, kbn, moment, _) {
var max = _.isUndefined(scope.range.to) ? null : scope.range.to.getTime();
options.xaxis = {
timezone: dashboard.current.timezone,
timezone: dashboard.timezone,
show: scope.panel['x-axis'],
mode: "time",
min: min,
@@ -258,8 +334,8 @@ function (angular, $, kbn, moment, _) {
var defaults = {
position: 'left',
show: scope.panel['y-axis'],
min: scope.panel.grid.min,
max: scope.panel.percentage && scope.panel.stack ? 100 : scope.panel.grid.max,
min: scope.panel.grid.leftMin,
max: scope.panel.percentage && scope.panel.stack ? 100 : scope.panel.grid.leftMax,
};
options.yaxes.push(defaults);
@@ -267,6 +343,8 @@ function (angular, $, kbn, moment, _) {
if (_.findWhere(data, {yaxis: 2})) {
var secondY = _.clone(defaults);
secondY.position = 'right';
secondY.min = scope.panel.grid.rightMin;
secondY.max = scope.panel.percentage && scope.panel.stack ? 100 : scope.panel.grid.rightMax;
options.yaxes.push(secondY);
configureAxisMode(options.yaxes[1], scope.panel.y_formats[1]);
}
@@ -275,9 +353,9 @@ function (angular, $, kbn, moment, _) {
}
function configureAxisMode(axis, format) {
if (format !== 'none') {
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) {
@@ -302,57 +380,19 @@ function (angular, $, kbn, moment, _) {
return "%H:%M";
}
var $tooltip = $('<div>');
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>' + ' ' +
(decodeURIComponent(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);
timestamp = dashboard.current.timezone === 'browser' ?
moment(item.datapoint[0]).format('YYYY-MM-DD HH:mm:ss') :
moment.utc(item.datapoint[0]).format('YYYY-MM-DD HH:mm:ss');
$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', '');
url += '&bgcolor=1f1f1f'; // @grayDarker & @kibanaPanelBackground
url += '&bgcolor=1f1f1f'; // @grayDarker & @grafanaPanelBackground
url += '&fgcolor=BBBFC2'; // @textColor & @grayLighter
url += scope.panel.stack ? '&areaMode=stacked' : '';
url += scope.panel.fill !== 0 ? ('&areaAlpha=' + (scope.panel.fill/10).toFixed(1)) : '';
url += scope.panel.linewidth !== 0 ? '&lineWidth=' + scope.panel.linewidth : '';
url += scope.panel.legend.show ? '&hideLegend=false' : '&hideLegend=true';
url += scope.panel.grid.min !== null ? '&yMin=' + scope.panel.grid.min : '';
url += scope.panel.grid.max !== null ? '&yMax=' + scope.panel.grid.max : '';
url += scope.panel.grid.leftMin !== null ? '&yMin=' + scope.panel.grid.leftMin : '';
url += scope.panel.grid.leftMax !== null ? '&yMax=' + scope.panel.grid.leftMax : '';
url += scope.panel.grid.rightMin !== null ? '&yMin=' + scope.panel.grid.rightMin : '';
url += scope.panel.grid.rightMax !== null ? '&yMax=' + scope.panel.grid.rightMax : '';
url += scope.panel['x-axis'] ? '' : '&hideAxes=true';
url += scope.panel['y-axis'] ? '' : '&hideYAxis=true';
@@ -363,6 +403,9 @@ function (angular, $, kbn, moment, _) {
case 'bits':
url += '&yUnitSystem=binary';
break;
case 'bps':
url += '&yUnitSystem=si';
break;
case 'short':
url += '&yUnitSystem=si';
break;
@@ -387,9 +430,13 @@ 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() {
scope.filter.setTime({
timeSrv.setTime({
from : moment.utc(ranges.xaxis.from).toDate(),
to : moment.utc(ranges.xaxis.to).toDate(),
});

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,61 +0,0 @@
<span ng-show="panel.legend.show"
ng-class="{'pull-right': series.yaxis === 2, 'hidden-series': hiddenSeries[series.alias]}"
ng-repeat='series in legend'
class="histogram-legend">
<i class='icon-minus pointer'
ng-style="{color: series.color}"
bs-popover="'colorPopup.html'"
>
</i>
<span class='small histogram-legend-item'>
<a ng-click="toggleSeries(series, $event)" data-unique="1" data-placement="{{series.yaxis === 2 ? 'bottomRight' : 'bottomLeft'}}">
{{series.alias | urlDecode}}
</a>
<span ng-if="panel.legend.values">
<span ng-show="panel.legend.current">
&nbsp;&nbsp;Current: {{series.current}}&nbsp;
</span>
<span ng-show="panel.legend.min">
&nbsp;&nbsp;Min: {{series.min}}&nbsp;
</span>
<span ng-show="panel.legend.max">
&nbsp;&nbsp;Max: {{series.max}}&nbsp;
</span>
<span ng-show="panel.legend.total">
&nbsp;&nbsp;Total: {{series.total}}&nbsp;
</span>
<span ng-show="panel.legend.avg">
&nbsp;&nbsp;Avg: {{series.avg}}&nbsp;
</span>
</span>
</span>
</span>
<script type="text/ng-template" id="colorPopup.html">
<div class="histogram-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)"
class="btn btn-mini"
ng-class="{'btn-success': series.yaxis === 1 }">
Left
</button>
<button ng-click="toggleYAxis(series)"
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,33 +1,42 @@
<div ng-controller='graph'
ng-init="init()"
style="min-height:{{panel.height || row.height}}"
ng-class="{'panel-fullscreen': fullscreen}">
<div ng-controller='GraphCtrl'>
<div style="position: relative">
<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 ng-if="panel.legend" class="grafana-legend-container">
<div ng-include="'app/panels/graph/legend.html'"></div>
</div>
<div class="clearfix"></div>
<div class="graph-legend-wrapper" ng-if="panel.legend.show" graph-legend></div>
</div>
<div class="panel-full-edit-tabs" ng-if="editMode">
<div ng-model="editor.index" bs-tabs>
<div ng-repeat="tab in editorTabs" data-title="{{tab}}">
</div>
</div>
<div class="clearfix"></div>
<div class="tab-content" ng-repeat="tab in panelMeta.fullEditorTabs" ng-show="editorTabs[editor.index] == tab.title">
<div ng-include src="tab.src"></div>
</div>
</div>
</div>
<div style="margin-top: 30px" ng-if="editMode">
<div class="dashboard-editor-header">
<div class="dashboard-editor-title">
<i class="icon icon-bar-chart"></i>
Graph
</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

@@ -1,149 +1,78 @@
/** @scratch /panels/5
* include::panels/histogram.asciidoc[]
*/
/** @scratch /panels/histogram/0
* == Histogram
* Status: *Stable*
*
* The histogram panel allow for the display of time charts. It includes several modes and tranformations
* to display event counts, mean, min, max and total of numeric fields, and derivatives of counter
* fields.
*
*/
define([
'angular',
'app',
'jquery',
'underscore',
'lodash',
'kbn',
'moment',
'./timeSeries',
'components/timeSeries',
'components/panelmeta',
'services/panelSrv',
'services/annotationsSrv',
'services/datasourceSrv',
'jquery.flot',
'jquery.flot.events',
'jquery.flot.selection',
'jquery.flot.time',
'jquery.flot.byte',
'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('kibana.panels.graph', []);
app.useModule(module);
var module = angular.module('grafana.panels.graph');
module.controller('graph', function($scope, $rootScope, datasourceSrv, $timeout, annotationsSrv) {
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','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 : {
max: null,
min: null,
leftMax: null,
rightMax: null,
leftMin: null,
rightMin: null,
threshold1: null,
threshold2: null,
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
@@ -153,133 +82,73 @@ 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,
},
targets: [],
// metric queries
targets: [{}],
// series color overrides
aliasColors: {},
aliasYAxis: {},
// other style overrides
seriesOverrides: [],
};
_.defaults($scope.panel,_d);
_.defaults($scope.panel.tooltip, _d.tooltip);
_.defaults($scope.panel.annotate, _d.annotate);
_.defaults($scope.panel.grid, _d.grid);
_.defaults($scope.panel.legend, _d.legend);
// backward compatible stuff
if (_.isBoolean($scope.panel.legend)) {
$scope.panel.legend = { show: $scope.panel.legend };
_.defaults($scope.panel.legend, _d.legend);
}
if ($scope.panel.y_format) {
$scope.panel.y_formats[0] = $scope.panel.y_format;
delete $scope.panel.y_format;
}
if ($scope.panel.y2_format) {
$scope.panel.y_formats[1] = $scope.panel.y2_format;
delete $scope.panel.y2_format;
}
$scope.init = function() {
$scope.initBaseController(this, $scope);
$scope.fullscreen = false;
$scope.editor = { index: 1 };
$scope.editorTabs = _.pluck($scope.panelMeta.fullEditorTabs,'title');
$scope.hiddenSeries = {};
$scope.datasources = datasourceSrv.listOptions();
$scope.setDatasource($scope.panel.datasource);
if ($scope.panel.targets.length === 0) {
$scope.panel.targets.push({});
}
};
$scope.setDatasource = function(datasource) {
$scope.panel.datasource = datasource;
$scope.datasource = datasourceSrv.get(datasource);
if (!$scope.datasource) {
$scope.panel.error = "Cannot find datasource " + datasource;
return;
}
$scope.get_data();
};
$scope.removeTarget = function (target) {
$scope.panel.targets = _.without($scope.panel.targets, target);
$scope.get_data();
};
$scope.hiddenSeries = {};
$scope.seriesList = [];
$scope.updateTimeRange = function () {
$scope.range = this.filter.timeRange();
$scope.rangeUnparsed = this.filter.timeRange(false);
$scope.resolution = Math.ceil($(window).width() * ($scope.panel.span / 12));
$scope.interval = '10m';
if ($scope.range) {
$scope.interval = kbn.secondsToHms(
kbn.calculate_interval($scope.range.from, $scope.range.to, $scope.resolution, 0) / 1000
);
$scope.range = timeSrv.timeRange();
$scope.rangeUnparsed = timeSrv.timeRange(false);
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);
};
$scope.get_data = function() {
delete $scope.panel.error;
$scope.panelMeta.loading = true;
$scope.updateTimeRange();
var graphiteQuery = {
var metricsQuery = {
range: $scope.rangeUnparsed,
interval: $scope.interval,
targets: $scope.panel.targets,
format: $scope.panel.renderer === 'png' ? 'png' : 'json',
maxDataPoints: $scope.resolution,
datasource: $scope.panel.datasource
cacheTimeout: $scope.panel.cacheTimeout
};
$scope.annotationsPromise = annotationsSrv.getAnnotations($scope.filter, $scope.rangeUnparsed);
$scope.annotationsPromise = annotationsSrv.getAnnotations($scope.rangeUnparsed, $scope.dashboard);
return $scope.datasource.query($scope.filter, graphiteQuery)
return $scope.datasource.query(metricsQuery)
.then($scope.dataHandler)
.then(null, function(err) {
$scope.panelMeta.loading = false;
$scope.panel.error = err.message || "Timeseries data request error";
$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;
}
@@ -288,41 +157,34 @@ 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 || !$scope.datapointsOutside;
$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);
});
};
$scope.seriesHandler = function(seriesData, index) {
var datapoints = seriesData.datapoints;
var alias = seriesData.target;
var color = $scope.panel.aliasColors[alias] || $scope.colors[index];
var yaxis = $scope.panel.aliasYAxis[alias] || 1;
var color = $scope.panel.aliasColors[alias] || $rootScope.colors[index];
var seriesInfo = {
alias: alias,
color: color,
enable: true,
yaxis: yaxis
};
$scope.legend.push(seriesInfo);
var series = new timeSeries.ZeroFilled({
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;
@@ -334,16 +196,8 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
return series;
};
$scope.add_target = function() {
$scope.panel.targets.push({target: ''});
};
$scope.otherPanelInFullscreenMode = function() {
return $rootScope.fullscreen && !$scope.fullscreen;
};
$scope.render = function(data) {
$scope.$emit('render', data);
$scope.$broadcast('render', data);
};
$scope.changeSeriesColor = function(series, color) {
@@ -353,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) {
@@ -375,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;
}
@@ -385,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;
}
@@ -402,8 +256,12 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
};
$scope.toggleYAxis = function(info) {
info.yaxis = info.yaxis === 2 ? 1 : 2;
$scope.panel.aliasYAxis[info.alias] = info.yaxis;
var override = _.findWhere($scope.panel.seriesOverrides, { alias: info.alias });
if (!override) {
override = { alias: info.alias };
$scope.panel.seriesOverrides.push(override);
}
override.yaxis = info.yaxis === 2 ? 1 : 2;
$scope.render();
};
@@ -412,6 +270,26 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
$scope.render();
};
$scope.addSeriesOverride = function(override) {
$scope.panel.seriesOverrides.push(override || {});
};
$scope.removeSeriesOverride = function(override) {
$scope.panel.seriesOverrides = _.without($scope.panel.seriesOverrides, override);
$scope.render();
};
// 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

@@ -0,0 +1,89 @@
define([
'angular',
'app',
'lodash',
], function(angular, app, _) {
'use strict';
var module = angular.module('grafana.panels.graph', []);
app.useModule(module);
module.controller('SeriesOverridesCtrl', function($scope) {
$scope.overrideMenu = [];
$scope.currentOverrides = [];
$scope.override = $scope.override || {};
$scope.addOverrideOption = function(name, propertyName, values) {
var option = {};
option.text = name;
option.propertyName = propertyName;
option.index = $scope.overrideMenu.length;
option.values = values;
option.submenu = _.map(values, function(value, index) {
return {
text: String(value),
click: 'menuItemSelected(' + option.index + ',' + index + ')'
};
});
$scope.overrideMenu.push(option);
};
$scope.setOverride = function(optionIndex, valueIndex) {
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();
};
$scope.removeOverride = function(option) {
delete $scope.override[option.propertyName];
$scope.updateCurrentOverrides();
$scope.render();
};
$scope.getSeriesNames = function() {
return _.map($scope.seriesList, function(series) {
return series.alias;
});
};
$scope.updateCurrentOverrides = function() {
$scope.currentOverrides = [];
_.each($scope.overrideMenu, function(option) {
var value = $scope.override[option.propertyName];
if (_.isUndefined(value)) { return; }
$scope.currentOverrides.push({
name: option.text,
propertyName: option.propertyName,
value: String(value)
});
});
};
$scope.addOverrideOption('Bars', 'bars', [true, false]);
$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]);
$scope.addOverrideOption('Stack', 'stack', [true, false, 2, 3, 4, 5]);
$scope.addOverrideOption('Y-axis', 'yaxis', [1, 2]);
$scope.addOverrideOption('Z-index', 'zindex', [-1,-2,-3,0,1,2,3]);
$scope.updateCurrentOverrides();
});
});

View File

@@ -1,17 +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">
@@ -29,27 +21,19 @@
<select class="input-mini" ng-model="panel.pointradius" ng-options="f for f in [1,2,3,4,5,6,7,8,9,10]" ng-change="render()"></select>
</div>
<div class="editor-option">
<label class="small">Null point mode <tip>Define how null values should be drawn</tip></label>
<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">
@@ -63,4 +47,57 @@
<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>
<div>
<div class="grafana-target" ng-repeat="override in panel.seriesOverrides" ng-controller="SeriesOverridesCtrl">
<div class="grafana-target-inner">
<ul class="grafana-segment-list">
<li class="grafana-target-segment">
<i class="icon-remove pointer" ng-click="removeSeriesOverride(override)"></i>
</li>
<li class="grafana-target-segment">
alias or regex
</li>
<li>
<input type="text"
ng-model="override.alias"
bs-typeahead="getSeriesNames"
ng-blur="render()"
data-min-length=0 data-items=100
class="input-medium grafana-target-segment-input" >
</li>
<li class="grafana-target-segment" ng-repeat="option in currentOverrides">
<i class="pointer icon-remove" ng-click="removeOverride(option)"></i>
{{option.name}}: {{option.value}}
</li>
<li class="dropdown" dropdown-typeahead="overrideMenu" dropdown-typeahead-on-select="setOverride($optionIndex, $valueIndex)">
</li>
</ul>
<div class="clearfix"></div>
</div>
</div>
</div>
<button class="btn btn-success" style="margin-top: 20px" ng-click="addSeriesOverride()">Add series override rule</button>
</div>
</div>

View File

@@ -1,74 +0,0 @@
define([
'underscore',
'kbn'
],
function (_, kbn) {
'use strict';
var ts = {};
ts.ZeroFilled = function (opts) {
this.datapoints = opts.datapoints;
this.info = opts.info;
this.label = opts.info.alias;
};
ts.ZeroFilled.prototype.getFlotPairs = function (fillStyle, yFormats) {
var result = [];
this.color = this.info.color;
this.yaxis = this.info.yaxis;
this.info.total = 0;
this.info.max = null;
this.info.min = 212312321312;
_.each(this.datapoints, function(valueArray) {
var currentTime = valueArray[1];
var currentValue = valueArray[0];
if (currentValue === null) {
if (fillStyle === 'connected') {
return;
}
if (fillStyle === 'null as zero') {
currentValue = 0;
}
}
if (_.isNumber(currentValue)) {
this.info.total += currentValue;
}
if (currentValue > this.info.max) {
this.info.max = currentValue;
}
if (currentValue < this.info.min) {
this.info.min = currentValue;
}
result.push([currentTime * 1000, currentValue]);
}, this);
if (result.length > 2) {
this.info.timeStep = result[1][0] - result[0][0];
}
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;
}
return result;
};
return ts;
});

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

@@ -9,10 +9,9 @@
</div>
<label class=small>Content
<span ng-show="panel.mode == 'html'">(This area uses HTML sanitized via AngularJS's <a href='http://docs.angularjs.org/api/ngSanitize.$sanitize'>$sanitize</a> service)</span>
<span ng-show="panel.mode == 'markdown'">(This area uses <a target="_blank" href="http://en.wikipedia.org/wiki/Markdown">Markdown</a>. HTML is not supported)</span>
</label>
<textarea ng-model="panel.content" rows="6" style="width:95%" ng-change="render()" ng-model-onblur>
<textarea ng-model="panel.content" rows="20" style="width:95%" ng-change="render()" ng-model-onblur>
</textarea>
</div>
</div>

View File

@@ -1,10 +1,4 @@
<div ng-controller='text' ng-init="init()" style="min-height:{{panel.height || row.height}}" ng-dblclick="openEditor()">
<!--<p ng-style="panel.style" ng-bind-html-unsafe="panel.content | striphtml | newlines"></p>-->
<markdown ng-show="ready && panel.mode == 'markdown'">
{{panel.content}}
</markdown>
<p ng-show="panel.mode == 'text'" ng-style='panel.style' ng-bind-html-unsafe="panel.content | striphtml | newlines">
</p>
<p ng-show="panel.mode == 'html'" ng-bind-html-unsafe="panel.content">
<div ng-controller='text'>
<p ng-bind-html="content" ng-show="content">
</p>
</div>

View File

@@ -1,101 +1,98 @@
/** @scratch /panels/5
* include::panels/text.asciidoc[]
*/
/** @scratch /panels/text/0
* == text
* Status: *Stable*
*
* The text panel is used for displaying static text formated as markdown, sanitized html or as plain
* text.
*
*/
define([
'angular',
'app',
'underscore',
'require'
'lodash',
'require',
'components/panelmeta',
],
function (angular, app, _, require) {
function (angular, app, _, require, PanelMeta) {
'use strict';
var module = angular.module('kibana.panels.text', []);
var module = angular.module('grafana.panels.text', []);
app.useModule(module);
module.controller('text', function($scope) {
var converter;
$scope.panelMeta = {
module.controller('text', function($scope, templateSrv, $sce, panelSrv) {
$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: {},
};
_.defaults($scope.panel,_d);
_.defaults($scope.panel, _d);
$scope.init = function() {
$scope.initBaseController(this, $scope);
panelSrv.init($scope);
$scope.ready = false;
$scope.$on('refresh', $scope.render);
$scope.render();
};
$scope.render = function() {
$scope.$emit('render');
};
$scope.openEditor = function() {
//$scope.$emit('open-modal','paneleditor');
console.log('scope id', $scope.$id);
};
});
module.directive('markdown', function() {
return {
restrict: 'E',
link: function(scope, element) {
scope.$on('render', function() {
render_panel();
});
function render_panel() {
require(['./lib/showdown'], function (Showdown) {
scope.ready = true;
var converter = new Showdown.converter();
var text = scope.panel.content.replace(/&/g, '&amp;')
.replace(/>/g, '&gt;')
.replace(/</g, '&lt;');
var htmlText = converter.makeHtml(text);
element.html(htmlText);
// For whatever reason, this fixes chrome. I don't like it, I think
// it makes things slow?
if(!scope.$$phase) {
scope.$apply();
}
});
}
render_panel();
if ($scope.panel.mode === 'markdown') {
$scope.renderMarkdown($scope.panel.content);
}
else if ($scope.panel.mode === 'html') {
$scope.updateContent($scope.panel.content);
}
else if ($scope.panel.mode === 'text') {
$scope.renderText($scope.panel.content);
}
};
});
module.filter('newlines', function() {
return function (input) {
return input.replace(/\n/g, '<br/>');
$scope.renderText = function(content) {
content = content
.replace(/&/g, '&amp;')
.replace(/>/g, '&gt;')
.replace(/</g, '&lt;')
.replace(/\n/g, '<br/>');
$scope.updateContent(content);
};
});
module.filter('striphtml', function () {
return function(text) {
return text
$scope.renderMarkdown = function(content) {
var text = content
.replace(/&/g, '&amp;')
.replace(/>/g, '&gt;')
.replace(/</g, '&lt;');
if (converter) {
$scope.updateContent(converter.makeHtml(text));
}
else {
require(['./lib/showdown'], function (Showdown) {
converter = new Showdown.converter();
$scope.updateContent(converter.makeHtml(text));
});
}
};
$scope.updateContent = function(html) {
try {
$scope.content = $sce.trustAsHtml(templateSrv.replace(html));
} catch(e) {
console.log('Text panel error: ', e);
$scope.content = $sce.trustAsHtml(html);
}
if(!$scope.$$phase) {
$scope.$digest();
}
};
$scope.openEditor = function() {
};
$scope.init();
});
});
});

Some files were not shown because too many files have changed in this diff Show More