Compare commits

...

480 Commits

Author SHA1 Message Date
Torkel Ödegaard
05cb97819c Updated changelog 2014-09-30 18:21:19 +02:00
Torkel Ödegaard
ce972d4f19 Graph: css & scroll fix for dropdown menus in graph edit mode, Closes #855 2014-09-30 17:56:43 +02:00
Torkel Ödegaard
bf361d2b02 Updated package.json version and latest.json, preparation for 1.8.1 release 2014-09-30 09:03:14 +02:00
Torkel Ödegaard
64f3303711 InfluxDB: save dashboard issue, another fix for #859 2014-09-30 09:02:25 +02:00
Torkel Ödegaard
eaa899e9cf Small fix to fullscreen mode where scrolling would scroll the background dashboard & page header 2014-09-24 14:00:44 +02:00
Torkel Ödegaard
978a345ad8 Dashboard: When deleting dashboard show dashboard title in confirmation popup, Closes #860 2014-09-24 12:20:27 +02:00
Torkel Ödegaard
d5ffe6acef White theme: Fixes for hidden series legend text and disabled annotations color, Closes #852 2014-09-24 12:14:20 +02:00
Torkel Ödegaard
bce6e75cfa InfluxDB: Fix for bug when saving dashboard where title is the same as slugified url id, Fixes #859 2014-09-24 11:35:08 +02:00
Torkel Ödegaard
34f36fff5c small fix for graphite-web import 2014-09-24 11:17:34 +02:00
Torkel Ödegaard
f4e24038fe Import: Fixes to import from json file and import from graphite. Issues was lingering state from previous dashboard. Closes #840, Closes #853 2014-09-24 10:51:20 +02:00
Torkel Ödegaard
81747e1623 Annotations: Fix for annotations not reloaded when switching between 2 dashboards with annotations, Fixes #851 2014-09-24 09:03:04 +02:00
Torkel Ödegaard
0fbace7285 Row: fix for row editor and scroll pos, Fixes #846 2014-09-23 08:32:04 +02:00
Torkel Ödegaard
af8fec941c Graph: Fix for series draw order not being the same after hiding/unhiding series, Fixes #847 2014-09-23 08:18:59 +02:00
Torkel Ödegaard
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
9f3681642a Merge branch 'dashboard_loading_refactoring' of github.com:torkelo/grafana-private into dashboard_loading_refactoring 2014-06-14 14:55:29 +02:00
Torkel Ödegaard
a4ca679b65 Merge branch 'master' of github.com:torkelo/grafana-private into dashboard_loading_refactoring 2014-06-14 14:54:55 +02:00
Torkel Ödegaard
d198095d7a Merge branch 'master' into dashboard_loading_refactoring 2014-06-14 14:51:09 +02:00
Torkel Ödegaard
438455bc8c finally removed elasticjs library 2014-06-13 13:58:34 +02:00
Torkel Ödegaard
7914f65f68 removed redundant load/save options 2014-06-13 13:50:09 +02:00
Torkel Ödegaard
d0b79ad335 now dashboard deleting works 2014-06-12 17:40:37 +02:00
Torkel Ödegaard
7dc4484f6a Renamed dashboard service to dashboardModel, fixed saving default dashboard feature 2014-06-12 17:03:52 +02:00
Torkel Ödegaard
257ea391da fixed issues with unsaved changes srv 2014-06-12 16:35:58 +02:00
Torkel Ödegaard
4c64bcfae7 keyboard binding now work again 2014-06-12 13:37:40 +02:00
Torkel Ödegaard
99d2f537c2 sharing temp dashboard is now working again 2014-06-11 20:22:05 +02:00
Torkel Ödegaard
6c32365e00 more work on refactoring ES usage 2014-06-11 08:19:52 +02:00
Torkel Ödegaard
95925aafb1 removed dependence on elasticsearch js client from search, will try to remove this depdence in other places and just use $http, elasticsearch js client is to big and does not provide enough value 2014-06-11 08:11:13 +02:00
Torkel Ödegaard
0a3d4a5ab0 elasticsearch saving does now work, still have to do ttl and temp dashboard 2014-06-10 21:32:38 +02:00
Torkel Ödegaard
2328c60d12 small refactoring progress 2014-06-08 20:46:30 +02:00
Torkel Ödegaard
61cd5cf4e6 fixed jshint and jscs checks 2014-06-08 16:09:36 +02:00
Torkel Ödegaard
1eb9efe2d5 fixing broken things 2014-06-08 16:08:12 +02:00
Torkel Ödegaard
d5882f2efe ES and file loading is working 2014-06-08 15:28:50 +02:00
Torkel Ödegaard
5f3991127b refactoring progress 2014-06-08 14:40:44 +02:00
Torkel Ödegaard
b995dc54f1 further refactoring 2014-06-07 21:00:05 +02:00
Torkel Ödegaard
79404e754e started on some big refactoring of how the app starts and how dashboard object is loaded, created. This should make it easier to add other dashboard storage backends and other views 2014-06-07 19:43:15 +02:00
230 changed files with 34844 additions and 43198 deletions

3
.gitignore vendored
View File

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

View File

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

View File

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

135
README.md
View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,21 +4,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 = [],
@@ -48,102 +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'
];
var module_types = ['controllers', 'directives', 'factories', 'services', 'services.dashboard', 'filters'];
var module_types = ['controllers', 'directives', 'factories', 'services', 'filters', 'routes'];
_.each(module_types, function (type) {
var module_name = 'kibana.'+type;
var module_name = 'grafana.'+type;
// create the module
app.useModule(angular.module(module_name, []));
// push it into the apps dependencies
apps_deps.push(module_name);
});
// load the core components
require([
var preBootRequires = [
'controllers/all',
'directives/all',
'filters/all',
'components/partials',
], function () {
'routes/all',
];
// bootstrap the app
angular
.element(document)
.ready(function() {
$('body').attr('ng-controller', 'DashCtrl');
angular.bootstrap(document, apps_deps)
.invoke(['$rootScope', function ($rootScope) {
_.each(pre_boot_modules, function (module) {
_.extend(module, register_fns);
});
pre_boot_modules = false;
$rootScope.requireContext = appLevelRequire;
$rootScope.require = function (deps, fn) {
var $scope = this;
$scope.requireContext(deps, function () {
var deps = _.toArray(arguments);
// Check that this is a valid scope.
if($scope.$id) {
$scope.$apply(function () {
fn.apply($scope, deps);
});
}
});
};
}]);
});
_.each(config.plugins.dependencies, function(dep) {
preBootRequires.push('../plugins/' + dep);
});
app.boot = function() {
require(preBootRequires, function () {
// disable tool tip animation
$.fn.tooltip.defaults.animation = false;
// bootstrap the app
angular
.element(document)
.ready(function() {
angular.bootstrap(document, apps_deps)
.invoke(['$rootScope', function ($rootScope) {
_.each(pre_boot_modules, function (module) {
_.extend(module, register_fns);
});
pre_boot_modules = false;
$rootScope.requireContext = appLevelRequire;
$rootScope.require = function (deps, fn) {
var $scope = this;
$scope.requireContext(deps, function () {
var deps = _.toArray(arguments);
// Check that this is a valid scope.
if($scope.$id) {
$scope.$apply(function () {
fn.apply($scope, deps);
});
}
});
};
}]);
});
});
};
return app;
});

View File

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

View File

