services.js 21 KB

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