mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
Merge branch 'master' of github.com:elasticsearch/kibana into feature/ssl-support
Conflicts: package.json src/server/config/kibana.yml
This commit is contained in:
commit
b73ec90e70
20 changed files with 263 additions and 94 deletions
|
@ -46,13 +46,14 @@
|
|||
"express": "~4.10.6",
|
||||
"glob": "^4.3.2",
|
||||
"http-auth": "^2.2.5",
|
||||
"http-proxy": "^1.8.1",
|
||||
"jade": "~1.8.2",
|
||||
"js-yaml": "^3.2.5",
|
||||
"less-middleware": "1.0.x",
|
||||
"lodash": "^2.4.1",
|
||||
"morgan": "~1.5.1",
|
||||
"serve-favicon": "~2.2.0"
|
||||
"request": "^2.40.0",
|
||||
"serve-favicon": "~2.2.0",
|
||||
"ssl-root-cas": "^1.1.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"bluebird": "~2.0.7",
|
||||
|
@ -89,7 +90,6 @@
|
|||
"opn": "~1.0.0",
|
||||
"path-browserify": "0.0.0",
|
||||
"progress": "^1.1.8",
|
||||
"request": "^2.40.0",
|
||||
"requirejs": "~2.1.14",
|
||||
"rjs-build-analysis": "0.0.3",
|
||||
"simple-git": "^0.11.0",
|
||||
|
|
|
@ -17,5 +17,6 @@
|
|||
class="form-control"
|
||||
name="interval"
|
||||
min="0"
|
||||
input-whole-number
|
||||
>
|
||||
</div>
|
||||
|
|
|
@ -19,7 +19,6 @@ define(function (require) {
|
|||
if (updater.fired) return;
|
||||
updater.fired = true;
|
||||
|
||||
|
||||
var method;
|
||||
var body;
|
||||
var updated = [];
|
||||
|
@ -44,6 +43,9 @@ define(function (require) {
|
|||
queue.forEach(function (q) { q.resolve(resp); });
|
||||
}, function (err) {
|
||||
queue.forEach(function (q) { q.reject(err); });
|
||||
})
|
||||
.finally(function () {
|
||||
$rootScope.$emit('change:config', updated.concat(deleted));
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -68,7 +70,7 @@ define(function (require) {
|
|||
var defer = Promise.defer();
|
||||
queue.push(defer);
|
||||
notify.log('config change: ' + key + ': ' + oldVal + ' -> ' + newVal);
|
||||
$rootScope.$broadcast('change:config.' + key, newVal, oldVal);
|
||||
$rootScope.$emit('change:config.' + key, newVal, oldVal);
|
||||
|
||||
// reset the fire timer
|
||||
clearTimeout(timer);
|
||||
|
|
|
@ -25,7 +25,15 @@ define(function (require) {
|
|||
{ from: 'now-12h', to: 'now', display: 'Last 12 hours', section: 2 },
|
||||
{ from: 'now-24h', to: 'now', display: 'Last 24 hours', section: 2 },
|
||||
{ from: 'now-7d', to: 'now', display: 'Last 7 days', section: 2 },
|
||||
{ from: 'now-30d', to: 'now', display: 'Last 30 days', section: 2 },
|
||||
|
||||
{ from: 'now-30d', to: 'now', display: 'Last 30 days', section: 3 },
|
||||
{ from: 'now-60d', to: 'now', display: 'Last 60 days', section: 3 },
|
||||
{ from: 'now-90d', to: 'now', display: 'Last 90 days', section: 3 },
|
||||
{ from: 'now-6M', to: 'now', display: 'Last 6 months', section: 3 },
|
||||
{ from: 'now-1y', to: 'now', display: 'Last 1 year', section: 3 },
|
||||
{ from: 'now-2y', to: 'now', display: 'Last 2 years', section: 3 },
|
||||
{ from: 'now-5y', to: 'now', display: 'Last 5 years', section: 3 },
|
||||
|
||||
]);
|
||||
|
||||
});
|
||||
|
|
|
@ -187,8 +187,10 @@ define(function (require) {
|
|||
// Append the bars
|
||||
circles = layer
|
||||
.selectAll('rect')
|
||||
.data(function appendData(d) {
|
||||
return d;
|
||||
.data(function appendData(data) {
|
||||
return data.filter(function isNotZero(d) {
|
||||
return d.y !== 0;
|
||||
});
|
||||
});
|
||||
|
||||
// exit
|
||||
|
|
19
src/kibana/directives/input_whole_number.js
Normal file
19
src/kibana/directives/input_whole_number.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
define(function (require) {
|
||||
var module = require('modules').get('kibana');
|
||||
|
||||
module.directive('inputWholeNumber', function () {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: 'ngModel',
|
||||
link: function ($scope, $elem, attrs, ngModel) {
|
||||
ngModel.$parsers.push(checkWholeNumber);
|
||||
ngModel.$formatters.push(checkWholeNumber);
|
||||
|
||||
function checkWholeNumber(value) {
|
||||
ngModel.$setValidity('whole', value % 1 === 0);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
BIN
src/kibana/images/initial_load.gif
Normal file
BIN
src/kibana/images/initial_load.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
|
@ -7,6 +7,22 @@
|
|||
<meta name="viewport" content="width=device-width">
|
||||
<link rel="shortcut icon" href="styles/theme/elk.ico">
|
||||
<title>Kibana 4</title>
|
||||
<link rel="stylesheet" href="styles/main.css?_b=@@buildNum">
|
||||
|
||||
</head>
|
||||
<body kibana ng-class="'application-' + activeApp.id">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-offset-4 col-md-4 page-header initial-load">
|
||||
<center>
|
||||
<img width="128" src="images/initial_load.gif">
|
||||
<h1>
|
||||
<strong>Kibana</strong>
|
||||
<small>is loading. Give me a moment here. I'm loading a whole bunch of code. Don't worry, all this good stuff will be cached up for next time!</small>
|
||||
</h1>
|
||||
</center>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
window.KIBANA_VERSION='@@version';
|
||||
|
@ -14,7 +30,6 @@
|
|||
window.KIBANA_COMMIT_SHA='@@commitSha';
|
||||
</script>
|
||||
|
||||
<link rel="stylesheet" href="styles/main.css?_b=@@buildNum">
|
||||
<script src="bower_components/requirejs/require.js?_b=@@buildNum"></script>
|
||||
<script src="require.config.js?_b=@@buildNum"></script>
|
||||
<script>
|
||||
|
@ -25,6 +40,6 @@
|
|||
|
||||
require(['kibana'], function (kibana) { kibana.init(); });
|
||||
</script>
|
||||
</head>
|
||||
<body kibana ng-class="'application-' + activeApp.id"></body>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
</div>
|
||||
<div class="clearfix visible-xs-block"></div>
|
||||
<input
|
||||
input-focus
|
||||
ng-model="filter"
|
||||
ng-attr-placeholder="{{noun}} Filter"
|
||||
class="form-control"
|
||||
|
|
|
@ -10,59 +10,52 @@ define(function (require) {
|
|||
});
|
||||
|
||||
require('modules').get('apps/settings')
|
||||
.directive('kbnSettingsAdvanced', function (config, Notifier, Private) {
|
||||
.directive('kbnSettingsAdvanced', function (config, Notifier, Private, $rootScope) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
link: function ($scope) {
|
||||
var notify = new Notifier();
|
||||
var configVals = config._vals();
|
||||
var keyCodes = {
|
||||
ESC: 27
|
||||
};
|
||||
|
||||
// determine if a value is too complex to be edditted (at this time)
|
||||
var tooComplex = function (conf) {
|
||||
// get the type of the current value or the default
|
||||
switch (conf.type) {
|
||||
case 'string':
|
||||
case 'number':
|
||||
case 'null':
|
||||
case 'undefined':
|
||||
conf.normal = true;
|
||||
break;
|
||||
case 'json':
|
||||
conf.json = true;
|
||||
break;
|
||||
default:
|
||||
if (_.isArray(config.get(conf.name))) {
|
||||
conf.array = true;
|
||||
} else if (typeof config.get(conf.name) === 'boolean') {
|
||||
conf.bool = true;
|
||||
} else {
|
||||
conf.tooComplex = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
var NAMED_EDITORS = ['json', 'array', 'boolean'];
|
||||
var NORMAL_EDITOR = ['number', 'string', 'null', 'undefined'];
|
||||
|
||||
$scope.configs = _.map(configDefaults, function (def, name) {
|
||||
var conf = {
|
||||
name: name,
|
||||
defVal: def.value,
|
||||
type: (def.type || typeof config.get(name)),
|
||||
description: def.description,
|
||||
value: configVals[name]
|
||||
};
|
||||
function getEditorType(conf) {
|
||||
if (_.contains(NORMAL_EDITOR, conf.type)) return 'normal';
|
||||
if (_.contains(NAMED_EDITORS, conf.type)) return conf.type;
|
||||
}
|
||||
|
||||
tooComplex(conf);
|
||||
function isTypeComplex(conf) {
|
||||
return !(conf.json || conf.array || conf.bool || conf.normal);
|
||||
}
|
||||
|
||||
$scope.$on('change:config.' + name, function () {
|
||||
configVals = config._vals();
|
||||
conf.value = configVals[name];
|
||||
tooComplex(conf);
|
||||
function readConfigVals() {
|
||||
var configVals = config._vals();
|
||||
|
||||
$scope.configs = _.map(configDefaults, function (def, name) {
|
||||
var val = configVals[name];
|
||||
var conf = {
|
||||
name: name,
|
||||
defVal: def.value,
|
||||
type: (def.type || _.isArray(val) || typeof val),
|
||||
description: def.description,
|
||||
value: val,
|
||||
};
|
||||
|
||||
var editor = getEditorType(conf);
|
||||
conf.json = editor === 'json';
|
||||
conf.bool = editor === 'bool';
|
||||
conf.array = editor === 'array';
|
||||
conf.normal = editor === 'normal';
|
||||
conf.tooComplex = !editor;
|
||||
|
||||
return conf;
|
||||
});
|
||||
}
|
||||
|
||||
return conf;
|
||||
});
|
||||
readConfigVals();
|
||||
$rootScope.$on('change:config', readConfigVals);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -15,7 +15,7 @@ define(function (require) {
|
|||
|
||||
// wrapper directive, which sets some global stuff up like the left nav
|
||||
require('modules').get('apps/settings')
|
||||
.directive('kbnSettingsIndices', function ($route, config, kbnUrl) {
|
||||
.directive('kbnSettingsIndices', function ($route, config, kbnUrl, $rootScope) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
transclude: true,
|
||||
|
@ -23,7 +23,7 @@ define(function (require) {
|
|||
link: function ($scope) {
|
||||
$scope.edittingId = $route.current.params.id;
|
||||
$scope.defaultIndex = config.get('defaultIndex');
|
||||
$scope.$on('change:config.defaultIndex', function () {
|
||||
$rootScope.$on('change:config.defaultIndex', function () {
|
||||
$scope.defaultIndex = config.get('defaultIndex');
|
||||
});
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<div ng-controller="KbnTableVisController" class="table-vis">
|
||||
<div ng-if="!hasSomeRows" class="table-vis-error">
|
||||
<div ng-if="!hasSomeRows && hasSomeRows !== null" class="table-vis-error">
|
||||
<h2><i class="fa fa-meh-o"></i></h2>
|
||||
<h4>No results found</h4>
|
||||
</div>
|
||||
|
|
|
@ -434,6 +434,10 @@ textarea {
|
|||
resize: vertical;
|
||||
}
|
||||
|
||||
.initial-load {
|
||||
margin-top: 60px;
|
||||
}
|
||||
|
||||
.field-collapse-toggle {
|
||||
color: #999;
|
||||
margin-left: 10px !important;
|
||||
|
|
|
@ -3,6 +3,8 @@ define(function (require) {
|
|||
var moment = require('moment');
|
||||
var datemath = require('utils/datemath');
|
||||
|
||||
require('directives/input_whole_number');
|
||||
|
||||
/**
|
||||
* Calculate a graph interval
|
||||
*
|
||||
|
|
|
@ -34,6 +34,11 @@ shard_timeout: 0
|
|||
# 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
|
||||
|
||||
|
|
|
@ -1,52 +1,104 @@
|
|||
var logger = require('../lib/logger');
|
||||
var express = require('express');
|
||||
var router = module.exports = express.Router();
|
||||
var httpProxy = require('http-proxy');
|
||||
var config = require('../config');
|
||||
var request = require('request');
|
||||
var buffer = require('buffer');
|
||||
var querystring = require('querystring');
|
||||
var express = require('express');
|
||||
var _ = require('lodash');
|
||||
var fs = require('fs');
|
||||
var url = require('url');
|
||||
var target = url.parse(config.elasticsearch);
|
||||
var proxy = new httpProxy.createProxyServer({});
|
||||
var buffer = require('buffer');
|
||||
var join = require('path').join;
|
||||
|
||||
proxy.on('proxyReq', function (proxyReq, req, res, options) {
|
||||
// To support the elasticsearch_preserve_host feature we need to change the
|
||||
// host header to the target host header.
|
||||
if (config.kibana.elasticsearch_preserve_host) {
|
||||
proxyReq.setHeader('host', target.host);
|
||||
|
||||
// If the target is backed by an SSL and a CA is provided via the config
|
||||
// then we need to inject the CA
|
||||
var hasCustomCA = false;
|
||||
if (/^https/.test(target.protocol) && config.kibana.ca) {
|
||||
var sslRootCAs = require('ssl-root-cas/latest');
|
||||
sslRootCAs.inject();
|
||||
var ca = fs.readFileSync(config.kibana.ca, 'utf8');
|
||||
var https = require('https');
|
||||
https.globalAgent.options.ca.push(ca);
|
||||
hasCustomCA = true;
|
||||
}
|
||||
|
||||
// Create the router
|
||||
var router = module.exports = express.Router();
|
||||
|
||||
// We need to capture the raw body before moving on
|
||||
router.use(function (req, res, next) {
|
||||
req.rawBody = '';
|
||||
req.setEncoding('utf8');
|
||||
req.on('data', function (chunk) {
|
||||
req.rawBody += chunk;
|
||||
});
|
||||
req.on('end', next);
|
||||
});
|
||||
|
||||
function getPort(req) {
|
||||
var matches = req.headers.host.match(/:(\d+)/);
|
||||
if (matches[1]) return matches[1];
|
||||
return req.connection.pair ? '443' : '80';
|
||||
}
|
||||
|
||||
// Create the proxy middleware
|
||||
router.use(function (req, res, next) {
|
||||
|
||||
var uri = _.defaults({}, target);
|
||||
var options = {
|
||||
url: uri.protocol + '//' + uri.host + req.url,
|
||||
method: req.method,
|
||||
headers: _.defaults({ host: target.hostname }, req.headers),
|
||||
strictSSL: config.kibana.verify_ssl,
|
||||
timeout: config.kibana.request_timeout
|
||||
};
|
||||
|
||||
|
||||
options.headers['x-forward-for'] = req.connection.remoteAddress || req.socket.remoteAddress;
|
||||
options.headers['x-forward-port'] = getPort(req);
|
||||
options.headers['x-forward-proto'] = req.connection.pair ? 'https' : 'http';
|
||||
|
||||
// If the server has a custom CA we need to add it to the agent options
|
||||
if (hasCustomCA) {
|
||||
options.agentOptions = { ca: https.globalAgent.options.ca };
|
||||
}
|
||||
|
||||
// Only send the body if it's a PATCH, PUT, or POST
|
||||
if (req.rawBody) {
|
||||
options.body = req.rawBody;
|
||||
}
|
||||
|
||||
// Support for handling basic auth
|
||||
if (config.kibana.elasticsearch_username && config.kibana.elasticsearch_password) {
|
||||
var code = new buffer.Buffer(config.kibana.elasticsearch_username + ':' + config.kibana.elasticsearch_password);
|
||||
var auth = 'Basic ' + code.toString('base64');
|
||||
proxyReq.setHeader('authorization', auth);
|
||||
}
|
||||
});
|
||||
|
||||
// Error handling for the proxy
|
||||
proxy.on('error', function (err, req, res) {
|
||||
var code = 502;
|
||||
var body = { message: 'Bad Gateway' };
|
||||
|
||||
if (err.code === 'ECONNREFUSED') {
|
||||
body.message = 'Unable to connect to Elasticsearch';
|
||||
options.headers.authorization = auth;
|
||||
}
|
||||
|
||||
if (err.message === 'DEPTH_ZERO_SELF_SIGNED_CERT') {
|
||||
body.message = 'SSL handshake with Elasticsearch failed';
|
||||
// To support the elasticsearch_preserve_host feature we need to change the
|
||||
// host header to the target host header. I don't quite understand the value
|
||||
// of this... but it's a feature we had before so I guess we are keeping it.
|
||||
if (config.kibana.elasticsearch_preserve_host) {
|
||||
options.headers.host = target.host;
|
||||
}
|
||||
|
||||
res.writeHead(502, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify(body));
|
||||
});
|
||||
|
||||
router.use(function (req, res, next) {
|
||||
var options = {
|
||||
target: config.elasticsearch,
|
||||
secure: config.kibana.verify_ssl,
|
||||
xfwd: true,
|
||||
timeout: (config.kibana.request_timeout)
|
||||
};
|
||||
proxy.web(req, res, options);
|
||||
// Create the request and pipe the response
|
||||
var esRequest = request(options);
|
||||
esRequest.on('error', function (err) {
|
||||
var code = 502;
|
||||
var body = { message: 'Bad Gateway' };
|
||||
|
||||
if (err.code === 'ECONNREFUSED') {
|
||||
body.message = 'Unable to connect to Elasticsearch';
|
||||
}
|
||||
|
||||
if (err.message === 'DEPTH_ZERO_SELF_SIGNED_CERT') {
|
||||
body.message = 'SSL handshake with Elasticsearch failed';
|
||||
}
|
||||
|
||||
body.err = err.message;
|
||||
res.status(code).json(body);
|
||||
});
|
||||
esRequest.pipe(res);
|
||||
});
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ module.exports = function (grunt) {
|
|||
grunt.registerTask('npm_install_kibana', 'NPM isntall kibana server into dist', function () {
|
||||
var done = this.async();
|
||||
var cwd = join(grunt.config.get('build'), 'dist', 'kibana', 'src');
|
||||
var command = 'npm install --production';
|
||||
var command = 'npm install --production --no-optional';
|
||||
var options = { cwd: cwd };
|
||||
child_process.exec(command, options, function (err, stdout, stderr) {
|
||||
if (err) {
|
||||
|
|
48
test/unit/specs/directives/input_whole_number.js
Normal file
48
test/unit/specs/directives/input_whole_number.js
Normal file
|
@ -0,0 +1,48 @@
|
|||
define(function (require) {
|
||||
var angular = require('angular');
|
||||
require('directives/input_whole_number');
|
||||
|
||||
describe('Whole number input directive', function () {
|
||||
var $compile, $rootScope;
|
||||
var html = '<input type="text" ng-model="value" input-whole-number />';
|
||||
|
||||
beforeEach(module('kibana'));
|
||||
|
||||
beforeEach(inject(function (_$compile_, _$rootScope_) {
|
||||
$compile = _$compile_;
|
||||
$rootScope = _$rootScope_;
|
||||
}));
|
||||
|
||||
it('should allow whole numbers', function () {
|
||||
var element = $compile(html)($rootScope);
|
||||
|
||||
$rootScope.value = '123';
|
||||
$rootScope.$digest();
|
||||
expect(element.hasClass('ng-valid')).to.be.ok();
|
||||
|
||||
$rootScope.value = '1.0';
|
||||
$rootScope.$digest();
|
||||
expect(element.hasClass('ng-valid')).to.be.ok();
|
||||
|
||||
$rootScope.value = '-5.0';
|
||||
$rootScope.$digest();
|
||||
expect(element.hasClass('ng-valid')).to.be.ok();
|
||||
});
|
||||
|
||||
it('should disallow numbers with decimals', function () {
|
||||
var element = $compile(html)($rootScope);
|
||||
|
||||
$rootScope.value = '123.0';
|
||||
$rootScope.$digest();
|
||||
expect(element.hasClass('ng-valid')).to.be.ok();
|
||||
|
||||
$rootScope.value = '1.2';
|
||||
$rootScope.$digest();
|
||||
expect(element.hasClass('ng-invalid')).to.be.ok();
|
||||
|
||||
$rootScope.value = '-5.5';
|
||||
$rootScope.$digest();
|
||||
expect(element.hasClass('ng-invalid')).to.be.ok();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -153,8 +153,8 @@ define(function (require) {
|
|||
$scope.$digest();
|
||||
});
|
||||
|
||||
it('should contain 3 lists of options', function (done) {
|
||||
expect($elem.find('.kbn-timepicker-section .list-unstyled').length).to.be(3);
|
||||
it('should contain 4 lists of options', function (done) {
|
||||
expect($elem.find('.kbn-timepicker-section .list-unstyled').length).to.be(4);
|
||||
done();
|
||||
});
|
||||
|
||||
|
|
|
@ -183,6 +183,23 @@ define(function (require) {
|
|||
expect($(chart.chartEl).find('circle').length).to.be.greaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not draw circles where d.y === 0', function () {
|
||||
vis.handler.charts.forEach(function (chart) {
|
||||
var series = chart.chartData.series;
|
||||
var isZero = series.some(function (d) {
|
||||
return d.y === 0;
|
||||
});
|
||||
var circles = $.makeArray($(chart.chartEl).find('circle'));
|
||||
var isNotDrawn = circles.some(function (d) {
|
||||
return d.__data__.y === 0;
|
||||
});
|
||||
|
||||
if (isZero) {
|
||||
expect(isNotDrawn).to.be(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Cannot seem to get these tests to work on the box
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue