services.js 27 KB

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