Compare commits

..

738 Commits

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

Besides that, the goal of this commit was to rework the
filterSrv singleton into a factory, so we move the filterSrv
instance around via the scope. This should be a better solution
than the current situation, because services shouldn't contain
model data - the scope should.
This will eventually straighten out control flow between dashboard,
filters and so on, and allow us to leverage angularJS mechanics more.
The latter has already started, since I could rework a bit of the
existing event infrastructure to watches on times.
2014-05-19 15:31:30 +02:00
Harald Kraemer
f70bf61ff6 Added vim swp files to gitignore 2014-05-15 15:07:03 +02:00
Harald Kraemer
4a362704fd Moved dashboard keybinding setup to own service.
The goal is to split up the situation between the dashboard
controller and the current dashboard service. I want to be
able to use the routing in order to select various dashboard
controllers, so I can extend the current scripting
mechanism by implementing new dashboard controllers.

However, to do this in a non-insane way, I need to move as
much functionality as possible into services, so the individual
controllers just need to throw the right set of services
together and add a bit of loading logic.
2014-05-15 15:04:23 +02:00
Harald Kraemer
2c43fdc409 Merge branch 'master' of github.com:Tetha/grafana 2014-05-15 13:17:03 +02:00
Torkel Ödegaard
2a6a3a3af5 updated changelog 2014-05-13 20:20:28 +02:00
Torkel Ödegaard
d8e2192179 Updated version to 1.5.4 2014-05-13 19:08:46 +02:00
Torkel Ödegaard
cfc5fa87d0 Merge branch 'gridmin' of github.com:kamaradclimber/grafana into kamaradclimber-gridmin 2014-05-10 01:56:27 +02:00
Torkel Ödegaard
5ad95a1c3e Merge branch 'influxdb' of github.com:pmenglund/grafana into pmenglund-influxdb 2014-05-10 01:41:25 +02:00
Torkel Ödegaard
de8d456089 Merge branch 'master' of github.com:torkelo/grafana 2014-05-10 01:40:23 +02:00
Torkel Ödegaard
07610a5f92 small change to influxdb template to support older versions of influxdb 2014-05-10 01:40:08 +02:00
felixbarnsteiner
5d6998cf0b url decode metric names #327 2014-05-09 11:18:18 +02:00
Torkel Ödegaard
21b96068e4 Merge pull request #382 from ktoso/nanos-formatting
Added nanosecond y-scale formatting
2014-05-06 09:50:34 -07:00
Stefan Majer
c9df61c43c Quote timeseries names to be able to have special character such as @ in a seriename. 2014-05-06 08:42:49 +02:00
Stefan Majer
c1266abb98 Quote timeseries names to be able to have special character such as @ in a seriename. 2014-05-06 08:38:09 +02:00
Martin Englund
9dfb03aa0f use the influxdb built in time precision 2014-05-05 22:45:15 -07:00
Gregoire Seux
3a35dee0e3 Default to auto scaling for grid min setting
fixes #226
2014-05-05 09:33:45 +02:00
Torkel Ödegaard
f4568ef589 trying to get more unit tests working 2014-05-03 17:00:48 +02:00
Konrad 'ktoso' Malawski
f80871ce7e Added nanosecond y-scale formatting 2014-05-03 16:26:28 +02:00
Torkel Ödegaard
8fe4422d21 updated readme.md with new grafana organization urls 2014-05-01 15:00:56 +02:00
Torkel Ödegaard
c19fafa581 Improvement to series toggling, CTRL+MouseClick on series name will now hide all others (Closes #350) 2014-05-01 11:18:23 +02:00
Marco Vito Moscaritolo
cc43f15eab InfluxDB Filters: added filter to queries. 2014-05-01 00:44:42 +02:00
Marco Vito Moscaritolo
bfecb04697 InfluxDB Filters: added function to generate filter list. 2014-05-01 00:43:34 +02:00
Harald Kraemer
52e1f5273c Extracted multi-graphite query function, fixed loadRecursive
The new function in metricKeys generalizes what my loadAll
from the previous commit did, it takes a request and a
callback. The request is executed on each graphite installation
and the callback is executed for each result. This makes
implementing loadAll and loadRecursive almost as simple as
it was.
2014-04-30 13:13:51 +02:00
Torkel Ödegaard
2b5e974132 updated changelog with PR #369 2014-04-30 10:37:50 +02:00
Torkel Ödegaard
e3d7e3c195 Merge pull request #369 from jbripley/influxdb-columnname-escape
Change InfluxDB SQL generation template to wrap column names in quoutes
2014-04-30 10:33:47 +02:00
Joakim Bodin
11446b3e53 Change InfluxDB SQL generation template to wrap column names in quoutes
Since column names can contain dots or dashes, which will give syntax errors from InfluxDB API, unless wrapped in quoutes
2014-04-30 10:22:22 +02:00
Harald Kraemer
637c720de5 Modified metricKeys to handle multiple graphite sources.
This wasn't all too hard to change, I mostly changed the
single http get to multiple http gets, one for each data
source of type graphite.

It's still not a good idea to call this on the web
frontend, since the whole thing was working for about
20 - 30 minutes when I clicked it, so I'm not
committing my changes to settings.js or the view itself.
2014-04-30 09:40:06 +02:00
Torkel Ödegaard
0dae8f9977 removed unused function 'endsWithWildcard', fixes jshint error 2014-04-28 20:08:25 +02:00
Torkel Ödegaard
9c5674a076 Fixes to filters and the 'All' option. It now never uses '*' as value, but all options in a {node1, node2, node3} expression (Closes #228, Closes #359) 2014-04-28 19:58:25 +02:00
Torkel Ödegaard
35368442ec Merge pull request #356 from Tetha/master
Fixed an exception when adding a graphite function.
2014-04-28 12:09:49 +02:00
Torkel Ödegaard
035b5163fb Merge branch 'unsaved_changes_warning' (Closes #324) 2014-04-27 12:28:57 +02:00
Torkel Ödegaard
a1d5e26f6b Added onbeforeunload check to unsaved changes warning feature 2014-04-27 12:27:06 +02:00
Torkel Ödegaard
bdd2c9d033 More work on unsaved changes warning (Issue #324) 2014-04-27 12:22:38 +02:00
Torkel Ödegaard
8ad00faebc small progress on unsaved changes warning 2014-04-25 16:50:35 +02:00
Harald Kraemer
061bfffd4d Fixed an exception when adding a graphite function.
The error was that funcDef is just a string parsed from an event
like onMouseDown or something like that. This event is turned
into a function instance, and the function instance has
expected parameters and so forth. However, the number of
parameters was actually checked on the funcDef.
2014-04-25 16:48:17 +02:00
Torkel Ödegaard
bfb2376aa3 working on unsaved changes warning 2014-04-25 15:23:17 +02:00
Torkel Ödegaard
9e4656e43f New config setting for graphite datasource to control if json render request is POST or GET (Closes #345) 2014-04-24 17:58:34 +02:00
Torkel Ödegaard
c26926148c began work on unsaved changes warning when changing dashboard, Issue #324 2014-04-23 21:16:53 +02:00
Torkel Ödegaard
827e1846a8 Closes #333, New version availability check will now checks http://grafanarel.s3.amazonaws.com/latest.json if there is a new version. And the link now points to http://grafana.org/download 2014-04-23 09:05:10 +02:00
Torkel Ödegaard
2f58aa280c Review and small refactoring of PR #331 2014-04-22 13:08:14 +02:00
Torkel Ödegaard
66db99faf8 Merge branch 'feature/influxdb-add-where-conditions' of github.com:mavimo/grafana into mavimo-feature/influxdb-add-where-conditions 2014-04-22 12:38:15 +02:00
Torkel Ödegaard
0efafc50dc Increased resolution for graphite datapoints (maxDataPoints), now equal to panel pixel width. (Closes #5) 2014-04-22 12:01:51 +02:00
Marco Vito Moscaritolo
595aab4edf removed unrequired variable (error on merge from master) 2014-04-21 18:14:30 +02:00
Marco Vito Moscaritolo
02696c8bca better variable naming 2014-04-21 18:13:05 +02:00
Marco Vito Moscaritolo
01f3d728ef Added labels per series management 2014-04-21 18:07:55 +02:00
Marco Vito Moscaritolo
bba76cac4d Added conditions management 2014-04-21 18:07:24 +02:00
Marco Vito Moscaritolo
72ab721f22 Added where condiction operators 2014-04-21 18:06:13 +02:00
Marco Vito Moscaritolo
f98db943a5 Merge branch 'master' into feature/influxdb-add-where-conditions
Conflicts:
	src/app/partials/influxdb/editor.html
	src/app/services/influxdb/influxdbDatasource.js
2014-04-21 16:32:05 +02:00
Marco Vito Moscaritolo
f122da58c1 added support to coustom where conditions 2014-04-21 16:20:08 +02:00
Torkel Ödegaard
125db1777a influxdb editor: rawquery text input, changed from fixed pixel width to span10 class 2014-04-20 14:17:25 +02:00
Torkel Ödegaard
c592db4024 Merge branch 'influxdb-updates' of github.com:influxdb/grafana into influxdb-influxdb-updates 2014-04-20 13:59:31 +02:00
Torkel Ödegaard
02af2dbe73 Added rounding for graphites from and to time range filters
for very short absolute ranges (Fixes #320)
2014-04-18 16:06:55 +02:00
Torkel Ödegaard
b1bd04566e updated changelog 2014-04-17 22:36:12 +02:00
Torkel Odegaard
15288b0e84 version bump to 1.5.3 2014-04-17 22:27:19 +02:00
Torkel Ödegaard
dfaa529fa3 updated readme 2014-04-17 14:33:09 +02:00
Torkel Ödegaard
ef54bb1b7c Merge pull request #323 from magicrobotmonkey/slideshow_leak
unbind resize event during slideshow
2014-04-17 09:52:48 +02:00
Todd Persen
b3e459863e Add support for multiple hosts (with retries) and raw queries. 2014-04-16 09:54:25 -04:00
Aaron Bassett
ada56ea8ce unbind resize event during slideshow 2014-04-16 09:52:29 -04:00
Torkel Ödegaard
8ca589cdf5 Support for async scripted dashboards, and accesss to jquery, window & document, Closes #274) 2014-04-16 14:57:35 +02:00
Torkel Odegaard
390c4eed7e Revert "Merge pull request #310 from roidelapluie/master"
This reverts commit 9fc93e285c, reversing
changes made to 30bf838065.
2014-04-15 19:56:35 +02:00
Torkel Ödegaard
9fc93e285c Merge pull request #310 from roidelapluie/master
Reload filters when loading page
2014-04-15 19:50:46 +02:00
Julien Pivotto
1f417f0606 Reload filters when loading page
This commit refreshes the dynamic filters when loading page.
2014-04-14 14:47:39 +02:00
Torkel Ödegaard
30bf838065 added smartSummarize graphite function definition 2014-04-14 08:17:42 +02:00
Torkel Ödegaard
0019101f0e clarified timezoneOffset setting 2014-04-13 15:01:06 +02:00
Torkel Ödegaard
75b2e383c2 Merge branch 'auto_tz' of github.com:magicrobotmonkey/grafana into magicrobotmonkey-auto_tz 2014-04-13 13:45:08 +02:00
Torkel Ödegaard
b5830e0cde Merge pull request #307 from awilliams/patch-1
Fixes documentation typo
2014-04-11 16:33:35 +02:00
Adam Williams
e9dcf0e00b Fixes documentation typo 2014-04-11 15:22:20 +02:00
Torkel Ödegaard
ccd5c01d91 Merge pull request #306 from cruatta/master
Grafana needs hideLegend=false in graphite urls when show legend is selected for Graphite PNG
2014-04-11 11:25:30 +02:00
Cameron Ruatta
2ea3f7a558 Changing behavior to always obey the show legend option in grafana. without hideLegend=false, grafana defaults to the graphite behavior of hiding the legend at (default 10) X number of series. Also should be checking scope.panel.legend.show. 2014-04-10 14:10:29 -07:00
Torkel Ödegaard
993a2daccc fixed failing unit test, added 2 more for setTime & auto refresh behavior 2014-04-10 12:44:19 +02:00
Torkel Ödegaard
541baac49e Closes #282, Disable auto refresh zooming in (every time you change to an absolute time range), refresh will be restored when you change time range back to relative 2014-04-10 12:28:06 +02:00
Torkel Ödegaard
d69b04e9ed Closes #294, Browser page title is now Grafana - {{dashboard title}} 2014-04-10 12:12:15 +02:00
Torkel Ödegaard
78fab41310 Merge pull request #303 from jaimegago/master
Update README.md
2014-04-10 08:58:36 +02:00
Jaime Gago
4502ed068d Update README.md
Caveat about using "*" in CORS
2014-04-09 12:56:31 -07:00
Torkel Ödegaard
5495e0f96a Fixes #302, year dates in changelog was 2013, now corrected to 2014, thx LordFPL for spotting this 2014-04-09 12:36:35 +02:00
Torkel Ödegaard
60a8fe31f1 Fixes #297, filter remove fix 2014-04-08 20:51:26 +02:00
Torkel Ödegaard
93ab2a93d4 updated changelog 2014-04-08 20:51:26 +02:00
Aaron Bassett
688c21cd94 added little workraround for graphites timezones/DST failures 2014-04-08 09:56:00 -04:00
Torkel Ödegaard
39e19fdfba Merge pull request #295 from jaimegago/master
Update dashLoader.js
2014-04-07 14:21:40 -04:00
Jaime Gago
5525ccc409 Update dashLoader.js
Remove "Kibana" left overs in dashboard alerts
2014-04-07 11:13:18 -07:00
Torkel Ödegaard
db9949d403 Fixes #291, row height correction bug fixed 2014-04-07 08:33:52 +02:00
Torkel Ödegaard
f36d91336a Fixes #289, PanelBaseCtrl needs to specify inject dependency annotations as it doesnt follow regular angular controller syntax, ngmin grunt task misses it 2014-04-06 12:44:00 +02:00
Torkel Ödegaard
da13de6af5 fixed failing unit test 2014-04-06 11:07:22 +02:00
Torkel Ödegaard
4029dc3165 Added jshint file for unit tests, and grunt task to verify test syntax. 2014-04-06 10:47:14 +02:00
Torkel Ödegaard
6795383461 Fixes #106. Css fix for Firefox and using top menu dropdowns in panel fullscren / edit mode 2014-04-06 09:53:07 +02:00
Torkel Ödegaard
305d5c5aa9 Fixes #189, correction to ms axis formater, now formats days correctly 2014-04-06 09:17:43 +02:00
Torkel Ödegaard
49492e7bca Removed panel title hide feature I added yesterday, could not get it right right now. 2014-04-06 09:17:43 +02:00
Torkel Odegaard
bd0e96f166 small fix: (lowercase) file name tp panelBaseCtrl 2014-04-05 21:37:03 +02:00
Torkel Ödegaard
2e7b145a81 css fix for legend color selector popover 2014-04-05 19:29:14 +02:00
Torkel Ödegaard
6f114b17d6 Closes ##278, improvements in row height matching, refactorings of base kibana panel features into PanelBaseCtrl 2014-04-05 19:22:24 +02:00
Torkel Ödegaard
5cd73cea4b small code cleanup in grafanaGraph 2014-04-05 19:22:23 +02:00
Torkel Ödegaard
347f6aed3f small code cleanup in grafanaGraph 2014-04-05 19:22:23 +02:00
Torkel Ödegaard
24ea23ce83 Merge pull request #283 from ragzilla/master
use influxdb aliases to distinguish between multiple columns
2014-04-05 10:15:25 -04:00
Torkel Ödegaard
5cb4cfcdd2 Closes #265, Graphite errors are now easier to troubleshoot with the new inspector! 2014-04-05 16:04:49 +02:00
Torkel Ödegaard
58ef61a0bb working on inspector 2014-04-05 13:26:48 +02:00
Matt Addison
58a494f33f fix for jslint maxlen 2014-04-04 13:58:23 -04:00
Matt Addison
c97213f177 use influxdb aliases to distinguish between multiple columns in a single series 2014-04-03 22:49:10 -04:00
Torkel Ödegaard
a9abd2ff5c began work in inspector feature for easy troubleshooting, Issue #265 2014-04-03 22:22:38 +02:00
Torkel Ödegaard
8cba594070 Merge branch 'master' of github.com:nikicat/grafana into nikicat-master 2014-04-03 12:45:08 +02:00
Torkel Ödegaard
f2c9cc17df #Closes #269, changed byte and bit formats to conform with IEC standard symbols, like Kib, Mib for bits, and KiB, MiB, GiB for bytes 2014-04-03 12:42:50 +02:00
Torkel Ödegaard
1a34c2eee3 Merge pull request #280 from syepes/add_functions_v2
Add rangeOfSeries,sortByTotal,removeAbove*,removeBelow*
2014-04-03 02:48:11 -04:00
Nikolay Bryskin
a1632e4874 identation fixed 2014-04-03 02:38:01 +04:00
Sebastian YEPES
9534fccebf Add rangeOfSeries,sortByTotal,removeAbove*,removeBelow* 2014-04-03 00:37:02 +02:00
Nikolay Bryskin
ae9d9ea284 import from graphite: set correct datasource for panel 2014-04-03 02:11:58 +04:00
Nikolay Bryskin
8ea4351797 import from non-default dashboard support 2014-04-03 01:35:49 +04:00
Torkel Odegaard
34a18514b6 Fixes #271, renamed Export schema to Export dashboard 2014-04-01 21:42:05 +02:00
Torkel Ödegaard
7248358ac1 Merge pull request #268 from ImmobilienScout24/feature/linebreak_in_annotations_tooltip
replace linebreaks with <br/> in data section of tooltip.
2014-04-01 09:18:28 -04:00
Jan Gaedicke
9e1e7be574 replace linebreaks with <br/> in data section of tooltip. 2014-04-01 15:14:15 +02:00
Torkel Ödegaard
18e0a60b9e Fixes 267, Functions without params (like integral) now causes graph to update correctly 2014-04-01 12:39:05 +02:00
Torkel Ödegaard
c727863616 Merge pull request #255 from bobrik/patch-1
Fixed installation instructions for config.js
2014-03-31 12:40:46 -04:00
Torkel Ödegaard
75fc0c7017 Merge pull request #256 from bobrik/undefined-length-fix
Fixed annoying undefined length error
2014-03-31 12:35:38 -04:00
Ian Babrou
e3b046aecf fixed annoying undefined length error 2014-03-31 18:53:43 +04:00
Ian Babrou
046f80b9f2 Fixed installation instructions for config.js
Because you cannot edit the file which does not even exist.
2014-03-31 18:22:55 +04:00
Torkel Ödegaard
b1b54740f4 Merge branch 'html_unsafe' of github.com:bruce-lyft/grafana into bruce-lyft-html_unsafe 2014-03-30 16:41:50 +02:00
Torkel Ödegaard
0c1bc378ba updated changelog 2014-03-30 16:40:54 +02:00
Torkel Ödegaard
d067b69d20 Fixes #251, text panel change is now updating panel correctly. Changed button text from Cancel to Close 2014-03-30 15:00:09 +02:00
Torkel Ödegaard
ba928e18bf Merge pull request #245 from magicrobotmonkey/add_functions
Add transformNull, minSeries and maxSeries
2014-03-29 05:00:41 -04:00
Torkel Ödegaard
2d89cb0978 Merge pull request #248 from syepes/add_functions
Add functions: sortByMaxima,sortByMinima,limit,mostDeviant,movingMedian,stdev
2014-03-29 04:59:50 -04:00
Sebastian YEPES
c7c88902bd Add sortByMaxima,sortByMinima,limit,mostDeviant,movingMedian,stdev 2014-03-28 22:23:33 +01:00
Aaron Bassett
8d7669bc08 added min and max 2014-03-28 09:12:25 -04:00
Aaron Bassett
1cff3a6751 add transformNull 2014-03-28 09:12:20 -04:00
Torkel Ödegaard
6100336b2a Merge pull request #237 from maage/some-graphite-func-definitions
Some graphite func definitions
2014-03-27 10:18:36 -04:00
Torkel Ödegaard
da7455f859 Merge pull request #238 from maage/bits-y-axis-format
Fixes #148 add bits format for Y-Axis
2014-03-27 10:17:01 -04:00
Markus Linnala
5ec525a430 add bits format for Y-Axis 2014-03-27 15:58:04 +02:00
Markus Linnala
863e58a8e7 add support for graphite functions: maximumAbove maximumBelow minimumAbove 2014-03-27 15:18:01 +02:00
Markus Linnala
e441e97030 add support for graphite func substr 2014-03-27 15:17:51 +02:00
Markus Linnala
2b0152f6f4 add support for graphite func consolidateBy 2014-03-27 15:17:46 +02:00
Bruce Sherrod
eb20c5bb43 make html-unsafe for text/html panels 2014-03-26 12:00:08 -07:00
Bruce Sherrod
89c8547820 Merge branch 'master' of github.com:bruce-lyft/grafana 2014-03-26 11:52:00 -07:00
Torkel Ödegaard
5087cd2b19 Fixes #225, grid min not sent to graphite png renderer when set to 0 2014-03-26 11:51:52 -07:00
Torkel Ödegaard
06e9256050 Closes #209, sub folder with project name and version suffix in release zip files 2014-03-26 11:51:52 -07:00
Torkel Ödegaard
8aff6f50db Fixes #223, float arguments to functions like scale should now work as expected 2014-03-26 11:51:52 -07:00
Torkel Ödegaard
b2aca66e2d aliasByNode support for second node param, Closes #167 2014-03-26 11:51:52 -07:00
Marco Vito Moscaritolo
add61cf783 Moved function definition out of template. 2014-03-26 11:51:52 -07:00
Marco Vito Moscaritolo
a549bd73f9 Added more query function in InfluxDB 2014-03-26 11:51:51 -07:00
Torkel Ödegaard
f849b8cd4b fixed build issues with last commit (spec file syntax error) 2014-03-26 11:51:51 -07:00
Torkel Ödegaard
d8cf427425 more work on filterSrv unit tests 2014-03-26 11:51:51 -07:00
Torkel Ödegaard
517ac3f111 added unit tests for filterSrv 2014-03-26 11:51:51 -07:00
Jaime Gago
43f1cfba5a Fix a "kibana" leftover in comments 2014-03-26 11:51:51 -07:00
Torkel Ödegaard
dd18b6c836 Adds support to have filters inside filters, (nested templated segments), Closes #128 2014-03-26 11:51:51 -07:00
Bruce Sherrod
d8b86a899d Merge remote-tracking branch 'remotes/torkelo/master' 2014-03-24 12:35:12 -07:00
Torkel Ödegaard
ed9e336c51 Fixes #225, grid min not sent to graphite png renderer when set to 0 2014-03-24 13:17:27 +01:00
Torkel Ödegaard
94fea502b9 Closes #209, sub folder with project name and version suffix in release zip files 2014-03-24 12:51:49 +01:00
Torkel Ödegaard
1e79e39161 Fixes #223, float arguments to functions like scale should now work as expected 2014-03-24 12:20:28 +01:00
Torkel Ödegaard
6f9c2211fa aliasByNode support for second node param, Closes #167 2014-03-24 09:57:03 +01:00
Torkel Ödegaard
2e1e7462dc Merge pull request #218 from mavimo/feature/influx-query-functions
[InfluxDB] Added more query functions
2014-03-23 16:08:34 -04:00
Marco Vito Moscaritolo
6fb36acc9a Moved function definition out of template. 2014-03-23 20:20:18 +01:00
Marco Vito Moscaritolo
f35baffbef Added more query function in InfluxDB 2014-03-23 18:52:56 +01:00
Torkel Ödegaard
69b9189220 fixed build issues with last commit (spec file syntax error) 2014-03-23 16:04:21 +01:00
Torkel Ödegaard
06fe572113 more work on filterSrv unit tests 2014-03-23 07:03:43 +01:00
Torkel Ödegaard
dfd1d09641 added unit tests for filterSrv 2014-03-23 07:03:43 +01:00
Bruce Sherrod
6d981afcbc Merge branch 'master' of github.com:bruce-lyft/grafana 2014-03-19 13:38:08 -07:00
Bruce Sherrod
f90db0ed50 make html-unsafe for text/html panels 2014-03-19 13:37:29 -07:00
Bruce Sherrod
9894023177 make html-unsafe for text/html panels 2014-03-19 13:36:52 -07:00
Torkel Ödegaard
5ac6d28abf Merge pull request #213 from jaimegago/master
Fix a "kibana" leftover in comments
2014-03-19 06:05:55 +01:00
Jaime Gago
b813f48a7e Fix a "kibana" leftover in comments 2014-03-18 17:53:57 -07:00
Torkel Ödegaard
a3b4e40982 Adds support to have filters inside filters, (nested templated segments), Closes #128 2014-03-18 21:08:51 +01:00
Torkel Ödegaard
0c42b9a68e Merge pull request #212 from andrewmichaelsmith/log_func
Add logarithmic scale function
2014-03-18 21:08:31 +01:00
Andy Smith
48d3317136 Add logarithmic scale function 2014-03-18 19:36:31 +00:00
Matt Page
30b62e172d Add an OpenTSDB datasource.
This adds support for querying OpenTSDB for metric data.
2014-03-18 12:21:40 -07:00
Torkel Ödegaard
5a0fa8c09f added phantomjs karma test to run in travis 2014-03-18 20:04:04 +01:00
Torkel Ödegaard
0a6b393b35 fixed karama tests 2014-03-18 19:27:42 +01:00
Torkel Ödegaard
dadae53e51 Merge branch 'master' of github.com:torkelo/grafana 2014-03-17 17:25:21 +01:00
Torkel Ödegaard
c22c4b864c fixed string replace issue that caused problems, this removes build cache busting in requirejs, will need to fix that later 2014-03-17 17:24:38 +01:00
Torkel Ödegaard
aa27c13b20 Merge pull request #208 from fstern/master
Added graphite functions: nPercentile and keepLastValue
2014-03-17 11:31:06 +01:00
Bruno Renié
50771b0762 Workaround a behavior modification probably in grunt-string-replace 2014-03-16 20:50:15 +01:00
Torkel Ödegaard
336cf768d8 Merge pull request #194 from danharvey/add-hitcount-function
Added hitcount Transform function.
2014-03-12 09:57:47 +01:00
Dan Harvey
a76d9b87f0 Improved markdown formatting in the contributing page. 2014-03-12 08:23:28 +00:00
Dan Harvey
5794fc533f Added hitcount Transform function. 2014-03-11 18:43:55 +00:00
Torkel Ödegaard
9ebf769b4d Fixes #191, update graph after adding new function and not changing default value 2014-03-11 13:00:28 +01:00
Falk Stern
2ea21e1f96 Added nPercentile and keepLastValue 2014-03-11 12:06:30 +01:00
Torkel Ödegaard
b396563235 Merge pull request #185 from nikicat/master
added graphite functions: percentileOfSeries, sumSeriesWithWildcards, averageSeriesWithWildcards
2014-03-10 14:17:15 +01:00
Nikolay Bryskin
c5b992e330 added graphite functions: percentileOfSeries, sumSeriesWithWildcards, averageSeriesWithWildcards 2014-03-10 17:04:34 +04:00
Torkel Ödegaard
ec6fae9e65 Merge pull request #183 from merc1031/master
round up maxDataPoints to nearest integer in request. graphite 0.9.x and...
2014-03-10 11:11:24 +01:00
Leonidas Loucas
7f5c6ca18d round up maxDataPoints to nearest integer in request. graphite 0.9.x and 0.9.12 seem to treat decimal maxDataPoints badly returning enourmous amounts of data 2014-03-10 02:50:11 -07:00
Torkel Odegaard
9de60a9a7c updated readme and changelog 2014-03-09 19:12:02 +01:00
252 changed files with 37079 additions and 43221 deletions

10
.gitignore vendored
View File

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

13
.jscs.json Normal file
View File

@@ -0,0 +1,13 @@
{
"disallowImplicitTypeConversion": ["string"],
"disallowKeywords": ["with"],
"disallowMultipleLineBreaks": true,
"disallowMixedSpacesAndTabs": true,
"disallowTrailingWhitespace": true,
"requireSpacesInFunctionExpression": {
"beforeOpeningCurlyBrace": true
},
"disallowSpacesInsideArrayBrackets": true,
"disallowSpacesInsideParentheses": true,
"validateIndentation": 2
}

View File

@@ -18,12 +18,12 @@
"noempty": true,
"undef": true,
"boss": true,
"trailing": false,
"trailing": true,
"laxbreak": true,
"laxcomma": true,
"sub": true,
"unused": true,
"maxdepth": 5,
"maxlen": 140,
"globals": {

View File

@@ -1,5 +1,9 @@
language: node_js
node_js:
- "0.10"
git:
depth: 1
before_script:
- npm install -g grunt-cli
- npm install -g grunt-cli
after_script:
- npm run coveralls

View File

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

View File

@@ -3,12 +3,12 @@ And if you have time clone this repo and submit a pull request and help me make
kickass metrics & devops dashboard we all dream about!
Prerequisites:
Nodejs (for jshint & grunt & development server)
- Nodejs (for jshint & grunt & development server)
Clone repository:
npm install
grunt server (starts development web server in src folder)
grunt (runs jshint and less -> css compilation)
npm install
grunt server (starts development web server in src folder)
grunt (runs jshint and less -> css compilation)
Please remember to run grunt before doing pull request to verify that your code passes all the jshint validations.
Please remember to run grunt before doing pull request to verify that your code passes all the jshint validations.

135
README.md
View File

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

View File

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

4
latest.json Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,28 +1,13 @@
define(['jquery','underscore','moment','chromath'],
define([
'jquery',
'lodash',
'moment'
],
function($, _, moment) {
'use strict';
var kbn = {};
/**
* Calculate a graph interval
*
* from:: Date object containing the start time
* to:: Date object containing the finish time
* size:: Calculate to approximately this many bars
* user_interval:: User specified histogram interval
*
*/
kbn.calculate_interval = function(from,to,size,user_interval) {
if(_.isObject(from)) {
from = from.valueOf();
}
if(_.isObject(to)) {
to = to.valueOf();
}
return user_interval === 0 ? kbn.round_interval((to - from)/size) : user_interval;
};
kbn.round_interval = function(interval) {
switch (true) {
// 0.5s
@@ -78,7 +63,7 @@ function($, _, moment) {
}
};
kbn.secondsToHms = function(seconds){
kbn.secondsToHms = function(seconds) {
var numyears = Math.floor(seconds / 31536000);
if(numyears){
return numyears + 'y';
@@ -127,6 +112,28 @@ function($, _, moment) {
s: 1
};
kbn.calculateInterval = function(range, resolution, userInterval) {
var lowLimitMs = 1; // 1 millisecond default low limit
var intervalMs, lowLimitInterval;
if (userInterval) {
if (userInterval[0] === '>') {
lowLimitInterval = userInterval.slice(1);
lowLimitMs = kbn.interval_to_ms(lowLimitInterval);
}
else {
return userInterval;
}
}
intervalMs = kbn.round_interval((range.to.valueOf() - range.from.valueOf()) / resolution);
if (lowLimitMs > intervalMs) {
intervalMs = lowLimitMs;
}
return kbn.secondsToHms(intervalMs / 1000);
};
kbn.describe_interval = function (string) {
var matches = string.match(kbn.interval_regex);
if (!matches || !_.has(kbn.intervals_in_seconds, matches[2])) {
@@ -191,7 +198,7 @@ function($, _, moment) {
kbn.parseDateMath = function(mathString, time, roundUp) {
var dateTime = moment(time);
for (var i = 0; i < mathString.length; ) {
for (var i = 0; i < mathString.length;) {
var c = mathString.charAt(i++),
type,
num,
@@ -227,36 +234,36 @@ function($, _, moment) {
if (type === 0) {
roundUp ? dateTime.endOf('year') : dateTime.startOf('year');
} else if (type === 1) {
dateTime.add('years',num);
dateTime.add(num, 'years');
} else if (type === 2) {
dateTime.subtract('years',num);
dateTime.subtract(num, 'years');
}
break;
case 'M':
if (type === 0) {
roundUp ? dateTime.endOf('month') : dateTime.startOf('month');
} else if (type === 1) {
dateTime.add('months',num);
dateTime.add(num, 'months');
} else if (type === 2) {
dateTime.subtract('months',num);
dateTime.subtract(num, 'months');
}
break;
case 'w':
if (type === 0) {
roundUp ? dateTime.endOf('week') : dateTime.startOf('week');
} else if (type === 1) {
dateTime.add('weeks',num);
dateTime.add(num, 'weeks');
} else if (type === 2) {
dateTime.subtract('weeks',num);
dateTime.subtract(num, 'weeks');
}
break;
case 'd':
if (type === 0) {
roundUp ? dateTime.endOf('day') : dateTime.startOf('day');
} else if (type === 1) {
dateTime.add('days',num);
dateTime.add(num, 'days');
} else if (type === 2) {
dateTime.subtract('days',num);
dateTime.subtract(num, 'days');
}
break;
case 'h':
@@ -264,27 +271,27 @@ function($, _, moment) {
if (type === 0) {
roundUp ? dateTime.endOf('hour') : dateTime.startOf('hour');
} else if (type === 1) {
dateTime.add('hours',num);
dateTime.add(num, 'hours');
} else if (type === 2) {
dateTime.subtract('hours',num);
dateTime.subtract(num,'hours');
}
break;
case 'm':
if (type === 0) {
roundUp ? dateTime.endOf('minute') : dateTime.startOf('minute');
} else if (type === 1) {
dateTime.add('minutes',num);
dateTime.add(num, 'minutes');
} else if (type === 2) {
dateTime.subtract('minutes',num);
dateTime.subtract(num, 'minutes');
}
break;
case 's':
if (type === 0) {
roundUp ? dateTime.endOf('second') : dateTime.startOf('second');
} else if (type === 1) {
dateTime.add('seconds',num);
dateTime.add(num, 'seconds');
} else if (type === 2) {
dateTime.subtract('seconds',num);
dateTime.subtract(num, 'seconds');
}
break;
default:
@@ -294,121 +301,12 @@ function($, _, moment) {
return dateTime.toDate();
};
// LOL. hahahahaha. DIE.
kbn.flatten_json = function(object,root,array) {
if (typeof array === 'undefined') {
array = {};
}
if (typeof root === 'undefined') {
root = '';
}
for(var index in object) {
var obj = object[index];
var rootname = root.length === 0 ? index : root + '.' + index;
if(typeof obj === 'object' ) {
if(_.isArray(obj)) {
if(obj.length > 0 && typeof obj[0] === 'object') {
var strval = '';
for (var objidx = 0, objlen = obj.length; objidx < objlen; objidx++) {
if (objidx > 0) {
strval = strval + ', ';
}
strval = strval + JSON.stringify(obj[objidx]);
}
array[rootname] = strval;
} else if(obj.length === 1 && _.isNumber(obj[0])) {
array[rootname] = parseFloat(obj[0]);
} else {
array[rootname] = typeof obj === 'undefined' ? null : obj;
}
} else {
kbn.flatten_json(obj,rootname,array);
}
} else {
array[rootname] = typeof obj === 'undefined' ? null : obj;
}
}
return kbn.sortObj(array);
};
kbn.xmlEnt = function(value) {
if(_.isString(value)) {
var stg1 = value.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/\r\n/g, '<br/>')
.replace(/\r/g, '<br/>')
.replace(/\n/g, '<br/>')
.replace(/\t/g, '&nbsp;&nbsp;&nbsp;&nbsp;')
.replace(/ /g, '&nbsp;&nbsp;')
.replace(/&lt;del&gt;/g, '<del>')
.replace(/&lt;\/del&gt;/g, '</del>');
return stg1;
} else {
return value;
}
};
kbn.sortObj = function(arr) {
// Setup Arrays
var sortedKeys = [];
var sortedObj = {};
var i;
// Separate keys and sort them
for (i in arr) {
sortedKeys.push(i);
}
sortedKeys.sort();
// Reconstruct sorted obj based on keys
for (i in sortedKeys) {
sortedObj[sortedKeys[i]] = arr[sortedKeys[i]];
}
return sortedObj;
};
kbn.query_color_dot = function (color, diameter) {
return '<div class="icon-circle" style="' + [
'display:inline-block',
'color:' + color,
'font-size:' + diameter + 'px',
].join(';') + '"></div>';
};
kbn.colorSteps = function(col,steps) {
var _d = steps > 5 ? 1.6/steps : 0.25, // distance between steps
_p = []; // adjustment percentage
// Create a range of numbers between -0.8 and 0.8
for(var i = 1; i<steps+1; i+=1) {
_p.push(i%2 ? ((i-1)*_d*-1)/2 : i*_d/2);
}
// Create the color range
return _.map(_p.sort(function(a,b){return a-b;}),function(v) {
return v<0 ? Chromath.darken(col,v*-1).toString() : Chromath.lighten(col,v).toString();
});
};
// Find the smallest missing number in an array
kbn.smallestMissing = function(arr,start,end) {
start = start || 0;
end = end || arr.length-1;
if(start > end) {
return end + 1;
}
if(start !== arr[start]) {
return start;
}
var middle = Math.floor((start + end) / 2);
if (arr[middle] > middle) {
return kbn.smallestMissing(arr, start, middle);
} else {
return kbn.smallestMissing(arr, middle + 1, end);
}
'display:inline-block',
'color:' + color,
'font-size:' + diameter + 'px',
].join(';') + '"></div>';
};
kbn.byteFormat = function(size, decimals) {
@@ -430,28 +328,122 @@ function($, _, moment) {
ext = " B";
break;
case 1:
ext = " KB";
ext = " KiB";
break;
case 2:
ext = " MB";
ext = " MiB";
break;
case 3:
ext = " GB";
ext = " GiB";
break;
case 4:
ext = " TB";
ext = " TiB";
break;
case 5:
ext = " PB";
ext = " PiB";
break;
case 6:
ext = " EB";
ext = " EiB";
break;
case 7:
ext = " ZB";
ext = " ZiB";
break;
case 8:
ext = " YB";
ext = " YiB";
break;
}
return (size.toFixed(decimals) + ext);
};
kbn.bitFormat = 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.bpsFormat = 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 = " 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;
}
@@ -515,6 +507,18 @@ function($, _, moment) {
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);
@@ -523,48 +527,110 @@ function($, _, moment) {
return function(val) {
return kbn.microsFormat(val, decimals);
};
default:
case 'ns':
return function(val) {
return val % 1 === 0 ? val : val.toFixed(decimals);
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);
// if exponent return directly
if (formatted.indexOf('e') !== -1 || value === 0) {
return formatted;
}
// If tickDecimals was specified, ensure that we have exactly that
// much precision; otherwise default to the value's own precision.
if (decimals != null) {
var decimalPos = formatted.indexOf(".");
var precision = decimalPos === -1 ? 0 : formatted.length - decimalPos - 1;
if (precision < decimals) {
return (precision ? formatted : formatted + ".") + (String(factor)).substr(1, decimals - precision);
}
}
return formatted;
};
kbn.msFormat = function(size, decimals) {
if (size < 1000) {
return size.toFixed(0) + " ms";
// 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";
}
// Less than 1 min
else if (size < 60000) {
else if (Math.abs(size) < 60000) {
return (size / 1000).toFixed(decimals) + " s";
}
// Less than 1 hour, devide in minutes
else if (size < 3600000) {
else if (Math.abs(size) < 3600000) {
return (size / 60000).toFixed(decimals) + " min";
}
// Less than one day, devide in hours
else if (size < 86400000) {
else if (Math.abs(size) < 86400000) {
return (size / 3600000).toFixed(decimals) + " hour";
}
// Less than one week, devide in days
else if (size < 604800000) {
// Less than one year, devide in days
else if (Math.abs(size) < 31536000000) {
return (size / 86400000).toFixed(decimals) + " day";
}
// Less than one month, devide in weeks
else if (size < 2.62974e9) {
return (size / 604800000).toFixed(decimals) + " week";
return (size / 31536000000).toFixed(decimals) + " 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 one year, devide in weeks
else if (size < 3.15569e10) {
return (size / 2.62974e9).toFixed(decimals) + " year";
// Less than 10 min, use seconds
else if (Math.abs(size) < 600) {
return size.toFixed(decimals) + " s";
}
// Less than 1 hour, devide in minutes
else if (Math.abs(size) < 3600) {
return (size / 60).toFixed(decimals) + " min";
}
// Less than one day, devide in hours
else if (Math.abs(size) < 86400) {
return (size / 3600).toFixed(decimals) + " hour";
}
// Less than one week, devide in days
else if (Math.abs(size) < 604800) {
return (size / 86400).toFixed(decimals) + " day";
}
// Less than one year, devide in week
else if (Math.abs(size) < 31536000) {
return (size / 604800).toFixed(decimals) + " week";
}
return (size / 3.15569e7).toFixed(decimals) + " year";
};
kbn.microsFormat = function(size, decimals) {
if (size < 1000) {
return size.toFixed(0) + " µs";
// Less than 1 micro, downscale to nano
if (size !== 0 && Math.abs(size) < 1) {
return kbn.nanosFormat(size * 1000, decimals);
}
else if (size < 1000000) {
else if (Math.abs(size) < 1000) {
return size.toFixed(decimals) + " µs";
}
else if (Math.abs(size) < 1000000) {
return (size / 1000).toFixed(decimals) + " ms";
}
else {
@@ -572,5 +638,42 @@ function($, _, moment) {
}
};
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";
}
else if (Math.abs(size) < 1000000) {
return (size / 1000).toFixed(decimals) + " µs";
}
else if (Math.abs(size) < 1000000000) {
return (size / 1000000).toFixed(decimals) + " ms";
}
else if (Math.abs(size) < 60000000000){
return (size / 1000000000).toFixed(decimals) + " s";
}
else {
return (size / 60000000000).toFixed(decimals) + " m";
}
};
kbn.slugifyForUrl = function(str) {
return str
.toLowerCase()
.replace(/[^\w ]+/g,'')
.replace(/ +/g,'-');
};
kbn.stringToJsRegex = function(str) {
if (str[0] !== '/') {
return new RegExp(str);
}
var match = str.match(new RegExp('^/(.*?)/(g?i?m?y?)$'));
return new RegExp(match[1], match[2]);
};
return kbn;
});

View File

@@ -1,5 +1,5 @@
define([
'underscore-src'
'lodash-src'
],
function () {
'use strict';
@@ -11,7 +11,7 @@ function () {
*/
_.mixin({
move: function (array, fromIndex, toIndex) {
array.splice(toIndex, 0, array.splice(fromIndex, 1)[0] );
array.splice(toIndex, 0, array.splice(fromIndex, 1)[0]);
return array;
},
remove: function (array, index) {
@@ -33,4 +33,4 @@ function () {
});
return _;
});
});

View File

@@ -0,0 +1,2 @@
define([
], function () {});

View File

@@ -3,32 +3,32 @@
*/
require.config({
baseUrl: 'app',
// urlArgs: 'r=@REV@',
paths: {
config: ['../config', '../config.sample'],
settings: 'components/settings',
kbn: 'components/kbn',
store: 'components/store',
css: '../vendor/require/css',
text: '../vendor/require/text',
moment: '../vendor/moment',
filesaver: '../vendor/filesaver',
chromath: '../vendor/chromath',
angular: '../vendor/angular/angular',
'angular-route': '../vendor/angular/angular-route',
'angular-dragdrop': '../vendor/angular/angular-dragdrop',
'angular-strap': '../vendor/angular/angular-strap',
'angular-sanitize': '../vendor/angular/angular-sanitize',
timepicker: '../vendor/angular/timepicker',
datepicker: '../vendor/angular/datepicker',
bindonce: '../vendor/angular/bindonce',
crypto: '../vendor/crypto.min',
spectrum: '../vendor/spectrum',
underscore: 'components/underscore.extended',
'underscore-src': '../vendor/underscore',
lodash: 'components/lodash.extended',
'lodash-src': '../vendor/lodash',
bootstrap: '../vendor/bootstrap/bootstrap',
jquery: '../vendor/jquery/jquery-1.8.0',
jquery: '../vendor/jquery/jquery-2.1.1.min',
'jquery-ui': '../vendor/jquery/jquery-ui-1.10.3',
'extend-jquery': 'components/extend-jquery',
@@ -40,18 +40,13 @@ require.config({
'jquery.flot.stack': '../vendor/jquery/jquery.flot.stack',
'jquery.flot.stackpercent':'../vendor/jquery/jquery.flot.stackpercent',
'jquery.flot.time': '../vendor/jquery/jquery.flot.time',
'jquery.flot.byte': '../vendor/jquery/jquery.flot.byte',
modernizr: '../vendor/modernizr-2.6.1',
elasticjs: '../vendor/elasticjs/elastic-angular-client',
'bootstrap-tagsinput': '../vendor/tagsinput/bootstrap-tagsinput',
},
shim: {
underscore: {
exports: '_'
},
spectrum: {
deps: ['jquery']
@@ -82,15 +77,12 @@ require.config({
//
'jquery-ui': ['jquery'],
'jquery.flot': ['jquery'],
'jquery.flot.byte': ['jquery', 'jquery.flot'],
'jquery.flot.pie': ['jquery', 'jquery.flot'],
'jquery.flot.events': ['jquery', 'jquery.flot'],
'jquery.flot.selection':['jquery', 'jquery.flot'],
'jquery.flot.stack': ['jquery', 'jquery.flot'],
'jquery.flot.stackpercent':['jquery', 'jquery.flot'],
'jquery.flot.time': ['jquery', 'jquery.flot'],
'angular-sanitize': ['angular'],
'angular-cookies': ['angular'],
'angular-dragdrop': ['jquery','jquery-ui','angular'],
'angular-loader': ['angular'],
@@ -104,8 +96,6 @@ require.config({
timepicker: ['jquery', 'bootstrap'],
datepicker: ['jquery', 'bootstrap'],
elasticjs: ['angular', '../vendor/elasticjs/elastic'],
'bootstrap-tagsinput': ['jquery'],
},
waitSeconds: 60,

View File

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

View File

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

View File

@@ -0,0 +1,120 @@
define([
'lodash',
'kbn'
],
function (_, kbn) {
'use strict';
function TimeSeries(opts) {
this.datapoints = opts.datapoints;
this.info = opts.info;
this.label = opts.info.alias;
}
function matchSeriesOverride(aliasOrRegex, seriesAlias) {
if (!aliasOrRegex) { return false; }
if (aliasOrRegex[0] === '/') {
var regex = kbn.stringToJsRegex(aliasOrRegex);
return seriesAlias.match(regex) != null;
}
return aliasOrRegex === seriesAlias;
}
function translateFillOption(fill) {
return fill === 0 ? 0.001 : fill/10;
}
TimeSeries.prototype.applySeriesOverrides = function(overrides) {
this.lines = {};
this.points = {};
this.bars = {};
this.info.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)) {
continue;
}
if (override.lines !== void 0) { this.lines.show = override.lines; }
if (override.points !== void 0) { this.points.show = override.points; }
if (override.bars !== void 0) { this.bars.show = override.bars; }
if (override.fill !== void 0) { this.lines.fill = translateFillOption(override.fill); }
if (override.stack !== void 0) { this.stack = override.stack; }
if (override.linewidth !== void 0) { this.lines.lineWidth = override.linewidth; }
if (override.pointradius !== void 0) { this.points.radius = override.pointradius; }
if (override.steppedLine !== void 0) { this.lines.steps = override.steppedLine; }
if (override.zindex !== void 0) { this.zindex = override.zindex; }
if (override.yaxis !== void 0) {
this.info.yaxis = override.yaxis;
}
}
};
TimeSeries.prototype.getFlotPairs = function (fillStyle, yFormats) {
var result = [];
this.color = this.info.color;
this.yaxis = this.info.yaxis;
this.info.total = 0;
this.info.max = -212312321312;
this.info.min = 212312321312;
var ignoreNulls = fillStyle === 'connected';
var nullAsZero = fillStyle === 'null as zero';
var currentTime;
var currentValue;
for (var i = 0; i < this.datapoints.length; i++) {
currentValue = this.datapoints[i][0];
currentTime = this.datapoints[i][1];
if (currentValue === null) {
if (ignoreNulls) { continue; }
if (nullAsZero) {
currentValue = 0;
}
}
if (_.isNumber(currentValue)) {
this.info.total += currentValue;
}
if (currentValue > this.info.max) {
this.info.max = currentValue;
}
if (currentValue < this.info.min) {
this.info.min = currentValue;
}
result.push([currentTime * 1000, currentValue]);
}
if (result.length > 2) {
this.info.timeStep = result[1][0] - result[0][0];
}
if (result.length) {
this.info.avg = (this.info.total / result.length);
this.info.current = result[result.length-1][1];
var formater = kbn.getFormatFunction(yFormats[this.yaxis - 1], 2);
this.info.avg = this.info.avg != null ? formater(this.info.avg) : null;
this.info.current = this.info.current != null ? formater(this.info.current) : null;
this.info.min = this.info.min != null ? formater(this.info.min) : null;
this.info.max = this.info.max != null ? formater(this.info.max) : null;
this.info.total = this.info.total != null ? formater(this.info.total) : null;
}
return result;
};
return TimeSeries;
});

View File

@@ -1,6 +1,7 @@
define([
'./dash',
'./dashLoader',
'./grafanaCtrl',
'./dashboardCtrl',
'./dashboardNavCtrl',
'./row',
'./submenuCtrl',
'./pulldown',
@@ -10,4 +11,9 @@ define([
'./graphiteImport',
'./influxTargetCtrl',
'./playlistCtrl',
], function () {});
'./inspectCtrl',
'./opentsdbTargetCtrl',
'./annotationsEditorCtrl',
'./templateEditorCtrl',
'./jsonEditorCtrl',
], function () {});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,23 +1,33 @@
define([
'angular',
'app',
'underscore'
'lodash'
],
function (angular, app, _) {
'use strict';
var module = angular.module('kibana.controllers');
var module = angular.module('grafana.controllers');
module.controller('GraphiteImportCtrl', function($scope, $rootScope, $timeout, datasourceSrv, dashboard) {
module.controller('GraphiteImportCtrl', function($scope, $rootScope, $timeout, datasourceSrv) {
$scope.init = function() {
console.log('hej!');
$scope.datasources = datasourceSrv.getMetricSources();
$scope.setDatasource(null);
};
$scope.setDatasource = function(datasource) {
$scope.datasource = datasourceSrv.get(datasource);
if (!$scope.datasource) {
$scope.error = "Cannot find datasource " + datasource;
return;
}
};
$scope.listAll = function(query) {
delete $scope.error;
datasourceSrv.default.listDashboards(query)
$scope.datasource.listDashboards(query)
.then(function(results) {
$scope.dashboards = results;
})
@@ -29,20 +39,20 @@ function (angular, app, _) {
$scope.import = function(dashName) {
delete $scope.error;
datasourceSrv.default.loadDashboard(dashName)
$scope.datasource.loadDashboard(dashName)
.then(function(results) {
if (!results.data || !results.data.state) {
throw { message: 'no dashboard state received from graphite' };
}
graphiteToGrafanaTranslator(results.data.state);
graphiteToGrafanaTranslator(results.data.state, $scope.datasource.name);
})
.then(null, function(err) {
$scope.error = err.message || 'Failed to import dashboard';
});
};
function graphiteToGrafanaTranslator(state) {
function graphiteToGrafanaTranslator(state, datasource) {
var graphsPerRow = 2;
var rowHeight = 300;
var rowTemplate;
@@ -57,7 +67,7 @@ function (angular, app, _) {
currentRow = angular.copy(rowTemplate);
var newDashboard = angular.copy(dashboard.current);
var newDashboard = angular.copy($scope.dashboard);
newDashboard.rows = [];
newDashboard.title = state.name;
newDashboard.rows.push(currentRow);
@@ -72,7 +82,8 @@ function (angular, app, _) {
type: 'graphite',
span: 12 / graphsPerRow,
title: graph[1].title,
targets: []
targets: [],
datasource: datasource
};
_.each(graph[1].target, function(target) {
@@ -84,7 +95,8 @@ function (angular, app, _) {
currentRow.panels.push(panel);
});
dashboard.dash_load(newDashboard);
$scope.emitAppEvent('setup-dashboard', newDashboard);
$scope.dismiss();
}
});

View File

@@ -1,6 +1,6 @@
define([
'angular',
'underscore',
'lodash',
'config',
'../services/graphite/gfunc',
'../services/graphite/parser'
@@ -8,11 +8,15 @@ define([
function (angular, _, config, gfunc, Parser) {
'use strict';
var module = angular.module('kibana.controllers');
var module = angular.module('grafana.controllers');
var targetLetters = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O'];
module.controller('GraphiteTargetCtrl', function($scope, $http, filterSrv) {
module.controller('GraphiteTargetCtrl', function($scope, $sce, templateSrv) {
$scope.init = function() {
$scope.target.target = $scope.target.target || '';
$scope.targetLetters = targetLetters;
parseTarget();
};
@@ -50,6 +54,13 @@ function (angular, _, config, gfunc, Parser) {
checkOtherSegments($scope.segments.length - 1);
}
function addFunctionParameter(func, value, index, shiftBack) {
if (shiftBack) {
index = Math.max(index - 1, 0);
}
func.params[index] = value;
}
function parseTargeRecursive(astNode, func, index) {
if (astNode === null) {
return null;
@@ -57,7 +68,7 @@ function (angular, _, config, gfunc, Parser) {
switch(astNode.type) {
case 'function':
var innerFunc = gfunc.createFuncInstance(astNode.name);
var innerFunc = gfunc.createFuncInstance(astNode.name, { withDefaultParams: false });
_.each(astNode.params, function(param, index) {
parseTargeRecursive(param, innerFunc, index);
@@ -67,40 +78,27 @@ function (angular, _, config, gfunc, Parser) {
$scope.functions.push(innerFunc);
break;
case 'series-ref':
addFunctionParameter(func, astNode.value, index, $scope.segments.length > 0);
break;
case 'string':
case 'number':
if ((index-1) >= func.def.params.length) {
throw { message: 'invalid number of parameters to method ' + func.def.name };
}
if (index === 0) {
func.params[index] = astNode.value;
}
else {
func.params[index - 1] = astNode.value;
}
addFunctionParameter(func, astNode.value, index, true);
break;
case 'metric':
if ($scope.segments.length > 0) {
throw { message: 'Multiple metric params not supported, use text editor.' };
if (astNode.segments.length !== 1) {
throw { message: 'Multiple metric params not supported, use text editor.' };
}
addFunctionParameter(func, astNode.segments[0].value, index, true);
break;
}
$scope.segments = _.map(astNode.segments, function(segment) {
var node = {
type: segment.type,
val: segment.value,
html: segment.value
};
if (segment.value === '*') {
node.html = '<i class="icon-asterisk"><i>';
}
if (segment.type === 'template') {
node.val = node.html = '[[' + segment.value + ']]';
node.html = "<span style='color: #ECEC09'>" + node.html + "</span>";
}
return node;
return new MetricSegment(segment);
});
}
}
@@ -109,13 +107,13 @@ function (angular, _, config, gfunc, Parser) {
var arr = $scope.segments.slice(0, index);
return _.reduce(arr, function(result, segment) {
return result ? (result + "." + segment.val) : segment.val;
return result ? (result + "." + segment.value) : segment.value;
}, "");
}
function checkOtherSegments(fromIndex) {
if (fromIndex === 0) {
$scope.segments.push({html: 'select metric'});
$scope.segments.push(new MetricSegment('select metric'));
return;
}
@@ -123,13 +121,15 @@ function (angular, _, config, gfunc, Parser) {
return $scope.datasource.metricFindQuery(path)
.then(function(segments) {
if (segments.length === 0) {
$scope.segments = $scope.segments.splice(0, fromIndex);
$scope.segments.push({html: 'select metric'});
if (path !== '') {
$scope.segments = $scope.segments.splice(0, fromIndex);
$scope.segments.push(new MetricSegment('select metric'));
}
return;
}
if (segments[0].expandable) {
if ($scope.segments.length === fromIndex) {
$scope.segments.push({html: 'select metric'});
$scope.segments.push(new MetricSegment('select metric'));
}
else {
return checkOtherSegments(fromIndex + 1);
@@ -154,39 +154,37 @@ function (angular, _, config, gfunc, Parser) {
$scope.getAltSegments = function (index) {
$scope.altSegments = [];
var query = index === 0 ?
'*' : getSegmentPathUpTo(index) + '.*';
var query = index === 0 ? '*' : getSegmentPathUpTo(index) + '.*';
return $scope.datasource.metricFindQuery(query)
.then(function(segments) {
_.each(segments, function(segment) {
segment.html = segment.val = segment.text;
$scope.altSegments = _.map(segments, function(segment) {
return new MetricSegment({ value: segment.text, expandable: segment.expandable });
});
_.each(filterSrv.list, function(filter) {
segments.unshift({
_.each(templateSrv.variables, function(variable) {
$scope.altSegments.unshift(new MetricSegment({
type: 'template',
html: '[[' + filter.name + ']]',
val: '[[' + filter.name + ']]',
value: '$' + variable.name,
expandable: true,
});
}));
});
segments.unshift({val: '*', html: '<i class="icon-asterisk"></i>', expandable: true });
$scope.altSegments = segments;
$scope.altSegments.unshift(new MetricSegment('*'));
})
.then(null, function(err) {
$scope.parserError = err.message || 'Failed to issue metric query';
});
};
$scope.setSegment = function (altIndex, segmentIndex) {
$scope.segmentValueChanged = function (segment, segmentIndex) {
delete $scope.parserError;
$scope.segments[segmentIndex].val = $scope.altSegments[altIndex].val;
$scope.segments[segmentIndex].html = $scope.altSegments[altIndex].html;
if ($scope.functions.length > 0 && $scope.functions[0].def.fake) {
$scope.functions = [];
}
if ($scope.altSegments[altIndex].expandable) {
if (segment.expandable) {
return checkOtherSegments(segmentIndex + 1)
.then(function () {
setSegmentFocus(segmentIndex + 1);
@@ -211,10 +209,14 @@ function (angular, _, config, gfunc, Parser) {
return;
}
var oldTarget = $scope.target.target;
var target = getSegmentPathUpTo($scope.segments.length);
target = _.reduce($scope.functions, wrapFunction, target);
$scope.target.target = target;
$scope.$parent.get_data();
$scope.target.target = _.reduce($scope.functions, wrapFunction, target);
if ($scope.target.target !== oldTarget) {
$scope.$parent.get_data();
}
};
$scope.removeFunction = function(func) {
@@ -223,14 +225,18 @@ function (angular, _, config, gfunc, Parser) {
};
$scope.addFunction = function(funcDef) {
var newFunc = gfunc.createFuncInstance(funcDef);
var newFunc = gfunc.createFuncInstance(funcDef, { withDefaultParams: true });
newFunc.added = true;
$scope.functions.push(newFunc);
$scope.moveAliasFuncLast();
$scope.smartlyHandleNewAliasByNode(newFunc);
if (!funcDef.params && newFunc.added) {
if ($scope.segments.length === 1 && $scope.segments[0].value === 'select metric') {
$scope.segments = [];
}
if (!newFunc.params.length && newFunc.added) {
$scope.targetChanged();
}
};
@@ -253,7 +259,7 @@ function (angular, _, config, gfunc, Parser) {
return;
}
for(var i = 0; i < $scope.segments.length; i++) {
if ($scope.segments[i].val.indexOf('*') >= 0) {
if ($scope.segments[i].value.indexOf('*') >= 0) {
func.params[0] = i;
func.added = false;
$scope.targetChanged();
@@ -262,11 +268,38 @@ function (angular, _, config, gfunc, Parser) {
}
};
$scope.toggleMetricOptions = function() {
$scope.panel.metricOptionsEnabled = !$scope.panel.metricOptionsEnabled;
if (!$scope.panel.metricOptionsEnabled) {
delete $scope.panel.cacheTimeout;
}
};
$scope.duplicate = function() {
var clone = angular.copy($scope.target);
$scope.panel.targets.push(clone);
};
function MetricSegment(options) {
if (options === '*' || options.value === '*') {
this.value = '*';
this.html = $sce.trustAsHtml('<i class="icon-asterisk"><i>');
this.expandable = true;
return;
}
if (_.isString(options)) {
this.value = options;
this.html = $sce.trustAsHtml(this.value);
return;
}
this.value = options.value;
this.type = options.type;
this.expandable = options.expandable;
this.html = $sce.trustAsHtml(templateSrv.highlightVariablesAsHtml(this.value));
}
});
module.directive('focusMe', function($timeout, $parse) {
@@ -285,4 +318,4 @@ function (angular, _, config, gfunc, Parser) {
};
});
});
});

View File

@@ -4,31 +4,69 @@ define([
function (angular) {
'use strict';
var module = angular.module('kibana.controllers');
var module = angular.module('grafana.controllers');
var seriesList = null;
module.controller('InfluxTargetCtrl', function($scope, $timeout) {
$scope.init = function() {
if (!$scope.target.function) {
$scope.target.function = 'mean';
var target = $scope.target;
target.function = target.function || 'mean';
target.column = target.column || 'value';
// backward compatible correction of schema
if (target.condition_value) {
target.condition = target.condition_key + ' ' + target.condition_op + ' ' + target.condition_value;
delete target.condition_key;
delete target.condition_op;
delete target.condition_value;
}
$scope.oldSeries = $scope.target.series;
$scope.$on('typeahead-updated', function(){
if (target.groupby_field_add === false) {
target.groupby_field = '';
delete target.groupby_field_add;
}
$scope.rawQuery = false;
$scope.functions = [
'count', 'mean', 'sum', 'min',
'max', 'mode', 'distinct', 'median',
'derivative', 'stddev', 'first', 'last',
'difference'
];
$scope.operators = ['=', '=~', '>', '<', '!~', '<>'];
$scope.oldSeries = target.series;
$scope.$on('typeahead-updated', function() {
$timeout($scope.get_data);
});
};
$scope.showQuery = function () {
$scope.target.rawQuery = true;
};
$scope.hideQuery = function () {
$scope.target.rawQuery = false;
};
// Cannot use typeahead and ng-change on blur at the same time
$scope.seriesBlur = function() {
if ($scope.oldSeries !== $scope.target.series) {
$scope.oldSeries = $scope.target.series;
$scope.columnList = null;
$scope.get_data();
}
};
$scope.changeFunction = function(func) {
$scope.target.function = func;
$scope.get_data();
};
// called outside of digest
$scope.listColumns = function(query, callback) {
if (!$scope.columnList) {
@@ -45,7 +83,7 @@ function (angular) {
};
$scope.listSeries = function(query, callback) {
if (!seriesList) {
if (!seriesList || query === '') {
seriesList = [];
$scope.datasource.listSeries().then(function(series) {
seriesList = series;
@@ -64,4 +102,4 @@ function (angular) {
});
});
});

View File

@@ -0,0 +1,86 @@
define([
'angular',
'lodash'
],
function (angular, _) {
'use strict';
var module = angular.module('grafana.controllers');
module.controller('InspectCtrl', function($scope) {
var model = $scope.inspector;
function getParametersFromQueryString(queryString) {
var result = [];
var parameters = queryString.split("&");
for (var i = 0; i < parameters.length; i++) {
var keyValue = parameters[i].split("=");
if (keyValue[1].length > 0) {
result.push({ key: keyValue[0], value: window.unescape(keyValue[1]) });
}
}
return result;
}
$scope.init = function () {
$scope.editor = { index: 0 };
if (!model.error) {
return;
}
if (_.isString(model.error.data)) {
$scope.response = model.error.data;
}
if (model.error.config && model.error.config.params) {
$scope.request_parameters = _.map(model.error.config.params, function(value, key) {
return { key: key, value: value};
});
}
if (model.error.stack) {
$scope.editor.index = 2;
$scope.stack_trace = model.error.stack;
$scope.message = model.error.message;
}
else if (model.error.config && model.error.config.data) {
$scope.editor.index = 1;
$scope.request_parameters = getParametersFromQueryString(model.error.config.data);
if (model.error.data.indexOf('DOCTYPE') !== -1) {
$scope.response_html = model.error.data;
}
}
};
});
angular
.module('grafana.directives')
.directive('iframeContent', function($parse) {
return {
restrict: 'A',
link: function($scope, elem, attrs) {
var getter = $parse(attrs.iframeContent), value = getter($scope);
$scope.$on("$destroy",function() {
elem.remove();
});
var iframe = document.createElement('iframe');
iframe.width = '100%';
iframe.height = '400px';
iframe.style.border = 'none';
iframe.src = 'about:blank';
elem.append(iframe);
iframe.contentWindow.document.open('text/html', 'replace');
iframe.contentWindow.document.write(value);
iframe.contentWindow.document.close();
}
};
});
});

View File

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

View File

@@ -1,12 +1,12 @@
define([
'angular',
'underscore',
'lodash',
'config'
],
function (angular, _, config) {
'use strict';
var module = angular.module('kibana.controllers');
var module = angular.module('grafana.controllers');
module.controller('MetricKeysCtrl', function($scope, $http, $q) {
var elasticSearchUrlForMetricIndex = config.elasticsearch + '/' + config.grafana_metrics_index + '/';
@@ -52,18 +52,24 @@ function (angular, _, config) {
$scope.loadAll = function() {
$scope.infoText = "Fetching all metrics from graphite...";
return $http.get(config.graphiteUrl + "/metrics/index.json")
.then(saveMetricsArray)
.then(function () {
getFromEachGraphite('/metrics/index.json', saveMetricsArray)
.then(function() {
$scope.infoText = "Indexing complete!";
})
.then(null, function(err) {
}).then(null, function(err) {
$scope.errorText = err;
});
};
function saveMetricsArray(data, currentIndex)
{
function getFromEachGraphite(request, data_callback, error_callback) {
return $q.all(_.map(config.datasources, function(datasource) {
if (datasource.type = 'graphite') {
return $http.get(datasource.url + request)
.then(data_callback, error_callback);
}
}));
}
function saveMetricsArray(data, currentIndex) {
if (!data && !data.data && data.data.length === 0) {
return $q.reject('No metrics from graphite');
}
@@ -112,7 +118,7 @@ function (angular, _, config) {
type : "nGram",
min_gram : "3",
max_gram : "8",
token_chars: [ "letter", "digit", "punctuation", "symbol"]
token_chars: ["letter", "digit", "punctuation", "symbol"]
}
}
}
@@ -172,9 +178,9 @@ function (angular, _, config) {
function loadMetricsRecursive(metricPath)
{
return $http.get(config.graphiteUrl + '/metrics/find/?query=' + metricPath).then(receiveMetric);
return getFromEachGraphite('/metrics/find/?query=' + metricPath, receiveMetric);
}
});
});
});

View File

@@ -0,0 +1,118 @@
define([
'angular',
'lodash',
'kbn'
],
function (angular, _, kbn) {
'use strict';
var module = angular.module('grafana.controllers');
module.controller('OpenTSDBTargetCtrl', function($scope, $timeout) {
$scope.init = function() {
$scope.target.errors = validateTarget($scope.target);
$scope.aggregators = ['avg', 'sum', 'min', 'max', 'dev', 'zimsum', 'mimmin', 'mimmax'];
if (!$scope.target.aggregator) {
$scope.target.aggregator = 'sum';
}
if (!$scope.target.downsampleAggregator) {
$scope.target.downsampleAggregator = 'sum';
}
$scope.$on('typeahead-updated', function() {
$timeout($scope.targetBlur);
});
};
$scope.targetBlur = function() {
$scope.target.errors = validateTarget($scope.target);
// this does not work so good
if (!_.isEqual($scope.oldTarget, $scope.target) && _.isEmpty($scope.target.errors)) {
$scope.oldTarget = angular.copy($scope.target);
$scope.get_data();
}
};
$scope.duplicate = function() {
var clone = angular.copy($scope.target);
$scope.panel.targets.push(clone);
};
$scope.suggestMetrics = function(query, callback) {
$scope.datasource
.performSuggestQuery(query, 'metrics')
.then(callback);
};
$scope.suggestTagKeys = function(query, callback) {
$scope.datasource
.performSuggestQuery(query, 'tagk')
.then(callback);
};
$scope.suggestTagValues = function(query, callback) {
$scope.datasource
.performSuggestQuery(query, 'tagv')
.then(callback);
};
$scope.addTag = function() {
if (!$scope.addTagMode) {
$scope.addTagMode = true;
return;
}
if (!$scope.target.tags) {
$scope.target.tags = {};
}
$scope.target.errors = validateTarget($scope.target);
if (!$scope.target.errors.tags) {
$scope.target.tags[$scope.target.currentTagKey] = $scope.target.currentTagValue;
$scope.target.currentTagKey = '';
$scope.target.currentTagValue = '';
$scope.targetBlur();
}
$scope.addTagMode = false;
};
$scope.removeTag = function(key) {
delete $scope.target.tags[key];
$scope.targetBlur();
};
function validateTarget(target) {
var errs = {};
if (!target.metric) {
errs.metric = "You must supply a metric name.";
}
if (target.shouldDownsample) {
try {
if (target.downsampleInterval) {
kbn.describe_interval(target.downsampleInterval);
} else {
errs.downsampleInterval = "You must supply a downsample interval (e.g. '1m' or '1h').";
}
} catch(err) {
errs.downsampleInterval = err.message;
}
}
if (target.tags && _.has(target.tags, target.currentTagKey)) {
errs.tags = "Duplicate tag key '" + target.currentTagKey + "'.";
}
return errs;
}
});
});

View File

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

View File

@@ -1,43 +1,42 @@
define([
'angular',
'app',
'underscore'
'lodash'
],
function (angular, app, _) {
'use strict';
var module = angular.module('kibana.controllers');
var module = angular.module('grafana.controllers');
module.controller('PulldownCtrl', function($scope, $rootScope, $timeout) {
var _d = {
collapse: false,
notice: false,
enable: true
};
var _d = {
collapse: false,
notice: false,
enable: true
};
_.defaults($scope.pulldown,_d);
_.defaults($scope.pulldown,_d);
$scope.init = function() {
// Provide a combined skeleton for panels that must interact with panel and row.
// This might create name spacing issues.
$scope.panel = $scope.pulldown;
$scope.row = $scope.pulldown;
};
$scope.init = function() {
// Provide a combined skeleton for panels that must interact with panel and row.
// This might create name spacing issues.
$scope.panel = $scope.pulldown;
$scope.row = $scope.pulldown;
};
$scope.toggle_pulldown = function(pulldown) {
pulldown.collapse = pulldown.collapse ? false : true;
if (!pulldown.collapse) {
$timeout(function() {
$scope.$broadcast('render');
});
} else {
$scope.row.notice = false;
}
};
$scope.toggle_pulldown = function(pulldown) {
pulldown.collapse = pulldown.collapse ? false : true;
if (!pulldown.collapse) {
$timeout(function() {
$scope.$broadcast('render');
});
} else {
$scope.row.notice = false;
}
};
$scope.init();
$scope.init();
}
);
});
});

View File

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

View File

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

View File

@@ -1,28 +1,37 @@
define([
'angular',
'app',
'underscore'
'lodash'
],
function (angular, app, _) {
'use strict';
var module = angular.module('kibana.controllers');
var module = angular.module('grafana.controllers');
module.controller('SubmenuCtrl', function($scope) {
var _d = {
enable: true
};
module.controller('SubmenuCtrl', function($scope, $q, $rootScope, templateValuesSrv) {
var _d = {
enable: true
};
_.defaults($scope.pulldown,_d);
_.defaults($scope.pulldown,_d);
$scope.init = function() {
$scope.panel = $scope.pulldown;
$scope.row = $scope.pulldown;
};
$scope.init = function() {
$scope.panel = $scope.pulldown;
$scope.row = $scope.pulldown;
$scope.variables = $scope.dashboard.templating.list;
};
$scope.init();
$scope.disableAnnotation = function (annotation) {
annotation.enable = !annotation.enable;
$rootScope.$broadcast('refresh');
};
}
);
$scope.setVariableValue = function(param, option) {
templateValuesSrv.setVariableValue(param, option);
};
});
$scope.init();
});
});

View File

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

View File

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

View File

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

View File

@@ -5,32 +5,36 @@
* This script generates a dashboard object that Grafana can load. It also takes a number of user
* supplied URL parameters (int ARGS variable)
*
* Return a dashboard object, or a function
*
* For async scripts, return a function, this function must take a single callback function as argument,
* call this callback function with the dashboard object (look at scripted_async.js for an example)
*/
'use strict';
// accessable variables in this scope
var window, document, ARGS, $, jQuery, moment, kbn;
// Setup some variables
var dashboard, _d_timespan;
var dashboard, timspan;
// All url parameters are available via the ARGS object
var ARGS;
// Set a default timespan if one isn't specified
_d_timespan = '1d';
timspan = '1d';
// Intialize a skeleton with nothing but a rows array and service object
dashboard = {
rows : [],
services : {}
};
// Set a title
dashboard.title = 'Scripted dash';
dashboard.services.filter = {
time: {
from: "now-"+(ARGS.from || _d_timespan),
to: "now"
}
dashboard.time = {
from: "now-" + (ARGS.from || timspan),
to: "now"
};
var rows = 1;
@@ -52,7 +56,7 @@ for (var i = 0; i < rows; i++) {
panels: [
{
title: 'Events',
type: 'graphite',
type: 'graph',
span: 12,
fill: 1,
linewidth: 2,
@@ -67,8 +71,7 @@ for (var i = 0; i < rows; i++) {
}
]
});
}
// Now return the object and we're good!
return dashboard;
return dashboard;

View File

@@ -0,0 +1,79 @@
/* 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)
*
* Global accessable variables
* window, document, $, jQuery, ARGS, moment
*
* Return a dashboard object, or a function
*
* For async scripts, return a function, this function must take a single callback function,
* call this function with the dasboard object
*/
'use strict';
// accessable variables in this scope
var window, document, ARGS, $, jQuery, moment, kbn;
return function(callback) {
// Setup some variables
var dashboard, timspan;
// Set a default timespan if one isn't specified
timspan = '1d';
// Intialize a skeleton with nothing but a rows array and service object
dashboard = {
rows : [],
services : {}
};
// Set a title
dashboard.title = 'Scripted dash';
dashboard.time = {
from: "now-" + (ARGS.from || timspan),
to: "now"
};
var rows = 1;
var seriesName = 'argName';
if(!_.isUndefined(ARGS.rows)) {
rows = parseInt(ARGS.rows, 10);
}
if(!_.isUndefined(ARGS.name)) {
seriesName = ARGS.name;
}
$.ajax({
method: 'GET',
url: '/'
})
.done(function(result) {
dashboard.rows.push({
title: 'Chart',
height: '300px',
panels: [
{
title: 'Async dashboard test',
type: 'text',
span: 12,
fill: 1,
content: '# Async test'
}
]
});
// when dashboard is composed call the callback
// function and pass the dashboard
callback(dashboard);
});
}

View File

@@ -0,0 +1,96 @@
/* global _ */
/*
* Complex scripted dashboard
* This script generates a dashboard object that Grafana can load. It also takes a number of user
* supplied URL parameters (int ARGS variable)
*
* Return a dashboard object, or a function
*
* For async scripts, return a function, this function must take a single callback function as argument,
* call this callback function with the dashboard object (look at scripted_async.js for an example)
*/
'use strict';
// accessable variables in this scope
var window, document, ARGS, $, jQuery, moment, kbn;
// Setup some variables
var dashboard, timspan;
// All url parameters are available via the ARGS object
var ARGS;
// Set a default timespan if one isn't specified
timspan = '1d';
// Intialize a skeleton with nothing but a rows array and service object
dashboard = {
rows : [],
};
// Set a title
dashboard.title = 'Scripted dash';
dashboard.time = {
from: "now-" + (ARGS.from || timspan),
to: "now"
};
dashboard.templating = {
enable: true,
list: [
{
name: 'test',
query: 'apps.backend.*',
refresh: true,
options: [],
current: null,
},
{
name: 'test2',
query: '*',
refresh: true,
options: [],
current: null,
}
]
};
var rows = 1;
var seriesName = 'argName';
if(!_.isUndefined(ARGS.rows)) {
rows = parseInt(ARGS.rows, 10);
}
if(!_.isUndefined(ARGS.name)) {
seriesName = ARGS.name;
}
for (var i = 0; i < rows; i++) {
dashboard.rows.push({
title: 'Chart',
height: '300px',
panels: [
{
title: 'Events',
type: 'graph',
span: 12,
fill: 1,
linewidth: 2,
targets: [
{
'target': "randomWalk('" + seriesName + "')"
},
{
'target': "randomWalk('[[test2]]')"
}
],
}
]
});
}
return dashboard;

View File

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

View File

@@ -1,16 +1,15 @@
define([
'angular',
'app',
'underscore',
'lodash',
'jquery',
'../services/graphite/gfunc',
],
function (angular, app, _, $, gfunc) {
'use strict';
angular
.module('kibana.directives')
.module('grafana.directives')
.directive('graphiteAddFunc', function($compile) {
var inputTemplate = '<input type="text"'+
' class="grafana-target-segment-input input-medium grafana-target-segment-input"' +
@@ -18,7 +17,7 @@ function (angular, app, _, $, gfunc) {
var buttonTemplate = '<a class="grafana-target-segment grafana-target-function dropdown-toggle"' +
' tabindex="1" gf-dropdown="functionMenu" data-toggle="dropdown"' +
' data-placement="bottom"><i class="icon-plus"></i></a>';
' data-placement="top"><i class="icon-plus"></i></a>';
return {
link: function($scope, elem) {
@@ -39,6 +38,15 @@ function (angular, app, _, $, gfunc) {
items: 10,
updater: function (value) {
var funcDef = gfunc.getFuncDef(value);
if (!funcDef) {
// try find close match
value = value.toLowerCase();
funcDef = _.find(allFunctions, function(funcName) {
return funcName.toLowerCase().indexOf(value) === 0;
});
if (!funcDef) { return; }
}
$scope.$apply(function() {
$scope.addFunction(funcDef);
@@ -98,4 +106,4 @@ function (angular, app, _, $, gfunc) {
};
});
}
});
});

View File

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

View File

@@ -1,10 +1,10 @@
define([
'./addPanel',
'./arrayJoin',
'./dashUpload',
'./kibanaPanel',
'./kibanaSimplePanel',
'./grafanaPanel',
'./grafanaSimplePanel',
'./ngBlur',
'./dashEditLink',
'./ngModelOnBlur',
'./tip',
'./confirmClick',
@@ -14,5 +14,9 @@ define([
'./bootstrap-tagsinput',
'./bodyClass',
'./addGraphiteFunc',
'./graphiteFuncEditor'
], function () {});
'./graphiteFuncEditor',
'./templateParamSelector',
'./graphiteSegment',
'./grafanaVersionCheck',
'./influxdbFuncEditor'
], function () {});

View File

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

View File

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

View File

@@ -7,7 +7,7 @@ function (angular, $) {
'use strict';
angular
.module('kibana.directives')
.module('grafana.directives')
.directive('bootstrapTagsinput', function() {
function getItemProperty(scope, property) {
@@ -84,16 +84,15 @@ function (angular, $) {
});
angular
.module('kibana.directives')
.module('grafana.directives')
.directive('gfDropdown', function ($parse, $compile, $timeout) {
function buildTemplate(items, ul) {
if (!ul) {
ul = [
'<ul class="dropdown-menu" role="menu" aria-labelledby="drop1">',
'</ul>'
];
}
function buildTemplate(items, placement) {
var upclass = placement === 'top' ? 'dropup' : '';
var ul = [
'<ul class="dropdown-menu ' + upclass + '" role="menu" aria-labelledby="drop1">',
'</ul>'
];
angular.forEach(items, function (item, index) {
if (item.divider) {
@@ -103,7 +102,7 @@ function (angular, $) {
var li = '<li' + (item.submenu && item.submenu.length ? ' class="dropdown-submenu"' : '') + '>' +
'<a tabindex="-1" ng-href="' + (item.href || '') + '"' + (item.click ? ' ng-click="' + item.click + '"' : '') +
(item.target ? ' target="' + item.target + '"' : '') + (item.method ? ' data-method="' + item.method + '"' : '') +
(item.configModal ? ' config-modal="' + item.configModal + '"' : "") +
(item.configModal ? ' dash-editor-link="' + item.configModal + '"' : "") +
'>' + (item.text || '') + '</a>';
if (item.submenu && item.submenu.length) {
@@ -122,12 +121,14 @@ function (angular, $) {
link: function postLink(scope, iElement, iAttrs) {
var getter = $parse(iAttrs.gfDropdown), items = getter(scope);
$timeout(function () {
var dropdown = angular.element(buildTemplate(items).join(''));
var placement = iElement.data('placement');
var dropdown = angular.element(buildTemplate(items, placement).join(''));
dropdown.insertAfter(iElement);
$compile(iElement.next('ul.dropdown-menu'))(scope);
});
iElement.addClass('dropdown-toggle').attr('data-toggle', 'dropdown');
}
};
});
});
});

View File

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

View File

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

View File

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

View File

@@ -4,9 +4,9 @@ define([
function (angular) {
'use strict';
var module = angular.module('kibana.directives');
var module = angular.module('grafana.directives');
module.directive('dashUpload', function(timer, dashboard, alertSrv){
module.directive('dashUpload', function(timer, alertSrv) {
return {
restrict: 'A',
link: function(scope) {
@@ -14,8 +14,10 @@ function (angular) {
var files = evt.target.files; // FileList object
var readerOnload = function() {
return function(e) {
dashboard.dash_load(JSON.parse(e.target.result));
scope.$apply();
var dashboard = JSON.parse(e.target.result);
scope.$apply(function() {
scope.emitAppEvent('setup-dashboard', dashboard);
});
};
};
for (var i = 0, f; f = files[i]; i++) {
@@ -34,4 +36,4 @@ function (angular) {
}
};
});
});
});

182
src/app/directives/grafanaGraph.js Normal file → Executable file
View File

@@ -3,63 +3,94 @@ define([
'jquery',
'kbn',
'moment',
'underscore'
'lodash'
],
function (angular, $, kbn, moment, _) {
'use strict';
var module = angular.module('kibana.directives');
var module = angular.module('grafana.directives');
module.directive('grafanaGraph', function(filterSrv, $rootScope, dashboard) {
module.directive('grafanaGraph', function($rootScope, timeSrv) {
return {
restrict: 'A',
template: '<div> </div>',
link: function(scope, elem) {
var data, plot;
var data, annotations;
var hiddenData = {};
var dashboard = scope.dashboard;
var legendSideLastValue = null;
scope.$on('refresh',function() {
if (scope.otherPanelInFullscreenMode()) { return; }
scope.get_data();
});
scope.$on('toggleLegend', function(e, alias) {
if (hiddenData[alias]) {
data.push(hiddenData[alias]);
delete hiddenData[alias];
}
scope.$on('toggleLegend', function(e, series) {
_.each(series, function(serie) {
if (hiddenData[serie.alias]) {
data.push(hiddenData[serie.alias]);
delete hiddenData[serie.alias];
}
});
render_panel();
});
// Receive render events
scope.$on('render',function(event, d) {
data = d || data;
render_panel();
});
// Re-render if the window is resized
angular.element(window).bind('resize', function() {
scope.$on('render',function(event, renderData) {
data = renderData || data;
if (!data) {
scope.get_data();
return;
}
annotations = data.annotations || annotations;
render_panel();
});
function setElementHeight() {
try {
elem.css({ height: scope.height || scope.panel.height || scope.row.height });
var height = scope.height || scope.panel.height || scope.row.height;
if (_.isString(height)) {
height = parseInt(height.replace('px', ''), 10);
}
height = height - 32; // subtract panel title bar
if (scope.panel.legend.show && !scope.panel.legend.rightSide) {
height = height - 21; // subtract one line legend
}
elem.css('height', height + 'px');
return true;
} catch(e) { // IE throws errors sometimes
return false;
}
}
// Function for rendering panel
function render_panel() {
if (!data) { return; }
if (scope.otherPanelInFullscreenMode()) { return; }
if (!setElementHeight()) { return; }
function shouldAbortRender() {
if (!data) {
return true;
}
if ($rootScope.fullscreen && !scope.fullscreen) {
return true;
}
if (!setElementHeight()) { return true; }
if (_.isString(data)) {
render_panel_as_graphite_png(data);
return true;
}
if (elem.width() === 0) {
return;
}
}
// Function for rendering panel
function render_panel() {
if (shouldAbortRender()) {
return;
}
@@ -86,7 +117,7 @@ function (angular, $, kbn, moment, _) {
lines: {
show: panel.lines,
zero: false,
fill: panel.fill === 0 ? 0.001 : panel.fill/10,
fill: translateFillOption(panel.fill),
lineWidth: panel.linewidth,
steps: panel.steppedLine
},
@@ -108,6 +139,7 @@ function (angular, $, kbn, moment, _) {
yaxes: [],
xaxis: {},
grid: {
minBorderMargin: 0,
markings: [],
backgroundColor: null,
borderWidth: 0,
@@ -121,11 +153,12 @@ function (angular, $, kbn, moment, _) {
};
for (var i = 0; i < data.length; i++) {
var _d = data[i].getFlotPairs(panel.nullPointMode, panel.y_formats);
data[i].data = _d;
var series = data[i];
series.applySeriesOverrides(panel.seriesOverrides);
series.data = series.getFlotPairs(panel.nullPointMode, panel.y_formats);
}
if (panel.bars && data.length && data[0].info.timeStep) {
if (data.length && data[0].info.timeStep) {
options.series.bars.barWidth = data[0].info.timeStep / 1.5;
}
@@ -134,9 +167,39 @@ function (angular, $, kbn, moment, _) {
addAnnotations(options);
configureAxisOptions(data, options);
plot = $.plot(elem, data, options);
var sortedSeries = _.sortBy(data, function(series) { return series.zindex; });
addAxisLabels();
function callPlot() {
try {
$.plot(elem, sortedSeries, options);
} catch (e) {
console.log('flotcharts error', e);
}
addAxisLabels();
}
if (shouldDelayDraw(panel)) {
setTimeout(callPlot, 50);
legendSideLastValue = panel.legend.rightSide;
}
else {
callPlot();
}
}
function translateFillOption(fill) {
return fill === 0 ? 0.001 : fill/10;
}
function shouldDelayDraw(panel) {
if (panel.legend.rightSide) {
return true;
}
if (legendSideLastValue !== null && panel.legend.rightSide !== legendSideLastValue) {
return true;
}
return false;
}
function addTimeAxis(options) {
@@ -145,7 +208,7 @@ function (angular, $, kbn, moment, _) {
var max = _.isUndefined(scope.range.to) ? null : scope.range.to.getTime();
options.xaxis = {
timezone: dashboard.current.timezone,
timezone: dashboard.timezone,
show: scope.panel['x-axis'],
mode: "time",
min: min,
@@ -180,13 +243,13 @@ function (angular, $, kbn, moment, _) {
}
function addAnnotations(options) {
if(!data.annotations || data.annotations.length === 0) {
if(!annotations || annotations.length === 0) {
return;
}
var types = {};
_.each(data.annotations, function(event) {
_.each(annotations, function(event) {
if (!types[event.annotation.name]) {
types[event.annotation.name] = {
level: _.keys(types).length + 1,
@@ -209,7 +272,7 @@ function (angular, $, kbn, moment, _) {
options.events = {
levels: _.keys(types).length + 1,
data: data.annotations,
data: annotations,
types: types
};
}
@@ -231,8 +294,8 @@ function (angular, $, kbn, moment, _) {
var defaults = {
position: 'left',
show: scope.panel['y-axis'],
min: scope.panel.grid.min,
max: scope.panel.percentage && scope.panel.stack ? 100 : scope.panel.grid.max,
min: scope.panel.grid.leftMin,
max: scope.panel.percentage && scope.panel.stack ? 100 : scope.panel.grid.leftMax,
};
options.yaxes.push(defaults);
@@ -240,6 +303,8 @@ function (angular, $, kbn, moment, _) {
if (_.findWhere(data, {yaxis: 2})) {
var secondY = _.clone(defaults);
secondY.position = 'right';
secondY.min = scope.panel.grid.rightMin;
secondY.max = scope.panel.percentage && scope.panel.stack ? 100 : scope.panel.grid.rightMax;
options.yaxes.push(secondY);
configureAxisMode(options.yaxes[1], scope.panel.y_formats[1]);
}
@@ -248,12 +313,7 @@ function (angular, $, kbn, moment, _) {
}
function configureAxisMode(axis, format) {
if (format === 'bytes') {
axis.mode = 'byte';
}
else if (format !== 'none') {
axis.tickFormatter = kbn.getFormatFunction(format, 1);
}
axis.tickFormatter = kbn.getFormatFunction(format, 1);
}
function time_format(interval, ticks, min, max) {
@@ -278,7 +338,7 @@ function (angular, $, kbn, moment, _) {
return "%H:%M";
}
var $tooltip = $('<div>');
var $tooltip = $('<div id="tooltip">');
elem.bind("plothover", function (event, pos, item) {
var group, value, timestamp, seriesInfo, format;
@@ -290,7 +350,7 @@ function (angular, $, kbn, moment, _) {
if (seriesInfo.alias) {
group = '<small style="font-size:0.9em;">' +
'<i class="icon-circle" style="color:'+item.series.color+';"></i>' + ' ' +
(seriesInfo.alias || seriesInfo.query)+
seriesInfo.alias +
'</small><br>';
} else {
group = kbn.query_color_dot(item.series.color, 15) + ' ';
@@ -303,16 +363,10 @@ function (angular, $, kbn, moment, _) {
value = item.datapoint[1];
}
value = kbn.getFormatFunction(format, 2)(value);
value = kbn.getFormatFunction(format, 2)(value, item.series.yaxis);
timestamp = dashboard.formatDate(item.datapoint[0]);
timestamp = dashboard.current.timezone === 'browser' ?
moment(item.datapoint[0]).format('YYYY-MM-DD HH:mm:ss') :
moment.utc(item.datapoint[0]).format('YYYY-MM-DD HH:mm:ss');
$tooltip
.html(
group + value + " @ " + timestamp
)
.place_tt(pos.pageX, pos.pageY);
$tooltip.html(group + value + " @ " + timestamp).place_tt(pos.pageX, pos.pageY);
} else {
$tooltip.detach();
}
@@ -321,14 +375,16 @@ function (angular, $, kbn, moment, _) {
function render_panel_as_graphite_png(url) {
url += '&width=' + elem.width();
url += '&height=' + elem.css('height').replace('px', '');
url += '&bgcolor=1f1f1f'; // @grayDarker & @kibanaPanelBackground
url += '&bgcolor=1f1f1f'; // @grayDarker & @grafanaPanelBackground
url += '&fgcolor=BBBFC2'; // @textColor & @grayLighter
url += scope.panel.stack ? '&areaMode=stacked' : '';
url += scope.panel.fill !== 0 ? ('&areaAlpha=' + (scope.panel.fill/10).toFixed(1)) : '';
url += scope.panel.linewidth !== 0 ? '&lineWidth=' + scope.panel.linewidth : '';
url += scope.panel.legend ? '' : '&hideLegend=true';
url += scope.panel.grid.min ? '&yMin=' + scope.panel.grid.min : '';
url += scope.panel.grid.max ? '&yMax=' + scope.panel.grid.max : '';
url += scope.panel.legend.show ? '&hideLegend=false' : '&hideLegend=true';
url += scope.panel.grid.leftMin !== null ? '&yMin=' + scope.panel.grid.leftMin : '';
url += scope.panel.grid.leftMax !== null ? '&yMax=' + scope.panel.grid.leftMax : '';
url += scope.panel.grid.rightMin !== null ? '&yMin=' + scope.panel.grid.rightMin : '';
url += scope.panel.grid.rightMax !== null ? '&yMax=' + scope.panel.grid.rightMax : '';
url += scope.panel['x-axis'] ? '' : '&hideAxes=true';
url += scope.panel['y-axis'] ? '' : '&hideYAxis=true';
@@ -336,6 +392,12 @@ function (angular, $, kbn, moment, _) {
case 'bytes':
url += '&yUnitSystem=binary';
break;
case 'bits':
url += '&yUnitSystem=binary';
break;
case 'bps':
url += '&yUnitSystem=si';
break;
case 'short':
url += '&yUnitSystem=si';
break;
@@ -361,9 +423,11 @@ function (angular, $, kbn, moment, _) {
}
elem.bind("plotselected", function (event, ranges) {
filterSrv.setTime({
from : moment.utc(ranges.xaxis.from).toDate(),
to : moment.utc(ranges.xaxis.to).toDate(),
scope.$apply(function() {
timeSrv.setTime({
from : moment.utc(ranges.xaxis.from).toDate(),
to : moment.utc(ranges.xaxis.to).toDate(),
});
});
});
}

View File

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

View File

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

View File

@@ -0,0 +1,33 @@
define([
'angular'
],
function (angular) {
'use strict';
angular
.module('grafana.directives')
.directive('grafanaVersionCheck', function($http, grafanaVersion) {
return {
restrict: 'A',
link: function(scope, elem) {
if (grafanaVersion[0] === '@') {
return;
}
$http({ method: 'GET', url: 'https://grafanarel.s3.amazonaws.com/latest.json' })
.then(function(response) {
if (!response.data || !response.data.version) {
return;
}
if (grafanaVersion !== response.data.version) {
elem.append('<i class="icon-info-sign"></i> ' +
'<a href="http://grafana.org/download" target="_blank"> ' +
'New version available: ' + response.data.version +
'</a>');
}
});
}
};
});
});

View File

@@ -1,14 +1,14 @@
define([
'angular',
'underscore',
'lodash',
'jquery',
],
function (angular, _, $) {
'use strict';
angular
.module('kibana.directives')
.directive('graphiteFuncEditor', function($compile) {
.module('grafana.directives')
.directive('graphiteFuncEditor', function($compile, templateSrv) {
var funcSpanTemplate = '<a ng-click="">{{func.def.name}}</a><span>(</span>';
var paramTemplate = '<input type="text" style="display:none"' +
@@ -29,6 +29,8 @@ function (angular, _, $) {
var $funcControls = $(funcControlsTemplate);
var func = $scope.func;
var funcDef = func.def;
var scheduledRelink = false;
var paramCountAtLink = 0;
function clickFuncParam(paramIndex) {
/*jshint validthis:true */
@@ -51,20 +53,33 @@ function (angular, _, $) {
}
}
function scheduledRelinkIfNeeded() {
if (paramCountAtLink === func.params.length) {
return;
}
if (!scheduledRelink) {
scheduledRelink = true;
setTimeout(function() {
relink();
scheduledRelink = false;
}, 200);
}
}
function inputBlur(paramIndex) {
/*jshint validthis:true */
var $input = $(this);
var $link = $input.prev();
var newValue = $input.val();
if ($input.val() !== '') {
$link.text($input.val());
if (newValue !== '' || func.def.params[paramIndex].optional) {
$link.html(templateSrv.highlightVariablesAsHtml(newValue));
if (func.updateParam($input.val(), paramIndex)) {
$scope.$apply(function() {
$scope.targetChanged();
});
}
func.updateParam($input.val(), paramIndex);
scheduledRelinkIfNeeded();
$scope.$apply($scope.targetChanged);
}
$input.hide();
@@ -73,7 +88,6 @@ function (angular, _, $) {
function inputKeyPress(paramIndex, e) {
/*jshint validthis:true */
if(e.which === 13) {
inputBlur.call(this, paramIndex);
}
@@ -89,7 +103,7 @@ function (angular, _, $) {
var options = funcDef.params[paramIndex].options;
if (funcDef.params[paramIndex].type === 'int') {
options = _.map(options, function(val) { return val.toString(); } );
options = _.map(options, function(val) { return val.toString(); });
}
$input.typeahead({
@@ -132,9 +146,20 @@ function (angular, _, $) {
$funcLink.appendTo(elem);
_.each(funcDef.params, function(param, index) {
var $paramLink = $('<a ng-click="" class="graphite-func-param-link">' + func.params[index] + '</a>');
if (param.optional && func.params.length <= index) {
return;
}
if (index > 0) {
$('<span>, </span>').appendTo(elem);
}
var paramValue = templateSrv.highlightVariablesAsHtml(func.params[index]);
var $paramLink = $('<a ng-click="" class="graphite-func-param-link">' + paramValue + '</a>');
var $input = $(paramTemplate);
paramCountAtLink++;
$paramLink.appendTo(elem);
$input.appendTo(elem);
@@ -143,10 +168,6 @@ function (angular, _, $) {
$input.keypress(_.partial(inputKeyPress, index));
$paramLink.click(_.partial(clickFuncParam, index));
if (index !== funcDef.params.length - 1) {
$('<span>, </span>').appendTo(elem);
}
if (funcDef.params[index].options) {
addTypeahead($input, index);
}
@@ -203,14 +224,19 @@ function (angular, _, $) {
});
}
addElementsAndCompile();
ifJustAddedFocusFistParam();
registerFuncControlsToggle();
registerFuncControlsActions();
function relink() {
elem.children().remove();
addElementsAndCompile();
ifJustAddedFocusFistParam();
registerFuncControlsToggle();
registerFuncControlsActions();
}
relink();
}
};
});
});
});

View File

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

View File

@@ -0,0 +1,136 @@
define([
'angular',
'lodash',
'jquery',
],
function (angular, _, $) {
'use strict';
angular
.module('grafana.directives')
.directive('influxdbFuncEditor', function($compile) {
var funcSpanTemplate = '<a gf-dropdown="functionMenu" class="dropdown-toggle" ' +
'data-toggle="dropdown">{{target.function}}</a><span>(</span>';
var paramTemplate = '<input type="text" style="display:none"' +
' class="input-mini grafana-function-param-input"></input>';
return {
restrict: 'A',
link: function postLink($scope, elem) {
var $funcLink = $(funcSpanTemplate);
$scope.functionMenu = _.map($scope.functions, function(func) {
return {
text: func,
click: "changeFunction('" + func + "');"
};
});
function clickFuncParam() {
/*jshint validthis:true */
var $link = $(this);
var $input = $link.next();
$input.val($scope.target.column);
$input.css('width', ($link.width() + 16) + 'px');
$link.hide();
$input.show();
$input.focus();
$input.select();
var typeahead = $input.data('typeahead');
if (typeahead) {
$input.val('');
typeahead.lookup();
}
}
function inputBlur() {
/*jshint validthis:true */
var $input = $(this);
var $link = $input.prev();
if ($input.val() !== '') {
$link.text($input.val());
$scope.target.column = $input.val();
$scope.$apply($scope.get_data);
}
$input.hide();
$link.show();
}
function inputKeyPress(e) {
/*jshint validthis:true */
if(e.which === 13) {
inputBlur.call(this);
}
}
function inputKeyDown() {
/*jshint validthis:true */
this.style.width = (3 + this.value.length) * 8 + 'px';
}
function addTypeahead($input) {
$input.attr('data-provide', 'typeahead');
$input.typeahead({
source: function () {
return $scope.listColumns.apply(null, arguments);
},
minLength: 0,
items: 20,
updater: function (value) {
setTimeout(function() {
inputBlur.call($input[0]);
}, 0);
return value;
}
});
var typeahead = $input.data('typeahead');
typeahead.lookup = function () {
var items;
this.query = this.$element.val() || '';
items = this.source(this.query, $.proxy(this.process, this));
return items ? this.process(items) : items;
};
}
function addElementsAndCompile() {
$funcLink.appendTo(elem);
var $paramLink = $('<a ng-click="" class="graphite-func-param-link">' + $scope.target.column + '</a>');
var $input = $(paramTemplate);
$paramLink.appendTo(elem);
$input.appendTo(elem);
$input.blur(inputBlur);
$input.keyup(inputKeyDown);
$input.keypress(inputKeyPress);
$paramLink.click(clickFuncParam);
addTypeahead($input);
$('<span>)</span>').appendTo(elem);
$compile(elem.contents())($scope);
}
addElementsAndCompile();
}
};
});
});

View File

@@ -1,231 +0,0 @@
define([
'angular',
'jquery',
'underscore'
],
function (angular, $, _) {
'use strict';
angular
.module('kibana.directives')
.directive('kibanaPanel', function($compile, $timeout, $rootScope) {
var container = '<div class="panel-container"></div>';
var content = '<div class="panel-content"></div>';
var panelHeader =
'<div class="panel-header">'+
'<div class="row-fluid">' +
'<div class="span12 alert-error panel-error" ng-hide="!panel.error">' +
'<a class="close" ng-click="panel.error=false">&times;</a>' +
'<i class="icon-exclamation-sign"></i> <strong>Oops!</strong> {{panel.error}}' +
'</div>' +
'</div>\n' +
'<div class="row-fluid panel-extra">' +
'<div class="panel-extra-container">' +
'<span class="panel-loading" ng-show="panelMeta.loading == true">' +
'<i class="icon-spinner icon-spin icon-large"></i>' +
'</span>' +
'<span class="dropdown">' +
'<span class="panel-text panel-title pointer" gf-dropdown="panelMeta.menu" tabindex="1" ' +
'data-drag=true data-jqyoui-options="kbnJqUiDraggableOptions"'+
' jqyoui-draggable="'+
'{'+
'animate:false,'+
'mutate:false,'+
'index:{{$index}},'+
'onStart:\'panelMoveStart\','+
'onStop:\'panelMoveStop\''+
'}" ng-model="row.panels" ' +
'>' +
'{{panel.title || "No title"}}' +
'</span>' +
'</span>'+
'</div>'+
'</div>\n'+
'</div>';
return {
restrict: 'E',
link: function($scope, elem, attr) {
// once we have the template, scan it for controllers and
// load the module.js if we have any
var newScope = $scope.$new();
$scope.kbnJqUiDraggableOptions = {
revert: 'invalid',
helper: function() {
return $('<div style="width:200px;height:100px;background: rgba(100,100,100,0.50);"/>');
},
placeholder: 'keep'
};
// compile the module and uncloack. We're done
function loadModule($module) {
$module.appendTo(elem);
elem.wrap(container);
/* jshint indent:false */
$compile(elem.contents())(newScope);
elem.removeClass("ng-cloak");
}
newScope.$on('$destroy',function(){
elem.unbind();
elem.remove();
});
$scope.$watch(attr.type, function (name) {
elem.addClass("ng-cloak");
// load the panels module file, then render it in the dom.
var nameAsPath = name.replace(".", "/");
$scope.require([
'jquery',
'text!panels/'+nameAsPath+'/module.html'
], function ($, moduleTemplate) {
var $module = $(moduleTemplate);
// top level controllers
var $controllers = $module.filter('ngcontroller, [ng-controller], .ng-controller');
// add child controllers
$controllers = $controllers.add($module.find('ngcontroller, [ng-controller], .ng-controller'));
if ($controllers.length) {
$controllers.first().prepend(panelHeader);
$controllers.first().find('.panel-header').nextAll().wrapAll(content);
$scope.require(['panels/' + nameAsPath + '/module'], function() {
loadModule($module);
});
} else {
loadModule($module);
}
});
});
/*
/* Panel base functionality
/* */
newScope.initPanel = function(scope) {
scope.updateColumnSpan = function(span) {
scope.panel.span = span;
$timeout(function() {
scope.$emit('render');
});
};
function enterFullscreenMode(options) {
var docHeight = $(window).height();
var editHeight = Math.floor(docHeight * 0.3);
var fullscreenHeight = Math.floor(docHeight * 0.7);
var oldTimeRange = scope.range;
scope.height = options.edit ? editHeight : fullscreenHeight;
scope.editMode = options.edit;
if (!scope.fullscreen) {
var closeEditMode = $rootScope.$on('panel-fullscreen-exit', function() {
scope.editMode = false;
scope.fullscreen = false;
delete scope.height;
closeEditMode();
$timeout(function() {
if (oldTimeRange !== $scope.range) {
scope.dashboard.refresh();
}
else {
scope.$emit('render');
}
});
});
}
$(window).scrollTop(0);
scope.fullscreen = true;
$rootScope.$emit('panel-fullscreen-enter');
$timeout(function() {
scope.$emit('render');
});
}
scope.toggleFullscreenEdit = function() {
if (scope.editMode) {
$rootScope.$emit('panel-fullscreen-exit');
return;
}
enterFullscreenMode({edit: true});
};
$scope.toggleFullscreen = function() {
if (scope.fullscreen && !scope.editMode) {
$rootScope.$emit('panel-fullscreen-exit');
return;
}
enterFullscreenMode({ edit: false });
};
var menu = [
{
text: 'Edit',
configModal: "app/partials/paneleditor.html",
condition: !scope.panelMeta.fullscreenEdit
},
{
text: 'Edit',
click: "toggleFullscreenEdit()",
condition: scope.panelMeta.fullscreenEdit
},
{
text: "Fullscreen",
click: 'toggleFullscreen()',
condition: scope.panelMeta.fullscreenView
},
{
text: 'Duplicate',
click: 'duplicatePanel(panel)',
condition: true
},
{
text: 'Span',
submenu: [
{ text: '1', click: 'updateColumnSpan(1)' },
{ text: '2', click: 'updateColumnSpan(2)' },
{ text: '3', click: 'updateColumnSpan(3)' },
{ text: '4', click: 'updateColumnSpan(4)' },
{ text: '5', click: 'updateColumnSpan(5)' },
{ text: '6', click: 'updateColumnSpan(6)' },
{ text: '7', click: 'updateColumnSpan(7)' },
{ text: '8', click: 'updateColumnSpan(8)' },
{ text: '9', click: 'updateColumnSpan(9)' },
{ text: '10', click: 'updateColumnSpan(10)' },
{ text: '11', click: 'updateColumnSpan(11)' },
{ text: '12', click: 'updateColumnSpan(12)' },
],
condition: true
},
{
text: 'Remove',
click: 'remove_panel_from_row(row, panel)',
condition: true
}
];
scope.panelMeta.menu = _.where(menu, { condition: true });
};
}
};
});
});

View File

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

View File

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

View File

@@ -6,7 +6,7 @@ function (angular) {
'use strict';
angular
.module('kibana.directives')
.module('grafana.directives')
.directive('spectrumPicker', function() {
return {
restrict: 'E',

View File

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

View File

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

63
src/app/filters/all.js Normal file → Executable file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,89 +0,0 @@
/*
## filtering
*/
define([
'angular',
'app',
'underscore'
],
function (angular, app, _) {
'use strict';
var module = angular.module('kibana.panels.filtering', []);
app.useModule(module);
module.controller('filtering', function($scope, filterSrv, datasourceSrv, $rootScope, dashboard) {
$scope.panelMeta = {
status : "Stable",
description : "graphite target filters"
};
// Set and populate defaults
var _d = {
};
_.defaults($scope.panel,_d);
$scope.init = function() {
$scope.filterSrv = filterSrv;
};
$scope.remove = function(filter) {
filterSrv.remove(filter);
};
$scope.applyFilter = function(filter) {
datasourceSrv.default.metricFindQuery(filter.query)
.then(function (results) {
filter.editing=undefined;
filter.options = _.map(results, function(node) {
return { text: node.text, value: node.text };
});
if (filter.includeAll) {
if(endsWithWildcard(filter.query)) {
filter.options.unshift({text: 'All', value: '*'});
}
else {
var allExpr = '{';
_.each(filter.options, function(option) {
allExpr += option.text + ',';
});
allExpr = allExpr.substring(0, allExpr.length - 1) + '}';
filter.options.unshift({text: 'All', value: allExpr});
}
}
filterSrv.filterOptionSelected(filter, filter.options[0]);
});
};
$scope.add = function() {
filterSrv.add({
type : 'filter',
name : 'filter name',
editing : true,
query : 'metric.path.query.*',
});
};
$scope.refresh = function() {
dashboard.refresh();
};
$scope.render = function() {
$rootScope.$broadcast('render');
};
function endsWithWildcard(query) {
if (query.length === 0) {
return false;
}
return query[query.length - 1] === '*';
}
});
});

View File

@@ -1,81 +1,57 @@
<div class="editor-row">
<div class="section">
<h5>Axes</h5>
<div class="editor-option">
<label class="small">X-Axis</label><input type="checkbox" ng-model="panel['x-axis']" ng-checked="panel['x-axis']" ng-change="render()">
</div>
<div class="editor-option">
<label class="small">Y-Axis</label><input type="checkbox" ng-model="panel['y-axis']" ng-checked="panel['y-axis']" ng-change="render()">
</div>
<div class="editor-option">
<label class="small">Left Y Format <tip>Y-axis formatting</tip></label>
<select class="input-small" ng-model="panel.y_formats[0]" ng-options="f for f in ['none','short','bytes', 'ms', 'µs']" ng-change="render()"></select>
</div>
<div class="editor-option">
<label class="small">Right Y Format <tip>Y-axis formatting</tip></label>
<select class="input-small" ng-model="panel.y_formats[1]" ng-options="f for f in ['none','short','bytes', 'ms', 'µs']" ng-change="render()"></select>
</div>
<div class="editor-option">
<label class="small">Left Y-axis label</label>
<input ng-change="get_data()" ng-model-onblur placeholder="" type="text" class="input-medium" ng-model="panel.leftYAxisLabel">
</div>
<div class="editor-option">
<label class="small">Right Y-axis label</label>
<input ng-change="get_data()" ng-model-onblur placeholder="" type="text" class="input-medium" ng-model="panel.rightYAxisLabel">
</div>
<h5>Left Y Axis</h5>
<div class="editor-option">
<label class="small">Format <tip>Y-axis formatting</tip></label>
<select class="input-small" ng-model="panel.y_formats[0]" ng-options="f for f in ['none','short','bytes', 'bits', 'bps', 's', 'ms', 'µs', 'ns', 'percent']" ng-change="render()"></select>
</div>
<div class="editor-option">
<label class="small">Min / <a ng-click="toggleGridMinMax('leftMin')">Auto <i class="icon-star" ng-show="_.isNull(panel.grid.leftMin)"></i></a></label>
<input type="number" class="input-small" ng-model="panel.grid.leftMin" ng-change="render()" ng-model-onblur />
</div>
<div class="editor-option">
<label class="small">Max / <a ng-click="toggleGridMinMax('leftMax')">Auto <i class="icon-star" ng-show="_.isNull(panel.grid.leftMax)"></i></a></label>
<input type="number" class="input-small" ng-model="panel.grid.leftMax" ng-change="render()" ng-model-onblur />
</div>
<div class="editor-option">
<label class="small">Label</label>
<input ng-change="get_data()" ng-model-onblur placeholder="" type="text" class="input-medium" ng-model="panel.leftYAxisLabel">
</div>
</div>
<div class="section">
<h5>Right Y Axis</h5>
<div class="editor-option">
<label class="small">Format <tip>Y-axis formatting</tip></label>
<select class="input-small" ng-model="panel.y_formats[1]" ng-options="f for f in ['none','short','bytes', 'bits', 'bps', 's', 'ms', 'µs', 'ns', 'percent']" ng-change="render()"></select>
</div>
<div class="editor-option">
<label class="small">Min / <a ng-click="toggleGridMinMax('rightMin')">Auto <i class="icon-star" ng-show="_.isNull(panel.grid.rightMin)"></i></a></label>
<input type="number" class="input-small" ng-model="panel.grid.rightMin" ng-change="render()" ng-model-onblur />
</div>
<div class="editor-option">
<label class="small">Max / <a ng-click="toggleGridMinMax('rightMax')">Auto <i class="icon-star" ng-show="_.isNull(panel.grid.rightMax)"></i></a></label>
<input type="number" class="input-small" ng-model="panel.grid.rightMax" ng-change="render()" ng-model-onblur />
</div>
</div>
</div>
<div class="editor-row">
<div class="section">
<h5>Grid</h5>
<div class="editor-option">
<label class="small">Min / <a ng-click="toggleGridMinMax('min')">Auto <i class="icon-star" ng-show="_.isNull(panel.grid.min)"></i></a></label>
<input type="number" class="input-small" ng-model="panel.grid.min" ng-change="render()" ng-model-onblur />
</div>
<div class="editor-option">
<label class="small">Max / <a ng-click="toggleGridMinMax('max')">Auto <i class="icon-star" ng-show="_.isNull(panel.grid.max)"></i></a></label>
<input type="number" class="input-small" ng-model="panel.grid.max" ng-change="render()" ng-model-onblur />
</div>
</div>
<div class="section">
<h5>Grid thresholds</h5>
<div class="editor-option">
<label class="small">Level1</label>
<input type="number" class="input-small" ng-model="panel.grid.threshold1" ng-change="render()" ng-model-onblur />
</div>
<div class="editor-option">
<label class="small">Color</label>
<spectrum-picker ng-model="panel.grid.threshold1Color" ng-change="render()" ></spectrum-picker>
</div>
<div class="editor-option">
<label class="small">Level2</label>
<input type="number" class="input-small" ng-model="panel.grid.threshold2" ng-change="render()" ng-model-onblur />
</div>
<div class="editor-option">
<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>
</div>
<div class="section">
<h5>Legend</h5>
<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>
<div class="section" ng-if="panel.legend.values">
@@ -102,4 +78,37 @@
</div>
</div>
<div class="section">
<h5>Grid thresholds</h5>
<div class="editor-option">
<label class="small">Level1</label>
<input type="number" class="input-small" ng-model="panel.grid.threshold1" ng-change="render()" ng-model-onblur />
</div>
<div class="editor-option">
<label class="small">Color</label>
<spectrum-picker ng-model="panel.grid.threshold1Color" ng-change="render()" ></spectrum-picker>
</div>
<div class="editor-option">
<label class="small">Level2</label>
<input type="number" class="input-small" ng-model="panel.grid.threshold2" ng-change="render()" ng-model-onblur />
</div>
<div class="editor-option">
<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>
</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>
</div>
</div>

View File

@@ -0,0 +1,58 @@
<section class="graph-legend" ng-class="{'graph-legend-table': panel.legend.alignAsTable}">
<div class="graph-legend-series"
ng-repeat='series in legend'
ng-class="{'pull-right': series.yaxis === 2, 'graph-legend-series-hidden': hiddenSeries[series.alias]}"
>
<div class="graph-legend-icon">
<i class='icon-minus pointer' ng-style="{color: series.color}" bs-popover="'colorPopup.html'" data-placement="bottom">
</i>
</div>
<div class="graph-legend-alias small">
<a ng-click="toggleSeries(series, $event)" data-unique="1" data-placement="{{series.yaxis === 2 ? 'bottomRight' : 'bottomLeft'}}">
{{series.alias}}
</a>
</div>
<div class="graph-legend-value current small" ng-show="panel.legend.values && panel.legend.current" ng-bind="series.current">
</div>
<div class="graph-legend-value min small" ng-show="panel.legend.values && panel.legend.min" ng-bind="series.min">
</div>
<div class="graph-legend-value max small" ng-show="panel.legend.values && panel.legend.max" ng-bind="series.max">
</div>
<div class="graph-legend-value total small" ng-show="panel.legend.values && panel.legend.total" ng-bind="series.total">
</div>
<div class="graph-legend-value avg small" ng-show="panel.legend.values && panel.legend.avg" ng-bind="series.avg">
</div>
</div>
</section>
<script type="text/ng-template" id="colorPopup.html">
<div class="graph-legend-popover">
<a class="close" ng-click="dismiss();" href="">×</a>
<div class="editor-row small" style="padding-bottom: 0;">
<label>Axis:</label>
<button ng-click="toggleYAxis(series);dismiss();"
class="btn btn-mini"
ng-class="{'btn-success': series.yaxis === 1 }">
Left
</button>
<button ng-click="toggleYAxis(series);dismiss();"
class="btn btn-mini"
ng-class="{'btn-success': series.yaxis === 2 }">
Right
</button>
</div>
<div class="editor-row">
<i ng-repeat="color in colors"
class="pointer"
ng-class="{'icon-circle-blank': color === series.color,'icon-circle': color !== series.color}"
ng-style="{color:color}"
ng-click="changeSeriesColor(series, color);dismiss();">
</i>
</div>
</div>
</script>

View File

@@ -0,0 +1,43 @@
<div ng-controller='GraphCtrl'>
<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 grafana-graph class="pointer histogram-chart">
</div>
</div>
<div class="graph-legend-wrapper"
ng-if="panel.legend.show"
ng-include="'app/panels/graph/legend.html'">
</div>
</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-bar-chart"></i>
Graph
</div>
<div ng-model="editor.index" bs-tabs>
<div ng-repeat="tab in editorTabs" data-title="{{tab}}">
</div>
</div>
</div>
<div class="dashboard-editor-body">
<div ng-repeat="tab in panelMeta.fullEditorTabs" ng-if="editorTabs[editor.index] == tab.title">
<div ng-include src="tab.src"></div>
</div>
</div>
</div>
</div>

View File

@@ -1,40 +1,29 @@
/** @scratch /panels/5
* include::panels/histogram.asciidoc[]
*/
/** @scratch /panels/histogram/0
* == Histogram
* Status: *Stable*
*
* The histogram panel allow for the display of time charts. It includes several modes and tranformations
* to display event counts, mean, min, max and total of numeric fields, and derivatives of counter
* fields.
*
*/
define([
'angular',
'app',
'jquery',
'underscore',
'lodash',
'kbn',
'moment',
'./timeSeries',
'components/timeSeries',
'./seriesOverridesCtrl',
'services/panelSrv',
'services/annotationsSrv',
'services/datasourceSrv',
'jquery.flot',
'jquery.flot.events',
'jquery.flot.selection',
'jquery.flot.time',
'jquery.flot.byte',
'jquery.flot.stack',
'jquery.flot.stackpercent'
],
function (angular, app, $, _, kbn, moment, timeSeries) {
function (angular, app, $, _, kbn, moment, TimeSeries) {
'use strict';
var module = angular.module('kibana.panels.graphite', []);
var module = angular.module('grafana.panels.graph');
app.useModule(module);
module.controller('graphite', function($scope, $rootScope, filterSrv, datasourceSrv, $timeout, annotationsSrv) {
module.controller('GraphCtrl', function($scope, $rootScope, panelSrv, annotationsSrv, timeSrv) {
$scope.panelMeta = {
modals : [],
@@ -50,11 +39,11 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
},
{
title:'Axes & Grid',
src:'app/panels/graphite/axisEditor.html'
src:'app/panels/graph/axisEditor.html'
},
{
title:'Display Styles',
src:'app/panels/graphite/styleEditor.html'
src:'app/panels/graph/styleEditor.html'
}
],
fullscreenEdit: true,
@@ -84,7 +73,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
*/
scale : 1,
/** @scratch /panels/histogram/3
* y_formats :: 'none','bytes','short', 'ms'
* y_formats :: 'none','bytes','bits','bps','short', 's', 'ms'
*/
y_formats : ['short', 'short'],
/** @scratch /panels/histogram/5
@@ -93,8 +82,10 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
* grid.ma1::: Maximum y-axis value
*/
grid : {
max: null,
min: 0,
leftMax: null,
rightMax: null,
leftMin: null,
rightMin: null,
threshold1: null,
threshold2: null,
threshold1Color: 'rgba(216, 200, 27, 0.27)',
@@ -140,7 +131,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
*/
stack : false,
/** @scratch /panels/histogram/3
* legend:: Display the legond
* legend:: Display the legend
*/
legend: {
show: true, // disable/enable legend
@@ -172,96 +163,49 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
query_as_alias: true
},
targets: [],
targets: [{}],
aliasColors: {},
aliasYAxis: {},
seriesOverrides: [],
};
_.defaults($scope.panel,_d);
_.defaults($scope.panel.tooltip, _d.tooltip);
_.defaults($scope.panel.annotate, _d.annotate);
_.defaults($scope.panel.grid, _d.grid);
_.defaults($scope.panel.legend, _d.legend);
// backward compatible stuff
if (_.isBoolean($scope.panel.legend)) {
$scope.panel.legend = { show: $scope.panel.legend };
_.defaults($scope.panel.legend, _d.legend);
}
if ($scope.panel.y_format) {
$scope.panel.y_formats[0] = $scope.panel.y_format;
delete $scope.panel.y_format;
}
if ($scope.panel.y2_format) {
$scope.panel.y_formats[1] = $scope.panel.y2_format;
delete $scope.panel.y2_format;
}
$scope.init = function() {
$scope.initPanel($scope);
$scope.fullscreen = false;
$scope.editor = { index: 1 };
$scope.editorTabs = _.pluck($scope.panelMeta.fullEditorTabs,'title');
$scope.hiddenSeries = {};
$scope.datasources = datasourceSrv.listOptions();
$scope.setDatasource($scope.panel.datasource);
};
$scope.setDatasource = function(datasource) {
$scope.panel.datasource = datasource;
$scope.datasource = datasourceSrv.get(datasource);
if (!$scope.datasource) {
$scope.panel.error = "Cannot find datasource " + datasource;
return;
}
$scope.get_data();
};
$scope.removeTarget = function (target) {
$scope.panel.targets = _.without($scope.panel.targets, target);
$scope.get_data();
};
$scope.hiddenSeries = {};
$scope.updateTimeRange = function () {
$scope.range = filterSrv.timeRange();
$scope.rangeUnparsed = filterSrv.timeRange(false);
$scope.resolution = ($(window).width() * ($scope.panel.span / 12)) / 2;
$scope.interval = '10m';
if ($scope.range) {
$scope.interval = kbn.secondsToHms(
kbn.calculate_interval($scope.range.from, $scope.range.to, $scope.resolution, 0) / 1000
);
}
$scope.range = timeSrv.timeRange();
$scope.rangeUnparsed = timeSrv.timeRange(false);
$scope.resolution = Math.ceil($(window).width() * ($scope.panel.span / 12));
$scope.interval = kbn.calculateInterval($scope.range, $scope.resolution, $scope.panel.interval);
};
$scope.get_data = function() {
delete $scope.panel.error;
$scope.panelMeta.loading = true;
$scope.updateTimeRange();
var graphiteQuery = {
var metricsQuery = {
range: $scope.rangeUnparsed,
interval: $scope.interval,
targets: $scope.panel.targets,
format: $scope.panel.renderer === 'png' ? 'png' : 'json',
maxDataPoints: $scope.resolution,
datasource: $scope.panel.datasource
cacheTimeout: $scope.panel.cacheTimeout
};
$scope.annotationsPromise = annotationsSrv.getAnnotations($scope.rangeUnparsed);
$scope.annotationsPromise = annotationsSrv.getAnnotations($scope.rangeUnparsed, $scope.dashboard);
return $scope.datasource.query(graphiteQuery)
return $scope.datasource.query(metricsQuery)
.then($scope.dataHandler)
.then(null, function(err) {
$scope.panel.error = err.message || "Graphite HTTP Request Error";
$scope.panelMeta.loading = false;
$scope.panelMeta.error = err.message || "Timeseries data request error";
$scope.inspector.error = err;
$scope.render([]);
});
};
@@ -281,7 +225,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
var data = _.map(results.data, $scope.seriesHandler);
$scope.datapointsWarning = $scope.datapointsCount || !$scope.datapointsOutside;
$scope.datapointsWarning = $scope.datapointsCount === 0 || $scope.datapointsOutside;
$scope.annotationsPromise
.then(function(annotations) {
@@ -295,19 +239,16 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
$scope.seriesHandler = function(seriesData, index) {
var datapoints = seriesData.datapoints;
var alias = seriesData.target;
var color = $scope.panel.aliasColors[alias] || $scope.colors[index];
var yaxis = $scope.panel.aliasYAxis[alias] || 1;
var color = $scope.panel.aliasColors[alias] || $rootScope.colors[index];
var seriesInfo = {
alias: alias,
color: color,
enable: true,
yaxis: yaxis
};
$scope.legend.push(seriesInfo);
var series = new timeSeries.ZeroFilled({
var series = new TimeSeries({
datapoints: datapoints,
info: seriesInfo,
});
@@ -318,21 +259,13 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
if (last - from < -10000) {
$scope.datapointsOutside = true;
}
$scope.datapointsCount += datapoints.length;
}
$scope.datapointsCount += datapoints.length;
return series;
};
$scope.add_target = function() {
$scope.panel.targets.push({target: ''});
};
$scope.otherPanelInFullscreenMode = function() {
return $rootScope.fullscreen && !$scope.fullscreen;
};
$scope.render = function(data) {
$scope.$emit('render', data);
};
@@ -343,20 +276,62 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
$scope.render();
};
$scope.toggleSeries = function(info) {
if ($scope.hiddenSeries[info.alias]) {
delete $scope.hiddenSeries[info.alias];
$scope.toggleSeries = function(serie, event) {
if ($scope.hiddenSeries[serie.alias]) {
delete $scope.hiddenSeries[serie.alias];
}
else {
$scope.hiddenSeries[info.alias] = true;
$scope.hiddenSeries[serie.alias] = true;
}
$scope.$emit('toggleLegend', info.alias);
if (event.ctrlKey || event.metaKey || event.shiftKey) {
$scope.toggleSeriesExclusiveMode(serie);
}
$scope.$emit('toggleLegend', $scope.legend);
};
$scope.toggleSeriesExclusiveMode = function(serie) {
var hidden = $scope.hiddenSeries;
if (hidden[serie.alias]) {
delete hidden[serie.alias];
}
// check if every other series is hidden
var alreadyExclusive = _.every($scope.legend, function(value) {
if (value.alias === serie.alias) {
return true;
}
return hidden[value.alias];
});
if (alreadyExclusive) {
// remove all hidden series
_.each($scope.legend, function(value) {
delete $scope.hiddenSeries[value.alias];
});
}
else {
// hide all but this serie
_.each($scope.legend, function(value) {
if (value.alias === serie.alias) {
return;
}
$scope.hiddenSeries[value.alias] = true;
});
}
};
$scope.toggleYAxis = function(info) {
info.yaxis = info.yaxis === 2 ? 1 : 2;
$scope.panel.aliasYAxis[info.alias] = info.yaxis;
var override = _.findWhere($scope.panel.seriesOverrides, { alias: info.alias });
if (!override) {
override = { alias: info.alias };
$scope.panel.seriesOverrides.push(override);
}
override.yaxis = info.yaxis === 2 ? 1 : 2;
$scope.render();
};
@@ -365,8 +340,24 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
$scope.render();
};
$scope.addSeriesOverride = function() {
$scope.panel.seriesOverrides.push({});
};
$scope.removeSeriesOverride = function(override) {
$scope.panel.seriesOverrides = _.without($scope.panel.seriesOverrides, override);
$scope.render();
};
$scope.toggleEditorHelp = function(index) {
if ($scope.editorHelpIndex === index) {
$scope.editorHelpIndex = null;
return;
}
$scope.editorHelpIndex = index;
};
panelSrv.init($scope);
});
});

View File

@@ -0,0 +1,80 @@
define([
'angular',
'app',
'lodash',
], function(angular, app, _) {
'use strict';
var module = angular.module('grafana.panels.graph', []);
app.useModule(module);
module.controller('SeriesOverridesCtrl', function($scope) {
$scope.overrideMenu = [];
$scope.currentOverrides = [];
$scope.override = $scope.override || {};
$scope.addOverrideOption = function(name, propertyName, values) {
var option = {};
option.text = name;
option.propertyName = propertyName;
option.index = $scope.overrideMenu.length;
option.values = values;
option.submenu = _.map(values, function(value, index) {
return {
text: String(value),
click: 'setOverride(' + option.index + ',' + index + ')'
};
});
$scope.overrideMenu.push(option);
};
$scope.setOverride = function(optionIndex, valueIndex) {
var option = $scope.overrideMenu[optionIndex];
var value = option.values[valueIndex];
$scope.override[option.propertyName] = value;
$scope.updateCurrentOverrides();
$scope.render();
};
$scope.removeOverride = function(option) {
delete $scope.override[option.propertyName];
$scope.updateCurrentOverrides();
$scope.render();
};
$scope.getSeriesNames = function() {
return _.map($scope.legend, function(info) {
return info.alias;
});
};
$scope.updateCurrentOverrides = function() {
$scope.currentOverrides = [];
_.each($scope.overrideMenu, function(option) {
var value = $scope.override[option.propertyName];
if (_.isUndefined(value)) { return; }
$scope.currentOverrides.push({
name: option.text,
propertyName: option.propertyName,
value: String(value)
});
});
};
$scope.addOverrideOption('Bars', 'bars', [true, false]);
$scope.addOverrideOption('Lines', 'lines', [true, false]);
$scope.addOverrideOption('Line fill', 'fill', [0,1,2,3,4,5,6,7,8,9,10]);
$scope.addOverrideOption('Line width', 'linewidth', [0,1,2,3,4,5,6,7,8,9,10]);
$scope.addOverrideOption('Staircase line', 'steppedLine', [true, false]);
$scope.addOverrideOption('Points', 'points', [true, false]);
$scope.addOverrideOption('Points Radius', 'pointradius', [1,2,3,4,5]);
$scope.addOverrideOption('Stack', 'stack', [true, false, 2, 3, 4, 5]);
$scope.addOverrideOption('Y-axis', 'yaxis', [1, 2]);
$scope.addOverrideOption('Z-index', 'zindex', [-1,-2,-3,0,1,2,3]);
$scope.updateCurrentOverrides();
});
});

View File

@@ -1,5 +1,3 @@
<div class="editor-row">
<div class="section">
<h5>Chart Options</h5>
@@ -29,7 +27,7 @@
<select class="input-mini" ng-model="panel.pointradius" ng-options="f for f in [1,2,3,4,5,6,7,8,9,10]" ng-change="render()"></select>
</div>
<div class="editor-option">
<label class="small">Null point mode <tip>Define how null values should be drawn</tip></label>
<label class="small">Null point mode<tip>Define how null values should be drawn</tip></label>
<select class="input-medium" ng-model="panel.nullPointMode" ng-options="f for f in ['connected', 'null', 'null as zero']" ng-change="render()"></select>
</div>
<div class="editor-option">
@@ -64,3 +62,44 @@
</div>
</div>
</div>
<div class="editor-row">
<div class="section">
<h5>Series specific overrides <tip>Regex match example: /server[0-3]/i </tip></h5>
<div>
<div class="grafana-target" ng-repeat="override in panel.seriesOverrides" ng-controller="SeriesOverridesCtrl">
<div class="grafana-target-inner">
<ul class="grafana-segment-list">
<li class="grafana-target-segment">
<i class="icon-remove pointer" ng-click="removeSeriesOverride(override)"></i>
</li>
<li class="grafana-target-segment">
alias or regex
</li>
<li>
<input type="text"
ng-model="override.alias"
bs-typeahead="getSeriesNames"
ng-blur="render()"
data-min-length=0 data-items=100
class="input-medium grafana-target-segment-input" >
</li>
<li class="grafana-target-segment" ng-repeat="option in currentOverrides">
<i class="pointer icon-remove" ng-click="removeOverride(option)"></i>
{{option.name}}: {{option.value}}
</li>
<li class="dropdown">
<a class="dropdown-toggle grafana-target-segment" data-toggle="dropdown" gf-dropdown="overrideMenu" bs-tooltip="'set option to override'" data-placement="right">
<i class="icon-plus"></i>
</a>
</li>
</ul>
<div class="clearfix"></div>
</div>
</div>
</div>
<button class="btn btn-success" style="margin-top: 20px" ng-click="addSeriesOverride()">Add series override rule</button>
</div>
</div>

View File

@@ -1,57 +0,0 @@
define([
'kbn'
],
function (kbn) {
'use strict';
/**
* manages the interval logic
* @param {[type]} interval_string An interval string in the format '1m', '1y', etc
*/
function Interval(interval_string) {
this.string = interval_string;
var info = kbn.describe_interval(interval_string);
this.type = info.type;
this.ms = info.sec * 1000 * info.count;
// does the length of the interval change based on the current time?
if (this.type === 'y' || this.type === 'M') {
// we will just modify this time object rather that create a new one constantly
this.get = this.get_complex;
this.date = new Date(0);
} else {
this.get = this.get_simple;
}
}
Interval.prototype = {
toString: function () {
return this.string;
},
after: function(current_ms) {
return this.get(current_ms, 1);
},
before: function (current_ms) {
return this.get(current_ms, -1);
},
get_complex: function (current, delta) {
this.date.setTime(current);
switch(this.type) {
case 'M':
this.date.setUTCMonth(this.date.getUTCMonth() + delta);
break;
case 'y':
this.date.setUTCFullYear(this.date.getUTCFullYear() + delta);
break;
}
return this.date.getTime();
},
get_simple: function (current, delta) {
return current + (delta * this.ms);
}
};
return Interval;
});

View File

@@ -1,61 +0,0 @@
<span ng-show="panel.legend.show"
ng-class="{'pull-right': series.yaxis === 2, 'hidden-series': hiddenSeries[series.alias]}"
ng-repeat='series in legend'
class="histogram-legend">
<i class='icon-minus pointer'
ng-style="{color: series.color}"
bs-popover="'colorPopup.html'"
>
</i>
<span class='small histogram-legend-item'>
<a ng-click="toggleSeries(series)" data-unique="1" data-placement="{{series.yaxis === 2 ? 'bottomRight' : 'bottomLeft'}}">
{{series.alias}}
</a>
<span ng-if="panel.legend.values">
<span ng-show="panel.legend.current">
&nbsp;&nbsp;Current: {{series.current}}&nbsp;
</span>
<span ng-show="panel.legend.min">
&nbsp;&nbsp;Min: {{series.min}}&nbsp;
</span>
<span ng-show="panel.legend.max">
&nbsp;&nbsp;Max: {{series.max}}&nbsp;
</span>
<span ng-show="panel.legend.total">
&nbsp;&nbsp;Total: {{series.total}}&nbsp;
</span>
<span ng-show="panel.legend.avg">
&nbsp;&nbsp;Avg: {{series.avg}}&nbsp;
</span>
</span>
</span>
</span>
<script type="text/ng-template" id="colorPopup.html">
<div class="histogram-legend-popover">
<a class="close" ng-click="dismiss();" href="">×</a>
<div class="editor-row small" style="padding-bottom: 0;">
<label>Axis:</label>
<button ng-click="toggleYAxis(series)"
class="btn btn-mini"
ng-class="{'btn-success': series.yaxis === 1 }">
Left
</button>
<button ng-click="toggleYAxis(series)"
class="btn btn-mini"
ng-class="{'btn-success': series.yaxis === 2 }">
Right
</button>
</div>
<div class="editor-row">
<i ng-repeat="color in colors"
class="pointer"
ng-class="{'icon-circle-blank': color === series.color,'icon-circle': color !== series.color}"
ng-style="{color:color}"
ng-click="changeSeriesColor(series, color);dismiss();">
</i>
</div>
</div>
</script>

View File

@@ -1,32 +0,0 @@
<div ng-controller='graphite'
ng-init="init()"
style="min-height:{{panel.height || row.height}}"
ng-class="{'panel-fullscreen': fullscreen}">
<div style="position: relative">
<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>
<div ng-if="panel.legend" class="grafana-legend-container">
<div ng-include="'app/panels/graphite/legend.html'"></div>
</div>
<div class="panel-full-edit-tabs" ng-if="editMode">
<div ng-model="editor.index" bs-tabs>
<div ng-repeat="tab in editorTabs" data-title="{{tab}}">
</div>
</div>
<div class="tab-content" ng-repeat="tab in panelMeta.fullEditorTabs" ng-show="editorTabs[editor.index] == tab.title">
<div ng-include src="tab.src"></div>
</div>
</div>
</div>

View File

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

View File

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

View File

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

View File

@@ -1,97 +1,94 @@
/** @scratch /panels/5
* include::panels/text.asciidoc[]
*/
/** @scratch /panels/text/0
* == text
* Status: *Stable*
*
* The text panel is used for displaying static text formated as markdown, sanitized html or as plain
* text.
*
*/
define([
'angular',
'app',
'underscore',
'require'
'lodash',
'require',
],
function (angular, app, _, require) {
'use strict';
var module = angular.module('kibana.panels.text', []);
var module = angular.module('grafana.panels.text', []);
app.useModule(module);
module.controller('text', function($scope) {
var converter;
module.controller('text', function($scope, templateSrv, $sce, panelSrv) {
$scope.panelMeta = {
description : "A static text panel that can use plain text, markdown, or (sanitized) HTML"
};
// Set and populate defaults
var _d = {
/** @scratch /panels/text/5
* === Parameters
*
* mode:: `html', `markdown' or `text'
*/
mode : "markdown", // 'html','markdown','text'
/** @scratch /panels/text/5
* content:: The content of your panel, written in the mark up specified in +mode+
*/
mode : "markdown", // 'html', 'markdown', 'text'
content : "",
style: {},
};
_.defaults($scope.panel,_d);
_.defaults($scope.panel, _d);
$scope.init = function() {
$scope.initPanel($scope);
panelSrv.init(this);
$scope.ready = false;
$scope.$on('refresh', $scope.render);
$scope.render();
};
});
module.directive('markdown', function() {
return {
restrict: 'E',
link: function(scope, element) {
scope.$on('render', function() {
render_panel();
});
function render_panel() {
require(['./lib/showdown'], function (Showdown) {
scope.ready = true;
var converter = new Showdown.converter();
var text = scope.panel.content.replace(/&/g, '&amp;')
.replace(/>/g, '&gt;')
.replace(/</g, '&lt;');
var htmlText = converter.makeHtml(text);
element.html(htmlText);
// For whatever reason, this fixes chrome. I don't like it, I think
// it makes things slow?
if(!scope.$$phase) {
scope.$apply();
}
});
}
render_panel();
$scope.render = function() {
if ($scope.panel.mode === 'markdown') {
$scope.renderMarkdown($scope.panel.content);
}
else if ($scope.panel.mode === 'html') {
$scope.updateContent($scope.panel.content);
}
else if ($scope.panel.mode === 'text') {
$scope.renderText($scope.panel.content);
}
};
});
module.filter('newlines', function(){
return function (input) {
return input.replace(/\n/g, '<br/>');
$scope.renderText = function(content) {
content = content
.replace(/&/g, '&amp;')
.replace(/>/g, '&gt;')
.replace(/</g, '&lt;')
.replace(/\n/g, '<br/>');
$scope.updateContent(content);
};
});
module.filter('striphtml', function () {
return function(text) {
return text
$scope.renderMarkdown = function(content) {
var text = content
.replace(/&/g, '&amp;')
.replace(/>/g, '&gt;')
.replace(/</g, '&lt;');
if (converter) {
$scope.updateContent(converter.makeHtml(text));
}
else {
require(['./lib/showdown'], function (Showdown) {
converter = new Showdown.converter();
$scope.updateContent(converter.makeHtml(text));
});
}
};
$scope.updateContent = function(html) {
try {
$scope.content = $sce.trustAsHtml(templateSrv.replace(html));
} catch(e) {
console.log('Text panel error: ', e);
$scope.content = $sce.trustAsHtml(html);
}
if(!$scope.$$phase) {
$scope.$digest();
}
};
$scope.openEditor = function() {
};
$scope.init();
});
});
});

View File

@@ -1,78 +1,84 @@
<div class="modal-body">
<style>
.timepicker-to-column {
margin-top: 10px;
}
<div class="dashboard-editor-header">
<div class="dashboard-editor-title">
<i class="icon icon-calendar-empty"></i>
Custom time range
</div>
</div>
.timepicker-input input {
outline: 0 !important;
border: 0px !important;
-webkit-box-shadow: 0;
-moz-box-shadow: 0;
box-shadow: 0;
position: relative;
}
<div class="dashboard-editor-body">
<style>
.timepicker-to-column {
margin-top: 10px;
}
.timepicker-input input::-webkit-outer-spin-button,
.timepicker-input input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
.timepicker-input input {
outline: 0 !important;
border: 0px !important;
-webkit-box-shadow: 0;
-moz-box-shadow: 0;
box-shadow: 0;
position: relative;
}
input.timepicker-date {
width: 90px;
}
input.timepicker-hms {
width: 20px;
}
input.timepicker-ms {
width: 25px;
}
div.timepicker-now {
float: right;
}
</style>
.timepicker-input input::-webkit-outer-spin-button,
.timepicker-input input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
<div class="timepicker form-horizontal">
<form name="input">
input.timepicker-date {
width: 90px;
}
input.timepicker-hms {
width: 20px;
}
input.timepicker-ms {
width: 25px;
}
div.timepicker-now {
float: right;
}
</style>
<div class="timepicker-from-column">
<label class="small">From</label>
<div class="fake-input timepicker-input">
<input class="timepicker-date" type="text" ng-change="validate(temptime)" ng-model="temptime.from.date" data-date-format="yyyy-mm-dd" required bs-datepicker />@
<input class="timepicker-hms" type="text" maxlength="2" ng-change="validate(temptime)" ng-model="temptime.from.hour" required ng-pattern="patterns.hour" onClick="this.select();"/>:
<input class="timepicker-hms" type="text" maxlength="2" ng-change="validate(temptime)" ng-model="temptime.from.minute" required ng-pattern="patterns.minute" onClick="this.select();"/>:
<input class="timepicker-hms" type="text" maxlength="2" ng-change="validate(temptime)" ng-model="temptime.from.second" required ng-pattern="patterns.second" onClick="this.select();"/>.
<input class="timepicker-ms" type="text" maxlength="3" ng-change="validate(temptime)" ng-model="temptime.from.millisecond" required ng-pattern="patterns.millisecond" onClick="this.select();"/>
</div>
</div>
<div class="timepicker form-horizontal">
<form name="input">
<div class="timepicker-to-column">
<div class="timepicker-from-column">
<label class="small">From</label>
<div class="fake-input timepicker-input">
<input class="timepicker-date" type="text" ng-change="validate(temptime)" ng-model="temptime.from.date" data-date-format="yyyy-mm-dd" required bs-datepicker />@
<input class="timepicker-hms" type="text" maxlength="2" ng-change="validate(temptime)" ng-model="temptime.from.hour" required ng-pattern="patterns.hour" onClick="this.select();"/>:
<input class="timepicker-hms" type="text" maxlength="2" ng-change="validate(temptime)" ng-model="temptime.from.minute" required ng-pattern="patterns.minute" onClick="this.select();"/>:
<input class="timepicker-hms" type="text" maxlength="2" ng-change="validate(temptime)" ng-model="temptime.from.second" required ng-pattern="patterns.second" onClick="this.select();"/>.
<input class="timepicker-ms" type="text" maxlength="3" ng-change="validate(temptime)" ng-model="temptime.from.millisecond" required ng-pattern="patterns.millisecond" onClick="this.select();"/>
</div>
</div>
<label class="small">To (<a class="link" ng-class="{'strong':panel.now}" ng-click="setNow();panel.now=true">now</a>)</label>
<div class="timepicker-to-column">
<div class="fake-input timepicker-input">
<div ng-hide="panel.now">
<input class="timepicker-date" type="text" ng-change="validate(temptime)" ng-model="temptime.to.date" data-date-format="yyyy-mm-dd" required bs-datepicker />@
<input class="timepicker-hms" type="text" maxlength="2" ng-change="validate(temptime)" ng-model="temptime.to.hour" required ng-pattern="patterns.hour" onClick="this.select();"/>:
<input class="timepicker-hms" type="text" maxlength="2" ng-change="validate(temptime)" ng-model="temptime.to.minute" required ng-pattern="patterns.minute" onClick="this.select();"/>:
<input class="timepicker-hms" type="text" maxlength="2" ng-change="validate(temptime)" ng-model="temptime.to.second" required ng-pattern="patterns.second" onClick="this.select();"/>.
<input class="timepicker-ms" type="text" maxlength="3" ng-change="validate(temptime)" ng-model="temptime.to.millisecond" required ng-pattern="patterns.millisecond" onClick="this.select();"/>
</div>
<span type="text" ng-show="panel.now" ng-disabled="panel.now">&nbsp <i class="pointer icon-remove-sign" ng-click="setNow();panel.now=false"></i> Right Now <input type="text" name="dummy" style="visibility:hidden" /></span>
</div>
</div>
<label class="small">To (<a class="link" ng-class="{'strong':temptime.now}" ng-click="setNow();temptime.now=true">now</a>)</label>
</form>
<div class="clearfix"></div>
</div>
</div>
<div class="fake-input timepicker-input">
<div ng-hide="temptime.now">
<input class="timepicker-date" type="text" ng-change="validate(temptime)" ng-model="temptime.to.date" data-date-format="yyyy-mm-dd" required bs-datepicker />@
<input class="timepicker-hms" type="text" maxlength="2" ng-change="validate(temptime)" ng-model="temptime.to.hour" required ng-pattern="patterns.hour" onClick="this.select();"/>:
<input class="timepicker-hms" type="text" maxlength="2" ng-change="validate(temptime)" ng-model="temptime.to.minute" required ng-pattern="patterns.minute" onClick="this.select();"/>:
<input class="timepicker-hms" type="text" maxlength="2" ng-change="validate(temptime)" ng-model="temptime.to.second" required ng-pattern="patterns.second" onClick="this.select();"/>.
<input class="timepicker-ms" type="text" maxlength="3" ng-change="validate(temptime)" ng-model="temptime.to.millisecond" required ng-pattern="patterns.millisecond" onClick="this.select();"/>
</div>
<span type="text" ng-show="temptime.now" ng-disabled="temptime.now">&nbsp <i class="pointer icon-remove-sign" ng-click="setNow();temptime.now=false;"></i> Right Now <input type="text" name="dummy" style="visibility:hidden" /></span>
</div>
</div>
<div class="modal-footer">
<form name="input" style="margin-bottom:0">
<span class="" ng-hide="input.$valid">Invalid date or range</span>
<button ng-click="setAbsoluteTimeFilter(validate(temptime));dismiss();" ng-disabled="!input.$valid" class="btn btn-success">Apply</button>
<button ng-click="dismiss();" class="btn btn-danger">Cancel</button>
</form>
<div class="clearfix"></div>
</div>
</div>
</form>
</div>
<div class="dashboard-editor-footer">
<form name="input" style="margin-bottom:0">
<span class="" ng-hide="input.$valid">Invalid date or range</span>
<button ng-click="setAbsoluteTimeFilter(validate(temptime));dismiss();" ng-disabled="!input.$valid" class="btn btn-success">Apply</button>
<button ng-click="dismiss();" class="btn btn-success pull-right">Cancel</button>
</form>
</div>

View File

@@ -2,11 +2,17 @@
<div class="section">
<div class="editor-option">
<label class="small">Relative time options <small>comma seperated</small></label>
<input type="text" array-join class="input-large" ng-model="panel.time_options">
<input type="text" array-join class="input-xlarge" ng-model="panel.time_options">
</div>
<div class="editor-option">
<label class="small">Auto-refresh options <small>comma seperated</small></label>
<input type="text" array-join class="input-large" ng-model="panel.refresh_intervals">
<input type="text" array-join class="input-xlarge" ng-model="panel.refresh_intervals">
</div>
<p>
<br>
<i class="icon-info-sign"></i>
For these changes to fully take effect save and reload the dashboard.
</i>
</div>
</div>

View File

@@ -15,40 +15,36 @@
<ul class="nav nav-pills timepicker-dropdown">
<li class="dropdown">
<a class="dropdown-toggle timepicker-dropdown" data-toggle="dropdown" href="" bs-tooltip="time.from.date ? (time.from.date | date:'yyyy-MM-dd HH:mm:ss.sss') + ' <br>to<br>' +(time.to.date | date:'yyyy-MM-dd HH:mm:ss.sss') : 'Click to set a time filter'" data-placement="bottom" ng-click="dismiss();">
<span ng-show="filterSrv.time">
<span class="pointer" ng-hide="panel.now">{{time.from.date | date:'MMM d, y HH:mm:ss'}}</span>
<span class="pointer" ng-show="panel.now">{{time.from.date | moment:'ago'}}</span>
to
<span class="pointer" ng-hide="panel.now" >{{time.to.date | date:'MMM d, y HH:mm:ss'}}</span>
<span class="pointer" ng-show="panel.now">{{time.to.date | moment:'ago'}}</span>
</span>
<span ng-hide="filterSrv.time">Time filter</span>
<span ng-show="dashboard.current.refresh" class="text-warning">refreshed every {{dashboard.current.refresh}} </span>
<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 ng-repeat='timespan in panel.time_options track by $index'>
<a ng-click="setRelativeFilter(timespan)">Last {{timespan}}</a>
<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 -->
<li class="dropdown-submenu">
<a href="#">Auto-Refresh</a>
<ul class="dropdown-menu">
<li><a ng-click="dashboard.set_interval(false)">Off</a></li>
<li ng-repeat="interval in panel.refresh_intervals track by $index"><a ng-click="dashboard.set_interval(interval)">Every {{interval}}</a></li>
<li>
<a ng-click="timeSrv.set_interval(false)">Off</a>
</li>
<li bindonce ng-repeat="interval in panel.refresh_intervals track by $index">
<a ng-click="timeSrv.set_interval(interval)" bo-text="'Every ' + interval"></a>
</li>
</ul>
</li>
<li><a ng-click="customTime()">Custom</a></li>
</ul>
</li>
<li ng-show="!dashboard.current.refresh" class="grafana-menu-refresh">
<a class="icon-refresh" ng-click="dashboard.refresh()"></a>
<li ng-show="!dashboard.refresh" class="grafana-menu-refresh">
<a ng-click="timeSrv.refreshDashboard()"><i class="icon-refresh"></i></a>
</li>
</ul>

View File

@@ -15,24 +15,23 @@
define([
'angular',
'app',
'underscore',
'lodash',
'moment',
'kbn'
],
function (angular, app, _, moment, kbn) {
'use strict';
var module = angular.module('kibana.panels.timepicker', []);
var module = angular.module('grafana.panels.timepicker', []);
app.useModule(module);
module.controller('timepicker', function($scope, $modal, $q, filterSrv) {
module.controller('timepicker', function($scope, $rootScope, timeSrv) {
$scope.panelMeta = {
status : "Stable",
description : "A panel for controlling the time range filters. If you have time based data, "+
" or if you're using time stamped indices, you need one of these"
description : ""
};
// Set and populate defaults
var _d = {
status : "Stable",
@@ -40,12 +39,8 @@ function (angular, app, _, moment, kbn) {
refresh_intervals : ['5s','10s','30s','1m','5m','15m','30m','1h','2h','1d'],
};
var customTimeModal = null;
_.defaults($scope.panel,_d);
$scope.filterSrv = filterSrv;
// ng-pattern regexs
$scope.patterns = {
date: /^[0-9]{2}\/[0-9]{2}\/[0-9]{4}$/,
@@ -55,38 +50,36 @@ function (angular, app, _, moment, kbn) {
millisecond: /^[0-9]*$/
};
$scope.$on('refresh', function(){$scope.init();});
$scope.timeSrv = timeSrv;
$scope.$on('refresh', function() {
$scope.init();
});
$scope.init = function() {
var time = filterSrv.timeRange();
var time = timeSrv.timeRange(true);
if(time) {
$scope.panel.now = filterSrv.timeRange(false).to === "now" ? true : false;
$scope.panel.now = timeSrv.timeRange(false).to === "now" ? true : false;
$scope.time = getScopeTimeObj(time.from,time.to);
}
};
$scope.customTime = function() {
if (!customTimeModal) {
customTimeModal = $modal({
template: './app/panels/timepicker/custom.html',
persist: true,
show: false,
scope: $scope,
keyboard: false
});
}
// Assume the form is valid since we're setting it to something valid
$scope.input.$setValidity("dummy", true);
$scope.temptime = cloneTime($scope.time);
$scope.temptime.now = $scope.panel.now;
$scope.temptime.from.date.setHours(0,0,0,0);
$scope.temptime.to.date.setHours(0,0,0,0);
// Date picker needs the date to be at the start of the day
$scope.temptime.from.date.setHours(1,0,0,0);
$scope.temptime.to.date.setHours(1,0,0,0);
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();
}
$q.when(customTimeModal).then(function(modalEl) {
modalEl.modal('show');
});
$scope.emitAppEvent('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
@@ -113,7 +106,7 @@ function (angular, app, _, moment, kbn) {
return false;
}
return {from:_from,to:_to};
return { from: _from, to:_to, now: time.now};
};
$scope.setNow = function() {
@@ -130,12 +123,12 @@ function (angular, app, _, moment, kbn) {
// Create filter object
var _filter = _.clone(time);
if($scope.panel.now) {
if(time.now) {
_filter.to = "now";
}
// Set the filter
$scope.panel.filter_id = filterSrv.setTime(_filter);
$scope.panel.filter_id = timeSrv.setTime(_filter);
// Update our representation
$scope.time = getScopeTimeObj(time.from,time.to);
@@ -149,14 +142,14 @@ function (angular, app, _, moment, kbn) {
to: "now"
};
filterSrv.setTime(_filter);
timeSrv.setTime(_filter);
$scope.time = getScopeTimeObj(kbn.parseDate(_filter.from),new Date());
};
var pad = function(n, width, z) {
z = z || '0';
n = n + '';
n = n.toString();
return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n;
};
@@ -172,10 +165,28 @@ function (angular, app, _, moment, kbn) {
};
var getScopeTimeObj = function(from,to) {
return {
from: getTimeObj(from),
to: getTimeObj(to)
};
var model = { from: getTimeObj(from), to: getTimeObj(to), };
if (model.from.date) {
model.tooltip = $scope.dashboard.formatDate(model.from.date) + ' <br>to<br>';
model.tooltip += $scope.dashboard.formatDate(model.to.date);
}
else {
model.tooltip = 'Click to set time filter';
}
if (timeSrv.time) {
if ($scope.panel.now) {
model.rangeString = moment(model.from.date).fromNow() + ' to ' +
moment(model.to.date).fromNow();
}
else {
model.rangeString = $scope.dashboard.formatDate(model.from.date, 'MMM D, YYYY HH:mm:ss') + ' to ' +
$scope.dashboard.formatDate(model.to.date, 'MMM D, YYYY HH:mm:ss');
}
}
return model;
};
var getTimeObj = function(date) {
@@ -194,6 +205,5 @@ function (angular, app, _, moment, kbn) {
return moment(new Date(date.getTime() + date.getTimezoneOffset() * 60000)).toDate();
};
});
});

View File

@@ -0,0 +1,85 @@
<div ng-controller="AnnotationsEditorCtrl" ng-init="init()">
<div class="dashboard-editor-header">
<div class="dashboard-editor-title">
<i class="icon icon-bolt"></i>
Annotations
</div>
<div ng-model="editor.index" bs-tabs style="text-transform:capitalize;">
<div ng-repeat="tab in ['Overview', 'Add', 'Edit']" data-title="{{tab}}">
</div>
</div>
</div>
<div class="dashboard-editor-body">
<div class="editor-row row" ng-if="editor.index == 0">
<div class="span6">
<div ng-if="variables.length === 0">
<em>No annotations defined</em>
</div>
<table class="grafana-options-table">
<tr ng-repeat="annotation in annotations">
<td style="width:90%">
<i class="icon-bolt"></i> &nbsp;
{{annotation.name}}
</td>
<td style="width: 1%">
<a ng-click="edit(annotation)" class="btn btn-success btn-mini">
<i class="icon-edit"></i>
Edit
</a>
</td>
<td style="width: 1%"><i ng-click="_.move(annotations,$index,$index-1)" ng-hide="$first" class="pointer icon-arrow-up"></i></td>
<td style="width: 1%"><i ng-click="_.move(annotations,$index,$index+1)" ng-hide="$last" class="pointer icon-arrow-down"></i></td>
<td style="width: 1%">
<a ng-click="removeAnnotation(annotation)" class="btn btn-danger btn-mini">
<i class="icon-remove"></i>
</a>
</td>
</tr>
</table>
</div>
</div>
<div ng-if="editor.index == 1 || (editor.index == 2 && !currentIsNew)">
<div class="editor-row">
<div class="editor-option">
<label class="small">Name</label>
<input type="text" class="input-medium" ng-model='currentAnnotation.name' placeholder="name"></input>
</div>
<div class="editor-option">
<label class="small">Datasource</label>
<select ng-model="currentAnnotation.datasource" ng-options="f.name as f.name for f in datasources" ng-change="datasourceChanged()"></select>
</div>
<div class="editor-option">
<label class="small">Icon color</label>
<spectrum-picker ng-model="currentAnnotation.iconColor"></spectrum-picker>
</div>
<div class="editor-option">
<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>
<div class="editor-option">
<label class="small">Line color</label>
<spectrum-picker ng-model="currentAnnotation.lineColor"></spectrum-picker>
</div>
</div>
<div ng-include src="currentDatasource.editorSrc">
</div>
</div>
</div>
<div class="dashboard-editor-footer">
<button ng-show="editor.index === 1" type="button" class="btn btn-success" ng-click="add()">Add</button>
<button ng-show="editor.index === 2" type="button" class="btn btn-success pull-left" ng-click="update();">Update</button>
<button type="button" class="btn btn-success pull-right" ng-click="close_edit();dismiss();dashboard.refresh();">Close</button>
</div>
</div>

View File

@@ -0,0 +1,19 @@
<div class="grafana-console" ng-controller="ConsoleCtrl">
<div class="grafana-console-header">
<span class="grafana-console-title large"><i class="icon-terminal"></i></span>
</div>
<div class="grafana-console-body">
<div class="grafana-console-item" ng-repeat="item in events" ng-class="{'grafana-console-error': item.error}">
<span class="grafana-console-time gfc-col" ng-bind="item.time"></span>
<span class="grafana-console-type gfc-col">
<span class="label label-info" ng-bind="item.type"></span>
</span>
<span class="gfc-col grafana-console-method" ng-bind="item.method"></span>
<span class="gfc-col grafana-console-title" ng-bind="item.title"></span>
<span class="gfc-col grafana-console-elapsed" ng-bind="item.elapsed"></span>
<span class="gfc-col grafana-console-field1" ng-bind="item.field1"></span>
<span class="gfc-col grafana-console-field2" ng-bind="item.field2"></span>
<span class="gfc-col grafana-console-field3" ng-bind="item.field3"></span>
</div>
</div>
</div>

View File

@@ -1,77 +0,0 @@
<style>
.noarrow>a:after {
display: none !important;
}
</style>
<li ng-show="fullscreen">
<a ng-click="exitFullscreen()">
Back to dashboard
</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.current.nav" ng-controller="PulldownCtrl" ng-show="pulldown.enable"><kibana-simple-panel type="pulldown.type" ng-cloak></kibana-simple-panel></li>
<li class="dropdown grafana-menu-save" ng-show="showDropdown('save')">
<a href="#" bs-tooltip="'Save'" data-placement="bottom" class="dropdown-toggle" data-toggle="dropdown" ng-click="openSaveDropdown()">
<i class='icon-save'></i>
</a>
<ul class="save-dashboard-dropdown dropdown-menu">
<li ng-show="dashboard.current.loader.save_elasticsearch">
<form class="input-prepend nomargin save-dashboard-dropdown-save-form">
<input class='input-medium' ng-model="dashboard.current.title" type="text" ng-model="elasticsearch.title"/>
<button class="btn" ng-click="elasticsearch_save('dashboard')"><i class="icon-save"></i></button>
</form>
</li>
<li ng-show="dashboard.current.loader.save_default">
<a class="link" ng-click="set_default()">Save as Home</a>
</li>
<li ng-show="dashboard.current.loader.save_default">
<a class="link" ng-click="purge_default()">Reset Home</a>
</li>
<li ng-show="!isFavorite">
<a class="link" ng-click="markAsFavorite()">Mark as favorite</a>
</li>
<li ng-show="isFavorite">
<a class="link" ng-click="removeAsFavorite()">Remove as favorite</a>
</li>
<li ng-show="dashboard.current.loader.save_local">
<a class="link" ng-click="dashboard.to_file()">Export schema</a>
</li>
<li ng-show="showDropdown('share')"><a bs-tooltip="'Share'" data-placement="bottom" ng-click="elasticsearch_save('temp',dashboard.current.loader.save_temp_ttl)" config-modal="app/partials/dashLoaderShare.html">Share temp copy</i></a></li>
<li ng-show="dashboard.current.loader.save_gist" style="margin:10px">
<h6>Gist</h6>
<form class="input-append">
<input class='input-medium' placeholder='Title' type="text" ng-model="gist.title"/>
<button class="btn" ng-click="save_gist()"><i class="icon-github-alt"></i></button>
</form><br>
<small ng-show="gist.last">Last gist: <a target="_blank" href="{{gist.last}}">{{gist.last}}</a></small>
</li>
</ul>
</li>
<li class="dropdown grafana-menu-load" ng-show="showDropdown('load')" ng-controller="SearchCtrl" ng-init="init()" ng-include="'app/partials/search.html'">
</li>
<li class="grafana-menu-home"><a bs-tooltip="'Goto saved default'" data-placement="bottom" href='#/dashboard'><i class='icon-home'></i></a></li>
<li class="grafana-menu-edit" ng-show="dashboard.current.editable" bs-tooltip="'Configure dashboard'" data-placement="bottom"><a class="link" config-modal="app/partials/dasheditor.html"><i class='icon-cog pointer'></i></a></li>
<li class="grafana-menu-stop-playlist hide">
<a class='small' ng-click='stopPlaylist(2)'>
Stop playlist
</a>
</li>

View File

@@ -4,7 +4,7 @@
</div>
<div class="modal-body">
<label>Share this dashboard with this URL</label>
<input ng-model='share.link' type="text" style="width:90%" onclick="this.select()" onfocus="this.select()" ng-change="share = dashboard.share_link(share.title,share.type,share.id)">
<input ng-model='share.url' type="text" style="width:90%" onclick="this.select()" onfocus="this.select()">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-success" ng-click="dismiss();$broadcast('render')">Close</button>

View File

@@ -1,81 +1,115 @@
<div class="submenu-controls">
<div class="submenu-panel" ng-controller="SubmenuCtrl" ng-repeat="pulldown in dashboard.current.pulldowns | filter:{ enable: true }">
<div class="submenu-panel-title">
<span class="small"><strong>{{pulldown.type}}:</strong></span>
</div>
<div class="submenu-panel-wrapper">
<kibana-simple-panel type="pulldown.type" ng-cloak></kibana-simple-panel>
</div>
<div ng-controller="DashboardCtrl" body-class class="dashboard" ng-class="{'dashboard-fullscreen': dashboardViewState.fullscreen}">
<div ng-include="'app/partials/dashboard_topnav.html'">
</div>
<div ng-if="submenuEnabled" ng-include="'app/partials/submenu.html'">
</div>
<div class="clearfix"></div>
<div dash-editor-view>
</div>
<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-close" ng-show="row.collapse" data-placement="bottom" >
<div class="row-close-buttons">
<span class="row-button bgPrimary" ng-click="toggle_row(row)">
<i bs-tooltip="'Expand row'" data-placement="right" class="icon-caret-left pointer" ></i>
</span>
</div>
<span class="row-text pointer" ng-click="toggle_row(row)" ng-bind="row.title"></span>
</div>
<div class="row-open" ng-show="!row.collapse">
<div class='row-tab bgSuccess dropdown' ng-show="row.editable">
<span class="row-tab-button dropdown-toggle" data-toggle="dropdown">
<i class="icon-th-list"></i>
</span>
<ul class="dropdown-menu dropdown-menu-right" role="menu" aria-labelledby="drop1">
<li>
<a ng-click="toggle_row(row)">Collapse row</a>
</li>
<li class="dropdown-submenu">
<a href="javascript:void(0);">Add Panel</a>
<ul class="dropdown-menu">
<li bindonce ng-repeat="name in panelNames">
<a ng-click="add_panel_default(name)" bo-text="name"></a>
</li>
</ul>
</li>
<li class="dropdown-submenu">
<a href="javascript:void(0);">Set height</a>
<ul class="dropdown-menu">
<li><a ng-click="set_height('25px')">25 px</a></li>
<li><a ng-click="set_height('100px')">100 px</a></li>
<li><a ng-click="set_height('150px')">150 px</a></li>
<li><a ng-click="set_height('200px')">200 px</a></li>
<li><a ng-click="set_height('250px')">250 px</a></li>
<li><a ng-click="set_height('300px')">300 px</a></li>
<li><a ng-click="set_height('350px')">350 px</a></li>
<li><a ng-click="set_height('450px')">450 px</a></li>
<li><a ng-click="set_height('500px')">500 px</a></li>
<li><a ng-click="set_height('600px')">600 px</a></li>
<li><a ng-click="set_height('700px')">700 px</a></li>
</ul>
</li>
<li class="dropdown-submenu">
<a href="javascript:void(0);">Move</a>
<ul class="dropdown-menu">
<li><a ng-click="move_row(-1)">Up</a></li>
<li><a ng-click="move_row(1)">Down</a></li>
</ul>
</li>
<li>
<a dash-editor-link="app/partials/roweditor.html">Row editor</a>
</li>
<li>
<a ng-click="delete_row()">Delete row</a>
</li>
</ul>
</div>
</div>
</div>
<div style="padding-top:0px" 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}">
<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>
<div class="clearfix"></div>
</div>
</div>
</div>
<div ng-show='dashboard.editable' class="row-fluid add-row-panel-hint">
<div class="span12" style="text-align:right;">
<span style="margin-right: 10px;" ng-click="add_row_default()" class="pointer btn btn-info btn-mini">
<span><i class="icon-plus-sign"></i> ADD A ROW</span>
</span>
</div>
</div>
</div>
<div ng-include="'app/partials/console.html'" ng-if="consoleEnabled">
</div>
</div>
<div class="clearfix"></div>
<div class="container-fluid main">
<div>
<div class="grafana-container container">
<!-- Rows -->
<div class="kibana-row" ng-controller="RowCtrl" ng-repeat="(row_name, row) in dashboard.current.rows" ng-style="row_style(row)">
<div class="row-control">
<div class="grafana-row" style="padding:0px;margin:0px;position:relative;">
<div class="row-close" ng-show="row.collapse" data-placement="bottom" >
<span class="row-button bgWarning" config-modal="app/partials/roweditor.html" class="pointer">
<i bs-tooltip="'Configure row'" data-placement="right" ng-show="row.editable" class="icon-cog pointer"></i>
</span>
<span class="row-button bgPrimary" ng-click="toggle_row(row)" ng-show="row.collapsable">
<i bs-tooltip="'Expand row'" data-placement="right" ng-show="row.editable" class="icon-caret-left pointer" ></i>
</span>
<span class="row-button row-text" ng-click="toggle_row(row)" ng-class="{'pointer':row.collapsable}">{{row.title || 'Row '+$index}}</span>
</div>
<div style="text-align:center" class="row-open" ng-show="!row.collapse">
<div ng-show="row.collapsable" class='row-tab bgPrimary' ng-click="toggle_row(row)">
<i bs-tooltip="'Collapse row'" data-placement="right" class="icon-caret-right" ></i>
<br>
</div>
<div config-modal="app/partials/roweditor.html" class='row-tab bgWarning' ng-show="row.editable">
<i bs-tooltip="'Configure row'" data-placement="right" class="icon-cog pointer"></i>
<br>
</div>
<div ng-show="rowSpan(row) > 12" class='row-tab bgDanger'>
<i bs-tooltip="'Total span > 12. This row may format poorly'" data-placement="right" class="icon-warning-sign"></i>
<br>
</div>
</div>
</div>
<div style="padding-top:0px" ng-if="!row.collapse">
<!-- Panels -->
<div ng-repeat="(name, panel) in row.panels|filter:isPanel" ng-hide="panel.hide" class="panel nospace" ng-style="{'width':!panel.span?'100%':(panel.span/1.2)*10+'%'}" data-drop="true" ng-model="row.panels" data-jqyoui-options jqyoui-droppable="{index:$index,mutate:false,onDrop:'panelMoveDrop',onOver:'panelMoveOver(true)',onOut:'panelMoveOut'}" ng-class="{'dragInProgress':dashboard.panelDragging}">
<!-- Content Panel -->
<div style="position:relative">
<kibana-panel type="panel.type" ng-cloak></kibana-panel>
</div>
</div>
<div ng-show="rowSpan(row) < 10 && dashboard.panelDragging" class="panel" style="margin:5px;width:30%;background:rgba(100,100,100,0.50)" ng-class="{'dragInProgress':dashboard.panelDragging}" 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>
<span config-modal="app/partials/roweditor.html" ng-show="!dashboard.panelDragging && !dashboard.current.hideControls">
<i ng-hide="rowSpan(row) >= 10" class="pointer icon-plus-sign" ng-click="editor.index = 2" bs-tooltip="'Add a panel to this row'" data-placement="right"></i>
</span>
<div class="clearfix"></div>
</div>
</div>
</div>
</div>
<div ng-show='dashboard.current.editable && dashboard.current.panel_hints' class="row-fluid add-row-panel-hint">
<div class="span12" style="text-align:right;">
<span style="margin-left: 0px;" class="pointer btn btn-mini" config-modal="app/partials/dasheditor.html">
<span ng-click="editor.index = 1"><i class="icon-plus-sign"></i> ADD A ROW</span>
</span>
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,86 @@
<div class="navbar navbar-static-top">
<div class="navbar-inner">
<div class="container-fluid">
<span class="brand">
<img class="logo-icon" src="img/fav32.png" bs-tooltip="'Grafana'" data-placement="bottom"></img>
<span class="page-title">{{dashboard.title}}</span>
</span>
<ul class="nav pull-right" ng-controller='DashboardNavCtrl' ng-init="init()">
<li ng-show="dashboardViewState.fullscreen">
<a ng-click="exitFullscreen()">
Back to dashboard
</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>
</li>
<li class="dropdown grafana-menu-save">
<a bs-tooltip="'Save'" data-placement="bottom" class="dropdown-toggle" data-toggle="dropdown" ng-click="openSaveDropdown()">
<i class='icon-save'></i>
</a>
<ul class="save-dashboard-dropdown dropdown-menu" ng-if="saveDropdownOpened">
<li>
<form class="input-prepend nomargin save-dashboard-dropdown-save-form">
<input class='input-medium' ng-model="dashboard.title" type="text" />
<button class="btn" ng-click="saveDashboard()"><i class="icon-save"></i></button>
</form>
</li>
<li>
<a class="link" ng-click="set_default()">Save as Home</a>
</li>
<li>
<a class="link" ng-click="purge_default()">Reset Home</a>
</li>
<li ng-show="!isFavorite">
<a class="link" ng-click="markAsFavorite()">Mark as favorite</a>
</li>
<li ng-show="isFavorite">
<a class="link" ng-click="removeAsFavorite()">Remove as favorite</a>
</li>
<li>
<a class="link" ng-click="editJson()">Dashboard JSON</a>
</li>
<li>
<a class="link" ng-click="exportDashboard()">Export dashboard</a>
</li>
<li ng-show="db.saveTemp">
<a bs-tooltip="'Share'" data-placement="bottom" ng-click="saveForSharing()" config-modal="app/partials/dashLoaderShare.html">
Share temp copy
</a>
</li>
</ul>
</li>
<li class="dropdown grafana-menu-load">
<a bs-tooltip="'Search'" ng-click="openSearch()">
<i class='icon-folder-open'></i>
</a>
</li>
<li class="grafana-menu-home"><a bs-tooltip="'Goto saved default'" data-placement="bottom" href='#/'><i class='icon-home'></i></a></li>
<li class="grafana-menu-edit" ng-show="dashboard.editable" bs-tooltip="'Configure dashboard'" data-placement="bottom"><a class="link" dash-editor-link="app/partials/dasheditor.html"><i class='icon-cog pointer'></i></a></li>
<li class="grafana-menu-stop-playlist hide">
<a class='small' ng-click='stopPlaylist(2)'>
Stop playlist
</a>
</li>
</ul>
</div>
</div>
</div>

View File

@@ -1,160 +1,113 @@
<div class="modal-body">
<div class="pull-right editor-title">Dashboard settings</div>
<div class="dashboard-editor-header">
<div class="dashboard-editor-title">
<i class="icon icon-cogs"></i>
Dashboard settings
</div>
<div ng-model="editor.index" bs-tabs style="text-transform:capitalize;">
<div ng-repeat="tab in ['General', 'Rows','Controls', 'Metrics', 'Import']" data-title="{{tab}}">
</div>
<div ng-repeat="tab in dashboard.current.nav" data-title="{{tab.type}}">
</div>
</div>
<div ng-if="editor.index == 0">
<div class="editor-row">
<div class="section">
<div class="editor-option">
<label class="small">Title</label><input type="text" class="input-large" ng-model='dashboard.current.title'></input>
</div>
<div class="editor-option">
<label class="small">Style</label><select class="input-small" ng-model="dashboard.current.style" ng-options="f for f in ['dark','light']"></select>
</div>
<div class="editor-option">
<label class="small">Time correction</label>
<select ng-model="dashboard.current.timezone" class='input-small' ng-options="f for f in ['browser','utc']"></select>
</div>
<div class="editor-option">
<label class="small"> Hints <tip>Show 'Add panel' hints in empty spaces</tip></label><input type="checkbox" ng-model="dashboard.current.panel_hints" ng-checked="dashboard.current.panel_hints" />
</div>
<div class="editor-option">
<label class="small">Hide controls</label>
<input type="checkbox" ng-model="dashboard.current.hideControls" ng-checked="dashboard.current.hideControls">
</div>
</div>
</div>
<div class="editor-row">
<div class="section">
<div class="editor-option">
<label class="small">Tags</label>
<bootstrap-tagsinput ng-model="dashboard.current.tags" tagclass="label label-tag" placeholder="add tags">
</bootstrap-tagsinput>
<tip>Press enter to a add tag</tip>
</div>
</div>
</div>
</div>
<div ng-if="editor.index == 1">
<div class="editor-row">
<div class="span8">
<h4>Rows</h4>
<table class="table table-striped">
<thead>
<th width="1%"></th>
<th width="1%"></th>
<th width="1%"></th>
<th width="97%">Title</th>
</thead>
<tr ng-repeat="row in dashboard.current.rows">
<td><i ng-click="_.move(dashboard.current.rows,$index,$index-1)" ng-hide="$first" class="pointer icon-arrow-up"></i></td>
<td><i ng-click="_.move(dashboard.current.rows,$index,$index+1)" ng-hide="$last" class="pointer icon-arrow-down"></i></td>
<td><i ng-click="dashboard.current.rows = _.without(dashboard.current.rows,row)" class="pointer icon-remove"></i></td>
<td>{{row.title}}</td>
</tr>
</table>
</div>
<div class="span4">
<h4>Add Row</h4>
<label class="small">Title</label>
<input type="text" class="input-medium" ng-model='row.title' placeholder="New row"></input>
<label class="small">Height</label>
<input type="text" class="input-mini" ng-model='row.height'></input>
</div>
</div>
</div>
<div ng-if="editor.index == 2" ng-controller="dashLoader">
<div class="editor-row">
<div class="section">
<h5>Save to</h5>
<div class="editor-option">
<label class="small">Export</label><input type="checkbox" ng-model="dashboard.current.loader.save_local" ng-checked="dashboard.current.loader.save_local">
</div>
<div class="editor-option">
<label class="small">Browser</label><input type="checkbox" ng-model="dashboard.current.loader.save_default" ng-checked="dashboard.current.loader.save_default">
</div>
<div class="editor-option">
<label class="small">Gist <tip>Requires your domain to be OAUTH registered with Github<tip></label><input type="checkbox" ng-model="dashboard.current.loader.save_gist" ng-checked="dashboard.current.loader.save_gist">
</div>
<div class="editor-option">
<label class="small">Elasticsearch</label><input type="checkbox" ng-model="dashboard.current.loader.save_elasticsearch" ng-checked="dashboard.current.loader.save_elasticsearch">
</div>
</div>
<div class="section">
<h5>Load from</h5>
<div class="editor-option">
<label class="small">Local file</label><input type="checkbox" ng-model="dashboard.current.loader.load_local" ng-checked="dashboard.current.loader.load_local">
</div>
<div class="editor-option">
<label class="small">Gist</label><input type="checkbox" ng-model="dashboard.current.loader.load_gist" ng-checked="dashboard.current.loader.load_gist">
</div>
<div class="editor-option">
<label class="small">Elasticsearch</label><input type="checkbox" ng-model="dashboard.current.loader.load_elasticsearch" ng-checked="dashboard.current.loader.load_elasticsearch">
</div>
<div class="editor-option" ng-show="dashboard.current.loader.load.elasticsearch">
<label class="small">ES list size</label><input class="input-mini" type="number" ng-model="dashboard.current.loader.load_elasticsearch_size">
</div>
</div>
<div class="section">
<h5>Sharing</h5>
<div class="editor-option" >
<label class="small">Allow Sharing <tip>Allow generating adhoc links to dashboards</tip></label><input type="checkbox" ng-model="dashboard.current.loader.save_temp" ng-checked="dashboard.current.loader.save_temp">
</div>
<div class="editor-option" ng-show="dashboard.current.loader.save_temp">
<label class="small">TTL <tip>Expire temp urls</tip></label><input type="checkbox" ng-model="dashboard.current.loader.save_temp_ttl_enable">
</div>
<div class="editor-option" ng-show="dashboard.current.loader.save_temp &amp;&amp; dashboard.current.loader.save_temp_ttl_enable">
<label class="small">TTL Duration <tip>Elasticsearch date math, eg: 1m,1d,1w,30d </tip></label><input class="input-small" type="text" ng-model="dashboard.current.loader.save_temp_ttl">
</div>
</div>
</div>
</div>
<div ng-if="editor.index == 2">
<div class="editor-row">
<div class="section">
<h5>Feature toggles</h5>
<div class="editor-option" ng-repeat="pulldown in dashboard.current.pulldowns">
<label class="small" style="text-transform:capitalize;">{{pulldown.type}}</label><input type="checkbox" ng-model="pulldown.enable" ng-checked="pulldown.enable">
</div>
<div class="editor-option" ng-repeat="pulldown in dashboard.current.nav">
<label class="small" style="text-transform:capitalize;">{{pulldown.type}}</label><input type="checkbox" ng-model="pulldown.enable" ng-checked="pulldown.enable">
</div>
</div>
</div>
</div>
<div ng-if="editor.index == 3">
<ng-include src="'app/partials/loadmetrics.html'"></ng-include>
</div>
<div ng-if="editor.index == 4">
<ng-include src="'app/partials/import.html'"></ng-include>
</div>
<div ng-repeat="pulldown in dashboard.current.nav" ng-controller="SubmenuCtrl" ng-show="editor.index == 5+$index">
<ng-include ng-show="pulldown.enable" src="edit_path(pulldown.type)"></ng-include>
<button ng-hide="pulldown.enable" class="btn" ng-click="pulldown.enable = true">Enable the {{pulldown.type}}</button>
</div>
<div ng-model="editor.index" bs-tabs style="text-transform:capitalize;">
<div ng-repeat="tab in ['General', 'Rows', 'Features', 'Import']" data-title="{{tab}}">
</div>
<div ng-repeat="tab in dashboard.nav" data-title="{{tab.type}}">
</div>
</div>
</div>
<div class="modal-footer">
<div class="pull-left" style="padding-top: 15px;" ng-if="editor.index == 0">
<span class="editor-option small">
Grafana {{grafanaVersion}}
</span>
(<a class="small" href="https://github.com/torkelo/grafana/releases" target="_blank">check for updates</a>)
</div>
<div class="dashboard-editor-body">
<button ng-click="add_row(dashboard.current,row); reset_row();" class="btn btn-success" ng-show="editor.index == 1">Create Row</button>
<button type="button" class="btn btn-danger" ng-click="editor.index=0;dismiss();reset_panel();dashboard.refresh()">Close</button>
</div>
<div ng-if="editor.index == 0">
<div class="editor-row">
<div class="section">
<div class="editor-option">
<label class="small">Title</label><input type="text" class="input-large" ng-model='dashboard.title'></input>
</div>
<div class="editor-option">
<label class="small">Theme</label><select class="input-small" ng-model="dashboard.style" ng-options="f for f in ['dark','light']" ng-change="styleUpdated()"></select>
</div>
<div class="editor-option">
<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>
</div>
</div>
<div class="editor-row">
<div class="section">
<div class="editor-option">
<label class="small">Tags</label>
<bootstrap-tagsinput ng-model="dashboard.tags" tagclass="label label-tag" placeholder="add tags">
</bootstrap-tagsinput>
<tip>Press enter to a add tag</tip>
</div>
</div>
</div>
</div>
<div ng-if="editor.index == 1">
<div class="editor-row">
<div class="span6">
<table class="grafana-options-table">
<tr ng-repeat="row in dashboard.rows">
<td style="width: 97%">
{{row.title}}
</td>
<td><i ng-click="_.move(dashboard.rows,$index,$index-1)" ng-hide="$first" class="pointer icon-arrow-up"></i></td>
<td><i ng-click="_.move(dashboard.rows,$index,$index+1)" ng-hide="$last" class="pointer icon-arrow-down"></i></td>
<td>
<a ng-click="dashboard.rows = _.without(dashboard.rows,row)" class="btn btn-danger btn-mini">
<i class="icon-remove"></i>
</a>
</td>
</tr>
</table>
</div>
</div>
</div>
<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">
</div>
</div>
</div>
</div>
<div ng-if="editor.index == 3">
<ng-include src="'app/partials/import.html'"></ng-include>
</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>
<button ng-hide="pulldown.enable" class="btn" ng-click="pulldown.enable = true">Enable the {{pulldown.type}}</button>
</div>
</div>
<div class="clearfix"></div>
</div>
<div class="dashboard-editor-footer">
<div class="grafana-version-info" ng-show="editor.index === 0">
<span class="editor-option small">
Grafana version: {{grafanaVersion}} &nbsp;&nbsp;
</span>
<span grafana-version-check>
</span>
</div>
<button type="button" class="btn btn-success pull-right" ng-click="editor.index=0;dismiss();reset_panel();dashboard.emit_refresh()">Close</button>
</div>

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