Merge branch 'master' of github.com:elasticsearch/kibana into feature/config-migration

Conflicts:
	src/server/config/index.js
	src/server/index.js
This commit is contained in:
Chris Cowan 2015-01-29 11:17:10 -07:00
commit 3fa0dfdf3a
22 changed files with 204 additions and 176 deletions

1
.gitignore vendored
View file

@ -12,3 +12,4 @@ target
*.iml
*.log
esvm
.htpasswd

View file

@ -17,31 +17,16 @@ Please make sure you have signed the [Contributor License Agreement](http://www.
nvm install 0.10
```
- Install ruby *1.9.3* (we recommend using [rbenv](https://github.com/sstephenson/rbenv))
- See [rbenv docs](https://github.com/sstephenson/rbenv#installation) for installation assistance
```sh
## install ruby and ruby-build using your local package manager (apt, brew, etc)
brew install rbenv ruby-build
```
- Run `rbenv init` and add `eval "$(rbenv init -)"` to your shell (ex. .bashrc/.bash_profile)
- Run `rbenv install` to install the required version
- Run `ruby -v` and make sure you are using 1.9.3
- Check the installation docs if you have issues getting the correct version
- Install bundler by running `gem install bundler`
- Install local gems by running `bundle`
- Install grunt and bower globally
- Install grunt and bower globally (as root if not using nvm)
```sh
npm install -g grunt-cli bower
```
- Install node, bower, and ruby dependencies
- Install node and bower dependencies
```sh
npm install && bower install && bundle
npm install && bower install
```
- Start the development server.

View file

@ -47,6 +47,7 @@
"elasticsearch": "^3.1.1",
"express": "~4.10.6",
"glob": "^4.3.2",
"http-auth": "^2.2.5",
"jade": "~1.8.2",
"js-yaml": "^3.2.5",
"less-middleware": "1.0.x",

View file

@ -38,7 +38,10 @@ define(function (require) {
params: [
{
name: 'field',
filterFieldTypes: 'date'
filterFieldTypes: 'date',
default: function (aggConfig) {
return aggConfig.vis.indexPattern.timeFieldName;
}
},
{

View file

@ -3,7 +3,7 @@
<span ng-click="sort(indexPattern.timeFieldName)" tooltip="Sort by time">Time <i ng-class="headerClass(indexPattern.timeFieldName)"></i></span>
</th>
<th ng-repeat="name in columns">
<span ng-click="sort(name)" class="table-header-name" tooltip="Sort by {{name | shortDots}}">
<span ng-click="sort(name)" class="table-header-name" tooltip="{{tooltip(name)}}">
{{name | shortDots}} <i ng-class="headerClass(name)"></i>
</span>
<span class="table-header-move">

View file

@ -4,7 +4,7 @@ define(function (require) {
require('filters/short_dots');
module.directive('kbnTableHeader', function () {
module.directive('kbnTableHeader', function (shortDotsFilter) {
var headerHtml = require('text!components/doc_table/components/table_header.html');
return {
restrict: 'A',
@ -21,6 +21,10 @@ define(function (require) {
return $scope.indexPattern.fields.byName[field].sortable;
};
$scope.tooltip = function (column) {
if (!sortableField(column)) return ''; else return 'Sort by ' + shortDotsFilter(column);
};
$scope.headerClass = function (column) {
if (!sortableField(column)) return;

View file

@ -38,7 +38,7 @@
</table>
<kbn-infinite-scroll ng-if="infiniteScroll" more="addRows"></kbn-infinite-scroll>
</div>
<div ng-if="!hits.length" class="table-vis-error">
<h2><i class="fa fa-meh-o"></i></h2>
<h4>No results found</h4>
</div>
<div ng-if="hits != null && !hits.length" class="table-vis-error">
<h2><i class="fa fa-meh-o"></i></h2>
<h4>No results found</h4>
</div>

View file

@ -76,7 +76,13 @@ define(function (require) {
if (val == null) {
if (aggParam.default == null) return;
else val = aggParam.default;
if (!_.isFunction(aggParam.default)) {
val = aggParam.default;
} else {
val = aggParam.default(self);
if (val == null) return;
}
}
if (aggParam.deserialize) {
@ -104,8 +110,8 @@ define(function (require) {
* @return {object} the new params object
*/
AggConfig.prototype.resetParams = function () {
// We need to ensure that row doesn't get overriden.
return this.fillDefaults(_.pick(this.params, 'row'));
// We need to ensure that row and field don't get overriden.
return this.fillDefaults(_.pick(this.params, 'row', 'field'));
};
AggConfig.prototype.write = function () {

View file

@ -8,9 +8,9 @@ define(function (require) {
var sequencer = require('utils/sequencer');
var SCHEDULE = ResizeChecker.SCHEDULE = sequencer.createEaseIn(
5, // shortest delay
100, // shortest delay
10000, // longest delay
125 // tick count
50 // tick count
);
// maximum ms that we can delay emitting 'resize'. This is only used

View file

@ -1,4 +1,6 @@
<div ng-controller="KbnMetricVisController" class="metric-vis">
<div class="metric-value" ng-style="{'font-size': vis.params.fontSize+'pt'}">{{metric.value}}</div>
<div>{{metric.label}}</div>
<div class="metric-container" ng-repeat="metric in metrics">
<div class="metric-value" ng-style="{'font-size': vis.params.fontSize+'pt'}">{{metric.value}}</div>
<div>{{metric.label}}</div>
</div>
</div>

View file

@ -2,12 +2,22 @@
@import (reference) "lesshat.less";
.metric-vis {
text-align: center;
.flex-parent();
.justify-content(center);
width: 100%;
.display(flex);
.flex-direction(row);
.flex-wrap(wrap);
.justify-content(space-around);
.align-items(center);
.align-content(space-around);
.metric-value {
font-weight: bold;
.ellipsis();
}
.metric-container {
text-align: center;
padding: 1em;
.flex();
}
}

View file

@ -3,20 +3,27 @@ define(function (require) {
// didn't already
var module = require('modules').get('kibana/metric_vis', ['kibana']);
module.controller('KbnMetricVisController', function ($scope) {
var metric = $scope.metric = {
label: null,
value: null
module.controller('KbnMetricVisController', function ($scope, Private) {
var tabifyAggResponse = Private(require('components/agg_response/tabify/tabify'));
var metrics = $scope.metrics = [];
$scope.processTableGroups = function (tableGroups) {
tableGroups.tables.forEach(function (table) {
table.columns.forEach(function (column, i) {
var fieldFormatter = table.aggConfig(column).fieldFormatter();
metrics.push({
label: column.title,
value: fieldFormatter(table.rows[0][i])
});
});
});
};
$scope.$watch('esResponse', function (resp) {
if (!resp) {
metric.label = metric.value = null;
} else {
var agg = $scope.vis.aggs[0];
metric.label = agg.makeLabel();
if (agg.type.name === 'count') metric.value = resp.hits.total;
else metric.value = agg.fieldFormatter()(resp.aggregations[agg.id].value);
if (resp) {
metrics.length = 0;
$scope.processTableGroups(tabifyAggResponse($scope.vis, resp));
}
});
});

View file

@ -4,8 +4,6 @@ define(function (require) {
require('modules')
.get('app/visualize')
.directive('visAggParamEditor', function (config, $parse, Private) {
var FieldAggParam = Private(require('components/agg_types/param_types/field'));
return {
restrict: 'E',
scope: true,
@ -26,13 +24,6 @@ define(function (require) {
return true;
};
// set default value on field agg params
if ($scope.aggParam instanceof FieldAggParam) {
if (!$scope.agg.params[$scope.aggParam.name]) {
$scope.agg.params[$scope.aggParam.name] = $scope.indexedFields[0];
}
}
}
}
};

View file

@ -17,26 +17,27 @@
</vis-editor-vis-options>
<!-- apply/discard -->
<li class="vis-editor-sidebar-buttons sidebar-item">
<p
ng-if="visualizeEditor.$invalid"
class="text-center text-danger sidebar-item-text">
<i class="fa fa-warning"></i> {{visualizeEditor.describeErrors()}}
</p>
<button
ng-disabled="!vis.dirty || visualizeEditor.$invalid"
type="submit"
class="sidebar-item-button success">
Apply
</button>
<button
ng-disabled="!vis.dirty"
type="button"
ng-click="resetEditableVis()"
class="sidebar-item-button default">
Discard
</button>
</li>
</ul>
<div class="vis-editor-sidebar-buttons">
<p
ng-if="visualizeEditor.$invalid"
class="text-center text-danger sidebar-item-text">
<i class="fa fa-warning"></i> {{visualizeEditor.describeErrors()}}
</p>
<button
ng-disabled="!vis.dirty || visualizeEditor.$invalid"
type="submit"
class="sidebar-item-button success">
Apply
</button>
<button
ng-disabled="!vis.dirty"
type="button"
ng-click="resetEditableVis()"
class="sidebar-item-button default">
Discard
</button>
</div>
</form>
</div>
</div>

View file

@ -50,33 +50,38 @@
&-sidebar {
.flex-parent(0, 0, auto);
overflow: auto;
// overflow: auto;
// overrided for tablet and desktop
@media (min-width: @screen-md-min) {
.flex-basis(@vis-editor-sidebar-basis);
min-width: @vis-editor-sidebar-min-width;
max-width: @vis-editor-sidebar-min-width;
margin-bottom: (@input-height-base * 2) - 3;
// margin-bottom: (@input-height-base * 2) - 3;
}
&-buttons {
// overrides for tablet and desktop
@media (min-width: @screen-md-min) {
position: absolute;
bottom: 0;
min-width: @vis-editor-sidebar-min-width;
}
}
.sidebar-item {
border-top: 0 !important;
}
.sidebar-container {
.flex(1, 0, auto);
.flex-parent(1, 1, auto);
background-color: @body-bg;
border-right-color: @sidebar-bg;
form {
.flex-parent(1, 1, auto);
> ul {
.flex(1, 1, auto);
overflow-y: auto;
}
> .vis-edit-sidebar-buttons {
.flex(0, 0, auto)
}
}
}
.sidebar-item-title {

View file

@ -2,6 +2,7 @@ var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var requestLogger = require('./lib/requestLogger');
var auth = require('./lib/auth');
var appHeaders = require('./lib/appHeaders');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
@ -18,9 +19,10 @@ app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
app.set('x-powered-by', false);
app.use(favicon(path.join(config.public_folder, 'styles', 'theme', 'elk.ico')));
app.use(requestLogger());
app.use(auth());
app.use(appHeaders());
app.use(favicon(path.join(config.public_folder, 'styles', 'theme', 'elk.ico')));
if (app.get('env') === 'development') {
require('./dev')(app);

View file

@ -7,14 +7,24 @@ 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');
try {
fs.statSync(public_folder);
} catch (err) {
public_folder = path.resolve(__dirname, '..', '..', 'kibana');
}
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 {
@ -33,7 +43,8 @@ var config = module.exports = {
external_plugins_folder : process.env.PLUGINS_FOLDER || null,
bundled_plugins_folder : path.resolve(public_folder, 'plugins'),
kibana : kibana,
package : require(packagePath)
package : require(packagePath),
htpasswd : htpasswdPath
};
config.plugins = listPlugins(config);

View file

@ -38,3 +38,7 @@ verify_ssl: true
# 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

View file

@ -3,18 +3,35 @@
*/
var app = require('./app');
var http = require('http');
var fs = require('fs');
var config = require('./config');
var logger = require('./lib/logger');
var Promise = require('bluebird');
var initialization = require('./lib/serverInitialization');
var key, cert;
try {
key = fs.readFileSync(config.kibana.ssl_key_file, 'utf8');
cert = fs.readFileSync(config.kibana.ssl_cert_file, 'utf8');
} catch (err) {
if (err.code === 'ENOENT') {
logger.fatal('Failed to read %s', err.path);
process.exit(1);
}
}
/**
* Create HTTP server.
* Create HTTPS/HTTP server.
*/
var server = http.createServer(app);
var server;
if (key && cert) {
server = require('https').createServer({
key: key,
cert: cert
}, app);
} else {
server = require('http').createServer(app);
}
server.on('error', onError);
server.on('listening', onListening);

10
src/server/lib/auth.js Normal file
View file

@ -0,0 +1,10 @@
var config = require('../config');
var httpAuth = require('http-auth');
module.exports = function () {
var basic;
if (config.htpasswd) {
basic = httpAuth.basic({ file: config.htpasswd });
return httpAuth.connect(basic);
}
return function (req, res, next) { return next(); };
};

View file

@ -97,6 +97,7 @@ define(function (require) {
var vis;
beforeEach(function () {
vis = {
indexPattern: indexPattern,
type: {
schemas: new Schemas([
{
@ -140,7 +141,7 @@ define(function (require) {
it('should NOT set the defaults defined in the schema when some exist', function () {
var ac = new AggConfigs(vis, [{ schema: 'segment', type: 'date_histogram' }]);
expect(ac).to.have.length(3);
expect(ac.bySchemaName['segment'][0].type.name).to.equal('date_histogram');
expect(ac.bySchemaName.segment[0].type.name).to.equal('date_histogram');
});
});
});

View file

@ -1,49 +1,11 @@
define(function (require) {
var metricVis = {
aggs: [{
type: {name: 'count'},
schema: 'metric',
makeLabel: function () {
return 'Count of documents';
}
}]
};
var averageVis = {
aggs: [{
id: 'agg',
type: {name: 'average'},
schema: 'metric',
makeLabel: function () {
return 'Average bytes';
},
fieldFormatter: function () {
return function (val) {
return val;
};
}
}]
};
var fieldFormatterVis = {
aggs: [{
id: 'agg',
type: {name: 'average'},
schema: 'metric',
makeLabel: function () {
return 'Average bytes';
},
fieldFormatter: function () {
return function (val) {
return val.toFixed(3);
};
}
}]
};
describe('metric vis', function () {
var $scope;
var formatter = function (value) {
return value.toFixed(3);
};
beforeEach(module('kibana/metric_vis'));
beforeEach(inject(function ($rootScope, $controller) {
$scope = $rootScope.$new();
@ -51,44 +13,49 @@ define(function (require) {
$scope.$digest();
}));
it('should set the metric', function () {
expect($scope).to.have.property('metric');
it('should set the metric label and value', function () {
$scope.processTableGroups({
tables: [{
columns: [{title: 'Count'}],
rows: [[4301021]],
aggConfig: function () {
return {
fieldFormatter: function () {
return formatter;
}
};
}
}]
});
expect($scope.metrics.length).to.be(1);
expect($scope.metrics[0].label).to.be('Count');
expect($scope.metrics[0].value).to.be('4301021.000');
});
it('should set the metric label and value for count', function () {
expect($scope.metric.label).to.not.be.ok();
expect($scope.metric.value).to.not.be.ok();
it('should support multi-value metrics', function () {
$scope.processTableGroups({
tables: [{
columns: [
{title: '1st percentile of bytes'},
{title: '99th percentile of bytes'}
],
rows: [[182, 445842.4634666484]],
aggConfig: function () {
return {
fieldFormatter: function () {
return formatter;
}
};
}
}]
});
$scope.vis = metricVis;
$scope.esResponse = {hits: {total: 4826}};
$scope.$digest();
expect($scope.metric.label).to.be('Count of documents');
expect($scope.metric.value).to.be($scope.esResponse.hits.total);
});
it('should set the metric value for average', function () {
expect($scope.metric.label).to.not.be.ok();
expect($scope.metric.value).to.not.be.ok();
$scope.vis = averageVis;
$scope.esResponse = {hits: {total: 4826}, aggregations: {agg: {value: 1234}}};
$scope.$digest();
expect($scope.metric.label).to.be('Average bytes');
expect($scope.metric.value).to.be($scope.esResponse.aggregations.agg.value);
});
it('should use the field formatter', function () {
expect($scope.metric.label).to.not.be.ok();
expect($scope.metric.value).to.not.be.ok();
$scope.vis = fieldFormatterVis;
$scope.esResponse = {hits: {total: 4826}, aggregations: {agg: {value: 1234.12345}}};
$scope.$digest();
expect($scope.metric.label).to.be('Average bytes');
expect($scope.metric.value).to.be($scope.esResponse.aggregations.agg.value.toFixed(3));
expect($scope.metrics.length).to.be(2);
expect($scope.metrics[0].label).to.be('1st percentile of bytes');
expect($scope.metrics[0].value).to.be('182.000');
expect($scope.metrics[1].label).to.be('99th percentile of bytes');
expect($scope.metrics[1].value).to.be('445842.463');
});
});
});