mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
split dev server in two, preventing server source file from forcing webpack rebuilds
This commit is contained in:
parent
52570cbaad
commit
382598fada
47 changed files with 802 additions and 682 deletions
|
@ -17,7 +17,6 @@
|
|||
"angular-bootstrap": "0.10.0",
|
||||
"angular-elastic": "2.4.2",
|
||||
"angular-mocks": "1.2.28",
|
||||
"angular-nvd3": "https://github.com/krispo/angular-nvd3.git#1.0.0-beta",
|
||||
"angular-route": "1.2.28",
|
||||
"angular-ui-ace": "0.2.3",
|
||||
"bluebird": "~2.9.27",
|
||||
|
@ -35,7 +34,6 @@
|
|||
"ng-clip": "0.2.6",
|
||||
"marked": "0.3.3",
|
||||
"numeral": "1.5.3",
|
||||
"nvd3": "1.7.1",
|
||||
"leaflet-draw": "0.2.4"
|
||||
},
|
||||
"resolutions": {
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
"url": "https://github.com/elastic/kibana.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"angular-nvd3": "^1.0.0-beta",
|
||||
"ansicolors": "^0.3.2",
|
||||
"autoprefixer-loader": "^2.0.0",
|
||||
"babel-jscs": "^1.0.3",
|
||||
|
@ -47,10 +48,11 @@
|
|||
"boom": "^2.8.0",
|
||||
"bootstrap": "^3.3.5",
|
||||
"bunyan": "^1.2.3",
|
||||
"commander": "^2.6.0",
|
||||
"commander": "^2.8.1",
|
||||
"compression": "^1.3.0",
|
||||
"cookie-parser": "^1.3.3",
|
||||
"css-loader": "^0.15.1",
|
||||
"d3": "^3.5.6",
|
||||
"debug": "^2.1.1",
|
||||
"elasticsearch": "^5.0.0",
|
||||
"exports-loader": "^0.6.2",
|
||||
|
@ -59,6 +61,7 @@
|
|||
"extract-text-webpack-plugin": "^0.8.2",
|
||||
"file-loader": "^0.8.4",
|
||||
"font-awesome": "^4.3.0",
|
||||
"gaze": "^0.5.1",
|
||||
"glob": "^4.3.2",
|
||||
"good": "^6.2.0",
|
||||
"good-squeeze": "^2.1.0",
|
||||
|
@ -77,6 +80,7 @@
|
|||
"moment": "^2.10.3",
|
||||
"node-libs-browser": "spalger/node-libs-browser",
|
||||
"numeral": "^1.5.3",
|
||||
"nvd3": "^1.8.1",
|
||||
"raw-loader": "^0.5.1",
|
||||
"request": "^2.40.0",
|
||||
"requirefrom": "^0.2.0",
|
||||
|
|
|
@ -15,7 +15,7 @@ program
|
|||
);
|
||||
|
||||
// attach commands
|
||||
var serve = require('./commands/serve/serve')(program);
|
||||
var serve = require('./commands/serve')(program);
|
||||
|
||||
// check for no command name
|
||||
if (!argv[2] || argv[2][0] === '-') {
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
let _ = require('lodash');
|
||||
|
||||
let readYamlConfig = require('../../readYamlConfig');
|
||||
let fromRoot = require('../../../utils/fromRoot');
|
||||
let KbnServer = require('../../../server/KbnServer');
|
||||
let readYamlConfig = require('../readYamlConfig');
|
||||
let fromRoot = require('../../utils/fromRoot');
|
||||
let KbnServer = require('../../server/KbnServer');
|
||||
|
||||
module.exports = function (program) {
|
||||
program
|
||||
|
@ -35,7 +35,7 @@ module.exports = function (program) {
|
|||
|
||||
if (opts.dev && !opts.noWatch && !require('cluster').isWorker) {
|
||||
// stop processing the action and handoff to watch cluster manager
|
||||
return require('./watch');
|
||||
return require('../watch/watch');
|
||||
}
|
||||
|
||||
let settings = readYamlConfig(opts.config || fromRoot('config/kibana.yml'));
|
|
@ -1,88 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
let _ = require('lodash');
|
||||
let Gaze = require('gaze').Gaze;
|
||||
let join = require('path').join;
|
||||
let cluster = require('cluster');
|
||||
let ansicolors = require('ansicolors');
|
||||
|
||||
let cliPath = join(__dirname, '..', 'cli.js');
|
||||
let green = require('../color').green;
|
||||
let red = require('../color').red;
|
||||
let yellow = require('../color').yellow;
|
||||
|
||||
console.log(yellow(' Kibana starting '), 'and watching for changes');
|
||||
|
||||
cluster.setupMaster({
|
||||
exec: cliPath,
|
||||
silent: false
|
||||
});
|
||||
|
||||
let baseArgv = [process.execPath, cliPath].concat(_.difference(process.argv.slice(2), ['--no-watch']));
|
||||
let serverArgv = JSON.stringify(baseArgv.concat(['--optimize.enable=false']));
|
||||
let optimizerArgv = JSON.stringify(baseArgv.concat(['--plugins.initialize=false']));
|
||||
|
||||
let changedFiles = [];
|
||||
let server;
|
||||
let startServer = _.debounce(function () {
|
||||
if (server && server.isDead()) {
|
||||
server.kill(); // once "exit" event is received with 0 status, startServer() is called again
|
||||
return;
|
||||
}
|
||||
|
||||
server = cluster.fork({ kbnWorkerArgv: serverArgv });
|
||||
server.on('online', function () {
|
||||
if (!changedFiles.length) {
|
||||
console.log(green(' Kibana Started '));
|
||||
return;
|
||||
}
|
||||
|
||||
let files = changedFiles.splice(0);
|
||||
let prefix = files.length > 1 ? '\n - ' : '';
|
||||
let fileList = files.reduce(function (list, file, i, files) {
|
||||
return `${list || ''}${prefix}"${file}"`;
|
||||
}, '');
|
||||
|
||||
console.log(yellow(' Kibana Restarted '), `due to changes in ${fileList}`);
|
||||
});
|
||||
}, 200);
|
||||
|
||||
let optimizer = cluster.fork({ kbnWorkerArgv: optimizerArgv });
|
||||
optimizer.on('online', startServer);
|
||||
|
||||
cluster.on('exit', function (worker, code) {
|
||||
if (worker === server) {
|
||||
if (code > 0) {
|
||||
console.log(red(' Kibana Crashed '), 'with status code', code);
|
||||
} else {
|
||||
// graceful shutdowns should only happen if we are restarting
|
||||
startServer();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (worker === optimizer) {
|
||||
console.log(red(' optimizer crashed '), 'with status code', code);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
let gaze = new Gaze([
|
||||
'src/**/*.{js,json,tmpl,yml}',
|
||||
'!src/**/public/',
|
||||
'config/',
|
||||
], {
|
||||
cwd: join(__dirname, '..', '..', '..')
|
||||
});
|
||||
|
||||
// A file has been added/changed/deleted
|
||||
gaze.on('all', function (event, path) {
|
||||
changedFiles.push(path);
|
||||
startServer();
|
||||
});
|
||||
|
||||
gaze.on('error', function (err) {
|
||||
console.log(red(' failed to watch files \n'), err.stack);
|
||||
process.exit(1);
|
||||
});
|
||||
|
82
src/cli/watch/Worker.js
Normal file
82
src/cli/watch/Worker.js
Normal file
|
@ -0,0 +1,82 @@
|
|||
'use strict';
|
||||
|
||||
let _ = require('lodash');
|
||||
let cluster = require('cluster');
|
||||
let join = require('path').join;
|
||||
|
||||
let log = require('./log');
|
||||
|
||||
let cliPath = join(__dirname, '..', 'cli.js');
|
||||
let baseArgs = _.difference(process.argv.slice(2), ['--no-watch']);
|
||||
let baseArgv = [process.execPath, cliPath].concat(baseArgs);
|
||||
|
||||
cluster.setupMaster({
|
||||
exec: cliPath,
|
||||
silent: false
|
||||
});
|
||||
|
||||
module.exports = class Worker {
|
||||
constructor(gaze, opts) {
|
||||
var self = this;
|
||||
opts = opts || {};
|
||||
|
||||
self.type = opts.type;
|
||||
self.title = opts.title || opts.type;
|
||||
self.filter = opts.filter || _.constant(true);
|
||||
self.changeBuffer = [];
|
||||
|
||||
self.env = {
|
||||
kbnWorkerType: self.type,
|
||||
kbnWorkerArgv: JSON.stringify(baseArgv.concat(opts.args || []))
|
||||
};
|
||||
|
||||
self.start = _.debounce(_.bind(self.start, self), 25);
|
||||
cluster.on('exit', function (fork, code) {
|
||||
if (self.fork !== fork) return;
|
||||
self.onExit(code);
|
||||
});
|
||||
}
|
||||
|
||||
onExit(code) {
|
||||
if (code) log.red(`${this.title} crashed`, 'with status code', code);
|
||||
else this.start(); // graceful shutdowns happen on restart
|
||||
}
|
||||
|
||||
onChange(path) {
|
||||
if (!this.filter(path)) return;
|
||||
this.changeBuffer.push(path);
|
||||
this.start();
|
||||
}
|
||||
|
||||
onOnline() {
|
||||
log.green(`${this.title} started`);
|
||||
}
|
||||
|
||||
flushChangeBuffer() {
|
||||
let files = _.unique(this.changeBuffer.splice(0));
|
||||
let prefix = files.length > 1 ? '\n - ' : '';
|
||||
return files.reduce(function (list, file, i, files) {
|
||||
return `${list || ''}${prefix}"${file}"`;
|
||||
}, '');
|
||||
}
|
||||
|
||||
start() {
|
||||
if (this.fork) {
|
||||
if (!this.fork.isDead()) {
|
||||
this.fork.kill();
|
||||
// once "exit" event is received with 0 status, start() is called again
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.changeBuffer.length) {
|
||||
log.yellow(`${this.title} restarting`, `due to changes in ${this.flushChangeBuffer()}`);
|
||||
}
|
||||
}
|
||||
else {
|
||||
log.yellow(`${this.title} starting`);
|
||||
}
|
||||
|
||||
this.fork = cluster.fork(this.env);
|
||||
this.fork.once('online', _.bindKey(this, 'onOnline'));
|
||||
}
|
||||
};
|
12
src/cli/watch/log.js
Normal file
12
src/cli/watch/log.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
'use strict';
|
||||
|
||||
let _ = require('lodash');
|
||||
let ansicolors = require('ansicolors');
|
||||
|
||||
let log = _.restParam(function (color, label, rest1) {
|
||||
console.log.apply(console, [color(` ${_.trim(label)} `)].concat(rest1));
|
||||
});
|
||||
|
||||
exports.green = _.partial(log, require('../color').green);
|
||||
exports.red = _.partial(log, require('../color').red);
|
||||
exports.yellow = _.partial(log, require('../color').yellow);
|
48
src/cli/watch/watch.js
Normal file
48
src/cli/watch/watch.js
Normal file
|
@ -0,0 +1,48 @@
|
|||
'use strict';
|
||||
|
||||
let _ = require('lodash');
|
||||
let Gaze = require('gaze').Gaze;
|
||||
let join = require('path').join;
|
||||
|
||||
let log = require('./log');
|
||||
let cluster = require('cluster');
|
||||
let Worker = require('./Worker');
|
||||
|
||||
let gaze = new Gaze([
|
||||
'src/**/*.{js,yml}',
|
||||
'!src/**/public/**/*',
|
||||
'config/**/*',
|
||||
], {
|
||||
cwd: join(__dirname, '..', '..', '..'),
|
||||
debounceDelay: 200
|
||||
});
|
||||
|
||||
let workers = [
|
||||
new Worker(gaze, {
|
||||
type: 'optmzr',
|
||||
title: 'optimizer',
|
||||
args: ['--plugins.initialize=false', '--server.autoListen=false'],
|
||||
filter: function (path) {
|
||||
return /\/server\/optimize\//.test(path);
|
||||
}
|
||||
}),
|
||||
|
||||
new Worker(gaze, {
|
||||
type: 'server',
|
||||
args: ['--optimize.enable=false']
|
||||
})
|
||||
];
|
||||
|
||||
gaze.on('all', function (e, path) {
|
||||
_.invoke(workers, 'onChange', path);
|
||||
});
|
||||
|
||||
gaze.on('ready', function (watcher) {
|
||||
log.green('Watching for changes', `(${_.size(watcher.watched())} files)`);
|
||||
_.invoke(workers, 'start');
|
||||
});
|
||||
|
||||
gaze.on('error', function (err) {
|
||||
log.red('Failed to watch files!\n', err.stack);
|
||||
process.exit(1);
|
||||
});
|
3
src/plugins/.jshintrc
Normal file
3
src/plugins/.jshintrc
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"extends": "../../.jshintrc.node"
|
||||
}
|
3
src/plugins/appSwitcher/public/.jshintrc
Normal file
3
src/plugins/appSwitcher/public/.jshintrc
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"extends": "../../../../.jshintrc.browser"
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
.application {
|
||||
background-color: #ecf0f1;
|
||||
}
|
||||
.app-links {
|
||||
width: 700px;
|
||||
margin: 25px auto;
|
||||
text-align: justify;
|
||||
}
|
||||
.app-links .app-link {
|
||||
display: inline-block;
|
||||
background: white;
|
||||
width: 200px;
|
||||
margin: 0 30px 30px 0;
|
||||
}
|
||||
.app-links .app-link .app-icon {
|
||||
display: block;
|
||||
height: 200px;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
.app-links .app-link .app-info {
|
||||
display: block;
|
||||
padding: 15px;
|
||||
}
|
||||
.app-links .app-link .app-title {
|
||||
margin: 0 0 10px;
|
||||
color: #444444;
|
||||
}
|
||||
.app-links .app-link .app-description {
|
||||
font-size: 1em;
|
||||
color: #95a5a6;
|
||||
text-align: left;
|
||||
margin: 0;
|
||||
}
|
||||
/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi8vVXNlcnMvc3BlbmNlci9kZXYvZXMva2liYW5hL3NyYy91aS9hcHBTd2l0Y2hlci9hcHBTd2l0Y2hlci5sZXNzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUdBO0VBQ0UseUJBQUE7O0FBR0Y7RUFDRSxZQUFBO0VBQ0EsaUJBQUE7RUFDQSxtQkFBQTs7QUFIRixVQUtFO0VBQ0UscUJBQUE7RUFDQSxpQkFBQTtFQUNBLFlBQUE7RUFDQSxxQkFBQTs7QUFUSixVQUtFLFVBTUU7RUFDRSxjQUFBO0VBQ0EsYUFBQTtFQUNBLHNCQUFBO0VBQ0EsMkJBQUE7O0FBZk4sVUFLRSxVQWFFO0VBQ0UsY0FBQTtFQUNBLGFBQUE7O0FBcEJOLFVBS0UsVUFrQkU7RUFDRSxnQkFBQTtFQUNBLGNBQUE7O0FBekJOLFVBS0UsVUF1QkU7RUFDRSxjQUFBO0VBQ0EsY0FBQTtFQUNBLGdCQUFBO0VBQ0EsU0FBQSJ9 */
|
|
@ -1,29 +1,33 @@
|
|||
module.exports = function devServerPlugin(kibana) {
|
||||
var _ = require('lodash');
|
||||
var KbnServer = require('../server/KbnServer');
|
||||
'use strict';
|
||||
|
||||
var glob = require('glob');
|
||||
var join = require('path').join;
|
||||
var basename = require('path').basename;
|
||||
var relative = require('path').relative;
|
||||
var normalize = require('path').normalize;
|
||||
module.exports = function devModePlugin(kibana) {
|
||||
let _ = require('lodash');
|
||||
|
||||
var ROOT = join(__dirname, '..', '..', '..');
|
||||
var fromRoot = function () {
|
||||
return normalize(join([ROOT].concat(_.toArray(arguments))));
|
||||
};
|
||||
let glob = require('glob');
|
||||
let join = require('path').join;
|
||||
let basename = require('path').basename;
|
||||
let relative = require('path').relative;
|
||||
let normalize = require('path').normalize;
|
||||
let istanbul = require('./istanbul');
|
||||
let amdWrapper = require('./amdWrapper');
|
||||
let kibanaSrcFilter = require('./kibanaSrcFilter');
|
||||
|
||||
var SRC = fromRoot('src');
|
||||
var NODE_MODULES = fromRoot('node_modules');
|
||||
var UI = fromRoot('src/ui');
|
||||
var TEST = fromRoot('test');
|
||||
var UNIT = fromRoot('test/unit');
|
||||
var SPECS = fromRoot('test/unit/specs/**/*.js');
|
||||
var istanbul = require('./lib/istanbul');
|
||||
var amdWrapper = require('./lib/amd_wrapper');
|
||||
var kibanaSrcFilter = require('./lib/kibana_src_filter');
|
||||
|
||||
let fromRoot = require('../../utils/fromRoot');
|
||||
const ROOT = fromRoot('.');
|
||||
const SRC = fromRoot('src');
|
||||
const NODE_MODULES = fromRoot('node_modules');
|
||||
const UI = fromRoot('src/ui');
|
||||
const TEST = fromRoot('test');
|
||||
const UNIT = fromRoot('test/unit');
|
||||
const SPECS = fromRoot('test/unit/specs/**/*.js');
|
||||
|
||||
return new kibana.Plugin({
|
||||
|
||||
initCondition: function (config) {
|
||||
return config.get('env.dev');
|
||||
},
|
||||
|
||||
init: function (server, options) {
|
||||
server.ext('onPreHandler', istanbul({ root: SRC, displayRoot: SRC, filter: kibanaSrcFilter }));
|
||||
server.ext('onPreHandler', istanbul({ root: UI, displayRoot: SRC, filter: kibanaSrcFilter }));
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"name": "devMode",
|
||||
"main": "./devMode.js",
|
||||
"version": "1.0.0"
|
||||
}
|
|
@ -7,6 +7,7 @@ module.exports = function (kibana) {
|
|||
|
||||
config: function (Joi) {
|
||||
return Joi.object({
|
||||
enabled: Joi.boolean().default(true),
|
||||
url: Joi.string().uri({ scheme: ['http', 'https'] }).default('http://localhost:9200'),
|
||||
preserveHost: Joi.boolean().default(true),
|
||||
username: Joi.string(),
|
||||
|
|
|
@ -6,6 +6,8 @@ module.exports = function (kibana) {
|
|||
|
||||
config: function (Joi) {
|
||||
return Joi.object({
|
||||
enabled: Joi.boolean().default(true),
|
||||
defaultAppId: Joi.string().default('discover'),
|
||||
index: Joi.string().default('.kibana'),
|
||||
buildNum: Joi.string().default('@@buildNum')
|
||||
}).default();
|
||||
|
|
41
src/plugins/serverStatus/index.js
Normal file
41
src/plugins/serverStatus/index.js
Normal file
|
@ -0,0 +1,41 @@
|
|||
module.exports = function (kibana) {
|
||||
return new kibana.Plugin({
|
||||
|
||||
init: function (server, options) {
|
||||
var app = this.app;
|
||||
|
||||
server.expose('app', app);
|
||||
|
||||
server.route({
|
||||
path: '/status',
|
||||
method: 'GET',
|
||||
handler: function (req, reply) {
|
||||
return reply.renderApp(app);
|
||||
}
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
uiExports: {
|
||||
app: {
|
||||
title: 'Server Status',
|
||||
main: 'plugins/serverStatus/serverStatus',
|
||||
hidden: false,
|
||||
|
||||
defaultModules: {
|
||||
angular: [],
|
||||
require: [
|
||||
'chrome',
|
||||
'angular-bootstrap'
|
||||
]
|
||||
.concat(kibana.autoload.styles)
|
||||
}
|
||||
},
|
||||
|
||||
loaders: [
|
||||
{ test: /\/angular-nvd3\//, loader: 'imports?angular,d3' }
|
||||
]
|
||||
}
|
||||
});
|
||||
};
|
||||
|
4
src/plugins/serverStatus/package.json
Normal file
4
src/plugins/serverStatus/package.json
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"name": "serverStatus",
|
||||
"version": "1.0.0"
|
||||
}
|
3
src/plugins/serverStatus/public/.jshintrc
Normal file
3
src/plugins/serverStatus/public/.jshintrc
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"extends": "../../../../.jshintrc.browser"
|
||||
}
|
218
src/plugins/serverStatus/public/KibanaStatusApp.js
Normal file
218
src/plugins/serverStatus/public/KibanaStatusApp.js
Normal file
|
@ -0,0 +1,218 @@
|
|||
var angular = require('angular');
|
||||
var $ = require('jquery');
|
||||
var _ = require('lodash');
|
||||
var moment = require('moment');
|
||||
var numeral = require('numeral');
|
||||
require('angular-nvd3');
|
||||
|
||||
// Make sure we don't have to deal with statuses by hand
|
||||
function getStatus(plugin) {
|
||||
var statusMap = {
|
||||
green: {
|
||||
label: 'success',
|
||||
msg: 'Ready',
|
||||
idx: 1
|
||||
},
|
||||
yellow: {
|
||||
label: 'warning',
|
||||
msg: 'S.N.A.F.U.',
|
||||
idx: 2
|
||||
},
|
||||
red: {
|
||||
label: 'danger',
|
||||
msg: 'Danger Will Robinson! Danger!',
|
||||
idx: 3
|
||||
},
|
||||
loading: {
|
||||
label: 'info',
|
||||
msg: 'Loading...',
|
||||
idx: 0
|
||||
}
|
||||
};
|
||||
if (!_.isObject(plugin) || _.isUndefined(plugin)) {
|
||||
plugin = {state: plugin};
|
||||
}
|
||||
return statusMap[plugin.state];
|
||||
}
|
||||
|
||||
function getLabel(plugin) { return getStatus(plugin).label; }
|
||||
|
||||
// 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 = {
|
||||
// show the system status by going through all of the plugins,
|
||||
// and making sure they're green.
|
||||
systemStatus: (function () {
|
||||
// for convenience
|
||||
function getIdx(plugin) { return getStatus(plugin).idx; }
|
||||
|
||||
return function () {
|
||||
var currentStatus = 'loading';
|
||||
var currentIdx = getIdx(currentStatus);
|
||||
|
||||
// FIXME eh, not too thrilled about this.
|
||||
var status = _.reduce($scope.ui.plugins, function (curr, plugin, key) {
|
||||
var pluginIdx = getIdx(plugin);
|
||||
if (pluginIdx > currentIdx) {
|
||||
// set the current status
|
||||
currentStatus = plugin.state;
|
||||
currentIdx = getIdx(plugin);
|
||||
}
|
||||
return currentStatus;
|
||||
}, 'loading');
|
||||
|
||||
// give the ui the label for colors and such
|
||||
return getStatus(status);
|
||||
};
|
||||
}()),
|
||||
charts: {},
|
||||
plugins: []
|
||||
};
|
||||
|
||||
var windowHasFocus = true;
|
||||
angular.element($window).bind({
|
||||
blur: function () { windowHasFocus = false; },
|
||||
focus: function () {
|
||||
windowHasFocus = true;
|
||||
getAppStatus();
|
||||
}
|
||||
});
|
||||
|
||||
function getAppStatus() {
|
||||
// go ahead and get the info you want
|
||||
$http
|
||||
.get('/status/health')
|
||||
.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.plugins = _.mapValues(data.status, function (plugin) {
|
||||
plugin.uiStatus = getLabel(plugin);
|
||||
return plugin;
|
||||
});
|
||||
|
||||
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?');
|
||||
});
|
||||
}
|
||||
|
||||
// Start it all up
|
||||
getAppStatus();
|
||||
});
|
41
src/plugins/serverStatus/public/serverStatus.html
Normal file
41
src/plugins/serverStatus/public/serverStatus.html
Normal file
|
@ -0,0 +1,41 @@
|
|||
<header>
|
||||
<h1>
|
||||
<strong>Kibana</strong> Status Page
|
||||
</h1>
|
||||
</header>
|
||||
<div>
|
||||
<section class="section">
|
||||
<h4>What is this page?</h4>
|
||||
<p>This page is your sanity check, and your savior. You can check for potential problems</p>
|
||||
<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 system_status_{{ui.systemStatus().label}}">
|
||||
<h3 class="title">
|
||||
<b>System Status:</b> {{ui.systemStatus().msg}}
|
||||
</h3>
|
||||
<div id="plugin_table">
|
||||
<div class="plugin_table_header plugin_row">
|
||||
<div class="col-md-1 plugin_key">Plugin</div>
|
||||
<div class="col-md-11 plugin_state">Status</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div ng-repeat="(key, plugin) in ui.plugins" class="plugin_table_plugin plugin_row plugin_status_{{plugin.uiStatus}}">
|
||||
<div class="col-md-1 plugin_key">{{key}}</div>
|
||||
<div class="col-md-11 plugin_state">{{plugin.message}}</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
13
src/plugins/serverStatus/public/serverStatus.js
Normal file
13
src/plugins/serverStatus/public/serverStatus.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
require('plugins/serverStatus/KibanaStatusApp');
|
||||
require('plugins/serverStatus/serverStatus.less');
|
||||
|
||||
require('chrome')
|
||||
.setNavBackground('grey')
|
||||
.setTabs([
|
||||
{
|
||||
id: '',
|
||||
title: 'Server Status'
|
||||
}
|
||||
])
|
||||
.setRootTemplate(require('plugins/serverStatus/serverStatus.html'))
|
||||
.setRootController('statusPage', 'StatusPage');
|
|
@ -1,88 +1,94 @@
|
|||
var _ = require('lodash');
|
||||
var Promise = require('bluebird');
|
||||
var Hapi = require('hapi');
|
||||
var dirname = require('path').dirname;
|
||||
'use strict';
|
||||
|
||||
var rootDir = require('../utils/fromRoot')('.');
|
||||
var package = require('../utils/closestPackageJson').getSync();
|
||||
let _ = require('lodash');
|
||||
let EventEmitter = require('events').EventEmitter;
|
||||
let promify = require('bluebird').promisify;
|
||||
let each = require('bluebird').each;
|
||||
let Hapi = require('hapi');
|
||||
|
||||
function KbnServer(settings) {
|
||||
this.name = package.name;
|
||||
this.version = package.version;
|
||||
this.build = package.build || false;
|
||||
this.rootDir = rootDir;
|
||||
this.settings = settings || {};
|
||||
let rootDir = require('../utils/fromRoot')('.');
|
||||
let pkg = require('../utils/closestPackageJson').getSync();
|
||||
|
||||
this.server = new Hapi.Server();
|
||||
module.exports = class KbnServer extends EventEmitter {
|
||||
constructor(settings) {
|
||||
super();
|
||||
|
||||
// mixin server modules
|
||||
this.ready = _.constant(this.mixin(
|
||||
require('./config'),
|
||||
require('./logging'),
|
||||
require('./http'),
|
||||
require('./ui'), // sets this.uiExports
|
||||
require('./status'), // sets this.status
|
||||
require('./plugins'), // sets this.plugins
|
||||
require('./optimize')
|
||||
));
|
||||
}
|
||||
this.name = pkg.name;
|
||||
this.version = pkg.version;
|
||||
this.build = pkg.build || false;
|
||||
this.rootDir = rootDir;
|
||||
this.settings = settings || {};
|
||||
|
||||
/**
|
||||
* Extend the KbnServer outside of the constraits of a plugin. This allows access
|
||||
* to APIs that are not exposed (intentionally) to the plugins and should only
|
||||
* be used when the code will be kept up to date with Kibana.
|
||||
*
|
||||
* @param {...function} - functions that should be called to mixin functionality.
|
||||
* They are called with the arguments (kibana, server, config)
|
||||
* and can return a promise to delay execution of the next mixin
|
||||
* @return {Promise} - promise that is resolved when the final mixin completes.
|
||||
*/
|
||||
KbnServer.prototype.mixin = function (/* ...fns */) {
|
||||
var fns = _.toArray(arguments);
|
||||
var self = this;
|
||||
var server = self.server;
|
||||
this.server = new Hapi.Server();
|
||||
|
||||
return Promise.each(fns, function (fn) {
|
||||
return fn(self, server, server.config && server.config());
|
||||
})
|
||||
// clear the return value
|
||||
.then(_.noop);
|
||||
};
|
||||
require('./config')(this, this.server);
|
||||
let config = this.server.config();
|
||||
|
||||
/**
|
||||
* Tell the server to listen for incoming requests.
|
||||
*
|
||||
* @return {Promise} resolved with the server once it is listening
|
||||
*/
|
||||
KbnServer.prototype.listen = function () {
|
||||
var self = this;
|
||||
var server = self.server;
|
||||
var start = _.ary(Promise.promisify(server.start, server), 0);
|
||||
this.ready = _.constant(this.mixin(
|
||||
require('./logging'),
|
||||
require('./http'),
|
||||
require('./status'), // sets this.status
|
||||
require('./plugins'), // sets this.plugins
|
||||
require('./ui'), // sets this.uiExports
|
||||
require('./optimize')
|
||||
));
|
||||
|
||||
return self.ready()
|
||||
.then(function () {
|
||||
return self.mixin(start, require('./pid'));
|
||||
})
|
||||
.then(
|
||||
function () {
|
||||
server.log('info', 'Server running at ' + server.info.uri);
|
||||
return server;
|
||||
},
|
||||
function (err) {
|
||||
server.log('fatal', err);
|
||||
throw err;
|
||||
this.listen = _.once(this.listen);
|
||||
if (config.get('server.autoListen')) {
|
||||
this.listen();
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
// if this module was called from the command line, go ahead and start listening
|
||||
if (require.main === module) {
|
||||
(new KbnServer())
|
||||
.listen()
|
||||
.catch(function (err) {
|
||||
console.log(err.stack);
|
||||
process.exit(1);
|
||||
});
|
||||
} else {
|
||||
module.exports = KbnServer;
|
||||
}
|
||||
/**
|
||||
* Extend the KbnServer outside of the constraits of a plugin. This allows access
|
||||
* to APIs that are not exposed (intentionally) to the plugins and should only
|
||||
* be used when the code will be kept up to date with Kibana.
|
||||
*
|
||||
* @param {...function} - functions that should be called to mixin functionality.
|
||||
* They are called with the arguments (kibana, server, config)
|
||||
* and can return a promise to delay execution of the next mixin
|
||||
* @return {Promise} - promise that is resolved when the final mixin completes.
|
||||
*/
|
||||
mixin(/* ...fns */) {
|
||||
let fns = _.compact(_.toArray(arguments));
|
||||
let self = this;
|
||||
let server = self.server;
|
||||
|
||||
return each(fns, function (fn) {
|
||||
return fn.call(self, self, server, server.config());
|
||||
})
|
||||
.then(_.noop); // clear the return value
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the server to listen for incoming requests, or get
|
||||
* a promise that will be resolved once the server is listening.
|
||||
*
|
||||
* Calling this function has no effect, unless the "server.autoListen"
|
||||
* is set to false.
|
||||
*
|
||||
* @return undefined
|
||||
*/
|
||||
listen() {
|
||||
let self = this;
|
||||
let server = self.server;
|
||||
let start = _.ary(promify(server.start, server), 0);
|
||||
|
||||
self.ready().then(function () {
|
||||
return self.mixin(start, require('./pid'));
|
||||
})
|
||||
.then(
|
||||
function () {
|
||||
server.log('info', 'Server running at ' + server.info.uri);
|
||||
self.emit('listening');
|
||||
return server;
|
||||
},
|
||||
function (err) {
|
||||
server.log('fatal', err);
|
||||
self.emit('error', err);
|
||||
}
|
||||
);
|
||||
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,104 +1,114 @@
|
|||
var Promise = require('bluebird');
|
||||
var Joi = require('joi');
|
||||
var _ = require('lodash');
|
||||
var override = require('./override');
|
||||
'use strict';
|
||||
|
||||
function Config(schema, defaults) {
|
||||
this.schema = schema || Joi.object({}).default();
|
||||
this.config = {};
|
||||
this.set(defaults);
|
||||
}
|
||||
let Promise = require('bluebird');
|
||||
let Joi = require('joi');
|
||||
let _ = require('lodash');
|
||||
let override = require('./override');
|
||||
|
||||
Config.prototype.extendSchema = function (key, schema) {
|
||||
var additionalSchema = {};
|
||||
if (!this.has(key)) {
|
||||
additionalSchema[key] = schema;
|
||||
this.schema = this.schema.keys(additionalSchema);
|
||||
this.reset(this.config);
|
||||
}
|
||||
};
|
||||
|
||||
Config.prototype.reset = function (obj) {
|
||||
this._commit(obj);
|
||||
};
|
||||
|
||||
Config.prototype.set = function (key, value) {
|
||||
// clone and modify the config
|
||||
var config = _.cloneDeep(this.config);
|
||||
if (_.isPlainObject(key)) {
|
||||
config = override(config, key);
|
||||
} else {
|
||||
_.set(config, key, value);
|
||||
module.exports = class Config {
|
||||
constructor(schema, defaults) {
|
||||
this.schema = schema || Joi.object({}).default();
|
||||
this.config = {};
|
||||
this.set(defaults);
|
||||
}
|
||||
|
||||
// attempt to validate the config value
|
||||
this._commit(config);
|
||||
};
|
||||
|
||||
Config.prototype._commit = function (newConfig) {
|
||||
// resolve the current environment
|
||||
var env = newConfig.env;
|
||||
delete newConfig.env;
|
||||
if (_.isObject(env)) env = env.name;
|
||||
if (!env) env = process.env.NODE_ENV || 'production';
|
||||
|
||||
// pass the environment as context so that it can be refed in config
|
||||
var context = {
|
||||
env: env,
|
||||
prod: env === 'production',
|
||||
dev: env === 'development',
|
||||
};
|
||||
|
||||
if (!context.dev && !context.prod) {
|
||||
throw new TypeError(`Unexpected environment "${env}", expected one of "development" or "production"`);
|
||||
}
|
||||
|
||||
var results = Joi.validate(newConfig, this.schema, {
|
||||
context: context
|
||||
});
|
||||
|
||||
if (results.error) {
|
||||
throw results.error;
|
||||
}
|
||||
|
||||
this.config = results.value;
|
||||
};
|
||||
|
||||
Config.prototype.get = function (key) {
|
||||
if (!key) {
|
||||
return _.cloneDeep(this.config);
|
||||
}
|
||||
|
||||
var value = _.get(this.config, key);
|
||||
if (value === undefined) {
|
||||
extendSchema(key, schema) {
|
||||
let additionalSchema = {};
|
||||
if (!this.has(key)) {
|
||||
throw new Error('Unknown config key: ' + key);
|
||||
additionalSchema[key] = schema;
|
||||
this.schema = this.schema.keys(additionalSchema);
|
||||
this.reset(this.config);
|
||||
} else {
|
||||
throw new Error(`Config schema already has key ${key}`);
|
||||
}
|
||||
}
|
||||
return _.cloneDeep(value);
|
||||
};
|
||||
|
||||
Config.prototype.has = function (key) {
|
||||
function has(key, schema, path) {
|
||||
path = path || [];
|
||||
// Catch the partial paths
|
||||
if (path.join('.') === key) return true;
|
||||
// Only go deep on inner objects with children
|
||||
if (schema._inner.children.length) {
|
||||
for (var i = 0; i < schema._inner.children.length; i++) {
|
||||
var child = schema._inner.children[i];
|
||||
// If the child is an object recurse through it's children and return
|
||||
// true if there's a match
|
||||
if (child.schema._type === 'object') {
|
||||
if (has(key, child.schema, path.concat([child.key]))) return true;
|
||||
// if the child matches, return true
|
||||
} else if (path.concat([child.key]).join('.') === key) {
|
||||
return true;
|
||||
reset(obj) {
|
||||
this._commit(obj);
|
||||
}
|
||||
|
||||
set(key, value) {
|
||||
// clone and modify the config
|
||||
let config = _.cloneDeep(this.config);
|
||||
if (_.isPlainObject(key)) {
|
||||
config = override(config, key);
|
||||
} else {
|
||||
_.set(config, key, value);
|
||||
}
|
||||
|
||||
// attempt to validate the config value
|
||||
this._commit(config);
|
||||
}
|
||||
|
||||
_commit(newConfig) {
|
||||
// resolve the current environment
|
||||
let env = newConfig.env;
|
||||
delete newConfig.env;
|
||||
if (_.isObject(env)) env = env.name;
|
||||
if (!env) env = process.env.NODE_ENV || 'production';
|
||||
|
||||
// pass the environment as context so that it can be refed in config
|
||||
let context = {
|
||||
env: env,
|
||||
prod: env === 'production',
|
||||
dev: env === 'development',
|
||||
};
|
||||
|
||||
if (!context.dev && !context.prod) {
|
||||
throw new TypeError(`Unexpected environment "${env}", expected one of "development" or "production"`);
|
||||
}
|
||||
|
||||
let results = Joi.validate(newConfig, this.schema, {
|
||||
context: context
|
||||
});
|
||||
|
||||
if (results.error) {
|
||||
throw results.error;
|
||||
}
|
||||
|
||||
this.config = results.value;
|
||||
}
|
||||
|
||||
get(key) {
|
||||
if (!key) {
|
||||
return _.cloneDeep(this.config);
|
||||
}
|
||||
|
||||
let value = _.get(this.config, key);
|
||||
if (value === undefined) {
|
||||
if (!this.has(key)) {
|
||||
throw new Error('Unknown config key: ' + key);
|
||||
}
|
||||
}
|
||||
return _.cloneDeep(value);
|
||||
}
|
||||
|
||||
has(key) {
|
||||
function has(key, schema, path) {
|
||||
path = path || [];
|
||||
// Catch the partial paths
|
||||
if (path.join('.') === key) return true;
|
||||
// Only go deep on inner objects with children
|
||||
if (schema._inner.children.length) {
|
||||
for (let i = 0; i < schema._inner.children.length; i++) {
|
||||
let child = schema._inner.children[i];
|
||||
// If the child is an object recurse through it's children and return
|
||||
// true if there's a match
|
||||
if (child.schema._type === 'object') {
|
||||
if (has(key, child.schema, path.concat([child.key]))) return true;
|
||||
// if the child matches, return true
|
||||
} else if (path.concat([child.key]).join('.') === key) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return !!has(key, this.schema);
|
||||
};
|
||||
|
||||
module.exports = Config;
|
||||
if (_.isArray(key)) {
|
||||
// TODO: add .has() support for array keys
|
||||
key = key.join('.');
|
||||
}
|
||||
|
||||
return !!has(key, this.schema);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -19,6 +19,7 @@ module.exports = Joi.object({
|
|||
server: Joi.object({
|
||||
host: Joi.string().hostname().default('0.0.0.0'),
|
||||
port: Joi.number().default(5601),
|
||||
autoListen: Joi.boolean().default(true),
|
||||
defaultRoute: Joi.string(),
|
||||
ssl: Joi.object({
|
||||
cert: Joi.string(),
|
||||
|
@ -52,10 +53,12 @@ module.exports = Joi.object({
|
|||
|
||||
plugins: Joi.object({
|
||||
paths: Joi.array().items(Joi.string()).default([]),
|
||||
scanDirs: Joi.array().items(Joi.string()).default([])
|
||||
scanDirs: Joi.array().items(Joi.string()).default([]),
|
||||
initialize: Joi.boolean().default(true)
|
||||
}).default(),
|
||||
|
||||
optimize: Joi.object({
|
||||
enable: Joi.boolean().default(true),
|
||||
bundleDir: Joi.string().default(fromRoot('optimize/bundles')),
|
||||
viewCaching: Joi.boolean().default(Joi.ref('$prod')),
|
||||
watch: Joi.boolean().default(Joi.ref('$dev')),
|
||||
|
|
|
@ -16,16 +16,23 @@ let typeColors = {
|
|||
error: 'red',
|
||||
fatal: 'magenta',
|
||||
plugins: 'yellow',
|
||||
debug: 'brightBlack'
|
||||
debug: 'brightBlack',
|
||||
server: 'brightBlack',
|
||||
optmzr: 'white',
|
||||
};
|
||||
|
||||
let color = _.memoize(function (name) {
|
||||
return ansicolors[typeColors[name]] || _.identity;
|
||||
});
|
||||
|
||||
let type = _.memoize(function (t) {
|
||||
return _.chain(t).padLeft(6).trunc(6).thru(color(t)).value();
|
||||
});
|
||||
|
||||
let workerType = process.env.kbnWorkerType ? `${type(process.env.kbnWorkerType)}: ` : '';
|
||||
|
||||
module.exports = class KbnLoggerJsonFormat extends LogFormat {
|
||||
format(data) {
|
||||
let type = _.chain(data.type).padLeft(6).trunc(6).thru(color(data.type)).value();
|
||||
let time = color('time')(moment(data.timestamp).format());
|
||||
let msg = data.error ? color('error')(data.error.stack) : color('message')(data.message);
|
||||
|
||||
|
@ -37,6 +44,6 @@ module.exports = class KbnLoggerJsonFormat extends LogFormat {
|
|||
return s + `[${ color(t)(t) }]`;
|
||||
}, '');
|
||||
|
||||
return `${type}: [${time}] ${tags} ${msg}`;
|
||||
return `${workerType}${type(data.type)}: [${time}] ${tags} ${msg}`;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -72,7 +72,6 @@ class Optimizer extends EventEmitter {
|
|||
|
||||
resolve: {
|
||||
extensions: ['', '.js', '.less'],
|
||||
packageMains: [],
|
||||
modulesDirectories: [ fromRoot('node_modules'), assets.root ],
|
||||
root: fromRoot(),
|
||||
alias: modules.aliases
|
||||
|
|
|
@ -8,9 +8,10 @@ module.exports = function (kbnServer, server, config) {
|
|||
var status = kbnServer.status.create('optimize');
|
||||
|
||||
server.exposeStaticDir('/bundles/{path*}', bundleDir);
|
||||
if (config.get('optimize.sourceMaps')) {
|
||||
server.exposeStaticDir('/src/{path*}', fromRoot('src'));
|
||||
server.exposeStaticDir('/node_modules/{paths*}', fromRoot('node_modules'));
|
||||
|
||||
if (!config.get('optimize.enable')) {
|
||||
status.disabled();
|
||||
return;
|
||||
}
|
||||
|
||||
return (new Optimizer({
|
||||
|
|
|
@ -6,6 +6,10 @@ let Joi = require('joi');
|
|||
let Promise = require('bluebird');
|
||||
let join = require('path').join;
|
||||
|
||||
const defaultConfigSchema = Joi.object({
|
||||
enabled: Joi.boolean().default(true)
|
||||
}).default();
|
||||
|
||||
module.exports = class Plugin {
|
||||
constructor(kbnServer, path, pkg, opts) {
|
||||
this.kbnServer = kbnServer;
|
||||
|
@ -17,8 +21,9 @@ module.exports = class Plugin {
|
|||
this.requiredIds = opts.require || [];
|
||||
this.version = opts.version || pkg.version;
|
||||
this.publicDir = _.get(opts, 'publicDir', join(path, 'public'));
|
||||
this.externalCondition = opts.initCondition || _.constant(true);
|
||||
this.externalInit = opts.init || _.noop;
|
||||
this.getConfig = opts.config || _.noop;
|
||||
this.getConfigSchema = opts.config || _.noop;
|
||||
this.init = _.once(this.init);
|
||||
}
|
||||
|
||||
|
@ -45,18 +50,28 @@ module.exports = class Plugin {
|
|||
});
|
||||
|
||||
return Promise.try(function () {
|
||||
return self.getConfig(Joi);
|
||||
return self.getConfigSchema(Joi);
|
||||
})
|
||||
.then(function (schema) {
|
||||
if (schema) config.extendSchema(id, schema);
|
||||
else config.extendSchema(id, defaultConfigSchema);
|
||||
})
|
||||
.then(function () {
|
||||
return status.decoratePlugin(self);
|
||||
})
|
||||
.then(function () {
|
||||
return self.kbnServer.uiExports.consumePlugin(self);
|
||||
if (config.get([id, 'enabled'])) {
|
||||
return self.externalCondition(config);
|
||||
}
|
||||
})
|
||||
.then(function () {
|
||||
.then(function (enabled) {
|
||||
if (!enabled) {
|
||||
// Only change the plugin status if it wasn't set by the externalCondition
|
||||
if (self.status.state === 'uninitialized') {
|
||||
self.status.disabled();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let register = function (server, options, next) {
|
||||
Promise.try(self.externalInit, [server, options], self).nodeify(next);
|
||||
|
@ -69,15 +84,14 @@ module.exports = class Plugin {
|
|||
register: register,
|
||||
options: config.has(id) ? config.get(id) : null
|
||||
}, cb);
|
||||
})
|
||||
.then(function () {
|
||||
// Only change the plugin status to green if the
|
||||
// intial status has not been updated
|
||||
if (self.status.state === 'uninitialized') {
|
||||
self.status.green('Ready');
|
||||
}
|
||||
});
|
||||
|
||||
})
|
||||
.then(function () {
|
||||
// Only change the plugin status to green if the
|
||||
// intial status has not been updated
|
||||
if (self.status.state === 'uninitialized') {
|
||||
self.status.green('Ready');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ module.exports = class Plugins extends Array {
|
|||
this.kbnServer = kbnServer;
|
||||
}
|
||||
|
||||
load(path) {
|
||||
new(path) {
|
||||
var self = this;
|
||||
var api = new PluginApi(this.kbnServer, path);
|
||||
|
||||
|
|
|
@ -9,9 +9,14 @@ module.exports = function (kbnServer, server, config) {
|
|||
|
||||
return resolve(kbnServer.pluginPaths)
|
||||
.map(function (path) {
|
||||
return plugins.load(path);
|
||||
return plugins.new(path);
|
||||
})
|
||||
.then(function () {
|
||||
if (!config.get('plugins.initialize')) {
|
||||
server.log(['info'], 'Plugin initialization disabled.');
|
||||
return;
|
||||
}
|
||||
|
||||
var others = _.indexBy(plugins, 'id');
|
||||
|
||||
return Promise.all(plugins.map(function recurse(plugin) {
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"extends": "../../../../../.jshintrc.browser"
|
||||
}
|
|
@ -1,229 +0,0 @@
|
|||
define(function (require) {
|
||||
var angular = require('angular');
|
||||
var $ = require('jquery');
|
||||
var _ = require('lodash');
|
||||
var moment = require('moment');
|
||||
var numeral = require('numeral');
|
||||
require('nvd3_directives');
|
||||
|
||||
// Make sure we don't have to deal with statuses by hand
|
||||
function getStatus(plugin) {
|
||||
var statusMap = {
|
||||
green: {
|
||||
label: 'success',
|
||||
msg: 'Ready',
|
||||
idx: 1
|
||||
},
|
||||
yellow: {
|
||||
label: 'warning',
|
||||
msg: 'S.N.A.F.U.',
|
||||
idx: 2
|
||||
},
|
||||
red: {
|
||||
label: 'danger',
|
||||
msg: 'Danger Will Robinson! Danger!',
|
||||
idx: 3
|
||||
},
|
||||
loading: {
|
||||
label: 'info',
|
||||
msg: 'Loading...',
|
||||
idx: 0
|
||||
}
|
||||
};
|
||||
if (!_.isObject(plugin) || _.isUndefined(plugin)) {
|
||||
plugin = {state: plugin};
|
||||
}
|
||||
return statusMap[plugin.state];
|
||||
}
|
||||
|
||||
function getLabel(plugin) { return getStatus(plugin).label; }
|
||||
|
||||
// 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 = {
|
||||
// show the system status by going through all of the plugins,
|
||||
// and making sure they're green.
|
||||
systemStatus: (function () {
|
||||
// for convenience
|
||||
function getIdx(plugin) { return getStatus(plugin).idx; }
|
||||
|
||||
return function () {
|
||||
var currentStatus = 'loading';
|
||||
var currentIdx = getIdx(currentStatus);
|
||||
|
||||
// FIXME eh, not too thrilled about this.
|
||||
var status = _.reduce($scope.ui.plugins, function (curr, plugin, key) {
|
||||
var pluginIdx = getIdx(plugin);
|
||||
if (pluginIdx > currentIdx) {
|
||||
// set the current status
|
||||
currentStatus = plugin.state;
|
||||
currentIdx = getIdx(plugin);
|
||||
}
|
||||
return currentStatus;
|
||||
}, 'loading');
|
||||
|
||||
// give the ui the label for colors and such
|
||||
return getStatus(status);
|
||||
};
|
||||
}()),
|
||||
charts: {},
|
||||
plugins: []
|
||||
};
|
||||
|
||||
var windowHasFocus = true;
|
||||
angular.element($window).bind({
|
||||
blur: function () { windowHasFocus = false; },
|
||||
focus: function () {
|
||||
windowHasFocus = true;
|
||||
getAppStatus();
|
||||
}
|
||||
});
|
||||
|
||||
function getAppStatus() {
|
||||
// go ahead and get the info you want
|
||||
$http
|
||||
.get('/status/health')
|
||||
.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.plugins = _.mapValues(data.status, function (plugin) {
|
||||
plugin.uiStatus = getLabel(plugin);
|
||||
return plugin;
|
||||
});
|
||||
|
||||
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?');
|
||||
});
|
||||
}
|
||||
|
||||
// Start it all up
|
||||
getAppStatus();
|
||||
});
|
||||
|
||||
return {
|
||||
init: function () {
|
||||
$(function () {
|
||||
angular.bootstrap(window.document, ['nvd3', 'KibanaStatusApp']);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
});
|
|
@ -1,23 +0,0 @@
|
|||
require.config({
|
||||
baseUrl: '/',
|
||||
paths: {
|
||||
angular: '/bower_components/angular/angular',
|
||||
css: '/bower_components/require-css/css',
|
||||
d3: '/bower_components/d3/d3',
|
||||
jquery: '/bower_components/jquery/dist/jquery',
|
||||
lodash: '/utils/lodash-mixins/index',
|
||||
lodash_src: '/bower_components/lodash/lodash',
|
||||
moment: '/bower_components/moment/moment',
|
||||
nvd3: '/bower_components/nvd3/build/nv.d3',
|
||||
nvd3_directives: '/bower_components/angular-nvd3/dist/angular-nvd3',
|
||||
numeral: '/bower_components/numeral/numeral'
|
||||
},
|
||||
shim: {
|
||||
angular: {
|
||||
deps: ['jquery'],
|
||||
exports: 'angular'
|
||||
},
|
||||
nvd3: ['css!bower_components/nvd3/build/nv.d3.css', 'd3'],
|
||||
nvd3_directives: ['angular', 'nvd3']
|
||||
}
|
||||
});
|
|
@ -1,20 +1,33 @@
|
|||
var Status = require('./Status');
|
||||
'use strict';
|
||||
|
||||
function ServerStatus(server) {
|
||||
this.server = server;
|
||||
this.each = {};
|
||||
}
|
||||
let _ = require('lodash');
|
||||
|
||||
ServerStatus.prototype.create = function (name) {
|
||||
return (this.each[name] = new Status(name, this.server));
|
||||
let Status = require('./Status');
|
||||
|
||||
module.exports = class ServerStatus {
|
||||
constructor(server) {
|
||||
this.server = server;
|
||||
this._created = {};
|
||||
}
|
||||
|
||||
create(name) {
|
||||
return (this._created[name] = new Status(name, this.server));
|
||||
}
|
||||
|
||||
each(fn) {
|
||||
let self = this;
|
||||
_.forOwn(self._created, function (status, i, list) {
|
||||
if (status.state !== 'disabled') {
|
||||
fn.call(self, status, i, list);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
decoratePlugin(plugin) {
|
||||
plugin.status = this.create(plugin.id);
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return this._created;
|
||||
}
|
||||
};
|
||||
|
||||
ServerStatus.prototype.decoratePlugin = function (plugin) {
|
||||
plugin.status = this.create(plugin.id);
|
||||
};
|
||||
|
||||
ServerStatus.prototype.toJSON = function () {
|
||||
return this.each;
|
||||
};
|
||||
|
||||
module.exports = ServerStatus;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
var _ = require('lodash');
|
||||
var util = require('util');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
|
||||
|
@ -11,7 +12,7 @@ function Status(name, server) {
|
|||
|
||||
this.on('change', function (current, previous) {
|
||||
server.log(['plugins', name, 'info'], {
|
||||
tmpl: 'Change status from <%= prev %> to <%= cur %> - <%= curMsg %>',
|
||||
tmpl: 'Status changed from <%= prev %> to <%= cur %><% curMsg && print(` - ${curMsg}`) %>',
|
||||
name: name,
|
||||
prev: previous.state,
|
||||
cur: current.state,
|
||||
|
@ -23,6 +24,13 @@ function Status(name, server) {
|
|||
Status.prototype.green = makeStatusUpdateFn('green');
|
||||
Status.prototype.yellow = makeStatusUpdateFn('yellow');
|
||||
Status.prototype.red = makeStatusUpdateFn('red');
|
||||
|
||||
Status.prototype.disabled = _.wrap(makeStatusUpdateFn('disabled'), function (update, msg) {
|
||||
var ret = update.call(this, msg);
|
||||
this.green = this.yellow = this.red = _.noop;
|
||||
return ret;
|
||||
});
|
||||
|
||||
function makeStatusUpdateFn(color) {
|
||||
return function (message) {
|
||||
var previous = {
|
||||
|
|
|
@ -10,8 +10,6 @@ module.exports = function (kbnServer) {
|
|||
kbnServer.status = new ServerStatus(kbnServer.server);
|
||||
kbnServer.metrics = new Samples(60);
|
||||
|
||||
server.exposeStaticDir('/status/{path*}', join(__dirname, 'public'));
|
||||
|
||||
server.plugins.good.monitor.on('ops', function (event) {
|
||||
var port = config.get('server.port');
|
||||
|
||||
|
|
|
@ -5,12 +5,11 @@ var join = require('path').join;
|
|||
var defAutoload = require('./autoload');
|
||||
|
||||
class UiApp {
|
||||
constructor(uiExports, plugin, spec) {
|
||||
constructor(uiExports, spec) {
|
||||
this.uiExports = uiExports;
|
||||
this.plugin = plugin || null;
|
||||
this.spec = spec || {};
|
||||
|
||||
this.id = this.spec.id || _.get(this, 'plugin.id');
|
||||
this.id = this.spec.id;
|
||||
if (!this.id) {
|
||||
throw new Error('Every app must specify it\'s id');
|
||||
}
|
||||
|
|
|
@ -19,12 +19,12 @@ module.exports = class UiApps extends Array {
|
|||
|
||||
}
|
||||
|
||||
new(plugin, spec) {
|
||||
new(spec) {
|
||||
if (this.hidden && spec.hidden) {
|
||||
return this.hidden.new(plugin, spec);
|
||||
return this.hidden.new(spec);
|
||||
}
|
||||
|
||||
let app = new UiApp(this.uiExports, plugin, spec);
|
||||
let app = new UiApp(this.uiExports, spec);
|
||||
|
||||
if (_.includes(this.claimedIds, app.id)) {
|
||||
throw new Error('Unable to create two apps with the id ' + app.id + '.');
|
||||
|
@ -34,6 +34,7 @@ module.exports = class UiApps extends Array {
|
|||
|
||||
this._byId = null;
|
||||
this.push(app);
|
||||
return app;
|
||||
}
|
||||
|
||||
get byId() {
|
||||
|
|
|
@ -9,9 +9,10 @@ class UiExports {
|
|||
constructor(kbnServer) {
|
||||
this.kbnServer = kbnServer;
|
||||
this.apps = new UiApps(this);
|
||||
this.appCount = 0;
|
||||
this.aliases = {};
|
||||
this.exportConsumer = _.memoize(this.exportConsumer);
|
||||
|
||||
kbnServer.plugins.forEach(_.bindKey(this, 'consumePlugin'));
|
||||
}
|
||||
|
||||
consumePlugin(plugin) {
|
||||
|
@ -35,7 +36,8 @@ class UiExports {
|
|||
switch (type) {
|
||||
case 'app':
|
||||
return function (plugin, spec) {
|
||||
self.apps.new(plugin, spec);
|
||||
spec = _.defaults({}, spec, { id: plugin.id });
|
||||
plugin.app = self.apps.new(spec);
|
||||
};
|
||||
|
||||
case 'visTypes':
|
||||
|
|
|
@ -42,7 +42,8 @@ module.exports = function (grunt) {
|
|||
'amdefine@0.1.1': ['BSD-3-Clause', 'MIT'],
|
||||
'flatten@0.0.1': ['MIT'],
|
||||
'color-name@1.0.0': ['UNLICENSE'],
|
||||
'css-color-names@0.0.1': ['MIT']
|
||||
'css-color-names@0.0.1': ['MIT'],
|
||||
'inherits@1.0.0': ['ISC']
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
34
tasks/dev.js
34
tasks/dev.js
|
@ -1,34 +0,0 @@
|
|||
module.exports = function (grunt) {
|
||||
var _ = require('lodash');
|
||||
|
||||
grunt.registerTask('dev', function () {
|
||||
var tasks = [
|
||||
'less:dev',
|
||||
'jade',
|
||||
'esvm:dev',
|
||||
'maybeStartKibana',
|
||||
'watch'
|
||||
];
|
||||
|
||||
if (!grunt.option('with-es')) {
|
||||
_.pull(tasks, 'esvm:dev');
|
||||
}
|
||||
|
||||
grunt.task.run(tasks);
|
||||
});
|
||||
|
||||
grunt.registerTask('devServer', function (keepalive) {
|
||||
var port = grunt.option('port');
|
||||
var quiet = !(grunt.option('debug') || grunt.option('verbose'));
|
||||
|
||||
require('../src/devServer').run(port, quiet)
|
||||
.then(function (server) {
|
||||
grunt.log.ok('Server started: ' + server.info.uri);
|
||||
if (keepalive) {
|
||||
// return a never resolving promise
|
||||
return new Promise(_.noop);
|
||||
}
|
||||
})
|
||||
.nodeify(this.async());
|
||||
});
|
||||
};
|
|
@ -36,17 +36,19 @@ module.exports = function (grunt) {
|
|||
var licenseStats = _.map(result, processPackage);
|
||||
var invalidLicenses = _.filter(licenseStats, function (pkg) { return !pkg.valid;});
|
||||
|
||||
if (grunt.option('only-invalid')) {
|
||||
grunt.log.debug(JSON.stringify(invalidLicenses, null, 2));
|
||||
} else {
|
||||
if (!grunt.option('only-invalid')) {
|
||||
grunt.log.debug(JSON.stringify(licenseStats, null, 2));
|
||||
}
|
||||
|
||||
|
||||
if (invalidLicenses.length) {
|
||||
grunt.fail.warn('Non-confirming licenses: ' + _.pluck(invalidLicenses, 'name').join(', ') +
|
||||
'. Use --only-invalid for details.', invalidLicenses.length);
|
||||
grunt.log.debug(JSON.stringify(invalidLicenses, null, 2));
|
||||
grunt.fail.warn(
|
||||
'Non-confirming licenses: ' + _.pluck(invalidLicenses, 'name').join(', '),
|
||||
invalidLicenses.length
|
||||
);
|
||||
}
|
||||
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
module.exports = function (grunt) {
|
||||
var join = require('path').join;
|
||||
|
||||
grunt.registerTask('require_css_deps:copy', function () {
|
||||
[
|
||||
'css-builder.js',
|
||||
'normalize.js'
|
||||
].forEach(function (dep) {
|
||||
grunt.file.copy(
|
||||
join(grunt.config.get('bowerComponentsDir'), 'require-css', dep),
|
||||
join(grunt.config.get('build'), 'src', dep)
|
||||
);
|
||||
});
|
||||
});
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue