| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742 |
- /*jshint globalstrict:true */
- /*global angular:true */
- /*global Blob:false*/
- 'use strict';
- angular.module('kibana.services', [])
- .service('fields', function() {
- // Save a reference to this
- var self = this;
- this.list = ['_type'];
- this.add_fields = function(f) {
- self.list = _.union(f,self.list);
- };
- })
- .service('kbnIndex',function($http) {
- // returns a promise containing an array of all indices matching the index
- // pattern that exist in a given range
- this.indices = function(from,to,pattern,interval) {
- var possible = [];
- _.each(expand_range(fake_utc(from),fake_utc(to),interval),function(d){
- possible.push(d.format(pattern));
- });
- return all_indices().then(function(p) {
- var indices = _.intersection(possible,p);
- indices.reverse();
- return indices;
- });
- };
- // returns a promise containing an array of all indices in an elasticsearch
- // cluster
- function all_indices() {
- var something = $http({
- url: config.elasticsearch + "/_aliases",
- method: "GET"
- }).error(function(data, status, headers, config) {
- // Handle error condition somehow?
- });
- return something.then(function(p) {
- var indices = [];
- _.each(p.data, function(v,k) {
- indices.push(k);
- // Also add the aliases. Could be expensive on systems with a lot of them
- _.each(v.aliases, function(v, k) {
- indices.push(k);
- });
- });
- return indices;
- });
- }
- // this is stupid, but there is otherwise no good way to ensure that when
- // I extract the date from an object that I get the UTC date. Stupid js.
- // I die a little inside every time I call this function.
- // Update: I just read this again. I died a little more inside.
- // Update2: More death.
- function fake_utc(date) {
- date = moment(date).clone().toDate();
- return moment(new Date(date.getTime() + date.getTimezoneOffset() * 60000));
- }
- // Create an array of date objects by a given interval
- function expand_range(start, end, interval) {
- if(_.contains(['hour','day','week','month','year'],interval)) {
- var range;
- start = moment(start).clone();
- range = [];
- while (start.isBefore(end)) {
- range.push(start.clone());
- switch (interval) {
- case 'hour':
- start.add('hours',1);
- break;
- case 'day':
- start.add('days',1);
- break;
- case 'week':
- start.add('weeks',1);
- break;
- case 'month':
- start.add('months',1);
- break;
- case 'year':
- start.add('years',1);
- break;
- }
- }
- range.push(moment(end).clone());
- return range;
- } else {
- return false;
- }
- }
- })
- .service('timer', function($timeout) {
- // This service really just tracks a list of $timeout promises to give us a
- // method for cancelling them all when we need to
- var timers = [];
- this.register = function(promise) {
- timers.push(promise);
- return promise;
- };
- this.cancel = function(promise) {
- timers = _.without(timers,promise);
- $timeout.cancel(promise);
- };
- this.cancel_all = function() {
- _.each(timers, function(t){
- $timeout.cancel(t);
- });
- timers = [];
- };
- })
- .service('querySrv', function(dashboard, ejsResource) {
- // Create an object to hold our service state on the dashboard
- dashboard.current.services.query = dashboard.current.services.query || {};
- _.defaults(dashboard.current.services.query,{
- idQueue : [],
- list : {},
- ids : [],
- });
- // For convenience
- var ejs = ejsResource(config.elasticsearch);
- var _q = dashboard.current.services.query;
- this.colors = [
- "#7EB26D","#EAB839","#6ED0E0","#EF843C","#E24D42","#1F78C1","#BA43A9","#705DA0", //1
- "#508642","#CCA300","#447EBC","#C15C17","#890F02","#0A437C","#6D1F62","#584477", //2
- "#B7DBAB","#F4D598","#70DBED","#F9BA8F","#F29191","#82B5D8","#E5A8E2","#AEA2E0", //3
- "#629E51","#E5AC0E","#64B0C8","#E0752D","#BF1B00","#0A50A1","#962D82","#614D93", //4
- "#9AC48A","#F2C96D","#65C5DB","#F9934E","#EA6460","#5195CE","#D683CE","#806EB7", //5
- "#3F6833","#967302","#2F575E","#99440A","#58140C","#052B51","#511749","#3F2B5B", //6
- "#E0F9D7","#FCEACA","#CFFAFF","#F9E2D2","#FCE2DE","#BADFF4","#F9D9F9","#DEDAF7" //7
- ];
- // Save a reference to this
- var self = this;
- this.init = function() {
- _q = dashboard.current.services.query;
- self.list = dashboard.current.services.query.list;
- self.ids = dashboard.current.services.query.ids;
-
- if (self.ids.length === 0) {
- self.set({});
- }
- };
- // This is used both for adding queries and modifying them. If an id is passed, the query at that id is updated
- this.set = function(query,id) {
- if(!_.isUndefined(id)) {
- if(!_.isUndefined(self.list[id])) {
- _.extend(self.list[id],query);
- return id;
- } else {
- return false;
- }
- } else {
- var _id = nextId();
- var _query = {
- query: '*',
- alias: '',
- color: colorAt(_id),
- pin: false,
- id: _id,
- type: 'lucene'
- };
- _.defaults(query,_query);
- self.list[_id] = query;
- self.ids.push(_id);
- return _id;
- }
- };
- this.remove = function(id) {
- if(!_.isUndefined(self.list[id])) {
- delete self.list[id];
- // This must happen on the full path also since _.without returns a copy
- self.ids = dashboard.current.services.query.ids = _.without(self.ids,id);
- _q.idQueue.unshift(id);
- _q.idQueue.sort(function(v,k){
- return v-k;
- });
- return true;
- } else {
- return false;
- }
- };
- this.getEjsObj = function(id) {
- return self.toEjsObj(self.list[id]);
- };
- this.toEjsObj = function (q) {
- switch(q.type)
- {
- case 'lucene':
- return ejs.QueryStringQuery(q.query || '*');
- default:
- return _.isUndefined(q.query) ? false : ejs.QueryStringQuery(q.query || '*');
- }
- };
- this.findQuery = function(queryString) {
- return _.findWhere(self.list,{query:queryString});
- };
- this.idsByMode = function(config) {
- switch(config.mode)
- {
- case 'all':
- return self.ids;
- case 'pinned':
- return _.pluck(_.where(self.list,{pin:true}),'id');
- case 'unpinned':
- return _.difference(self.ids,_.pluck(_.where(self.list,{pin:true}),'id'));
- case 'selected':
- return _.intersection(self.ids,config.ids);
- default:
- return self.ids;
- }
- };
- var nextId = function() {
- if(_q.idQueue.length > 0) {
- return _q.idQueue.shift();
- } else {
- return self.ids.length;
- }
- };
- var colorAt = function(id) {
- return self.colors[id % self.colors.length];
- };
- self.init();
- })
- .service('filterSrv', function(dashboard, ejsResource) {
- // Create an object to hold our service state on the dashboard
- dashboard.current.services.filter = dashboard.current.services.filter || {};
- _.defaults(dashboard.current.services.filter,{
- idQueue : [],
- list : {},
- ids : []
- });
- // For convenience
- var ejs = ejsResource(config.elasticsearch);
- var _f = dashboard.current.services.filter;
- // Save a reference to this
- var self = this;
- // Call this whenever we need to reload the important stuff
- this.init = function() {
- // Accessors
- self.list = dashboard.current.services.filter.list;
- self.ids = dashboard.current.services.filter.ids;
- _f = dashboard.current.services.filter;
- _.each(self.getByType('time',true),function(time) {
- self.list[time.id].from = new Date(time.from);
- self.list[time.id].to = new Date(time.to);
- });
- };
- // This is used both for adding filters and modifying them.
- // If an id is passed, the filter at that id is updated
- this.set = function(filter,id) {
- _.defaults(filter,{mandate:'must'});
- filter.active = true;
- if(!_.isUndefined(id)) {
- if(!_.isUndefined(self.list[id])) {
- _.extend(self.list[id],filter);
- return id;
- } else {
- return false;
- }
- } else {
- if(_.isUndefined(filter.type)) {
- return false;
- } else {
- var _id = nextId();
- var _filter = {
- alias: '',
- id: _id
- };
- _.defaults(filter,_filter);
- self.list[_id] = filter;
- self.ids.push(_id);
- return _id;
- }
- }
- };
- this.getBoolFilter = function(ids) {
- // A default match all filter, just in case there are no other filters
- var bool = ejs.BoolFilter().must(ejs.MatchAllFilter());
- var either_bool = ejs.BoolFilter().must(ejs.MatchAllFilter());
- _.each(ids,function(id) {
- if(self.list[id].active) {
- switch(self.list[id].mandate)
- {
- case 'mustNot':
- bool = bool.mustNot(self.getEjsObj(id));
- break;
- case 'either':
- either_bool = either_bool.should(self.getEjsObj(id));
- break;
- default:
- bool = bool.must(self.getEjsObj(id));
- }
- }
- });
- return bool.must(either_bool);
- };
- this.getEjsObj = function(id) {
- return self.toEjsObj(self.list[id]);
- };
- this.toEjsObj = function (filter) {
- if(!filter.active) {
- return false;
- }
- switch(filter.type)
- {
- case 'time':
- return ejs.RangeFilter(filter.field)
- .from(filter.from.valueOf())
- .to(filter.to.valueOf());
- case 'range':
- return ejs.RangeFilter(filter.field)
- .from(filter.from)
- .to(filter.to);
- case 'querystring':
- return ejs.QueryFilter(ejs.QueryStringQuery(filter.query)).cache(true);
- case 'field':
- return ejs.QueryFilter(ejs.FieldQuery(filter.field,filter.query)).cache(true);
- case 'terms':
- return ejs.TermsFilter(filter.field,filter.value);
- case 'exists':
- return ejs.ExistsFilter(filter.field);
- case 'missing':
- return ejs.MissingFilter(filter.field);
- default:
- return false;
- }
- };
- this.getByType = function(type,inactive) {
- return _.pick(self.list,self.idsByType(type,inactive));
- };
- this.removeByType = function(type) {
- var ids = self.idsByType(type);
- _.each(ids,function(id) {
- self.remove(id);
- });
- return ids;
- };
- this.idsByType = function(type,inactive) {
- var _require = inactive ? {type:type} : {type:type,active:true};
- return _.pluck(_.where(self.list,_require),'id');
- };
- // This special function looks for all time filters, and returns a time range according to the mode
- this.timeRange = function(mode) {
- var _t = _.where(self.list,{type:'time',active:true});
- if(_t.length === 0) {
- return false;
- }
- switch(mode) {
- case "min":
- return {
- from: new Date(_.max(_.pluck(_t,'from'))),
- to: new Date(_.min(_.pluck(_t,'to')))
- };
- case "max":
- return {
- from: new Date(_.min(_.pluck(_t,'from'))),
- to: new Date(_.max(_.pluck(_t,'to')))
- };
- default:
- return false;
- }
- };
- this.remove = function(id) {
- if(!_.isUndefined(self.list[id])) {
- delete self.list[id];
- // This must happen on the full path also since _.without returns a copy
- self.ids = dashboard.current.services.filter.ids = _.without(self.ids,id);
- _f.idQueue.unshift(id);
- _f.idQueue.sort(function(v,k){return v-k;});
- return true;
- } else {
- return false;
- }
- };
- var nextId = function() {
- if(_f.idQueue.length > 0) {
- return _f.idQueue.shift();
- } else {
- return self.ids.length;
- }
- };
- // Now init
- self.init();
- })
- .service('dashboard', function($routeParams, $http, $rootScope, $injector, ejsResource, timer, kbnIndex) {
- // A hash of defaults to use when loading a dashboard
- var _dash = {
- title: "",
- editable: true,
- rows: [],
- services: {},
- index: {
- interval: 'none',
- pattern: '_all',
- default: 'INDEX_MISSING'
- },
- };
- // An elasticJS client to use
- var ejs = ejsResource(config.elasticsearch);
- var gist_pattern = /(^\d{5,}$)|(^[a-z0-9]{10,}$)|(gist.github.com(\/*.*)\/[a-z0-9]{5,}\/*$)/;
- // Store a reference to this
- var self = this;
- var filterSrv,querySrv;
- this.current = {};
- this.last = {};
- $rootScope.$on('$routeChangeSuccess',function(){
- // Clear the current dashboard to prevent reloading
- self.current = {};
- self.indices = [];
- route();
- });
- var route = function() {
- // Is there a dashboard type and id in the URL?
- if(!(_.isUndefined($routeParams.type)) && !(_.isUndefined($routeParams.id))) {
- var _type = $routeParams.type;
- var _id = $routeParams.id;
- switch(_type) {
- case ('elasticsearch'):
- self.elasticsearch_load('dashboard',_id);
- break;
- case ('temp'):
- self.elasticsearch_load('temp',_id);
- break;
- case ('file'):
- self.file_load(_id);
- break;
- default:
- self.file_load('default.json');
- }
- // No dashboard in the URL
- } else {
- // Check if browser supports localstorage, and if there's a dashboard
- if (window.Modernizr.localstorage &&
- !(_.isUndefined(window.localStorage['dashboard'])) &&
- window.localStorage['dashboard'] !== ''
- ) {
- var dashboard = JSON.parse(window.localStorage['dashboard']);
- self.dash_load(dashboard);
- // No? Ok, grab default.json, its all we have now
- } else {
- self.file_load('default.json');
- }
- }
- };
- // Since the dashboard is responsible for index computation, we can compute and assign the indices
- // here before telling the panels to refresh
- this.refresh = function() {
- if(self.current.index.interval !== 'none') {
- if(filterSrv.idsByType('time').length > 0) {
- var _range = filterSrv.timeRange('min');
- kbnIndex.indices(_range.from,_range.to,
- self.current.index.pattern,self.current.index.interval
- ).then(function (p) {
- if(p.length > 0) {
- self.indices = p;
- } else {
- self.indices = [self.current.index.default];
- }
- $rootScope.$broadcast('refresh');
- });
- } else {
- // This is not optimal, we should be getting the entire index list here, or at least every
- // index that possibly matches the pattern
- self.indices = [self.current.index.default];
- $rootScope.$broadcast('refresh');
- }
- } else {
- self.indices = [self.current.index.default];
- $rootScope.$broadcast('refresh');
- }
- };
- this.dash_load = function(dashboard) {
- // Cancel all timers
- timer.cancel_all();
- // Make sure the dashboard being loaded has everything required
- _.defaults(dashboard,_dash);
- // If not using time based indices, use the default index
- if(dashboard.index.interval === 'none') {
- self.indices = [dashboard.index.default];
- }
- self.current = _.clone(dashboard);
- // Ok, now that we've setup the current dashboard, we can inject our services
- querySrv = $injector.get('querySrv');
- filterSrv = $injector.get('filterSrv');
- // Make sure these re-init
- querySrv.init();
- filterSrv.init();
- // If there's an index interval set and no existing time filter, send a refresh to set one
- if(dashboard.index.interval !== 'none' && filterSrv.idsByType('time').length === 0) {
- self.refresh();
- }
- return true;
- };
- this.gist_id = function(string) {
- if(self.is_gist(string)) {
- return string.match(gist_pattern)[0].replace(/.*\//, '');
- }
- };
- this.is_gist = function(string) {
- if(!_.isUndefined(string) && string !== '' && !_.isNull(string.match(gist_pattern))) {
- return string.match(gist_pattern).length > 0 ? true : false;
- } else {
- return false;
- }
- };
- this.to_file = function() {
- var blob = new Blob([angular.toJson(self.current,true)], {type: "application/json;charset=utf-8"});
- // from filesaver.js
- window.saveAs(blob, self.current.title+"-"+new Date().getTime());
- return true;
- };
- this.set_default = function(dashboard) {
- if (window.Modernizr.localstorage) {
- window.localStorage['dashboard'] = angular.toJson(dashboard || self.current);
- return true;
- } else {
- return false;
- }
- };
- this.purge_default = function() {
- if (window.Modernizr.localstorage) {
- window.localStorage['dashboard'] = '';
- return true;
- } else {
- return false;
- }
- };
- // TOFIX: Pretty sure this breaks when you're on a saved dashboard already
- this.share_link = function(title,type,id) {
- return {
- location : window.location.href.replace(window.location.hash,""),
- type : type,
- id : id,
- link : window.location.href.replace(window.location.hash,"")+"#dashboard/"+type+"/"+id,
- title : title
- };
- };
- this.file_load = function(file) {
- return $http({
- url: "dashboards/"+file,
- method: "GET",
- }).then(function(result) {
- var _dashboard = result.data;
- _.defaults(_dashboard,_dash);
- self.dash_load(_dashboard);
- return true;
- },function(result) {
- return false;
- });
- };
- this.elasticsearch_load = function(type,id) {
- var request = ejs.Request().indices(config.kibana_index).types(type);
- var results = request.query(
- ejs.IdsQuery(id)
- ).doSearch();
- return results.then(function(results) {
- if(_.isUndefined(results)) {
- return false;
- } else {
- self.dash_load(angular.fromJson(results.hits.hits[0]['_source']['dashboard']));
- return true;
- }
- });
- };
- this.elasticsearch_save = function(type,title,ttl) {
- // Clone object so we can modify it without influencing the existing obejct
- var save = _.clone(self.current);
- var id;
-
- // Change title on object clone
- if (type === 'dashboard') {
- id = save.title = _.isUndefined(title) ? self.current.title : title;
- }
- // Create request with id as title. Rethink this.
- var request = ejs.Document(config.kibana_index,type,id).source({
- user: 'guest',
- group: 'guest',
- title: save.title,
- dashboard: angular.toJson(save)
- });
-
- request = type === 'temp' && ttl ? request.ttl(ttl) : request;
- // TOFIX: Implement error handling here
- return request.doIndex(
- // Success
- function(result) {
- return result;
- },
- // Failure
- function(result) {
- return false;
- }
- );
- };
- this.elasticsearch_delete = function(id) {
- return ejs.Document(config.kibana_index,'dashboard',id).doDelete(
- // Success
- function(result) {
- return result;
- },
- // Failure
- function(result) {
- return false;
- }
- );
- };
- this.elasticsearch_list = function(query,count) {
- var request = ejs.Request().indices(config.kibana_index).types('dashboard');
- return request.query(
- ejs.QueryStringQuery(query || '*')
- ).size(count).doSearch(
- // Success
- function(result) {
- return result;
- },
- // Failure
- function(result) {
- return false;
- }
- );
- };
- // TOFIX: Gist functionality
- this.save_gist = function(title,dashboard) {
- var save = _.clone(dashboard || self.current);
- save.title = title || self.current.title;
- return $http({
- url: "https://api.github.com/gists",
- method: "POST",
- data: {
- "description": save.title,
- "public": false,
- "files": {
- "kibana-dashboard.json": {
- "content": angular.toJson(save,true)
- }
- }
- }
- }).then(function(data, status, headers, config) {
- return data.data.html_url;
- }, function(data, status, headers, config) {
- return false;
- });
- };
- this.gist_list = function(id) {
- return $http.jsonp("https://api.github.com/gists/"+id+"?callback=JSON_CALLBACK"
- ).then(function(response) {
- var files = [];
- _.each(response.data.data.files,function(v,k) {
- try {
- var file = JSON.parse(v.content);
- files.push(file);
- } catch(e) {
- // Nothing?
- }
- });
- return files;
- }, function(data, status, headers, config) {
- return false;
- });
- };
- });
|