Merge branch 'feature/3270/statusPage' of github.com:panda01/kibana into apps/home

This commit is contained in:
Spencer Alger 2015-07-13 16:51:34 -07:00
commit 22fb1e5a96
19 changed files with 328 additions and 195 deletions

View file

@ -9,6 +9,7 @@
"require": true,
"console": false,
"-event": true,
"-name": true
"-name": true,
"module": true
}
}

View file

@ -39,7 +39,7 @@
"url": "https://github.com/elastic/kibana.git"
},
"dependencies": {
"angular-nvd3": "krispo/angular-nvd3#7f04d396cb69cb0ed944555ffeb95e0d7457c952",
"angular-nvd3": "panda01/angular-nvd3#kibana",
"ansicolors": "^0.3.2",
"autoprefixer-loader": "^2.0.0",
"babel-jscs": "^1.0.3",
@ -80,7 +80,7 @@
"moment": "^2.10.3",
"node-libs-browser": "spalger/node-libs-browser",
"numeral": "^1.5.3",
"nvd3": "novus/nvd3#679fe2ff96c194e442970cb9959ebccd79f6bd43",
"nvd3": "panda01/nvd3#kibana",
"raw-loader": "^0.5.1",
"request": "^2.40.0",
"requirefrom": "^0.2.0",

View file

@ -1,166 +0,0 @@
var angular = require('angular');
var $ = require('jquery');
var _ = require('lodash');
var moment = require('moment');
var numeral = require('numeral');
require('angular-nvd3');
// Turns thisIsASentence to
// This Is A Sentence
function niceName(name) {
return name
.split(/(?=[A-Z])/)
.map(function (word) { return word[0].toUpperCase() + _.rest(word).join(''); })
.join(' ');
}
function formatNumber(num, which) {
var format = '0.00';
var postfix = '';
switch (which) {
case 'time':
return moment(num).format('HH:mm:ss');
case 'byte':
format += 'b';
break;
case 'ms':
postfix = 'ms';
break;
}
return numeral(num).format(format) + postfix;
}
function numberType(key) {
var byteMetrics = ['heapTotal', 'heapUsed', 'rss'];
var msMetrics = ['delay', 'responseTimeAvg', 'responseTimeMax'];
var preciseMetric = ['requests', 'load'];
if ( byteMetrics.indexOf(key) > -1 ) {
return 'byte';
} else if (msMetrics.indexOf(key) > -1 ) {
return 'ms';
} else {
return 'precise';
}
}
var makeChartOptions = _.memoize(function (type) {
return {
chart: {
type: 'lineChart',
height: 200,
showLegend: false,
showXAxis: false,
showYAxis: false,
useInteractiveGuideline: true,
tooltips: true,
pointSize: 0,
color: ['#444', '#777', '#aaa'],
margin: {
top: 10,
left: 0,
right: 0,
bottom: 20
},
xAxis: { tickFormat: function (d) { return formatNumber(d, 'time'); } },
yAxis: { tickFormat: function (d) { return formatNumber(d, type); }, },
y: function (d) { return d.y; },
x: function (d) { return d.x; }
}
};
});
// The Kibana App
require('modules')
.get('KibanaStatusApp', ['nvd3'])
.controller('StatusPage', function ($scope, $http, $window, $timeout) {
// the object representing all of the elements the ui touches
$scope.ui = {
overallStatus: null,
statuses: null,
loading: true,
charts: {}
};
var windowHasFocus = true;
angular.element($window).bind({
blur: function () { windowHasFocus = false; },
focus: function () {
windowHasFocus = true;
getAppStatus();
}
});
function getAppStatus() {
$scope.ui.loading = true;
// go ahead and get the info you want
$http
.get('/api/status')
.success(function (data) {
// Assign the propper variables to the scope and change them as necessary
// setup The charts
// wrap the metrics data and append the average
$scope.ui.charts = _.mapValues(data.metrics, function (metric, name) {
// Metric Values format
// metric: [[xValue, yValue], ...]
// LoadMetric:
// metric: [[xValue, [yValue, yValue2, yValue3]], ...]
// return [
// {type: 'line', key: name, yAxis: 1, values: [{x: xValue, y: yValue}, ...]},
// {type: 'line', key: name, yAxis: 1, values: [{x: xValue, y: yValue1}, ...]},
// {type: 'line', key: name, yAxis: 1, values: [{x: xValue, y: yValue2}, ...]}]
//
// Go through all of the metric values and split the values out.
// returns an array of all of the averages
var metricList = [];
var metricNumberType = numberType(name);
// convert the [x,y] into {x: x, y: y}
metric.forEach(function (vector) {
vector = _.flatten(vector);
var x = vector.shift();
vector.forEach(function (yValue, idx) {
if (!metricList[idx]) {
metricList[idx] = {
key: name + idx,
values: []
};
}
// unshift to make sure they're in the correct order
metricList[idx].values.unshift({x: x, y: yValue});
});
});
var average = metricList.map(function (data) {
var uglySum = data.values.reduce(function (sumSoFar, vector) {
return sumSoFar + vector.y;
}, 0);
return formatNumber(uglySum / data.values.length, metricNumberType);
});
var options = makeChartOptions(metricNumberType);
return { data: metricList, average: average, niceName: niceName(name), options: options };
});
// give the plugins their proper name so CSS classes can be properply applied
$scope.ui.overallStatus = data.status.overall;
$scope.ui.statuses = data.status.statuses;
if (windowHasFocus) {
// go ahead and get another status in 5 seconds
$timeout(getAppStatus, 5000);
}
})
.error(function () {
window.alert('Something went terribly wrong while making the request!!! Perhaps your server is down?');
})
.finally(function () {
$scope.ui.loading = false;
});
}
// Start it all up
getAppStatus();
});

View file

@ -0,0 +1,19 @@
var moment = require('moment');
var numeral = require('numeral');
module.exports = function formatNumber(num, which) {
var format = '0.00';
var postfix = '';
switch (which) {
case 'time':
return moment(num).format('HH:mm:ss');
case 'byte':
format += 'b';
break;
case 'ms':
postfix = 'ms';
break;
}
return numeral(num).format(format) + postfix;
};

View file

@ -0,0 +1,28 @@
var formatNumber = require('./formatNumber');
module.exports = function makeChartOptions(type) {
return {
chart: {
type: 'lineChart',
height: 200,
showLegend: false,
showXAxis: false,
showYAxis: false,
useInteractiveGuideline: true,
tooltips: true,
pointSize: 0,
color: ['#444', '#777', '#aaa'],
margin: {
top: 10,
left: 0,
right: 0,
bottom: 20
},
xAxis: { tickFormat: function (d) { return formatNumber(d, 'time'); } },
yAxis: { tickFormat: function (d) { return formatNumber(d, type); }, },
y: function (d) { return d.y; },
x: function (d) { return d.x; }
}
};
};

View file

@ -0,0 +1,39 @@
var _ = require('lodash');
module.exports = function readStatData(data, seriesNames) {
// Metric Values format
// metric: [[xValue, yValue], ...]
// LoadMetric:
// metric: [[xValue, [yValue, yValue2, yValue3]], ...]
// return [
// {type: 'line', key: name, yAxis: 1, values: [{x: xValue, y: yValue}, ...]},
// {type: 'line', key: name, yAxis: 1, values: [{x: xValue, y: yValue1}, ...]},
// {type: 'line', key: name, yAxis: 1, values: [{x: xValue, y: yValue2}, ...]}]
//
// Go through all of the metric values and split the values out.
// returns an array of all of the averages
var metricList = [];
seriesNames = seriesNames || [];
data.forEach(function (vector) {
vector = _.flatten(vector);
var x = vector.shift();
vector.forEach(function (yValue, i) {
var series = seriesNames[i] || '';
if (!metricList[i]) {
metricList[i] = {
key: series,
values: []
};
}
// unshift to make sure they're in the correct order
metricList[i].values.unshift({
x: x,
y: yValue
});
});
});
return metricList;
};

View file

@ -0,0 +1,10 @@
var _ = require('lodash');
// Turns thisIsASentence to
// This Is A Sentence
module.exports = function toTitleCase(name) {
return name
.split(/(?=[A-Z])/)
.map(function (word) { return word[0].toUpperCase() + _.rest(word).join(''); })
.join(' ');
};

View file

@ -11,13 +11,13 @@
<p>Here is the status of your kibana instance and the plugins you have installed along with some, statistics to asses potential problems.</p>
</section>
<div class="system_status_wrapper state_default state_{{ui.overallStatus.state}}">
<div class="system_status_wrapper state_default state_{{ui.overall.state}}">
<h3 class="title">
<b>
System Status:
</b>
{{ui.overallStatus.nickname || ui.overallStatus.title}}
{{ui.overall.nickname || ui.overall.title}}
</h3>
<div ng-if="!ui.statuses && ui.loading" class="loading_statuses">
@ -44,13 +44,8 @@
<h2>Server Metrics</h2>
<p>Interval of 5 seconds, with a max history of 5 minutes.</p>
<div id="chart_cont" class="row">
<div ng-repeat="(key, chart) in ui.charts" class="status_chart_wrapper col-md-4">
<h3 class="title">{{chart.niceName}}</h2>
<h4 class="average">
<span ng-repeat="average in chart.average track by $index"><span ng-if="$index">, </span>{{average}}</span>
</h4>
<nvd3 options="chart.options" data="chart.data"></nvd3>
<div ng-repeat="(name, data) in ui.metrics">
<server-status-metric></server-status-metric>
</div>
<div class="clearfix"></div>
</div>
</div>

View file

@ -1,4 +1,5 @@
require('plugins/serverStatus/KibanaStatusApp');
require('plugins/serverStatus/serverStatusController');
require('plugins/serverStatus/serverStatusMetric');
require('plugins/serverStatus/serverStatus.less');
require('chrome')
@ -9,4 +10,4 @@ require('chrome')
}
])
.setRootTemplate(require('plugins/serverStatus/serverStatus.html'))
.setRootController('statusPage', 'StatusPage');
.setRootController('ui', 'ServerStatusController');

View file

@ -0,0 +1,48 @@
var angular = require('angular');
var $ = require('jquery');
var _ = require('lodash');
var notify = require('components/notify');
// The Kibana App
require('modules')
.get('kibana')
.controller('ServerStatusController', function ($http, $window, $timeout) {
var ui = this;
ui.loading = false;
ui.init = function () {
$($window)
.on('blur', ui.blur)
.on('focus', ui.refresh);
return ui.refresh();
};
ui.blur = function () {
$timeout.cancel(ui.timeout);
};
ui.refresh = function () {
ui.loading = true;
// go ahead and get the info you want
return $http
.get('/api/status')
.then(function (resp) {
var data = resp.data;
ui.metrics = data.metrics;
ui.overall = data.status.overall;
ui.statuses = data.status.statuses;
})
.catch(function () {
notify.error('Failed to request server ui. Perhaps your server is down?');
})
.then(function () {
ui.loading = false;
ui.timeout = $timeout(ui.refresh, 5000);
});
};
ui.init();
});

View file

@ -0,0 +1,8 @@
<div class="status_chart_wrapper col-md-4">
<h3 class="title">{{chart.niceName}}</h3>
<h4 class="average">
<span ng-repeat="average in chart.average track by $index"><span ng-if="$index">,&nbsp;</span>{{average}}</span>
</h4>
<nvd3 options="chart.options" data="chart.data"></nvd3>
</h4>
</div>

