mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
Creating the basics for the new server... tests coming
This commit is contained in:
parent
dd9ff1852f
commit
bba50454f2
26 changed files with 903 additions and 1 deletions
10
package.json
10
package.json
|
@ -30,7 +30,7 @@
|
|||
],
|
||||
"scripts": {
|
||||
"test": "grunt test",
|
||||
"start": "node ./src/server/bin/kibana.js",
|
||||
"start": "node ./src/hapi/index.js",
|
||||
"server": "node ./src/server/bin/kibana.js",
|
||||
"precommit": "grunt lintStagedFiles"
|
||||
},
|
||||
|
@ -50,12 +50,20 @@
|
|||
"elasticsearch": "^3.1.1",
|
||||
"express": "~4.10.6",
|
||||
"glob": "^4.3.2",
|
||||
"good": "^5.1.2",
|
||||
"good-console": "^4.1.0",
|
||||
"good-file": "^4.0.2",
|
||||
"good-reporter": "^3.0.1",
|
||||
"hapi": "^8.4.0",
|
||||
"http-auth": "^2.2.5",
|
||||
"jade": "~1.8.2",
|
||||
"js-yaml": "^3.2.5",
|
||||
"json-stringify-safe": "^5.0.0",
|
||||
"less-middleware": "1.0.x",
|
||||
"lodash": "^2.4.1",
|
||||
"moment": "^2.9.0",
|
||||
"morgan": "~1.5.1",
|
||||
"numeral": "^1.5.3",
|
||||
"request": "^2.40.0",
|
||||
"requirefrom": "^0.2.0",
|
||||
"semver": "^4.2.0",
|
||||
|
|
53
src/hapi/config/index.js
Normal file
53
src/hapi/config/index.js
Normal file
|
@ -0,0 +1,53 @@
|
|||
var _ = require('lodash');
|
||||
var fs = require('fs');
|
||||
var yaml = require('js-yaml');
|
||||
var path = require('path');
|
||||
var listPlugins = require('../lib/list_plugins');
|
||||
var configPath = process.env.CONFIG_PATH || path.join(__dirname, 'kibana.yml');
|
||||
var kibana = yaml.safeLoad(fs.readFileSync(configPath, 'utf8'));
|
||||
var env = process.env.NODE_ENV || 'development';
|
||||
|
||||
function checkPath(path) {
|
||||
try {
|
||||
fs.statSync(path);
|
||||
return true;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the local public folder is present. This means we are running in
|
||||
// the NPM module. If it's not there then we are running in the git root.
|
||||
var public_folder = path.resolve(__dirname, '..', 'public');
|
||||
if (!checkPath(public_folder)) public_folder = path.resolve(__dirname, '..', '..', 'kibana');
|
||||
|
||||
// Check to see if htpasswd file exists in the root directory otherwise set it to false
|
||||
var htpasswdPath = path.resolve(__dirname, '..', '..', '.htpasswd');
|
||||
if (!checkPath(htpasswdPath)) htpasswdPath = path.resolve(__dirname, '..', '..', '..', '..', '.htpasswd');
|
||||
if (!checkPath(htpasswdPath)) htpasswdPath = false;
|
||||
|
||||
var packagePath = path.resolve(__dirname, '..', 'package.json');
|
||||
try {
|
||||
fs.statSync(packagePath);
|
||||
} catch (err) {
|
||||
packagePath = path.resolve(__dirname, '..', '..', '..', 'package.json');
|
||||
}
|
||||
|
||||
var config = module.exports = {
|
||||
port : kibana.port || 5601,
|
||||
host : kibana.host || '0.0.0.0',
|
||||
elasticsearch : kibana.elasticsearch_url || 'http : //localhost : 9200',
|
||||
root : path.normalize(path.join(__dirname, '..')),
|
||||
quiet : false,
|
||||
public_folder : public_folder,
|
||||
external_plugins_folder : process.env.PLUGINS_FOLDER || null,
|
||||
bundled_plugins_folder : path.resolve(public_folder, 'plugins'),
|
||||
kibana : kibana,
|
||||
package : require(packagePath),
|
||||
htpasswd : htpasswdPath,
|
||||
buildNum : '@@buildNum',
|
||||
maxSockets : kibana.maxSockets || Infinity,
|
||||
log_file : kibana.log_file || null
|
||||
};
|
||||
|
||||
config.plugins = listPlugins(config);
|
54
src/hapi/config/kibana.yml
Normal file
54
src/hapi/config/kibana.yml
Normal file
|
@ -0,0 +1,54 @@
|
|||
# Kibana is served by a back end server. This controls which port to use.
|
||||
port: 5601
|
||||
|
||||
# The host to bind the server to.
|
||||
host: "0.0.0.0"
|
||||
|
||||
# The Elasticsearch instance to use for all your queries.
|
||||
elasticsearch_url: "http://localhost:9200"
|
||||
|
||||
# preserve_elasticsearch_host true will send the hostname specified in `elasticsearch`. If you set it to false,
|
||||
# then the host you use to connect to *this* Kibana instance will be sent.
|
||||
elasticsearch_preserve_host: true
|
||||
|
||||
# Kibana uses an index in Elasticsearch to store saved searches, visualizations
|
||||
# and dashboards. It will create a new index if it doesn't already exist.
|
||||
kibana_index: ".kibana"
|
||||
|
||||
# If your Elasticsearch is protected with basic auth, this is the user credentials
|
||||
# used by the Kibana server to perform maintence on the kibana_index at statup. Your Kibana
|
||||
# users will still need to authenticate with Elasticsearch (which is proxied thorugh
|
||||
# the Kibana server)
|
||||
# kibana_elasticsearch_username: user
|
||||
# kibana_elasticsearch_password: pass
|
||||
|
||||
|
||||
# The default application to load.
|
||||
default_app_id: "discover"
|
||||
|
||||
# Time in milliseconds to wait for responses from the back end or elasticsearch.
|
||||
# This must be > 0
|
||||
request_timeout: 300000
|
||||
|
||||
# Time in milliseconds for Elasticsearch to wait for responses from shards.
|
||||
# Set to 0 to disable.
|
||||
shard_timeout: 0
|
||||
|
||||
# Set to false to have a complete disregard for the validity of the SSL
|
||||
# certificate.
|
||||
verify_ssl: true
|
||||
|
||||
# If you need to provide a CA certificate for your Elasticsarech instance, put
|
||||
# the path of the pem file here.
|
||||
# ca: /path/to/your/CA.pem
|
||||
|
||||
# SSL for outgoing requests from the Kibana Server (PEM formatted)
|
||||
# ssl_key_file: /path/to/your/server.key
|
||||
# ssl_cert_file: /path/to/your/server.crt
|
||||
|
||||
# Set the path to where you would like the process id file to be created.
|
||||
# pid_file: /var/run/kibana.pid
|
||||
|
||||
# If you would like to send the log output to a file you can set the path below.
|
||||
# This will also turn off the STDOUT log output.
|
||||
# log_file: ./kibana.log
|
9
src/hapi/index.js
Normal file
9
src/hapi/index.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
module.exports.extendHapi = require('./lib/extend_hapi');
|
||||
module.exports.Plugin = require('./lib/plugin');
|
||||
module.exports.start = require('./lib/start');
|
||||
|
||||
if (require.main === module) {
|
||||
module.exports.start().catch(function (err) {
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
18
src/hapi/lib/check_dependencies.js
Normal file
18
src/hapi/lib/check_dependencies.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
var _ = require('lodash');
|
||||
var checkDependencies = module.exports = function (name, deps, callStack) {
|
||||
if (!deps[name]) throw new Error('Missing dependency: ' + name);
|
||||
callStack = callStack || [];
|
||||
if (_.contains(callStack, name)) {
|
||||
callStack.push(name);
|
||||
throw new Error('Circular dependency: ' + callStack.join(' -> '));
|
||||
}
|
||||
for (var i = 0; i < deps[name].length; i++) {
|
||||
var task = deps[name][i];
|
||||
if (!deps[task]) throw new Error('Missing dependency: ' + task);
|
||||
if (deps[task].length) {
|
||||
checkDependencies(task, deps, callStack.concat(name));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
3
src/hapi/lib/config.js
Normal file
3
src/hapi/lib/config.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
module.exports = function () {
|
||||
return require('../config');
|
||||
};
|
4
src/hapi/lib/extend_hapi.js
Normal file
4
src/hapi/lib/extend_hapi.js
Normal file
|
@ -0,0 +1,4 @@
|
|||
module.exports = function (server) {
|
||||
server.decorate('server', 'config', require('./config'));
|
||||
server.decorate('server', 'loadKibanaPlugins', require('./load_kibana_plugins'));
|
||||
};
|
128
src/hapi/lib/good_reporters/_event_to_json.js
Normal file
128
src/hapi/lib/good_reporters/_event_to_json.js
Normal file
|
@ -0,0 +1,128 @@
|
|||
var moment = require('moment');
|
||||
var _ = require('lodash');
|
||||
var env = process.env.NODE_ENV || 'development';
|
||||
var numeral = require('numeral');
|
||||
var ansicolors = require('ansicolors');
|
||||
var stringify = require('json-stringify-safe');
|
||||
var querystring = require('querystring');
|
||||
|
||||
function serializeError(err) {
|
||||
return {
|
||||
message: err.message,
|
||||
name: err.name,
|
||||
stack: err.stack,
|
||||
code: err.code,
|
||||
signal: err.signal
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
var levelColor = function (code) {
|
||||
if (code < 299) {
|
||||
return ansicolors.green(code);
|
||||
}
|
||||
if (code < 399) {
|
||||
return ansicolors.yellow(code);
|
||||
}
|
||||
if (code < 499) {
|
||||
return ansicolors.magenta(code);
|
||||
}
|
||||
return ansicolors.red(code);
|
||||
};
|
||||
|
||||
function lookup(name) {
|
||||
switch (name) {
|
||||
case 'error':
|
||||
return 'error';
|
||||
default:
|
||||
return 'info';
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = function (name, event) {
|
||||
var data = {
|
||||
'@timestamp': moment.utc(event.timestamp).format(),
|
||||
level: lookup(event),
|
||||
node_env: env,
|
||||
tags: event.tags,
|
||||
pid: event.pid
|
||||
};
|
||||
if (name === 'response') {
|
||||
_.defaults(data, _.pick(event, [
|
||||
'method',
|
||||
'statusCode'
|
||||
]));
|
||||
|
||||
data.req = {
|
||||
url: event.path,
|
||||
method: event.method,
|
||||
headers: event.headers,
|
||||
remoteAddress: event.source.remoteAddress,
|
||||
userAgent: event.source.remoteAddress,
|
||||
referer: event.source.referer
|
||||
};
|
||||
|
||||
var contentLength = 0;
|
||||
if (typeof event.responsePayload === 'object') {
|
||||
contentLength = stringify(event.responsePayload).length;
|
||||
} else {
|
||||
contentLength = event.responsePayload.toString().length;
|
||||
}
|
||||
|
||||
data.res = {
|
||||
statusCode: event.statusCode,
|
||||
responseTime: event.responseTime,
|
||||
contentLength: contentLength
|
||||
};
|
||||
|
||||
var query = querystring.stringify(event.query);
|
||||
if (query) data.req.url += '?' + query;
|
||||
|
||||
|
||||
data.message = data.req.method.toUpperCase() + ' ';
|
||||
data.message += data.req.url;
|
||||
data.message += ' ';
|
||||
data.message += levelColor(data.res.statusCode);
|
||||
data.message += ' ';
|
||||
data.message += ansicolors.brightBlack(data.res.responseTime + 'ms');
|
||||
data.message += ansicolors.brightBlack(' - ' + numeral(contentLength).format('0.0b'));
|
||||
}
|
||||
else if (name === 'ops') {
|
||||
_.defaults(data, _.pick(event, [
|
||||
'pid',
|
||||
'os',
|
||||
'proc',
|
||||
'load'
|
||||
]));
|
||||
data.message = ansicolors.brightBlack('memory: ');
|
||||
data.message += numeral(data.proc.mem.heapUsed).format('0.0b');
|
||||
data.message += ' ';
|
||||
data.message += ansicolors.brightBlack('uptime: ');
|
||||
data.message += numeral(data.proc.uptime).format('00:00:00');
|
||||
data.message += ' ';
|
||||
data.message += ansicolors.brightBlack('load: [');
|
||||
data.message += data.os.load.map(function (val) {
|
||||
return numeral(val).format('0.00');
|
||||
}).join(' ');
|
||||
data.message += ansicolors.brightBlack(']');
|
||||
data.message += ' ';
|
||||
data.message += ansicolors.brightBlack('delay: ');
|
||||
data.message += numeral(data.proc.delay).format('0.000');
|
||||
}
|
||||
else if (name === 'error') {
|
||||
data.level = 'error';
|
||||
data.message = event.error.message;
|
||||
data.error = serializeError(event.error);
|
||||
data.url = event.url;
|
||||
}
|
||||
else {
|
||||
if (event.data instanceof Error) {
|
||||
data.level = _.contains(event.tags, 'fatal') ? 'fatal' : 'error';
|
||||
data.message = event.data.message;
|
||||
data.error = serializeError(event.data);
|
||||
} else {
|
||||
data.message = event.data;
|
||||
}
|
||||
}
|
||||
return data;
|
||||
};
|
58
src/hapi/lib/good_reporters/console.js
Normal file
58
src/hapi/lib/good_reporters/console.js
Normal file
|
@ -0,0 +1,58 @@
|
|||
var ansicolors = require('ansicolors');
|
||||
var eventToJson = require('./_event_to_json');
|
||||
var GoodReporter = require('good-reporter');
|
||||
var util = require('util');
|
||||
var moment = require('moment');
|
||||
var stringify = require('json-stringify-safe');
|
||||
var querystring = require('querystring');
|
||||
var numeral = require('numeral');
|
||||
|
||||
var colors = {
|
||||
log: 'blue',
|
||||
req: 'green',
|
||||
res: 'green',
|
||||
ops: 'cyan',
|
||||
err: 'red',
|
||||
info: 'blue',
|
||||
error: 'red',
|
||||
fatal: 'magenta'
|
||||
};
|
||||
|
||||
function stripColors(string) {
|
||||
return string.replace(/\u001b[^m]+m/g, '');
|
||||
}
|
||||
|
||||
var Console = module.exports = function (events, options) {
|
||||
this._json = options.json;
|
||||
GoodReporter.call(this, events);
|
||||
};
|
||||
util.inherits(Console, GoodReporter);
|
||||
|
||||
Console.prototype.stop = function () { };
|
||||
|
||||
Console.prototype._report = function (name, data) {
|
||||
data = eventToJson(name, data);
|
||||
var nameCrayon = ansicolors[colors[name.substr(0, 3)]];
|
||||
var typeCrayon = ansicolors[colors[data.level]];
|
||||
var output;
|
||||
if (this._json) {
|
||||
data.message = stripColors(data.message);
|
||||
output = stringify(data);
|
||||
} else {
|
||||
output = nameCrayon(name.substr(0, 3));
|
||||
output += ': ';
|
||||
output += typeCrayon(data.level.toUpperCase());
|
||||
output += ' ';
|
||||
output += '[ ';
|
||||
output += ansicolors.brightBlack(moment(data.timestamp).format());
|
||||
output += ' ] ';
|
||||
|
||||
if (data.error) {
|
||||
output += ansicolors.red(data.error.stack);
|
||||
} else {
|
||||
output += data.message;
|
||||
}
|
||||
|
||||
}
|
||||
console.log(output);
|
||||
};
|
0
src/hapi/lib/good_reporters/file.js
Normal file
0
src/hapi/lib/good_reporters/file.js
Normal file
19
src/hapi/lib/list_plugins.js
Normal file
19
src/hapi/lib/list_plugins.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
var _ = require('lodash');
|
||||
var glob = require('glob');
|
||||
var path = require('path');
|
||||
|
||||
var plugins = function (dir) {
|
||||
if (!dir) return [];
|
||||
var files = glob.sync(path.join(dir, '*', 'index.js')) || [];
|
||||
return files.map(function (file) {
|
||||
return file.replace(dir, 'plugins').replace(/\.js$/, '');
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = function (config) {
|
||||
var bundled_plugin_ids = config.kibana.bundled_plugin_ids || [];
|
||||
var bundled_plugins = plugins(config.bundled_plugins_folder);
|
||||
var external_plugins = plugins(config.external_plugins_folder);
|
||||
return bundled_plugin_ids.concat(bundled_plugins, external_plugins);
|
||||
};
|
||||
|
11
src/hapi/lib/load_kibana_plugins.js
Normal file
11
src/hapi/lib/load_kibana_plugins.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
var _ = require('lodash');
|
||||
var Promise = require('bluebird');
|
||||
var registerPlugins = require('./register_plugins');
|
||||
var requirePlugins = require('./require_plugins');
|
||||
var setupLogging = require('./setup_logging');
|
||||
module.exports = function (externalPlugins) {
|
||||
var plugins = requirePlugins().concat(externalPlugins);
|
||||
return setupLogging(this).then(function (server) {
|
||||
registerPlugins(server, plugins);
|
||||
});
|
||||
};
|
13
src/hapi/lib/plugin.js
Normal file
13
src/hapi/lib/plugin.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
var _ = require('lodash');
|
||||
var Promise = require('bluebird');
|
||||
function Plugin(options) {
|
||||
options = _.defaults(options, {
|
||||
require: [],
|
||||
init: function (server, options) {
|
||||
return Promise.reject(new Error('You must override the init function for plugins'));
|
||||
}
|
||||
});
|
||||
_.assign(this, options);
|
||||
}
|
||||
|
||||
module.exports = Plugin;
|
71
src/hapi/lib/register_plugins.js
Normal file
71
src/hapi/lib/register_plugins.js
Normal file
|
@ -0,0 +1,71 @@
|
|||
var _ = require('lodash');
|
||||
var Promise = require('bluebird');
|
||||
var checkDependencies = require('./check_dependencies');
|
||||
|
||||
function checkForCircularDependency(tasks) {
|
||||
var deps = {};
|
||||
tasks.forEach(function (task) {
|
||||
deps[task.name] = [];
|
||||
if (task.require) deps[task.name] = task.require;
|
||||
});
|
||||
return _(deps).keys().map(function (task) {
|
||||
return checkDependencies(task, deps);
|
||||
}).every(function (result) {
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = function (server, plugins) {
|
||||
var total = plugins.length;
|
||||
var results = {};
|
||||
var running = {};
|
||||
var finished = false;
|
||||
var todo = plugins.concat();
|
||||
|
||||
function allDone(tasks) {
|
||||
var done = _.keys(results);
|
||||
return tasks.every(function (dep) {
|
||||
return _.contains(done, dep);
|
||||
});
|
||||
}
|
||||
|
||||
function registerPlugin(plugin) {
|
||||
var config = server.config();
|
||||
return new Promise(function (resolve, reject) {
|
||||
var register = function (server, options, next) {
|
||||
Promise.try(plugin.init, [server, options]).nodeify(next);
|
||||
};
|
||||
register.attributes = { name: plugin.name };
|
||||
var options = config[plugin.name] || {};
|
||||
server.register({ register: register, options: options }, function (err) {
|
||||
if (err) return reject(err);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
// Check to see if we have a circular dependency
|
||||
if (checkForCircularDependency(plugins)) {
|
||||
(function runPending() {
|
||||
plugins.forEach(function (plugin) {
|
||||
// The running tasks are the same length as the results then we are
|
||||
// done with all the plugin initalization tasks
|
||||
if (_.keys(results).length === total) return resolve(results);
|
||||
// If the current plugin is done or running the continue to the next one
|
||||
if (results[plugin.name] || running[plugin.name]) return;
|
||||
// If the current plugin doesn't have dependencies or all the dependencies
|
||||
// are fullfilled then try running the plugin.
|
||||
if (!plugin.require || (plugin.require && allDone(plugin.require))) {
|
||||
running[plugin.name] = true;
|
||||
registerPlugin(plugin)
|
||||
.then(function () {
|
||||
results[plugin.name] = true;
|
||||
runPending();
|
||||
}).catch(reject);
|
||||
}
|
||||
});
|
||||
})();
|
||||
}
|
||||
});
|
||||
};
|
17
src/hapi/lib/require_plugins.js
Normal file
17
src/hapi/lib/require_plugins.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
var path = require('path');
|
||||
var join = path.join;
|
||||
var glob = require('glob');
|
||||
var Promise = require('bluebird');
|
||||
|
||||
module.exports = function (globPath) {
|
||||
globPath = globPath || join( __dirname, '..', 'plugins', '*', 'index.js');
|
||||
return glob.sync(globPath).map(function (file) {
|
||||
var module = require(file);
|
||||
var regex = new RegExp('([^' + path.sep + ']+)' + path.sep + 'index.js');
|
||||
var matches = file.match(regex);
|
||||
if (!module.name && matches) {
|
||||
module.name = matches[1];
|
||||
}
|
||||
return module;
|
||||
});
|
||||
};
|
6
src/hapi/lib/run_setup_tasks.js
Normal file
6
src/hapi/lib/run_setup_tasks.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
var Promise = require('bluebird');
|
||||
module.exports = function (server, tasks) {
|
||||
return Promise.each(tasks, function (task) {
|
||||
return task(server);
|
||||
});
|
||||
};
|
29
src/hapi/lib/setup_logging.js
Normal file
29
src/hapi/lib/setup_logging.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
var Promise = require('bluebird');
|
||||
var good = require('good');
|
||||
var path = require('path');
|
||||
var join = path.join;
|
||||
var Console = require('./good_reporters/console');
|
||||
|
||||
var reporters = [
|
||||
{
|
||||
reporter: Console,
|
||||
args: [{ ops: '*', log: '*', response: '*', error: '*' }, { json: false }]
|
||||
}
|
||||
];
|
||||
|
||||
module.exports = function (server) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
server.register({
|
||||
register: good,
|
||||
options: {
|
||||
opsInterval: 5000,
|
||||
logRequestHeaders: true,
|
||||
logResponsePayload: true,
|
||||
reporters: reporters
|
||||
}
|
||||
}, function (err) {
|
||||
if (err) return reject(err);
|
||||
resolve(server);
|
||||
});
|
||||
});
|
||||
};
|
46
src/hapi/lib/start.js
Normal file
46
src/hapi/lib/start.js
Normal file
|
@ -0,0 +1,46 @@
|
|||
var Promise = require('bluebird');
|
||||
var Hapi = require('hapi');
|
||||
var requirePlugins = require('./require_plugins');
|
||||
var validatePlugin = require('./validate_plugin');
|
||||
var extendHapi = require('./extend_hapi');
|
||||
|
||||
module.exports = function (plugins) {
|
||||
// Plugin authors can use this to add plugins durring development
|
||||
plugins = plugins || [];
|
||||
|
||||
if (plugins.length && !plugins.every(validatePlugin)) {
|
||||
return Promise.reject(new Error('Plugins must have a name attribute.'));
|
||||
}
|
||||
|
||||
// Initalize the Hapi server
|
||||
var server = new Hapi.Server();
|
||||
|
||||
// Extend Hapi with Kibana
|
||||
extendHapi(server);
|
||||
|
||||
// Create a new connection
|
||||
server.connection({ host: server.config().host, port: server.config().port });
|
||||
|
||||
// Load external plugins
|
||||
var externalPlugins = [];
|
||||
if (server.config().external_plugins_folder) {
|
||||
externalPlugins = requirePlugins(server.config().external_plugins_folder);
|
||||
}
|
||||
|
||||
// Load the plugins
|
||||
return server.loadKibanaPlugins(externalPlugins.concat(plugins))
|
||||
.then(function () {
|
||||
// Start the server
|
||||
return new Promise(function (resolve, reject) {
|
||||
server.start(function (err) {
|
||||
if (err) return reject(err);
|
||||
server.log('server', 'Server running at ' + server.info.uri);
|
||||
resolve(server);
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(function (err) {
|
||||
server.log('fatal', err);
|
||||
return Promise.reject(err);
|
||||
});
|
||||
};
|
3
src/hapi/lib/validate_plugin.js
Normal file
3
src/hapi/lib/validate_plugin.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
module.exports = function (plugin) {
|
||||
return !!plugin.name;
|
||||
};
|
25
src/hapi/plugins/config/index.js
Normal file
25
src/hapi/plugins/config/index.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
var _ = require('lodash');
|
||||
var Promise = require('bluebird');
|
||||
var kibana = require('../../');
|
||||
|
||||
module.exports = new kibana.Plugin({
|
||||
init: function (server, options) {
|
||||
|
||||
server.route({
|
||||
method: 'GET',
|
||||
path: '/config',
|
||||
handler: function (request, reply) {
|
||||
var config = server.config();
|
||||
var keys = [
|
||||
'kibana_index',
|
||||
'default_app_id',
|
||||
'shard_timeout'
|
||||
];
|
||||
var data = _.pick(config.kibana, keys);
|
||||
data.plugins = config.plugins;
|
||||
reply(data);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
});
|
53
src/hapi/plugins/elasticsearch/index.js
Normal file
53
src/hapi/plugins/elasticsearch/index.js
Normal file
|
@ -0,0 +1,53 @@
|
|||
var url = require('url');
|
||||
var http = require('http');
|
||||
var fs = require('fs');
|
||||
var querystring = require('querystring');
|
||||
var kibana = require('../../');
|
||||
|
||||
module.exports = new kibana.Plugin({
|
||||
|
||||
require: ['status'],
|
||||
|
||||
init: function (server, options) {
|
||||
var config = server.config();
|
||||
var target = url.parse(config.elasticsearch);
|
||||
|
||||
var agentOptions = {
|
||||
rejectUnauthorized: config.kibana.verify_ssl
|
||||
};
|
||||
|
||||
var customCA;
|
||||
if (/^https/.test(target.protocol) && config.kibana.ca) {
|
||||
customCA = fs.readFileSync(config.kibana.ca, 'utf8');
|
||||
agentOptions.ca = [customCA];
|
||||
}
|
||||
|
||||
// Add client certificate and key if required by elasticsearch
|
||||
if (/^https/.test(target.protocol) &&
|
||||
config.kibana.kibana_elasticsearch_client_crt &&
|
||||
config.kibana.kibana_elasticsearch_client_key) {
|
||||
agentOptions.crt = fs.readFileSync(config.kibana.kibana_elasticsearch_client_crt, 'utf8');
|
||||
agentOptions.key = fs.readFileSync(config.kibana.kibana_elasticsearch_client_key, 'utf8');
|
||||
}
|
||||
|
||||
server.route({
|
||||
method: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
|
||||
path: '/elasticsearch/{path*}',
|
||||
handler: {
|
||||
proxy: {
|
||||
mapUri: function (request, callback) {
|
||||
var url = config.elasticsearch;
|
||||
if (!/\/$/.test(url)) url += '/';
|
||||
if (request.params.path) url += request.params.path;
|
||||
var query = querystring.stringify(request.query);
|
||||
if (query) url += '?' + query;
|
||||
callback(null, url);
|
||||
},
|
||||
passThrough: true,
|
||||
agent: new http.Agent(agentOptions)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
});
|
16
src/hapi/plugins/static/index.js
Normal file
16
src/hapi/plugins/static/index.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
var kibana = require('../../');
|
||||
|
||||
module.exports = new kibana.Plugin({
|
||||
init: function (server, options) {
|
||||
var config = server.config();
|
||||
server.route({
|
||||
method: 'GET',
|
||||
path: '/{param*}',
|
||||
handler: {
|
||||
directory: {
|
||||
path: config.public_folder
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
77
src/hapi/plugins/status/index.js
Normal file
77
src/hapi/plugins/status/index.js
Normal file
|
@ -0,0 +1,77 @@
|
|||
var join = require('path').join;
|
||||
var kibana = require('../../');
|
||||
function Series(size) {
|
||||
this.size = size;
|
||||
this.data = [];
|
||||
}
|
||||
Series.prototype.push = function (value) {
|
||||
this.data.push([Date.now(), value]);
|
||||
if (this.data.length > this.size) this.data.shift();
|
||||
};
|
||||
Series.prototype.toJSON = function () {
|
||||
return this.data;
|
||||
};
|
||||
module.exports = new kibana.Plugin({
|
||||
|
||||
init: function (server, options) {
|
||||
|
||||
var config = server.config();
|
||||
|
||||
var fiveMinuteData = {
|
||||
rss: new Series(60),
|
||||
heapTotal: new Series(60),
|
||||
heapUsed: new Series(60),
|
||||
load: new Series(60),
|
||||
delay: new Series(60),
|
||||
concurrency: new Series(60),
|
||||
responseTimeAvg: new Series(60),
|
||||
responseTimeMax: new Series(60),
|
||||
requests: new Series(60),
|
||||
};
|
||||
|
||||
server.plugins.good.monitor.on('ops', function (event) {
|
||||
var port = String(config.port);
|
||||
fiveMinuteData.rss.push(event.psmem.rss);
|
||||
fiveMinuteData.heapTotal.push(event.psmem.heapTotal);
|
||||
fiveMinuteData.heapUsed.push(event.psmem.heapUsed);
|
||||
fiveMinuteData.load.push(event.osload);
|
||||
fiveMinuteData.delay.push(event.psdelay);
|
||||
fiveMinuteData.concurrency.push(parseInt(event.concurrents[port], 0));
|
||||
if (event.responseTimes[port]) {
|
||||
var responseTimeAvg = event.responseTimes[port].avg;
|
||||
if (isNaN(responseTimeAvg)) responseTimeAvg = 0;
|
||||
fiveMinuteData.responseTimeAvg.push(responseTimeAvg);
|
||||
fiveMinuteData.responseTimeMax.push(event.responseTimes[port].max);
|
||||
} else {
|
||||
fiveMinuteData.responseTimeAvg.push(0);
|
||||
fiveMinuteData.responseTimeMax.push(0);
|
||||
}
|
||||
if (event.requests[port]) {
|
||||
fiveMinuteData.requests.push(event.requests[port].total);
|
||||
} else {
|
||||
fiveMinuteData.requests.push(0);
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: 'GET',
|
||||
path: '/status/{param*}',
|
||||
handler: {
|
||||
directory: {
|
||||
path: join(__dirname, 'public')
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: 'GET',
|
||||
path: '/status/health',
|
||||
handler: function (request, reply) {
|
||||
return reply({
|
||||
metrics: fiveMinuteData
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
});
|
9
src/hapi/plugins/status/public/index.html
Normal file
9
src/hapi/plugins/status/public/index.html
Normal file
|
@ -0,0 +1,9 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Kibana Status</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Kibana Status Page</h1>
|
||||
<p>Statusy stuff goes here... it's goign to be totally awesome!</p>
|
||||
</body>
|
||||
</html>
|
76
test/unit/server/lib/check_dependencies.js
Normal file
76
test/unit/server/lib/check_dependencies.js
Normal file
|
@ -0,0 +1,76 @@
|
|||
var checkDependencies = require('../../../../src/hapi/lib/check_dependencies');
|
||||
var expect = require('expect.js');
|
||||
|
||||
describe('src/server/lib/check_dependencies', function () {
|
||||
|
||||
it('should return true for first -> second -> third', function () {
|
||||
var deps = {
|
||||
first: [],
|
||||
second: ['first'],
|
||||
third: ['second']
|
||||
};
|
||||
|
||||
var results = checkDependencies('first', deps);
|
||||
expect(results).to.be(true);
|
||||
});
|
||||
|
||||
it('should throw an error for first -> third -> second -> first', function () {
|
||||
var deps = {
|
||||
first: ['third'],
|
||||
second: ['first'],
|
||||
third: ['second']
|
||||
};
|
||||
|
||||
var run = function () {
|
||||
checkDependencies('first', deps);
|
||||
};
|
||||
expect(run).to.throwException(function (e) {
|
||||
expect(e.message).to.be('Circular dependency: first -> third -> second -> first');
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error for first -> missing', function () {
|
||||
var deps = {
|
||||
first: ['missing']
|
||||
};
|
||||
|
||||
var run = function () {
|
||||
checkDependencies('first', deps);
|
||||
};
|
||||
expect(run).to.throwException(function (e) {
|
||||
expect(e.message).to.be('Missing dependency: missing');
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error for missing dependency', function () {
|
||||
var deps = {
|
||||
first: ['missing']
|
||||
};
|
||||
|
||||
var run = function () {
|
||||
checkDependencies('missing', deps);
|
||||
};
|
||||
expect(run).to.throwException(function (e) {
|
||||
expect(e.message).to.be('Missing dependency: missing');
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error on complex circulars', function () {
|
||||
var deps = {
|
||||
first: ['second', 'fifth'],
|
||||
second: ['fourth'],
|
||||
third: [],
|
||||
fourth: ['third'],
|
||||
fifth: ['sixth'],
|
||||
sixth: ['first']
|
||||
};
|
||||
|
||||
var run = function () {
|
||||
checkDependencies('first', deps);
|
||||
};
|
||||
expect(run).to.throwException(function (e) {
|
||||
expect(e.message).to.be('Circular dependency: first -> fifth -> sixth -> first');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
96
test/unit/server/lib/register_plugins.js
Normal file
96
test/unit/server/lib/register_plugins.js
Normal file
|
@ -0,0 +1,96 @@
|
|||
var expect = require('expect.js');
|
||||
var sinon = require('sinon');
|
||||
var registerPlugins = require('../../../../src/hapi/lib/register_plugins');
|
||||
var Promise = require('bluebird');
|
||||
|
||||
describe('server/lib/register_plugins', function () {
|
||||
|
||||
describe('registerPlugins() wrapper', function () {
|
||||
|
||||
it('should pass server, options and next to the init function', function () {
|
||||
var options = { foo: 'bar' };
|
||||
var server = { register: sinon.stub() };
|
||||
var next = function (err) {
|
||||
server.register.args[0][1](err);
|
||||
};
|
||||
server.register.yieldsTo('register', [server, options, next]);
|
||||
var plugin = { name: 'first', init: sinon.stub().yields() };
|
||||
var plugins = [plugin];
|
||||
return registerPlugins(server, plugins).then(function () {
|
||||
expect(plugin.init.args[0][0]).to.equal(server);
|
||||
expect(plugin.init.args[0][1]).to.equal(options);
|
||||
expect(plugin.init.args[0][2]).to.equal(next);
|
||||
});
|
||||
});
|
||||
|
||||
it('should call next() when plugin.init completes', function () {
|
||||
var called = false;
|
||||
var options = { foo: 'bar' };
|
||||
var server = { register: sinon.stub() };
|
||||
var next = function (err) {
|
||||
called = true;
|
||||
server.register.args[0][1](err);
|
||||
};
|
||||
server.register.yieldsTo('register', [server, options, next]);
|
||||
var plugin = { name: 'first', init: sinon.stub().yields() };
|
||||
var plugins = [plugin];
|
||||
return registerPlugins(server, plugins).then(function () {
|
||||
expect(called).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('dependencies', function () {
|
||||
var server, nextStub;
|
||||
|
||||
beforeEach(function () {
|
||||
server = { register: sinon.stub() };
|
||||
var count = 0;
|
||||
var next = function (err) {
|
||||
server.register.args[count++][1](err);
|
||||
};
|
||||
server.register.yieldsTo('register', [server, {}, next]);
|
||||
});
|
||||
|
||||
it('should run second after first and third and third after first', function () {
|
||||
var first = { name: 'first', init: sinon.stub().yields() };
|
||||
var second = { name: 'second', require: ['first', 'third'], init: sinon.stub().yields() };
|
||||
var third = { name: 'third', require: ['first'], init: sinon.stub().yields() };
|
||||
var plugins = [second, first, third];
|
||||
return registerPlugins(server, plugins).then(function () {
|
||||
expect(second.init.calledAfter(first.init)).to.be(true);
|
||||
expect(second.init.calledAfter(third.init)).to.be(true);
|
||||
expect(third.init.calledAfter(first.init)).to.be(true);
|
||||
sinon.assert.calledThrice(server.register);
|
||||
});
|
||||
});
|
||||
|
||||
it('should run first, second, third', function () {
|
||||
var first = { name: 'first', init: sinon.stub().yields() };
|
||||
var second = { name: 'second', require: ['first'], init: sinon.stub().yields() };
|
||||
var third = { name: 'third', require: ['second'], init: sinon.stub().yields() };
|
||||
var plugins = [second, first, third];
|
||||
return registerPlugins(server, plugins).then(function () {
|
||||
sinon.assert.calledOnce(first.init);
|
||||
expect(second.init.calledAfter(first.init)).to.be(true);
|
||||
expect(third.init.calledAfter(second.init)).to.be(true);
|
||||
sinon.assert.calledThrice(server.register);
|
||||
});
|
||||
});
|
||||
|
||||
it('should detect circular dependencies', function (done) {
|
||||
var first = { name: 'first', require: ['third'], init: sinon.stub() };
|
||||
var second = { name: 'second', require: ['first'], init: sinon.stub() };
|
||||
var third = { name: 'third', require: ['second'], init: sinon.stub() };
|
||||
var plugins = [second, first, third];
|
||||
registerPlugins(server, plugins).catch(function (err) {
|
||||
expect(err).to.be.a(Error);
|
||||
expect(err.message).to.be('Circular dependency: second -> first -> third -> second');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
}); // end dependencies tests
|
||||
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue