services.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882
  1. /*jshint globalstrict:true, forin:false */
  2. /*global angular:true */
  3. /*global Blob:false*/
  4. 'use strict';
  5. angular.module('kibana.services', [])
  6. .service('alertSrv', function($timeout) {
  7. var self = this;
  8. // List of all alert objects
  9. this.list = [];
  10. this.set = function(title,text,severity,timeout) {
  11. var
  12. _a = {
  13. title: title || '',
  14. text: text || '',
  15. severity: severity || 'info',
  16. },
  17. _ca = angular.toJson(_a),
  18. _clist = _.map(self.list,function(alert){return angular.toJson(alert);});
  19. // If we already have this alert, remove it and add a new one
  20. // Why do this instead of skipping the add because it resets the timer
  21. if(_.contains(_clist,_ca)) {
  22. _.remove(self.list,_.indexOf(_clist,_ca));
  23. }
  24. self.list.push(_a);
  25. if (timeout > 0) {
  26. $timeout(function() {
  27. self.list = _.without(self.list,_a);
  28. }, timeout);
  29. }
  30. };
  31. this.clear = function(alert) {
  32. self.list = _.without(self.list,alert);
  33. };
  34. this.clearAll = function() {
  35. self.list = [];
  36. };
  37. })
  38. .service('fields', function(dashboard, $rootScope, $http, alertSrv) {
  39. // Save a reference to this
  40. var self = this;
  41. this.list = ['_type'];
  42. this.mapping = {};
  43. this.add_fields = function(f) {
  44. //self.list = _.union(f,self.list);
  45. };
  46. $rootScope.$watch(function(){return dashboard.indices;},function(n) {
  47. if(!_.isUndefined(n) && n.length) {
  48. // Only get the mapping for indices we don't know it for
  49. var indices = _.difference(n,_.keys(self.mapping));
  50. // Only get the mapping if there are indices
  51. if(indices.length > 0) {
  52. self.map(indices).then(function(result) {
  53. self.mapping = _.extend(self.mapping,result);
  54. self.list = mapFields(self.mapping);
  55. });
  56. // Otherwise just use the cached mapping
  57. } else {
  58. self.list = mapFields(_.pick(self.mapping,n));
  59. }
  60. }
  61. });
  62. var mapFields = function (m) {
  63. var fields = [];
  64. _.each(m, function(types,index) {
  65. _.each(types, function(v,k) {
  66. fields = _.union(fields,_.keys(v));
  67. });
  68. });
  69. return fields;
  70. };
  71. this.map = function(indices) {
  72. var request = $http({
  73. url: config.elasticsearch + "/" + indices.join(',') + "/_mapping",
  74. method: "GET"
  75. }).error(function(data, status, headers, conf) {
  76. if(status === 0) {
  77. alertSrv.set('Error',"Could not contact Elasticsearch at "+config.elasticsearch+
  78. ". Please ensure that Elasticsearch is reachable from your system." ,'error');
  79. } else {
  80. alertSrv.set('Error',"Could not find "+config.elasticsearch+"/"+indices.join(',')+"/_mapping. If you"+
  81. " are using a proxy, ensure it is configured correctly",'error');
  82. }
  83. });
  84. return request.then(function(p) {
  85. var mapping = {};
  86. _.each(p.data, function(v,k) {
  87. mapping[k] = {};
  88. _.each(v, function (v,f) {
  89. mapping[k][f] = flatten(v);
  90. });
  91. });
  92. return mapping;
  93. });
  94. };
  95. var flatten = function(obj,prefix) {
  96. var propName = (prefix) ? prefix : '',
  97. dot = (prefix) ? '.':'',
  98. ret = {};
  99. for(var attr in obj){
  100. // For now only support multi field on the top level
  101. // and if if there is a default field set.
  102. if(obj[attr]['type'] === 'multi_field') {
  103. ret[attr] = obj[attr]['fields'][attr] || obj[attr];
  104. continue;
  105. }
  106. if (attr === 'properties') {
  107. _.extend(ret,flatten(obj[attr], propName));
  108. } else if(typeof obj[attr] === 'object'){
  109. _.extend(ret,flatten(obj[attr], propName + dot + attr));
  110. } else {
  111. ret[propName] = obj;
  112. }
  113. }
  114. return ret;
  115. };
  116. })
  117. .service('kbnIndex',function($http,alertSrv) {
  118. // returns a promise containing an array of all indices matching the index
  119. // pattern that exist in a given range
  120. this.indices = function(from,to,pattern,interval) {
  121. var possible = [];
  122. _.each(expand_range(fake_utc(from),fake_utc(to),interval),function(d){
  123. possible.push(d.format(pattern));
  124. });
  125. return all_indices().then(function(p) {
  126. var indices = _.intersection(possible,p);
  127. indices.reverse();
  128. return indices;
  129. });
  130. };
  131. // returns a promise containing an array of all indices in an elasticsearch
  132. // cluster
  133. function all_indices() {
  134. var something = $http({
  135. url: config.elasticsearch + "/_aliases",
  136. method: "GET"
  137. }).error(function(data, status, headers, conf) {
  138. if(status === 0) {
  139. alertSrv.set('Error',"Could not contact Elasticsearch at "+config.elasticsearch+
  140. ". Please ensure that Elasticsearch is reachable from your system." ,'error');
  141. } else {
  142. alertSrv.set('Error',"Could not reach "+config.elasticsearch+"/_aliases. If you"+
  143. " are using a proxy, ensure it is configured correctly",'error');
  144. }
  145. });
  146. return something.then(function(p) {
  147. var indices = [];
  148. _.each(p.data, function(v,k) {
  149. indices.push(k);
  150. // Also add the aliases. Could be expensive on systems with a lot of them
  151. _.each(v.aliases, function(v, k) {
  152. indices.push(k);
  153. });
  154. });
  155. return indices;
  156. });
  157. }
  158. // this is stupid, but there is otherwise no good way to ensure that when
  159. // I extract the date from an object that I get the UTC date. Stupid js.
  160. // I die a little inside every time I call this function.
  161. // Update: I just read this again. I died a little more inside.
  162. // Update2: More death.
  163. function fake_utc(date) {
  164. date = moment(date).clone().toDate();
  165. return moment(new Date(date.getTime() + date.getTimezoneOffset() * 60000));
  166. }
  167. // Create an array of date objects by a given interval
  168. function expand_range(start, end, interval) {
  169. if(_.contains(['hour','day','week','month','year'],interval)) {
  170. var range;
  171. start = moment(start).clone();
  172. range = [];
  173. while (start.isBefore(end)) {
  174. range.push(start.clone());
  175. switch (interval) {
  176. case 'hour':
  177. start.add('hours',1);
  178. break;
  179. case 'day':
  180. start.add('days',1);
  181. break;
  182. case 'week':
  183. start.add('weeks',1);
  184. break;
  185. case 'month':
  186. start.add('months',1);
  187. break;
  188. case 'year':
  189. start.add('years',1);
  190. break;
  191. }
  192. }
  193. range.push(moment(end).clone());
  194. return range;
  195. } else {
  196. return false;
  197. }
  198. }
  199. })
  200. .service('timer', function($timeout) {
  201. // This service really just tracks a list of $timeout promises to give us a
  202. // method for cancelling them all when we need to
  203. var timers = [];
  204. this.register = function(promise) {
  205. timers.push(promise);
  206. return promise;
  207. };
  208. this.cancel = function(promise) {
  209. timers = _.without(timers,promise);
  210. $timeout.cancel(promise);
  211. };
  212. this.cancel_all = function() {
  213. _.each(timers, function(t){
  214. $timeout.cancel(t);
  215. });
  216. timers = [];
  217. };
  218. })
  219. .service('querySrv', function(dashboard, ejsResource) {
  220. // Create an object to hold our service state on the dashboard
  221. dashboard.current.services.query = dashboard.current.services.query || {};
  222. _.defaults(dashboard.current.services.query,{
  223. idQueue : [],
  224. list : {},
  225. ids : [],
  226. });
  227. // For convenience
  228. var ejs = ejsResource(config.elasticsearch);
  229. var _q = dashboard.current.services.query;
  230. this.colors = [
  231. "#7EB26D","#EAB839","#6ED0E0","#EF843C","#E24D42","#1F78C1","#BA43A9","#705DA0", //1
  232. "#508642","#CCA300","#447EBC","#C15C17","#890F02","#0A437C","#6D1F62","#584477", //2
  233. "#B7DBAB","#F4D598","#70DBED","#F9BA8F","#F29191","#82B5D8","#E5A8E2","#AEA2E0", //3
  234. "#629E51","#E5AC0E","#64B0C8","#E0752D","#BF1B00","#0A50A1","#962D82","#614D93", //4
  235. "#9AC48A","#F2C96D","#65C5DB","#F9934E","#EA6460","#5195CE","#D683CE","#806EB7", //5
  236. "#3F6833","#967302","#2F575E","#99440A","#58140C","#052B51","#511749","#3F2B5B", //6
  237. "#E0F9D7","#FCEACA","#CFFAFF","#F9E2D2","#FCE2DE","#BADFF4","#F9D9F9","#DEDAF7" //7
  238. ];
  239. // Save a reference to this
  240. var self = this;
  241. this.init = function() {
  242. _q = dashboard.current.services.query;
  243. self.list = dashboard.current.services.query.list;
  244. self.ids = dashboard.current.services.query.ids;
  245. if (self.ids.length === 0) {
  246. self.set({});
  247. }
  248. };
  249. // This is used both for adding queries and modifying them. If an id is passed, the query at that id is updated
  250. this.set = function(query,id) {
  251. if(!_.isUndefined(id)) {
  252. if(!_.isUndefined(self.list[id])) {
  253. _.extend(self.list[id],query);
  254. return id;
  255. } else {
  256. return false;
  257. }
  258. } else {
  259. var _id = nextId();
  260. var _query = {
  261. query: '*',
  262. alias: '',
  263. color: colorAt(_id),
  264. pin: false,
  265. id: _id,
  266. type: 'lucene'
  267. };
  268. _.defaults(query,_query);
  269. self.list[_id] = query;
  270. self.ids.push(_id);
  271. return _id;
  272. }
  273. };
  274. this.remove = function(id) {
  275. if(!_.isUndefined(self.list[id])) {
  276. delete self.list[id];
  277. // This must happen on the full path also since _.without returns a copy
  278. self.ids = dashboard.current.services.query.ids = _.without(self.ids,id);
  279. _q.idQueue.unshift(id);
  280. _q.idQueue.sort(function(v,k){
  281. return v-k;
  282. });
  283. return true;
  284. } else {
  285. return false;
  286. }
  287. };
  288. this.getEjsObj = function(id) {
  289. return self.toEjsObj(self.list[id]);
  290. };
  291. this.toEjsObj = function (q) {
  292. switch(q.type)
  293. {
  294. case 'lucene':
  295. return ejs.QueryStringQuery(q.query || '*');
  296. default:
  297. return _.isUndefined(q.query) ? false : ejs.QueryStringQuery(q.query || '*');
  298. }
  299. };
  300. this.findQuery = function(queryString) {
  301. return _.findWhere(self.list,{query:queryString});
  302. };
  303. this.idsByMode = function(config) {
  304. switch(config.mode)
  305. {
  306. case 'all':
  307. return self.ids;
  308. case 'pinned':
  309. return _.pluck(_.where(self.list,{pin:true}),'id');
  310. case 'unpinned':
  311. return _.difference(self.ids,_.pluck(_.where(self.list,{pin:true}),'id'));
  312. case 'selected':
  313. return _.intersection(self.ids,config.ids);
  314. default:
  315. return self.ids;
  316. }
  317. };
  318. var nextId = function() {
  319. if(_q.idQueue.length > 0) {
  320. return _q.idQueue.shift();
  321. } else {
  322. return self.ids.length;
  323. }
  324. };
  325. var colorAt = function(id) {
  326. return self.colors[id % self.colors.length];
  327. };
  328. self.init();
  329. })
  330. .service('filterSrv', function(dashboard, ejsResource) {
  331. // Create an object to hold our service state on the dashboard
  332. dashboard.current.services.filter = dashboard.current.services.filter || {};
  333. _.defaults(dashboard.current.services.filter,{
  334. idQueue : [],
  335. list : {},
  336. ids : []
  337. });
  338. // For convenience
  339. var ejs = ejsResource(config.elasticsearch);
  340. var _f = dashboard.current.services.filter;
  341. // Save a reference to this
  342. var self = this;
  343. // Call this whenever we need to reload the important stuff
  344. this.init = function() {
  345. // Accessors
  346. self.list = dashboard.current.services.filter.list;
  347. self.ids = dashboard.current.services.filter.ids;
  348. _f = dashboard.current.services.filter;
  349. _.each(self.getByType('time',true),function(time) {
  350. self.list[time.id].from = new Date(time.from);
  351. self.list[time.id].to = new Date(time.to);
  352. });
  353. };
  354. // This is used both for adding filters and modifying them.
  355. // If an id is passed, the filter at that id is updated
  356. this.set = function(filter,id) {
  357. _.defaults(filter,{mandate:'must'});
  358. filter.active = true;
  359. if(!_.isUndefined(id)) {
  360. if(!_.isUndefined(self.list[id])) {
  361. _.extend(self.list[id],filter);
  362. return id;
  363. } else {
  364. return false;
  365. }
  366. } else {
  367. if(_.isUndefined(filter.type)) {
  368. return false;
  369. } else {
  370. var _id = nextId();
  371. var _filter = {
  372. alias: '',
  373. id: _id
  374. };
  375. _.defaults(filter,_filter);
  376. self.list[_id] = filter;
  377. self.ids.push(_id);
  378. return _id;
  379. }
  380. }
  381. };
  382. this.getBoolFilter = function(ids) {
  383. // A default match all filter, just in case there are no other filters
  384. var bool = ejs.BoolFilter().must(ejs.MatchAllFilter());
  385. var either_bool = ejs.BoolFilter().must(ejs.MatchAllFilter());
  386. _.each(ids,function(id) {
  387. if(self.list[id].active) {
  388. switch(self.list[id].mandate)
  389. {
  390. case 'mustNot':
  391. bool = bool.mustNot(self.getEjsObj(id));
  392. break;
  393. case 'either':
  394. either_bool = either_bool.should(self.getEjsObj(id));
  395. break;
  396. default:
  397. bool = bool.must(self.getEjsObj(id));
  398. }
  399. }
  400. });
  401. return bool.must(either_bool);
  402. };
  403. this.getEjsObj = function(id) {
  404. return self.toEjsObj(self.list[id]);
  405. };
  406. this.toEjsObj = function (filter) {
  407. if(!filter.active) {
  408. return false;
  409. }
  410. switch(filter.type)
  411. {
  412. case 'time':
  413. return ejs.RangeFilter(filter.field)
  414. .from(filter.from.valueOf())
  415. .to(filter.to.valueOf());
  416. case 'range':
  417. return ejs.RangeFilter(filter.field)
  418. .from(filter.from)
  419. .to(filter.to);
  420. case 'querystring':
  421. return ejs.QueryFilter(ejs.QueryStringQuery(filter.query)).cache(true);
  422. case 'field':
  423. return ejs.QueryFilter(ejs.FieldQuery(filter.field,filter.query)).cache(true);
  424. case 'terms':
  425. return ejs.TermsFilter(filter.field,filter.value);
  426. case 'exists':
  427. return ejs.ExistsFilter(filter.field);
  428. case 'missing':
  429. return ejs.MissingFilter(filter.field);
  430. default:
  431. return false;
  432. }
  433. };
  434. this.getByType = function(type,inactive) {
  435. return _.pick(self.list,self.idsByType(type,inactive));
  436. };
  437. this.removeByType = function(type) {
  438. var ids = self.idsByType(type);
  439. _.each(ids,function(id) {
  440. self.remove(id);
  441. });
  442. return ids;
  443. };
  444. this.idsByType = function(type,inactive) {
  445. var _require = inactive ? {type:type} : {type:type,active:true};
  446. return _.pluck(_.where(self.list,_require),'id');
  447. };
  448. // TOFIX: Error handling when there is more than one field
  449. this.timeField = function() {
  450. return _.pluck(self.getByType('time'),'field');
  451. };
  452. // This special function looks for all time filters, and returns a time range according to the mode
  453. // No idea when max would actually be used
  454. this.timeRange = function(mode) {
  455. var _t = _.where(self.list,{type:'time',active:true});
  456. if(_t.length === 0) {
  457. return false;
  458. }
  459. switch(mode) {
  460. case "min":
  461. return {
  462. from: new Date(_.max(_.pluck(_t,'from'))),
  463. to: new Date(_.min(_.pluck(_t,'to')))
  464. };
  465. case "max":
  466. return {
  467. from: new Date(_.min(_.pluck(_t,'from'))),
  468. to: new Date(_.max(_.pluck(_t,'to')))
  469. };
  470. default:
  471. return false;
  472. }
  473. };
  474. this.remove = function(id) {
  475. if(!_.isUndefined(self.list[id])) {
  476. delete self.list[id];
  477. // This must happen on the full path also since _.without returns a copy
  478. self.ids = dashboard.current.services.filter.ids = _.without(self.ids,id);
  479. _f.idQueue.unshift(id);
  480. _f.idQueue.sort(function(v,k){return v-k;});
  481. return true;
  482. } else {
  483. return false;
  484. }
  485. };
  486. var nextId = function() {
  487. if(_f.idQueue.length > 0) {
  488. return _f.idQueue.shift();
  489. } else {
  490. return self.ids.length;
  491. }
  492. };
  493. // Now init
  494. self.init();
  495. })
  496. .service('dashboard', function($routeParams, $http, $rootScope, $injector, ejsResource, timer, kbnIndex, alertSrv) {
  497. // A hash of defaults to use when loading a dashboard
  498. var _dash = {
  499. title: "",
  500. style: "dark",
  501. editable: true,
  502. failover: false,
  503. rows: [],
  504. services: {},
  505. index: {
  506. interval: 'none',
  507. pattern: '_all',
  508. default: 'INDEX_MISSING'
  509. },
  510. };
  511. // An elasticJS client to use
  512. var ejs = ejsResource(config.elasticsearch);
  513. var gist_pattern = /(^\d{5,}$)|(^[a-z0-9]{10,}$)|(gist.github.com(\/*.*)\/[a-z0-9]{5,}\/*$)/;
  514. // Store a reference to this
  515. var self = this;
  516. var filterSrv,querySrv;
  517. this.current = _.clone(_dash);
  518. this.last = {};
  519. $rootScope.$on('$routeChangeSuccess',function(){
  520. // Clear the current dashboard to prevent reloading
  521. self.current = {};
  522. self.indices = [];
  523. route();
  524. });
  525. var route = function() {
  526. // Is there a dashboard type and id in the URL?
  527. if(!(_.isUndefined($routeParams.type)) && !(_.isUndefined($routeParams.id))) {
  528. var _type = $routeParams.type;
  529. var _id = $routeParams.id;
  530. switch(_type) {
  531. case ('elasticsearch'):
  532. self.elasticsearch_load('dashboard',_id);
  533. break;
  534. case ('temp'):
  535. self.elasticsearch_load('temp',_id);
  536. break;
  537. case ('file'):
  538. self.file_load(_id);
  539. break;
  540. default:
  541. self.file_load('default.json');
  542. }
  543. // No dashboard in the URL
  544. } else {
  545. // Check if browser supports localstorage, and if there's a dashboard
  546. if (window.Modernizr.localstorage &&
  547. !(_.isUndefined(window.localStorage['dashboard'])) &&
  548. window.localStorage['dashboard'] !== ''
  549. ) {
  550. var dashboard = JSON.parse(window.localStorage['dashboard']);
  551. self.dash_load(dashboard);
  552. // No? Ok, grab default.json, its all we have now
  553. } else {
  554. self.file_load('default.json');
  555. }
  556. }
  557. };
  558. // Since the dashboard is responsible for index computation, we can compute and assign the indices
  559. // here before telling the panels to refresh
  560. this.refresh = function() {
  561. if(self.current.index.interval !== 'none') {
  562. if(filterSrv.idsByType('time').length > 0) {
  563. var _range = filterSrv.timeRange('min');
  564. kbnIndex.indices(_range.from,_range.to,
  565. self.current.index.pattern,self.current.index.interval
  566. ).then(function (p) {
  567. if(p.length > 0) {
  568. self.indices = p;
  569. } else {
  570. //TODO: Option to not failover
  571. if(self.current.failover) {
  572. self.indices = [self.current.index.default];
  573. } else {
  574. alertSrv.set('No indices matched','The pattern <i>'+self.current.index.pattern+
  575. '</i> did not match any indices in your selected'+
  576. ' time range.','info',5000);
  577. // Do not issue refresh if no indices match. This should be removed when panels
  578. // properly understand when no indices are present
  579. return false;
  580. }
  581. }
  582. $rootScope.$broadcast('refresh');
  583. });
  584. } else {
  585. // This is not optimal, we should be getting the entire index list here, or at least every
  586. // index that possibly matches the pattern
  587. self.indices = [self.current.index.default];
  588. $rootScope.$broadcast('refresh');
  589. }
  590. } else {
  591. self.indices = [self.current.index.default];
  592. $rootScope.$broadcast('refresh');
  593. }
  594. };
  595. this.dash_load = function(dashboard) {
  596. // Cancel all timers
  597. timer.cancel_all();
  598. // Make sure the dashboard being loaded has everything required
  599. _.defaults(dashboard,_dash);
  600. // If not using time based indices, use the default index
  601. if(dashboard.index.interval === 'none') {
  602. self.indices = [dashboard.index.default];
  603. }
  604. self.current = _.clone(dashboard);
  605. // Ok, now that we've setup the current dashboard, we can inject our services
  606. querySrv = $injector.get('querySrv');
  607. filterSrv = $injector.get('filterSrv');
  608. // Make sure these re-init
  609. querySrv.init();
  610. filterSrv.init();
  611. // If there's an index interval set and no existing time filter, send a refresh to set one
  612. if(dashboard.index.interval !== 'none' && filterSrv.idsByType('time').length === 0) {
  613. self.refresh();
  614. }
  615. return true;
  616. };
  617. this.gist_id = function(string) {
  618. if(self.is_gist(string)) {
  619. return string.match(gist_pattern)[0].replace(/.*\//, '');
  620. }
  621. };
  622. this.is_gist = function(string) {
  623. if(!_.isUndefined(string) && string !== '' && !_.isNull(string.match(gist_pattern))) {
  624. return string.match(gist_pattern).length > 0 ? true : false;
  625. } else {
  626. return false;
  627. }
  628. };
  629. this.to_file = function() {
  630. var blob = new Blob([angular.toJson(self.current,true)], {type: "application/json;charset=utf-8"});
  631. // from filesaver.js
  632. window.saveAs(blob, self.current.title+"-"+new Date().getTime());
  633. return true;
  634. };
  635. this.set_default = function(dashboard) {
  636. if (window.Modernizr.localstorage) {
  637. window.localStorage['dashboard'] = angular.toJson(dashboard || self.current);
  638. return true;
  639. } else {
  640. return false;
  641. }
  642. };
  643. this.purge_default = function() {
  644. if (window.Modernizr.localstorage) {
  645. window.localStorage['dashboard'] = '';
  646. return true;
  647. } else {
  648. return false;
  649. }
  650. };
  651. // TOFIX: Pretty sure this breaks when you're on a saved dashboard already
  652. this.share_link = function(title,type,id) {
  653. return {
  654. location : window.location.href.replace(window.location.hash,""),
  655. type : type,
  656. id : id,
  657. link : window.location.href.replace(window.location.hash,"")+"#dashboard/"+type+"/"+id,
  658. title : title
  659. };
  660. };
  661. this.file_load = function(file) {
  662. return $http({
  663. url: "dashboards/"+file,
  664. method: "GET",
  665. }).then(function(result) {
  666. var _dashboard = result.data;
  667. _.defaults(_dashboard,_dash);
  668. self.dash_load(_dashboard);
  669. return true;
  670. },function(result) {
  671. alertSrv.set('Error',"Could not load <i>dashboards/"+file+"</i>. Please make sure it exists" ,'error');
  672. return false;
  673. });
  674. };
  675. this.elasticsearch_load = function(type,id) {
  676. return $http({
  677. url: config.elasticsearch + "/" + config.kibana_index + "/"+type+"/"+id,
  678. method: "GET"
  679. }).error(function(data, status, headers, conf) {
  680. if(status === 0) {
  681. alertSrv.set('Error',"Could not contact Elasticsearch at "+config.elasticsearch+
  682. ". Please ensure that Elasticsearch is reachable from your system." ,'error');
  683. } else {
  684. alertSrv.set('Error',"Could not find "+id+". If you"+
  685. " are using a proxy, ensure it is configured correctly",'error');
  686. }
  687. return false;
  688. }).success(function(data, status, headers) {
  689. self.dash_load(angular.fromJson(data['_source']['dashboard']));
  690. });
  691. };
  692. this.elasticsearch_save = function(type,title,ttl) {
  693. // Clone object so we can modify it without influencing the existing obejct
  694. var save = _.clone(self.current);
  695. var id;
  696. // Change title on object clone
  697. if (type === 'dashboard') {
  698. id = save.title = _.isUndefined(title) ? self.current.title : title;
  699. }
  700. // Create request with id as title. Rethink this.
  701. var request = ejs.Document(config.kibana_index,type,id).source({
  702. user: 'guest',
  703. group: 'guest',
  704. title: save.title,
  705. dashboard: angular.toJson(save)
  706. });
  707. request = type === 'temp' && ttl ? request.ttl(ttl) : request;
  708. return request.doIndex(
  709. // Success
  710. function(result) {
  711. return result;
  712. },
  713. // Failure
  714. function(result) {
  715. return false;
  716. }
  717. );
  718. };
  719. this.elasticsearch_delete = function(id) {
  720. return ejs.Document(config.kibana_index,'dashboard',id).doDelete(
  721. // Success
  722. function(result) {
  723. return result;
  724. },
  725. // Failure
  726. function(result) {
  727. return false;
  728. }
  729. );
  730. };
  731. this.elasticsearch_list = function(query,count) {
  732. var request = ejs.Request().indices(config.kibana_index).types('dashboard');
  733. return request.query(
  734. ejs.QueryStringQuery(query || '*')
  735. ).size(count).doSearch(
  736. // Success
  737. function(result) {
  738. return result;
  739. },
  740. // Failure
  741. function(result) {
  742. return false;
  743. }
  744. );
  745. };
  746. // TOFIX: Gist functionality
  747. this.save_gist = function(title,dashboard) {
  748. var save = _.clone(dashboard || self.current);
  749. save.title = title || self.current.title;
  750. return $http({
  751. url: "https://api.github.com/gists",
  752. method: "POST",
  753. data: {
  754. "description": save.title,
  755. "public": false,
  756. "files": {
  757. "kibana-dashboard.json": {
  758. "content": angular.toJson(save,true)
  759. }
  760. }
  761. }
  762. }).then(function(data, status, headers, config) {
  763. return data.data.html_url;
  764. }, function(data, status, headers, config) {
  765. return false;
  766. });
  767. };
  768. this.gist_list = function(id) {
  769. return $http.jsonp("https://api.github.com/gists/"+id+"?callback=JSON_CALLBACK"
  770. ).then(function(response) {
  771. var files = [];
  772. _.each(response.data.data.files,function(v,k) {
  773. try {
  774. var file = JSON.parse(v.content);
  775. files.push(file);
  776. } catch(e) {
  777. // Nothing?
  778. }
  779. });
  780. return files;
  781. }, function(data, status, headers, config) {
  782. return false;
  783. });
  784. };
  785. });