Kibana the way YOU want it

The ELK stack has been adopted rapidly in the last few years - and for good reason. It can be configured and deployed fast and without many dependencies, and it can take care of all your monitoring needs.

However, Kibana only has a rather simple interface, the default visualizations do not always support everything we need (or just want).

I was recently working on a project where we needed to monitor the balance left in an account. Initially we used the metric visualization, yet that wasn’t enough. People who weren’t familiar enough with the dashboard couldn’t make sense of all the numbers floating around - so I decided to write my own visualization for that. (Based upon the official metric visualization here.)

As it turns out, all you need is some basic AngularJS to get started.

Defining your plugin

At first, we need to define our plugin, so kibana knows what we are exporting:

package.json:

1
2
3
4
{
"name": "health_vis_metric",
"version": "0.3.0"
}

index.js:

1
2
3
4
5
6
7
8
9
module.exports = function (kibana) {
return new kibana.Plugin({
uiExports: {
visTypes: [
'plugins/health_metric_vis/health_metric_vis'
]
}
});
};

In this case we are exporting a “visTypes” defined by the “health_metric_vis” plugin.

The Visualization

Next, we define the visualization itself, this includes the AngularJS view and controller for our plugin:

health_metric_vis.html:

1
2
3
4
5
6
<div ng-controller="KbnHealthMetricVisController" class="health-metric-vis">
    <div class="health-metric-container" ng-repeat="metric in metrics">
        <div class="health-metric-value" ng-style="{'font-size': vis.params.fontSize+'pt', 'color': metric.color }">{{metric.formattedValue}}</div>
        <div>{{metric.label}}</div>
    </div>
</div>

health_metric_vis_controller.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
define(function (require) {
let _ = require('lodash');
const module = require('ui/modules').get('health_metric_vis');
module.controller('KbnHealthMetricVisController', function ($scope, Private) {
const tabifyAggResponse = Private(require('ui/agg_response/tabify/tabify'));
const metrics = $scope.metrics = [];
function isInvalid(val) {
return _.isUndefined(val) || _.isNull(val) || _.isNaN(val);
}
function getColor(val, visParams) {
if (!visParams.invertScale) {
if (val <= visParams.redThreshold) {
return visParams.redColor;
}
else if (val < visParams.greenThreshold) {
return visParams.yellowColor;
}
else {
return visParams.greenColor;
}
}
else {
if (val <= visParams.greenThreshold) {
return visParams.greenColor;
}
else if (val < visParams.redThreshold) {
return visParams.yellowColor;
}
else {
return visParams.redColor;
}
}
}
$scope.processTableGroups = function (tableGroups) {
tableGroups.tables.forEach(function (table) {
table.columns.forEach(function (column, i) {
const fieldFormatter = table.aggConfig(column).fieldFormatter();
let value = table.rows[0][i];
let formattedValue = isInvalid(value) ? '?' : fieldFormatter(value);
let color = getColor(value, $scope.vis.params);
metrics.push({
label: column.title,
formattedValue: formattedValue,
color: color
});
});
});
};
$scope.$watch('esResponse', function (resp) {
if (resp) {
metrics.length = 0;
$scope.processTableGroups(tabifyAggResponse($scope.vis, resp));
}
});
});
});

health_metric_vis.less:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@import (reference) "~ui/styles/mixins.less";
.health-metric-vis {
width: 100%;
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-around;
align-items: center;
align-content: space-around;
.health-metric-value {
font-weight: bold;
.ellipsis();
}
.health-metric-container {
text-align: center;
padding: 1em;
}
}

Configuration

We don’t want our visualization to be limited by hardcoded limits and colors - that’s what configuration is for! All you need for this is setting up a group of input fields:

health_metric_vis_params.html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<div class="form-group">
<label>Font Size - {{ vis.params.fontSize }}pt</label>
<input type="range" ng-model="vis.params.fontSize" class="form-control" min="12" max="120" />
</div>
<div class="form-group">
<label>Red threshold <span ng-bind-template="({{!vis.params.invertScale ? 'below':'above'}} this value will be red)"></span></label>
<input type="number" ng-model="vis.params.redThreshold" class="form-control"/>
</div>
<div class="form-group">
<label>Green threshold <span ng-bind-template="({{!vis.params.invertScale ? 'above':'below'}} this value will be green)"></span></label>
<input type="number" ng-model="vis.params.greenThreshold" class="form-control"/>
</div>
<div class="form-group">
<label>
<input type="checkbox" ng-model="vis.params.invertScale">
Invert scale
</label>
</div>
<div class="form-group">
<label>Green color:</label>
<input type="color" ng-model="vis.params.greenColor" class="form-control"/>
</div>
<div class="form-group">
<label>Yellow color:</label>
<input type="color" ng-model="vis.params.yellowColor" class="form-control"/>
</div>
<div class="form-group">
<label>Red color:</label>
<input type="color" ng-model="vis.params.redColor" class="form-control"/>
</div>

The end result of this would be:

options

Piecing it together

Now that we have all the components, all that’s left is to tell Kibana how all these different pieces interact with each other.

health_metric_vis.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
define(function (require) {
// Load the required css files
require('plugins/health_metric_vis/health_metric_vis.less');
// Load the controller
require('plugins/health_metric_vis/health_metric_vis_controller');
// Register our provider with kibana (So it shows up in the menu)
require('ui/registry/vis_types').register(HealthMetricVisProvider);
function HealthMetricVisProvider(Private) {
// This means we are creating a visualization that uses a template.
const TemplateVisType = Private(require('ui/template_vis_type/TemplateVisType'));
const Schemas = Private(require('ui/Vis/Schemas'));
// Here we set up our visualization
return new TemplateVisType({
name: 'health-metric',
title: 'Health Metric',
description: 'A numeric health metric, can show a number and color it accordingly.',
icon: 'fa-calculator',
// Here we load the template file we created
template: require('plugins/health_metric_vis/health_metric_vis.html'),
params: {
// Setting up defaults
defaults: {
handleNoResults: true,
fontSize: 60,
invertScale: false,
redThreshold: 0,
greenThreshold: 0,
redColor: "#fd482f",
yellowColor: "#ffa500",
greenColor: "#6dc066"
},
// This is the configuration page
editor: require('plugins/health_metric_vis/health_metric_vis_params.html')
},
// Here you can configure what kind of query is build for your vis
schemas: new Schemas([
{
group: 'metrics',
name: 'metric',
title: 'Metric',
min: 1,
max: 1,
defaults: [
{ type: 'count', schema: 'metric' }
]
}
])
});
}
// export the provider so that the visType can be required with Private()
return HealthMetricVisProvider;
});

Installation

Finally, we can install our plugin using the kibana plugin command, either from a local directory or from a url.

In our case:
kibana plugin -i health_metric_vis -u https://github.com/DeanF/health_metric_vis/archive/master.zip

Results

After installing the plugin, we can now see it in the visualization screen:
visoptions

After setting up your metric, you’re done.

example

The full code can be found on GitHub. I hope this article helps you get more out of ELK.

Share0 Comments