View file

@ -0,0 +1,57 @@
var _ = require('lodash');
var moment = require('moment');
var numeral = require('numeral');
require('angular-nvd3');
var toTitleCase = require('./lib/toTitleCase');
var formatNumber = require('./lib/formatNumber');
var getChartOptions = _.memoize(require('./lib/makeChartOptions'));
var readStatData = require('./lib/readStatData');
function metricToNumberType(stat) {
switch (stat) {
case 'heapTotal':
case 'heapUsed':
case 'rss':
return 'byte';
case 'delay':
case 'responseTimeAvg':
case 'responseTimeMax':
return 'ms';
default:
return 'precise';
}
}
function calcAvg(metricList, metricNumberType) {
return metricList.map(function (data) {
var uglySum = data.values.reduce(function (sumSoFar, vector) {
return sumSoFar + vector.y;
}, 0);
return formatNumber(uglySum / data.values.length, metricNumberType);
});
}
require('modules')
.get('kibana', ['nvd3'])
.directive('serverStatusMetric', function () {
return {
restrict: 'E',
template: require('plugins/serverStatus/serverStatusMetric.html'),
link: function ($scope, $el, attrs) {
var metricNumberType = metricToNumberType($scope.name);
$scope.chart = {
niceName: toTitleCase($scope.name),
options: getChartOptions(metricNumberType)
};
$scope.seriesNames = $scope.name === 'load' ? ['1min', '5min', '15min'] : null;
$scope.$watch('data', function () {
$scope.chart.data = readStatData($scope.data, $scope.seriesNames);
$scope.chart.average = calcAvg($scope.chart.data, metricNumberType);
});
}
};
});

View file

