Compare commits

..

221 Commits

Author SHA1 Message Date
Torkel Ödegaard 4f674c8d19 Updated changelog with RC1 release date 2014-11-17 15:21:43 +01:00
Torkel Ödegaard e197163019 Changed version to 1.9-rc1 2014-11-17 11:19:17 +01:00
Torkel Ödegaard 7ba0099fa9 Panel: fixes to panel menu when in edito/fullscreen mode, Fixes #1069 2014-11-17 09:59:37 +01:00
Torkel Ödegaard f45797ec4b trying out panel actions buttons 2014-11-17 09:11:54 +01:00
Torkel Ödegaard 02e1ac12b2 InfluxDB: fixed annotations, broken after switch to millisecond resolution, Fixes #1061 2014-11-17 07:09:21 +01:00
Torkel Ödegaard bde138177d Singlestat: fixed decimal issue when value was 1, Fixes #1066 2014-11-15 10:38:01 +01:00
Torkel Ödegaard 640c558446 Graph: added export graph time series data as csv file feature, accessed from panel menu dropdown, #861 2014-11-14 12:12:09 +01:00
Torkel Ödegaard c11ce99bb3 SinglestatPanel: another small fix for drilldown link click while fullscreen mode, #1041 2014-11-14 11:41:36 +01:00
Torkel Ödegaard 8ad83b8d58 SinglestatPanel: small fix for drilldown link click while fullscreen mode, #1041 2014-11-14 11:36:20 +01:00
Torkel Ödegaard 873d3d7c4a SinglestatPanel: Added integration with drilldown link feature, if a drilldown link is present the entire singlestat panel will act as a link, with hover tooltip containing the link name, #951, #1041 2014-11-14 09:33:03 +01:00
Torkel Ödegaard d4adaaaf2b Graphite: annotations stop working after recent change to millisecond resolution, Fixes #1061 2014-11-14 08:19:49 +01:00
Torkel Ödegaard 104493e725 SharePanel: fix for interval variables and auto value, Fixes #1056 2014-11-13 15:23:34 +01:00
Torkel Ödegaard b172e7afdc SinglestatPanel: mini fix 2014-11-13 15:23:34 +01:00
Torkel Ödegaard b1efbeb220 Merge pull request #1057 from kaos/master
panelmeta: fix metrics tab title.
2014-11-13 15:06:31 +01:00
Andreas Stenius f2a6657b72 panelmeta: fix metrics tab title. 2014-11-13 14:29:42 +01:00
Torkel Ödegaard 4eb4974909 Graph: change to current legend value handling, if last value is null, current will pick next to last value, Closes #190 2014-11-12 13:47:06 +01:00
Torkel Ödegaard 3e2c898881 Graph: legend fix for current legend value 2014-11-12 13:40:22 +01:00
Torkel Ödegaard 2fb176a244 Graphite: fix for graphite query editor, when adding functions and the function selection dropdown get push down the selection did not work properly, Fixes #1038 2014-11-12 12:57:22 +01:00
Torkel Ödegaard 53a6a7b305 Annotations: fixed spelling issue in annoation error handling code 2014-11-12 12:41:53 +01:00
Torkel Ödegaard 2045380223 Graph: moved graph and graph tooltip directive into panels/graph folder 2014-11-12 11:58:04 +01:00
Torkel Ödegaard 1c0fc3c924 Graph: small fix to legend sorting, #1030 2014-11-12 10:19:33 +01:00
Torkel Ödegaard 0b966b7a28 Graph: legend sorting while in table mode implemented, panel persisted property, Closes #1030 2014-11-12 10:11:01 +01:00
Torkel Ödegaard 882a477c0f Graph: increase decimal precision in tooltip and legend values when unit scaling is applied, #1043 2014-11-12 09:09:05 +01:00
Torkel Ödegaard ebcf2c3f68 InfluxDB: Support for sub second resolution graphs, Closes #714, #728, #752 2014-11-12 08:39:04 +01:00
Torkel Ödegaard 54fafb3a76 Graph: fix to typeahead for series overrides, (bug introduced in recent commit), Fixes #1046 2014-11-11 21:09:50 +01:00
Torkel Ödegaard 01bd662046 Graph: legend fix for bug when running optimized build, Fixes #1045 2014-11-11 21:06:01 +01:00
Torkel Ödegaard 9e26d3e85d SinglestatPanel: increased scaled decimal offset by 2 for, improves decimal precision when units gets scaled, #1043, #951 2014-11-11 16:30:20 +01:00
Torkel Ödegaard 17114778b7 Panel: fixed bug introduced in recent commit that caused blank title in optimized build, #1041 2014-11-11 15:00:31 +01:00
Torkel Ödegaard 98037ca0c6 SinglestatPanel: fixed issue when value is zero, #1039 2014-11-11 14:55:23 +01:00
Torkel Ödegaard 4ec59e8211 Tech: cleanup unused angular-strap directives, Closes #1029 2014-11-11 13:57:39 +01:00
Torkel Ödegaard 1e6a5ff8ec Updated changelog with #1041 2014-11-11 13:44:25 +01:00
Torkel Ödegaard c12d830162 Panel: added search typeahead for dashboard links, #1041 2014-11-11 13:38:34 +01:00
Torkel Ödegaard a49a9b3b64 Panel: more work on panel links, #1041 2014-11-11 12:51:57 +01:00
Torkel Ödegaard 5da3da5962 PanelLinks: began work on drilldown panel links feature, #1041 2014-11-11 11:48:27 +01:00
Torkel Ödegaard 8bb51d47f8 DashboardViewState: small fix for bug that caused issue for singlestat panel 2014-11-11 08:07:05 +01:00
Torkel Ödegaard 381b9ee7ee Graph: legend css tweaks 2014-11-11 07:51:52 +01:00
Torkel Ödegaard a301c96c9d SharePanel: fix to share feature, Closes #1035 2014-11-10 16:18:59 +01:00
Torkel Ödegaard f9c3cdab67 UI: replaced native confirm dialogs with nicer custom ones 2014-11-10 15:01:30 +01:00
Torkel Ödegaard 27ec0d532e began work on custom confirm modal 2014-11-10 10:40:45 +01:00
Torkel Ödegaard 3aa619b617 Panel: fixed duplicate panel, broken after yesterdays panel menu model refactoring 2014-11-09 09:44:16 +01:00
Torkel Ödegaard ef92272bee Panel: fixed menu position after moving json to new dropdown 2014-11-08 19:28:16 +01:00
Torkel Ödegaard 815ef05daf Graph: refactoring some stuff with legend values 2014-11-08 19:16:22 +01:00
Torkel Ödegaard 2ab19148c1 Graph: toggle legend on/off from panel menu, #941, no keyboard shortcut yet, but could come 2014-11-08 18:12:05 +01:00
Torkel Ödegaard d12f4a4aee Panels: refactoring panel meta model & menu, will open up panel specific menu actions 2014-11-08 16:27:49 +01:00
Torkel Ödegaard 81b1939f92 Graph: small fix to legend table mode 2014-11-08 11:44:23 +01:00
Torkel Ödegaard 834daeecd0 OpenTSDB: updated change log with templating support PR #917 2014-11-08 11:35:42 +01:00
Torkel Ödegaard 6aa0208316 Updated changelog with #1030 2014-11-07 14:23:18 +01:00
Torkel Ödegaard aa87d8eb22 Graph: changed style of legend table mode to full width table and with headers, #1030 2014-11-07 14:20:58 +01:00
Torkel Ödegaard cd21fa7016 Graph: new legend table display style, #1030 2014-11-07 13:57:52 +01:00
Torkel Ödegaard 7ba4f6b93f Singlestat: removed debug console.log 2014-11-07 13:56:28 +01:00
Torkel Ödegaard e16a51ad06 Singlestat: fixed decimal precision bug, #951 2014-11-07 13:54:19 +01:00
Torkel Ödegaard 6861dc137f Graph: fix for legend show/hide toggle, broken by recent legend rewrite 2014-11-07 13:39:47 +01:00
Torkel Ödegaard e530e4d4bc Tech: rewrite of how the legend is implement, performance increase, and will make future legend enhancements easier 2014-11-07 12:15:34 +01:00
Torkel Ödegaard d150bc1e52 Graphite: added mapSeries and reduceSeries functions, Closes #1018 2014-11-07 06:34:47 +01:00
Torkel Ödegaard cc8961360a Graphite: added maxDataPoints override option and a help section that describes how graphite point consolidation work, Closes #5 2014-11-06 16:25:46 +01:00
Torkel Ödegaard c0539e483e Added singlestat panel to changelog 2014-11-06 14:10:25 +01:00
Torkel Ödegaard f0b7099be3 SingleStatPanel: renamed panel to singlestat 2014-11-06 14:07:32 +01:00
Torkel Ödegaard ee183d4574 SingleStatPanel: some more tweaks and polish 2014-11-06 13:57:16 +01:00
Torkel Ödegaard fa813024ca SingleStatPanel: various fixes 2014-11-06 12:30:42 +01:00
Torkel Ödegaard 37176fa42d SingleStatPanel: Finnaly solved automatic decimal precision calculation for singlestat panel, #951 2014-11-06 12:17:46 +01:00
Torkel Ödegaard 7ff8931def SingleStatPanel: added font size options for value, prefix and postfix, #951 2014-11-06 10:47:46 +01:00
Torkel Ödegaard 2a962bf8fd Singlestat: progress on singlestat panel 2014-11-06 09:56:50 +01:00
Torkel Ödegaard bbbcba8b98 Merge branch 'master' into valuepanel 2014-11-06 08:54:01 +01:00
Torkel Ödegaard ecdcd10612 Graph: Series hide/show toggle changed to be default exclusive, so clicking on a series name will show only that series. (SHIFT or meta)+click will toggle hide/show. Closes #1007 2014-11-03 10:21:16 +01:00
Torkel Ödegaard 10ea140358 MetricEditors: Ability to reorder metric queries in metrics tag, Closes #716, Closes #856 2014-11-03 08:56:13 +01:00
Torkel Ödegaard 74e0309241 Fixes #912, URL parameters are lost when switching to fullscreen/edit mode 2014-11-02 11:56:51 +01:00
Torkel Ödegaard c88bfbbf82 SingleStatPanel: editor cleanup 2014-11-02 11:36:11 +01:00
Torkel Ödegaard 4edb89eeb9 Merge branch 'summarize-alignToFrom' of github.com:adriensamson/grafana into adriensamson-summarize-alignToFrom 2014-10-30 09:24:27 +01:00
Torkel Ödegaard cdb4b3cc7d Panel: fix fullscreen/edit view and page refresh when panel is in collapsed/hidden row, Fixes #992 2014-10-29 09:51:44 +01:00
Torkel Ödegaard ed8dd03fa1 Merge branch 'master' into valuepanel 2014-10-29 09:24:49 +01:00
Torkel Ödegaard e5bb7f7c2d CustomDatasource: fixed build optimization issue with custom datasources, Fixes #994 2014-10-28 19:00:46 +01:00
Torkel Ödegaard a7b0f6dc9f ScriptedDashboard: datasource services are now available in scripted dashboards, you can query datasource for metric keys, generate dashboards, and even save them in a scripted dashboard (see scripted_gen_and_save.js for example), Closes #991 2014-10-28 16:16:52 +01:00
Torkel Ödegaard c42986c07d Merge branch 'services_in_scripted_dashboard' 2014-10-28 16:14:41 +01:00
Torkel Ödegaard a982dd1765 ScriptedDashboard: using grafana services in scripted dashboard 2014-10-28 16:14:28 +01:00
Torkel Ödegaard c3900398fc Merge pull request #917 from mchataigner/master
adding templating in opentsdb graphs
2014-10-28 15:39:14 +01:00
Torkel Ödegaard 4b79a5e9da OpenTSDB: merged PR #930 adding counter max and counter reset options to OpenTSDB query editor 2014-10-28 12:41:17 +01:00
Torkel Ödegaard eed2feea97 Merge branch 'opentsdb-counterMax' of github.com:rsimiciuc/grafana into rsimiciuc-opentsdb-counterMax 2014-10-28 12:36:15 +01:00
Torkel Ödegaard 60a2d9f624 Dashboard: fix for collapsed rows became invisible when hide controls was enabled, Fixes #987 2014-10-27 10:42:13 +01:00
Torkel Ödegaard 3cd33b6ffc Tech: added cache busting for requirejs fetches, will only affect config.js for optimized builds, should solve user issues where browsers cache config.js which causes initial confusing setup/config issues. 2014-10-26 08:45:42 +01:00
Torkel Ödegaard e3942b3438 Graph: multi series tooltip fix for long series names, and metric value row alignment, Fixes #983, #984 2014-10-25 12:01:31 +02:00
Torkel Ödegaard 0bf37b8c00 Merge pull request #981 from swoop-inc/mb_value_update_bug
When get_data fails the legend should be cleared
2014-10-24 21:10:07 +02:00
Torkel Ödegaard 0e5dbf3889 Merge pull request #980 from maxstepanov/patch-1
Replace all $interval occurrences in query string
2014-10-24 21:07:38 +02:00
Torkel Ödegaard 785f96aabe Merge pull request #975 from swoop-inc/mb_support_influx_regex_merge
Add support for regex based series merging in InfluxDB
2014-10-24 21:01:52 +02:00
Mark Bell 5cec936128 When get_data fails the legend should be cleared 2014-10-24 14:51:45 -04:00
Max Stepanov 02861142cb Replace all $interval occurrences in query string
because template
2014-10-24 20:06:39 +03:00
Torkel Ödegaard 1cfc4d2f31 ScriptedDashboard: bugfix for scripted dashboards and collapsed rows, Fixes #965 2014-10-24 11:48:08 +02:00
Mark Bell 08e816a539 Add support for regex based series merging in InfluxDB 2014-10-23 10:35:57 -04:00
Torkel Ödegaard 272cf64aac SinglsStatPanel: more work on sparkline option for single stat panel 2014-10-23 13:44:15 +02:00
Torkel Ödegaard ed57a4099b Merge branch 'master' into valuepanel 2014-10-23 13:35:54 +02:00
Torkel Ödegaard 79c5d48a3c Timepicker: Fix for Zoom out top menu link still visible when timepicker was disabled, Fixes #963 2014-10-23 12:36:25 +02:00
Adrien Samson 6c70122e55 Add alignToFrom param to summarize 2014-10-22 11:31:41 +02:00
Torkel Ödegaard 6c83699e6f Panel: css fix for panel fullscreen/edit mode for latest chrome 38 and Firefox 33, #954 2014-10-20 12:14:58 -04:00
Torkel Ödegaard ff254ce08d Help: fix for help modal shortcut, and added missing shortcut, #952 2014-10-20 11:33:07 -04:00
Torkel Ödegaard 8a80ea26b8 Graph: fix for series override controller unit test 2014-10-20 10:10:18 -04:00
Torkel Ödegaard b85fe62389 Graph: series override dropdown menu select did not work after recent commit that added typeahead to this dropdown 2014-10-20 10:06:34 -04:00
Torkel Ödegaard 31a4d9204c SingleStatPanel: Added graph to single stat panel, #951 2014-10-19 19:36:59 -04:00
Torkel Ödegaard 69fdfd5cb3 Merge branch 'master' into valuepanel 2014-10-19 12:34:01 -04:00
Torkel Ödegaard 16e7980982 Help: added help modal, accessed by shortcut '?', the help modal only contains a list of all shortcuts right now but will be extended in the future, #952 2014-10-19 12:30:41 -04:00
Torkel Ödegaard e3e08cf8e7 Graph: fix for second y axis tick unit labels wrapping on the next line, Fixes #505 2014-10-18 13:31:15 -04:00
Torkel Ödegaard cae6626b06 StatsPanel: small change to stats panel, #951 2014-10-17 10:38:15 -04:00
Torkel Ödegaard 956d93e871 StatsPanel: fine tuning colors 2014-10-17 10:36:04 -04:00
Torkel Ödegaard 7c4d1b7b01 StatsPanel: made big values template based 2014-10-16 13:44:52 -04:00
Torkel Ödegaard 9866e0851b Graph: single series tooltip and unit format change fix, Closes #946 2014-10-16 11:21:56 -04:00
Torkel Ödegaard 0a97a2435b StatsPanel: more progress 2014-10-16 11:16:20 -04:00
Torkel Ödegaard 5c80f03eae StatsPanel: small progress on stats panel 2014-10-15 18:16:04 -04:00
Torkel Ödegaard dd03a4b011 Dashboard: fix for fullscreen mode and small gap sometimes showing the underlying dashboard 2014-10-15 17:37:08 -04:00
Torkel Ödegaard 6cd1bc32fe StatsPanel: more work on stats panel 2014-10-15 17:29:47 -04:00
Torkel Ödegaard dd0193a9a8 Dashboard: removed wip stats panel from settings.js, accidental commit 2014-10-15 15:48:57 -04:00
Torkel Ödegaard 123faa6f8e Merge branch 'master' into valuepanel 2014-10-15 15:48:09 -04:00
Torkel Ödegaard f743288ce0 Graph: fix for legends on the side and graph dimensions adaptation 2014-10-15 15:47:59 -04:00
Torkel Ödegaard a1d764bd26 Merge branch 'master' into valuepanel
Conflicts:
	src/app/components/settings.js
2014-10-15 15:41:58 -04:00
Torkel Ödegaard 61f6bd2c80 Graph: added typehead dropdown menu combination for the series override selection 2014-10-15 14:13:53 -04:00
Torkel Ödegaard fe620d8e44 Graph: fill below to series override option not automatically adds lines=false, as overrides, if you want lines just for the fill below series just remove the overrides, #940 2014-10-15 11:07:51 -04:00
Torkel Ödegaard 22db28d3e7 Graph: New series style override option 'Fill below to', useful to visualize max & min as shadow for the mean, #940 2014-10-15 10:55:46 -04:00
Torkel Ödegaard 1330488e13 Panel: plugins panels can now reside outsude the app/panels directory, added example plugin panel 2014-10-14 16:57:33 -04:00
Torkel Ödegaard 22297be3cf Merge branch 'master' into valuepanel 2014-10-13 12:30:34 -04:00
Raul Simiciuc 29d7d6994a added reset value 2014-10-13 13:43:25 +01:00
Raul Simiciuc 87e8162a2d fixed identation 2014-10-13 13:07:46 +01:00
Raul Simiciuc 38b71bf386 adding counterMax option to opentsdb 2014-10-13 12:36:26 +01:00
Torkel Ödegaard 920689b80e InfluxDB: series lookup & typeahead is now handled by influxdb list series regex queries, Closes #888 2014-10-12 12:47:50 -04:00
Torkel Ödegaard e9c7523646 Graphite: added second optional parameter to averageSeriesWithWildcards, Closes #926 2014-10-11 06:24:44 -04:00
Torkel Ödegaard 88bbc720ca Graph: removed console logs 2014-10-11 06:08:33 -04:00
Torkel Ödegaard 1bf1469c80 Merge branch 'master' of github.com:grafana/grafana 2014-10-10 13:37:43 -04:00
Torkel Ödegaard c74eda20dc Graph: fix for legend values min & max, avg & current when series only has null values, Closes #923 2014-10-10 13:37:33 -04:00
Torkel Ödegaard db0a5bd537 Graph: fix for legend values min & max, avg & current when series only has null values, Closes #923 2014-10-10 10:45:34 -04:00
Torkel Ödegaard c6cb01aa3b Graph: series tooltip length check 2014-10-10 10:05:12 -04:00
Torkel Ödegaard 7463f91878 Tech: removed jquery-ui and jquiery-ui-angular-drag-drop, replaced with native drag drop for angular, saved ~50kb in js libs, much cleaner & simpler usage, Also changed some behavior for drag drop, now panels are replaced when droped on (switch places), Closes #920 2014-10-09 20:30:23 -04:00
Mathieu Chataigner 482b31298f fixing unused var 2014-10-09 19:12:56 +02:00
Torkel Ödegaard ce46ca2f39 Search: fixed bug introduced in recent PR #900, related to comment on #909 2014-10-09 12:31:51 -04:00
Mathieu Chataigner de00d18a7e adding templating in opentsdb graphs 2014-10-09 16:59:29 +02:00
Torkel Ödegaard b0cf0c558d Merge branch 'master' into valuepanel 2014-10-09 09:55:49 -04:00
Torkel Ödegaard 920e5c93e1 Graph: tooltip fix for single series tooltip and right floated value being pushed down 2014-10-08 17:00:07 -04:00
Torkel Ödegaard 0aae78c6bd Graph: fix for bars not displaying, caused by change in recent commit, added unit test so it does not happen again 2014-10-08 16:24:58 -04:00
Torkel Ödegaard 7fb048f423 Began work on experimental new stats panel 2014-10-08 11:43:51 -04:00
Torkel Ödegaard e86207bb28 Fixed default panel title when creating new panel, tweaks to panel height calculation, Closes #910 2014-10-08 06:44:46 -04:00
Torkel Ödegaard 75d60ccb69 Graphite: added perSecond function to the func def list 2014-10-07 06:40:45 -04:00
Torkel Ödegaard 9245cd6aae Merge branch 'inline_styles' of github.com:mikhailov/grafana into mikhailov-inline_styles 2014-10-07 06:32:13 -04:00
Torkel Ödegaard d8183b60c3 Merge branch 'mikhailov-patch-1' 2014-10-07 06:31:05 -04:00
Torkel Ödegaard a24272690d Merge branch 'patch-1' of github.com:mikhailov/grafana into mikhailov-patch-1 2014-10-07 06:30:53 -04:00
Torkel Ödegaard 40b088d6a2 Merge pull request #900 from mikhailov/patch-2
Speed search result parsing
2014-10-07 06:19:52 -04:00
Torkel Ödegaard f1125d64de Updated default max search results to 100, #909 2014-10-07 06:17:33 -04:00
Torkel Ödegaard 0cba818364 Graph: legend and digest phase fix 2014-10-06 12:37:51 -04:00
Torkel Ödegaard 4285c751b3 Graph: fixed value formating for tooltip, need original data. flotcharts copys the data, so changes to value formats func after plot call does not affect the plot.getData() series 2014-10-06 12:17:48 -04:00
Torkel Ödegaard 1b0cddfa72 Graph: Tooltip refactoring for testability 2014-10-06 11:34:51 -04:00
Torkel Ödegaard 231a599f09 legend html markup cleanup 2014-10-06 11:34:51 -04:00
Torkel Ödegaard 7ffc4d388a Merge pull request #895 from ngmlabs/master
second parameter of groupByNode() should be 0 indexed
2014-10-06 09:50:19 -04:00
Torkel Ödegaard 1f24171238 Merge pull request #902 from toni-moreno/fix_multiple_highlight_points
Fix #901: correct positioning for multiple highlighted stacked and staircase points
2014-10-06 09:27:33 -04:00
toni-moreno 24917a6df5 correct positioning for multiple highlighted stacked and staircase points, fix to #901 2014-10-06 06:22:55 +02:00
Anatoly Mikhailov 67fde17209 Speed searching up a bit
Reuse ElasticSearch hits length via hits.total and cache it rahter than calculation length every iteration.
2014-10-05 23:24:41 +01:00
Anatoly Mikhailov 6abad666db Debounce/Throttling Searching event
New backend call to each keydown may hit the back-end performance.
500ms is reasonable delay to avoid too many requests while user is
typing a search query
2014-10-05 20:54:53 +01:00
mikhailov 2d3f396571 Extract repetitive CSS inline styles
Inline styles slow down rendering proportionally to containers number
2014-10-05 18:30:22 +01:00
George Negoita 51bcbdac75 second parameter of groupByNode() is 0 indexed 2014-10-04 02:33:36 +03:00
Torkel Ödegaard e63889d5c4 Css fix for metrics tab, help boxes did extend page height so could not scroll if they extended below screen, #891 2014-10-03 07:31:27 +02:00
Torkel Ödegaard fe6a7c58bf Fixed issue casued by recent angular upgrade and stateful filter, casued template vars in graph titles no update properly, angular was not updated in 1.8.1 so this does affect any released version, Closes #892 2014-10-02 21:48:00 +02:00
Torkel Ödegaard dac3cb15c4 Merge branch 'v1.8.x' 2014-10-02 12:17:37 +02:00
Torkel Ödegaard ca654ccaf7 fixed stupid unit test mistake 2014-10-02 12:17:10 +02:00
Torkel Ödegaard 30512b7032 Merge fix from 'v1.8.x' branch
Conflicts:
	CHANGELOG.md
2014-10-02 12:15:21 +02:00
Torkel Ödegaard bc8fd62cff Dashboard: fix for dashboard schema upgrade during load, ensure that annotation and templating object as a list array, Closes #890 2014-10-02 12:13:18 +02:00
Torkel Ödegaard a9a51ee3c6 Fix to new checkboxes in features toggles tab 2014-10-02 11:54:06 +02:00
Torkel Ödegaard 2d2da7c881 Small tweak to shared tooltip edit option in Display styles tab 2014-10-02 10:55:08 +02:00
Torkel Ödegaard d22d8c4905 Added grafana logo to repo instead of fetching it from grafana.org, Fixes #881 2014-10-02 10:24:18 +02:00
Torkel Ödegaard bcdc8eafa6 Updated gitter badge 2014-10-02 10:13:23 +02:00
Torkel Ödegaard 9cf6ace979 Updated gitter badge 2014-10-02 10:09:16 +02:00
Torkel Ödegaard eef063cec2 Merge pull request #889 from gitter-badger/gitter-badge
Add a Gitter chat badge to README.md
2014-10-02 10:05:01 +02:00
The Gitter Badger 8b9bdf9054 Added Gitter badge 2014-10-02 07:59:43 +00:00
Torkel Ödegaard 1c7b898b01 Dashboard: show error when importing dashboard json file with broken/incorrect json syntax, Closes #878 2014-10-01 13:56:57 +02:00
Torkel Ödegaard 7ad18da08e update to test helper 2014-10-01 13:49:36 +02:00
Torkel Ödegaard 5530915b49 Updated AngularJS from 1.3-beta-17 to 1.3-RC3 2014-10-01 13:44:45 +02:00
Torkel Ödegaard 77f380c94b Graph: shared tooltip improvements, info tooltip when point counts are not the same, #850 2014-10-01 10:30:24 +02:00
Torkel Ödegaard c79ab84fdf More fixes related to shared crosshair #880 2014-10-01 09:42:42 +02:00
Torkel Ödegaard d77448d84e Fixed unit tests for grafanaGraph component 2014-10-01 09:10:49 +02:00
Torkel Ödegaard 8fc5a2785f Refactoring and fixes for PR #880 2014-10-01 09:08:11 +02:00
Torkel Ödegaard 27da2b026f Merge branch 'add_shared_croshair_to_dashboards' of github.com:toni-moreno/grafana into toni-moreno-add_shared_croshair_to_dashboards 2014-10-01 08:00:20 +02:00
Torkel Ödegaard 2e9cc2a74e Fix for SharePanelCtrl-specs and travis-ci timezone issue 2014-10-01 07:58:31 +02:00
toni-moreno ffd370176d fix tooltip test expecting for stacked data 2014-10-01 07:15:20 +02:00
toni-moreno 44f2a375f6 Added shared crosshair to dashboards to track better all graphs, and little Fix for #884 2014-10-01 07:03:59 +02:00
Torkel Ödegaard 282c834d9f updated package.json 2014-09-30 17:58:02 +02:00
Torkel Ödegaard 4a6ff9e2aa Merge branch 'v1.8.x' 2014-09-30 17:57:30 +02:00
Torkel Ödegaard b250d10320 Trying to fox unit tests for sharePanelCtrl 2014-09-30 17:55:07 +02:00
Torkel Ödegaard f63706d118 Small update to share panel 2014-09-30 15:31:18 +02:00
Torkel Ödegaard c41aa64719 ShareModal: Added template variables to share url, and an option for it, #864 2014-09-30 15:19:48 +02:00
Torkel Ödegaard 285d246c65 PanelMenu: fixed interpolate template vars for new panel title/menu 2014-09-30 14:48:36 +02:00
Torkel Ödegaard 2c85205259 SharePanelModal: working on share feature, #864 2014-09-30 14:42:59 +02:00
Torkel Ödegaard 2d866b9298 Updated changelog with the new panel menu, Closes #770 2014-09-30 10:49:59 +02:00
Torkel Ödegaard 7a7629acf7 Panel-menu: small tweeks to css for cursor types 2014-09-30 10:43:13 +02:00
Torkel Ödegaard debf820037 New checkbox change is complete 2014-09-30 10:27:56 +02:00
Torkel Ödegaard f908ae8c40 added checkbox image for white theme 2014-09-30 09:50:01 +02:00
Torkel Ödegaard 022cbdda31 Merge branch 'master' into panel_edit_menu_poc 2014-09-30 09:25:19 +02:00
Torkel Ödegaard ae2523aa59 Merge branch 'v1.8.x' 2014-09-30 09:23:07 +02:00
Torkel Ödegaard 06f382c454 Merge branch 'master' of github.com:grafana/grafana 2014-09-29 16:59:54 +02:00
Torkel Ödegaard 5f164d99ac Updated changelog with merged PR #850, shared multi series graph tooltip & crosshair 2014-09-29 16:59:17 +02:00
Torkel Ödegaard 2473ae3b47 Graph: shared multi series tooltip, refactoring PR #850 2014-09-29 16:57:05 +02:00
Torkel Ödegaard 3fb457ccd1 Merge branch 'master' into toni-moreno-add_shared_tooltips_to_graphs 2014-09-29 14:25:25 +02:00
toni-moreno 51333c9eda improved tooltip styles, add multiple highlight points, and changed highligth size when graph plotted without points. 2014-09-29 12:48:08 +02:00
Torkel Ödegaard 1aaf3961ff Merge pull request #867 from tobym/patch-1
Fix typo
2014-09-29 12:43:48 +02:00
Torkel Ödegaard af4f3f62e9 Merged yaxis_precision branch, #877, updated changelog 2014-09-29 12:38:52 +02:00
Torkel Ödegaard cc31a12b8c Smart decimal precision when using scaled unit format, Closes #877 2014-09-29 12:29:53 +02:00
Torkel Ödegaard bc9989f9be replaced checkbox options with the new editor-opt-bool directive 2014-09-27 10:47:48 +02:00
toni-moreno 7f33bec71c fixing the previous fix, now working fine 2014-09-25 15:45:18 +02:00
toni-moreno 3ea94c3484 little fix when searching x index time 2014-09-25 06:35:49 +02:00
Toby Matejovsky 68adaea128 Fix typo 2014-09-24 19:01:23 -04:00
Torkel Ödegaard 4997068a0d Added directive element for the new checkboxes 2014-09-24 19:10:00 +02:00
Torkel Ödegaard 4c59ec815e work on share panel view, and better look for checkbox 2014-09-24 18:55:55 +02:00
Torkel Ödegaard 440ea666d9 more work on share panel, #864 2014-09-24 17:15:58 +02:00
Torkel Ödegaard 6f1a6d5a56 Changed name of emitAppEvent to just appEvent 2014-09-24 16:26:39 +02:00
Torkel Ödegaard 69e80fd11c working on share feature, and refactorings 2014-09-24 16:20:55 +02:00
Torkel Ödegaard bef8cc2d70 Merge branch 'master' into panel_edit_menu_poc 2014-09-24 14:00:55 +02:00
Torkel Ödegaard 743c95d0f9 small fix to panel menu positioning 2014-09-24 12:45:03 +02:00
Torkel Ödegaard a08cb52ad9 tweaks to new panel menu, now how top area of panel is clickable, and if no title a 5px high area can be clicked, this will enable panels without title which is something I have wanted 2014-09-24 12:41:55 +02:00
Torkel Ödegaard b9604bf3bc Merge branch 'master' into panel_edit_menu_poc 2014-09-24 12:23:04 +02:00
Torkel Ödegaard 622c1a1dad small tweak to new panel edit menu 2014-09-24 11:58:02 +02:00
Torkel Ödegaard 79fea549ef Merge branch 'master' into panel_edit_menu_poc 2014-09-24 11:41:25 +02:00
Torkel Ödegaard d6f1c379c0 fixed text editor & scope issue 2014-09-23 22:30:01 +02:00
Torkel Ödegaard 6794260e3f Lots of progress on new panel edit menu, very tricky to get this right but think I am getting close to something that is good and will work long term 2014-09-23 22:10:10 +02:00
Torkel Ödegaard 1be840f19d More wort on panel edit menu 2014-09-23 16:11:31 +02:00
toni-moreno f59bb6461a added shared tooltips to graphs 2014-09-23 13:51:59 +02:00
Torkel Ödegaard bd3bae3af0 another attempt at improving panel edit menu 2014-09-23 10:52:31 +02:00
Torkel Ödegaard 139791b0d8 Merge branch 'master' into panel_edit_menu_poc 2014-09-22 18:11:52 +02:00
Torkel Ödegaard 6003fee33f Merge branch 'master' into panel_edit_menu_poc 2014-09-20 13:37:02 +02:00
Torkel Ödegaard e78c48620f Trying to improve yaxis precision 2014-09-19 13:24:15 +02:00
Torkel Ödegaard a6fa01f89b POC of panel edit menu 2014-09-06 14:05:07 +02:00
146 changed files with 7772 additions and 9448 deletions
+21
View File
@@ -0,0 +1,21 @@
{
"preset" : "default",
"lineBreak" : {
"before" : {
"VariableDeclarationWithoutInit" : 0,
},
"after": {
"AssignmentOperator": -1,
"ArgumentListArrayExpression": ">=1"
}
},
"whiteSpace" : {
"before" : {
},
"after" : {
}
}
}
+36 -1
View File
@@ -1,5 +1,40 @@
# 1.9.0 (unreleased)
# 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 #938](https://github.com/grafana/grafana/issues/938). Panel: Plugin panels now reside outside of app/panels directory
- [Issue #952](https://github.com/grafana/grafana/issues/952). Help: Shortcut "?" to open help modal with list of all shortcuts
- [Issue #991](https://github.com/grafana/grafana/issues/991). ScriptedDashboard: datasource services are now available in scripted dashboards, you can query datasource for metric keys, generate dashboards, and even save them in a scripted dashboard (see scripted_gen_and_save.js for example)
- [Issue #1041](https://github.com/grafana/grafana/issues/1041). Panel: All panels can now have links to other dashboards or absolute links, these links are available in the panel menu.
**Changes**
- [Issue #1007](https://github.com/grafana/grafana/issues/1007). Graph: Series hide/show toggle changed to be default exclusive, so clicking on a series name will show only that series. (SHIFT or meta)+click will toggle hide/show.
**OpenTSDB**
- [Issue #930](https://github.com/grafana/grafana/issues/930). OpenTSDB: Adding counter max and counter reset value to open tsdb query editor, thx @rsimiciuc
- [Issue #917](https://github.com/grafana/grafana/issues/917). OpenTSDB: Templating support for OpenTSDB series name and tags, thx @mchataigner
**InfluxDB**
- [Issue #714](https://github.com/grafana/grafana/issues/714). InfluxDB: Support for sub second resolution graphs
**Fixes**
- [Issue #925](https://github.com/grafana/grafana/issues/925). Graph: bar width calculation fix for some edge cases (bars would render on top of each other)
- [Issue #505](https://github.com/grafana/grafana/issues/505). Graph: fix for second y axis tick unit labels wrapping on the next line
- [Issue #987](https://github.com/grafana/grafana/issues/987). Dashboard: Collapsed rows became invisible when hide controls was enabled
=======
# 1.8.1 (2014-09-30)
**Fixes**
+1 -1
View File
@@ -1,4 +1,4 @@
[Grafana](http://grafana.org) [![Build Status](https://api.travis-ci.org/grafana/grafana.svg)](https://travis-ci.org/grafana/grafana) [![Coverage Status](https://coveralls.io/repos/grafana/grafana/badge.png)](https://coveralls.io/r/grafana/grafana)
[Grafana](http://grafana.org) [![Build Status](https://api.travis-ci.org/grafana/grafana.svg)](https://travis-ci.org/grafana/grafana) [![Coverage Status](https://coveralls.io/repos/grafana/grafana/badge.png)](https://coveralls.io/r/grafana/grafana) [![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/grafana/grafana?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
================
[Website](http://grafana.org) |
[Twitter](http://twitter.com/grafana) |
+2 -2
View File
@@ -1,4 +1,4 @@
{
"version": "1.8.1",
"url": "http://grafanarel.s3.amazonaws.com/grafana-1.8.1.tar.gz"
"version": "1.9.0-rc1",
"url": "http://grafanarel.s3.amazonaws.com/grafana-1.9.0-rc1.tar.gz"
}
+1 -1
View File
@@ -4,7 +4,7 @@
"company": "Coding Instinct AB"
},
"name": "grafana",
"version": "1.8.1",
"version": "1.9.0-rc1",
"repository": {
"type": "git",
"url": "http://github.com/torkelo/grafana.git"
+2 -1
View File
@@ -57,7 +57,6 @@ function (angular, $, _, appLevelRequire, config) {
register_fns.factory = $provide.factory;
register_fns.service = $provide.service;
register_fns.filter = $filterProvider.register;
});
var apps_deps = [
@@ -79,6 +78,8 @@ function (angular, $, _, appLevelRequire, config) {
});
var preBootRequires = [
'services/all',
'features/all',
'controllers/all',
'directives/all',
'filters/all',
+74 -270
View File
@@ -7,6 +7,7 @@ function($, _, moment) {
'use strict';
var kbn = {};
kbn.valueFormats = {};
kbn.round_interval = function(interval) {
switch (true) {
@@ -309,240 +310,35 @@ function($, _, moment) {
].join(';') + '"></div>';
};
kbn.byteFormat = function(size, decimals) {
var ext, steps = 0;
if(_.isUndefined(decimals)) {
decimals = 2;
} else if (decimals === 0) {
decimals = undefined;
}
while (Math.abs(size) >= 1024) {
steps++;
size /= 1024;
}
switch (steps) {
case 0:
ext = " B";
break;
case 1:
ext = " KiB";
break;
case 2:
ext = " MiB";
break;
case 3:
ext = " GiB";
break;
case 4:
ext = " TiB";
break;
case 5:
ext = " PiB";
break;
case 6:
ext = " EiB";
break;
case 7:
ext = " ZiB";
break;
case 8:
ext = " YiB";
break;
}
return (size.toFixed(decimals) + ext);
kbn.valueFormats.percent = function(size, decimals) {
return kbn.toFixed(size, decimals) + '%';
};
kbn.bitFormat = function(size, decimals) {
var ext, steps = 0;
kbn.formatFuncCreator = function(factor, extArray) {
return function(size, decimals, scaledDecimals) {
if (size === null) {
return "";
}
if(_.isUndefined(decimals)) {
decimals = 2;
} else if (decimals === 0) {
decimals = undefined;
}
var steps = 0;
while (Math.abs(size) >= 1024) {
steps++;
size /= 1024;
}
while (Math.abs(size) >= factor) {
steps++;
size /= factor;
}
if (steps > 0) {
decimals = scaledDecimals + (3 * steps);
}
switch (steps) {
case 0:
ext = " b";
break;
case 1:
ext = " Kib";
break;
case 2:
ext = " Mib";
break;
case 3:
ext = " Gib";
break;
case 4:
ext = " Tib";
break;
case 5:
ext = " Pib";
break;
case 6:
ext = " Eib";
break;
case 7:
ext = " Zib";
break;
case 8:
ext = " Yib";
break;
}
return (size.toFixed(decimals) + ext);
return kbn.toFixed(size, decimals) + extArray[steps];
};
};
kbn.bpsFormat = function(size, decimals) {
var ext, steps = 0;
if(_.isUndefined(decimals)) {
decimals = 2;
} else if (decimals === 0) {
decimals = undefined;
kbn.toFixed = function(value, decimals) {
if (value === null) {
return "";
}
while (Math.abs(size) >= 1000) {
steps++;
size /= 1000;
}
switch (steps) {
case 0:
ext = " bps";
break;
case 1:
ext = " Kbps";
break;
case 2:
ext = " Mbps";
break;
case 3:
ext = " Gbps";
break;
case 4:
ext = " Tbps";
break;
case 5:
ext = " Pbps";
break;
case 6:
ext = " Ebps";
break;
case 7:
ext = " Zbps";
break;
case 8:
ext = " Ybps";
break;
}
return (size.toFixed(decimals) + ext);
};
kbn.shortFormat = function(size, decimals) {
var ext, steps = 0;
if(_.isUndefined(decimals)) {
decimals = 2;
} else if (decimals === 0) {
decimals = undefined;
}
while (Math.abs(size) >= 1000) {
steps++;
size /= 1000;
}
switch (steps) {
case 0:
ext = "";
break;
case 1:
ext = " K";
break;
case 2:
ext = " Mil";
break;
case 3:
ext = " Bil";
break;
case 4:
ext = " Tri";
break;
case 5:
ext = " Quadr";
break;
case 6:
ext = " Quint";
break;
case 7:
ext = " Sext";
break;
case 8:
ext = " Sept";
break;
}
return (size.toFixed(decimals) + ext);
};
kbn.getFormatFunction = function(formatName, decimals) {
switch(formatName) {
case 'short':
return function(val) {
return kbn.shortFormat(val, decimals);
};
case 'bytes':
return function(val) {
return kbn.byteFormat(val, decimals);
};
case 'bits':
return function(val) {
return kbn.bitFormat(val, decimals);
};
case 'bps':
return function(val) {
return kbn.bpsFormat(val, decimals);
};
case 's':
return function(val) {
return kbn.sFormat(val, decimals);
};
case 'ms':
return function(val) {
return kbn.msFormat(val, decimals);
};
case 'µs':
return function(val) {
return kbn.microsFormat(val, decimals);
};
case 'ns':
return function(val) {
return kbn.nanosFormat(val, decimals);
};
case 'percent':
return function(val, axis) {
return kbn.noneFormat(val, axis ? axis.tickDecimals : null) + ' %';
};
default:
return function(val, axis) {
return kbn.noneFormat(val, axis ? axis.tickDecimals : null);
};
}
};
kbn.noneFormat = function(value, decimals) {
var factor = decimals ? Math.pow(10, decimals) : 1;
var formatted = String(Math.round(value * factor) / factor);
@@ -553,7 +349,6 @@ function($, _, moment) {
// If tickDecimals was specified, ensure that we have exactly that
// much precision; otherwise default to the value's own precision.
if (decimals != null) {
var decimalPos = formatted.indexOf(".");
var precision = decimalPos === -1 ? 0 : formatted.length - decimalPos - 1;
@@ -565,97 +360,95 @@ function($, _, moment) {
return formatted;
};
kbn.msFormat = function(size, decimals) {
// Less than 1 milli, downscale to micro
if (size !== 0 && Math.abs(size) < 1) {
return kbn.microsFormat(size * 1000, decimals);
}
else if (Math.abs(size) < 1000) {
return size.toFixed(decimals) + " ms";
kbn.valueFormats.bits = kbn.formatFuncCreator(1024, [' b', ' Kib', ' Mib', ' Gib', ' Tib', ' Pib', ' Eib', ' Zib', ' Yib']);
kbn.valueFormats.bytes = kbn.formatFuncCreator(1024, [' B', ' KiB', ' MiB', ' GiB', ' TiB', ' PiB', ' EiB', ' ZiB', ' YiB']);
kbn.valueFormats.bps = kbn.formatFuncCreator(1000, [' bps', ' Kbps', ' Mbps', ' Gbps', ' Tbps', ' Pbps', ' Ebps', ' Zbps', ' Ybps']);
kbn.valueFormats.short = kbn.formatFuncCreator(1000, ['', ' K', ' Mil', ' Bil', ' Tri', ' Qaudr', ' Quint', ' Sext', ' Sept']);
kbn.valueFormats.none = kbn.toFixed;
kbn.valueFormats.ms = function(size, decimals, scaledDecimals) {
if (size === null) { return ""; }
if (Math.abs(size) < 1000) {
return kbn.toFixed(size, decimals) + " ms";
}
// Less than 1 min
else if (Math.abs(size) < 60000) {
return (size / 1000).toFixed(decimals) + " s";
return kbn.toFixed(size / 1000, scaledDecimals + 3) + " s";
}
// Less than 1 hour, devide in minutes
else if (Math.abs(size) < 3600000) {
return (size / 60000).toFixed(decimals) + " min";
return kbn.toFixed(size / 60000, scaledDecimals + 5) + " min";
}
// Less than one day, devide in hours
else if (Math.abs(size) < 86400000) {
return (size / 3600000).toFixed(decimals) + " hour";
return kbn.toFixed(size / 3600000, scaledDecimals + 7) + " hour";
}
// Less than one year, devide in days
else if (Math.abs(size) < 31536000000) {
return (size / 86400000).toFixed(decimals) + " day";
return kbn.toFixed(size / 86400000, scaledDecimals + 8) + " day";
}
return (size / 31536000000).toFixed(decimals) + " year";
return kbn.toFixed(size / 31536000000, scaledDecimals + 10) + " year";
};
kbn.sFormat = function(size, decimals) {
// Less than 1 sec, downscale to milli
if (size !== 0 && Math.abs(size) < 1) {
return kbn.msFormat(size * 1000, decimals);
}
// Less than 10 min, use seconds
else if (Math.abs(size) < 600) {
return size.toFixed(decimals) + " s";
kbn.valueFormats.s = function(size, decimals, scaledDecimals) {
if (size === null) { return ""; }
if (Math.abs(size) < 600) {
return kbn.toFixed(size, decimals) + " s";
}
// Less than 1 hour, devide in minutes
else if (Math.abs(size) < 3600) {
return (size / 60).toFixed(decimals) + " min";
return kbn.toFixed(size / 60, scaledDecimals + 1) + " min";
}
// Less than one day, devide in hours
else if (Math.abs(size) < 86400) {
return (size / 3600).toFixed(decimals) + " hour";
return kbn.toFixed(size / 3600, scaledDecimals + 4) + " hour";
}
// Less than one week, devide in days
else if (Math.abs(size) < 604800) {
return (size / 86400).toFixed(decimals) + " day";
return kbn.toFixed(size / 86400, scaledDecimals + 5) + " day";
}
// Less than one year, devide in week
else if (Math.abs(size) < 31536000) {
return (size / 604800).toFixed(decimals) + " week";
return kbn.toFixed(size / 604800, scaledDecimals + 6) + " week";
}
return (size / 3.15569e7).toFixed(decimals) + " year";
return kbn.toFixed(size / 3.15569e7, scaledDecimals + 7) + " year";
};
kbn.microsFormat = function(size, decimals) {
// Less than 1 micro, downscale to nano
if (size !== 0 && Math.abs(size) < 1) {
return kbn.nanosFormat(size * 1000, decimals);
}
else if (Math.abs(size) < 1000) {
return size.toFixed(decimals) + " µs";
kbn.valueFormats['µs'] = function(size, decimals, scaledDecimals) {
if (size === null) { return ""; }
if (Math.abs(size) < 1000) {
return kbn.toFixed(size, decimals) + " µs";
}
else if (Math.abs(size) < 1000000) {
return (size / 1000).toFixed(decimals) + " ms";
return kbn.toFixed(size / 1000, scaledDecimals + 3) + " ms";
}
else {
return (size / 1000000).toFixed(decimals) + " s";
return kbn.toFixed(size / 1000000, scaledDecimals + 6) + " s";
}
};
kbn.nanosFormat = function(size, decimals) {
if (Math.abs(size) < 1) {
return size.toFixed(decimals) + " ns";
}
else if (Math.abs(size) < 1000) {
return size.toFixed(0) + " ns";
kbn.valueFormats.ns = function(size, decimals, scaledDecimals) {
if (size === null) { return ""; }
if (Math.abs(size) < 1000) {
return kbn.toFixed(size, decimals) + " ns";
}
else if (Math.abs(size) < 1000000) {
return (size / 1000).toFixed(decimals) + " µs";
return kbn.toFixed(size / 1000, scaledDecimals + 3) + " µs";
}
else if (Math.abs(size) < 1000000000) {
return (size / 1000000).toFixed(decimals) + " ms";
return kbn.toFixed(size / 1000000, scaledDecimals + 6) + " ms";
}
else if (Math.abs(size) < 60000000000){
return (size / 1000000000).toFixed(decimals) + " s";
return kbn.toFixed(size / 1000000000, scaledDecimals + 9) + " s";
}
else {
return (size / 60000000000).toFixed(decimals) + " m";
return kbn.toFixed(size / 60000000000, scaledDecimals + 12) + " m";
}
};
@@ -666,6 +459,17 @@ function($, _, moment) {
.replace(/ +/g,'-');
};
kbn.exportSeriesListToCsv = function(seriesList) {
var text = 'Series;Time;Value\n';
_.each(seriesList, function(series) {
_.each(series.datapoints, function(dp) {
text += series.alias + ';' + new Date(dp[1]).toISOString() + ';' + dp[0] + '\n';
});
});
var blob = new Blob([text], { type: "text/csv;charset=utf-8" });
window.saveAs(blob, 'grafana_data_export.csv');
};
kbn.stringToJsRegex = function(str) {
if (str[0] !== '/') {
return new RegExp(str);
+45
View File
@@ -0,0 +1,45 @@
define([
],
function () {
"use strict";
function PanelMeta(options) {
this.description = options.description;
this.titlePos = options.titlePos;
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;
});
+6 -3
View File
@@ -3,6 +3,7 @@
*/
require.config({
baseUrl: 'app',
urlArgs: 'bust=' + (new Date().getTime()),
paths: {
config: ['../config', '../config.sample'],
@@ -29,7 +30,6 @@ require.config({
bootstrap: '../vendor/bootstrap/bootstrap',
jquery: '../vendor/jquery/jquery-2.1.1.min',
'jquery-ui': '../vendor/jquery/jquery-ui-1.10.3',
'extend-jquery': 'components/extend-jquery',
@@ -40,6 +40,8 @@ require.config({
'jquery.flot.stack': '../vendor/jquery/jquery.flot.stack',
'jquery.flot.stackpercent':'../vendor/jquery/jquery.flot.stackpercent',
'jquery.flot.time': '../vendor/jquery/jquery.flot.time',
'jquery.flot.crosshair': '../vendor/jquery/jquery.flot.crosshair',
'jquery.flot.fillbelow': '../vendor/jquery/jquery.flot.fillbelow',
modernizr: '../vendor/modernizr-2.6.1',
@@ -75,7 +77,6 @@ require.config({
// simple dependency declaration
//
'jquery-ui': ['jquery'],
'jquery.flot': ['jquery'],
'jquery.flot.pie': ['jquery', 'jquery.flot'],
'jquery.flot.events': ['jquery', 'jquery.flot'],
@@ -83,8 +84,10 @@ require.config({
'jquery.flot.stack': ['jquery', 'jquery.flot'],
'jquery.flot.stackpercent':['jquery', 'jquery.flot'],
'jquery.flot.time': ['jquery', 'jquery.flot'],
'jquery.flot.crosshair':['jquery', 'jquery.flot'],
'jquery.flot.fillbelow':['jquery', 'jquery.flot'],
'angular-cookies': ['angular'],
'angular-dragdrop': ['jquery','jquery-ui','angular'],
'angular-dragdrop': ['jquery', 'angular'],
'angular-loader': ['angular'],
'angular-mocks': ['angular'],
'angular-resource': ['angular'],
+7 -3
View File
@@ -15,12 +15,16 @@ function (_, crypto) {
var defaults = {
datasources : {},
window_title_prefix : 'Grafana - ',
panels : ['graph', 'text'],
panels : {
'graph': { path: 'panels/graph' },
'singlestat': { path: 'panels/singlestat' },
'text': { path: 'panels/text' }
},
plugins : {},
default_route : '/dashboard/file/default.json',
playlist_timespan : "1m",
unsaved_changes_warning : true,
search : { max_results: 16 },
search : { max_results: 100 },
admin : {}
};
@@ -76,7 +80,7 @@ function (_, crypto) {
});
if (settings.plugins.panels) {
settings.panels = _.union(settings.panels, settings.plugins.panels);
_.extend(settings.panels, settings.plugins.panels);
}
if (!settings.plugins.dependencies) {
+45 -30
View File
@@ -7,8 +7,12 @@ function (_, kbn) {
function TimeSeries(opts) {
this.datapoints = opts.datapoints;
this.info = opts.info;
this.label = opts.info.alias;
this.label = opts.alias;
this.id = opts.alias;
this.alias = opts.alias;
this.color = opts.color;
this.valueFormater = kbn.valueFormats.none;
this.stats = {};
}
function matchSeriesOverride(aliasOrRegex, seriesAlias) {
@@ -30,13 +34,13 @@ function (_, kbn) {
this.lines = {};
this.points = {};
this.bars = {};
this.info.yaxis = 1;
this.yaxis = 1;
this.zindex = 0;
delete this.stack;
for (var i = 0; i < overrides.length; i++) {
var override = overrides[i];
if (!matchSeriesOverride(override.alias, this.info.alias)) {
if (!matchSeriesOverride(override.alias, this.alias)) {
continue;
}
if (override.lines !== void 0) { this.lines.show = override.lines; }
@@ -48,21 +52,23 @@ function (_, kbn) {
if (override.pointradius !== void 0) { this.points.radius = override.pointradius; }
if (override.steppedLine !== void 0) { this.lines.steps = override.steppedLine; }
if (override.zindex !== void 0) { this.zindex = override.zindex; }
if (override.fillBelowTo !== void 0) { this.fillBelowTo = override.fillBelowTo; }
if (override.yaxis !== void 0) {
this.info.yaxis = override.yaxis;
this.yaxis = override.yaxis;
}
}
};
TimeSeries.prototype.getFlotPairs = function (fillStyle, yFormats) {
TimeSeries.prototype.getFlotPairs = function (fillStyle) {
var result = [];
this.color = this.info.color;
this.yaxis = this.info.yaxis;
this.info.total = 0;
this.info.max = -212312321312;
this.info.min = 212312321312;
this.stats.total = 0;
this.stats.max = Number.MIN_VALUE;
this.stats.min = Number.MAX_VALUE;
this.stats.avg = null;
this.stats.current = null;
this.allIsNull = true;
var ignoreNulls = fillStyle === 'connected';
var nullAsZero = fillStyle === 'null as zero';
@@ -81,40 +87,49 @@ function (_, kbn) {
}
if (_.isNumber(currentValue)) {
this.info.total += currentValue;
this.stats.total += currentValue;
this.allIsNull = false;
}
if (currentValue > this.info.max) {
this.info.max = currentValue;
if (currentValue > this.stats.max) {
this.stats.max = currentValue;
}
if (currentValue < this.info.min) {
this.info.min = currentValue;
if (currentValue < this.stats.min) {
this.stats.min = currentValue;
}
result.push([currentTime * 1000, currentValue]);
result.push([currentTime, currentValue]);
}
if (result.length > 2) {
this.info.timeStep = result[1][0] - result[0][0];
if (this.datapoints.length >= 2) {
this.stats.timeStep = this.datapoints[1][1] - this.datapoints[0][1];
}
if (this.stats.max === Number.MIN_VALUE) { this.stats.max = null; }
if (this.stats.min === Number.MAX_VALUE) { this.stats.min = null; }
if (result.length) {
this.info.avg = (this.info.total / result.length);
this.info.current = result[result.length-1][1];
var formater = kbn.getFormatFunction(yFormats[this.yaxis - 1], 2);
this.info.avg = this.info.avg != null ? formater(this.info.avg) : null;
this.info.current = this.info.current != null ? formater(this.info.current) : null;
this.info.min = this.info.min != null ? formater(this.info.min) : null;
this.info.max = this.info.max != null ? formater(this.info.max) : null;
this.info.total = this.info.total != null ? formater(this.info.total) : null;
this.stats.avg = (this.stats.total / result.length);
this.stats.current = result[result.length-1][1];
if (this.stats.current === null && result.length > 1) {
this.stats.current = result[result.length-2][1];
}
}
return result;
};
TimeSeries.prototype.updateLegendValues = function(formater, decimals, scaledDecimals) {
this.valueFormater = formater;
this.decimals = decimals;
this.scaledDecimals = scaledDecimals;
};
TimeSeries.prototype.formatValue = function(value) {
return this.valueFormater(value, this.decimals, this.scaledDecimals);
};
return TimeSeries;
});
+1
View File
@@ -15,5 +15,6 @@ define([
'./opentsdbTargetCtrl',
'./annotationsEditorCtrl',
'./templateEditorCtrl',
'./sharePanelCtrl',
'./jsonEditorCtrl',
], function () {});
+23 -24
View File
@@ -3,7 +3,6 @@ define([
'jquery',
'config',
'lodash',
'services/all',
],
function (angular, $, config, _) {
"use strict";
@@ -18,11 +17,10 @@ function (angular, $, config, _) {
templateValuesSrv,
dashboardSrv,
dashboardViewStateSrv,
panelMoveSrv,
$timeout) {
$scope.editor = { index: 0 };
$scope.panelNames = config.panels;
$scope.panelNames = _.map(config.panels, function(value, key) { return key; });
var resizeEventTimeout;
this.init = function(dashboardData) {
@@ -51,14 +49,13 @@ function (angular, $, config, _) {
// init services
timeSrv.init($scope.dashboard);
templateValuesSrv.init($scope.dashboard, $scope.dashboardViewState);
panelMoveSrv.init($scope.dashboard, $scope);
$scope.checkFeatureToggles();
dashboardKeybindings.shortcuts($scope);
$scope.setWindowTitleAndTheme();
$scope.emitAppEvent("dashboard-loaded", $scope.dashboard);
$scope.appEvent("dashboard-loaded", $scope.dashboard);
};
$scope.setWindowTitleAndTheme = function() {
@@ -92,40 +89,42 @@ function (angular, $, config, _) {
};
};
$scope.edit_path = function(type) {
var p = $scope.panel_path(type);
if(p) {
return p+'/editor.html';
} else {
return false;
}
$scope.panelEditorPath = function(type) {
return 'app/' + config.panels[type].path + '/editor.html';
};
$scope.panel_path =function(type) {
if(type) {
return 'app/panels/'+type.replace(".","/");
} else {
return false;
}
$scope.pulldownEditorPath = function(type) {
return 'app/panels/'+type+'/editor.html';
};
$scope.showJsonEditor = function(evt, options) {
var editScope = $rootScope.$new();
editScope.object = options.object;
editScope.updateHandler = options.updateHandler;
$scope.emitAppEvent('show-dash-editor', { src: 'app/partials/edit_json.html', scope: editScope });
$scope.appEvent('show-dash-editor', { src: 'app/partials/edit_json.html', scope: editScope });
};
$scope.checkFeatureToggles = function() {
$scope.submenuEnabled = $scope.dashboard.templating.enable || $scope.dashboard.annotations.enable;
};
$scope.setEditorTabs = function(panelMeta) {
$scope.editorTabs = ['General','Panel'];
if(!_.isUndefined(panelMeta.editorTabs)) {
$scope.editorTabs = _.union($scope.editorTabs,_.pluck(panelMeta.editorTabs,'title'));
$scope.onDrop = function(panelId, row, dropTarget) {
var info = $scope.dashboard.getPanelInfoById(panelId);
if (dropTarget) {
var dropInfo = $scope.dashboard.getPanelInfoById(dropTarget.id);
dropInfo.row.panels[dropInfo.index] = info.panel;
info.row.panels[info.index] = dropTarget;
var dragSpan = info.panel.span;
info.panel.span = dropTarget.span;
dropTarget.span = dragSpan;
}
return $scope.editorTabs;
else {
info.row.panels.splice(info.index, 1);
info.panel.span = 12 - $scope.dashboard.rowSpan(row);
row.panels.push(info.panel);
}
$rootScope.$broadcast('render');
};
});
+17 -11
View File
@@ -69,7 +69,7 @@ function (angular, _, moment, config, store) {
};
$scope.openSearch = function() {
$scope.emitAppEvent('show-dash-editor', { src: 'app/partials/search.html' });
$scope.appEvent('show-dash-editor', { src: 'app/partials/search.html' });
};
$scope.saveDashboard = function() {
@@ -78,7 +78,7 @@ function (angular, _, moment, config, store) {
var clone = angular.copy($scope.dashboard);
$scope.db.saveDashboard(clone)
.then(function(result) {
alertSrv.set('Dashboard Saved', 'Saved as "' + result.title + '"','success', 3000);
$scope.appEvent('alert-success', ['Dashboard saved', 'Saved as ' + result.title]);
if (result.url !== $location.path()) {
$location.search({});
@@ -88,22 +88,28 @@ function (angular, _, moment, config, store) {
$rootScope.$emit('dashboard-saved', $scope.dashboard);
}, function(err) {
alertSrv.set('Save failed', err, 'error', 5000);
$scope.appEvent('alert-error', ['Save failed', err]);
});
};
$scope.deleteDashboard = function(evt, options) {
if (!confirm('Do you want to delete dashboard ' + options.title + ' ?')) {
return;
}
if (!$scope.isAdmin()) { return false; }
$scope.appEvent('confirm-modal', {
title: 'Delete dashboard',
text: 'Do you want to delete dashboard ' + options.title + '?',
onConfirm: function() {
$scope.deleteDashboardConfirmed(options);
}
});
};
$scope.deleteDashboardConfirmed = function(options) {
var id = options.id;
$scope.db.deleteDashboard(id).then(function(id) {
alertSrv.set('Dashboard Deleted', id + ' has been deleted', 'success', 5000);
}, function() {
alertSrv.set('Dashboard Not Deleted', 'An error occurred deleting the dashboard', 'error', 5000);
$scope.appEvent('alert-success', ['Dashboard Deleted', id + ' has been deleted']);
}, function(err) {
$scope.appEvent('alert-error', ['Deleted failed', err]);
});
};
@@ -138,7 +144,7 @@ function (angular, _, moment, config, store) {
};
$scope.editJson = function() {
$scope.emitAppEvent('show-json-editor', { object: $scope.dashboard });
$scope.appEvent('show-json-editor', { object: $scope.dashboard });
};
$scope.openSaveDropdown = function() {
+8 -12
View File
@@ -10,19 +10,19 @@ function (angular, config, _, $, store) {
var module = angular.module('grafana.controllers');
module.controller('GrafanaCtrl', function($scope, alertSrv, grafanaVersion, $rootScope, $controller) {
module.controller('GrafanaCtrl', function($scope, alertSrv, utilSrv, grafanaVersion, $rootScope, $controller) {
$scope.grafanaVersion = grafanaVersion[0] === '@' ? 'master' : grafanaVersion;
$scope.consoleEnabled = store.getBool('grafanaConsole');
$scope._ = _;
$rootScope.profilingEnabled = store.getBool('profilingEnabled');
$rootScope.performance = { loadStart: new Date().getTime() };
$scope.init = function() {
$scope._ = _;
if ($rootScope.profilingEnabled) { $scope.initProfiling(); }
alertSrv.init();
utilSrv.init();
$scope.dashAlerts = alertSrv;
$scope.grafana = { style: 'dark' };
};
@@ -41,7 +41,7 @@ function (angular, config, _, $, store) {
this.$on('$destroy', unbind);
};
$rootScope.emitAppEvent = function(name, payload) {
$rootScope.appEvent = function(name, payload) {
$rootScope.$emit(name, payload);
};
@@ -81,12 +81,8 @@ function (angular, config, _, $, store) {
$scope.initProfiling = function() {
var count = 0;
$scope.$watch(function digestCounter() {
count++;
}, function() {
});
$scope.onAppEvent('setup-dashboard', function() {
$scope.$watch(function digestCounter() { count++; }, function() { });
$scope.onAppEvent('dashboard-loaded', function() {
count = 0;
setTimeout(function() {
+5 -1
View File
@@ -201,7 +201,7 @@ function (angular, _, config, gfunc, Parser) {
$scope.targetTextChanged = function() {
parseTarget();
$scope.$parent.get_data();
$scope.get_data();
};
$scope.targetChanged = function() {
@@ -275,6 +275,10 @@ function (angular, _, config, gfunc, Parser) {
}
};
$scope.moveMetricQuery = function(fromIndex, toIndex) {
_.move($scope.panel.targets, fromIndex, toIndex);
};
$scope.duplicate = function() {
var clone = angular.copy($scope.target);
$scope.panel.targets.push(clone);
+10 -4
View File
@@ -1,7 +1,8 @@
define([
'angular'
'angular',
'lodash'
],
function (angular) {
function (angular, _) {
'use strict';
var module = angular.module('grafana.controllers');
@@ -83,10 +84,11 @@ function (angular) {
};
$scope.listSeries = function(query, callback) {
if (!seriesList || query === '') {
if (query !== '') {
seriesList = [];
$scope.datasource.listSeries().then(function(series) {
$scope.datasource.listSeries(query).then(function(series) {
seriesList = series;
console.log(series);
callback(seriesList);
});
}
@@ -95,6 +97,10 @@ function (angular) {
}
};
$scope.moveMetricQuery = function(fromIndex, toIndex) {
_.move($scope.panel.targets, fromIndex, toIndex);
};
$scope.duplicate = function() {
var clone = angular.copy($scope.target);
$scope.panel.targets.push(clone);
+30 -15
View File
@@ -23,6 +23,11 @@ function (angular, app, _) {
$scope.reset_panel();
};
$scope.togglePanelMenu = function(posX) {
$scope.showPanelMenu = !$scope.showPanelMenu;
$scope.panelMenuPos = posX;
};
$scope.toggle_row = function(row) {
row.collapse = row.collapse ? false : true;
if (!row.collapse) {
@@ -42,9 +47,13 @@ function (angular, app, _) {
};
$scope.delete_row = function() {
if (confirm("Are you sure you want to delete this row?")) {
$scope.dashboard.rows = _.without($scope.dashboard.rows, $scope.row);
}
$scope.appEvent('confirm-modal', {
title: 'Delete row',
text: 'Are you sure you want to delete this row?',
onConfirm: function() {
$scope.dashboard.rows = _.without($scope.dashboard.rows, $scope.row);
}
});
};
$scope.move_row = function(direction) {
@@ -71,9 +80,13 @@ function (angular, app, _) {
};
$scope.remove_panel_from_row = function(row, panel) {
if (confirm('Are you sure you want to remove this ' + panel.type + ' panel?')) {
row.panels = _.without(row.panels,panel);
}
$scope.appEvent('confirm-modal', {
title: 'Remove panel',
text: 'Are you sure you want to remove this panel?',
onConfirm: function() {
row.panels = _.without(row.panels, panel);
}
});
};
$scope.replacePanel = function(newPanel, oldPanel) {
@@ -89,15 +102,12 @@ function (angular, app, _) {
});
};
$scope.duplicatePanel = function(panel, row) {
$scope.dashboard.duplicatePanel(panel, row || $scope.row);
};
$scope.reset_panel = function(type) {
var defaultSpan = 12;
var _as = 12 - $scope.dashboard.rowSpan($scope.row);
$scope.panel = {
title: 'no title [click here]',
error : false,
span : _as < defaultSpan && _as > 0 ? _as : defaultSpan,
editable: true,
@@ -139,13 +149,18 @@ function (angular, app, _) {
module.directive('panelDropZone', function() {
return function(scope, element) {
scope.$watch('dashboard.$$panelDragging', function(newVal) {
if (newVal && scope.dashboard.rowSpan(scope.row) < 10) {
scope.$on("ANGULAR_DRAG_START", function() {
var dropZoneSpan = 12 - scope.dashboard.rowSpan(scope.row);
if (dropZoneSpan > 0) {
element.find('.panel-container').css('height', scope.row.height);
element[0].style.width = ((dropZoneSpan / 1.2) * 10) + '%';
element.show();
}
else {
element.hide();
}
});
scope.$on("ANGULAR_DRAG_END", function() {
element.hide();
});
};
});
+3 -2
View File
@@ -29,7 +29,7 @@ function (angular, _, config, $) {
$scope.keyDown = function (evt) {
if (evt.keyCode === 27) {
$scope.emitAppEvent('hide-dash-editor');
$scope.appEvent('hide-dash-editor');
}
if (evt.keyCode === 40) {
$scope.moveSelection(1);
@@ -62,6 +62,7 @@ function (angular, _, config, $) {
};
$scope.goToDashboard = function(id) {
$location.search({});
$location.path("/dashboard/db/" + id);
};
@@ -121,7 +122,7 @@ function (angular, _, config, $) {
$scope.deleteDashboard = function(dash, evt) {
evt.stopPropagation();
$scope.emitAppEvent('delete-dashboard', { id: dash.id, title: dash.title });
$scope.appEvent('delete-dashboard', { id: dash.id, title: dash.title });
$scope.results.dashboards = _.without($scope.results.dashboards, dash);
};
+85
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();
});
});
+1 -2
View File
@@ -1,9 +1,8 @@
define([
'angular',
'app',
'lodash'
],
function (angular, app, _) {
function (angular, _) {
'use strict';
var module = angular.module('grafana.controllers');
@@ -72,6 +72,9 @@ function (angular, _) {
if ($scope.current.type === 'interval') {
$scope.current.query = '1m,10m,30m,1h,6h,12h,1d,7d,14d,30d';
}
if ($scope.current.type === 'query') {
$scope.current.query = '';
}
};
$scope.removeVariable = function(variable) {
+1 -2
View File
@@ -17,7 +17,7 @@
"editable": true,
"type": "text",
"mode": "html",
"content": "<div class=\"text-center\" style=\"padding-top: 15px\">\n<img src=\"//grafana.org/assets/img/logo_transparent_200x75.png\"> \n</div>",
"content": "<div class=\"text-center\" style=\"padding-top: 15px\">\n<img src=\"img/logo_transparent_200x.png\"> \n</div>",
"style": {},
"title": "Welcome to"
}
@@ -101,7 +101,6 @@
"legend_counts": true,
"timezone": "browser",
"percentage": false,
"zerofill": true,
"nullPointMode": "connected",
"steppedLine": false,
"tooltip": {
+11
View File
@@ -68,6 +68,17 @@ for (var i = 0; i < rows; i++) {
'target': "randomWalk('random walk2')"
}
],
seriesOverrides: [
{
alias: '/random/',
yaxis: 2,
fill: 0,
linewidth: 5
}
],
tooltip: {
shared: true
}
}
]
});
@@ -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);
}
});
});
};
-1
View File
@@ -65,7 +65,6 @@
"avg": false
},
"percentage": false,
"zerofill": true,
"nullPointMode": "connected",
"steppedLine": false,
"tooltip": {
+3 -4
View File
@@ -68,13 +68,12 @@ function (angular, app, _, $, gfunc) {
});
$input.blur(function() {
$input.hide();
$input.val('');
$button.show();
$button.focus();
// clicking the function dropdown menu wont
// work if you remove class at once
setTimeout(function() {
$input.val('');
$input.hide();
$button.show();
elem.removeClass('open');
}, 200);
});
+1 -1
View File
@@ -10,7 +10,6 @@ define([
'./confirmClick',
'./configModal',
'./spectrumPicker',
'./grafanaGraph',
'./bootstrap-tagsinput',
'./bodyClass',
'./addGraphiteFunc',
@@ -18,5 +17,6 @@ define([
'./templateParamSelector',
'./graphiteSegment',
'./grafanaVersionCheck',
'./dropdown.typeahead',
'./influxdbFuncEditor'
], function () {});
+1 -1
View File
@@ -16,7 +16,7 @@ function (angular, $) {
elem.bind('click',function() {
$timeout(function() {
var editorScope = attrs.editorScope === 'isolated' ? null : scope;
scope.emitAppEvent('show-dash-editor', { src: partial, scope: editorScope });
scope.appEvent('show-dash-editor', { src: partial, scope: editorScope });
});
});
}
+7 -1
View File
@@ -16,7 +16,13 @@ function (angular, kbn) {
var readerOnload = function() {
return function(e) {
scope.$apply(function() {
window.grafanaImportDashboard = JSON.parse(e.target.result);
try {
window.grafanaImportDashboard = JSON.parse(e.target.result);
} catch (err) {
console.log(err);
scope.appEvent('alert-error', ['Import failed', 'JSON -> JS Serialization failed: ' + err.message]);
return;
}
var title = kbn.slugifyForUrl(window.grafanaImportDashboard.title);
$location.path('/dashboard/import/' + title);
});
+105
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);
}
};
});
});
+17 -32
View File
@@ -1,9 +1,10 @@
define([
'angular',
'jquery',
'lodash',
'config',
'./panelMenu',
],
function (angular, $) {
function (angular, $, config) {
'use strict';
angular
@@ -15,37 +16,19 @@ function (angular, $) {
var panelHeader =
'<div class="panel-header">'+
'<div class="row-fluid panel-extra">' +
'<div class="panel-extra-container">' +
'<span class="alert-error panel-error small pointer"' +
'config-modal="app/partials/inspector.html" ng-if="panelMeta.error">' +
'<span data-placement="right" bs-tooltip="panelMeta.error">' +
'<i class="icon-exclamation-sign"></i><span class="panel-error-arrow"></span>' +
'</span>' +
'<span class="alert-error panel-error small pointer"' +
'config-modal="app/partials/inspector.html" ng-if="panelMeta.error">' +
'<span data-placement="right" bs-tooltip="panelMeta.error">' +
'<i class="icon-exclamation-sign"></i><span class="panel-error-arrow"></span>' +
'</span>' +
'</span>' +
'<span class="panel-loading" ng-show="panelMeta.loading">' +
'<i class="icon-spinner icon-spin icon-large"></i>' +
'</span>' +
'<span class="panel-loading" ng-show="panelMeta.loading">' +
'<i class="icon-spinner icon-spin icon-large"></i>' +
'</span>' +
'<span class="dropdown">' +
'<span class="panel-text panel-title pointer" gf-dropdown="panelMeta.menu" tabindex="1" ' +
'data-drag=true data-jqyoui-options="kbnJqUiDraggableOptions"'+
' jqyoui-draggable="'+
'{'+
'animate:false,'+
'mutate:false,'+
'index:{{$index}},'+
'onStart:\'panelMoveStart\','+
'onStop:\'panelMoveStop\''+
'}" ng-model="panel" ' +
'>' +
'{{panel.title | interpolateTemplateVars}}' +
'</span>' +
'</span>'+
'</div>'+
'</div>\n'+
'<div class="panel-title-container drag-handle" panel-menu></div>' +
'</div>'+
'</div>';
return {
@@ -86,10 +69,12 @@ function (angular, $) {
elem.addClass('ng-cloak');
var panelPath = config.panels[panelType].path;
$scope.require([
'jquery',
'text!panels/'+panelType+'/module.html',
'panels/' + panelType + "/module",
'text!'+panelPath+'/module.html',
panelPath + "/module",
], function ($, moduleTemplate) {
var $module = $(moduleTemplate);
$module.prepend(panelHeader);
+160
View File
@@ -0,0 +1,160 @@
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);
};
if ($scope.panelMeta.titlePos && $scope.panel.title) {
elem.css('text-align', 'left');
$link.css('padding-left', '10px');
}
elem.click(showMenu);
$compile(elem.contents())($scope);
}
};
});
});
+5 -1
View File
@@ -32,7 +32,11 @@ function (angular) {
};
input.spectrum(options);
scope.$on('$destroy', function() {
input.spectrum('destroy');
});
}
};
});
});
});
+23
View File
@@ -17,4 +17,27 @@ function (angular, kbn) {
}
};
});
angular
.module('grafana.directives')
.directive('editorOptBool', function($compile) {
return {
restrict: 'E',
link: function(scope, elem, attrs) {
var ngchange = attrs.change ? (' ng-change="' + attrs.change + '"') : '';
var tip = attrs.tip ? (' <tip>' + attrs.tip + '</tip>') : '';
var showIf = attrs.showIf ? (' ng-show="' + attrs.showIf + '" ') : '';
var template = '<div class="editor-option text-center"' + showIf + '>' +
' <label for="' + attrs.model + '" class="small">' +
attrs.text + tip + '</label>' +
'<input class="cr1" id="' + attrs.model + '" type="checkbox" ' +
' ng-model="' + attrs.model + '"' + ngchange +
' ng-checked="' + attrs.model + '"></input>' +
' <label for="' + attrs.model + '" class="cr1"></label>';
elem.replaceWith($compile(angular.element(template))(scope));
}
};
});
});
+3
View File
@@ -0,0 +1,3 @@
define([
'./panellinkeditor/module',
], function () {});
@@ -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 += "&" + link.params;
}
return info;
};
});
});
@@ -0,0 +1,49 @@
<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</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>
@@ -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);
};
});
});
+6 -2
View File
@@ -56,9 +56,13 @@ define(['angular', 'jquery', 'lodash', 'moment'], function (angular, $, _, momen
});
module.filter('interpolateTemplateVars', function(templateSrv) {
return function(text) {
function interpolateTemplateVars(text) {
return templateSrv.replaceWithText(text);
};
}
interpolateTemplateVars.$stateful = true;
return interpolateTemplateVars;
});
});
+13 -42
View File
@@ -40,42 +40,19 @@
<div class="editor-row">
<div class="section">
<h5>Legend styles</h5>
<div class="editor-option">
<label class="small">Show Legend</label><input type="checkbox" ng-model="panel.legend.show" ng-checked="panel.legend.show" ng-change="render();">
</div>
<div class="editor-option">
<label class="small">Include Values</label><input type="checkbox" ng-model="panel.legend.values" ng-checked="panel.legend.values" ng-change="render();">
</div>
<div class="editor-option">
<label class="small">Align as table</label><input type="checkbox" ng-model="panel.legend.alignAsTable" ng-checked="panel.legend.alignAsTable">
</div>
<div class="editor-option">
<label class="small">Right side</label><input type="checkbox" ng-model="panel.legend.rightSide" ng-change="render();" ng-checked="panel.legend.rightSide">
</div>
</div>
<editor-opt-bool text="Show" model="panel.legend.show" change="get_data();"></editor-opt-bool>
<editor-opt-bool text="Values" model="panel.legend.values" change="render()"></editor-opt-bool>
<editor-opt-bool text="Table" model="panel.legend.alignAsTable" change="render()"></editor-opt-bool>
<editor-opt-bool text="Right side" model="panel.legend.rightSide" change="render()"></editor-opt-bool>
</div>
<div class="section" ng-if="panel.legend.values">
<h5>Legend values</h5>
<div class="editor-option">
<label class="small">Min</label><input type="checkbox" ng-model="panel.legend.min" ng-checked="panel.legend.min" ng-change="render();">
</div>
<div class="editor-option">
<label class="small">Max</label><input type="checkbox" ng-model="panel.legend.max" ng-checked="panel.legend.max" ng-change="render();">
</div>
<div class="editor-option">
<label class="small">Current</label><input type="checkbox" ng-model="panel.legend.current" ng-checked="panel.legend.current" ng-change="render();">
</div>
<div class="editor-option">
<label class="small">Total</label><input type="checkbox" ng-model="panel.legend.total" ng-checked="panel.legend.total" ng-change="render();">
</div>
<div class="editor-option">
<label class="small">Avg</label><input type="checkbox" ng-model="panel.legend.avg" ng-checked="panel.legend.avg" ng-change="render();">
</div>
<editor-opt-bool text="Min" model="panel.legend.min" change="render()"></editor-opt-bool>
<editor-opt-bool text="Max" model="panel.legend.max" change="render()"></editor-opt-bool>
<editor-opt-bool text="Current" model="panel.legend.current" change="render()"></editor-opt-bool>
<editor-opt-bool text="Total" model="panel.legend.total" change="render()"></editor-opt-bool>
<editor-opt-bool text="Avg" model="panel.legend.avg" change="render()"></editor-opt-bool>
</div>
<div class="section">
@@ -96,19 +73,13 @@
<label class="small">Color</label>
<spectrum-picker ng-model="panel.grid.threshold2Color" ng-change="render()" ></spectrum-picker>
</div>
<div class="editor-option">
<label class="small">Line mode</label><input type="checkbox" ng-model="panel.grid.thresholdLine" ng-checked="panel.grid.thresholdLine" ng-change="render();">
</div>
<editor-opt-bool text="Line mode" model="panel.grid.thresholdLine" change="render()"></editor-opt-bool>
</div>
<div class="section">
<h5>Show Axes</h5>
<div class="editor-option">
<label class="small">X-Axis</label><input type="checkbox" ng-model="panel['x-axis']" ng-checked="panel['x-axis']" ng-change="render()">
</div>
<div class="editor-option">
<label class="small">Y-Axis</label><input type="checkbox" ng-model="panel['y-axis']" ng-checked="panel['y-axis']" ng-change="render()">
</div>
<editor-opt-bool text="X-Axis" model="panel['x-axis']" change="render()"></editor-opt-bool>
<editor-opt-bool text="Y-axis" model="panel['y-axis']" change="render()"></editor-opt-bool>
</div>
</div>
@@ -3,9 +3,18 @@ define([
'jquery',
'kbn',
'moment',
'lodash'
'lodash',
'./graph.tooltip',
'jquery.flot',
'jquery.flot.events',
'jquery.flot.selection',
'jquery.flot.time',
'jquery.flot.stack',
'jquery.flot.stackpercent',
'jquery.flot.fillbelow',
'jquery.flot.crosshair'
],
function (angular, $, kbn, moment, _) {
function (angular, $, kbn, moment, _, GraphTooltip) {
'use strict';
var module = angular.module('grafana.directives');
@@ -15,16 +24,34 @@ function (angular, $, kbn, moment, _) {
restrict: 'A',
template: '<div> </div>',
link: function(scope, elem) {
var data, annotations;
var dashboard = scope.dashboard;
var data, annotations;
var legendSideLastValue = null;
scope.crosshairEmiter = false;
scope.$on('refresh',function() {
scope.get_data();
scope.onAppEvent('setCrosshair', function(event, info) {
// do not need to to this if event is from this panel
if (info.scope === scope) {
return;
}
if(dashboard.sharedCrosshair) {
var plot = elem.data().plot;
if (plot) {
plot.setCrosshair({ x: info.pos.x, y: info.pos.y });
}
}
});
scope.$on('toggleLegend', function() {
render_panel();
scope.onAppEvent('clearCrosshair', function() {
var plot = elem.data().plot;
if (plot) {
plot.clearCrosshair();
}
});
scope.$on('refresh', function() {
scope.get_data();
});
// Receive render events
@@ -45,10 +72,11 @@ function (angular, $, kbn, moment, _) {
height = parseInt(height.replace('px', ''), 10);
}
height = height - 32; // subtract panel title bar
height -= 5; // padding
height -= scope.panel.title ? 24 : 9; // subtract panel title bar
if (scope.panel.legend.show && !scope.panel.legend.rightSide) {
height = height - 21; // subtract one line legend
height = height - 26; // subtract one line legend
}
elem.css('height', height + 'px');
@@ -80,6 +108,18 @@ function (angular, $, kbn, moment, _) {
}
}
function updateLegendValues(plot) {
var yaxis = plot.getYAxes();
for (var i = 0; i < data.length; i++) {
var series = data[i];
var axis = yaxis[series.yaxis - 1];
var formater = kbn.valueFormats[scope.panel.y_formats[series.yaxis - 1]];
series.updateLegendValues(formater, axis.tickDecimals, axis.scaledDecimals + 2);
if(!scope.$$phase) { scope.$digest(); }
}
}
// Function for rendering panel
function render_panel() {
if (shouldAbortRender()) {
@@ -91,6 +131,7 @@ function (angular, $, kbn, moment, _) {
// Populate element
var options = {
hooks: { draw: [updateLegendValues] },
legend: { show: false },
series: {
stackpercent: panel.stack ? panel.percentage : false,
@@ -113,7 +154,8 @@ function (angular, $, kbn, moment, _) {
show: panel.points,
fill: 1,
fillColor: false,
radius: panel.pointradius
radius: panel.points ? panel.pointradius : 2
// little points when highlight points
},
shadowSize: 1
},
@@ -130,6 +172,9 @@ function (angular, $, kbn, moment, _) {
selection: {
mode: "x",
color: '#666'
},
crosshair: {
mode: panel.tooltip.shared || dashboard.sharedCrosshair ? "x" : null
}
};
@@ -137,15 +182,16 @@ function (angular, $, kbn, moment, _) {
var series = data[i];
series.applySeriesOverrides(panel.seriesOverrides);
series.data = series.getFlotPairs(panel.nullPointMode, panel.y_formats);
// if hidden remove points and disable stack
if (scope.hiddenSeries[series.info.alias]) {
if (scope.hiddenSeries[series.alias]) {
series.data = [];
series.stack = false;
}
}
if (data.length && data[0].info.timeStep) {
options.series.bars.barWidth = data[0].info.timeStep / 1.5;
if (data.length && data[0].stats.timeStep) {
options.series.bars.barWidth = data[0].stats.timeStep / 1.5;
}
addTimeAxis(options);
@@ -166,6 +212,8 @@ function (angular, $, kbn, moment, _) {
}
if (shouldDelayDraw(panel)) {
// temp fix for legends on the side, need to render twice to get dimensions right
callPlot();
setTimeout(callPlot, 50);
legendSideLastValue = panel.legend.rightSide;
}
@@ -299,7 +347,9 @@ function (angular, $, kbn, moment, _) {
}
function configureAxisMode(axis, format) {
axis.tickFormatter = kbn.getFormatFunction(format, 1);
axis.tickFormatter = function(val, axis) {
return kbn.valueFormats[format](val, axis.tickDecimals, axis.scaledDecimals);
};
}
function time_format(interval, ticks, min, max) {
@@ -324,40 +374,6 @@ function (angular, $, kbn, moment, _) {
return "%H:%M";
}
var $tooltip = $('<div id="tooltip">');
elem.bind("plothover", function (event, pos, item) {
var group, value, timestamp, seriesInfo, format;
if (item) {
seriesInfo = item.series.info;
format = scope.panel.y_formats[seriesInfo.yaxis - 1];
if (seriesInfo.alias) {
group = '<small style="font-size:0.9em;">' +
'<i class="icon-circle" style="color:'+item.series.color+';"></i>' + ' ' +
seriesInfo.alias +
'</small><br>';
} else {
group = kbn.query_color_dot(item.series.color, 15) + ' ';
}
if (scope.panel.stack && scope.panel.tooltip.value_type === 'individual') {
value = item.datapoint[1] - item.datapoint[2];
}
else {
value = item.datapoint[1];
}
value = kbn.getFormatFunction(format, 2)(value, item.series.yaxis);
timestamp = dashboard.formatDate(item.datapoint[0]);
$tooltip.html(group + value + " @ " + timestamp).place_tt(pos.pageX, pos.pageY);
} else {
$tooltip.detach();
}
});
function render_panel_as_graphite_png(url) {
url += '&width=' + elem.width();
url += '&height=' + elem.css('height').replace('px', '');
@@ -408,6 +424,10 @@ function (angular, $, kbn, moment, _) {
elem.html('<img src="' + url + '"></img>');
}
new GraphTooltip(elem, dashboard, scope, function() {
return data;
});
elem.bind("plotselected", function (event, ranges) {
scope.$apply(function() {
timeSrv.setTime({
+182
View File
@@ -0,0 +1,182 @@
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;
var results = [];
var pointCount = seriesList[0].data.length;
for (i = 1; i < seriesList.length; i++) {
if (seriesList[i].data.length !== pointCount) {
results.pointCountMismatch = true;
return results;
}
}
series = seriesList[0];
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 (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++) {
series = seriesList[i];
hoverInfo = seriesHoverInfo[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;
});
-58
View File
@@ -1,58 +0,0 @@
<section class="graph-legend" ng-class="{'graph-legend-table': panel.legend.alignAsTable}">
<div class="graph-legend-series"
ng-repeat='series in legend'
ng-class="{'pull-right': series.yaxis === 2, 'graph-legend-series-hidden': hiddenSeries[series.alias]}"
>
<div class="graph-legend-icon">
<i class='icon-minus pointer' ng-style="{color: series.color}" bs-popover="'colorPopup.html'" data-placement="bottom">
</i>
</div>
<div class="graph-legend-alias small">
<a ng-click="toggleSeries(series, $event)" data-unique="1" data-placement="{{series.yaxis === 2 ? 'bottomRight' : 'bottomLeft'}}">
{{series.alias}}
</a>
</div>
<div class="graph-legend-value current small" ng-show="panel.legend.values && panel.legend.current" ng-bind="series.current">
</div>
<div class="graph-legend-value min small" ng-show="panel.legend.values && panel.legend.min" ng-bind="series.min">
</div>
<div class="graph-legend-value max small" ng-show="panel.legend.values && panel.legend.max" ng-bind="series.max">
</div>
<div class="graph-legend-value total small" ng-show="panel.legend.values && panel.legend.total" ng-bind="series.total">
</div>
<div class="graph-legend-value avg small" ng-show="panel.legend.values && panel.legend.avg" ng-bind="series.avg">
</div>
</div>
</section>
<script type="text/ng-template" id="colorPopup.html">
<div class="graph-legend-popover">
<a class="close" ng-click="dismiss();" href="">×</a>
<div class="editor-row small" style="padding-bottom: 0;">
<label>Axis:</label>
<button ng-click="toggleYAxis(series);dismiss();"
class="btn btn-mini"
ng-class="{'btn-success': series.yaxis === 1 }">
Left
</button>
<button ng-click="toggleYAxis(series);dismiss();"
class="btn btn-mini"
ng-class="{'btn-success': series.yaxis === 2 }">
Right
</button>
</div>
<div class="editor-row">
<i ng-repeat="color in colors"
class="pointer"
ng-class="{'icon-circle-blank': color === series.color,'icon-circle': color !== series.color}"
ng-style="{color:color}"
ng-click="changeSeriesColor(series, color);dismiss();">
</i>
</div>
</div>
</script>
+162
View File
@@ -0,0 +1,162 @@
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];
var html = '<div class="graph-legend-series';
if (series.yaxis === 2) { html += ' pull-right'; }
if (scope.hiddenSeries[series.alias]) { html += ' graph-legend-series-hidden'; }
html += '" data-series-index="' + i + '">';
html += '<div class="graph-legend-icon">';
html += '<i class="icon-minus pointer" style="color:' + series.color + '"></i>';
html += '</div>';
html += '<div class="graph-legend-alias">';
html += '<a>' + series.label + '</a>';
html += '</div>';
var avg = series.formatValue(series.stats.avg);
var current = series.formatValue(series.stats.current);
var min = series.formatValue(series.stats.min);
var max = series.formatValue(series.stats.max);
var total = series.formatValue(series.stats.total);
if (panel.legend.values) {
if (panel.legend.min) { html += '<div class="graph-legend-value min">' + min + '</div>'; }
if (panel.legend.max) { html += '<div class="graph-legend-value max">' + max + '</div>'; }
if (panel.legend.avg) { html += '<div class="graph-legend-value avg">' + avg + '</div>'; }
if (panel.legend.current) { html += '<div class="graph-legend-value current">' + current + '</div>'; }
if (panel.legend.total) { html += '<div class="graph-legend-value total">' + total + '</div>'; }
}
html += '</div>';
$container.append($(html));
}
}
}
};
});
});
+26
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>
+15 -18
View File
@@ -1,25 +1,22 @@
<div ng-controller='GraphCtrl'>
<div ng-controller='GraphCtrl'>
<div class="graph-wrapper" ng-class="{'graph-legend-rightside': panel.legend.rightSide}">
<div class="graph-canvas-wrapper">
<div class="graph-wrapper" ng-class="{'graph-legend-rightside': panel.legend.rightSide}">
<div class="graph-canvas-wrapper">
<div ng-if="datapointsWarning" class="datapoints-warning">
<span class="small" ng-show="!datapointsCount">No datapoints <tip>Can be caused by timezone mismatch between browser and graphite server</tip></span>
<span class="small" ng-show="datapointsOutside">Datapoints outside time range <tip>Can be caused by timezone mismatch between browser and graphite server</tip></span>
</div>
<div ng-if="datapointsWarning" class="datapoints-warning">
<span class="small" ng-show="!datapointsCount">No datapoints <tip>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 grafana-graph class="pointer histogram-chart">
</div>
<div grafana-graph class="histogram-chart">
</div>
</div>
</div>
<div class="graph-legend-wrapper"
ng-if="panel.legend.show"
ng-include="'app/panels/graph/legend.html'">
</div>
</div>
<div class="graph-legend-wrapper" ng-if="panel.legend.show" graph-legend></div>
</div>
<div class="clearfix"></div>
<div class="clearfix"></div>
<div style="margin-top: 30px" ng-if="editMode">
<div class="dashboard-editor-header">
@@ -29,13 +26,13 @@
</div>
<div ng-model="editor.index" bs-tabs>
<div ng-repeat="tab in editorTabs" data-title="{{tab}}">
<div ng-repeat="tab in panelMeta.editorTabs" data-title="{{tab.title}}">
</div>
</div>
</div>
<div class="dashboard-editor-body">
<div ng-repeat="tab in panelMeta.fullEditorTabs" ng-if="editorTabs[editor.index] == tab.title">
<div ng-repeat="tab in panelMeta.editorTabs" ng-if="editor.index === $index">
<div ng-include src="tab.src"></div>
</div>
</div>
+76 -145
View File
@@ -6,81 +6,46 @@ define([
'kbn',
'moment',
'components/timeSeries',
'./seriesOverridesCtrl',
'components/panelmeta',
'services/panelSrv',
'services/annotationsSrv',
'services/datasourceSrv',
'jquery.flot',
'jquery.flot.events',
'jquery.flot.selection',
'jquery.flot.time',
'jquery.flot.stack',
'jquery.flot.stackpercent'
'./seriesOverridesCtrl',
'./graph',
'./legend',
],
function (angular, app, $, _, kbn, moment, TimeSeries) {
function (angular, app, $, _, kbn, moment, TimeSeries, PanelMeta) {
'use strict';
var module = angular.module('grafana.panels.graph');
app.useModule(module);
module.controller('GraphCtrl', function($scope, $rootScope, panelSrv, annotationsSrv, timeSrv) {
$scope.panelMeta = {
modals : [],
editorTabs: [],
fullEditorTabs : [
{
title: 'General',
src:'app/partials/panelgeneral.html'
},
{
title: 'Metrics',
src:'app/partials/metrics.html'
},
{
title:'Axes & Grid',
src:'app/panels/graph/axisEditor.html'
},
{
title:'Display Styles',
src:'app/panels/graph/styleEditor.html'
}
],
fullscreenEdit: true,
fullscreenView: true,
description : "Graphing"
};
$scope.panelMeta = new PanelMeta({
description: 'Graph panel',
fullscreen: true,
metricsEditor: true
});
$scope.panelMeta.addEditorTab('Axes & Grid', 'app/panels/graph/axisEditor.html');
$scope.panelMeta.addEditorTab('Display Styles', 'app/panels/graph/styleEditor.html');
$scope.panelMeta.addExtendedMenuItem('Export CSV', '', 'exportCsv()');
$scope.panelMeta.addExtendedMenuItem('Toggle legend', '', 'toggleLegend()');
// Set and populate defaults
var _d = {
// datasource name, null = default datasource
datasource: null,
/** @scratch /panels/histogram/3
* renderer:: sets client side (flot) or native graphite png renderer (png)
*/
// sets client side (flot) or native graphite png renderer (png)
renderer: 'flot',
/** @scratch /panels/histogram/3
* x-axis:: Show the x-axis
*/
// Show/hide the x-axis
'x-axis' : true,
/** @scratch /panels/histogram/3
* y-axis:: Show the y-axis
*/
// Show/hide y-axis
'y-axis' : true,
/** @scratch /panels/histogram/3
* scale:: Scale the y-axis by this factor
*/
scale : 1,
/** @scratch /panels/histogram/3
* y_formats :: 'none','bytes','bits','bps','short', 's', 'ms'
*/
// y axis formats, [left axis,right axis]
y_formats : ['short', 'short'],
/** @scratch /panels/histogram/5
* grid object:: Min and max y-axis values
* grid.min::: Minimum y-axis value
* grid.ma1::: Maximum y-axis value
*/
// grid options
grid : {
leftMax: null,
rightMax: null,
@@ -91,48 +56,23 @@ function (angular, app, $, _, kbn, moment, TimeSeries) {
threshold1Color: 'rgba(216, 200, 27, 0.27)',
threshold2Color: 'rgba(234, 112, 112, 0.22)'
},
annotate : {
enable : false,
},
/** @scratch /panels/histogram/3
* resolution:: If auto_int is true, shoot for this many bars.
*/
resolution : 100,
/** @scratch /panels/histogram/3
* ==== Drawing options
* lines:: Show line chart
*/
// show/hide lines
lines : true,
/** @scratch /panels/histogram/3
* fill:: Area fill factor for line charts, 1-10
*/
// fill factor
fill : 0,
/** @scratch /panels/histogram/3
* linewidth:: Weight of lines in pixels
*/
// line width in pixels
linewidth : 1,
/** @scratch /panels/histogram/3
* points:: Show points on chart
*/
// show hide points
points : false,
/** @scratch /panels/histogram/3
* pointradius:: Size of points in pixels
*/
// point radius in pixels
pointradius : 5,
/** @scratch /panels/histogram/3
* bars:: Show bars on chart
*/
// show hide bars
bars : false,
/** @scratch /panels/histogram/3
* stack:: Stack multiple series
*/
// enable/disable stacking
stack : false,
/** @scratch /panels/histogram/3
* legend:: Display the legend
*/
// stack percentage mode
percentage : false,
// legend options
legend: {
show: true, // disable/enable legend
values: false, // disable/enable legend values
@@ -142,31 +82,20 @@ function (angular, app, $, _, kbn, moment, TimeSeries) {
total: false,
avg: false
},
/** @scratch /panels/histogram/3
* ==== Transformations
/** @scratch /panels/histogram/3
* percentage:: Show the y-axis as a percentage of the axis total. Only makes sense for multiple
* queries
*/
percentage : false,
/** @scratch /panels/histogram/3
* zerofill:: Improves the accuracy of line charts at a small performance cost.
*/
zerofill : true,
// how null points should be handled
nullPointMode : 'connected',
// staircase line mode
steppedLine: false,
// tooltip options
tooltip : {
value_type: 'cumulative',
query_as_alias: true
shared: false,
},
// metric queries
targets: [{}],
// series color overrides
aliasColors: {},
// other style overrides
seriesOverrides: [],
};
@@ -177,11 +106,17 @@ function (angular, app, $, _, kbn, moment, TimeSeries) {
_.defaults($scope.panel.legend, _d.legend);
$scope.hiddenSeries = {};
$scope.seriesList = [];
$scope.updateTimeRange = function () {
$scope.range = timeSrv.timeRange();
$scope.rangeUnparsed = timeSrv.timeRange(false);
$scope.resolution = Math.ceil($(window).width() * ($scope.panel.span / 12));
if ($scope.panel.maxDataPoints) {
$scope.resolution = $scope.panel.maxDataPoints;
}
else {
$scope.resolution = Math.ceil($(window).width() * ($scope.panel.span / 12));
}
$scope.interval = kbn.calculateInterval($scope.range, $scope.resolution, $scope.panel.interval);
};
@@ -205,13 +140,13 @@ function (angular, app, $, _, kbn, moment, TimeSeries) {
$scope.panelMeta.loading = false;
$scope.panelMeta.error = err.message || "Timeseries data request error";
$scope.inspector.error = err;
$scope.seriesList = [];
$scope.render([]);
});
};
$scope.dataHandler = function(results) {
$scope.panelMeta.loading = false;
$scope.legend = [];
// png renderer returns just a url
if (_.isString(results)) {
@@ -223,16 +158,16 @@ function (angular, app, $, _, kbn, moment, TimeSeries) {
$scope.datapointsCount = 0;
$scope.datapointsOutside = false;
var data = _.map(results.data, $scope.seriesHandler);
$scope.seriesList = _.map(results.data, $scope.seriesHandler);
$scope.datapointsWarning = $scope.datapointsCount === 0 || $scope.datapointsOutside;
$scope.annotationsPromise
.then(function(annotations) {
data.annotations = annotations;
$scope.render(data);
$scope.seriesList.annotations = annotations;
$scope.render($scope.seriesList);
}, function() {
$scope.render(data);
$scope.render($scope.seriesList);
});
};
@@ -241,20 +176,14 @@ function (angular, app, $, _, kbn, moment, TimeSeries) {
var alias = seriesData.target;
var color = $scope.panel.aliasColors[alias] || $rootScope.colors[index];
var seriesInfo = {
alias: alias,
color: color,
};
$scope.legend.push(seriesInfo);
var series = new TimeSeries({
datapoints: datapoints,
info: seriesInfo,
alias: alias,
color: color,
});
if (datapoints && datapoints.length > 0) {
var last = moment.utc(datapoints[datapoints.length - 1][1] * 1000);
var last = moment.utc(datapoints[datapoints.length - 1][1]);
var from = moment.utc($scope.range.from);
if (last - from < -10000) {
$scope.datapointsOutside = true;
@@ -267,7 +196,7 @@ function (angular, app, $, _, kbn, moment, TimeSeries) {
};
$scope.render = function(data) {
$scope.$emit('render', data);
$scope.$broadcast('render', data);
};
$scope.changeSeriesColor = function(series, color) {
@@ -277,18 +206,18 @@ function (angular, app, $, _, kbn, moment, TimeSeries) {
};
$scope.toggleSeries = function(serie, event) {
if ($scope.hiddenSeries[serie.alias]) {
delete $scope.hiddenSeries[serie.alias];
}
else {
$scope.hiddenSeries[serie.alias] = true;
}
if (event.ctrlKey || event.metaKey || event.shiftKey) {
if ($scope.hiddenSeries[serie.alias]) {
delete $scope.hiddenSeries[serie.alias];
}
else {
$scope.hiddenSeries[serie.alias] = true;
}
} else {
$scope.toggleSeriesExclusiveMode(serie);
}
$scope.$emit('toggleLegend', $scope.legend);
$scope.render();
};
$scope.toggleSeriesExclusiveMode = function(serie) {
@@ -299,7 +228,7 @@ function (angular, app, $, _, kbn, moment, TimeSeries) {
}
// check if every other series is hidden
var alreadyExclusive = _.every($scope.legend, function(value) {
var alreadyExclusive = _.every($scope.seriesList, function(value) {
if (value.alias === serie.alias) {
return true;
}
@@ -309,13 +238,13 @@ function (angular, app, $, _, kbn, moment, TimeSeries) {
if (alreadyExclusive) {
// remove all hidden series
_.each($scope.legend, function(value) {
_.each($scope.seriesList, function(value) {
delete $scope.hiddenSeries[value.alias];
});
}
else {
// hide all but this serie
_.each($scope.legend, function(value) {
_.each($scope.seriesList, function(value) {
if (value.alias === serie.alias) {
return;
}
@@ -340,8 +269,8 @@ function (angular, app, $, _, kbn, moment, TimeSeries) {
$scope.render();
};
$scope.addSeriesOverride = function() {
$scope.panel.seriesOverrides.push({});
$scope.addSeriesOverride = function(override) {
$scope.panel.seriesOverrides.push(override || {});
};
$scope.removeSeriesOverride = function(override) {
@@ -349,12 +278,14 @@ function (angular, app, $, _, kbn, moment, TimeSeries) {
$scope.render();
};
$scope.toggleEditorHelp = function(index) {
if ($scope.editorHelpIndex === index) {
$scope.editorHelpIndex = null;
return;
}
$scope.editorHelpIndex = index;
// Called from panel menu
$scope.toggleLegend = function() {
$scope.panel.legend.show = !$scope.panel.legend.show;
$scope.get_data();
};
$scope.exportCsv = function() {
kbn.exportSeriesListToCsv($scope.seriesList);
};
panelSrv.init($scope);
+12 -3
View File
@@ -23,7 +23,7 @@ define([
option.submenu = _.map(values, function(value, index) {
return {
text: String(value),
click: 'setOverride(' + option.index + ',' + index + ')'
click: 'menuItemSelected(' + option.index + ',' + index + ')'
};
});
@@ -34,6 +34,14 @@ define([
var option = $scope.overrideMenu[optionIndex];
var value = option.values[valueIndex];
$scope.override[option.propertyName] = value;
// automatically disable lines for this series and the fill bellow to series
// can be removed by the user if they still want lines
if (option.propertyName === 'fillBelowTo') {
$scope.override['lines'] = false;
$scope.addSeriesOverride({ alias: value, lines: false });
}
$scope.updateCurrentOverrides();
$scope.render();
};
@@ -45,8 +53,8 @@ define([
};
$scope.getSeriesNames = function() {
return _.map($scope.legend, function(info) {
return info.alias;
return _.map($scope.seriesList, function(series) {
return series.alias;
});
};
@@ -67,6 +75,7 @@ define([
$scope.addOverrideOption('Lines', 'lines', [true, false]);
$scope.addOverrideOption('Line fill', 'fill', [0,1,2,3,4,5,6,7,8,9,10]);
$scope.addOverrideOption('Line width', 'linewidth', [0,1,2,3,4,5,6,7,8,9,10]);
$scope.addOverrideOption('Fill below to', 'fillBelowTo', $scope.getSeriesNames());
$scope.addOverrideOption('Staircase line', 'steppedLine', [true, false]);
$scope.addOverrideOption('Points', 'points', [true, false]);
$scope.addOverrideOption('Points Radius', 'pointradius', [1,2,3,4,5]);
+26 -28
View File
@@ -1,15 +1,9 @@
<div class="editor-row">
<div class="section">
<h5>Chart Options</h5>
<div class="editor-option">
<label class="small">Bars</label><input type="checkbox" ng-model="panel.bars" ng-checked="panel.bars" ng-change="render()">
</div>
<div class="editor-option">
<label class="small">Lines</label><input type="checkbox" ng-model="panel.lines" ng-checked="panel.lines" ng-change="render()">
</div>
<div class="editor-option">
<label class="small">Points</label><input type="checkbox" ng-model="panel.points" ng-checked="panel.points" ng-change="render()">
</div>
<editor-opt-bool text="Bars" model="panel.bars" change="render()"></editor-opt-bool>
<editor-opt-bool text="Lines" model="panel.lines" change="render()"></editor-opt-bool>
<editor-opt-bool text="Points" model="panel.points" change="render()"></editor-opt-bool>
</div>
<div class="section">
@@ -30,24 +24,16 @@
<label class="small">Null point mode<tip>Define how null values should be drawn</tip></label>
<select class="input-medium" ng-model="panel.nullPointMode" ng-options="f for f in ['connected', 'null', 'null as zero']" ng-change="render()"></select>
</div>
<div class="editor-option">
<label class="small">Staircase line</label><input type="checkbox" ng-model="panel.steppedLine" ng-checked="panel.steppedLine" ng-change="render()">
</div>
<editor-opt-bool text="Staircase line" model="panel.steppedLine" change="render()"></editor-opt-bool>
</div>
<div class="section">
<h5>Multiple Series</h5>
<div class="editor-option">
<label class="small">Stack</label><input type="checkbox" ng-model="panel.stack" ng-checked="panel.stack" ng-change="render()">
</div>
<div class="editor-option" ng-show="panel.stack">
<label style="white-space:nowrap" class="small">Percent <tip>Stack as a percentage of total</tip></label>
<input type="checkbox" ng-model="panel.percentage" ng-checked="panel.percentage" ng-change="render()">
</div>
<div class="editor-option" ng-show="panel.stack">
<label class="small">Stacked Values <tip>How should the values in stacked charts to be calculated?</tip></label>
<select class="input-small" ng-model="panel.tooltip.value_type" ng-options="f for f in ['cumulative','individual']" ng-change="render()"></select>
</div>
</div>
<editor-opt-bool text="Stack" model="panel.stack" change="render()"></editor-opt-bool>
<editor-opt-bool text="Percent" model="panel.percentage" change="render()" tip="Stack as a percentage of total"></editor-opt-bool>
</div>
<div class="section">
@@ -61,8 +47,21 @@
<input type="radio" class="input-small" ng-model="panel.renderer" value="png" ng-change="get_data()" />
</div>
</div>
<div class="section">
<h5>Tooltip</h5>
<editor-opt-bool
text="All series" model="panel.tooltip.shared" change="render()"
tip="Show all series on same tooltip and a x croshair to help follow all series">
</editor-opt-bool>
<div class="editor-option" ng-show="panel.stack">
<label class="small">Stacked Values <tip>How should the values in stacked charts to be calculated?</tip></label>
<select class="input-small" ng-model="panel.tooltip.value_type" ng-options="f for f in ['cumulative','individual']" ng-change="render()"></select>
</div>
</div>
</div>
<div class="editor-row">
<div class="section">
<h5>Series specific overrides <tip>Regex match example: /server[0-3]/i </tip></h5>
@@ -89,11 +88,10 @@
<i class="pointer icon-remove" ng-click="removeOverride(option)"></i>
{{option.name}}: {{option.value}}
</li>
<li class="dropdown">
<a class="dropdown-toggle grafana-target-segment" data-toggle="dropdown" gf-dropdown="overrideMenu" bs-tooltip="'set option to override'" data-placement="top">
<i class="icon-plus"></i>
</a>
<li class="dropdown" dropdown-typeahead="overrideMenu" dropdown-typeahead-on-select="setOverride($optionIndex, $valueIndex)">
</li>
</ul>
<div class="clearfix"></div>
</div>
+76
View File
@@ -0,0 +1,76 @@
<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>
<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 ['30%','50%','70%','80%','100%']" 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 ['30%','50%','70%','80%','100%', '110%', '120%']" 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 ['30%','50%','70%','80%','100%']" 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 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">
<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">
<label class="small">Color</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>
<div class="editor-row">
<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>
+26
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>
+197
View File
@@ -0,0 +1,197 @@
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',
titlePos: 'left',
fullscreen: true,
metricsEditor: true
});
$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: '',
valueName: 'avg',
prefixFontSize: '50%',
valueFontSize: '100%',
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('connected');
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 opts = {};
if (value === 0 || value === 1) {
return { decimals: 0, scaledDecimals: 0 };
}
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;
if (opts.minTickSize != null && size < opts.minTickSize) {
size = opts.minTickSize;
}
var result = {};
result.decimals = Math.max(0, dec);
result.scaledDecimals = result.decimals - Math.floor(Math.log(size) / Math.LN11) + 2;
return result;
};
$scope.render = function() {
var data = {};
if (!$scope.series || $scope.series.length === 0) {
data.flotpairs = [];
data.mainValue = Number.NaN;
data.mainValueFormated = 'NaN';
}
else {
var series = $scope.series[0];
data.mainValue = series.stats[$scope.panel.valueName];
var decimalInfo = $scope.getDecimalsForValue(data.mainValue);
var formatFunc = kbn.valueFormats[$scope.panel.format];
data.mainValueFormated = formatFunc(data.mainValue, decimalInfo.decimals, decimalInfo.scaledDecimals);
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.init();
});
});
@@ -0,0 +1,205 @@
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 && 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); }
$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.clientX+20, e.clientY-15);
});
}
};
});
});
+8 -4
View File
@@ -3,8 +3,9 @@ define([
'app',
'lodash',
'require',
'components/panelmeta',
],
function (angular, app, _, require) {
function (angular, app, _, require, PanelMeta) {
'use strict';
var module = angular.module('grafana.panels.text', []);
@@ -14,12 +15,15 @@ function (angular, app, _, require) {
module.controller('text', function($scope, templateSrv, $sce, panelSrv) {
$scope.panelMeta = {
$scope.panelMeta = new PanelMeta({
description : "A static text panel that can use plain text, markdown, or (sanitized) HTML"
};
});
$scope.panelMeta.addEditorTab('Edit text', 'app/panels/text/editor.html');
// Set and populate defaults
var _d = {
title : 'default title',
mode : "markdown", // 'html', 'markdown', 'text'
content : "",
style: {},
@@ -28,7 +32,7 @@ function (angular, app, _, require) {
_.defaults($scope.panel, _d);
$scope.init = function() {
panelSrv.init(this);
panelSrv.init($scope);
$scope.ready = false;
$scope.$on('refresh', $scope.render);
$scope.render();
+19 -15
View File
@@ -9,22 +9,27 @@
border: 0px !important;
}
</style>
<!-- This is a complete hack. The form actually exists in the modal, but due to transclusion
$scope.input isn't available on the controller unless the form element is in this file -->
<form name="input" style="margin:3px 0 0 0">
<ul class="nav nav-pills timepicker-dropdown">
<li class="dropdown">
<form name="input" style="margin:0">
<ul class="nav timepicker-dropdown">
<a class="dropdown-toggle timepicker-dropdown" data-toggle="dropdown" href="" bs-tooltip="time.tooltip" data-placement="bottom" ng-click="dismiss();">
<span ng-bind="time.rangeString"></span>
<span ng-show="dashboard.refresh" class="text-warning">refreshed every {{dashboard.refresh}} </span>
<i class="icon-caret-down"></i>
</a>
<li class="grafana-menu-zoom-out">
<a class='small' ng-click='zoom(2)'>
Zoom Out
</a>
</li>
<ul class="dropdown-menu">
<!-- Relative time options -->
<li bindonce ng-repeat='timespan in panel.time_options track by $index'>
<a ng-click="setRelativeFilter(timespan)" bo-text="'Last ' + timespan"></a>
<li class="dropdown">
<a class="dropdown-toggle timepicker-dropdown" data-toggle="dropdown" href="" bs-tooltip="time.tooltip" data-placement="bottom" ng-click="dismiss();">
<span ng-bind="time.rangeString"></span>
<span ng-show="dashboard.refresh" class="text-warning">refreshed every {{dashboard.refresh}} </span>
<i class="icon-caret-down"></i>
</a>
<ul class="dropdown-menu">
<!-- Relative time options -->
<li bindonce ng-repeat='timespan in panel.time_options track by $index'>
<a ng-click="setRelativeFilter(timespan)" bo-text="'Last ' + timespan"></a>
</li>
<!-- Auto refresh submenu -->
@@ -47,6 +52,5 @@
<a ng-click="timeSrv.refreshDashboard()"><i class="icon-refresh"></i></a>
</li>
</ul>
</form>
</div>
+3 -3
View File
@@ -75,11 +75,11 @@ function (angular, app, _, moment, kbn) {
// Date picker needs the date to be at the start of the day
if(new Date().getTimezoneOffset() < 0) {
$scope.temptime.from.date = moment($scope.temptime.from.date).add('days',1).toDate();
$scope.temptime.to.date = moment($scope.temptime.to.date).add('days',1).toDate();
$scope.temptime.from.date = moment($scope.temptime.from.date).add(1, 'days').toDate();
$scope.temptime.to.date = moment($scope.temptime.to.date).add(1, 'days').toDate();
}
$scope.emitAppEvent('show-dash-editor', {src: 'app/panels/timepicker/custom.html', scope: $scope });
$scope.appEvent('show-dash-editor', {src: 'app/panels/timepicker/custom.html', scope: $scope });
};
// Constantly validate the input of the fields. This function does not change any date variables
+1 -4
View File
@@ -61,10 +61,7 @@
<label class="small">Icon size</label>
<select class="input-mini" ng-model="currentAnnotation.iconSize" ng-options="f for f in [7,8,9,10,13,15,17,20,25,30]"></select>
</div>
<div class="editor-option">
<label class="small">Grid line</label>
<input type="checkbox" ng-model="currentAnnotation.showLine" ng-checked="currentAnnotation.showLine">
</div>
<editor-opt-bool text="Grid line" model="currentAnnotation.showLine"></editor-opt-bool>
<div class="editor-option">
<label class="small">Line color</label>
<spectrum-picker ng-model="currentAnnotation.lineColor"></spectrum-picker>
+23
View File
@@ -0,0 +1,23 @@
<div class="modal-body">
<div class="dashboard-editor-header">
<div class="dashboard-editor-title">
<i class="icon icon-ok"></i>
{{title}}
</div>
</div>
<div class="dashboard-editor-body">
<p class="row-fluid text-center large">
{{text}}
<br>
<br>
</p>
<div class="row-fluid">
<span class="span4"></span>
<button type="button" class="btn btn-success span2" ng-click="dismiss()">No</button>
<button type="button" class="btn btn-danger span2" ng-click="onConfirm();dismiss();">Yes</button>
<span class="span4"></span>
</div>
</div>
+15 -11
View File
@@ -14,7 +14,7 @@
<div class="main-view-container">
<div class="grafana-row" ng-controller="RowCtrl" ng-repeat="(row_name, row) in dashboard.rows" row-height>
<div class="row-control">
<div class="row-control-inner" style="padding:0px;margin:0px;position:relative;">
<div class="row-control-inner">
<div class="row-close" ng-show="row.collapse" data-placement="bottom" >
<div class="row-close-buttons">
<span class="row-button bgPrimary" ng-click="toggle_row(row)">
@@ -74,24 +74,28 @@
</div>
</div>
<div style="padding-top:0px" ng-if="!row.collapse">
<div class="panels-wrapper" ng-if="!row.collapse">
<div class="row-text pointer" ng-click="toggle_row(row)" ng-if="row.showTitle" ng-bind="row.title">
</div>
<!-- Panels -->
<div ng-repeat="(name, panel) in row.panels"
class="panel nospace"
style="position:relative"
data-drop="true"
panel-width
ng-model="panel"
data-jqyoui-options
jqyoui-droppable="{index:$index,mutate:false,onDrop:'panelMoveDrop',onOver:'panelMoveOver(true)',onOut:'panelMoveOut'}"
ng-class="{'dragInProgress':dashboard.$$panelDragging}">
class="panel"
ui-draggable="true" drag="panel.id"
ui-on-Drop="onDrop($data, row, panel)"
drag-handle-class="drag-handle" panel-width ng-model="panel">
<grafana-panel type="panel.type" ng-cloak></grafana-panel>
</div>
<div panel-drop-zone class="panel dragInProgress" style="margin:5px;width:30%;background:rgba(100,100,100,0.50)" ng-style="{height:row.height}" data-drop="true" ng-model="row.panels" data-jqyoui-options jqyoui-droppable="{index:row.panels.length,mutate:false,onDrop:'panelMoveDrop',onOver:'panelMoveOver',onOut:'panelMoveOut'}">
<div panel-drop-zone class="panel panel-drop-zone"
ui-on-drop="onDrop($data, row)"
data-drop="true">
<div class="panel-container" style="background: transparent">
<div style="text-align: center">
<em>Drop here</em>
</div>
</div>
</div>
<div class="clearfix"></div>
-6
View File
@@ -13,12 +13,6 @@
</a>
</li>
<li class="grafana-menu-zoom-out">
<a class='small' ng-click='zoom(2)'>
Zoom Out
</a>
</li>
<li ng-repeat="pulldown in dashboard.nav" ng-controller="PulldownCtrl" ng-show="pulldown.enable">
<grafana-simple-panel type="pulldown.type" ng-cloak>
</grafana-simple-panel>
+9 -16
View File
@@ -28,10 +28,7 @@
<label class="small">Time correction</label>
<select ng-model="dashboard.timezone" class='input-small' ng-options="f for f in ['browser','utc']"></select>
</div>
<div class="editor-option">
<label class="small">Hide controls (CTRL+H)</label>
<input type="checkbox" ng-model="dashboard.hideControls" ng-checked="dashboard.hideControls">
</div>
<editor-opt-bool text="Hide controls (CTRL+H)" model="dashboard.hideControls"></editor-opt-bool>
</div>
</div>
<div class="editor-row">
@@ -42,7 +39,6 @@
</bootstrap-tagsinput>
<tip>Press enter to a add tag</tip>
</div>
</div>
</div>
</div>
@@ -71,17 +67,14 @@
<div ng-if="editor.index == 2">
<div class="editor-row">
<div class="section">
<div class="editor-option">
<label class="small">Templating</label>
<input type="checkbox" ng-model="dashboard.templating.enable" ng-checked="dashboard.templating.enable" ng-change="checkFeatureToggles()"x >
</div>
<div class="editor-option">
<label class="small">Annotations</label>
<input type="checkbox" ng-model="dashboard.annotations.enable" ng-checked="dashboard.annotations.enable" ng-change="checkFeatureToggles()">
</div>
<div class="editor-option" ng-repeat="pulldown in dashboard.nav">
<label class="small" style="text-transform:capitalize;">{{pulldown.type}}</label><input type="checkbox" ng-model="pulldown.enable" ng-checked="pulldown.enable">
<editor-opt-bool text="Templating" model="dashboard.templating.enable" change="checkFeatureToggles()"></editor-opt-bool>
<editor-opt-bool text="Annotations" model="dashboard.annotations.enable" change="checkFeatureToggles()"></editor-opt-bool>
<div class="editor-option text-center" ng-repeat="pulldown in dashboard.nav">
<label class="small" style="text-transform:capitalize;">{{pulldown.type}}</label>
<input class="cr1" id="pulldown{{pulldown.type}}" type="checkbox" ng-model="pulldown.enable" ng-checked="pulldown.enable">
<label for="pulldown{{pulldown.type}}" class="cr1"></label>
</div>
<editor-opt-bool text="Shared Crosshair (CTRL+O)" model="dashboard.sharedCrosshair"></editor-opt-bool>
</div>
</div>
</div>
@@ -91,7 +84,7 @@
</div>
<div ng-repeat="pulldown in dashboard.nav" ng-controller="SubmenuCtrl" ng-show="editor.index == 4+$index">
<ng-include ng-show="pulldown.enable" src="edit_path(pulldown.type)"></ng-include>
<ng-include ng-show="pulldown.enable" src="pulldownEditorPath(pulldown.type)"></ng-include>
<button ng-hide="pulldown.enable" class="btn" ng-click="pulldown.enable = true">Enable the {{pulldown.type}}</button>
</div>
+51 -9
View File
@@ -30,6 +30,19 @@
ng-click="duplicate()">
Duplicate
</a>
</li>
<li role="menuitem">
<a tabindex="1"
ng-click="moveMetricQuery($index, $index-1)">
Move up
</a>
</li>
<li role="menuitem">
<a tabindex="1"
ng-click="moveMetricQuery($index, $index+1)">
Move down
</a>
</li>
</ul>
</li>
<li>
@@ -83,16 +96,29 @@
<i class="icon-wrench"></i>
</li>
<li class="grafana-target-segment">
cacheTimeout
Cache timeout
</li>
<li>
<input type="text"
class="input-mini grafana-target-segment-input"
ng-model="panel.cacheTimeout"
bs-tooltip="'Graphite parameter to overwride memcache default timeout (unit is seconds)'"
data-placement="right"
spellcheck='false'
placeholder="60">
class="input-mini grafana-target-segment-input"
ng-model="panel.cacheTimeout"
bs-tooltip="'Graphite parameter to overwride memcache default timeout (unit is seconds)'"
data-placement="right"
spellcheck='false'
placeholder="60">
</li>
<li class="grafana-target-segment">
Max data points
</li>
<li>
<input type="text"
class="input-mini grafana-target-segment-input"
ng-model="panel.maxDataPoints"
bs-tooltip="'Override max data points, automatically set to graph width in pixels.'"
data-placement="right"
ng-model-onblur ng-change="get_data()"
spellcheck='false'
placeholder="auto">
</li>
</ul>
<div class="clearfix"></div>
@@ -122,6 +148,11 @@
templating
</a>
</li>
<li class="grafana-target-segment">
<a ng-click="toggleEditorHelp(5)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
max data points
</a>
</li>
</ul>
<div class="clearfix"></div>
</div>
@@ -177,7 +208,18 @@
</ul>
</div>
<div class="grafana-info-box span6" ng-if="editorHelpIndex === 5">
<h5>Max data points</h5>
<ul>
<li>Every graphite request is issued with a maxDataPoints parameter</li>
<li>Graphite uses this parameter to consolidate the real number of values down to this number</li>
<li>If there are more real values, then by default they will be consolidated using averages</li>
<li>This could hide real peaks and max values in your series</li>
<li>You can change how point consolidation is made using the consolidateBy graphite function</li>
<li>Point consolidation will effect series legend values (min,max,total,current)</li>
<li>If you override maxDataPoint and set a high value performance can be severely effected</li>
</ul>
</div>
</div>
</div>
+50
View File
@@ -0,0 +1,50 @@
<div class="modal-body">
<div class="dashboard-editor-header">
<div class="dashboard-editor-title">
<i class="icon icon-keyboard"></i>
Keyboard shutcuts
</div>
</div>
<div class="dashboard-editor-body">
<table class="shortcut-table">
<tr>
<th></th>
<th style="text-align: left;">Dashboard wide shortcuts</th>
</tr>
<tr>
<td style="text-align: right;"><span class="label label-info">ESC</span></td>
<td>Exit fullscreen edit/view mode, close search or any editor view</td>
</tr>
<tr>
<td><span class="label label-info">CTRL+F</span></td>
<td>Open dashboard search view (also contains import/playlist controls)</td>
</tr>
<tr>
<td><span class="label label-info">CTRL+S</span></td>
<td>Save dashboard</td>
</tr>
<tr>
<td><span class="label label-info">CTRL+H</span></td>
<td>Hide row controls</td>
</tr>
<tr>
<td><span class="label label-info">CTRL+Z</span></td>
<td>Zoom out</td>
</tr>
<tr>
<td><span class="label label-info">CTRL+R</span></td>
<td>Refresh (Fetches new data and rerenders panels)</td>
</tr>
<tr>
<td><span class="label label-info">CTRL+O</span></td>
<td>Enable/Disable shared graph crosshair</td>
</tr>
</table>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-info" ng-click="dismiss()">Close</button>
</div>
+21 -19
View File
@@ -15,26 +15,26 @@
tabindex="1">
<i class="icon icon-cog"></i>
</a>
<ul class="dropdown-menu pull-right" role="menu">
<li role="menuitem">
<a tabindex="1" ng-click="duplicate()">Duplicate</a>
<a tabindex="2" ng-click="showQuery()" ng-hide="target.rawQuery">Raw query mode</a>
<a tabindex="2" ng-click="hideQuery()" ng-show="target.rawQuery">Query editor mode</a>
</li>
</ul>
</li>
<li>
<a class="pointer" tabindex="1" ng-click="removeDataQuery(target)">
<i class="icon icon-remove"></i>
</a>
</li>
</ul>
<ul class="dropdown-menu pull-right" role="menu">
<li role="menuitem"><a tabindex="1" ng-click="duplicate()">Duplicate</a></li>
<li role="menuitem"><a tabindex="1" ng-click="showQuery()" ng-hide="target.rawQuery">Raw query mode</a></li>
<li role="menuitem"><a tabindex="1" ng-click="hideQuery()" ng-show="target.rawQuery">Query editor mode</a></li>
<li role="menuitem"><a tabindex="1" ng-click="moveMetricQuery($index, $index-1)">Move up </a></li>
<li role="menuitem"><a tabindex="1" ng-click="moveMetricQuery($index, $index+1)">Move down</a></li>
</ul>
</li>
<li>
<a class="pointer" tabindex="1" ng-click="removeDataQuery(target)">
<i class="icon icon-remove"></i>
</a>
</li>
</ul>
<ul class="grafana-segment-list">
<li>
<a class="grafana-target-segment" ng-click="target.hide = !target.hide; get_data();" role="menuitem">
<i class="icon-eye-open"></i>
</a>
<ul class="grafana-segment-list">
<li>
<a class="grafana-target-segment" ng-click="target.hide = !target.hide; get_data();" role="menuitem">
<i class="icon-eye-open"></i>
</a>
</li>
</ul>
@@ -64,6 +64,8 @@
ng-model="target.series"
spellcheck='false'
bs-typeahead="listSeries"
match-all="true"
min-length="3"
placeholder="series name"
data-min-length=0 data-items=100
ng-blur="seriesBlur()">
+1
View File
@@ -14,4 +14,5 @@
</ul>
</div>
<div class="clearfix"></div>
</div>
+26
View File
@@ -89,6 +89,32 @@
ng-model="target.isCounter"
ng-change="targetBlur()">
</li>
<li class="grafana-target-segment" ng-hide="!target.isCounter">
Counter Max:
</li>
<li ng-hide="!target.isCounter">
<input type="text"
class="grafana-target-segment-input input-medium"
ng-disabled="!target.shouldComputeRate"
ng-model="target.counterMax"
spellcheck='false'
placeholder="Counter max value"
ng-blur="targetBlur()"
/>
</li>
<li class="grafana-target-segment" ng-hide="!target.isCounter">
Counter Reset Value:
</li>
<li ng-hide="!target.isCounter">
<input type="text"
class="grafana-target-segment-input input-medium"
ng-disabled="!target.shouldComputeRate"
ng-model="target.counterResetValue"
spellcheck='false'
placeholder="Counter reset value"
ng-blur="targetBlur()"
/>
</li>
<li class="grafana-target-segment">
Alias:
</li>
+2 -10
View File
@@ -5,22 +5,14 @@
</div>
<div ng-model="editor.index" bs-tabs style="text-transform:capitalize;">
<div ng-repeat="tab in setEditorTabs(panelMeta)" data-title="{{tab}}">
<div ng-repeat="tab in panelMeta.editorTabs" data-title="{{tab.title}}">
</div>
</div>
</div>
<div class="dashboard-editor-body">
<div ng-show="editorTabs[editor.index] == 'General'">
<div ng-include src="'app/partials/panelgeneral.html'"></div>
</div>
<div ng-show="editorTabs[editor.index] == 'Panel'">
<div ng-include src="edit_path(panel.type)"></div>
</div>
<div ng-repeat="tab in panelMeta.editorTabs" ng-show="editorTabs[editor.index] == tab.title">
<div ng-repeat="tab in panelMeta.editorTabs" ng-show="editor.index == $index">
<div ng-include src="tab.src"></div>
</div>
</div>
+4
View File
@@ -12,3 +12,7 @@
</div>
</div>
</div>
<panel-link-editor panel="panel"></panel-link-editor>
+2 -1
View File
@@ -22,7 +22,8 @@
{{dashboard.title}}
</td>
<td style="text-align: center">
<input type="checkbox" ng-model="dashboard.include" ng-checked="dashboard.include" />
<input id="dash-{{$index}}" class="cr1" type="checkbox" ng-model="dashboard.include" ng-checked="dashboard.include" />
<label for="dash-{{$index}}" class="cr1"></label>
</td>
<td style="text-align: center">
<i class="icon-remove pointer" ng-click="removeAsFavorite(dashboard)"></i>
+2 -6
View File
@@ -20,12 +20,8 @@
<div class="editor-option">
<label class="small">Height</label><input type="text" class="input-mini" ng-model='row.height'></input>
</div>
<div class="editor-option">
<label class="small"> Editable </label><input type="checkbox" ng-model="row.editable" ng-checked="row.editable" />
</div>
<div class="editor-option">
<label class="small"> Show title </label><input type="checkbox" ng-model="row.showTitle" ng-checked="row.showTitle" />
</div>
<editor-opt-bool text="Editable" model="row.editable"></editor-opt-bool>
<editor-opt-bool text="Show title" model="row.showTitle"></editor-opt-bool>
</div>
<div class="row-fluid" ng-if="editor.index == 1">
<div class="span12">
+1 -1
View File
@@ -22,7 +22,7 @@
</button>
<span style="position: relative;">
<input type="text" placeholder="search dashboards, metrics, or graphs" xng-focus="giveSearchFocus"
ng-keydown="keyDown($event)" ng-model="query.query" spellcheck='false' ng-change="search()" />
ng-keydown="keyDown($event)" ng-model="query.query" ng-model-options="{ debounce: 500 }" spellcheck='false' ng-change="search()" />
<a class="search-tagview-switch" href="javascript:void(0);" ng-class="{'active': tagsOnly}" ng-click="showTags($event)">tags</a>
</span>
</div>
+33
View File
@@ -0,0 +1,33 @@
<div ng-controller="SharePanelCtrl">
<div class="modal-header">
<div class="dashboard-editor-header">
<div class="dashboard-editor-title">
<i class="icon icon-share"></i>
Share
</div>
<div ng-model="editor.index" bs-tabs style="text-transform:capitalize;">
<div ng-repeat="tab in ['Link']" data-title="{{tab}}">
</div>
</div>
</div>
</div>
<div class="modal-body">
<div class="editor-row">
<editor-opt-bool text="Current time range" model="forCurrent" change="buildUrl()"></editor-opt-bool>
<editor-opt-bool text="To this panel only" model="toPanel" change="buildUrl()"></editor-opt-bool>
<editor-opt-bool text="Include template variables" model="includeTemplateVars" change="buildUrl()"></editor-opt-bool>
</div>
<div class="editor-row" style="margin-top: 20px;">
<input type="text" data-share-panel-url class="input input-fluid" ng-model='shareUrl'></input>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-success pull-right" ng-click="dismiss();">close</button>
</div>
</div>
+6 -12
View File
@@ -66,10 +66,10 @@
<label class="small">Datasource</label>
<select class="input input-medium" ng-model="current.datasource" ng-options="f.value as f.name for f in datasources"></select>
</div>
<div class="editor-option text-center" ng-show="current.type === 'query'">
<label class="small">Refresh on load <tip>Check if you want values to be updated on dashboard load, will slow down dashboard load time.</tip></label>
<input type="checkbox" ng-model="current.refresh" ng-checked="current.refresh">
</div>
<editor-opt-bool text="Refresh on load" show-if="current.type === 'query'"
tip="Check if you want values to be updated on dashboard load, will slow down dashboard load time"
model="current.refresh"></editor-opt-bool>
</div>
<div ng-show="current.type === 'interval'">
@@ -80,10 +80,7 @@
</div>
</div>
<div class="editor-row">
<div class="editor-option text-center">
<label class="small">Include auto interval</label>
<input type="checkbox" ng-model="current.auto" ng-checked="current.auto" ng-change="runQuery()">
</div>
<editor-opt-bool text="Include auto interval" model="current.auto" change="runQuery()"></editor-opt-bool>
<div class="editor-option" ng-show="current.auto">
<label class="small">Auto interval steps <tip>How many steps, roughly, the interval is rounded and will not always match this count<tip></label>
<select class="input-mini" ng-model="current.auto_count" ng-options="f for f in [3,5,10,30,50,100,200]" ng-change="runQuery()"></select>
@@ -118,10 +115,7 @@
</div>
<div class="editor-row" style="margin: 15px 0">
<div class="editor-option text-center">
<label class="small">All option</label>
<input type="checkbox" ng-model="current.includeAll" ng-checked="current.includeAll" ng-change="runQuery()">
</div>
<editor-opt-bool text="All option" model="current.includeAll" change="runQuery()"></editor-opt-bool>
<div class="editor-option" ng-show="current.includeAll">
<label class="small">All format</label>
<select class="input-medium" ng-model="current.allFormat" ng-change="runQuery()" ng-options="f for f in ['glob', 'wildcard', 'regex wildcard', 'regex values']"></select>
+3 -4
View File
@@ -1,19 +1,18 @@
<div class="modal-header">
</div>
<div class="modal-body">
<h3 class="text-center"><i class="icon-warning-sign"></i> Unsaved changes</h3>
<div class="modal-body">
<h4 class="text-center"><i class="icon-warning-sign"></i> Unsaved changes</h4>
<div class="row-fluid">
<span class="span3">
{{changes}}
</span>
<button type="button" class="btn btn-success span2" ng-click="dismiss()">Cancel</button>
<button type="button" class="btn btn-success span2" ng-click="save();dismiss();">Save</button>
<button type="button" class="btn btn-warning span2" ng-click="ignore();dismiss();">Ignore</button>
<span class="span3"></span>
</div>
</div>
<div class="modal-footer">
</div>
+4 -4
View File
@@ -31,7 +31,7 @@ function (angular) {
});
module.controller('DashFromDBProvider', function($scope, $rootScope, datasourceSrv, $routeParams, alertSrv) {
module.controller('DashFromDBProvider', function($scope, $rootScope, datasourceSrv, $routeParams) {
var db = datasourceSrv.getGrafanaDB();
var isTemp = window.location.href.indexOf('dashboard/temp') !== -1;
@@ -41,14 +41,14 @@ function (angular) {
$scope.initDashboard(dashboard, $scope);
}).then(null, function(error) {
$scope.initDashboard({ title: 'Grafana'}, $scope);
alertSrv.set('Error', error, 'error');
$scope.appEvent('alert-error', ['Dashboard load failed', error]);
});
});
module.controller('DashFromImportCtrl', function($scope, $location, alertSrv) {
module.controller('DashFromImportCtrl', function($scope, $location) {
if (!window.grafanaImportDashboard) {
alertSrv.set('Not found', 'Cannot reload page with unsaved imported dashboard', 'warning', 7000);
$scope.appEvent('alert-warning', ['Dashboard load failed', 'Cannot reload unsaved imported dashboard']);
$location.path('');
return;
}
+12 -5
View File
@@ -16,21 +16,28 @@ function (angular, $, config, _, kbn, moment) {
.when('/dashboard/script/:jsFile', {
templateUrl: 'app/partials/dashboard.html',
controller : 'DashFromScriptProvider',
reloadOnSearch: false,
});
});
module.controller('DashFromScriptProvider', function($scope, $rootScope, $http, $routeParams, alertSrv, $q) {
module.controller('DashFromScriptProvider', function($scope, $rootScope, $http, $routeParams, $q, dashboardSrv, datasourceSrv, $timeout) {
var execute_script = function(result) {
var services = {
dashboardSrv: dashboardSrv,
datasourceSrv: datasourceSrv,
$q: $q,
};
/*jshint -W054 */
var script_func = new Function('ARGS','kbn','_','moment','window','document','$','jQuery', result.data);
var script_result = script_func($routeParams, kbn, _ , moment, window, document, $, $);
var script_func = new Function('ARGS','kbn','_','moment','window','document','$','jQuery', 'services', result.data);
var script_result = script_func($routeParams, kbn, _ , moment, window, document, $, $, services);
// Handle async dashboard scripts
if (_.isFunction(script_result)) {
var deferred = $q.defer();
script_result(function(dashboard) {
$rootScope.$apply(function() {
$timeout(function() {
deferred.resolve({ data: dashboard });
});
});
@@ -47,7 +54,7 @@ function (angular, $, config, _, kbn, moment) {
.then(execute_script)
.then(null,function(err) {
console.log('Script dashboard error '+ err);
alertSrv.set('Error', "Could not load <i>scripts/"+file+"</i>. Please make sure it exists and returns a valid dashboard", 'error');
$scope.appEvent('alert-error', ["Script Error", "Please make sure it exists and returns a valid dashboard"]);
return false;
});
};
+37 -1
View File
@@ -7,9 +7,22 @@ function (angular, _) {
var module = angular.module('grafana.services');
module.service('alertSrv', function($timeout, $sce) {
module.service('alertSrv', function($timeout, $sce, $rootScope, $modal, $q) {
var self = this;
this.init = function() {
$rootScope.onAppEvent('alert-error', function(e, alert) {
self.set(alert[0], alert[1], 'error');
});
$rootScope.onAppEvent('alert-warning', function(e, alert) {
self.set(alert[0], alert[1], 'warning', 5000);
});
$rootScope.onAppEvent('alert-success', function(e, alert) {
self.set(alert[0], alert[1], 'success', 3000);
});
$rootScope.onAppEvent('confirm-modal', this.showConfirmModal);
};
// List of all alert objects
this.list = [];
@@ -45,5 +58,28 @@ function (angular, _) {
this.clearAll = function() {
self.list = [];
};
this.showConfirmModal = function(e, payload) {
var scope = $rootScope.$new();
scope.title = payload.title;
scope.text = payload.text;
scope.onConfirm = payload.onConfirm;
var confirmModal = $modal({
template: './app/partials/confirm_modal.html',
persist: true,
modalClass: 'confirm-modal',
show: false,
scope: scope,
keyboard: false
});
$q.when(confirmModal).then(function(modalEl) {
modalEl.modal('show');
});
};
});
});
+2 -1
View File
@@ -1,14 +1,15 @@
define([
'./alertSrv',
'./utilSrv',
'./datasourceSrv',
'./timeSrv',
'./templateSrv',
'./templateValuesSrv',
'./panelSrv',
'./timer',
'./panelMove',
'./keyboardManager',
'./annotationsSrv',
'./popoverSrv',
'./playlistSrv',
'./unsavedChangesSrv',
'./dashboard/dashboardKeyBindings',
+1 -1
View File
@@ -58,7 +58,7 @@ define([
function errorHandler(err) {
console.log('Annotation error: ', err);
var message = err.message || "Aannotation query failed";
var message = err.message || "Annotation query failed";
alertSrv.set('Annotations error', message,'error');
}
@@ -1,14 +1,13 @@
define([
'angular',
'jquery',
'services/all'
],
function(angular, $) {
"use strict";
var module = angular.module('grafana.services');
module.service('dashboardKeybindings', function($rootScope, keyboardManager) {
module.service('dashboardKeybindings', function($rootScope, keyboardManager, $modal, $q) {
this.shortcuts = function(scope) {
@@ -18,11 +17,40 @@ function(angular, $) {
keyboardManager.unbind('ctrl+s');
keyboardManager.unbind('ctrl+r');
keyboardManager.unbind('ctrl+z');
keyboardManager.unbind('ctrl+o');
keyboardManager.unbind('esc');
});
var helpModalScope = null;
keyboardManager.bind('shift+?', function() {
if (helpModalScope) { return; }
helpModalScope = $rootScope.$new();
var helpModal = $modal({
template: './app/partials/help_modal.html',
persist: false,
show: false,
scope: helpModalScope,
keyboard: false
});
helpModalScope.$on('$destroy', function() { helpModalScope = null; });
$q.when(helpModal).then(function(modalEl) { modalEl.modal('show'); });
}, { inputDisabled: true });
keyboardManager.bind('ctrl+f', function() {
scope.emitAppEvent('show-dash-editor', { src: 'app/partials/search.html' });
scope.appEvent('show-dash-editor', { src: 'app/partials/search.html' });
}, { inputDisabled: true });
keyboardManager.bind('ctrl+o', function() {
var current = scope.dashboard.sharedCrosshair;
scope.dashboard.sharedCrosshair = !current;
scope.dashboard.emit_refresh('refresh');
}, { inputDisabled: true });
keyboardManager.bind('ctrl+l', function() {
scope.$broadcast('toggle-all-legends');
}, { inputDisabled: true });
keyboardManager.bind('ctrl+h', function() {
@@ -31,7 +59,7 @@ function(angular, $) {
}, { inputDisabled: true });
keyboardManager.bind('ctrl+s', function(evt) {
scope.emitAppEvent('save-dashboard', evt);
scope.appEvent('save-dashboard', evt);
}, { inputDisabled: true });
keyboardManager.bind('ctrl+r', function() {
@@ -39,7 +67,7 @@ function(angular, $) {
}, { inputDisabled: true });
keyboardManager.bind('ctrl+z', function(evt) {
scope.emitAppEvent('zoom-out', evt);
scope.appEvent('zoom-out', evt);
}, { inputDisabled: true });
keyboardManager.bind('esc', function() {
@@ -53,7 +81,7 @@ function(angular, $) {
modalData.$scope.dismiss();
}
scope.emitAppEvent('hide-dash-editor');
scope.appEvent('hide-dash-editor');
scope.exitFullscreen();
}, { inputDisabled: true });
+32 -4
View File
@@ -27,23 +27,31 @@ function (angular, $, kbn, _, moment) {
this.timezone = data.timezone || 'browser';
this.editable = data.editable === false ? false : true;
this.hideControls = data.hideControls || false;
this.sharedCrosshair = data.sharedCrosshair || false;
this.rows = data.rows || [];
this.nav = data.nav || [];
this.time = data.time || { from: 'now-6h', to: 'now' };
this.templating = data.templating || { list: [], enable: false };
this.annotations = data.annotations || { list: [], enable: false};
this.templating = this._ensureListExist(data.templating);
this.annotations = this._ensureListExist(data.annotations);
this.refresh = data.refresh;
this.version = data.version || 0;
this.hideAllLegends = data.hideAllLegends || false;
if (this.nav.length === 0) {
this.nav.push({ type: 'timepicker' });
}
this.updateSchema(data);
this._updateSchema(data);
}
var p = DashboardModel.prototype;
p._ensureListExist = function (data) {
if (!data) { data = {}; }
if (!data.list) { data.list = []; }
return data;
};
p.getNextPanelId = function() {
var i, j, row, panel, max = 0;
for (i = 0; i < this.rows.length; i++) {
@@ -84,6 +92,26 @@ function (angular, $, kbn, _, moment) {
row.panels.push(panel);
};
p.getPanelInfoById = function(panelId) {
var result = {};
_.each(this.rows, function(row) {
_.each(row.panels, function(panel, index) {
if (panel.id === panelId) {
result.panel = panel;
result.row = row;
result.index = index;
return;
}
});
});
if (!result.panel) {
return null;
}
return result;
};
p.duplicatePanel = function(panel, row) {
var rowIndex = _.indexOf(this.rows, row);
var newPanel = angular.copy(panel);
@@ -115,7 +143,7 @@ function (angular, $, kbn, _, moment) {
$rootScope.$broadcast('refresh');
};
p.updateSchema = function(old) {
p._updateSchema = function(old) {
var i, j, k;
var oldVersion = this.version;
var panelUpgrades = [];
@@ -32,33 +32,34 @@ function (angular, _, $) {
});
this.update(this.getQueryStringState(), true);
this.expandRowForPanel();
}
DashboardViewState.prototype.expandRowForPanel = function() {
if (!this.state.panelId) { return; }
var panelInfo = this.$scope.dashboard.getPanelInfoById(this.state.panelId);
if (panelInfo) {
panelInfo.row.collapse = false;
}
};
DashboardViewState.prototype.needsSync = function(urlState) {
return _.isEqual(this.state, urlState) === false;
};
DashboardViewState.prototype.getQueryStringState = function() {
var queryParams = $location.search();
var urlState = {
panelId: parseInt(queryParams.panelId) || null,
fullscreen: queryParams.fullscreen ? true : false,
edit: queryParams.edit ? true : false,
};
_.each(queryParams, function(value, key) {
if (key.indexOf('var-') !== 0) { return; }
urlState[key] = value;
});
return urlState;
var state = $location.search();
state.panelId = parseInt(state.panelId) || null;
state.fullscreen = state.fullscreen ? true : null;
state.edit = (state.edit === "true" || state.edit === true) || null;
return state;
};
DashboardViewState.prototype.serializeToUrl = function() {
var urlState = _.clone(this.state);
urlState.fullscreen = this.state.fullscreen ? true : null,
urlState.edit = this.state.edit ? true : null;
return urlState;
};
@@ -68,7 +69,8 @@ function (angular, _, $) {
if (!this.state.fullscreen) {
this.state.panelId = null;
this.state.edit = false;
this.state.fullscreen = null;
this.state.edit = null;
}
if (!skipUrlSync) {
@@ -224,7 +224,7 @@ function (angular, _, config, kbn, moment) {
var endsInOpen = function(string, opener, closer) {
var character;
var count = 0;
for (var i=0; i<string.length; i++) {
for (var i = 0, len = string.length; i < len; i++) {
character = string[i];
if (character === opener) {
@@ -279,18 +279,20 @@ function (angular, _, config, kbn, moment) {
return { dashboards: [], tags: [] };
}
var hits = { dashboards: [], tags: results.facets.tags.terms || [] };
var resultsHits = results.hits.hits;
var displayHits = { dashboards: [], tags: results.facets.tags.terms || [] };
for (var i = 0; i < results.hits.hits.length; i++) {
hits.dashboards.push({
id: results.hits.hits[i]._id,
title: results.hits.hits[i]._source.title,
tags: results.hits.hits[i]._source.tags
for (var i = 0, len = resultsHits.length; i < len; i++) {
var hit = resultsHits[i];
displayHits.dashboards.push({
id: hit._id,
title: hit._source.title,
tags: hit._source.tags
});
}
hits.tagsOnly = tagsOnly;
return hits;
displayHits.tagsOnly = tagsOnly;
return displayHits;
});
};
+40 -5
View File
@@ -39,6 +39,13 @@ function (_) {
defaultParams: [1],
});
addFuncDef({
name: 'perSecond',
category: categories.Transform,
params: [],
defaultParams: [],
});
addFuncDef({
name: "holtWintersForecast",
category: categories.Calculate,
@@ -93,6 +100,27 @@ function (_) {
category: categories.Combine,
});
addFuncDef({
name: 'mapSeries',
shortName: 'map',
params: [{ name: "node", type: 'int' }],
defaultParams: [3],
category: categories.Combine,
});
addFuncDef({
name: 'reduceSeries',
shortName: 'reduce',
params: [
{ name: "function", type: 'string', options: ['asPercent', 'diffSeries', 'divideSeries'] },
{ name: "reduceNode", type: 'int', options: [0,1,2,3,4,5,6,7,8,9,10,11,12,13] },
{ name: "reduceMatchers", type: 'string' },
{ name: "reduceMatchers", type: 'string' },
],
defaultParams: ['asPercent', 2, 'used_bytes', 'total_bytes'],
category: categories.Combine,
});
addFuncDef({
name: 'sumSeries',
shortName: 'sum',
@@ -148,7 +176,10 @@ function (_) {
addFuncDef({
name: 'averageSeriesWithWildcards',
category: categories.Combine,
params: [{ name: "node", type: "int" }],
params: [
{ name: "node", type: "int" },
{ name: "node", type: "int", optional: true },
],
defaultParams: [3]
});
@@ -193,7 +224,7 @@ function (_) {
{
name: "node",
type: "int",
options: [1,2,3,4,5,6,7,8,9,10,12]
options: [0,1,2,3,4,5,6,7,8,9,10,12]
},
{
name: "function",
@@ -329,8 +360,12 @@ function (_) {
addFuncDef({
name: 'summarize',
category: categories.Transform,
params: [{ name: "interval", type: "string" }, { name: "func", type: "select", options: ['sum', 'avg', 'min', 'max', 'last'] }],
defaultParams: ['1h', 'sum']
params: [
{ name: "interval", type: "string" },
{ name: "func", type: "select", options: ['sum', 'avg', 'min', 'max', 'last'] },
{ name: "alignToFrom", type: "boolean", optional: true, options: ['false', 'true'] },
],
defaultParams: ['1h', 'sum', 'false']
});
addFuncDef({
@@ -533,7 +568,7 @@ function (_) {
var parameters = _.map(this.params, function(value, index) {
var paramType = this.def.params[index].type;
if (paramType === 'int' || paramType === 'value_or_series') {
if (paramType === 'int' || paramType === 'value_or_series' || paramType === 'boolean') {
return value;
}
@@ -53,13 +53,24 @@ function (angular, _, $, config, kbn, moment) {
httpOptions.headers = { 'Content-Type': 'application/x-www-form-urlencoded' };
}
return this.doGraphiteRequest(httpOptions);
return this.doGraphiteRequest(httpOptions).then(this.convertDataPointsToMs);
}
catch(err) {
return $q.reject(err);
}
};
GraphiteDatasource.prototype.convertDataPointsToMs = function(result) {
if (!result || !result.data) { return []; }
for (var i = 0; i < result.data.length; i++) {
var series = result.data[i];
for (var y = 0; y < series.datapoints.length; y++) {
series.datapoints[y][1] *= 1000;
}
}
return result;
};
GraphiteDatasource.prototype.annotationQuery = function(annotation, rangeUnparsed) {
// Graphite metric as annotation
if (annotation.target) {
@@ -84,7 +95,7 @@ function (angular, _, $, config, kbn, moment) {
list.push({
annotation: annotation,
time: datapoint[1] * 1000,
time: datapoint[1],
title: target.target
});
}
@@ -18,7 +18,7 @@ function () {
var query = 'select ';
var seriesName = target.series;
if(!seriesName.match('^/.*/')) {
if(!seriesName.match('^/.*/') && !seriesName.match(/^merge\(.*\)/)) {
seriesName = '"' + seriesName+ '"';
}
+1 -1
View File
@@ -88,7 +88,7 @@ function (_) {
_.each(series.points, function (point) {
var data = {
annotation: self.annotation,
time: point[timeCol] * 1000,
time: point[timeCol],
title: point[titleCol],
tags: point[tagsCol],
text: point[textCol]
@@ -44,7 +44,7 @@ function (angular, _, kbn, InfluxSeries, InfluxQueryBuilder) {
// replace grafana variables
query = query.replace('$timeFilter', timeFilter);
query = query.replace('$interval', (target.interval || options.interval));
query = query.replace(/\$interval/g, (target.interval || options.interval));
// replace templated variables
query = templateSrv.replace(query);
@@ -85,8 +85,13 @@ function (angular, _, kbn, InfluxSeries, InfluxQueryBuilder) {
});
};
InfluxDatasource.prototype.listSeries = function() {
return this._seriesQuery('list series').then(function(data) {
InfluxDatasource.prototype.listSeries = function(query) {
// wrap in regex
if (query && query.length > 0 && query[0] !== '/') {
query = '/' + query + '/';
}
return this._seriesQuery('list series ' + query).then(function(data) {
if (!data || data.length === 0) {
return [];
}
@@ -141,7 +146,6 @@ function (angular, _, kbn, InfluxSeries, InfluxQueryBuilder) {
InfluxDatasource.prototype._seriesQuery = function(query) {
return this._influxRequest('GET', '/series', {
q: query,
time_precision: 's',
});
};
+5 -1
View File
@@ -61,6 +61,7 @@ function (angular) {
else if (e.which) {
code = e.which;
}
var character = String.fromCharCode(code).toLowerCase();
if (code === 188) {
@@ -93,6 +94,9 @@ function (angular) {
",": "<",
".": ">",
"/": "?",
"»": "?",
"«": "?",
"¿": "?",
"\\": "|"
};
// Special Keys - and their codes
@@ -277,4 +281,4 @@ function (angular) {
return keyboardManagerService;
}]);
});
});
@@ -1,14 +1,15 @@
define([
'angular',
'lodash',
'kbn'
'kbn',
'moment'
],
function (angular, _, kbn) {
'use strict';
var module = angular.module('grafana.services');
module.factory('OpenTSDBDatasource', function($q, $http) {
module.factory('OpenTSDBDatasource', function($q, $http, templateSrv) {
function OpenTSDBDatasource(datasource) {
this.type = 'opentsdb';
@@ -99,7 +100,7 @@ function (angular, _, kbn) {
// TSDB returns datapoints has a hash of ts => value.
// Can't use _.pairs(invert()) because it stringifies keys/values
_.each(md.dps, function (v, k) {
dps.push([v, k]);
dps.push([v, k * 1000]);
});
return { target: metricLabel, datapoints: dps };
@@ -123,12 +124,12 @@ function (angular, _, kbn) {
}
var query = {
metric: target.metric,
metric: templateSrv.replace(target.metric),
aggregator: "avg"
};
if (target.aggregator) {
query.aggregator = target.aggregator;
query.aggregator = templateSrv.replace(target.aggregator);
}
if (target.shouldComputeRate) {
@@ -136,6 +137,14 @@ function (angular, _, kbn) {
query.rateOptions = {
counter: !!target.isCounter
};
if (target.counterMax && target.counterMax.length) {
query.rateOptions.counterMax = parseInt(target.counterMax);
}
if (target.counterResetValue && target.counterResetValue.length) {
query.rateOptions.resetValue = parseInt(target.counterResetValue);
}
}
if (target.shouldDownsample) {
@@ -143,6 +152,11 @@ function (angular, _, kbn) {
}
query.tags = angular.copy(target.tags);
if(query.tags){
for(var key in query.tags){
query.tags[key] = templateSrv.replace(query.tags[key]);
}
}
return query;
}
-85
View File
@@ -1,85 +0,0 @@
define([
'angular',
'lodash'
],
function (angular, _) {
'use strict';
var module = angular.module('grafana.services');
module.service('panelMoveSrv', function($rootScope) {
function PanelMoveSrv(dashboard) {
this.dashboard = dashboard;
_.bindAll(this, 'onStart', 'onOver', 'onOut', 'onDrop', 'onStop', 'cleanup');
}
var p = PanelMoveSrv.prototype;
/* each of these can take event,ui,data parameters */
p.onStart = function() {
this.dashboard.$$panelDragging = true;
$rootScope.$apply();
};
p.onOver = function() {
$rootScope.$apply();
};
p.onOut = function() {
$rootScope.$apply();
};
/*
Use our own drop logic. the $parent.$parent this is ugly.
*/
p.onDrop = function(event,ui,data) {
var
dragRow = data.draggableScope.$parent.$parent.row.panels,
dropRow = data.droppableScope.$parent.$parent.row.panels,
dragIndex = data.dragSettings.index,
dropIndex = data.dropSettings.index;
// Remove panel from source row
dragRow.splice(dragIndex,1);
// Add to destination row
if (!_.isUndefined(dropRow)) {
dropRow.splice(dropIndex,0,data.dragItem);
}
this.dashboard.$$panelDragging = false;
// Cleanup nulls/undefined left behind
this.cleanup();
$rootScope.$apply();
$rootScope.$broadcast('render');
};
p.onStop = function() {
this.dashboard.$$panelDragging = false;
this.cleanup();
$rootScope.$apply();
};
p.cleanup = function () {
_.each(this.dashboard.rows, function(row) {
row.panels = _.without(row.panels,{});
row.panels = _.compact(row.panels);
});
};
return {
init: function(dashboard, scope) {
var panelMove = new PanelMoveSrv(dashboard);
scope.panelMoveDrop = panelMove.onDrop;
scope.panelMoveStart = panelMove.onStart;
scope.panelMoveStop = panelMove.onStop;
scope.panelMoveOver = panelMove.onOver;
scope.panelMoveOut = panelMove.onOut;
}
};
});
});
+30 -60
View File
@@ -10,70 +10,35 @@ function (angular, _) {
this.init = function($scope) {
if (!$scope.panel.span) { $scope.panel.span = 12; }
if (!$scope.panel.title) { $scope.panel.title = 'No title'; }
var menu = [
{
text: 'Edit',
configModal: "app/partials/paneleditor.html",
condition: !$scope.panelMeta.fullscreenEdit
},
{
text: 'Edit',
click: "toggleFullscreen(true)",
condition: $scope.panelMeta.fullscreenEdit
},
{
text: "Fullscreen",
click: 'toggleFullscreen(false)',
condition: $scope.panelMeta.fullscreenView
},
{
text: 'Duplicate',
click: 'duplicatePanel(panel)',
condition: true
},
{
text: 'Span',
submenu: [
{ text: '1', click: 'updateColumnSpan(1)' },
{ text: '2', click: 'updateColumnSpan(2)' },
{ text: '3', click: 'updateColumnSpan(3)' },
{ text: '4', click: 'updateColumnSpan(4)' },
{ text: '5', click: 'updateColumnSpan(5)' },
{ text: '6', click: 'updateColumnSpan(6)' },
{ text: '7', click: 'updateColumnSpan(7)' },
{ text: '8', click: 'updateColumnSpan(8)' },
{ text: '9', click: 'updateColumnSpan(9)' },
{ text: '10', click: 'updateColumnSpan(10)' },
{ text: '11', click: 'updateColumnSpan(11)' },
{ text: '12', click: 'updateColumnSpan(12)' },
],
condition: true
},
{
text: 'Advanced',
submenu: [
{ text: 'Panel JSON', click: 'editPanelJson()' },
],
condition: true
},
{
text: 'Remove',
click: 'remove_panel_from_row(row, panel)',
condition: true
}
];
$scope.inspector = {};
$scope.panelMeta.menu = _.where(menu, { condition: true });
$scope.editPanel = function() {
if ($scope.panelMeta.fullscreen) {
$scope.toggleFullscreen(true);
}
else {
$scope.appEvent('show-dash-editor', { src: 'app/partials/paneleditor.html', scope: $scope });
}
};
$scope.sharePanel = function() {
$scope.appEvent('show-modal', {
src: './app/partials/share-panel.html',
scope: $scope.$new()
});
};
$scope.editPanelJson = function() {
$scope.emitAppEvent('show-json-editor', { object: $scope.panel, updateHandler: $scope.replacePanel });
$scope.appEvent('show-json-editor', { object: $scope.panel, updateHandler: $scope.replacePanel });
};
$scope.duplicatePanel = function() {
$scope.dashboard.duplicatePanel($scope.panel, $scope.row);
};
$scope.updateColumnSpan = function(span) {
$scope.panel.span = span;
$scope.panel.span = Math.min(Math.max($scope.panel.span + span, 1), 12);
$timeout(function() {
$scope.$emit('render');
@@ -104,6 +69,14 @@ function (angular, _) {
$scope.get_data();
};
$scope.toggleEditorHelp = function(index) {
if ($scope.editorHelpIndex === index) {
$scope.editorHelpIndex = null;
return;
}
$scope.editorHelpIndex = index;
};
$scope.toggleFullscreen = function(edit) {
$scope.dashboardViewState.update({ fullscreen: true, edit: edit, panelId: $scope.panel.id });
};
@@ -115,9 +88,6 @@ function (angular, _) {
// Post init phase
$scope.fullscreen = false;
$scope.editor = { index: 1 };
if ($scope.panelMeta.fullEditorTabs) {
$scope.editorTabs = _.pluck($scope.panelMeta.fullEditorTabs, 'title');
}
$scope.datasources = datasourceSrv.getMetricSources();
$scope.setDatasource($scope.panel.datasource);
+46
View File
@@ -0,0 +1,46 @@
define([
'angular',
'lodash',
],
function (angular, _) {
'use strict';
var module = angular.module('grafana.services');
module.service('popoverSrv', function($templateCache, $timeout, $q, $http, $compile) {
this.getTemplate = function(url) {
return $q.when($templateCache.get(url) || $http.get(url, {cache: true}));
};
this.show = function(options) {
var popover = options.element.data('popover');
if (popover) {
popover.scope.$destroy();
popover.destroy();
return;
}
this.getTemplate(options.templateUrl).then(function(result) {
var template = _.isString(result) ? result : result.data;
options.element.popover({
content: template,
placement: 'bottom',
html: true
});
popover = options.element.data('popover');
popover.hasContent = function () {
return template;
};
popover.toggle();
popover.scope = options.scope;
$compile(popover.$tip)(popover.scope);
});
};
});
});
+1
View File
@@ -30,6 +30,7 @@ function (angular, _, kbn) {
var option = _.findWhere(variable.options, { text: urlValue });
option = option || { text: urlValue, value: urlValue };
this.setVariableValue(variable, option, true);
this.updateAutoInterval(variable);
}
else if (variable.refresh) {
this.updateOptions(variable);
+13 -1
View File
@@ -91,10 +91,22 @@ define([
this.old_refresh = null;
}
$rootScope.emitAppEvent('time-range-changed', this.time);
$rootScope.appEvent('time-range-changed', this.time);
$timeout(this.refreshDashboard, 0);
};
this.timeRangeForUrl = function() {
var range = this.timeRange(false);
if (_.isString(range.to) && range.to.indexOf('now')) {
range = this.timeRange();
}
if (_.isDate(range.from)) { range.from = range.from.getTime(); }
if (_.isDate(range.to)) { range.to = range.to.getTime(); }
return range;
};
this.timeRange = function(parse) {
var _t = this.time;
if(_.isUndefined(_t) || _.isUndefined(_t.from)) {
+31
View File
@@ -0,0 +1,31 @@
define([
'angular',
],
function (angular) {
'use strict';
var module = angular.module('grafana.services');
module.service('utilSrv', function($rootScope, $modal, $q) {
this.init = function() {
$rootScope.onAppEvent('show-modal', this.showModal);
};
this.showModal = function(e, options) {
var modal = $modal({
template: options.src,
persist: false,
show: false,
scope: options.scope,
keyboard: false
});
$q.when(modal).then(function(modalEl) {
modalEl.modal('show');
});
};
});
});
+96 -94
View File
@@ -2,108 +2,110 @@
// config.js is where you will find the core Grafana configuration. This file contains parameter that
// must be set before Grafana is run for the first time.
define(['settings'],
function (Settings) {
define(['settings'], function(Settings) {
"use strict";
return new Settings({
/* Data sources
* ========================================================
* Datasources are used to fetch metrics, annotations, and serve as dashboard storage
* - You can have multiple of the same type.
* - grafanaDB: true marks it for use for dashboard storage
* - default: true marks the datasource as the default metric source (if you have multiple)
* - basic authentication: use url syntax http://username:password@domain:port
*/
/* Data sources
* ========================================================
* Datasources are used to fetch metrics, annotations, and serve as dashboard storage
* - You can have multiple of the same type.
* - grafanaDB: true marks it for use for dashboard storage
* - default: true marks the datasource as the default metric source (if you have multiple)
* - basic authentication: use url syntax http://username:password@domain:port
*/
// InfluxDB example setup (the InfluxDB databases specified need to exist)
/*
datasources: {
influxdb: {
type: 'influxdb',
url: "http://my_influxdb_server:8086/db/database_name",
username: 'admin',
password: 'admin',
// InfluxDB example setup (the InfluxDB databases specified need to exist)
/*
datasources: {
influxdb: {
type: 'influxdb',
url: "http://my_influxdb_server:8086/db/database_name",
username: 'admin',
password: 'admin',
},
grafana: {
type: 'influxdb',
url: "http://my_influxdb_server:8086/db/grafana",
username: 'admin',
password: 'admin',
grafanaDB: true
},
},
grafana: {
type: 'influxdb',
url: "http://my_influxdb_server:8086/db/grafana",
username: 'admin',
password: 'admin',
grafanaDB: true
},
},
*/
*/
// Graphite & Elasticsearch example setup
/*
datasources: {
graphite: {
type: 'graphite',
url: "http://my.graphite.server.com:8080",
// Graphite & Elasticsearch example setup
/*
datasources: {
graphite: {
type: 'graphite',
url: "http://my.graphite.server.com:8080",
},
elasticsearch: {
type: 'elasticsearch',
url: "http://my.elastic.server.com:9200",
index: 'grafana-dash',
grafanaDB: true,
}
},
elasticsearch: {
type: 'elasticsearch',
url: "http://my.elastic.server.com:9200",
index: 'grafana-dash',
grafanaDB: true,
*/
// OpenTSDB & Elasticsearch example setup
/*
datasources: {
opentsdb: {
type: 'opentsdb',
url: "http://opentsdb.server:4242",
},
elasticsearch: {
type: 'elasticsearch',
url: "http://my.elastic.server.com:9200",
index: 'grafana-dash',
grafanaDB: true,
}
},
*/
/* Global configuration options
* ========================================================
*/
// specify the limit for dashboard search results
search: {
max_results: 100
},
// default home dashboard
default_route: '/dashboard/file/default.json',
// set to false to disable unsaved changes warning
unsaved_changes_warning: true,
// set the default timespan for the playlist feature
// Example: "1m", "1h"
playlist_timespan: "1m",
// If you want to specify password before saving, please specify it below
// The purpose of this password is not security, but to stop some users from accidentally changing dashboards
admin: {
password: ''
},
// Change window title prefix from 'Grafana - <dashboard title>'
window_title_prefix: 'Grafana - ',
// Add your own custom panels
plugins: {
// list of plugin panels
panels: [],
// requirejs modules in plugins folder that should be loaded
// for example custom datasources
dependencies: [],
}
},
*/
// OpenTSDB & Elasticsearch example setup
/*
datasources: {
opentsdb: {
type: 'opentsdb',
url: "http://opentsdb.server:4242",
},
elasticsearch: {
type: 'elasticsearch',
url: "http://my.elastic.server.com:9200",
index: 'grafana-dash',
grafanaDB: true,
}
},
*/
/* Global configuration options
* ========================================================
*/
// specify the limit for dashboard search results
search: {
max_results: 20
},
// default home dashboard
default_route: '/dashboard/file/default.json',
// set to false to disable unsaved changes warning
unsaved_changes_warning: true,
// set the default timespan for the playlist feature
// Example: "1m", "1h"
playlist_timespan: "1m",
// If you want to specify password before saving, please specify it bellow
// The purpose of this password is not security, but to stop some users from accidentally changing dashboards
admin: {
password: ''
},
// Change window title prefix from 'Grafana - <dashboard title>'
window_title_prefix: 'Grafana - ',
// Add your own custom panels
plugins: {
// list of plugin panels
panels: [],
// requirejs modules in plugins folder that should be loaded
// for example custom datasources
dependencies: [],
}
});
});
});
+1 -1
View File
@@ -57,7 +57,7 @@ hr {
.brand {
padding: 0px 15px;
color: @grayLighter;
color: @navbarBrandColor;
font-weight: normal;
text-shadow: none;
}
+28
View File
@@ -0,0 +1,28 @@
input[type=text].input-fluid {
width: 100%;
box-sizing: border-box;
padding: 14px;
-moz-box-sizing: border-box;
height: 100%;
}
input[type="checkbox"].cr1 {
display: none;
}
input[type="checkbox"]+.cr1 {
display: inline-block;
height: 19px;
clear: none;
text-indent: 2px;
margin-top: 4px;
padding: 0 0 0 20px;
vertical-align:middle;
background: url(@checkboxImageUrl) left top no-repeat;
cursor:pointer;
}
input[type="checkbox"]:checked+label {
background: url(@checkboxImageUrl) 0px -18px no-repeat;
}

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