dashboard.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  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, 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: 'timepicker'
  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. warm_fields: true
  59. },
  60. refresh: false
  61. };
  62. // An elasticJS client to use
  63. var ejs = ejsResource(config.elasticsearch);
  64. var gist_pattern = /(^\d{5,}$)|(^[a-z0-9]{10,}$)|(gist.github.com(\/*.*)\/[a-z0-9]{5,}\/*$)/;
  65. // Store a reference to this
  66. var self = this;
  67. var filterSrv;
  68. var graphiteSrv;
  69. this.current = _.clone(_dash);
  70. this.last = {};
  71. this.availablePanels = [];
  72. $rootScope.$on('$routeChangeSuccess',function(){
  73. // Clear the current dashboard to prevent reloading
  74. self.current = {};
  75. self.indices = [];
  76. route();
  77. });
  78. var route = function() {
  79. // Is there a dashboard type and id in the URL?
  80. if(!(_.isUndefined($routeParams.kbnType)) && !(_.isUndefined($routeParams.kbnId))) {
  81. var _type = $routeParams.kbnType;
  82. var _id = $routeParams.kbnId;
  83. switch(_type) {
  84. case ('elasticsearch'):
  85. self.elasticsearch_load('dashboard',_id);
  86. break;
  87. case ('temp'):
  88. self.elasticsearch_load('temp',_id);
  89. break;
  90. case ('file'):
  91. self.file_load(_id);
  92. break;
  93. case('script'):
  94. self.script_load(_id);
  95. break;
  96. case('local'):
  97. self.local_load();
  98. break;
  99. default:
  100. $location.path(config.default_route);
  101. }
  102. // No dashboard in the URL
  103. } else {
  104. // Check if browser supports localstorage, and if there's an old dashboard. If there is,
  105. // inform the user that they should save their dashboard to Elasticsearch and then set that
  106. // as their default
  107. if (Modernizr.localstorage) {
  108. if(!(_.isUndefined(window.localStorage['dashboard'])) && window.localStorage['dashboard'] !== '') {
  109. console.log(window.localStorage['dashboard']);
  110. $location.path(config.default_route);
  111. alertSrv.set('Saving to browser storage has been replaced',' with saving to Elasticsearch.'+
  112. ' Click <a href="#/dashboard/local/deprecated">here</a> to load your old dashboard anyway.');
  113. } else if(!(_.isUndefined(window.localStorage.kibanaDashboardDefault))) {
  114. $location.path(window.localStorage.kibanaDashboardDefault);
  115. } else {
  116. $location.path(config.default_route);
  117. }
  118. // No? Ok, grab the default route, its all we have now
  119. } else {
  120. $location.path(config.default_route);
  121. }
  122. }
  123. };
  124. this.refresh = function() {
  125. $rootScope.$broadcast('refresh');
  126. };
  127. var dash_defaults = function(dashboard) {
  128. _.defaults(dashboard,_dash);
  129. _.defaults(dashboard.index,_dash.index);
  130. _.defaults(dashboard.loader,_dash.loader);
  131. var filtering = _.findWhere(dashboard.pulldowns, {type: 'filtering'});
  132. if (!filtering) {
  133. dashboard.pulldowns.push({
  134. type: 'filtering',
  135. enable: false,
  136. collapse: true
  137. });
  138. }
  139. return dashboard;
  140. };
  141. this.dash_load = function(dashboard) {
  142. // Cancel all timers
  143. timer.cancel_all();
  144. // Make sure the dashboard being loaded has everything required
  145. dashboard = dash_defaults(dashboard);
  146. // If not using time based indices, use the default index
  147. if(dashboard.index.interval === 'none') {
  148. self.indices = [dashboard.index.default];
  149. }
  150. // Set the current dashboard
  151. self.current = _.clone(dashboard);
  152. // Delay this until we're sure that querySrv and filterSrv are ready
  153. $timeout(function() {
  154. // Ok, now that we've setup the current dashboard, we can inject our services
  155. filterSrv = $injector.get('filterSrv');
  156. graphiteSrv = $injector.get('graphiteSrv');
  157. filterSrv.init();
  158. },0).then(function() {
  159. // Call refresh to calculate the indices and notify the panels that we're ready to roll
  160. self.refresh();
  161. });
  162. if(dashboard.refresh) {
  163. self.set_interval(dashboard.refresh);
  164. }
  165. // Set the available panels for the "Add Panel" drop down
  166. self.availablePanels = _.difference(config.panel_names,
  167. _.pluck(_.union(self.current.nav,self.current.pulldowns),'type'));
  168. // Take out any that we're not allowed to add from the gui.
  169. self.availablePanels = _.difference(self.availablePanels,config.hidden_panels);
  170. return true;
  171. };
  172. this.gist_id = function(string) {
  173. if(self.is_gist(string)) {
  174. return string.match(gist_pattern)[0].replace(/.*\//, '');
  175. }
  176. };
  177. this.is_gist = function(string) {
  178. if(!_.isUndefined(string) && string !== '' && !_.isNull(string.match(gist_pattern))) {
  179. return string.match(gist_pattern).length > 0 ? true : false;
  180. } else {
  181. return false;
  182. }
  183. };
  184. this.to_file = function() {
  185. var blob = new Blob([angular.toJson(self.current,true)], {type: "application/json;charset=utf-8"});
  186. // from filesaver.js
  187. window.saveAs(blob, self.current.title+"-"+new Date().getTime());
  188. return true;
  189. };
  190. this.set_default = function(route) {
  191. console.log(route);
  192. if (Modernizr.localstorage) {
  193. // Purge any old dashboards
  194. if(!_.isUndefined(window.localStorage['dashboard'])) {
  195. delete window.localStorage['dashboard'];
  196. }
  197. window.localStorage.kibanaDashboardDefault = route;
  198. return true;
  199. } else {
  200. return false;
  201. }
  202. };
  203. this.purge_default = function() {
  204. if (Modernizr.localstorage) {
  205. // Purge any old dashboards
  206. if(!_.isUndefined(window.localStorage['dashboard'])) {
  207. delete window.localStorage['dashboard'];
  208. }
  209. delete window.localStorage.kibanaDashboardDefault;
  210. return true;
  211. } else {
  212. return false;
  213. }
  214. };
  215. // TOFIX: Pretty sure this breaks when you're on a saved dashboard already
  216. this.share_link = function(title,type,id) {
  217. return {
  218. location : window.location.href.replace(window.location.hash,""),
  219. type : type,
  220. id : id,
  221. link : window.location.href.replace(window.location.hash,"")+"#dashboard/"+type+"/"+id,
  222. title : title
  223. };
  224. };
  225. var renderTemplate = function(json,params) {
  226. var _r;
  227. _.templateSettings = {interpolate : /\{\{(.+?)\}\}/g};
  228. var template = _.template(json);
  229. var rendered = template({ARGS:params});
  230. try {
  231. _r = angular.fromJson(rendered);
  232. } catch(e) {
  233. _r = false;
  234. }
  235. return _r;
  236. };
  237. this.local_load = function() {
  238. var dashboard = JSON.parse(window.localStorage['dashboard']);
  239. dashboard.rows.unshift({
  240. height: "30",
  241. title: "Deprecation Notice",
  242. panels: [
  243. {
  244. title: 'WARNING: Legacy dashboard',
  245. type: 'text',
  246. span: 12,
  247. mode: 'html',
  248. content: 'This dashboard has been loaded from the browsers local cache. If you use '+
  249. 'another brower or computer you will not be able to access it! '+
  250. '\n\n <h4>Good news!</h4> Kibana'+
  251. ' now stores saved dashboards in Elasticsearch. Click the <i class="icon-save"></i> '+
  252. 'button in the top left to save this dashboard. Then select "Set as Home" from'+
  253. ' the "advanced" sub menu to automatically use the stored dashboard as your Kibana '+
  254. 'landing page afterwards'+
  255. '<br><br><strong>Tip:</strong> You may with to remove this row before saving!'
  256. }
  257. ]
  258. });
  259. self.dash_load(dashboard);
  260. };
  261. this.file_load = function(file) {
  262. return $http({
  263. url: "app/dashboards/"+file.replace(/\.(?!json)/,"/")+'?' + new Date().getTime(),
  264. method: "GET",
  265. transformResponse: function(response) {
  266. return renderTemplate(response,$routeParams);
  267. }
  268. }).then(function(result) {
  269. if(!result) {
  270. return false;
  271. }
  272. self.dash_load(dash_defaults(result.data));
  273. return true;
  274. },function() {
  275. alertSrv.set('Error',"Could not load <i>dashboards/"+file+"</i>. Please make sure it exists" ,'error');
  276. return false;
  277. });
  278. };
  279. this.elasticsearch_load = function(type,id) {
  280. return $http({
  281. url: config.elasticsearch + "/" + config.kibana_index + "/"+type+"/"+id+'?' + new Date().getTime(),
  282. method: "GET",
  283. transformResponse: function(response) {
  284. return renderTemplate(angular.fromJson(response)._source.dashboard, $routeParams);
  285. }
  286. }).error(function(data, status) {
  287. if(status === 0) {
  288. alertSrv.set('Error',"Could not contact Elasticsearch at "+config.elasticsearch+
  289. ". Please ensure that Elasticsearch is reachable from your system." ,'error');
  290. } else {
  291. alertSrv.set('Error',"Could not find "+id+". If you"+
  292. " are using a proxy, ensure it is configured correctly",'error');
  293. }
  294. return false;
  295. }).success(function(data) {
  296. self.dash_load(data);
  297. });
  298. };
  299. this.script_load = function(file) {
  300. return $http({
  301. url: "app/dashboards/"+file.replace(/\.(?!js)/,"/"),
  302. method: "GET",
  303. transformResponse: function(response) {
  304. /*jshint -W054 */
  305. var _f = new Function('ARGS','kbn','_','moment','window','document','angular','require','define','$','jQuery',response);
  306. return _f($routeParams,kbn,_,moment);
  307. }
  308. }).then(function(result) {
  309. if(!result) {
  310. return false;
  311. }
  312. self.dash_load(dash_defaults(result.data));
  313. return true;
  314. },function() {
  315. alertSrv.set('Error',
  316. "Could not load <i>scripts/"+file+"</i>. Please make sure it exists and returns a valid dashboard" ,
  317. 'error');
  318. return false;
  319. });
  320. };
  321. this.elasticsearch_save = function(type,title,ttl) {
  322. // Clone object so we can modify it without influencing the existing obejct
  323. var save = _.clone(self.current);
  324. var id;
  325. // Change title on object clone
  326. if (type === 'dashboard') {
  327. id = save.title = _.isUndefined(title) ? self.current.title : title;
  328. }
  329. // Create request with id as title. Rethink this.
  330. var request = ejs.Document(config.kibana_index,type,id).source({
  331. user: 'guest',
  332. group: 'guest',
  333. title: save.title,
  334. dashboard: angular.toJson(save)
  335. });
  336. request = type === 'temp' && ttl ? request.ttl(ttl) : request;
  337. return request.doIndex(
  338. // Success
  339. function(result) {
  340. if(type === 'dashboard') {
  341. $location.path('/dashboard/elasticsearch/'+title);
  342. }
  343. return result;
  344. },
  345. // Failure
  346. function() {
  347. return false;
  348. }
  349. );
  350. };
  351. this.elasticsearch_delete = function(id) {
  352. return ejs.Document(config.kibana_index,'dashboard',id).doDelete(
  353. // Success
  354. function(result) {
  355. return result;
  356. },
  357. // Failure
  358. function() {
  359. return false;
  360. }
  361. );
  362. };
  363. this.elasticsearch_list = function(query,count) {
  364. var request = ejs.Request().indices(config.kibana_index).types('dashboard');
  365. return request.query(
  366. ejs.QueryStringQuery(query || '*')
  367. ).size(count).doSearch(
  368. // Success
  369. function(result) {
  370. return result;
  371. },
  372. // Failure
  373. function() {
  374. return false;
  375. }
  376. );
  377. };
  378. this.save_gist = function(title,dashboard) {
  379. var save = _.clone(dashboard || self.current);
  380. save.title = title || self.current.title;
  381. return $http({
  382. url: "https://api.github.com/gists",
  383. method: "POST",
  384. data: {
  385. "description": save.title,
  386. "public": false,
  387. "files": {
  388. "kibana-dashboard.json": {
  389. "content": angular.toJson(save,true)
  390. }
  391. }
  392. }
  393. }).then(function(data) {
  394. return data.data.html_url;
  395. }, function() {
  396. return false;
  397. });
  398. };
  399. this.gist_list = function(id) {
  400. return $http.jsonp("https://api.github.com/gists/"+id+"?callback=JSON_CALLBACK"
  401. ).then(function(response) {
  402. var files = [];
  403. _.each(response.data.data.files,function(v) {
  404. try {
  405. var file = JSON.parse(v.content);
  406. files.push(file);
  407. } catch(e) {
  408. return false;
  409. }
  410. });
  411. return files;
  412. }, function() {
  413. return false;
  414. });
  415. };
  416. this.set_interval = function (interval) {
  417. self.current.refresh = interval;
  418. if(interval) {
  419. var _i = kbn.interval_to_ms(interval);
  420. timer.cancel(self.refresh_timer);
  421. self.refresh_timer = timer.register($timeout(function() {
  422. self.set_interval(interval);
  423. self.refresh();
  424. },_i));
  425. self.refresh();
  426. } else {
  427. timer.cancel(self.refresh_timer);
  428. }
  429. };
  430. });
  431. });