@@ -1,28 +1,13 @@
define(['jquery','underscore','moment'],
define([
'jquery',
'lodash',
'moment'
],
function($, _, moment) {
'use strict';
var kbn = {};
/**
* Calculate a graph interval
*
* from:: Date object containing the start time
* to:: Date object containing the finish time
* size:: Calculate to approximately this many bars
* user_interval:: User specified histogram interval
*
*/
kbn.calculate_interval = function(from,to,size,user_interval) {
if(_.isObject(from)) {
from = from.valueOf();
}
if(_.isObject(to)) {
to = to.valueOf();
}
return user_interval === 0 ? kbn.round_interval((to - from)/size) : user_interval;
};
kbn.round_interval = function(interval) {
switch (true) {
// 0.5s
@@ -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])) {
@@ -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:
@@ -396,6 +403,53 @@ function($, _, moment) {
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;
}
return (size.toFixed(decimals) + ext);
};
kbn.shortFormat = function(size, decimals) {
var ext, steps = 0;
@@ -457,6 +511,10 @@ function($, _, moment) {
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);
@@ -473,31 +531,62 @@ function($, _, moment) {
return function(val) {
return kbn.nanosFormat(val, decimals);
};
case 'percent':
return function(val, axis) {
return kbn.noneFormat(val, axis ? axis.tickDecimals : null) + ' %';
};
default:
return function(val) {
return val % 1 === 0 ? val : val.toFixed(decimals);
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 year, devide in days
else if (size < 31536000000) {
else if (Math.abs(size) < 31536000000) {
return (size / 86400000).toFixed(decimals) + " day";
}
@@ -505,24 +594,28 @@ function($, _, moment) {
};
kbn.sFormat = function(size, decimals) {
// Less than 1 sec, downscale to milli
if (size !== 0 && Math.abs(size) < 1) {
return kbn.msFormat(size * 1000, decimals);
}
// Less than 10 min, use seconds
if (size < 600) {
else if (Math.abs(size) < 600) {
return size.toFixed(decimals) + " s";
}
// Less than 1 hour, devide in minutes
else if (size < 3600) {
else if (Math.abs(size) < 3600) {
return (size / 60).toFixed(decimals) + " min";
}
// Less than one day, devide in hours
else if (size < 86400) {
else if (Math.abs(size) < 86400) {
return (size / 3600).toFixed(decimals) + " hour";
}
// Less than one week, devide in days
else if (size < 604800) {
else if (Math.abs(size) < 604800) {
return (size / 86400).toFixed(decimals) + " day";
}
// Less than one year, devide in week
else if (size < 31536000) {
else if (Math.abs(size) < 31536000) {
return (size / 604800).toFixed(decimals) + " week";
}
@@ -530,10 +623,14 @@ function($, _, moment) {
};
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 {
@@ -542,16 +639,19 @@ function($, _, moment) {
};
kbn.nanosFormat = function(size, decimals) {
if (size < 1000) {
if (Math.abs(size) < 1) {
return size.toFixed(decimals) + " ns";
}
else if (Math.abs(size) < 1000) {
return size.toFixed(0) + " ns";
}
else if (size < 1000000) {
else if (Math.abs(size) < 1000000) {
return (size / 1000).toFixed(decimals) + " µs";
}
else if (size < 1000000000) {
else if (Math.abs(size) < 1000000000) {
return (size / 1000000).toFixed(decimals) + " ms";
}
else if (size < 60000000000){
else if (Math.abs(size) < 60000000000){
return (size / 1000000000).toFixed(decimals) + " s";
}
else {
@@ -559,5 +659,21 @@ function($, _, moment) {
}
};
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';
@@ -33,4 +33,4 @@ function () {
});
return _;
});
});

View File

@@ -8,26 +8,27 @@ require.config({
config: ['../config', '../config.sample'],
settings: 'components/settings',
kbn: 'components/kbn',
store: 'components/store',
css: '../vendor/require/css',
text: '../vendor/require/text',
moment: '../vendor/moment',
filesaver: '../vendor/filesaver',
angular: '../vendor/angular/angular',
'angular-route': '../vendor/angular/angular-route',
'angular-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',
@@ -39,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']
@@ -81,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'],
@@ -103,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,21 +13,15 @@ function (_, crypto) {
* @type {Object}
*/
var defaults = {
elasticsearch : "http://"+window.location.hostname+":9200",
datasources : {
default: {
url: "http://"+window.location.hostname+":8080",
default: true
}
},
datasources : {},
window_title_prefix : 'Grafana - ',
panels : ['graph', 'text'],
plugins : {},
default_route : '/dashboard/file/default.json',
grafana_index : 'grafana-dash',
elasticsearch_all_disabled : false,
timezoneOffset : null,
playlist_timespan : "1m",
unsaved_changes_warning : true
unsaved_changes_warning : true,
search : { max_results: 16 },
admin : {}
};
// This initializes a new hash on purpose, to avoid adding parameters to
@@ -57,30 +51,38 @@ function (_, crypto) {
return datasource;
};
// backward compatible with old config
if (options.graphiteUrl) {
settings.datasources = {
graphite: {
type: 'graphite',
url: options.graphiteUrl,
default: true
}
settings.datasources.graphite = {
type: 'graphite',
url: options.graphiteUrl,
default: true
};
}
if (options.elasticsearch) {
settings.datasources.elasticsearch = {
type: 'elasticsearch',
url: options.elasticsearch,
index: options.grafana_index,
grafanaDB: true
};
}
_.each(settings.datasources, function(datasource, key) {
datasource.name = key;
parseBasicAuth(datasource);
if (datasource.url) { parseBasicAuth(datasource); }
if (datasource.type === 'influxdb') { parseMultipleHosts(datasource); }
});
var elasticParsed = parseBasicAuth({ url: settings.elasticsearch });
settings.elasticsearchBasicAuth = elasticParsed.basicAuth;
settings.elasticsearch = elasticParsed.url;
if (settings.plugins.panels) {
settings.panels = _.union(settings.panels, settings.plugins.panels);
}
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',
@@ -12,4 +13,7 @@ define([
'./playlistCtrl',
'./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,152 +0,0 @@
/** @scratch /index/0
* = Kibana
*
* // Why can't I have a preamble here?
*
* == Introduction
*
* Kibana is an open source (Apache Licensed), browser based analytics and search dashboard for
* ElasticSearch. Kibana is a snap to setup and start using. Written entirely in HTML and Javascript
* it requires only a plain webserver, Kibana requires no fancy server side components.
* Kibana strives to be easy to get started with, while also being flexible and powerful, just like
* Elasticsearch.
*
* include::configuration/config.js.asciidoc[]
*
* include::panels.asciidoc[]
*
*/
define([
'angular',
'jquery',
'config',
'underscore',
'services/all',
'services/dashboard/all'
],
function (angular, $, config, _) {
"use strict";
var module = angular.module('kibana.controllers');
module.controller('DashCtrl', function(
$scope, $rootScope, $timeout, ejsResource, dashboard, filterSrv, dashboardKeybindings,
alertSrv, panelMove, keyboardManager, grafanaVersion) {
$scope.requiredElasticSearchVersion = ">=0.90.3";
$scope.editor = {
index: 0
};
$scope.grafanaVersion = grafanaVersion[0] === '@' ? 'master' : grafanaVersion;
// For moving stuff around the dashboard.
$scope.panelMoveDrop = panelMove.onDrop;
$scope.panelMoveStart = panelMove.onStart;
$scope.panelMoveStop = panelMove.onStop;
$scope.panelMoveOver = panelMove.onOver;
$scope.panelMoveOut = panelMove.onOut;
$scope.init = function() {
$scope.config = config;
// Make stuff, including underscore.js available to views
$scope._ = _;
$scope.dashboard = dashboard;
$scope.dashAlerts = alertSrv;
$scope.filter = filterSrv;
$scope.filter.init(dashboard.current);
$rootScope.$on("dashboard-loaded", function(event, dashboard) {
$scope.filter.init(dashboard);
});
// Clear existing alerts
alertSrv.clearAll();
$scope.reset_row();
$scope.ejs = ejsResource(config.elasticsearch, config.elasticsearchBasicAuth);
$scope.bindKeyboardShortcuts();
};
$scope.bindKeyboardShortcuts = dashboardKeybindings.shortcuts;
$scope.isPanel = function(obj) {
if(!_.isNull(obj) && !_.isUndefined(obj) && !_.isUndefined(obj.type)) {
return true;
} else {
return false;
}
};
$scope.add_row = function(dash, row) {
dash.rows.push(row);
};
$scope.add_row_default = function() {
$scope.reset_row();
$scope.row.title = 'New row';
$scope.add_row(dashboard.current, $scope.row);
};
$scope.reset_row = function() {
$scope.row = {
title: '',
height: '250px',
editable: true,
};
};
$scope.row_style = function(row) {
return { 'min-height': row.collapse ? '5px' : row.height };
};
$scope.panel_path =function(type) {
if(type) {
return 'app/panels/'+type.replace(".","/");
} else {
return false;
}
};
$scope.edit_path = function(type) {
var p = $scope.panel_path(type);
if(p) {
return p+'/editor.html';
} else {
return false;
}
};
$scope.setEditorTabs = function(panelMeta) {
$scope.editorTabs = ['General','Panel'];
if(!_.isUndefined(panelMeta.editorTabs)) {
$scope.editorTabs = _.union($scope.editorTabs,_.pluck(panelMeta.editorTabs,'title'));
}
return $scope.editorTabs;
};
// This is whoafully incomplete, but will do for now
$scope.parse_error = function(data) {
var _error = data.match("nested: (.*?);");
return _.isNull(_error) ? data : _error[1];
};
$scope.colors = [
"#7EB26D","#EAB839","#6ED0E0","#EF843C","#E24D42","#1F78C1","#BA43A9","#705DA0", //1
"#508642","#CCA300","#447EBC","#C15C17","#890F02","#0A437C","#6D1F62","#584477", //2
"#B7DBAB","#F4D598","#70DBED","#F9BA8F","#F29191","#82B5D8","#E5A8E2","#AEA2E0", //3
"#629E51","#E5AC0E","#64B0C8","#E0752D","#BF1B00","#0A50A1","#962D82","#614D93", //4
"#9AC48A","#F2C96D","#65C5DB","#F9934E","#EA6460","#5195CE","#D683CE","#806EB7", //5
"#3F6833","#967302","#2F575E","#99440A","#58140C","#052B51","#511749","#3F2B5B", //6
"#E0F9D7","#FCEACA","#CFFAFF","#F9E2D2","#FCE2DE","#BADFF4","#F9D9F9","#DEDAF7" //7
];
$scope.init();
});
});

View File

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

View File

@@ -0,0 +1,132 @@
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,
$timeout) {
$scope.editor = { index: 0 };
$scope.panelNames = config.panels;
var resizeEventTimeout;
this.init = function(dashboardData) {
$scope.availablePanels = config.panels;
$scope.reset_row();
$scope.registerWindowResizeEvent();
$scope.onAppEvent('show-json-editor', $scope.showJsonEditor);
$scope.setupDashboard(dashboardData);
};
$scope.registerWindowResizeEvent = function() {
angular.element(window).bind('resize', function() {
$timeout.cancel(resizeEventTimeout);
resizeEventTimeout = $timeout(function() { $scope.$broadcast('render'); }, 200);
});
};
$scope.setupDashboard = function(dashboardData) {
$rootScope.performance.dashboardLoadStart = new Date().getTime();
$rootScope.performance.panelsInitialized = 0;
$rootScope.performance.panelsRendered = 0;
$scope.dashboard = dashboardSrv.create(dashboardData);
$scope.dashboardViewState = dashboardViewStateSrv.create($scope);
// init services
timeSrv.init($scope.dashboard);
templateValuesSrv.init($scope.dashboard, $scope.dashboardViewState);
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;
};
});
});

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('Do you want to delete dashboard ' + options.title + ' ?')) {
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,116 @@
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, $controller) {
$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);
};
$scope.initDashboard = function(dashboardData, viewScope) {
$controller('DashboardCtrl', { $scope: viewScope }).init(dashboardData);
};
$rootScope.onAppEvent = function(name, callback) {
var unbind = $rootScope.$on(name, callback);
this.$on('$destroy', unbind);
};
$rootScope.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,18 +1,18 @@
define([
'angular',
'app',
'underscore'
'lodash',
'kbn'
],
function (angular, app, _) {
function (angular, app, _, kbn) {
'use strict';
var module = angular.module('kibana.controllers');
var module = angular.module('grafana.controllers');
module.controller('GraphiteImportCtrl', function($scope, $rootScope, $timeout, datasourceSrv, dashboard) {
module.controller('GraphiteImportCtrl', function($scope, $rootScope, $timeout, datasourceSrv, $location) {
$scope.init = function() {
console.log('hej!');
$scope.datasources = datasourceSrv.listOptions();
$scope.datasources = datasourceSrv.getMetricSources();
$scope.setDatasource(null);
};
@@ -68,23 +68,24 @@ function (angular, app, _) {
currentRow = angular.copy(rowTemplate);
var newDashboard = angular.copy(dashboard.current);
var newDashboard = angular.copy($scope.dashboard);
newDashboard.rows = [];
newDashboard.title = state.name;
newDashboard.rows.push(currentRow);
_.each(state.graphs, function(graph) {
_.each(state.graphs, function(graph, index) {
if (currentRow.panels.length === graphsPerRow) {
currentRow = angular.copy(rowTemplate);
newDashboard.rows.push(currentRow);
}
panel = {
type: 'graphite',
type: 'graph',
span: 12 / graphsPerRow,
title: graph[1].title,
targets: [],
datasource: datasource
datasource: datasource,
id: index + 1
};
_.each(graph[1].target, function(target) {
@@ -96,7 +97,10 @@ function (angular, app, _) {
currentRow.panels.push(panel);
});
dashboard.dash_load(newDashboard);
window.grafanaImportDashboard = newDashboard;
$location.path('/dashboard/import/' + kbn.slugifyForUrl(newDashboard.title));
$scope.dismiss();
}
});

View File

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

View File

@@ -4,15 +4,30 @@ 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() {
$scope.target.function = $scope.target.function || 'mean';
$scope.target.column = $scope.target.column || 'value';
var target = $scope.target;
target.function = target.function || 'mean';
target.column = target.column || 'value';
// backward compatible correction of schema
if (target.condition_value) {
target.condition = target.condition_key + ' ' + target.condition_op + ' ' + target.condition_value;
delete target.condition_key;
delete target.condition_op;
delete target.condition_value;
}
if (target.groupby_field_add === false) {
target.groupby_field = '';
delete target.groupby_field_add;
}
$scope.rawQuery = false;
@@ -24,7 +39,7 @@ function (angular) {
];
$scope.operators = ['=', '=~', '>', '<', '!~', '<>'];
$scope.oldSeries = $scope.target.series;
$scope.oldSeries = target.series;
$scope.$on('typeahead-updated', function() {
$timeout($scope.get_data);
});
@@ -42,6 +57,7 @@ function (angular) {
$scope.seriesBlur = function() {
if ($scope.oldSeries !== $scope.target.series) {
$scope.oldSeries = $scope.target.series;
$scope.columnList = null;
$scope.get_data();
}
};
@@ -67,7 +83,7 @@ function (angular) {
};
$scope.listSeries = function(query, callback) {
if (!seriesList) {
if (!seriesList || query === '') {
seriesList = [];
$scope.datasource.listSeries().then(function(series) {
seriesList = series;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,22 +1,20 @@
define([
'angular',
'app',
'underscore'
'lodash'
],
function (angular, app, _) {
'use strict';
var module = angular.module('kibana.controllers');
var module = angular.module('grafana.controllers');
module.controller('RowCtrl', function($scope, $rootScope, $timeout) {
var _d = {
title: "Row",
height: "150px",
collapse: false,
collapsable: true,
editable: true,
panels: [],
notice: false
};
_.defaults($scope.row,_d);
@@ -26,62 +24,31 @@ 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(panel) {
var rowSpan = $scope.rowSpan($scope.row);
var panelCount = $scope.row.panels.length;
var space = (12 - rowSpan) - panel.span;
// try to make room of there is no space left
if (space <= 0) {
if (panelCount === 1) {
$scope.row.panels[0].span = 6;
panel.span = 6;
}
else if (panelCount === 2) {
$scope.row.panels[0].span = 4;
$scope.row.panels[1].span = 4;
panel.span = 4;
}
}
$scope.row.panels.push(panel);
$scope.dashboard.add_panel(panel, $scope.row);
};
$scope.delete_row = function() {
if (confirm("Are you sure you want to delete this row?")) {
$scope.dashboard.current.rows = _.without($scope.dashboard.current.rows, $scope.row);
$scope.dashboard.rows = _.without($scope.dashboard.rows, $scope.row);
}
};
$scope.move_row = function(direction) {
var rowsList = $scope.dashboard.current.rows;
var rowsList = $scope.dashboard.rows;
var rowIndex = _.indexOf(rowsList, $scope.row);
var newIndex = rowIndex + direction;
if (newIndex >= 0 && newIndex <= (rowsList.length - 1)) {
@@ -109,65 +76,31 @@ 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
};
@@ -184,12 +117,37 @@ function (angular, app, _) {
$scope.row.height = fixRowHeight($scope.row.height);
};
/** @scratch /panels/2
* --
*/
$scope.init();
});
});
module.directive('rowHeight', function() {
return function(scope, element) {
scope.$watchGroup(['row.collapse', 'row.height'], function() {
element[0].style.minHeight = scope.row.collapse ? '5px' : scope.row.height;
});
};
});
module.directive('panelWidth', function() {
return function(scope, element) {
scope.$watch('panel.span', function() {
element[0].style.width = ((scope.panel.span / 1.2) * 10) + '%';
});
};
});
module.directive('panelDropZone', function() {
return function(scope, element) {
scope.$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,7 +48,8 @@ function (angular, _, config, $) {
var selectedDash = $scope.results.dashboards[$scope.selectedIndex];
if (selectedDash) {
$location.path("/dashboard/elasticsearch/" + encodeURIComponent(selectedDash._id));
$location.search({});
$location.path("/dashboard/db/" + selectedDash.id);
setTimeout(function() {
$('body').click(); // hack to force dropdown to close;
});
@@ -48,39 +57,41 @@ function (angular, _, config, $) {
}
};
$scope.searchDasboards = function(query) {
var request = $scope.ejs.Request().indices(config.grafana_index).types('dashboard');
var tagsOnly = query.indexOf('tags!:') === 0;
if (tagsOnly) {
var tagsQuery = query.substring(6, query.length);
query = 'tags:' + tagsQuery + '*';
}
else {
if (query.length === 0) {
query = 'title:';
}
$scope.moveSelection = function(direction) {
$scope.selectedIndex = Math.max(Math.min($scope.selectedIndex + direction, $scope.resultCount - 1), 0);
};
if (query[query.length - 1] !== '*') {
query += '*';
}
}
$scope.goToDashboard = function(id) {
$location.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, title: dash.title });
$scope.results.dashboards = _.without($scope.results.dashboards, dash);
};
$scope.addMetricToCurrentDashboard = function (metricId) {
dashboard.current.rows.push({
$scope.dashboard.rows.push({
title: '',
height: '250px',
editable: true,
@@ -168,8 +141,7 @@ function (angular, _, config, $) {
});
};
$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,14 +1,14 @@
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) {
module.controller('SubmenuCtrl', function($scope, $q, $rootScope, templateValuesSrv) {
var _d = {
enable: true
};
@@ -18,10 +18,20 @@ function (angular, app, _) {
$scope.init = function() {
$scope.panel = $scope.pulldown;
$scope.row = $scope.pulldown;
$scope.variables = $scope.dashboard.templating.list;
};
$scope.disableAnnotation = function (annotation) {
annotation.enable = !annotation.enable;
$rootScope.$broadcast('refresh');
};
$scope.setVariableValue = function(param, option) {
templateValuesSrv.setVariableValue(param, option);
};
$scope.init();
});
});
});

View File

@@ -0,0 +1,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": null
"min": null,
"leftMax": null,
"rightMax": null,
"leftMin": null,
"rightMin": null,
"threshold1": null,
"threshold2": null,
"threshold1Color": "rgba(216, 200, 27, 0.27)",
"threshold2Color": "rgba(234, 112, 112, 0.22)"
},
"resolution": 100,
"lines": true,
@@ -60,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

@@ -28,16 +28,13 @@ timspan = '1d';
// Intialize a skeleton with nothing but a rows array and service object
dashboard = {
rows : [],
services : {}
};
// Set a title
dashboard.title = 'Scripted dash';
dashboard.services.filter = {
time: {
from: "now-" + (ARGS.from || timspan),
to: "now"
}
dashboard.time = {
from: "now-" + (ARGS.from || timspan),
to: "now"
};
var rows = 1;
@@ -59,7 +56,7 @@ for (var i = 0; i < rows; i++) {
panels: [
{
title: 'Events',
type: 'graphite',
type: 'graph',
span: 12,
fill: 1,
linewidth: 2,
@@ -77,4 +74,4 @@ for (var i = 0; i < rows; i++) {
}
return dashboard;
return dashboard;

View File

@@ -35,11 +35,9 @@ return function(callback) {
// Set a title
dashboard.title = 'Scripted dash';
dashboard.services.filter = {
time: {
from: "now-" + (ARGS.from || timspan),
to: "now"
}
dashboard.time = {
from: "now-" + (ARGS.from || timspan),
to: "now"
};
var rows = 1;
@@ -78,4 +76,4 @@ return function(callback) {
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,7 +1,7 @@
define([
'angular',
'app',
'underscore',
'lodash',
'jquery',
'../services/graphite/gfunc',
],
@@ -9,7 +9,7 @@ function (angular, app, _, $, gfunc) {
'use strict';
angular
.module('kibana.directives')
.module('grafana.directives')
.directive('graphiteAddFunc', function($compile) {
var inputTemplate = '<input type="text"'+
' class="grafana-target-segment-input input-medium grafana-target-segment-input"' +
@@ -38,6 +38,15 @@ function (angular, app, _, $, gfunc) {
items: 10,
updater: function (value) {
var funcDef = gfunc.getFuncDef(value);
if (!funcDef) {
// try find close match
value = value.toLowerCase();
funcDef = _.find(allFunctions, function(funcName) {
return funcName.toLowerCase().indexOf(value) === 0;
});
if (!funcDef) { return; }
}
$scope.$apply(function() {
$scope.addFunction(funcDef);
@@ -97,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',
@@ -15,6 +15,8 @@ define([
'./bodyClass',
'./addGraphiteFunc',
'./graphiteFuncEditor',
'./templateParamSelector',
'./graphiteSegment',
'./grafanaVersionCheck',
'./influxdbFuncEditor'
], function () {});
], function () {});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,12 +1,13 @@
define([
'angular'
'angular',
'kbn'
],
function (angular) {
function (angular, kbn) {
'use strict';
var module = angular.module('kibana.directives');
var module = angular.module('grafana.directives');
module.directive('dashUpload', function(timer, dashboard, alertSrv) {
module.directive('dashUpload', function(timer, alertSrv, $location) {
return {
restrict: 'A',
link: function(scope) {
@@ -14,8 +15,11 @@ function (angular) {
var files = evt.target.files; // FileList object
var readerOnload = function() {
return function(e) {
dashboard.dash_load(JSON.parse(e.target.result));
scope.$apply();
scope.$apply(function() {
window.grafanaImportDashboard = JSON.parse(e.target.result);
var title = kbn.slugifyForUrl(window.grafanaImportDashboard.title);
$location.path('/dashboard/import/' + title);
});
};
};
for (var i = 0, f; f = files[i]; i++) {
@@ -34,4 +38,4 @@ function (angular) {
}
};
});
});
});

View File

@@ -3,46 +3,38 @@ 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($rootScope, dashboard) {
module.directive('grafanaGraph', function($rootScope, timeSrv) {
return {
restrict: 'A',
template: '<div> </div>',
link: function(scope, elem) {
var data, plot, annotations;
var hiddenData = {};
var data, annotations;
var dashboard = scope.dashboard;
var legendSideLastValue = null;
scope.$on('refresh',function() {
if (scope.otherPanelInFullscreenMode()) { return; }
scope.get_data();
});
scope.$on('toggleLegend', function(e, series) {
_.each(series, function(serie) {
if (hiddenData[serie.alias]) {
data.push(hiddenData[serie.alias]);
delete hiddenData[serie.alias];
}
});
scope.$on('toggleLegend', function() {
render_panel();
});
// Receive render events
scope.$on('render',function(event, renderData) {
data = renderData || data;
annotations = data.annotations;
render_panel();
});
// Re-render if the window is resized
angular.element(window).bind('resize', function() {
if (!data) {
scope.get_data();
return;
}
annotations = data.annotations || annotations;
render_panel();
});
@@ -55,7 +47,7 @@ function (angular, $, kbn, moment, _) {
height = height - 32; // subtract panel title bar
if (scope.panel.legend.show) {
if (scope.panel.legend.show && !scope.panel.legend.rightSide) {
height = height - 21; // subtract one line legend
}
@@ -82,6 +74,10 @@ function (angular, $, kbn, moment, _) {
render_panel_as_graphite_png(data);
return true;
}
if (elem.width() === 0) {
return;
}
}
// Function for rendering panel
@@ -91,17 +87,6 @@ function (angular, $, kbn, moment, _) {
}
var panel = scope.panel;
_.each(_.keys(scope.hiddenSeries), function(seriesAlias) {
var dataSeries = _.find(data, function(series) {
return series.info.alias === seriesAlias;
});
if (dataSeries) {
hiddenData[dataSeries.info.alias] = dataSeries;
data = _.without(data, dataSeries);
}
});
var stack = panel.stack ? true : null;
// Populate element
@@ -113,7 +98,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
},
@@ -135,6 +120,7 @@ function (angular, $, kbn, moment, _) {
yaxes: [],
xaxis: {},
grid: {
minBorderMargin: 0,
markings: [],
backgroundColor: null,
borderWidth: 0,
@@ -148,11 +134,17 @@ 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 hidden remove points and disable stack
if (scope.hiddenSeries[series.info.alias]) {
series.data = [];
series.stack = false;
}
}
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;
}
@@ -161,9 +153,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) {
@@ -172,7 +194,7 @@ function (angular, $, kbn, moment, _) {
var max = _.isUndefined(scope.range.to) ? null : scope.range.to.getTime();
options.xaxis = {
timezone: dashboard.current.timezone,
timezone: dashboard.timezone,
show: scope.panel['x-axis'],
mode: "time",
min: min,
@@ -258,8 +280,8 @@ function (angular, $, kbn, moment, _) {
var defaults = {
position: 'left',
show: scope.panel['y-axis'],
min: scope.panel.grid.min,
max: scope.panel.percentage && scope.panel.stack ? 100 : scope.panel.grid.max,
min: scope.panel.grid.leftMin,
max: scope.panel.percentage && scope.panel.stack ? 100 : scope.panel.grid.leftMax,
};
options.yaxes.push(defaults);
@@ -267,6 +289,8 @@ function (angular, $, kbn, moment, _) {
if (_.findWhere(data, {yaxis: 2})) {
var secondY = _.clone(defaults);
secondY.position = 'right';
secondY.min = scope.panel.grid.rightMin;
secondY.max = scope.panel.percentage && scope.panel.stack ? 100 : scope.panel.grid.rightMax;
options.yaxes.push(secondY);
configureAxisMode(options.yaxes[1], scope.panel.y_formats[1]);
}
@@ -275,9 +299,7 @@ function (angular, $, kbn, moment, _) {
}
function configureAxisMode(axis, format) {
if (format !== 'none') {
axis.tickFormatter = kbn.getFormatFunction(format, 1);
}
axis.tickFormatter = kbn.getFormatFunction(format, 1);
}
function time_format(interval, ticks, min, max) {
@@ -302,7 +324,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;
@@ -314,7 +336,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>' + ' ' +
(decodeURIComponent(seriesInfo.alias)) +
seriesInfo.alias +
'</small><br>';
} else {
group = kbn.query_color_dot(item.series.color, 15) + ' ';
@@ -327,16 +349,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();
}
@@ -345,14 +361,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.show ? '&hideLegend=false' : '&hideLegend=true';
url += scope.panel.grid.min !== null ? '&yMin=' + scope.panel.grid.min : '';
url += scope.panel.grid.max !== null ? '&yMax=' + scope.panel.grid.max : '';
url += scope.panel.grid.leftMin !== null ? '&yMin=' + scope.panel.grid.leftMin : '';
url += scope.panel.grid.leftMax !== null ? '&yMax=' + scope.panel.grid.leftMax : '';
url += scope.panel.grid.rightMin !== null ? '&yMin=' + scope.panel.grid.rightMin : '';
url += scope.panel.grid.rightMax !== null ? '&yMax=' + scope.panel.grid.rightMax : '';
url += scope.panel['x-axis'] ? '' : '&hideAxes=true';
url += scope.panel['y-axis'] ? '' : '&hideYAxis=true';
@@ -363,6 +381,9 @@ function (angular, $, kbn, moment, _) {
case 'bits':
url += '&yUnitSystem=binary';
break;
case 'bps':
url += '&yUnitSystem=si';
break;
case 'short':
url += '&yUnitSystem=si';
break;
@@ -389,7 +410,7 @@ function (angular, $, kbn, moment, _) {
elem.bind("plotselected", function (event, ranges) {
scope.$apply(function() {
scope.filter.setTime({
timeSrv.setTime({
from : moment.utc(ranges.xaxis.from).toDate(),
to : moment.utc(ranges.xaxis.to).toDate(),
});

View File

@@ -0,0 +1,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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
define(['angular', 'jquery', 'underscore', 'moment'], function (angular, $, _, moment) {
define(['angular', 'jquery', 'lodash', 'moment'], function (angular, $, _, moment) {
'use strict';
var module = angular.module('kibana.filters');
var module = angular.module('grafana.filters');
module.filter('stringSort', function() {
return function(input) {
@@ -9,18 +9,6 @@ define(['angular', 'jquery', 'underscore', 'moment'], function (angular, $, _, m
};
});
/*
Filter an array of objects by elasticsearch version requirements
*/
module.filter('esVersion', function(esVersion) {
return function(items, require) {
var ret = _.filter(items,function(qt) {
return esVersion.is(qt[require]) ? true : false;
});
return ret;
};
});
module.filter('slice', function() {
return function(arr, start, end) {
if(!_.isUndefined(arr)) {
@@ -67,57 +55,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(/.*\//, '');
}
}
};
});
module.filter('urlDecode', function() {
return function(input) {
return decodeURIComponent(input);
};
});
});
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,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', 'bits', 's', 'ms', 'µs', 'ns']" ng-change="render()"></select>
</div>
<div class="editor-option">
<label class="small">Right Y Format <tip>Y-axis formatting</tip></label>
<select class="input-small" ng-model="panel.y_formats[1]" ng-options="f for f in ['none','short','bytes', 'bits', 's', 'ms', 'µs', 'ns']" ng-change="render()"></select>
</div>
<div class="editor-option">
<label class="small">Left Y-axis label</label>
<input ng-change="get_data()" ng-model-onblur placeholder="" type="text" class="input-medium" ng-model="panel.leftYAxisLabel">
</div>
<div class="editor-option">
<label class="small">Right Y-axis label</label>
<input ng-change="get_data()" ng-model-onblur placeholder="" type="text" class="input-medium" ng-model="panel.rightYAxisLabel">
</div>
<h5>Left Y Axis</h5>
<div class="editor-option">
<label class="small">Format <tip>Y-axis formatting</tip></label>
<select class="input-small" ng-model="panel.y_formats[0]" ng-options="f for f in ['none','short','bytes', 'bits', 'bps', 's', 'ms', 'µs', 'ns', 'percent']" ng-change="render()"></select>
</div>
<div class="editor-option">
<label class="small">Min / <a ng-click="toggleGridMinMax('leftMin')">Auto <i class="icon-star" ng-show="_.isNull(panel.grid.leftMin)"></i></a></label>
<input type="number" class="input-small" ng-model="panel.grid.leftMin" ng-change="render()" ng-model-onblur />
</div>
<div class="editor-option">
<label class="small">Max / <a ng-click="toggleGridMinMax('leftMax')">Auto <i class="icon-star" ng-show="_.isNull(panel.grid.leftMax)"></i></a></label>
<input type="number" class="input-small" ng-model="panel.grid.leftMax" ng-change="render()" ng-model-onblur />
</div>
<div class="editor-option">
<label class="small">Label</label>
<input ng-change="get_data()" ng-model-onblur placeholder="" type="text" class="input-medium" ng-model="panel.leftYAxisLabel">
</div>
</div>
<div class="section">
<h5>Right Y Axis</h5>
<div class="editor-option">
<label class="small">Format <tip>Y-axis formatting</tip></label>
<select class="input-small" ng-model="panel.y_formats[1]" ng-options="f for f in ['none','short','bytes', 'bits', 'bps', 's', 'ms', 'µs', 'ns', 'percent']" ng-change="render()"></select>
</div>
<div class="editor-option">
<label class="small">Min / <a ng-click="toggleGridMinMax('rightMin')">Auto <i class="icon-star" ng-show="_.isNull(panel.grid.rightMin)"></i></a></label>
<input type="number" class="input-small" ng-model="panel.grid.rightMin" ng-change="render()" ng-model-onblur />
</div>
<div class="editor-option">
<label class="small">Max / <a ng-click="toggleGridMinMax('rightMax')">Auto <i class="icon-star" ng-show="_.isNull(panel.grid.rightMax)"></i></a></label>
<input type="number" class="input-small" ng-model="panel.grid.rightMax" ng-change="render()" ng-model-onblur />
</div>
</div>
</div>
<div class="editor-row">
<div class="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 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

@@ -1,48 +1,45 @@
<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'>
<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 | urlDecode}}
{{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>
</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="histogram-legend-popover">
<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)"
<button ng-click="toggleYAxis(series);dismiss();"
class="btn btn-mini"
ng-class="{'btn-success': series.yaxis === 1 }">
Left
</button>
<button ng-click="toggleYAxis(series)"
<button ng-click="toggleYAxis(series);dismiss();"
class="btn btn-mini"
ng-class="{'btn-success': series.yaxis === 2 }">
Right

View File

@@ -1,33 +1,43 @@
<div ng-controller='graph'
ng-init="init()"
style="min-height:{{panel.height || row.height}}"
ng-class="{'panel-fullscreen': fullscreen}">
<div ng-controller='GraphCtrl'>
<div style="position: relative">
<div class="graph-wrapper" ng-class="{'graph-legend-rightside': panel.legend.rightSide}">
<div class="graph-canvas-wrapper">
<div ng-if="datapointsWarning" class="datapoints-warning">
<span class="small" ng-show="!datapointsCount">No datapoints <tip>Can be caused by timezone mismatch between browser and graphite server</tip></span>
<span class="small" ng-show="datapointsOutside">Datapoints outside time range <tip>Can be caused by timezone mismatch between browser and graphite server</tip></span>
</div>
<div ng-if="datapointsWarning" class="datapoints-warning">
<span class="small" ng-show="!datapointsCount">No datapoints <tip>Can be caused by timezone mismatch between browser and graphite server</tip></span>
<span class="small" ng-show="datapointsOutside">Datapoints outside time range <tip>Can be caused by timezone mismatch between browser and graphite server</tip></span>
</div>
<div grafana-graph class="pointer histogram-chart">
</div>
<div grafana-graph class="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 ng-if="panel.legend" class="grafana-legend-container">
<div ng-include="'app/panels/graph/legend.html'"></div>
</div>
<div class="clearfix"></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 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 class="tab-content" ng-repeat="tab in panelMeta.fullEditorTabs" ng-show="editorTabs[editor.index] == tab.title">
<div ng-include src="tab.src"></div>
</div>
</div>
</div>
<div 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,42 +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.graph', []);
var module = angular.module('grafana.panels.graph');
app.useModule(module);
module.controller('graph', function($scope, $rootScope, datasourceSrv, $timeout, annotationsSrv) {
module.controller('GraphCtrl', function($scope, $rootScope, panelSrv, annotationsSrv, timeSrv) {
$scope.panelMeta = {
modals : [],
@@ -86,7 +73,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
*/
scale : 1,
/** @scratch /panels/histogram/3
* y_formats :: 'none','bytes','bits','short', 's', 'ms'
* y_formats :: 'none','bytes','bits','bps','short', 's', 'ms'
*/
y_formats : ['short', 'short'],
/** @scratch /panels/histogram/5
@@ -95,8 +82,10 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
* grid.ma1::: Maximum y-axis value
*/
grid : {
max: null,
min: null,
leftMax: null,
rightMax: null,
leftMin: null,
rightMin: null,
threshold1: null,
threshold2: null,
threshold1Color: 'rgba(216, 200, 27, 0.27)',
@@ -174,101 +163,47 @@ 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.initBaseController(this, $scope);
$scope.fullscreen = false;
$scope.editor = { index: 1 };
$scope.editorTabs = _.pluck($scope.panelMeta.fullEditorTabs,'title');
$scope.hiddenSeries = {};
$scope.datasources = datasourceSrv.listOptions();
$scope.setDatasource($scope.panel.datasource);
if ($scope.panel.targets.length === 0) {
$scope.panel.targets.push({});
}
};
$scope.setDatasource = function(datasource) {
$scope.panel.datasource = datasource;
$scope.datasource = datasourceSrv.get(datasource);
if (!$scope.datasource) {
$scope.panel.error = "Cannot find datasource " + datasource;
return;
}
$scope.get_data();
};
$scope.removeTarget = function (target) {
$scope.panel.targets = _.without($scope.panel.targets, target);
$scope.get_data();
};
$scope.hiddenSeries = {};
$scope.updateTimeRange = function () {
$scope.range = this.filter.timeRange();
$scope.rangeUnparsed = this.filter.timeRange(false);
$scope.range = timeSrv.timeRange();
$scope.rangeUnparsed = timeSrv.timeRange(false);
$scope.resolution = Math.ceil($(window).width() * ($scope.panel.span / 12));
$scope.interval = '10m';
if ($scope.range) {
$scope.interval = kbn.secondsToHms(
kbn.calculate_interval($scope.range.from, $scope.range.to, $scope.resolution, 0) / 1000
);
}
$scope.interval = kbn.calculateInterval($scope.range, $scope.resolution, $scope.panel.interval);
};
$scope.get_data = function() {
delete $scope.panel.error;
$scope.panelMeta.loading = true;
$scope.updateTimeRange();
var graphiteQuery = {
var metricsQuery = {
range: $scope.rangeUnparsed,
interval: $scope.interval,
targets: $scope.panel.targets,
format: $scope.panel.renderer === 'png' ? 'png' : 'json',
maxDataPoints: $scope.resolution,
datasource: $scope.panel.datasource
cacheTimeout: $scope.panel.cacheTimeout
};
$scope.annotationsPromise = annotationsSrv.getAnnotations($scope.filter, $scope.rangeUnparsed);
$scope.annotationsPromise = annotationsSrv.getAnnotations($scope.rangeUnparsed, $scope.dashboard);
return $scope.datasource.query($scope.filter, graphiteQuery)
return $scope.datasource.query(metricsQuery)
.then($scope.dataHandler)
.then(null, function(err) {
$scope.panelMeta.loading = false;
$scope.panel.error = err.message || "Timeseries data request error";
$scope.panelMeta.error = err.message || "Timeseries data request error";
$scope.inspector.error = err;
$scope.render([]);
});
@@ -290,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) {
@@ -304,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,
});
@@ -334,14 +266,6 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
return series;
};
$scope.add_target = function() {
$scope.panel.targets.push({target: ''});
};
$scope.otherPanelInFullscreenMode = function() {
return $rootScope.fullscreen && !$scope.fullscreen;
};
$scope.render = function(data) {
$scope.$emit('render', data);
};
@@ -402,8 +326,12 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
};
$scope.toggleYAxis = function(info) {
info.yaxis = info.yaxis === 2 ? 1 : 2;
$scope.panel.aliasYAxis[info.alias] = info.yaxis;
var override = _.findWhere($scope.panel.seriesOverrides, { alias: info.alias });
if (!override) {
override = { alias: info.alias };
$scope.panel.seriesOverrides.push(override);
}
override.yaxis = info.yaxis === 2 ? 1 : 2;
$scope.render();
};
@@ -412,6 +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="top">
<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,74 +0,0 @@
define([
'underscore',
'kbn'
],
function (_, kbn) {
'use strict';
var ts = {};
ts.ZeroFilled = function (opts) {
this.datapoints = opts.datapoints;
this.info = opts.info;
this.label = opts.info.alias;
};
ts.ZeroFilled.prototype.getFlotPairs = function (fillStyle, yFormats) {
var result = [];
this.color = this.info.color;
this.yaxis = this.info.yaxis;
this.info.total = 0;
this.info.max = null;
this.info.min = 212312321312;
_.each(this.datapoints, function(valueArray) {
var currentTime = valueArray[1];
var currentValue = valueArray[0];
if (currentValue === null) {
if (fillStyle === 'connected') {
return;
}
if (fillStyle === 'null as zero') {
currentValue = 0;
}
}
if (_.isNumber(currentValue)) {
this.info.total += currentValue;
}
if (currentValue > this.info.max) {
this.info.max = currentValue;
}
if (currentValue < this.info.min) {
this.info.min = currentValue;
}
result.push([currentTime * 1000, currentValue]);
}, this);
if (result.length > 2) {
this.info.timeStep = result[1][0] - result[0][0];
}
if (result.length) {
this.info.avg = (this.info.total / result.length);
this.info.current = result[result.length-1][1];
var formater = kbn.getFormatFunction(yFormats[this.yaxis - 1], 2);
this.info.avg = this.info.avg != null ? formater(this.info.avg) : null;
this.info.current = this.info.current != null ? formater(this.info.current) : null;
this.info.min = this.info.min != null ? formater(this.info.min) : null;
this.info.max = this.info.max != null ? formater(this.info.max) : null;
this.info.total = this.info.total != null ? formater(this.info.total) : null;
}
return result;
};
return ts;
});

View File

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

View File

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

View File

@@ -1,28 +1,18 @@
/** @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"
@@ -35,67 +25,70 @@ function (angular, app, _, require) {
style: {},
};
_.defaults($scope.panel,_d);
_.defaults($scope.panel, _d);
$scope.init = function() {
$scope.initBaseController(this, $scope);
panelSrv.init(this);
$scope.ready = false;
$scope.$on('refresh', $scope.render);
$scope.render();
};
$scope.render = function() {
$scope.$emit('render');
};
$scope.openEditor = function() {
//$scope.$emit('open-modal','paneleditor');
console.log('scope id', $scope.$id);
};
});
module.directive('markdown', function() {
return {
restrict: 'E',
link: function(scope, element) {
scope.$on('render', function() {
render_panel();
});
function render_panel() {
require(['./lib/showdown'], function (Showdown) {
scope.ready = true;
var converter = new Showdown.converter();
var text = scope.panel.content.replace(/&/g, '&amp;')
.replace(/>/g, '&gt;')
.replace(/</g, '&lt;');
var htmlText = converter.makeHtml(text);
element.html(htmlText);
// For whatever reason, this fixes chrome. I don't like it, I think
// it makes things slow?
if(!scope.$$phase) {
scope.$apply();
}
});
}
render_panel();
if ($scope.panel.mode === 'markdown') {
$scope.renderMarkdown($scope.panel.content);
}
else if ($scope.panel.mode === 'html') {
$scope.updateContent($scope.panel.content);
}
else if ($scope.panel.mode === 'text') {
$scope.renderText($scope.panel.content);
}
};
});
module.filter('newlines', function() {
return function (input) {
return input.replace(/\n/g, '<br/>');
$scope.renderText = function(content) {
content = content
.replace(/&/g, '&amp;')
.replace(/>/g, '&gt;')
.replace(/</g, '&lt;')
.replace(/\n/g, '<br/>');
$scope.updateContent(content);
};
});
module.filter('striphtml', function () {
return function(text) {
return text
$scope.renderMarkdown = function(content) {
var text = content
.replace(/&/g, '&amp;')
.replace(/>/g, '&gt;')
.replace(/</g, '&lt;');
if (converter) {
$scope.updateContent(converter.makeHtml(text));
}
else {
require(['./lib/showdown'], function (Showdown) {
converter = new Showdown.converter();
$scope.updateContent(converter.makeHtml(text));
});
}
};
$scope.updateContent = function(html) {
try {
$scope.content = $sce.trustAsHtml(templateSrv.replace(html));
} catch(e) {
console.log('Text panel error: ', e);
$scope.content = $sce.trustAsHtml(html);
}
if(!$scope.$$phase) {
$scope.$digest();
}
};
$scope.openEditor = function() {
};
$scope.init();
});
});
});

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="filter.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="filter.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,21 +15,21 @@
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) {
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
@@ -39,8 +39,6 @@ function (angular, app, _, moment, kbn) {
refresh_intervals : ['5s','10s','30s','1m','5m','15m','30m','1h','2h','1d'],
};
var customTimeModal = null;
_.defaults($scope.panel,_d);
// ng-pattern regexs
@@ -52,40 +50,36 @@ function (angular, app, _, moment, kbn) {
millisecond: /^[0-9]*$/
};
$scope.timeSrv = timeSrv;
$scope.$on('refresh', function() {
$scope.init();
});
$scope.init = function() {
var time = this.filter.timeRange(true);
var time = timeSrv.timeRange(true);
if(time) {
$scope.panel.now = this.filter.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
@@ -112,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() {
@@ -129,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 = $scope.filter.setTime(_filter);
$scope.panel.filter_id = timeSrv.setTime(_filter);
// Update our representation
$scope.time = getScopeTimeObj(time.from,time.to);
@@ -148,7 +142,7 @@ function (angular, app, _, moment, kbn) {
to: "now"
};
this.filter.setTime(_filter);
timeSrv.setTime(_filter);
$scope.time = getScopeTimeObj(kbn.parseDate(_filter.from),new Date());
};
@@ -171,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) {

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 dashboard</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,110 +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 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" 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 class="row-open" ng-show="!row.collapse">
<div ng-show="row.collapsable" class='row-tab bgPrimary' ng-click="toggle_row(row)">
<span class="row-tab-button">
<i class="icon-caret-right" ></i>
</span>
</div>
<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 class="dropdown-submenu">
<a href="javascript:void();">Add Panel</a>
<ul class="dropdown-menu">
<li><a ng-click="add_panel_default('graph')">Graph</li></a>
<li><a ng-click="add_panel_default('text')">Text</li></a>
</ul>
</li>
<li class="dropdown-submenu">
<a href="javascript:void();">Set height</a>
<ul class="dropdown-menu">
<li><a ng-click="set_height('100px')">100 px</li></a>
<li><a ng-click="set_height('150px')">150 px</li></a>
<li><a ng-click="set_height('200px')">200 px</li></a>
<li><a ng-click="set_height('250px')">250 px</li></a>
<li><a ng-click="set_height('300px')">300 px</li></a>
<li><a ng-click="set_height('350px')">350 px</li></a>
<li><a ng-click="set_height('450px')">450 px</li></a>
<li><a ng-click="set_height('500px')">500 px</li></a>
<li><a ng-click="set_height('600px')">600 px</li></a>
<li><a ng-click="set_height('700px')">700 px</li></a>
</ul>
</li>
<li class="dropdown-submenu">
<a href="javascript:void();">Move</a>
<ul class="dropdown-menu">
<li><a ng-click="move_row(-1)">Up</li></a>
<li><a ng-click="move_row(1)">Down</li></a>
</ul>
</li>
<li>
<a config-modal="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">
<!-- 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>
<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-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>
</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,161 +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-normal" 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 grafana-version-footer" ng-if="editor.index == 0">
<span class="editor-option small">
Grafana version: {{grafanaVersion}}
</span>
<div class="small" grafana-version-check>
</div>
</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-info" 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>

View File

@@ -0,0 +1,19 @@
<div ng-controller="JsonEditorCtrl">
<div class="dashboard-editor-header">
<div class="dashboard-editor-title">
<i class="icon icon-edit"></i>
JSON
</div>
</div>
<div class="dashboard-editor-body" style="height: 500px">
<textarea ng-model="json" rows="20" spellcheck="false" style="width: 90%;"></textarea>
</div>
<div class="dashboard-editor-footer">
<button type="button" class="btn btn-success pull-left" ng-show="canUpdate" ng-click="update(); dismiss();">Update</button>
<button type="button" class="btn btn-success pull-right" ng-click="dismiss();">Close</button>
</div>
</div>

View File

@@ -0,0 +1,39 @@
<div class="editor-row">
<div class="section">
<h5>Index name</h5>
<div class="editor-option">
<input type="text" class="span4" ng-model='currentAnnotation.index' placeholder="events-*"></input>
</div>
</div>
<div class="section">
<h5>Search query (lucene) <tip>Use [[filterName]] in query to replace part of the query with a filter value</h5>
<div class="editor-option">
<input type="text" class="span6" ng-model='currentAnnotation.query' placeholder="tags:deploy"></input>
</div>
</div>
</div>
<div class="editor-row">
<div class="section">
<h5>Field mappings</h5>
<div class="editor-option">
<label class="small">Time</label>
<input type="text" class="input-small" ng-model='currentAnnotation.timeField' placeholder="@timestamp"></input>
</div>
<div class="editor-option">
<label class="small">Title</label>
<input type="text" class="input-small" ng-model='currentAnnotation.titleField' placeholder="desc"></input>
</div>
<div class="editor-option">
<label class="small">Tags</label>
<input type="text" class="input-small" ng-model='currentAnnotation.tagsField' placeholder="tags"></input>
</div>
<div class="editor-option">
<label class="small">Text</label>
<input type="text" class="input-small" ng-model='currentAnnotation.textField' placeholder=""></input>
</div>
</div>
</div>

View File

@@ -0,0 +1,15 @@
<div class="editor-row">
<div class="editor-option">
<label class="small">Graphite target expression</label>
<input type="text" class="span10" ng-model='currentAnnotation.target' placeholder=""></input>
</div>
</div>
<div class="editor-row">
<div class="editor-option">
<label class="small">Graphite event tags</label>
<input type="text" ng-model='currentAnnotation.tags' placeholder=""></input>
</div>
</div>

View File

@@ -1,5 +1,4 @@
<div class="editor-row" style="margin-top: 10px;">
<div class="editor-row">
<div ng-repeat="target in panel.targets"
class="grafana-target"
@@ -11,19 +10,19 @@
<ul class="grafana-target-controls">
<li ng-show="parserError">
<a bs-tooltip="parserError" style="color: rgb(229, 189, 28)" role="menuitem">
<i class="icon-warning-sign"></i>
<i class="icon icon-warning-sign"></i>
</a>
</li>
<li>
<a class="pointer" tabindex="1" ng-click="showTextEditor = !showTextEditor">
<i class="icon-pencil"></i>
<i class="icon icon-pencil"></i>
</a>
</li>
<li class="dropdown">
<a class="pointer dropdown-toggle"
data-toggle="dropdown"
tabindex="1">
<i class="icon-cog"></i>
<i class="icon icon-cog"></i>
</a>
<ul class="dropdown-menu pull-right" role="menu">
<li role="menuitem">
@@ -31,17 +30,19 @@
ng-click="duplicate()">
Duplicate
</a>
</li>
</ul>
</li>
<li>
<a class="pointer" tabindex="1" ng-click="removeTarget(target)">
<i class="icon-remove"></i>
<a class="pointer" tabindex="1" ng-click="removeDataQuery(target)">
<i class="icon icon-remove"></i>
</a>
</li>
</ul>
<ul class="grafana-target-controls-left">
<ul class="grafana-segment-list">
<li class="grafana-target-segment" style="min-width: 15px; text-align: center">
{{targetLetters[$index]}}
</li>
<li>
<a class="grafana-target-segment"
ng-click="target.hide = !target.hide; get_data();"
@@ -60,46 +61,123 @@
ng-show="showTextEditor" />
<ul class="grafana-segment-list" role="menu" ng-hide="showTextEditor">
<li class="dropdown" ng-repeat="segment in segments" role="menuitem">
<a tabindex="1"
class="grafana-target-segment dropdown-toggle"
data-toggle="dropdown"
ng-click="getAltSegments($index)"
focus-me="segment.focus"
ng-bind-html-unsafe="segment.html">
</a>
<ul class="dropdown-menu scrollable grafana-segment-dropdown-menu" role="menu">
<li ng-repeat="altSegment in altSegments" role="menuitem">
<a href="javascript:void(0)" tabindex="1" ng-click="setSegment($index, $parent.$index)" ng-bind-html-unsafe="altSegment.html"></a>
</li>
</ul>
</li>
<li ng-repeat="func in functions">
<li ng-repeat="segment in segments" role="menuitem" graphite-segment></li>
<li ng-repeat="func in functions">
<span graphite-func-editor class="grafana-target-segment grafana-target-function">
</span>
<!-- <a class="grafana-target-segment grafana-target-function dropdown-toggle"
bs-popover="'app/partials/graphite/funcEditor.html'"
data-placement="bottom">
{{func.def.name}}
</a> -->
<!-- <span class="grafana-target-segment grafana-target-function">
<span>{{func.def.name}}(</span><span ng-repeat="param in func.def.params">
<input type="text"
class="input-mini grafana-function-param-input"
dynamic-width
ng-model="func.params[$index]"></input>
</span><span>)</span>
</span> -->
</li>
<li class="dropdown" graphite-add-func>
</li>
</ul>
<div class="clearfix"></div>
</div>
</div>
</div>
<section class="grafana-metric-options">
<div class="grafana-target">
<div class="grafana-target-inner">
<ul class="grafana-segment-list">
<li class="grafana-target-segment grafana-target-segment-icon">
<i class="icon-wrench"></i>
</li>
<li class="grafana-target-segment">
cacheTimeout
</li>
<li>
<input type="text"
class="input-mini grafana-target-segment-input"
ng-model="panel.cacheTimeout"
bs-tooltip="'Graphite parameter to overwride memcache default timeout (unit is seconds)'"
data-placement="right"
spellcheck='false'
placeholder="60">
</li>
</ul>
<div class="clearfix"></div>
</div>
<div class="grafana-target-inner">
<ul class="grafana-segment-list">
<li class="grafana-target-segment grafana-target-segment-icon">
<i class="icon-info-sign"></i>
</li>
<li class="grafana-target-segment">
<a ng-click="toggleEditorHelp(1);" bs-tooltip="'click to show helpful info'" data-placement="bottom">
shorter legend names
</a>
</li>
<li class="grafana-target-segment">
<a ng-click="toggleEditorHelp(2);" bs-tooltip="'click to show helpful info'" data-placement="bottom">
series as parameters
</a>
</li>
<li class="grafana-target-segment">
<a ng-click="toggleEditorHelp(3)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
stacking
</a>
</li>
<li class="grafana-target-segment">
<a ng-click="toggleEditorHelp(4)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
templating
</a>
</li>
</ul>
<div class="clearfix"></div>
</div>
</div>
</div>
</section>
<div class="editor-row">
<div class="pull-left" style="margin-top: 30px;">
<div class="grafana-info-box span8" ng-if="editorHelpIndex === 1">
<h5>Shorter legend names</h5>
<ul>
<li>alias() function to specify a custom series name</li>
<li>aliasByNode(2) to alias by a specific part of your metric path</li>
<li>aliasByNode(2, -1) you can add multiple segment paths, and use negative index</li>
<li>groupByNode(2, 'sum') is useful if you have 2 wildcards in your metric path and want to sumSeries and group by</li>
</ul>
</div>
<div class="grafana-info-box span8" ng-if="editorHelpIndex === 2">
<h5>Series as parameter</h5>
<ul>
<li>Some graphite functions allow you to have many series arguments</li>
<li>Use #[A-Z] to use a graphite query as parameter to a function</li>
<li>
Examples:
<ul>
<li>asPercent(#A, #B)</li>
<li>prod.srv-01.counters.count - asPercent(#A) : percentage of count in comparison with A query</li>
<li>prod.srv-01.counters.count - sumSeries(#A) : sum count and series A </li>
<li>divideSeries(#A, #B)</li>
</ul>
</li>
<li>If a query is added only to be used as a parameter, hide it from the graph with the eye icon</li>
</ul>
</div>
<div class="grafana-info-box span6" ng-if="editorHelpIndex === 3">
<h5>Stacking</h5>
<ul>
<li>You find the stacking option under Display Styles tab</li>
<li>When stacking is enabled make sure null point mode is set to 'null as zero'</li>
</ul>
</div>
<div class="grafana-info-box span6" ng-if="editorHelpIndex === 4">
<h5>Templating</h5>
<ul>
<li>You can use a template variable in place of metric names</li>
<li>You can use a template variable in place of function parameters</li>
<li>You enable the templating feature in Dashboard settings / Feature toggles </li>
</ul>
</div>
</div>
</div>

View File

@@ -1,4 +1,4 @@
<div ng-controller="GraphiteImportCtrl" ng-init="init()" style="height: 400px">
<div ng-controller="GraphiteImportCtrl" ng-init="init()">
<h5>Import dashboards from graphite web</h5>
<div class="editor-row">
@@ -12,15 +12,19 @@
</li>
</ul>
</div>
<button ng-click="listAll()" class="btn btn-primary">List all dashboards</button>
<button ng-click="listAll()" class="btn btn-success">List all dashboards</button>
</div>
</div>
<div class="editor-row" style="margin-top: 10px;">
<table class="table table-condensed table-striped">
<div class="editor-row" style="margin-top: 10px;max-height: 400px; overflow-y: scroll;max-width: 500px;">
<table class="grafana-options-table">
<tr ng-repeat="dash in dashboards">
<td style="padding-right: 20px;"><button class="btn btn-success" ng-click="import(dash.name)">Import</button>
<td style="width: 100%; vertical-align: middle;">{{dash.name}}</td>
<td style="">{{dash.name}}</td>
<td style="padding-left: 20px;">
<a class="pointer" ng-click="import(dash.name)">
import
</a>
</td>
</tr>
</table>
</div>

View File

@@ -0,0 +1,29 @@
<div class="editor-row">
<div class="section">
<h5>InfluxDB Query <tip>Example: select text from events where $timeFilter</tip></h5>
<div class="editor-option">
<input type="text" class="span10" ng-model='currentAnnotation.query' placeholder="select text from events where $timeFilter"></input>
</div>
</div>
</div>
<div class="editor-row">
<div class="section">
<h5>Column mappings <tip>If your influxdb query returns more than one column you need to specify the column names bellow. An annotation event is composed of a title, tags, and an additional text field.</tip></h5>
<div class="editor-option">
<label class="small">Title</label>
<input type="text" class="input-small" ng-model='currentAnnotation.titleColumn' placeholder=""></input>
</div>
<div class="editor-option">
<label class="small">Tags</label>
<input type="text" class="input-small" ng-model='currentAnnotation.tagsColumn' placeholder=""></input>
</div>
<div class="editor-option">
<label class="small">Text</label>
<input type="text" class="input-small" ng-model='currentAnnotation.textColumn' placeholder=""></input>
</div>
</div>
</div>

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