dashboard.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455
  1. define([
  2. 'angular',
  3. 'jquery',
  4. 'kbn',
  5. 'underscore',
  6. 'config',
  7. 'moment',
  8. 'modernizr',
  9. 'filesaver'
  10. ],
  11. function (angular, $, kbn, _, config, moment, Modernizr) {
  12. 'use strict';
  13. var module = angular.module('kibana.services');
  14. module.service('dashboard', function(
  15. $routeParams, $http, $rootScope, $injector, $location, $timeout,
  16. ejsResource, timer, kbnIndex, alertSrv
  17. ) {
  18. // A hash of defaults to use when loading a dashboard
  19. var _dash = {
  20. title: "",
  21. style: "dark",
  22. editable: true,
  23. failover: false,
  24. panel_hints: true,
  25. rows: [],
  26. pulldowns: [
  27. {
  28. type: 'query',
  29. },
  30. {
  31. type: 'filtering'
  32. }
  33. ],
  34. nav: [
  35. {
  36. type: 'timepicker2'
  37. }
  38. ],
  39. services: {},
  40. loader: {
  41. save_gist: false,
  42. save_elasticsearch: true,
  43. save_local: true,
  44. save_default: true,
  45. save_temp: true,
  46. save_temp_ttl_enable: true,
  47. save_temp_ttl: '30d',
  48. load_gist: false,
  49. load_elasticsearch: true,
  50. load_elasticsearch_size: 20,
  51. load_local: false,
  52. hide: false
  53. },
  54. index: {
  55. interval: 'none',
  56. pattern: '_all',
  57. default: 'INDEX_MISSING'
  58. },
  59. refresh: false
  60. };
  61. // An elasticJS client to use
  62. var ejs = ejsResource(config.elasticsearch);
  63. var gist_pattern = /(^\d{5,}$)|(^[a-z0-9]{10,}$)|(gist.github.com(\/*.*)\/[a-z0-9]{5,}\/*$)/;
  64. // Store a reference to this
  65. var self = this;
  66. var filterSrv,querySrv;
  67. this.current = _.clone(_dash);
  68. this.last = {};
  69. $rootScope.$on('$routeChangeSuccess',function(){
  70. // Clear the current dashboard to prevent reloading
  71. self.current = {};
  72. self.indices = [];
  73. route();
  74. });
  75. var route = function() {
  76. // Is there a dashboard type and id in the URL?
  77. if(!(_.isUndefined($routeParams.kbnType)) && !(_.isUndefined($routeParams.kbnId))) {
  78. var _type = $routeParams.kbnType;
  79. var _id = $routeParams.kbnId;
  80. switch(_type) {
  81. case ('elasticsearch'):
  82. self.elasticsearch_load('dashboard',_id);
  83. break;
  84. case ('temp'):
  85. self.elasticsearch_load('temp',_id);
  86. break;
  87. case ('file'):
  88. self.file_load(_id);
  89. break;
  90. case('script'):
  91. self.script_load(_id);
  92. break;
  93. default:
  94. self.file_load('default.json');
  95. }
  96. // No dashboard in the URL
  97. } else {
  98. // Check if browser supports localstorage, and if there's a dashboard
  99. if (Modernizr.localstorage &&
  100. !(_.isUndefined(window.localStorage['dashboard'])) &&
  101. window.localStorage['dashboard'] !== ''
  102. ) {
  103. var dashboard = JSON.parse(window.localStorage['dashboard']);
  104. self.dash_load(dashboard);
  105. // No? Ok, grab default.json, its all we have now
  106. } else {
  107. self.file_load('default.json');
  108. }
  109. }
  110. };
  111. // Since the dashboard is responsible for index computation, we can compute and assign the indices
  112. // here before telling the panels to refresh
  113. this.refresh = function() {
  114. if(self.current.index.interval !== 'none') {
  115. if(filterSrv.idsByType('time').length > 0) {
  116. var _range = filterSrv.timeRange('last');
  117. kbnIndex.indices(_range.from,_range.to,
  118. self.current.index.pattern,self.current.index.interval
  119. ).then(function (p) {
  120. if(p.length > 0) {
  121. self.indices = p;
  122. } else {
  123. // Option to not failover
  124. if(self.current.failover) {
  125. self.indices = [self.current.index.default];
  126. } else {
  127. // Do not issue refresh if no indices match. This should be removed when panels
  128. // properly understand when no indices are present
  129. alertSrv.set('No results','There were no results because no indices were found that match your'+
  130. ' selected time span','info',5000);
  131. return false;
  132. }
  133. }
  134. $rootScope.$broadcast('refresh');
  135. });
  136. } else {
  137. if(self.current.failover) {
  138. self.indices = [self.current.index.default];
  139. $rootScope.$broadcast('refresh');
  140. } else {
  141. alertSrv.set("No time filter",
  142. 'Timestamped indices are configured without a failover. Waiting for time filter.',
  143. 'info',5000);
  144. }
  145. }
  146. } else {
  147. self.indices = [self.current.index.default];
  148. $rootScope.$broadcast('refresh');
  149. }
  150. };
  151. var dash_defaults = function(dashboard) {
  152. _.defaults(dashboard,_dash);
  153. _.defaults(dashboard.index,_dash.index);
  154. _.defaults(dashboard.loader,_dash.loader);
  155. return dashboard;
  156. };
  157. this.dash_load = function(dashboard) {
  158. // Cancel all timers
  159. timer.cancel_all();
  160. // Make sure the dashboard being loaded has everything required
  161. dashboard = dash_defaults(dashboard);
  162. // If not using time based indices, use the default index
  163. if(dashboard.index.interval === 'none') {
  164. self.indices = [dashboard.index.default];
  165. }
  166. // Set the current dashboard
  167. self.current = _.clone(dashboard);
  168. // Ok, now that we've setup the current dashboard, we can inject our services
  169. querySrv = $injector.get('querySrv');
  170. filterSrv = $injector.get('filterSrv');
  171. // Make sure these re-init
  172. querySrv.init();
  173. filterSrv.init();
  174. // If there's an interval set, the indices have not been calculated yet,
  175. // so there is no data. Call refresh to calculate the indices and notify the panels.
  176. if(dashboard.index.interval !== 'none') {
  177. self.refresh();
  178. }
  179. if(dashboard.refresh) {
  180. self.set_interval(dashboard.refresh);
  181. }
  182. return true;
  183. };
  184. this.gist_id = function(string) {
  185. if(self.is_gist(string)) {
  186. return string.match(gist_pattern)[0].replace(/.*\//, '');
  187. }
  188. };
  189. this.is_gist = function(string) {
  190. if(!_.isUndefined(string) && string !== '' && !_.isNull(string.match(gist_pattern))) {
  191. return string.match(gist_pattern).length > 0 ? true : false;
  192. } else {
  193. return false;
  194. }
  195. };
  196. this.to_file = function() {
  197. var blob = new Blob([angular.toJson(self.current,true)], {type: "application/json;charset=utf-8"});
  198. // from filesaver.js
  199. window.saveAs(blob, self.current.title+"-"+new Date().getTime());
  200. return true;
  201. };
  202. this.set_default = function(dashboard) {
  203. if (Modernizr.localstorage) {
  204. window.localStorage['dashboard'] = angular.toJson(dashboard || self.current);
  205. $location.path('/dashboard');
  206. return true;
  207. } else {
  208. return false;
  209. }
  210. };
  211. this.purge_default = function() {
  212. if (Modernizr.localstorage) {
  213. window.localStorage['dashboard'] = '';
  214. return true;
  215. } else {
  216. return false;
  217. }
  218. };
  219. // TOFIX: Pretty sure this breaks when you're on a saved dashboard already
  220. this.share_link = function(title,type,id) {
  221. return {
  222. location : window.location.href.replace(window.location.hash,""),
  223. type : type,
  224. id : id,
  225. link : window.location.href.replace(window.location.hash,"")+"#dashboard/"+type+"/"+id,
  226. title : title
  227. };
  228. };
  229. var renderTemplate = function(json,params) {
  230. var _r;
  231. _.templateSettings = {interpolate : /\{\{(.+?)\}\}/g};
  232. var template = _.template(json);
  233. var rendered = template({ARGS:params});
  234. try {
  235. _r = angular.fromJson(rendered);
  236. } catch(e) {
  237. _r = false;
  238. }
  239. return _r;
  240. };
  241. this.file_load = function(file) {
  242. return $http({
  243. url: "app/dashboards/"+file+'?' + new Date().getTime(),
  244. method: "GET",
  245. transformResponse: function(response) {
  246. return renderTemplate(response,$routeParams);
  247. }
  248. }).then(function(result) {
  249. if(!result) {
  250. return false;
  251. }
  252. self.dash_load(dash_defaults(result.data));
  253. return true;
  254. },function() {
  255. alertSrv.set('Error',"Could not load <i>dashboards/"+file+"</i>. Please make sure it exists" ,'error');
  256. return false;
  257. });
  258. };
  259. this.elasticsearch_load = function(type,id) {
  260. return $http({
  261. url: config.elasticsearch + "/" + config.kibana_index + "/"+type+"/"+id,
  262. method: "GET",
  263. transformResponse: function(response) {
  264. return renderTemplate(angular.fromJson(response)._source.dashboard, $routeParams);
  265. }
  266. }).error(function(data, status) {
  267. if(status === 0) {
  268. alertSrv.set('Error',"Could not contact Elasticsearch at "+config.elasticsearch+
  269. ". Please ensure that Elasticsearch is reachable from your system." ,'error');
  270. } else {
  271. alertSrv.set('Error',"Could not find "+id+". If you"+
  272. " are using a proxy, ensure it is configured correctly",'error');
  273. }
  274. return false;
  275. }).success(function(data) {
  276. self.dash_load(data);
  277. });
  278. };
  279. this.script_load = function(file) {
  280. return $http({
  281. url: "app/dashboards/"+file,
  282. method: "GET",
  283. transformResponse: function(response) {
  284. /*jshint -W054 */
  285. var _f = new Function('ARGS','kbn','_','moment','window','document','angular','require','define','$','jQuery',response);
  286. return _f($routeParams,kbn,_,moment);
  287. }
  288. }).then(function(result) {
  289. if(!result) {
  290. return false;
  291. }
  292. self.dash_load(dash_defaults(result.data));
  293. return true;
  294. },function() {
  295. alertSrv.set('Error',
  296. "Could not load <i>scripts/"+file+"</i>. Please make sure it exists and returns a valid dashboard" ,
  297. 'error');
  298. return false;
  299. });
  300. };
  301. this.elasticsearch_save = function(type,title,ttl) {
  302. // Clone object so we can modify it without influencing the existing obejct
  303. var save = _.clone(self.current);
  304. var id;
  305. // Change title on object clone
  306. if (type === 'dashboard') {
  307. id = save.title = _.isUndefined(title) ? self.current.title : title;
  308. }
  309. // Create request with id as title. Rethink this.
  310. var request = ejs.Document(config.kibana_index,type,id).source({
  311. user: 'guest',
  312. group: 'guest',
  313. title: save.title,
  314. dashboard: angular.toJson(save)
  315. });
  316. request = type === 'temp' && ttl ? request.ttl(ttl) : request;
  317. return request.doIndex(
  318. // Success
  319. function(result) {
  320. if(type === 'dashboard') {
  321. $location.path('/dashboard/elasticsearch/'+title);
  322. }
  323. return result;
  324. },
  325. // Failure
  326. function() {
  327. return false;
  328. }
  329. );
  330. };
  331. this.elasticsearch_delete = function(id) {
  332. return ejs.Document(config.kibana_index,'dashboard',id).doDelete(
  333. // Success
  334. function(result) {
  335. return result;
  336. },
  337. // Failure
  338. function() {
  339. return false;
  340. }
  341. );
  342. };
  343. this.elasticsearch_list = function(query,count) {
  344. var request = ejs.Request().indices(config.kibana_index).types('dashboard');
  345. return request.query(
  346. ejs.QueryStringQuery(query || '*')
  347. ).size(count).doSearch(
  348. // Success
  349. function(result) {
  350. return result;
  351. },
  352. // Failure
  353. function() {
  354. return false;
  355. }
  356. );
  357. };
  358. this.save_gist = function(title,dashboard) {
  359. var save = _.clone(dashboard || self.current);
  360. save.title = title || self.current.title;
  361. return $http({
  362. url: "https://api.github.com/gists",
  363. method: "POST",
  364. data: {
  365. "description": save.title,
  366. "public": false,
  367. "files": {
  368. "kibana-dashboard.json": {
  369. "content": angular.toJson(save,true)
  370. }
  371. }
  372. }
  373. }).then(function(data) {
  374. return data.data.html_url;
  375. }, function() {
  376. return false;
  377. });
  378. };
  379. this.gist_list = function(id) {
  380. return $http.jsonp("https://api.github.com/gists/"+id+"?callback=JSON_CALLBACK"
  381. ).then(function(response) {
  382. var files = [];
  383. _.each(response.data.data.files,function(v) {
  384. try {
  385. var file = JSON.parse(v.content);
  386. files.push(file);
  387. } catch(e) {
  388. return false;
  389. }
  390. });
  391. return files;
  392. }, function() {
  393. return false;
  394. });
  395. };
  396. this.set_interval = function (interval) {
  397. self.current.refresh = interval;
  398. if(interval) {
  399. var _i = kbn.interval_to_ms(interval);
  400. timer.cancel(self.refresh_timer);
  401. self.refresh_timer = timer.register($timeout(function() {
  402. self.set_interval(interval);
  403. self.refresh();
  404. },_i));
  405. self.refresh();
  406. } else {
  407. timer.cancel(self.refresh_timer);
  408. }
  409. };
  410. });
  411. });