services.js 24 KB

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