The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/js/services.js

449 lines
12 KiB

/*jshint globalstrict:true */
/*global angular:true */
'use strict';
angular.module('kibana.services', [])
.service('eventBus', function($rootScope) {
// An array of registed types
var _types = []
this.broadcast = function(from,to,type,data) {
if(_.isUndefined(data))
var data = from
var packet = {
time: new Date(),
type: type,
from: from,
to: to,
data: data
}
if(_.contains(_types,'$kibana_debug'))
$rootScope.$broadcast('$kibana_debug',packet);
//console.log('Sent: '+type + ' to ' + to + ' from ' + from + ': ' + angular.toJson(data))
$rootScope.$broadcast(type,{
from: from,
to: to,
data: data
});
}
// This sets up an $on listener that checks to see if the event (packet) is
// addressed to the scope in question and runs the registered function if it
// is.
this.register = function(scope,type,fn) {
_types = _.union(_types,[type])
scope.$on(type,function(event,packet){
var _id = scope.$id;
var _to = packet.to;
var _from = packet.from;
var _type = packet.type
var _time = packet.time
var _group = (!(_.isUndefined(scope.panel))) ? scope.panel.group : ["NONE"]
//console.log('registered:' + type + " for " + scope.panel.title + " " + scope.$id)
if(!(_.isArray(_to)))
_to = [_to];
if(!(_.isArray(_group)))
_group = [_group];
// Transmit event only if the sender is not the receiver AND one of the following:
// 1) Receiver has group in _to 2) Receiver's $id is in _to
// 3) Event is addressed to ALL 4) Receiver is in ALL group
if((_.intersection(_to,_group).length > 0 ||
_.indexOf(_to,_id) > -1 ||
_.indexOf(_group,'ALL') > -1 ||
_.indexOf(_to,'ALL') > -1) &&
_from !== _id
) {
//console.log('Got: '+type + ' from ' + _from + ' to ' + _to + ': ' + angular.toJson(packet.data))
fn(event,packet.data,{time:_time,to:_to,from:_from,type:_type});
}
});
}
})
/*
Service: fields
Provides a global list of all seen fields for use in editor panels
*/
.factory('fields', function($rootScope) {
var fields = {
list : []
}
$rootScope.$on('fields', function(event,f) {
fields.list = _.union(f.data.all,fields.list)
})
return fields;
})
.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)
});
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 = new Array();
}
})
.service('query', function() {
})
.service('dashboard', function($routeParams, $http, $rootScope, ejsResource, timer) {
// A hash of defaults to use when loading a dashboard
var _dash = {
title: "",
editable: true,
rows: [],
services: {}
};
// 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,}\/*$)/;
// Empty dashboard object
this.current = {};
this.last = {};
// Store a reference to this
var self = this;
$rootScope.$on('$routeChangeSuccess',function(){
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;
if(_type === 'elasticsearch')
self.elasticsearch_load('dashboard',_id)
if(_type === 'temp')
self.elasticsearch_load('temp',_id)
if(_type === 'file')
self.file_load(_id)
// No dashboard in the URL
} else {
// Check if browser supports localstorage, and if there's a dashboard
if (Modernizr.localstorage &&
!(_.isUndefined(localStorage['dashboard'])) &&
localStorage['dashboard'] !== ''
) {
var dashboard = JSON.parse(localStorage['dashboard']);
_.defaults(dashboard,_dash);
self.dash_load(dashboard)
// No? Ok, grab default.json, its all we have now
} else {
self.file_load('default')
}
}
}
this.to_file = function() {
var blob = new Blob([angular.toJson(self.current,true)], {type: "application/json;charset=utf-8"});
// from filesaver.js
saveAs(blob, self.current.title+"-"+new Date().getTime());
return true;
}
this.set_default = function(dashboard) {
if (Modernizr.localstorage) {
localStorage['dashboard'] = angular.toJson(dashboard || self.current);
return true;
} else {
return false;
}
}
this.purge_default = function() {
if (Modernizr.localstorage) {
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 : location.href.replace(location.hash,""),
type : type,
id : id,
link : location.href.replace(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)
// Change title on object clone
if (type === 'dashboard') {
var 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)
})
if (type === 'temp')
request = request.ttl(ttl)
// 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;
});
}
this.dash_load = function(dashboard) {
self.current = dashboard;
timer.cancel_all();
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
}
})
.service('keylistener', function($rootScope) {
var keys = [];
$(document).keydown(function (e) {
keys[e.which] = true;
});
$(document).keyup(function (e) {
delete keys[e.which];
});
this.keyActive = function(key) {
return keys[key] == true;
}
});