services.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  1. /*jshint globalstrict:true */
  2. /*global angular:true */
  3. 'use strict';
  4. angular.module('kibana.services', [])
  5. .service('eventBus', function($rootScope) {
  6. // An array of registed types
  7. var _types = []
  8. this.broadcast = function(from,to,type,data) {
  9. if(_.isUndefined(data))
  10. var data = from
  11. var packet = {
  12. time: new Date(),
  13. type: type,
  14. from: from,
  15. to: to,
  16. data: data
  17. }
  18. if(_.contains(_types,'$kibana_debug'))
  19. $rootScope.$broadcast('$kibana_debug',packet);
  20. //console.log('Sent: '+type + ' to ' + to + ' from ' + from + ': ' + angular.toJson(data))
  21. $rootScope.$broadcast(type,{
  22. from: from,
  23. to: to,
  24. data: data
  25. });
  26. }
  27. // This sets up an $on listener that checks to see if the event (packet) is
  28. // addressed to the scope in question and runs the registered function if it
  29. // is.
  30. this.register = function(scope,type,fn) {
  31. _types = _.union(_types,[type])
  32. scope.$on(type,function(event,packet){
  33. var _id = scope.$id;
  34. var _to = packet.to;
  35. var _from = packet.from;
  36. var _type = packet.type
  37. var _time = packet.time
  38. var _group = (!(_.isUndefined(scope.panel))) ? scope.panel.group : ["NONE"]
  39. //console.log('registered:' + type + " for " + scope.panel.title + " " + scope.$id)
  40. if(!(_.isArray(_to)))
  41. _to = [_to];
  42. if(!(_.isArray(_group)))
  43. _group = [_group];
  44. // Transmit event only if the sender is not the receiver AND one of the following:
  45. // 1) Receiver has group in _to 2) Receiver's $id is in _to
  46. // 3) Event is addressed to ALL 4) Receiver is in ALL group
  47. if((_.intersection(_to,_group).length > 0 ||
  48. _.indexOf(_to,_id) > -1 ||
  49. _.indexOf(_group,'ALL') > -1 ||
  50. _.indexOf(_to,'ALL') > -1) &&
  51. _from !== _id
  52. ) {
  53. //console.log('Got: '+type + ' from ' + _from + ' to ' + _to + ': ' + angular.toJson(packet.data))
  54. fn(event,packet.data,{time:_time,to:_to,from:_from,type:_type});
  55. }
  56. });
  57. }
  58. })
  59. /*
  60. Service: fields
  61. Provides a global list of all seen fields for use in editor panels
  62. */
  63. .factory('fields', function($rootScope) {
  64. var fields = {
  65. list : []
  66. }
  67. $rootScope.$on('fields', function(event,f) {
  68. fields.list = _.union(f.data.all,fields.list)
  69. })
  70. return fields;
  71. })
  72. .service('kbnIndex',function($http) {
  73. // returns a promise containing an array of all indices matching the index
  74. // pattern that exist in a given range
  75. this.indices = function(from,to,pattern,interval) {
  76. var possible = [];
  77. _.each(expand_range(fake_utc(from),fake_utc(to),interval),function(d){
  78. possible.push(d.format(pattern));
  79. });
  80. return all_indices().then(function(p) {
  81. var indices = _.intersection(possible,p);
  82. indices.reverse();
  83. return indices
  84. })
  85. };
  86. // returns a promise containing an array of all indices in an elasticsearch
  87. // cluster
  88. function all_indices() {
  89. var something = $http({
  90. url: config.elasticsearch + "/_aliases",
  91. method: "GET"
  92. }).error(function(data, status, headers, config) {
  93. // Handle error condition somehow?
  94. });
  95. return something.then(function(p) {
  96. var indices = [];
  97. _.each(p.data, function(v,k) {
  98. indices.push(k)
  99. });
  100. return indices;
  101. });
  102. }
  103. // this is stupid, but there is otherwise no good way to ensure that when
  104. // I extract the date from an object that I get the UTC date. Stupid js.
  105. // I die a little inside every time I call this function.
  106. // Update: I just read this again. I died a little more inside.
  107. // Update2: More death.
  108. function fake_utc(date) {
  109. date = moment(date).clone().toDate()
  110. return moment(new Date(date.getTime() + date.getTimezoneOffset() * 60000));
  111. }
  112. // Create an array of date objects by a given interval
  113. function expand_range(start, end, interval) {
  114. if(_.contains(['hour','day','week','month','year'],interval)) {
  115. var range;
  116. start = moment(start).clone();
  117. range = [];
  118. while (start.isBefore(end)) {
  119. range.push(start.clone());
  120. switch (interval) {
  121. case 'hour':
  122. start.add('hours',1)
  123. break
  124. case 'day':
  125. start.add('days',1)
  126. break
  127. case 'week':
  128. start.add('weeks',1)
  129. break
  130. case 'month':
  131. start.add('months',1)
  132. break
  133. case 'year':
  134. start.add('years',1)
  135. break
  136. }
  137. }
  138. range.push(moment(end).clone());
  139. return range;
  140. } else {
  141. return false;
  142. }
  143. }
  144. })
  145. .service('timer', function($timeout) {
  146. // This service really just tracks a list of $timeout promises to give us a
  147. // method for cancelling them all when we need to
  148. var timers = [];
  149. this.register = function(promise) {
  150. timers.push(promise);
  151. return promise;
  152. }
  153. this.cancel = function(promise) {
  154. timers = _.without(timers,promise)
  155. $timeout.cancel(promise)
  156. }
  157. this.cancel_all = function() {
  158. _.each(timers, function(t){
  159. $timeout.cancel(t);
  160. });
  161. timers = new Array();
  162. }
  163. })
  164. .service('query', function() {
  165. })
  166. .service('dashboard', function($routeParams, $http, $rootScope, ejsResource, timer) {
  167. // A hash of defaults to use when loading a dashboard
  168. var _dash = {
  169. title: "",
  170. editable: true,
  171. rows: [],
  172. services: {}
  173. };
  174. // An elasticJS client to use
  175. var ejs = ejsResource(config.elasticsearch);
  176. var gist_pattern = /(^\d{5,}$)|(^[a-z0-9]{10,}$)|(gist.github.com(\/*.*)\/[a-z0-9]{5,}\/*$)/;
  177. // Empty dashboard object
  178. this.current = {};
  179. this.last = {};
  180. // Store a reference to this
  181. var self = this;
  182. $rootScope.$on('$routeChangeSuccess',function(){
  183. route();
  184. })
  185. var route = function() {
  186. // Is there a dashboard type and id in the URL?
  187. if(!(_.isUndefined($routeParams.type)) && !(_.isUndefined($routeParams.id))) {
  188. var _type = $routeParams.type;
  189. var _id = $routeParams.id;
  190. if(_type === 'elasticsearch')
  191. self.elasticsearch_load('dashboard',_id)
  192. if(_type === 'temp')
  193. self.elasticsearch_load('temp',_id)
  194. if(_type === 'file')
  195. self.file_load(_id)
  196. // No dashboard in the URL
  197. } else {
  198. // Check if browser supports localstorage, and if there's a dashboard
  199. if (Modernizr.localstorage &&
  200. !(_.isUndefined(localStorage['dashboard'])) &&
  201. localStorage['dashboard'] !== ''
  202. ) {
  203. var dashboard = JSON.parse(localStorage['dashboard']);
  204. _.defaults(dashboard,_dash);
  205. self.dash_load(dashboard)
  206. // No? Ok, grab default.json, its all we have now
  207. } else {
  208. self.file_load('default')
  209. }
  210. }
  211. }
  212. this.to_file = function() {
  213. var blob = new Blob([angular.toJson(self.current,true)], {type: "application/json;charset=utf-8"});
  214. // from filesaver.js
  215. saveAs(blob, self.current.title+"-"+new Date().getTime());
  216. return true;
  217. }
  218. this.set_default = function(dashboard) {
  219. if (Modernizr.localstorage) {
  220. localStorage['dashboard'] = angular.toJson(dashboard || self.current);
  221. return true;
  222. } else {
  223. return false;
  224. }
  225. }
  226. this.purge_default = function() {
  227. if (Modernizr.localstorage) {
  228. localStorage['dashboard'] = '';
  229. return true;
  230. } else {
  231. return false;
  232. }
  233. }
  234. // TOFIX: Pretty sure this breaks when you're on a saved dashboard already
  235. this.share_link = function(title,type,id) {
  236. return {
  237. location : location.href.replace(location.hash,""),
  238. type : type,
  239. id : id,
  240. link : location.href.replace(location.hash,"")+"#dashboard/"+type+"/"+id,
  241. title : title
  242. };
  243. }
  244. this.file_load = function(file) {
  245. return $http({
  246. url: "dashboards/"+file,
  247. method: "GET",
  248. }).then(function(result) {
  249. var _dashboard = result.data
  250. _.defaults(_dashboard,_dash);
  251. self.dash_load(_dashboard);
  252. return true;
  253. },function(result) {
  254. return false;
  255. });
  256. }
  257. this.elasticsearch_load = function(type,id) {
  258. var request = ejs.Request().indices(config.kibana_index).types(type);
  259. var results = request.query(
  260. ejs.IdsQuery(id)
  261. ).doSearch();
  262. return results.then(function(results) {
  263. if(_.isUndefined(results)) {
  264. return false;
  265. } else {
  266. self.dash_load(angular.fromJson(results.hits.hits[0]['_source']['dashboard']))
  267. return true;
  268. }
  269. });
  270. }
  271. this.elasticsearch_save = function(type,title,ttl) {
  272. // Clone object so we can modify it without influencing the existing obejct
  273. var save = _.clone(self.current)
  274. // Change title on object clone
  275. if (type === 'dashboard') {
  276. var id = save.title = _.isUndefined(title) ? self.current.title : title;
  277. }
  278. // Create request with id as title. Rethink this.
  279. var request = ejs.Document(config.kibana_index,type,id).source({
  280. user: 'guest',
  281. group: 'guest',
  282. title: save.title,
  283. dashboard: angular.toJson(save)
  284. })
  285. if (type === 'temp')
  286. request = request.ttl(ttl)
  287. // TOFIX: Implement error handling here
  288. return request.doIndex(
  289. // Success
  290. function(result) {
  291. return result;
  292. },
  293. // Failure
  294. function(result) {
  295. return false;
  296. }
  297. );
  298. }
  299. this.elasticsearch_delete = function(id) {
  300. return ejs.Document(config.kibana_index,'dashboard',id).doDelete(
  301. // Success
  302. function(result) {
  303. return result;
  304. },
  305. // Failure
  306. function(result) {
  307. return false;
  308. }
  309. );
  310. }
  311. this.elasticsearch_list = function(query,count) {
  312. var request = ejs.Request().indices(config.kibana_index).types('dashboard');
  313. return request.query(
  314. ejs.QueryStringQuery(query || '*')
  315. ).size(count).doSearch(
  316. // Success
  317. function(result) {
  318. return result;
  319. },
  320. // Failure
  321. function(result) {
  322. return false;
  323. }
  324. );
  325. }
  326. // TOFIX: Gist functionality
  327. this.save_gist = function(title,dashboard) {
  328. var save = _.clone(dashboard || self.current)
  329. save.title = title || self.current.title;
  330. return $http({
  331. url: "https://api.github.com/gists",
  332. method: "POST",
  333. data: {
  334. "description": save.title,
  335. "public": false,
  336. "files": {
  337. "kibana-dashboard.json": {
  338. "content": angular.toJson(save,true)
  339. }
  340. }
  341. }
  342. }).then(function(data, status, headers, config) {
  343. return data.data.html_url;
  344. }, function(data, status, headers, config) {
  345. return false;
  346. });
  347. }
  348. this.gist_list = function(id) {
  349. return $http.jsonp("https://api.github.com/gists/"+id+"?callback=JSON_CALLBACK"
  350. ).then(function(response) {
  351. var files = []
  352. _.each(response.data.data.files,function(v,k) {
  353. try {
  354. var file = JSON.parse(v.content)
  355. files.push(file)
  356. } catch(e) {
  357. // Nothing?
  358. }
  359. });
  360. return files;
  361. }, function(data, status, headers, config) {
  362. return false;
  363. });
  364. }
  365. this.dash_load = function(dashboard) {
  366. self.current = dashboard;
  367. timer.cancel_all();
  368. return true;
  369. }
  370. this.gist_id = function(string) {
  371. if(self.is_gist(string))
  372. return string.match(gist_pattern)[0].replace(/.*\//, '');
  373. }
  374. this.is_gist = function(string) {
  375. if(!_.isUndefined(string) && string != '' && !_.isNull(string.match(gist_pattern)))
  376. return string.match(gist_pattern).length > 0 ? true : false;
  377. else
  378. return false
  379. }
  380. })
  381. .service('keylistener', function($rootScope) {
  382. var keys = [];
  383. $(document).keydown(function (e) {
  384. keys[e.which] = true;
  385. });
  386. $(document).keyup(function (e) {
  387. delete keys[e.which];
  388. });
  389. this.keyActive = function(key) {
  390. return keys[key] == true;
  391. }
  392. });