templated and scripted dashboards
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
/* Complex scripted Logstash dashboard */
|
||||
|
||||
|
||||
var dashboard, ARGS, queries;
|
||||
|
||||
// arguments[0] contains a hash of the URL parameters, make it shorter
|
||||
ARGS = arguments[0];
|
||||
|
||||
// Intialize a skeleton with nothing but a rows array and service object
|
||||
dashboard = {
|
||||
rows : [],
|
||||
services : {}
|
||||
};
|
||||
|
||||
// Set a title
|
||||
dashboard.title = 'Logstash Search';
|
||||
|
||||
// Allow the user to set the index, if they dont, fall back to logstash.
|
||||
if(!_.isUndefined(ARGS.index)) {
|
||||
dashboard.index = {
|
||||
default: ARGS.index,
|
||||
interval: 'none'
|
||||
}
|
||||
} else {
|
||||
dashboard.index = {
|
||||
default: ARGS.index||'ADD_A_TIME_FILTER',
|
||||
pattern: ARGS.pattern||'[logstash-]YYYY.MM.DD',
|
||||
interval: ARGS.interval||'day'
|
||||
}
|
||||
}
|
||||
|
||||
// In this dashboard we let users pass queries as comma seperated list to the query parameter.
|
||||
// Or they can specify a split character using the split aparameter
|
||||
// If query is defined, split it into a list of query objects
|
||||
// NOTE: ids must be integers, hence the parseInt()s
|
||||
if(!_.isUndefined(ARGS.query)) {
|
||||
queries = _.object(_.map(ARGS.query.split(ARGS.split||','), function(v,k) {
|
||||
return [k,{
|
||||
query: v,
|
||||
id: parseInt(k),
|
||||
alias: v
|
||||
}];
|
||||
}));
|
||||
} else {
|
||||
// No queries passed? Initialize a single query to match everything
|
||||
queries = {
|
||||
0: {
|
||||
query: '*',
|
||||
id: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now populate the query service with our objects
|
||||
dashboard.services.query = {
|
||||
list : queries,
|
||||
ids : _.map(_.keys(queries),function(v){return parseInt(v);})
|
||||
}
|
||||
|
||||
// Lets also add a default time filter, the value of which can be specified by the user
|
||||
dashboard.services.filter = {
|
||||
list: {
|
||||
0: {
|
||||
from: kbn.time_ago(ARGS.from||'15m'),
|
||||
to: new Date(),
|
||||
field: ARGS.timefield||"@timestamp",
|
||||
type: "time",
|
||||
active: true,
|
||||
id: 0
|
||||
}
|
||||
},
|
||||
ids: [0]
|
||||
}
|
||||
|
||||
// Ok, lets make some rows. The Filters row is collapsed by default
|
||||
dashboard.rows = [
|
||||
{
|
||||
title: "Input",
|
||||
height: "30px"
|
||||
},
|
||||
{
|
||||
title: "Filters",
|
||||
height: "100px",
|
||||
collapse: true
|
||||
},
|
||||
{
|
||||
title: "Chart",
|
||||
height: "300px"
|
||||
},
|
||||
{
|
||||
title: "Events",
|
||||
height: "400px"
|
||||
}
|
||||
];
|
||||
|
||||
// Setup some panels. A query panel and a filter panel on the same row
|
||||
dashboard.rows[0].panels = [
|
||||
{
|
||||
type: 'query',
|
||||
span: 7
|
||||
},
|
||||
{
|
||||
type: 'timepicker',
|
||||
span: 5,
|
||||
timespan: ARGS.from||'15m'
|
||||
}
|
||||
];
|
||||
|
||||
// Add a filtering panel to the 2nd row
|
||||
dashboard.rows[1].panels = [
|
||||
{
|
||||
type: 'filtering'
|
||||
}
|
||||
]
|
||||
|
||||
// And a histogram that allows the user to specify the interval and time field
|
||||
dashboard.rows[2].panels = [
|
||||
{
|
||||
type: 'histogram',
|
||||
time_field: ARGS.timefield||"@timestamp",
|
||||
auto_int: true
|
||||
}
|
||||
]
|
||||
|
||||
// And a table row where you can specify field and sort order
|
||||
dashboard.rows[3].panels = [
|
||||
{
|
||||
type: 'table',
|
||||
fields: !_.isUndefined(ARGS.fields) ? ARGS.fields.split(',') : ['@timestamp','@message'],
|
||||
sort: !_.isUndefined(ARGS.sort) ? ARGS.sort.split(',') : [ARGS.timefield||'@timestamp','desc'],
|
||||
overflow: 'expand'
|
||||
}
|
||||
]
|
||||
|
||||
// Now return the object and we're good!
|
||||
return dashboard;
|
||||
@@ -3,14 +3,11 @@
|
||||
"services": {
|
||||
"query": {
|
||||
"idQueue": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4
|
||||
1
|
||||
],
|
||||
"list": {
|
||||
"0": {
|
||||
"query": "*",
|
||||
"query": "{{ARGS.query || '*'}}",
|
||||
"alias": "",
|
||||
"color": "#7EB26D",
|
||||
"id": 0
|
||||
@@ -22,8 +19,7 @@
|
||||
},
|
||||
"filter": {
|
||||
"idQueue": [
|
||||
1,
|
||||
2
|
||||
1
|
||||
],
|
||||
"list": {
|
||||
"0": {
|
||||
@@ -70,7 +66,7 @@
|
||||
"7d",
|
||||
"30d"
|
||||
],
|
||||
"timespan": "1h",
|
||||
"timespan": "{{ARGS.from || '1h'}}",
|
||||
"timefield": "@timestamp",
|
||||
"timeformat": "",
|
||||
"refresh": {
|
||||
@@ -246,4 +242,4 @@
|
||||
"pattern": "[logstash-]YYYY.MM.DD",
|
||||
"default": "NO_TIME_FILTER_OR_INDEX_PATTERN_NOT_MATCHED"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+80
-14
@@ -252,6 +252,13 @@ angular.module('kibana.services', [])
|
||||
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;
|
||||
@@ -275,6 +282,12 @@ angular.module('kibana.services', [])
|
||||
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({});
|
||||
}
|
||||
@@ -290,16 +303,12 @@ angular.module('kibana.services', [])
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
var _id = nextId();
|
||||
var _query = {
|
||||
query: '*',
|
||||
alias: '',
|
||||
color: colorAt(_id),
|
||||
pin: false,
|
||||
id: _id,
|
||||
type: 'lucene'
|
||||
};
|
||||
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;
|
||||
@@ -373,11 +382,13 @@ angular.module('kibana.services', [])
|
||||
.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,{
|
||||
|
||||
// Defaults for it
|
||||
var _d = {
|
||||
idQueue : [],
|
||||
list : {},
|
||||
ids : []
|
||||
});
|
||||
};
|
||||
|
||||
// For convenience
|
||||
var ejs = ejsResource(config.elasticsearch);
|
||||
@@ -388,6 +399,9 @@ angular.module('kibana.services', [])
|
||||
|
||||
// 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;
|
||||
@@ -606,6 +620,9 @@ angular.module('kibana.services', [])
|
||||
case ('file'):
|
||||
self.file_load(_id);
|
||||
break;
|
||||
case('script'):
|
||||
self.script_load(_id);
|
||||
break;
|
||||
default:
|
||||
self.file_load('default.json');
|
||||
}
|
||||
@@ -665,6 +682,7 @@ angular.module('kibana.services', [])
|
||||
};
|
||||
|
||||
this.dash_load = function(dashboard) {
|
||||
|
||||
// Cancel all timers
|
||||
timer.cancel_all();
|
||||
|
||||
@@ -744,11 +762,32 @@ angular.module('kibana.services', [])
|
||||
};
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
var _dashboard = result.data;
|
||||
_.defaults(_dashboard,_dash);
|
||||
self.dash_load(_dashboard);
|
||||
@@ -759,11 +798,13 @@ angular.module('kibana.services', [])
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
this.elasticsearch_load = function(type,id) {
|
||||
return $http({
|
||||
url: config.elasticsearch + "/" + config.kibana_index + "/"+type+"/"+id,
|
||||
method: "GET"
|
||||
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+
|
||||
@@ -774,7 +815,32 @@ angular.module('kibana.services', [])
|
||||
}
|
||||
return false;
|
||||
}).success(function(data, status, headers) {
|
||||
self.dash_load(angular.fromJson(data['_source']['dashboard']));
|
||||
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(response);
|
||||
return _f($routeParams);
|
||||
}
|
||||
}).then(function(result) {
|
||||
if(!result) {
|
||||
return false;
|
||||
}
|
||||
var _dashboard = result.data;
|
||||
_.defaults(_dashboard,_dash);
|
||||
self.dash_load(_dashboard);
|
||||
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;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -135,8 +135,6 @@ angular.module('kibana.histogram', [])
|
||||
if(dashboard.indices.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var _range = $scope.get_time_range();
|
||||
var _interval = $scope.get_interval(_range);
|
||||
|
||||
@@ -177,6 +175,7 @@ angular.module('kibana.histogram', [])
|
||||
// Then run it
|
||||
var results = request.doSearch();
|
||||
|
||||
|
||||
// Populate scope when we have results
|
||||
results.then(function(results) {
|
||||
$scope.panelMeta.loading = false;
|
||||
|
||||
Reference in New Issue
Block a user