module.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. /*
  2. ## Dashcontrol
  3. Dash control allows for saving, loading and sharing of dashboards. Do not
  4. disable the dashcontrol module as a special instance of it allows for loading
  5. the default dashboard from dashboards/default
  6. ### Parameters
  7. * save
  8. ** gist :: Allow saving to gist. Requires registering an oauth domain with Github
  9. ** elasticsearch :: Allow saving to a special Kibana index within Elasticsearch
  10. ** local :: Allow saving to local file
  11. * load
  12. ** gist :: Allow loading from gists
  13. ** elasticsearch :: Allow searching and loading of elasticsearch saved dashboards
  14. ** local :: Allow loading of dashboards from Elasticsearch
  15. * hide_control :: Upon save, hide this panel
  16. * elasticsearch_size :: show this many dashboards under the ES section in the load drop down
  17. * elasticsearch_saveto :: Special kibana index to save to
  18. * temp :: Allow saving of temp dashboards
  19. * temp_ttl :: How long should temp dashboards persist
  20. ### Group Events
  21. #### Sends
  22. * dashboard :: An object containing an entire dashboard to be loaded
  23. */
  24. angular.module('kibana.dashcontrol', [])
  25. .controller('dashcontrol', function($scope, $routeParams, $http, eventBus, timer) {
  26. $scope.panel = $scope.panel || {};
  27. // Set and populate defaults
  28. var _d = {
  29. status : "Stable",
  30. group : "default",
  31. save : {
  32. gist: false,
  33. elasticsearch: true,
  34. local: true,
  35. 'default': true
  36. },
  37. load : {
  38. gist: true,
  39. elasticsearch: true,
  40. local: true
  41. },
  42. hide_control: false,
  43. elasticsearch_size: 20,
  44. elasticsearch_saveto: $scope.config.kibana_index,
  45. temp: true,
  46. temp_ttl: '30d'
  47. }
  48. _.defaults($scope.panel,_d);
  49. // A hash of defaults for the dashboard object
  50. var _dash = {
  51. title: "",
  52. editable: true,
  53. rows: []
  54. }
  55. $scope.init = function() {
  56. // Long ugly if statement for figuring out which dashboard to load on init
  57. // If there is no dashboard defined, find one
  58. if(_.isUndefined($scope.dashboards)) {
  59. // First check the URL for a path to a dashboard
  60. if(!(_.isUndefined($routeParams.type)) && !(_.isUndefined($routeParams.id))) {
  61. var _type = $routeParams.type;
  62. var _id = $routeParams.id;
  63. if(_type === 'elasticsearch')
  64. $scope.elasticsearch_load('dashboard',_id)
  65. if(_type === 'temp')
  66. $scope.elasticsearch_load('temp',_id)
  67. if(_type === 'file')
  68. $scope.file_load(_id)
  69. // No dashboard in the URL
  70. } else {
  71. // Check if browser supports localstorage, and if there's a dashboard
  72. if (Modernizr.localstorage &&
  73. !(_.isUndefined(localStorage['dashboard'])) &&
  74. localStorage['dashboard'] !== ''
  75. ) {
  76. var dashboard = JSON.parse(localStorage['dashboard']);
  77. _.defaults(dashboard,_dash);
  78. $scope.dash_load(JSON.stringify(dashboard))
  79. // No? Ok, grab default.json, its all we have now
  80. } else {
  81. $scope.file_load('default')
  82. }
  83. }
  84. }
  85. $scope.gist_pattern = /(^\d{5,}$)|(^[a-z0-9]{10,}$)|(gist.github.com(\/*.*)\/[a-z0-9]{5,}\/*$)/;
  86. $scope.gist = {};
  87. $scope.elasticsearch = {};
  88. }
  89. $scope.to_file = function() {
  90. var blob = new Blob([angular.toJson($scope.dashboards,true)], {type: "application/json;charset=utf-8"});
  91. saveAs(blob, $scope.dashboards.title+"-"+new Date().getTime());
  92. }
  93. $scope.default = function() {
  94. if (Modernizr.localstorage) {
  95. localStorage['dashboard'] = angular.toJson($scope.dashboards);
  96. $scope.alert('Success',
  97. $scope.dashboards.title + " has been set as your default dashboard",
  98. 'success',5000)
  99. } else {
  100. $scope.alert('Bummer!',
  101. "Your browser is too old for this functionality",
  102. 'error',5000);
  103. }
  104. }
  105. $scope.share_link = function(title,type,id) {
  106. $scope.share = {
  107. location : location.href.replace(location.hash,""),
  108. type : type,
  109. id : id,
  110. link : location.href.replace(location.hash,"")+"#dashboard/"+type+"/"+id,
  111. title : title
  112. };
  113. }
  114. $scope.purge = function() {
  115. if (Modernizr.localstorage) {
  116. localStorage['dashboard'] = '';
  117. $scope.alert('Success',
  118. 'Default dashboard cleared',
  119. 'success',5000)
  120. } else {
  121. $scope.alert('Doh!',
  122. "Your browser is too old for this functionality",
  123. 'error',5000);
  124. }
  125. }
  126. $scope.file_load = function(file) {
  127. $http({
  128. url: "dashboards/"+file,
  129. method: "GET",
  130. }).success(function(data, status, headers, config) {
  131. var dashboard = data
  132. _.defaults(dashboard,_dash);
  133. $scope.dash_load(JSON.stringify(dashboard))
  134. }).error(function(data, status, headers, config) {
  135. $scope.alert('Default dashboard missing!','Could not locate dashboards/'+file,'error')
  136. });
  137. }
  138. $scope.elasticsearch_save = function(type) {
  139. // Clone object so we can modify it without influencing the existing obejct
  140. if($scope.panel.hide_control) {
  141. $scope.panel.hide = true;
  142. var save = _.clone($scope.dashboards)
  143. } else {
  144. var save = _.clone($scope.dashboards)
  145. }
  146. // Change title on object clone
  147. if(type === 'dashboard')
  148. var id = save.title = $scope.elasticsearch.title;
  149. // Create request with id as title. Rethink this.
  150. var request = $scope.ejs.Document($scope.panel.elasticsearch_saveto,type,id).source({
  151. user: 'guest',
  152. group: 'guest',
  153. title: save.title,
  154. dashboard: angular.toJson(save)
  155. })
  156. if(type === 'temp')
  157. request = request.ttl($scope.panel.temp_ttl)
  158. var result = request.doIndex();
  159. var id = result.then(function(result) {
  160. $scope.alert('Dashboard Saved','This dashboard has been saved to Elasticsearch','success',5000)
  161. $scope.elasticsearch_dblist($scope.elasticsearch.query);
  162. $scope.elasticsearch.title = '';
  163. if(type === 'temp')
  164. $scope.share_link($scope.dashboards.title,'temp',result._id)
  165. })
  166. $scope.panel.hide = false;
  167. }
  168. $scope.elasticsearch_delete = function(dashboard) {
  169. var result = $scope.ejs.Document($scope.panel.elasticsearch_saveto,'dashboard',dashboard._id).doDelete();
  170. result.then(function(result) {
  171. $scope.alert('Dashboard Deleted','','success',5000)
  172. $scope.elasticsearch.dashboards = _.without($scope.elasticsearch.dashboards,dashboard)
  173. })
  174. }
  175. $scope.elasticsearch_load = function(type,id) {
  176. var request = $scope.ejs.Request().indices($scope.panel.elasticsearch_saveto).types(type);
  177. var results = request.query(
  178. $scope.ejs.IdsQuery(id)
  179. ).size($scope.panel.elasticsearch_size).doSearch();
  180. results.then(function(results) {
  181. if(_.isUndefined(results)) {
  182. return;
  183. }
  184. $scope.panel.error = false;
  185. $scope.dash_load(results.hits.hits[0]['_source']['dashboard'])
  186. });
  187. }
  188. $scope.elasticsearch_dblist = function(query) {
  189. if($scope.panel.load.elasticsearch) {
  190. var request = $scope.ejs.Request().indices($scope.panel.elasticsearch_saveto).types('dashboard');
  191. var results = request.query(
  192. $scope.ejs.QueryStringQuery(query || '*')
  193. ).size($scope.panel.elasticsearch_size).doSearch();
  194. results.then(function(results) {
  195. if(_.isUndefined(results.hits)) {
  196. return;
  197. }
  198. $scope.panel.error = false;
  199. $scope.hits = results.hits.total;
  200. $scope.elasticsearch.dashboards = results.hits.hits
  201. });
  202. }
  203. }
  204. $scope.save_gist = function() {
  205. var save = _.clone($scope.dashboards)
  206. save.title = $scope.gist.title;
  207. $http({
  208. url: "https://api.github.com/gists",
  209. method: "POST",
  210. data: {
  211. "description": save.title,
  212. "public": false,
  213. "files": {
  214. "kibana-dashboard.json": {
  215. "content": angular.toJson(save,true)
  216. }
  217. }
  218. }
  219. }).success(function(data, status, headers, config) {
  220. $scope.gist.last = data.html_url;
  221. $scope.alert('Gist saved','You will be able to access your exported dashboard file at <a href="'+data.html_url+'">'+data.html_url+'</a> in a moment','success')
  222. }).error(function(data, status, headers, config) {
  223. $scope.alert('Unable to save','Save to gist failed for some reason','error',5000)
  224. });
  225. }
  226. $scope.gist_dblist = function(id) {
  227. $http.jsonp("https://api.github.com/gists/"+id+"?callback=JSON_CALLBACK"
  228. ).success(function(response) {
  229. $scope.gist.files = []
  230. _.each(response.data.files,function(v,k) {
  231. try {
  232. var file = JSON.parse(v.content)
  233. $scope.gist.files.push(file)
  234. } catch(e) {
  235. $scope.alert('Gist failure','The dashboard file is invalid','warning',5000)
  236. }
  237. });
  238. }).error(function(data, status, headers, config) {
  239. $scope.alert('Gist Failed','Could not retrieve dashboard list from gist','error',5000)
  240. });
  241. }
  242. $scope.dash_load = function(dashboard) {
  243. if(!_.isObject(dashboard))
  244. dashboard = JSON.parse(dashboard)
  245. eventBus.broadcast($scope.$id,'ALL','dashboard',{
  246. dashboard : dashboard,
  247. last : $scope.dashboards
  248. })
  249. timer.cancel_all();
  250. }
  251. $scope.gist_id = function(string) {
  252. if($scope.is_gist(string))
  253. return string.match($scope.gist_pattern)[0].replace(/.*\//, '');
  254. }
  255. $scope.is_gist = function(string) {
  256. if(!_.isUndefined(string) && string != '' && !_.isNull(string.match($scope.gist_pattern)))
  257. return string.match($scope.gist_pattern).length > 0 ? true : false;
  258. else
  259. return false
  260. }
  261. })
  262. .directive('dashUpload', function(timer, eventBus){
  263. return {
  264. restrict: 'A',
  265. link: function(scope, elem, attrs) {
  266. function file_selected(evt) {
  267. var files = evt.target.files; // FileList object
  268. // files is a FileList of File objects. List some properties.
  269. var output = [];
  270. for (var i = 0, f; f = files[i]; i++) {
  271. var reader = new FileReader();
  272. reader.onload = (function(theFile) {
  273. return function(e) {
  274. scope.dash_load(JSON.parse(e.target.result))
  275. scope.$apply();
  276. };
  277. })(f);
  278. reader.readAsText(f);
  279. }
  280. }
  281. // Check for the various File API support.
  282. if (window.File && window.FileReader && window.FileList && window.Blob) {
  283. // Something
  284. document.getElementById('dashupload').addEventListener('change', file_selected, false);
  285. } else {
  286. alert('Sorry, the HTML5 File APIs are not fully supported in this browser.');
  287. }
  288. }
  289. }
  290. }).filter('gistid', function() {
  291. var gist_pattern = /(\d{5,})|([a-z0-9]{10,})|(gist.github.com(\/*.*)\/[a-z0-9]{5,}\/*$)/;
  292. return function(input, scope) {
  293. //return input+"boners"
  294. if(!(_.isUndefined(input))) {
  295. var output = input.match(gist_pattern);
  296. if(!_.isNull(output) && !_.isUndefined(output))
  297. return output[0].replace(/.*\//, '');
  298. }
  299. }
  300. });;