@ -11,6 +11,24 @@ module.exports = function (kbnServer, server, config) {
server.exposeStaticDir('/bundles/{path*}', bundleDir);
server.ext('onRequest', function (request, reply) {
switch (status.state) {
case 'yellow':
return reply(`
<html>
<head><meta http-equiv="refresh" content="1"></head>
<body>${status.message}</body>
</html>
`);
case 'red':
return reply(`
<html><body>${status.message}, please wait.</body></html>
`);
default:
return reply.continue();
}
});
function start() {
kbnServer.optimizer = new Optimizer({
watch: config.get('optimize.watch'),

View file

@ -0,0 +1,42 @@
'use strict';
let promify = require('bluebird').promisify;
let glob = promify(require('glob'));
let write = promify(require('fs').writeFile);
let webpack = promify(require('webpack'));
let basename = require('path').basename;
let fromRoot = require('../../utils/fromRoot');
const TEST = fromRoot('test');
const UNIT = fromRoot('test/unit');
const ENTRY = fromRoot('test/unit.specs.entry.js');
const BUNDLE = fromRoot('test/unit.specs.bundle.js');
const SPECS = fromRoot('test/unit/specs/**/*.js');
const NODE_MODULES = fromRoot('node_modules');
module.exports = function () {
return glob(SPECS, { cwd: '/' })
.filter(function (path) {
return basename(path)[0] !== '_';
})
.reduce(function (memo, path) {
return `${memo}\nrequire('${path}');`;
}, '')
.then(function (contents) {
return write(ENTRY, contents, { encoding: 'utf8' });
})
.then(function () {
return webpack({
context: TEST,
entry: ENTRY,
output: {
path: TEST,
filename: 'unit.specs.bundle.js'
}
});
})
.then(function (stats) {
console.log(stats.toString({ colors: true }));
return BUNDLE;
});
};

View file

@ -37,15 +37,6 @@ module.exports = function (kbnServer, server, config) {
let app = apps.byId[id];
if (!app) return reply(Boom.notFound('Unkown app ' + id));
if (kbnServer.status.getState('optimize') === 'yellow') {
return reply(`
<html>
<head><meta http-equiv="refresh" content="1"></head>
<body>Optimization in progress, please wait.</body>
</html>
`);
}
if (kbnServer.status.isGreen()) {
return reply.renderApp(app);
} else {

View file

@ -5,7 +5,9 @@
ng-class="{ show: !chrome.embedded }"
bindonce
class="hide navbar navbar-inverse navbar-static-top">
<div class="navbar-header">
<!-- Mobile navbar -->
<div class=" -header">
<button ng-click="showCollapsed = !showCollapsed" type="button" class="navbar-toggle">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
@ -18,6 +20,7 @@
<span ng-show="chrome.httpActive.length" class="spinner"></span>
</span>
</div>
<!-- /Mobile navbar -->
<!-- Full navbar -->
<div collapse="!showCollapsed" class="navbar-collapse">
@ -51,7 +54,10 @@
<ul ng-show="timefilter.enabled" class="nav navbar-nav navbar-right navbar-timepicker">
<li>
<a ng-click="toggleRefresh()" ng-show="timefilter.refreshInterval.value > 0">
<a
ng-click="toggleRefresh()"
ng-show="timefilter.refreshInterval.value > 0">
<i class="fa" ng-class="timefilter.refreshInterval.pause ? 'fa-play' : 'fa-pause'"></i>
</a>
</li>
@ -85,8 +91,8 @@
<div class="spinner"></div>
</li>
</ul>
<!-- /Full navbar -->
</div>
<!-- /Full navbar -->
</nav>
<config

View file

@ -2,6 +2,6 @@
max-height: <%= truncateMaxHeight %>;
display: inline-block;
}
.truncate-by-height::before {
.truncate-by-height:before {
top: <%= truncateGradientTop %>;
}
}

View file

@ -7,7 +7,7 @@ define(function (require) {
.get('kibana')
.run(function ($rootScope, $compile, config) {
var truncateGradientHeight = 15;
var template = _.template(require('raw!components/style_compile/style_compile.css.tmpl'));
var template = _.template(require('components/style_compile/style_compile.css.tmpl'));
var locals = {};
$rootScope.$on('$destroy', function () {

36
tasks/config/watch.js Normal file
View file

@ -0,0 +1,36 @@
module.exports = function (grunt) {
var config = {
less: {
files: [
'<%= app %>/**/styles/**/*.less',
'<%= plugins %>/*/styles/**/*.less',
'<%= plugins %>/*/*.less',
'<%= app %>/**/components/**/*.less',
'<%= app %>/**/components/vislib/components/styles/**/*.less',
'<%= src %>/server/plugins/status/public/styles/main.less'
],
tasks: ['less:dev']
},
jade: {
files: [
'<%= unitTestDir %>/index.jade'
],
tasks: ['jade:test']
},
clientside_jade: {
files: [
'<%= testUtilsDir %>/istanbul_reporter/report.clientside.jade'
],
tasks: ['jade:clientside']
}
};
if (grunt.option('no-test-watcher')) {
// unset the test watcher
delete config.test;
}
return config;
};