mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Elasticsearch will throw a 404 if there is no indexes added yet, resulting in this unclear message -- even for people that are not using any sort of proxy. This message update should make it easier to quickly troubleshoot why the the dialogue is popping up.
968 lines
26 KiB
JavaScript
968 lines
26 KiB
JavaScript
/*jshint globalstrict:true, forin:false */
|
|
/*global angular:true */
|
|
/*global Blob:false*/
|
|
'use strict';
|
|
|
|
angular.module('kibana.services', [])
|
|
.service('alertSrv', function($timeout) {
|
|
var self = this;
|
|
|
|
// List of all alert objects
|
|
this.list = [];
|
|
|
|
this.set = function(title,text,severity,timeout) {
|
|
var
|
|
_a = {
|
|
title: title || '',
|
|
text: text || '',
|
|
severity: severity || 'info',
|
|
},
|
|
_ca = angular.toJson(_a),
|
|
_clist = _.map(self.list,function(alert){return angular.toJson(alert);});
|
|
|
|
// If we already have this alert, remove it and add a new one
|
|
// Why do this instead of skipping the add because it resets the timer
|
|
if(_.contains(_clist,_ca)) {
|
|
_.remove(self.list,_.indexOf(_clist,_ca));
|
|
}
|
|
|
|
self.list.push(_a);
|
|
if (timeout > 0) {
|
|
$timeout(function() {
|
|
self.list = _.without(self.list,_a);
|
|
}, timeout);
|
|
}
|
|
};
|
|
|
|
this.clear = function(alert) {
|
|
self.list = _.without(self.list,alert);
|
|
};
|
|
|
|
this.clearAll = function() {
|
|
self.list = [];
|
|
};
|
|
|
|
})
|
|
.service('fields', function(dashboard, $rootScope, $http, alertSrv) {
|
|
// Save a reference to this
|
|
var self = this;
|
|
|
|
this.list = ['_type'];
|
|
this.mapping = {};
|
|
|
|
this.add_fields = function(f) {
|
|
//self.list = _.union(f,self.list);
|
|
};
|
|
|
|
$rootScope.$watch(function(){return dashboard.indices;},function(n) {
|
|
if(!_.isUndefined(n) && n.length) {
|
|
// Only get the mapping for indices we don't know it for
|
|
var indices = _.difference(n,_.keys(self.mapping));
|
|
// Only get the mapping if there are indices
|
|
if(indices.length > 0) {
|
|
self.map(indices).then(function(result) {
|
|
self.mapping = _.extend(self.mapping,result);
|
|
self.list = mapFields(self.mapping);
|
|
});
|
|
// Otherwise just use the cached mapping
|
|
} else {
|
|
self.list = mapFields(_.pick(self.mapping,n));
|
|
}
|
|
}
|
|
});
|
|
|
|
var mapFields = function (m) {
|
|
var fields = [];
|
|
_.each(m, function(types,index) {
|
|
_.each(types, function(v,k) {
|
|
fields = _.union(fields,_.keys(v));
|
|
});
|
|
});
|
|
return fields;
|
|
};
|
|
|
|
this.map = function(indices) {
|
|
var request = $http({
|
|
url: config.elasticsearch + "/" + indices.join(',') + "/_mapping",
|
|
method: "GET"
|
|
}).error(function(data, status, headers, conf) {
|
|
if(status === 0) {
|
|
alertSrv.set('Error',"Could not contact Elasticsearch at "+config.elasticsearch+
|
|
". Please ensure that Elasticsearch is reachable from your system." ,'error');
|
|
} else {
|
|
alertSrv.set('Error',"No index found at "+config.elasticsearch+"/" +
|
|
indices.join(',')+"/_mapping. Please create at least one index." +
|
|
"If you're using a proxy ensure it is configured correctly.",'error');
|
|
}
|
|
});
|
|
|
|
return request.then(function(p) {
|
|
var mapping = {};
|
|
_.each(p.data, function(v,k) {
|
|
mapping[k] = {};
|
|
_.each(v, function (v,f) {
|
|
mapping[k][f] = flatten(v);
|
|
});
|
|
});
|
|
return mapping;
|
|
});
|
|
};
|
|
|
|
var flatten = function(obj,prefix) {
|
|
var propName = (prefix) ? prefix : '',
|
|
dot = (prefix) ? '.':'',
|
|
ret = {};
|
|
for(var attr in obj){
|
|
// For now only support multi field on the top level
|
|
// and if if there is a default field set.
|
|
if(obj[attr]['type'] === 'multi_field') {
|
|
ret[attr] = obj[attr]['fields'][attr] || obj[attr];
|
|
continue;
|
|
}
|
|
if (attr === 'properties') {
|
|
_.extend(ret,flatten(obj[attr], propName));
|
|
} else if(typeof obj[attr] === 'object'){
|
|
_.extend(ret,flatten(obj[attr], propName + dot + attr));
|
|
} else {
|
|
ret[propName] = obj;
|
|
}
|
|
}
|
|
return ret;
|
|
};
|
|
|
|
})
|
|
.service('kbnIndex',function($http,alertSrv) {
|
|
|
|
// 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, conf) {
|
|
if(status === 0) {
|
|
alertSrv.set('Error',"Could not contact Elasticsearch at "+config.elasticsearch+
|
|
". Please ensure that Elasticsearch is reachable from your system." ,'error');
|
|
} else {
|
|
alertSrv.set('Error',"Could not reach "+config.elasticsearch+"/_aliases. If you"+
|
|
" are using a proxy, ensure it is configured correctly",'error');
|
|
}
|
|
});
|
|
|
|
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 : [],
|
|
});
|
|
|
|
// Defaults for query objects
|
|
var _query = {
|
|
query: '*',
|
|
alias: '',
|
|
pin: false,
|
|
type: 'lucene'
|
|
};
|
|
// 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;
|
|
|
|
// Check each query object, populate its defaults
|
|
_.each(self.list,function(query,id) {
|
|
_.defaults(query,_query);
|
|
query.color = colorAt(id);
|
|
});
|
|
|
|
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 = query.id || nextId();
|
|
query.id = _id;
|
|
query.color = query.color || colorAt(_id);
|
|
_.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 for it
|
|
var _d = {
|
|
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() {
|
|
// Populate defaults
|
|
_.defaults(dashboard.current.services.filter,_d);
|
|
|
|
// 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');
|
|
};
|
|
|
|
// TOFIX: Error handling when there is more than one field
|
|
this.timeField = function() {
|
|
return _.pluck(self.getByType('time'),'field');
|
|
};
|
|
|
|
// This special function looks for all time filters, and returns a time range according to the mode
|
|
// No idea when max would actually be used
|
|
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, alertSrv) {
|
|
// A hash of defaults to use when loading a dashboard
|
|
|
|
var _dash = {
|
|
title: "",
|
|
style: "dark",
|
|
editable: true,
|
|
failover: false,
|
|
rows: [],
|
|
services: {},
|
|
loader: {
|
|
save_gist: false,
|
|
save_elasticsearch: true,
|
|
save_local: true,
|
|
save_default: true,
|
|
save_temp: true,
|
|
save_temp_ttl_enable: true,
|
|
save_temp_ttl: '30d',
|
|
load_gist: true,
|
|
load_elasticsearch: true,
|
|
load_elasticsearch_size: 20,
|
|
load_local: true,
|
|
hide: false
|
|
},
|
|
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 = _.clone(_dash);
|
|
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.kbnType)) && !(_.isUndefined($routeParams.kbnId))) {
|
|
var _type = $routeParams.kbnType;
|
|
var _id = $routeParams.kbnId;
|
|
|
|
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;
|
|
case('script'):
|
|
self.script_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 {
|
|
//TODO: Option to not failover
|
|
if(self.current.failover) {
|
|
self.indices = [self.current.index.default];
|
|
} else {
|
|
|
|
// Do not issue refresh if no indices match. This should be removed when panels
|
|
// properly understand when no indices are present
|
|
return false;
|
|
}
|
|
}
|
|
$rootScope.$broadcast('refresh');
|
|
});
|
|
} else {
|
|
if(self.current.failover) {
|
|
self.indices = [self.current.index.default];
|
|
$rootScope.$broadcast('refresh');
|
|
} else {
|
|
alertSrv.set("No time filter",
|
|
'Timestamped indices are configured without a failover. Waiting for time filter.',
|
|
'info',5000);
|
|
}
|
|
}
|
|
} else {
|
|
self.indices = [self.current.index.default];
|
|
$rootScope.$broadcast('refresh');
|
|
}
|
|
};
|
|
|
|
var dash_defaults = function(dashboard) {
|
|
_.defaults(dashboard,_dash);
|
|
_.defaults(dashboard.index,_dash.index);
|
|
_.defaults(dashboard.loader,_dash.loader);
|
|
return dashboard;
|
|
};
|
|
|
|
this.dash_load = function(dashboard) {
|
|
|
|
// Cancel all timers
|
|
timer.cancel_all();
|
|
|
|
// Make sure the dashboard being loaded has everything required
|
|
dashboard = dash_defaults(dashboard);
|
|
|
|
// 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
|
|
};
|
|
};
|
|
|
|
var renderTemplate = function(json,params) {
|
|
var _r;
|
|
_.templateSettings = {interpolate : /\{\{(.+?)\}\}/g};
|
|
var template = _.template(json);
|
|
var rendered = template({ARGS:params});
|
|
|
|
try {
|
|
_r = angular.fromJson(rendered);
|
|
} catch(e) {
|
|
_r = false;
|
|
}
|
|
return _r;
|
|
};
|
|
|
|
this.file_load = function(file) {
|
|
return $http({
|
|
url: "dashboards/"+file,
|
|
method: "GET",
|
|
transformResponse: function(response) {
|
|
return renderTemplate(response,$routeParams);
|
|
}
|
|
}).then(function(result) {
|
|
if(!result) {
|
|
return false;
|
|
}
|
|
|
|
self.dash_load(dash_defaults(result.data));
|
|
return true;
|
|
},function(result) {
|
|
alertSrv.set('Error',"Could not load <i>dashboards/"+file+"</i>. Please make sure it exists" ,'error');
|
|
return false;
|
|
});
|
|
};
|
|
|
|
this.elasticsearch_load = function(type,id) {
|
|
return $http({
|
|
url: config.elasticsearch + "/" + config.kibana_index + "/"+type+"/"+id,
|
|
method: "GET",
|
|
transformResponse: function(response) {
|
|
return renderTemplate(angular.fromJson(response)['_source']['dashboard'],$routeParams);
|
|
}
|
|
}).error(function(data, status, headers, conf) {
|
|
if(status === 0) {
|
|
alertSrv.set('Error',"Could not contact Elasticsearch at "+config.elasticsearch+
|
|
". Please ensure that Elasticsearch is reachable from your system." ,'error');
|
|
} else {
|
|
alertSrv.set('Error',"Could not find "+id+". If you"+
|
|
" are using a proxy, ensure it is configured correctly",'error');
|
|
}
|
|
return false;
|
|
}).success(function(data, status, headers) {
|
|
self.dash_load(data);
|
|
});
|
|
};
|
|
|
|
this.script_load = function(file) {
|
|
return $http({
|
|
url: "dashboards/"+file,
|
|
method: "GET",
|
|
transformResponse: function(response) {
|
|
/*jshint -W054 */
|
|
var _f = new Function("ARGS",response);
|
|
return _f($routeParams);
|
|
}
|
|
}).then(function(result) {
|
|
if(!result) {
|
|
return false;
|
|
}
|
|
self.dash_load(dash_defaults(result.data));
|
|
return true;
|
|
},function(result) {
|
|
alertSrv.set('Error',
|
|
"Could not load <i>scripts/"+file+"</i>. Please make sure it exists and returns a valid dashboard" ,
|
|
'error');
|
|
return false;
|
|
});
|
|
};
|
|
|
|
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;
|
|
|
|
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;
|
|
}
|
|
);
|
|
};
|
|
|
|
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) {
|
|
return false;
|
|
}
|
|
});
|
|
return files;
|
|
}, function(data, status, headers, config) {
|
|
return false;
|
|
});
|
|
};
|
|
});
|