Merge remote-tracking branch 'upstream/vislib/refactor' into vislib/refactor

Conflicts:
	src/kibana/components/vislib/lib/handler.js
	src/kibana/components/vislib/lib/legend.js
This commit is contained in:
Juan Thomassie 2014-09-11 13:35:17 -05:00
commit bcd03c3f5a
136 changed files with 2803 additions and 712 deletions

View file

@ -1,3 +1,3 @@
{
"directory": "./src/bower_components"
}
"directory": "./src/kibana/bower_components"
}

5
.gitignore vendored
View file

@ -1,7 +1,8 @@
.DS_Store
node_modules
src/bower_components
bower_components
**/*.css
trash
build
target
target
.jruby

1
.ruby-version Normal file
View file

@ -0,0 +1 @@
1.9.3-p547

View file

@ -12,9 +12,12 @@ module.exports = function (grunt) {
target: __dirname + '/target', // location of the compressed build targets
buildApp: __dirname + '/build/kibana', // build directory for the app
jrubyVersion: '1.7.14',
jrubyPath: __dirname + '/.jruby',
unitTestDir: __dirname + '/test/unit',
testUtilsDir: __dirname + '/test/utils',
bowerComponentsDir: __dirname + '/src/bower_components',
bowerComponentsDir: __dirname + '/src/kibana/bower_components',
meta: {
banner: '/*! <%= package.name %> - v<%= package.version %> - ' +
@ -36,4 +39,4 @@ module.exports = function (grunt) {
// load task definitions
grunt.loadTasks('tasks');
};
};

7
LICENSE.md Normal file
View file

@ -0,0 +1,7 @@
Copyright 2012-2014 Elasticsearch BV
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

View file

@ -14,6 +14,8 @@
- Change index to be the resolved in some way, last three months, last hour, last year, whatever
- **[src/kibana/components/vislib/vis.js](https://github.com/elasticsearch/kibana4/blob/master/src/kibana/components/vislib/vis.js)**
- need to come up with a solution for resizing when no data is available
- **[src/kibana/components/vislib/visualizations/column_chart.js](https://github.com/elasticsearch/kibana4/blob/master/src/kibana/components/vislib/visualizations/column_chart.js)**
- refactor so that this is called from the data module
- **[src/kibana/components/visualize/visualize.js](https://github.com/elasticsearch/kibana4/blob/master/src/kibana/components/visualize/visualize.js)**
- we need to have some way to clean up result requests
- **[src/kibana/directives/rows.js](https://github.com/elasticsearch/kibana4/blob/master/src/kibana/directives/rows.js)**

View file

@ -20,32 +20,32 @@
"tests"
],
"dependencies": {
"requirejs": "~2.1.10",
"angular": "~1.2.14",
"lodash": "~2.4.1",
"d3": "~3.4.8",
"angular-route": "~1.2.14",
"gridster": "~0.5.0",
"angular-mocks": "~1.2.14",
"font-awesome": "~4.0.3",
"requirejs-text": "~2.0.10",
"async": "~0.2.10",
"bootstrap": "~3.1.1",
"jquery": "~2.1.0",
"moment": "~2.5.1",
"require-css": "~0.1.2",
"angular-bootstrap": "~0.10.0",
"jsonpath": "*",
"moment-timezone": "~0.0.3",
"angular-bindonce": "~0.3.1",
"angular-ui-ace": "bower",
"angular-bootstrap": "~0.10.0",
"angular-elastic": "~2.3.3",
"inflection": "~1.3.5",
"FileSaver": "*",
"elasticsearch": "*",
"angular-mocks": "~1.2.14",
"angular-route": "~1.2.14",
"angular-ui-ace": "bower",
"async": "~0.2.10",
"bluebird": "~2.1.3",
"bootstrap": "~3.1.1",
"d3": "~3.4.8",
"elasticsearch": "*",
"Faker": "~1.1.0",
"FileSaver": "*",
"font-awesome": "~4.0.3",
"gridster": "~0.5.0",
"inflection": "~1.3.5",
"jquery": "~2.1.0",
"jsonpath": "*",
"lesshat": "~3.0.2",
"Faker": "~1.1.0"
"lodash": "~2.4.1",
"moment": "~2.5.1",
"moment-timezone": "~0.0.3",
"require-css": "~0.1.2",
"requirejs": "~2.1.10",
"requirejs-text": "~2.0.10"
},
"devDependencies": {}
}

View file

@ -9,7 +9,7 @@
"bluebird": "~2.0.7",
"connect": "~2.19.5",
"event-stream": "~3.1.5",
"expect.js": "~0.2.0",
"expect.js": "~0.3.1",
"grunt": "~0.4.5",
"grunt-contrib-clean": "~0.5.0",
"grunt-contrib-compress": "~0.9.1",
@ -20,15 +20,21 @@
"grunt-contrib-requirejs": "~0.4.4",
"grunt-contrib-watch": "~0.5.3",
"grunt-mocha": "~0.4.10",
"grunt-replace": "^0.7.9",
"grunt-run": "^0.2.3",
"http-proxy": "~1.1.4",
"husky": "~0.6.0",
"istanbul": "~0.2.4",
"load-grunt-config": "~0.7.0",
"lodash": "~2.4.1",
"mkdirp": "^0.5.0",
"mocha": "~1.20.1",
"path-browserify": "0.0.0",
"progress": "^1.1.8",
"request": "^2.40.0",
"requirejs": "~2.1.14",
"rjs-build-analysis": "0.0.3"
"rjs-build-analysis": "0.0.3",
"tar": "^1.0.1"
},
"scripts": {
"test": "grunt test",

View file

@ -1,30 +0,0 @@
require "java"
require "warbler"
HERE = File.expand_path(File.dirname(__FILE__))
task "default" => "jar:run"
namespace "jar" do
desc "Run the project jar file"
task "run" => "jar" do
exec("cd #{HERE} && rm -rf /tmp/kibana* && cp kibana.jar /tmp && cd /tmp && unzip -o kibana.jar 'kibana/public/*' && unzip -o kibana.jar kibana/config/web.ru && env PUBLIC_FOLDER=/tmp/kibana/public java -server -jar kibana.jar kibana/config/web.ru")
end
end
desc "Create the project jar file"
task "jar" do
system("cd #{HERE} && jruby -S warble")
end
# desc "Watch for changes"
# task "watch" => "vendor/fswatch" do
# system("killall fswatch")
# system("fswatch #{HERE}/lib \"bash -c \\\"kill -SIGUSR2 \\\`ps u|grep [o]rg.jruby.Main|grep bin/puma|awk {\'print \\\$2\'}\\\`\\\"\" &")
# end
desc "Run the project from jruby"
# task "run" => "watch" do
task "run" do
exec("cd #{HERE} && jruby -S bundle exec jruby -S bin/kibana config/web.ru")
end

View file

@ -1,38 +0,0 @@
#!/usr/bin/env ruby
#
# This file was generated by RubyGems.
#
# The application 'puma' is installed as part of a gem, and
# this file is here to facilitate running it.
#
require 'rubygems'
version = ">= 0"
HERE = File.expand_path(File.dirname(__FILE__))
if ARGV.first
str = ARGV.first
str = str.dup.force_encoding("BINARY") if str.respond_to? :force_encoding
if str =~ /\A_(.*)_\z/
version = $1
ARGV.shift
end
end
# Include the puma config unless it's been overriden
unless ARGV.include?('-C')
ARGV << '-C'
ARGV << "#{HERE}/../config/puma.rb"
end
# Include the rack config if it hasn't been included
if (ARGV.grep(/config\/web\.ru/)).empty?
ARGV << "#{HERE}/../config/web.ru"
end
print ARGV, "\n"
gem 'puma', version
load Gem.bin_path('puma', 'puma', version)

View file

@ -1,2 +0,0 @@
port 8000

View file

@ -1,12 +0,0 @@
# Add the libs directory to the load path
ROOT = File.expand_path("#{File.dirname(__FILE__)}/../")
$LOAD_PATH.unshift(ROOT)
require "rubygems"
require "bundler/setup"
# Require the application
require "#{ROOT}/lib/app"
# Run the application
run Kibana::App

View file

@ -1,27 +0,0 @@
# Add the root of the project to the $LOAD_PATH, For some reason it seems
# to be getting lost when we use warble to make the jar. This fixes it :D
$LOAD_PATH.unshift(ROOT)
require "rack/reverse_proxy"
require "routes/home"
require "routes/api"
module Kibana
class App < Sinatra::Base
configure do
set :root, ROOT
set :public_folder, "#{ROOT}/public"
set :httponly, true
end
# Rack middleware goes here
use Rack::ReverseProxy do
reverse_proxy /^\/elasticsearch(.*)$/, 'http://localhost:9200$1'
end
# Routes go here
use Routes::Home
use Routes::Api
end
end

View file

@ -1,22 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Welcome to the Future Home of Kibana</title>
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-12 text-center" style="padding-top: 40px;">
<h1>Welcome to the Future Home of Kibana</h1>
<p>This is the server component of Kibana. It's just a quick prototype of the things we need.</p>
<p><a href="/test.html">Static Server</a></p>
<p><a href="/api/foo">Server Side APIs</a></p>
<p><a href="/elasticsearch">Elasticsearch Proxy</a></p>
<h3>Much more coming soon...</h3>
</div>
</div>
</div>
</body>
</html>

View file

@ -1,9 +0,0 @@
<html>
<head>
<title>This is a test file</title>
</head>
<body>
<h1>This is a test</h1>
<p>This should work outside of anything else.</p>
</body>
</html>

View file

@ -1,16 +0,0 @@
require "routes/base"
require "lib/helpers"
module Kibana
module Routes
class Api < Base
helpers Kibana::Helpers
get "/api/foo" do
json :foo => doSomething()
end
end
end
end

View file

@ -1,15 +0,0 @@
require "sinatra/base"
require "sinatra/json"
module Kibana
module Routes
class Base < Sinatra::Base
helpers Sinatra::JSON
configure do
# Confirgure stuffs here
end
end
end
end

View file

@ -1,13 +0,0 @@
require "routes/base"
module Kibana
module Routes
class Home < Base
get "/" do
File.read(File.join(ROOT, 'public', 'index.html'))
end
end
end
end

View file

@ -25,7 +25,7 @@ define(function (require) {
require('routes')
.when('/dashboard', {
templateUrl: 'kibana/apps/dashboard/index.html',
template: require('text!apps/dashboard/index.html'),
resolve: {
dash: function (savedDashboards) {
return savedDashboards.get();
@ -33,7 +33,7 @@ define(function (require) {
}
})
.when('/dashboard/:id', {
templateUrl: 'kibana/apps/dashboard/index.html',
template: require('text!apps/dashboard/index.html'),
resolve: {
dash: function (savedDashboards, Notifier, $route, $location, courier) {
return savedDashboards.get($route.current.params.id)
@ -42,7 +42,7 @@ define(function (require) {
}
});
app.directive('dashboardApp', function (Notifier, courier, savedVisualizations, appStateFactory, timefilter) {
app.directive('dashboardApp', function (Notifier, courier, savedVisualizations, appStateFactory, timefilter, kbnUrl) {
return {
controller: function ($scope, $route, $routeParams, $location, configFile) {
var notify = new Notifier({
@ -113,7 +113,7 @@ define(function (require) {
.then(function () {
notify.info('Saved Dashboard as "' + dash.title + '"');
if (dash.id !== $routeParams.id) {
$location.url('/dashboard/' + encodeURIComponent(dash.id));
kbnUrl.change('/dashboard/{{id}}', {id: dash.id});
}
})
.catch(notify.fatal);

View file

@ -13,7 +13,7 @@ define(function (require) {
});
// This is the only thing that gets injected into controllers
module.service('savedDashboards', function (Promise, SavedDashboard, config, es) {
module.service('savedDashboards', function (Promise, SavedDashboard, config, es, kbnUrl) {
// Returns a single dashboard by ID, should be the name of the dashboard
this.get = function (id) {
@ -23,7 +23,7 @@ define(function (require) {
};
this.urlFor = function (id) {
return '#/dashboard/' + encodeURIComponent(id);
return kbnUrl.eval('#/dashboard/{{id}}', {id: id});
};
this.delete = function (ids) {

View file

@ -46,7 +46,7 @@ define(function (require) {
});
app.controller('discover', function ($scope, config, courier, $route, $window, savedSearches, savedVisualizations,
Notifier, $location, globalState, appStateFactory, timefilter, Promise, Private) {
Notifier, $location, globalState, appStateFactory, timefilter, Promise, Private, kbnUrl) {
var Vis = Private(require('components/vis/vis'));
var SegmentedFetch = Private(require('apps/discover/_segmented_fetch'));
@ -110,7 +110,8 @@ define(function (require) {
$state.index = config.get('defaultIndex');
} else {
notify.warning(reason + 'Please set a default index to continue.');
$location.url('/settings/indices');
kbnUrl.change('/settings/indices');
return;
}
}
@ -227,7 +228,7 @@ define(function (require) {
.then(function () {
notify.info('Saved Data Source "' + savedSearch.title + '"');
if (savedSearch.id !== $route.current.params.id) {
$location.url(globalState.writeToUrl('/discover/' + encodeURIComponent(savedSearch.id)));
kbnUrl.change('/discover/{{id}}', { id: savedSearch.id });
}
});
})
@ -388,7 +389,7 @@ define(function (require) {
};
$scope.newQuery = function () {
$location.url('/discover');
kbnUrl.change('/discover');
};
$scope.updateDataSource = function () {

View file

@ -19,10 +19,8 @@ define(function (require) {
scope: {
fields: '=',
toggle: '=',
refresh: '=',
data: '=',
state: '=',
updateFilterInQuery: '=filter',
searchSource: '='
},
template: html,
@ -165,8 +163,7 @@ define(function (require) {
count: 5,
grouped: false
});
var indexPattern = $scope.searchSource.get('index');
indexPattern.popularizeField(field.name, 1);
$scope.increaseFieldCounter(field, 1);
} else {
delete field.details;
}

View file

@ -29,23 +29,28 @@ define(function (require) {
if ($scope.mapping[column] && !$scope.mapping[column].indexed) return;
var sorting = $scope.sorting;
var defaultClass = ['fa', 'fa-sort', 'table-header-sortchange'];
if (!sorting) return [];
if (!sorting) return defaultClass;
if (column === sorting[0]) {
return ['fa', sorting[1] === 'asc' ? 'fa-sort-up' : 'fa-sort-down'];
} else {
return ['fa', 'fa-sort', 'table-header-sortchange'];
return defaultClass;
}
};
$scope.moveLeft = function (column) {
var index = _.indexOf($scope.columns, column);
if (index === 0) return;
_.move($scope.columns, index, --index);
};
$scope.moveRight = function (column) {
var index = _.indexOf($scope.columns, column);
if (index === $scope.columns.length - 1) return;
_.move($scope.columns, index, ++index);
};
@ -112,7 +117,6 @@ define(function (require) {
return {
restrict: 'A',
scope: {
fields: '=',
columns: '=',
filtering: '=',
mapping: '=',
@ -139,27 +143,14 @@ define(function (require) {
$scope.maxLength = 250;
}
// for now, rows are "tracked" by their index, but this could eventually
// be configured so that changing the order of the rows won't prevent
// them from staying open on update
function rowId(row) {
var id = $scope.rows.indexOf(row);
return ~id ? id : null;
}
// inverse of rowId()
function rowForId(id) {
return $scope.rows[id];
}
// toggle display of the rows details, a full list of the fields from each row
$scope.toggleRow = function (row, event) {
$scope.toggleRow = function () {
var row = $scope.row;
var id = row._id;
$scope.open = !$scope.open;
var $tr = $(event.delegateTarget.parentElement);
var $tr = element;
var $detailsTr = $tr.next();
///
@ -169,7 +160,7 @@ define(function (require) {
$detailsTr.toggle($scope.open);
// Change the caret icon
var $toggleIcon = $($(event.delegateTarget).children('i')[0]);
var $toggleIcon = $(element.children().first().find('i')[0]);
$toggleIcon.toggleClass('fa-caret-down');
$toggleIcon.toggleClass('fa-caret-right');
@ -207,7 +198,12 @@ define(function (require) {
$scope.filtering(field, row._source[field] || row[field], operation);
};
$scope.$watch('columns', function () {
$scope.$watch('columns', function (columns) {
element.empty();
createSummaryRow($scope.row, $scope.row._id);
});
$scope.$watch('timefield', function (timefield) {
element.empty();
createSummaryRow($scope.row, $scope.row._id);
});
@ -216,7 +212,7 @@ define(function (require) {
function createSummaryRow(row, id) {
var expandTd = $('<td>').html('<i class="fa fa-caret-right"></span>')
.attr('ng-click', 'toggleRow(row, $event)');
.attr('ng-click', 'toggleRow()');
$compile(expandTd)($scope);
element.append(expandTd);
@ -240,11 +236,7 @@ define(function (require) {
*/
function _displayField(el, row, field, truncate) {
var val = _getValForField(row, field, truncate);
if (val instanceof DOMNode) {
el.append(val);
} else {
el.text(val);
}
el.text(val);
return el;
}
@ -266,17 +258,9 @@ define(function (require) {
// undefined and null should just be an empty string
val = (val == null) ? '' : val;
// truncate
// truncate the column text, not the details
if (typeof val === 'string' && val.length > $scope.maxLength) {
if (untruncate) {
var complete = val;
val = document.createElement('kbn-truncated');
val.setAttribute('orig', complete);
val.setAttribute('length', $scope.maxLength);
val = $compile(val)($scope)[0];// return the actual element
} else {
val = val.substring(0, $scope.maxLength) + '...';
}
val = val.substring(0, $scope.maxLength) + '...';
}
return val;

View file

@ -3,7 +3,8 @@
<tbody>
<tr ng-repeat="row in rows |limitTo:limit track by row._index+row._id"
kbn-table-row="row"
columns="columns" mapping="mapping" sorting="sorting" timefield="timefield" max-length="maxLength" filtering="filtering"></tr>
columns="columns" mapping="mapping" sorting="sorting" timefield="timefield" max-length="maxLength" filtering="filtering"
class="discover-table-row"></tr>
</tbody>
</table>
<kbn-infinite-scroll more="addRows"></kbn-infinite-scroll>

View file

@ -3,7 +3,7 @@
<span ng-click="sort(timefield)">Time <i ng-class="headerClass(timefield)"></i></span>
</th>
<th ng-repeat="name in columns">
<span ng-click="sort(name)">
<span ng-click="sort(name)" class="table-header-name">
{{name}} <i ng-class="headerClass(name)"></i>
</span>
<span class="table-header-move">

View file

@ -15,7 +15,7 @@ define(function (require) {
title: 'searches'
});
module.service('savedSearches', function (Promise, config, configFile, es, createNotifier, SavedSearch) {
module.service('savedSearches', function (Promise, config, configFile, es, createNotifier, SavedSearch, kbnUrl) {
var notify = createNotifier({
@ -27,7 +27,7 @@ define(function (require) {
};
this.urlFor = function (id) {
return '#/discover/' + encodeURIComponent(id);
return kbnUrl.eval('#/discover/{{id}}', {id: id});
};
this.delete = function (ids) {

View file

@ -2,8 +2,8 @@
<nav class="navbar navbar-default navbar-static-top subnav">
<div class="container-fluid">
<ul class="nav navbar-nav">
<li ng-repeat="s in sections" ng-class="s.class">
<a class="navbar-link" ng-href="{{s.url}}">{{s.display}}</a>
<li ng-repeat="section in sections" ng-class="section.class">
<a class="navbar-link" ng-href="{{section.url}}">{{section.display}}</a>
</li>
</ul>
</div>

View file

@ -14,7 +14,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) {
.directive('kbnSettingsIndices', function ($route, config, kbnUrl) {
return {
restrict: 'E',
transclude: true,
@ -31,7 +31,7 @@ define(function (require) {
.map(function (id) {
return {
id: id,
url: '#/settings/indices/' + encodeURIComponent(id),
url: kbnUrl.eval('#/settings/indices/{{id}}', {id: id}),
class: 'sidebar-item-title ' + ($scope.edittingId === id ? 'active' : ''),
default: $scope.defaultIndex === id
};

View file

@ -41,7 +41,7 @@ define(function (require) {
'kibana/notify',
'kibana/courier'
])
.controller('VisEditor', function ($scope, $route, timefilter, appStateFactory, $location, globalState, $timeout) {
.controller('VisEditor', function ($scope, $route, timefilter, appStateFactory, $location, kbnUrl, $timeout) {
var _ = require('lodash');
var angular = require('angular');
@ -157,11 +157,7 @@ define(function (require) {
if (savedVis.id === $route.current.params.id) return;
$location.url(
globalState.writeToUrl(
'/visualize/edit/' + encodeURIComponent(savedVis.id)
)
);
kbnUrl.change('/visualize/edit/{{id}}', {id: savedVis.id});
}, notify.fatal);
};

View file

@ -11,7 +11,7 @@ define(function (require) {
title: 'visualizations'
});
app.service('savedVisualizations', function (Promise, es, config, SavedVis, Private, Notifier) {
app.service('savedVisualizations', function (Promise, es, config, SavedVis, Private, Notifier, kbnUrl) {
var visTypes = Private(require('components/vis_types/index'));
var notify = new Notifier({
location: 'saved visualization service'
@ -22,7 +22,7 @@ define(function (require) {
};
this.urlFor = function (id) {
return '#/visualize/edit/' + encodeURIComponent(id);
return kbnUrl.eval('#/visualize/edit/{{id}}', {id: id});
};
this.delete = function (ids) {

View file

@ -24,9 +24,9 @@ define(function (require) {
}
});
module.controller('VisualizeWizardStep1', function ($route, $scope, $location, timefilter) {
module.controller('VisualizeWizardStep1', function ($route, $scope, $location, timefilter, kbnUrl) {
$scope.step2WithSearchUrl = function (hit) {
return '#/visualize/step/2?savedSearchId=' + encodeURIComponent(hit.id);
return kbnUrl.eval('#/visualize/step/2?savedSearchId={{id}}', {id: hit.id});
};
timefilter.enabled = false;
@ -38,7 +38,7 @@ define(function (require) {
$scope.$watch('indexPattern.selection', function (pattern) {
if (!pattern) return;
$location.url('/visualize/step/2?indexPattern=' + encodeURIComponent(pattern));
kbnUrl.change('/visualize/step/2?indexPattern={{pattern}}', {pattern: pattern});
});
});

View file

@ -8,7 +8,6 @@ define(function (require) {
if (query.query_string && query.query_string.query) {
return query.query_string.query;
}
return JSON.stringify(query);
}
@ -32,4 +31,4 @@ define(function (require) {
]
});
};
});
});

View file

@ -3,7 +3,7 @@ define(function (require) {
'kibana/notify'
]);
var configFile = require('config_file');
var configFile = JSON.parse(require('text!config'));
// allow the rest of the app to get the configFile easily
module.constant('configFile', configFile);

View file

@ -1,7 +1,7 @@
define(function (require) {
var errors = require('errors');
return function RedirectWhenMissingFn($location, $route, globalState, Notifier) {
return function RedirectWhenMissingFn($location, kbnUrl, globalState, Notifier) {
var SavedObjectNotFound = errors.SavedObjectNotFound;
var notify = new Notifier();
@ -27,7 +27,7 @@ define(function (require) {
if (!url) url = '/';
notify.error(err);
$route.changeUrl(globalState.writeToUrl(url));
kbnUrl.change(url);
return;
};
};

View file

@ -1,5 +1,5 @@
define(function (require) {
return function EnsureSomeIndexPatternsFn(Private, Notifier, $location, $route) {
return function EnsureSomeIndexPatternsFn(Private, Notifier, $location, kbnUrl) {
var errors = require('errors');
var notify = new Notifier();
@ -7,7 +7,7 @@ define(function (require) {
return function promiseHandler(patterns) {
if (!patterns || patterns.length === 0) {
// notify.warning(new errors.NoDefinedIndexPatterns());
$route.change('/settings/indices');
kbnUrl.changePath('/settings/indices');
}
return patterns;

View file

@ -0,0 +1,68 @@
define(function (require) {
return function ReflowWatcherService(Private, $rootScope, $http) {
var angular = require('angular');
var $ = require('jquery');
var _ = require('lodash');
var EventEmitter = Private(require('factories/events'));
var $body = $(document.body);
var $window = $(window);
var MOUSE_EVENTS = 'mouseup';
var WINDOW_EVENTS = 'resize';
_(ReflowWatcher).inherits(EventEmitter);
/**
* Watches global activity which might hint at a change in the content, which
* in turn provides a hint to resizers that they should check their size
*/
function ReflowWatcher() {
ReflowWatcher.Super.call(this);
// bound version of trigger that can be used as a handler
this.trigger = _.bind(this.trigger, this);
this._emitReflow = _.bind(this._emitReflow, this);
// list of functions to call that will unbind our watchers
this._unwatchers = [
$rootScope.$watchCollection(function () {
return $http.pendingRequests;
}, this.trigger)
];
$body.on(MOUSE_EVENTS, this.trigger);
$window.on(WINDOW_EVENTS, this.trigger);
}
/**
* Simply emit reflow, but in a way that can be bound and passed to
* other functions. Using _.bind caused extra arguments to be added, and
* then emitted to other places. No Bueno
*
* @return {void}
*/
ReflowWatcher.prototype._emitReflow = function () {
this.emit('reflow');
};
/**
* Emit the "reflow" event in the next tick of the digest cycle
* @return {void}
*/
ReflowWatcher.prototype.trigger = function () {
$rootScope.$evalAsync(this._emitReflow);
};
/**
* Signal to the ReflowWatcher that it should clean up it's listeners
* @return {void}
*/
ReflowWatcher.prototype.destroy = function () {
$body.off(MOUSE_EVENTS, this.trigger);
$window.off(WINDOW_EVENTS, this.trigger);
_.callEach(this._unwatchers);
};
return new ReflowWatcher();
};
});

View file

@ -21,8 +21,8 @@ define(function (require) {
template: html,
controller: function ($scope) {
var init = function () {
$scope.formatRelative();
$scope.setMode($scope.mode);
$scope.formatRelative();
};
$scope.format = 'MMMM Do YYYY, HH:mm:ss.SSS';
@ -136,4 +136,4 @@ define(function (require) {
};
});
});
});

View file

@ -0,0 +1,97 @@
define(function (require) {
require('filters/uriescape');
require('filters/rison');
var _ = require('lodash');
var rison = require('utils/rison');
var location = require('modules').get('kibana/url');
location.service('kbnUrl', function ($route, $location, $rootScope, globalState, $parse) {
var self = this;
self.reloading = false;
self.change = function (url, paramObj, forceReload) {
self._changeLocation('url', url, paramObj, forceReload);
};
self.changePath = function (url, paramObj, forceReload) {
self._changeLocation('path', url, paramObj, forceReload);
};
self._changeLocation = function (type, url, paramObj, forceReload) {
var doReload = false;
if (_.isBoolean(paramObj)) {
forceReload = paramObj;
paramObj = undefined;
}
url = self.eval(url, paramObj);
// path change
if (type === 'path') {
if (url !== $location.path()) {
$location.path(globalState.writeToUrl(url));
doReload = (!self.matches(url));
}
// default to url change
} else {
if (url !== $location.url()) {
$location.url(globalState.writeToUrl(url));
doReload = (!self.matches(url));
}
}
if (forceReload || doReload) {
self.reload();
}
};
self.eval = function (url, paramObj) {
paramObj = paramObj || {};
return parseUrlPrams(url, paramObj);
};
self.matches = function (url) {
var route = $route.current.$$route;
if (!route || !route.regexp) return false;
return route.regexp.test(url);
};
$rootScope.$on('$routeUpdate', reloadingComplete);
$rootScope.$on('$routeChangeStart', reloadingComplete);
function parseUrlPrams(url, paramObj) {
return url.replace(/\{\{([^\}]+)\}\}/g, function (match, expr) {
// remove filters
var key = expr.split('|')[0].trim();
// verify that the expression can be evaluated
var p = $parse(key)(paramObj);
// if evaluation can't be made, throw
if (_.isUndefined(p)) {
throw new Error('Replacement failed, unresolved expression: ' + expr);
}
// append uriescape filter if not included
if (expr.indexOf('uriescape') === -1) {
expr += '|uriescape';
}
return $parse(expr)(paramObj);
});
}
self.reload = function () {
if (!self.reloading) {
$route.reload();
self.reloading = true;
}
};
function reloadingComplete() {
self.reloading = false;
}
});
});

View file

@ -10,7 +10,7 @@ define(function (require) {
shareYAxis: true,
addTooltip: true,
addLegend: true,
addEvents: true
addBrushing: true
},
schemas: new Schemas([
{

View file

@ -9,9 +9,10 @@ define(function (require) {
/*
* Specifies the visualization layout for column charts.
*
* This is done using an array of objects. Each object has
* a `parent` DOM element, a DOM `type` (e.g. div, svg, etc),
* and a `class`. These are required attributes.
* This is done using an array of objects. The first object has
* a `parent` DOM element, a DOM `type` (e.g. div, svg, etc),
* and a `class` (required). Each child can omit the parent object,
* but must include a type and class.
*
* Optionally, you can specify `datum` to be bound to the DOM
* element, a `splits` function that divides the selected element
@ -21,6 +22,7 @@ define(function (require) {
* Objects in children arrays are children of the current object and return
* DOM elements which are children of their respective parent element.
*/
return function (el, data) {
if (!el || !data) {
throw new Error('Both an el and data need to be specified');
@ -34,28 +36,23 @@ define(function (require) {
datum: data,
children: [
{
parent: 'vis-wrapper',
type: 'div',
class: 'y-axis-col-wrapper',
children: [
{
parent: 'y-axis-col-wrapper',
type: 'div',
class: 'y-axis-col',
children: [
{
parent: 'y-axis-col',
type: 'div',
class: 'y-axis-title'
},
{
parent: 'y-axis-col',
type: 'div',
class: 'y-axis-chart-title',
splits: chartTitleSplit
},
{
parent: 'y-axis-col',
type: 'div',
class: 'y-axis-div-wrapper',
splits: yAxisSplit
@ -63,42 +60,35 @@ define(function (require) {
]
},
{
parent: 'y-axis-col-wrapper',
type: 'div',
class: 'y-axis-spacer-block'
}
]
},
{
parent: 'vis-wrapper',
type: 'div',
class: 'vis-col-wrapper',
children: [
{
parent: 'vis-col-wrapper',
type: 'div',
class: 'chart-wrapper',
splits: chartSplit
},
{
parent: 'vis-col-wrapper',
type: 'div',
class: 'x-axis-wrapper',
children: [
{
parent: 'x-axis-wrapper',
type: 'div',
class: 'x-axis-div-wrapper',
splits: xAxisSplit
},
{
parent: 'x-axis-wrapper',
type: 'div',
class: 'x-axis-chart-title',
splits: chartTitleSplit
},
{
parent: 'x-axis-wrapper',
type: 'div',
class: 'x-axis-title'
}
@ -107,12 +97,10 @@ define(function (require) {
]
},
{
parent: 'vis-wrapper',
type: 'div',
class: 'legend-col-wrapper'
},
{
parent: 'vis-wrapper',
type: 'div',
class: 'k4tip'
}

View file

@ -12,12 +12,21 @@ define(function (require) {
* arguments:
* data => Provided data object
*/
function Data(data) {
function Data(data, attr) {
if (!(this instanceof Data)) {
return new Data(data);
return new Data(data, attr);
}
this.data = data;
this._attr = attr;
// d3 stack function
this._attr = _.defaults(attr || {}, {
offset: 'zero',
stack: d3.layout.stack()
.x(function (d) { return d.x; })
.y(function (d) { return d.y; })
.offset(this._attr.offset)
});
}
// Return the actual x and y data values
@ -53,6 +62,51 @@ define(function (require) {
return values;
};
Data.prototype.shouldBeStacked = function (series) {
// Series should be an array
if (series.length > 1) {
return true;
}
return false;
};
// Calculate the max y value from this.dataArray
Data.prototype.getYMaxValue = function () {
var self = this;
var arr = [];
// for each object in the dataArray,
// push the calculated y value to the initialized array (arr)
_.forEach(this.flatten(), function (series) {
arr.push(self.getYStackMax(series));
});
// return the largest value from the array
return _.max(arr);
};
Data.prototype.stackData = function (series) {
// Determine if the data should be stacked
if (this.shouldBeStacked(series)) {
// if true, stack data
return this._attr.stack(series);
}
return series;
};
Data.prototype.getYStackMax = function (series) {
// Return the calculated y value
return d3.max(this.stackData(series), function (data) {
return d3.max(data, function (d) {
// if stacked, need to add d.y0 + d.y for the y value
if (d.y0) {
return d.y0 + d.y;
}
return d.y;
});
});
};
// Inject zeros into the data
Data.prototype.injectZeros = function () {
return injectZeros(this.data);

View file

@ -1,6 +1,5 @@
define(function (require) {
var _ = require('lodash');
var $ = require('jquery');
return function HandlerBaseClass(d3, Private) {
var Data = Private(require('components/vislib/lib/data'));
@ -25,7 +24,7 @@ define(function (require) {
return new Handler(vis);
}
this.data = new Data(vis.data);
this.data = new Data(vis.data, vis._attr);
this.vis = vis;
this.el = vis.el;
this.ChartClass = vis.ChartClass;
@ -34,7 +33,6 @@ define(function (require) {
});
// Visualization constructors
// Add the visualization layout
this.layout = new Layout(this.el, this.data.injectZeros(), this._attr.type);
@ -60,8 +58,7 @@ define(function (require) {
// add a y axis
this.yAxis = new YAxis({
el: this.el,
chartData: this.data.chartData(),
dataArray: this.data.flatten(),
yMax: this.data.getYMaxValue(),
_attr: this._attr
});
@ -72,7 +69,7 @@ define(function (require) {
this.chartTitle = new ChartTitle(this.el);
// Array of objects to render to the visualization
this.renderArray = [
this.renderArray = _.filter([
this.layout,
this.legend,
this.tooltip,
@ -80,7 +77,7 @@ define(function (require) {
this.chartTitle,
this.yAxis,
this.xAxis
];
], Boolean);
}
// Render the visualization
@ -91,7 +88,7 @@ define(function (require) {
// Render objects in the render array
_.forEach(this.renderArray, function (property) {
if (property && typeof property.render === 'function') {
if (typeof property.render === 'function') {
property.render();
}
});
@ -137,8 +134,7 @@ define(function (require) {
this.removeAll(this.el);
// Return an error wrapper DOM element
return d3.select(this.el)
.append('div')
return d3.select(this.el).append('div')
// class name needs `chart` in it for the polling checkSize function
// to continuously call render on resize
.attr('class', 'chart error')

View file

@ -61,11 +61,12 @@ define(function (require) {
obj.parent = '.' + obj.parent;
}
var el = this.appendElem(obj.parent, obj.type, obj.class);
// append child
var childEl = this.appendElem(obj.parent, obj.type, obj.class);
if (obj.datum) {
// Bind datum to the element
el.datum(obj.datum);
childEl.datum(obj.datum);
}
if (obj.splits) {
@ -74,11 +75,20 @@ define(function (require) {
}
if (obj.children) {
// Recursively pass object to createLayout
// Creating the parent elem for the child nodes
var newParent = d3.select(this.el).select('.' + obj.class)[0][0];
_.forEach(obj.children, function (obj) {
if (!obj.parent) {
obj.parent = newParent;
}
});
// Recursively pass children to createLayout
this.createLayout(obj.children);
}
return el;
return childEl;
};
// Appends a `type` of DOM element to `el` and gives it a class name attribute `className`

View file

@ -66,7 +66,7 @@ define(function (require) {
.append('li')
.attr('class', function (d) {
// class names reflect the color assigned to the labels
return 'color ' + self.classify(args.color(d));
return 'color ' + self.colorToClass(args.color(d));
})
.html(function (d) {
// return the appropriate color for each dot
@ -75,7 +75,7 @@ define(function (require) {
};
// Create a class name based on the colors assigned to each label
Legend.prototype.classify = function (name) {
Legend.prototype.colorToClass = function (name) {
return 'c' + name.replace(/[#]/g, '');
};
@ -112,15 +112,12 @@ define(function (require) {
visEl.selectAll('.color')
.on('mouseover', function (d) {
var liClass = '.' + self.classify(self.color(d));
var liClass = '.' + self.colorToClass(self.color(d));
visEl.selectAll('.color').style('opacity', self._attr.blurredOpacity);
// select series on chart
visEl.selectAll(liClass).style('opacity', self._attr.focusOpacity);
});
visEl.selectAll('.color')
})
.on('mouseout', function () {
visEl.selectAll('.color').style('opacity', self._attr.defaultOpacity);
});

View file

@ -0,0 +1,209 @@
define(function (require) {
return function ResizeCheckerFactory(Private, Notifier) {
var $ = require('jquery');
var _ = require('lodash');
var EventEmitter = Private(require('factories/events'));
var reflowWatcher = Private(require('components/reflow_watcher'));
var sequencer = require('utils/sequencer');
var SCHEDULE_LONG = ResizeChecker.SCHEDULE_LONG = sequencer.createEaseOut(
250, // shortest delay
10000, // longest delay
150 // tick count
);
var SCHEDULE_SHORT = ResizeChecker.SCHEDULE_SHORT = sequencer.createEaseIn(
5, // shortest delay
500, // longest delay
100 // tick count
);
// maximum ms that we can delay emitting 'resize'. This is only used
// to debounce resizes when the size of the element is constantly changing
var MS_MAX_RESIZE_DELAY = ResizeChecker.MS_MAX_RESIZE_DELAY = 500;
/**
* Checks the size of an element on a regular basis. Provides
* an event that is emited when the element has changed size.
*
* @class ResizeChecker
* @param {HtmlElement} el - the element to track the size of
*/
_(ResizeChecker).inherits(EventEmitter);
function ResizeChecker(el) {
ResizeChecker.Super.call(this);
this.$el = $(el);
this.notify = new Notifier({ location: 'Vislib ResizeChecker ' + _.uniqueId() });
this.saveSize();
this.check = _.bind(this.check, this);
this.check();
this.onReflow = _.bind(this.onReflow, this);
reflowWatcher.on('reflow', this.onReflow);
}
ResizeChecker.prototype.onReflow = function () {
this.startSchedule(SCHEDULE_LONG);
};
/**
* Read the size of the element
*
* @method read
* @return {object} - an object with keys `w` (width) and `h` (height)
*/
ResizeChecker.prototype.read = function () {
return {
w: this.$el.width(),
h: this.$el.height()
};
};
/**
* Save the element size, preventing it from being considered as an
* update.
*
* @method save
* @param {object} [size] - optional size to save, otherwise #read() is called
* @return {boolean} - true if their was a change in the new
*/
ResizeChecker.prototype.saveSize = function (size) {
if (!size) size = this.read();
if (this._equalsSavedSize(size)) {
return false;
}
this._savedSize = size;
return true;
};
/**
* Determine if a given size matches the currently saved size.
*
* @private
* @method _equalsSavedSize
* @param {object} a - an object that matches the return value of #read()
* @return {boolean} - true if the passed in value matches the saved size
*/
ResizeChecker.prototype._equalsSavedSize = function (a) {
var b = this._savedSize || {};
return a.w === b.w && a.h === b.h;
};
/**
* Read the time that the dirty state last changed.
*
* @method lastDirtyChange
* @return {timestamp} - the unix timestamp (in ms) of the last update
* to the dirty state
*/
ResizeChecker.prototype.lastDirtyChange = function () {
return this._dirtyChangeStamp;
};
/**
* Record the dirty state
*
* @method saveDirty
* @param {boolean} val
* @return {boolean} - true if the dirty state changed by this save
*/
ResizeChecker.prototype.saveDirty = function (val) {
val = !!val;
if (val === this._isDirty) return false;
this._isDirty = val;
this._dirtyChangeStamp = Date.now();
return true;
};
/**
* The check routine that executes regularly and will reschedule itself
* to run again in the future. It determines the state of the elements
* size and decides when to emit the "update" event.
*
* @method check
* @return {void}
*/
ResizeChecker.prototype.check = function () {
var newSize = this.read();
var dirty = this.saveSize(newSize);
var dirtyChanged = this.saveDirty(dirty);
var doneDirty = !dirty && dirtyChanged;
var muchDirty = dirty && (this.lastDirtyChange() - Date.now() > MS_MAX_RESIZE_DELAY);
if (doneDirty || muchDirty) {
this.emit('resize', newSize);
}
// if the dirty state is unchanged, continue using the previous schedule
if (!dirtyChanged) {
return this.continueSchedule();
}
// when the state changes start a new schedule. Use a schedule that quickly
// slows down if it is unknown wether there are will be additional changes
return this.startSchedule(dirty ? SCHEDULE_SHORT : SCHEDULE_LONG);
};
/**
* Start running a new schedule, using one of the SCHEDULE_* constants.
*
* @method startSchedule
* @param {integer[]} schedule - an array of millisecond times that should
* be used to schedule calls to #check();
* @return {integer} - the id of the next timer
*/
ResizeChecker.prototype.startSchedule = function (schedule) {
this._tick = -1;
this._currentSchedule = schedule;
return this.continueSchedule();
};
/**
* Continue running the current schedule. MUST BE CALLED AFTER #startSchedule()
*
* @method continueSchedule
* @return {integer} - the id of the next timer
*/
ResizeChecker.prototype.continueSchedule = function () {
clearTimeout(this._timerId);
if (this._tick < this._currentSchedule.length - 1) {
// at the end of the schedule, don't progress any further but repeat the last value
this._tick += 1;
}
var check = this.check; // already bound
var tick = this._tick;
var notify = this.notify;
var ms = this._currentSchedule[this._tick];
return (this._timerId = setTimeout(function () {
check();
}, ms));
};
/**
* Signal that the ResizeChecker should shutdown.
*
* Cleans up it's listeners and timers.
*
* @method destroy
* @return {void}
*/
ResizeChecker.prototype.destroy = function () {
reflowWatcher.off('reflow', this.check);
clearTimeout(this._timerId);
};
return ResizeChecker;
};
});

View file

@ -52,60 +52,15 @@ define(function (require) {
// if time, return a time domain
if (ordered && ordered.date) {
// Calculate the min date, max date, and time interval;
return this.getTimeDomain(scale, this.xValues, ordered);
return this.getTimeDomain(scale, ordered);
}
// return a nominal domain, i.e. array of x values
return this.getOrdinalDomain(scale, this.xValues);
};
// Returns a time domain
XAxis.prototype.getTimeDomain = function (scale, xValues, ordered) {
var maxXValue = d3.max(xValues);
var timeInterval = ordered.interval;
// Take the min of the xValues or the min date sent on the ordered object
var minDate = Math.min(d3.min(xValues), ordered.min);
// Take the max of the xValues or the max date that sent on the ordered object
var maxDate = +maxXValue <= ordered.max ?
this.calculateMaxDate(ordered.max, +maxXValue, timeInterval) : +maxXValue + timeInterval;
// Add the domain to the scale
scale.domain([minDate, maxDate]);
return scale;
};
// Returns an accurate maxDate
XAxis.prototype.calculateMaxDate = function (orderedDate, maxXValue, interval) {
/*
* Elasticsearch returns bucketed data.
*
* Buckets have a beginning (the start time), an end (the end time),
* and an interval, the width of the bar minus padding.
*
* We need to create an x axis that ends at the end (or end time) of the
* last bucket.
*
* The time stamp values from the maxXValue represent the beginning
* of each bucket. We cannot guarantee that the values passed from
* the ordered.max field represents the end of a bucket.
*
* So, if we were to render either as the cutoff date, then the last bar
* on the far right side of the axis may be partially cut off.
* Therefore, we need to calculate the end time of the last bucket.
*/
// Difference between the ordered.max value and the max x value
var diff = orderedDate - maxXValue;
// if diff is smaller than the interval, but not zero, add the missing
// percentage of the interval back to the ordered.max date
if (diff !== 0 && diff < interval) {
// calculates the appropriate end time
return +orderedDate + ((1 - diff / interval) * interval);
}
// if diff is > than the interval or equals 0 return the ordered.max value
return orderedDate;
XAxis.prototype.getTimeDomain = function (scale, ordered) {
return scale.domain([ordered.min, ordered.max]);
};
// Return a nominal(d3 ordinal) domain
@ -118,12 +73,10 @@ define(function (require) {
XAxis.prototype.getRange = function (scale, ordered, width) {
// if time, return a normal range
if (ordered && ordered.date) {
scale.range([0, width]);
return scale;
return scale.range([0, width]);
}
// if nominal, return rangeBands with a default (0.1) spacer specified
scale.rangeBands([0, width], 0.1);
return scale;
return scale.rangeBands([0, width], 0.1);
};
// Return the x axis scale

View file

@ -9,20 +9,12 @@ define(function (require) {
* Append a y axis to the visualization
* arguments:
* el => reference to DOM element
* chartData => array(s) of x and y value objects
* dataArray => flattened array of all value (x, y) objects
* _attr => visualization attributes
*/
function YAxis(args) {
this.el = args.el;
this.chartData = args.chartData;
this.dataArray = args.dataArray;
this._attr = _.defaults(args._attr || {}, {
// d3 stack function
stack: d3.layout.stack()
.x(function (d) { return d.x; })
.y(function (d) { return d.y; })
});
this.yMax = args.yMax;
this._attr = _.defaults(args._attr || {}, {});
}
_(YAxis.prototype).extend(ErrorHandler.prototype);
@ -32,61 +24,8 @@ define(function (require) {
d3.select(this.el).selectAll('.y-axis-div').call(this.draw());
};
// Determine if data should be stacked
YAxis.prototype.isStacked = function () {
var data = this.chartData;
// if the length of the series array is > 1, stack is true
for (var i = 0; i < data.length; i++) {
if (data[i].series.length > 1) {
return true;
}
}
return false;
};
// Calculate the max y value from this.dataArray
YAxis.prototype.getYMaxValue = function () {
var self = this;
var arr = [];
// for each object in the dataArray,
// push the calculated y value to the initialized array (arr)
_.forEach(this.dataArray, function (series) {
arr.push(self.getYStackMax(series));
});
// return the largest value from the array
return _.max(arr);
};
// Calculate the y value from the value object
YAxis.prototype.getYStackMax = function (series) {
var self = this;
// Determine if the data should be stacked
if (this.isStacked()) {
// if true, stack data
series = this._attr.stack(series);
}
// Return the calculated y value
return d3.max(series, function (data) {
return d3.max(data, function (d) {
// if stacked, need to add d.y0 + d.y for the y value
if (self.isStacked()) {
return d.y0 + d.y;
}
return d.y;
});
});
};
// Return the d3 y scale
YAxis.prototype.getYScale = function (height) {
// save reference to max y value
this.yMax = this.getYMaxValue();
// save reference to y scale
this.yScale = d3.scale.linear()
.domain([0, this.yMax])

View file

@ -4,6 +4,7 @@ define(function (require) {
var _ = require('lodash');
var Handler = Private(require('components/vislib/lib/handler'));
var ResizeChecker = Private(require('components/vislib/lib/resize_checker'));
var Events = Private(require('factories/events'));
var chartTypes = Private(require('components/vislib/vis_types'));
@ -24,6 +25,12 @@ define(function (require) {
this.el = $el.get ? $el.get(0) : $el;
this.ChartClass = chartTypes[config.type];
this._attr = _.defaults(config || {}, {});
// bind the resize function so it can be used as an event handler
this.resize = _.bind(this.resize, this);
this.resizeChecker = new ResizeChecker(this.el);
this.resizeChecker.on('resize', this.resize);
}
// Exposed API for rendering charts.
@ -46,27 +53,11 @@ define(function (require) {
error.message === 'The height and/or width of this container is too small for this chart.') {
this.handler.error(error.message);
} else {
console.group(error.message);
console.log(error.message);
}
}
this.checkSize();
};
// Check for changes to the chart container height and width.
Vis.prototype.checkSize = _.debounce(function () {
if (arguments.length) { return; }
// enable auto-resize
var size = $(this.el).find('.chart').width() + ':' + $(this.el).find('.chart').height();
if (this.prevSize !== size) {
this.resize();
}
this.prevSize = size;
setTimeout(this.checkSize(), 250);
}, 250);
// Resize the chart
Vis.prototype.resize = function () {
if (!this.data) {
@ -78,16 +69,14 @@ define(function (require) {
// Destroy the chart
Vis.prototype.destroy = function () {
// Turn off checkSize
this.checkSize(false);
// Removing chart and all elements associated with it
d3.select(this.el).selectAll('*').remove();
// Cleaning up event listeners
this.off('click', null);
this.off('hover', null);
this.off('brush', null);
// remove event listeners
this.resizeChecker.off('resize', this.resize);
// pass destroy call down to owned objects
this.resizeChecker.destroy();
};
// Set attributes on the chart

View file

@ -21,11 +21,9 @@ define(function (require) {
ColumnChart.Super.apply(this, arguments);
// Column chart specific attributes
this._attr = _.defaults(vis._attr || {}, {
offset: 'zero',
xValue: function (d, i) { return d.x; },
yValue: function (d, i) { return d.y; },
dispatch: d3.dispatch('brush', 'click', 'hover', 'mouseenter', 'mouseleave', 'mouseover', 'mouseout'),
stack: this._attr.stack.offset(this.offset)
});
}
@ -45,6 +43,7 @@ define(function (require) {
};
// Stack data
// TODO: refactor so that this is called from the data module
ColumnChart.prototype.stackData = function (data) {
var self = this;
@ -77,8 +76,8 @@ define(function (require) {
});
});
// if `addEvents` is true, add brush canvas
if (self._attr.addEvents) {
// if `addBrushing` is true, add brush canvas
if (self._attr.addBrushing) {
svg.append('g')
.attr('class', 'brush')
.call(brush)
@ -116,7 +115,7 @@ define(function (require) {
bars.enter()
.append('rect')
.attr('class', function (d) {
return 'color ' + Legend.prototype.classify.call(this, color(d.label));
return 'color ' + Legend.prototype.colorToClass.call(this, color(d.label));
})
.attr('fill', function (d) {
return color(d.label);
@ -160,26 +159,21 @@ define(function (require) {
var self = this;
var tooltip = this.vis.tooltip;
var isTooltip = this._attr.addTooltip;
var addEvents = this._attr.addEvents;
var dispatch = this._attr.dispatch;
bars
.on('mouseover.bar', function (d, i) {
if (addEvents) {
d3.select(this)
.classed('hover', true)
.style('stroke', '#333')
.style('cursor', 'pointer');
d3.select(this)
.classed('hover', true)
.style('stroke', '#333')
.style('cursor', 'pointer');
dispatch.hover(self.eventResponse(d, i));
d3.event.stopPropagation();
}
dispatch.hover(self.eventResponse(d, i));
d3.event.stopPropagation();
})
.on('click.bar', function (d, i) {
if (addEvents) {
dispatch.click(self.eventResponse(d, i));
d3.event.stopPropagation();
}
dispatch.click(self.eventResponse(d, i));
d3.event.stopPropagation();
})
.on('mouseout.bar', function () {
d3.select(this).classed('hover', false)

View file

@ -8,11 +8,12 @@ define(function (require) {
require('components/config/config');
require('components/courier/courier');
require('components/notify/notify');
require('components/state_management/app_state_factory');
require('components/filter_bar/filter_bar');
require('components/storage/storage');
require('components/notify/notify');
require('components/persisted_log/persisted_log');
require('components/state_management/app_state_factory');
require('components/storage/storage');
require('components/url/url');
require('directives/click_focus');
require('directives/info');
require('directives/spinner');

View file

@ -4,7 +4,6 @@ define(function (require) {
.directive('confirmClick', function () {
return {
restrict: 'A',
scope: {},
link: function ($scope, $elem, attrs) {
$elem.bind('click', function () {
var message = attrs.confirmation || 'Are you sure?';

View file

@ -25,7 +25,7 @@ define(function (require) {
var stringify = function () {
var text;
// If both parts are date math, try to look up a reasonable string
if (!moment.isMoment($scope.from) && !moment.isMoment($scope.to)) {
if ($scope.from && $scope.to && !moment.isMoment($scope.from) && !moment.isMoment($scope.to)) {
var tryLookup = lookupByRange[$scope.from.toString() + ' to ' + $scope.to.toString()];
if (tryLookup) {
$elem.text(tryLookup.display);

View file

@ -3,7 +3,7 @@ define(function (require) {
var _ = require('lodash');
var rison = require('utils/rison');
module.directive('savedObjectFinder', function (savedSearches, savedVisualizations, savedDashboards, $location, $route) {
module.directive('savedObjectFinder', function (savedSearches, savedVisualizations, savedDashboards, $location, kbnUrl) {
var vars = {
searches: {
@ -85,12 +85,12 @@ define(function (require) {
// angular wants the '/path', not '#/path'
var path = url.substr(1);
if ($route.matches(path)) {
if (kbnUrl.matches(path)) {
$event.preventDefault();
// change works with paths, but we are only here because the paths
// are the same, so we have to change the whole url to be the new path
$route.changeUrl(path);
kbnUrl.change(path);
}
};

View file

@ -0,0 +1,16 @@
define(function (require) {
var rison = require('utils/rison');
var module = require('modules').get('kibana');
module.filter('rison', function () {
return function (str) {
return rison.encode(str);
};
});
module.filter('risonDecode', function () {
return function (str) {
return rison.decode(str);
};
});
});

View file

@ -4,7 +4,7 @@
define(function (require) {
var _ = require('lodash');
require('modules')
.get('kbn/filters')
.get('kibana')
.filter('shortDots', function (config) {
return function (str) {
if (!_.isString(str) || config.get('shortDots:enable') !== true) {

View file

@ -1,6 +1,6 @@
define(function (require) {
require('modules')
.get('kbn/filters')
.get('kibana')
.filter('uriescape', function () {
return function (str) {
return encodeURIComponent(str);

View file

@ -7,11 +7,11 @@
<meta name="viewport" content="width=device-width">
<title>Kibana 4</title>
<!-- load the root require context -->
<script src="bower_components/requirejs/require.js"></script>
<script src="/bower_components/requirejs/require.js"></script>
<script src="kibana/require.config.js"></script>
<script src="/require.config.js"></script>
<script>require(['kibana'], function (kibana) { kibana.init(); });</script>
<link rel="stylesheet" href="kibana/styles/main.css" >
<link rel="stylesheet" href="/styles/main.css" >
</head>
<body ng-controller="kibana" ng-class="'application-'+activeApp">
<kbn-notifications list="notifList"></kbn-notifications>

View file

@ -12,7 +12,7 @@ define(function (require) {
require('angular-route');
require('angular-bindonce');
var configFile = require('config_file');
var configFile = JSON.parse(require('text!config'));
var kibana = modules.get('kibana', [
// list external requirements here
@ -21,7 +21,7 @@ define(function (require) {
'ngRoute'
]);
configFile.elasticsearch = configFile.elasticsearch || ('http://' + window.location.hostname + ':9200');
configFile.elasticsearch = ('http://' + window.location.hostname + '/elasticsearch/');
kibana
// This stores the Kibana revision number, @REV@ is replaced by grunt.

View file

@ -1,9 +1,7 @@
require.config({
baseUrl: './kibana',
baseUrl: './',
paths: {
kibana: './index',
config_file: '../config',
kibana: 'index',
// special utils
routes: 'utils/routes/index',
errors: 'components/errors',
@ -11,27 +9,27 @@ require.config({
lodash: 'utils/_mixins',
// bower_components
'angular-bindonce': '../bower_components/angular-bindonce/bindonce',
'angular-bootstrap': '../bower_components/angular-bootstrap/ui-bootstrap-tpls',
'angular-elastic': '../bower_components/angular-elastic/elastic',
'angular-route': '../bower_components/angular-route/angular-route',
'angular-ui-ace': '../bower_components/angular-ui-ace/ui-ace',
ace: '../bower_components/ace-builds/src-noconflict/ace',
angular: '../bower_components/angular/angular',
async: '../bower_components/async/lib/async',
bower_components: '../bower_components',
css: '../bower_components/require-css/css',
d3: '../bower_components/d3/d3',
elasticsearch: '../bower_components/elasticsearch/elasticsearch.angular',
faker: '../bower_components/Faker/faker',
file_saver: '../bower_components/FileSaver/FileSaver',
gridster: '../bower_components/gridster/dist/jquery.gridster',
inflection: '../bower_components/inflection/lib/inflection',
jquery: '../bower_components/jquery/dist/jquery',
jsonpath: '../bower_components/jsonpath/lib/jsonpath',
lodash_src: '../bower_components/lodash/dist/lodash',
moment: '../bower_components/moment/moment',
text: '../bower_components/requirejs-text/text'
'angular-bindonce': 'bower_components/angular-bindonce/bindonce',
'angular-bootstrap': 'bower_components/angular-bootstrap/ui-bootstrap-tpls',
'angular-elastic': 'bower_components/angular-elastic/elastic',
'angular-route': 'bower_components/angular-route/angular-route',
'angular-ui-ace': 'bower_components/angular-ui-ace/ui-ace',
ace: 'bower_components/ace-builds/src-noconflict/ace',
angular: 'bower_components/angular/angular',
async: 'bower_components/async/lib/async',
bower_components: 'bower_components',
css: 'bower_components/require-css/css',
d3: 'bower_components/d3/d3',
elasticsearch: 'bower_components/elasticsearch/elasticsearch.angular',
faker: 'bower_components/Faker/faker',
file_saver: 'bower_components/FileSaver/FileSaver',
gridster: 'bower_components/gridster/dist/jquery.gridster',
inflection: 'bower_components/inflection/lib/inflection',
jquery: 'bower_components/jquery/dist/jquery',
jsonpath: 'bower_components/jsonpath/lib/jsonpath',
lodash_src: 'bower_components/lodash/dist/lodash',
moment: 'bower_components/moment/moment',
text: 'bower_components/requirejs-text/text'
},
shim: {
angular: {

View file

@ -14,4 +14,4 @@ define(function (require) {
return es;
});
});
});

View file

@ -62,10 +62,6 @@ define(function (require) {
}()));
}
Timefilter.prototype.enabled = function (state) {
this.enabled = !!state;
};
Timefilter.prototype.get = function (indexPattern) {
var filter;
var timefield = indexPattern.timeFieldName && _.find(indexPattern.fields, {name: indexPattern.timeFieldName});

View file

@ -1,4 +1,4 @@
@bs-less-dir: "../../bower_components/bootstrap/less";
@bs-less-dir: "../bower_components/bootstrap/less";
// Core variables and mixins
@import "theme/_variables.less";

View file

@ -1,4 +1,4 @@
@import "../../bower_components/font-awesome/less/font-awesome.less";
@import "../bower_components/font-awesome/less/font-awesome.less";
@import (reference) "lesshat.less";
// generic mixins

View file

@ -150,6 +150,15 @@ define(function (require) {
// always call flush, it might not do anything
flush(this, args);
};
},
chunk: function (arr, count) {
var size = Math.ceil(arr.length / count);
var chunks = new Array(count);
for (var i = 0; i < count; i ++) {
var start = i * size;
chunks[i] = arr.slice(start, start + size);
}
return chunks;
}
});

View file

@ -1,5 +1,5 @@
define(function (require) {
return function routeSetup(Promise, kbnSetup, config, $route, indexPatterns, Notifier) {
return function routeSetup(Promise, kbnSetup, config, $route, kbnUrl, indexPatterns, Notifier) {
var errors = require('errors');
var NoDefaultIndexPattern = errors.NoDefaultIndexPattern;
@ -26,7 +26,7 @@ define(function (require) {
if (err instanceof NoDefaultIndexPattern || err instanceof NoDefinedIndexPatterns) {
// .change short circuits the routes by calling $route.refresh(). We can safely swallow this error
// after reporting it to the user
$route.change('/settings/indices');
kbnUrl.change('/settings/indices');
(new Notifier()).error(err);
} else {
return Promise.reject(err);
@ -34,4 +34,4 @@ define(function (require) {
}
};
};
});
});

View file

@ -5,42 +5,6 @@ define(function (require) {
require('components/setup/setup');
require('services/promises');
require('modules').get('kibana')
.config(function ($provide) {
// decorate the $route object to include a change and changeUrl method
$provide.decorator('$route', function ($delegate, $location, $rootScope) {
var reloading;
var doneReloading = function () { reloading = false; };
$rootScope.$on('$routeUpdate', doneReloading);
$rootScope.$on('$routeChangeStart', doneReloading);
var reload = function () {
if (!reloading) $delegate.reload();
reloading = true;
};
$delegate.change = function (path) {
if (path !== $location.path()) {
$location.path(path);
reload();
}
};
$delegate.changeUrl = function (url) {
if (url !== $location.url()) {
$location.url(url);
reload();
}
};
$delegate.matches = function (url) {
var route = $delegate.current.$$route;
if (!route || !route.regexp) return null;
return route.regexp.test(url);
};
return $delegate;
});
});
function RouteManager() {
var when = [];
var additions = [];

View file

@ -0,0 +1,95 @@
define(function (require) {
var _ = require('lodash');
function create(min, max, length, mod) {
var seq = new Array(length);
var valueDist = max - min;
// range of values that the mod creates
var modRange = [mod(0, length), mod(length - 1, length)];
// distance between
var modRangeDist = modRange[1] - modRange[0];
_.times(length, function (i) {
var modIPercent = (mod(i, length) - modRange[0]) / modRangeDist;
// percent applied to distance and added to min to
// produce value
seq[i] = min + (valueDist * modIPercent);
});
seq.min = min;
seq.max = max;
return seq;
}
return {
/**
* Create an exponential sequence of numbers.
*
* Creates a curve resembling:
*
* ;
* /
* /
* .-'
* _.-"
* _.-'"
* _,.-'"
* _,..-'"
* _,..-'""
* _,..-'""
* ____,..--'""
*
* @param {number} min - the min value to produce
* @param {number} max - the max value to produce
* @param {number} length - the number of values to produce
* @return {number[]} - an array containing the sequence
*/
createEaseIn: _.partialRight(create, function (i, length) {
// generates numbers from 1 to +Infinity
return i * Math.pow(i, 1.1111);
}),
/**
* Create an sequence of numbers using sine.
*
* Create a curve resembling:
*
* ____,..--'""
* _,..-'""
* _,..-'""
* _,..-'"
* _,.-'"
* _.-'"
* _.-"
* .-'
* /
* /
* ;
*
*
* @param {number} min - the min value to produce
* @param {number} max - the max value to produce
* @param {number} length - the number of values to produce
* @return {number[]} - an array containing the sequence
*/
createEaseOut: _.partialRight(create, function (i, length) {
// adapted from output of http://www.timotheegroleau.com/Flash/experiments/easing_function_generator.htm
// generates numbers from 0 to 100
var ts = (i /= length) * i;
var tc = ts * i;
return 100 * (
0.5 * tc * ts +
-3 * ts * ts +
6.5 * tc +
-7 * ts +
4 * i
);
})
};
});

19
src/server/DIST_README.md Normal file
View file

@ -0,0 +1,19 @@
# Kibana @@version
Kibana is an open source (Apache Licensed), browser based analytics and search dashboard for Elasticsearch. Kibana is a snap to setup and start using. Kibana strives to be easy to get started with, while also being flexible and powerful, just like Elasticsearch.
## Installation
* Download: http://www.elasticsearch.org/overview/kibana/installation/
* Run **bin/kibana** on unix, or **bin/kibana.bat** on Windows.
* Visit http://localhost:5601
## Need Help?
Need help? Try #elasticsearch or #logstash on Freenode IRC. You can also find help on the elasticsearch-users@googlegroups.com or logstash-users@googlegroups.com mailing lists.
You can also find documentation at http://www.elasticsearch.com/guide/en/kibana/current
## Contributing
If you have a bugfix or new feature that you would like to contribute to Kibana, please find or open an issue about it first. Kibana is an open source project that is available on Github: https://github.com/elasticsearch/kibana

View file

@ -1,5 +1,3 @@
ruby "1.9.3", :engine => 'jruby', :engine_version => '1.7.13'
source "https://rubygems.org"
gem 'sinatra', :require => 'sinatra/base'
@ -8,3 +6,4 @@ gem 'puma'
gem 'warbler'
gem 'elasticsearch'
gem 'rack-reverse-proxy', :require => 'rack/reverse_proxy'
gem 'colorize'

View file

@ -2,6 +2,7 @@ GEM
remote: https://rubygems.org/
specs:
backports (3.6.0)
colorize (0.7.3)
elasticsearch (1.0.4)
elasticsearch-api (= 1.0.4)
elasticsearch-transport (= 1.0.4)
@ -52,6 +53,7 @@ PLATFORMS
ruby
DEPENDENCIES
colorize
elasticsearch
puma
rack-reverse-proxy

83
src/server/bin/initialize Executable file
View file

@ -0,0 +1,83 @@
#!/usr/bin/env ruby
require "optparse"
require 'rubygems'
require 'puma/cli'
require "yaml"
HERE = File.expand_path(File.dirname(__FILE__))
module Kibana
def self.global_settings
@settings ||= {}
end
end
# Defaults for the options
options = {
:config => File.expand_path("#{HERE}/../config/kibana.yml") || ENV["CONFIG_PATH"]
}
# Create a new parser
parser = OptionParser.new do |opts|
opts.on('-e', '--elasticsearch URI', 'Elasticsearch instance') do |arg|
options[:elasticsearch] = arg
end
opts.on('-c', '--config PATH', 'Path to config file') do |arg|
options[:config] = arg
end
opts.on('-p', '--port PORT', 'Kibana port') do |arg|
options[:port] = arg
end
opts.on('-H', '--host HOST', 'Kibana host') do |arg|
options[:host] = arg
end
opts.on('-v', '--version', 'Display version') do |arg|
puts ENV['KIBANA_VERSION'] || 'dev-build'
exit
end
opts.on('-h', '--help', 'Display this screen') do
puts opts
exit
end
end
# Set the usage banner
parser.banner = "Usage: kibana <options>\n\n"
# Parse the command line arguments
parser.parse! ARGV
# Load the config from default
config = YAML.load(IO.read(options[:config]))
# Set the override for the port
port = (options[:port] || config['port'])
# Set the override for the host
host = (options[:host] || config['host'])
# Set the override for Elasticsaerch
elasticsearch = (options[:elasticsearch] || config['elasticsearch'])
# If the env isn't set we need to set it to development
ENV["RACK_ENV"] = "development" if ENV["RACK_ENV"].nil?
# Set the global_settings that are shared across every app
Kibana.global_settings[:port] = port || 5601
Kibana.global_settings[:host] = host || '0.0.0.0'
Kibana.global_settings[:config] = config
Kibana.global_settings[:elasticsearch] = elasticsearch
Kibana.global_settings[:root] = File.expand_path("#{File.dirname(__FILE__)}/../")
# Set the public folder based on whether we are running in production or not.
if ENV['RACK_ENV'] == ('production')
Kibana.global_settings[:public_folder] = File.expand_path("#{File.dirname(__FILE__)}/../public/")
else
Kibana.global_settings[:public_folder] = File.expand_path("#{File.dirname(__FILE__)}/../../kibana/")
end
# Add the root of the project to the load path
$LOAD_PATH.unshift(Kibana.global_settings[:root])
require "lib/server"
Kibana::Server.run(Kibana.global_settings)

25
src/server/bin/kibana.bat Normal file
View file

@ -0,0 +1,25 @@
@echo off
SETLOCAL
if not defined JAVA_HOME goto java_home_err
set SCRIPT_DIR=%~dp0
for %%I in ("%SCRIPT_DIR%..") do set DIR=%%~dpfI
set RACK_ENV=production
set CONFIG_PATH=%DIR%\config\kibana.yml
set KIBANA_VERSION=@@version
TITLE Kibana %KIBANA_VERSION%
"%JAVA_HOME%\bin\java" -jar "%DIR%\lib\kibana.jar" %*
:java_home_err
echo JAVA_HOME enviroment variable must be set!
pause
goto finally
:finally
ENDLOCAL

30
src/server/bin/kibana.sh Executable file
View file

@ -0,0 +1,30 @@
#!/bin/sh
SCRIPT=$0
# SCRIPT may be an arbitrarily deep series of symlinks. Loop until we have the concrete path.
while [ -h "$SCRIPT" ] ; do
ls=`ls -ld "$SCRIPT"`
# Drop everything prior to ->
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
SCRIPT="$link"
else
SCRIPT=`dirname "$SCRIPT"`/"$link"
fi
done
DIR=$(dirname "${SCRIPT}")
if [ -x "${JAVA_HOME}/bin/java" ]; then
JAVA="${JAVA_HOME}/bin/java"
else
JAVA=`which java`
fi
if [ ! -x "${JAVA}" ]; then
echo "Could not find any executable Java binary. Please install Java in your PATH or set JAVA_HOME"
exit 1
fi
KIBANA_VERSION=@@version CONFIG_PATH=${DIR}/../config/kibana.yml RACK_ENV=production exec "${JAVA}" -jar "${DIR}/../lib/kibana.jar" "$@"

View file

@ -0,0 +1,20 @@
# Kibana is served by a backend server. This controls which port to use.
port: 5601
# The Elasticsearch instance to user for all your queries
elasticsearch: "http://localhost:9200"
# Kibana uses and index in Elasticsearch to store saved searches, visualizations
# and dashboard. It will create an new index if it doesn't already exist.
kibanaIndex: "kibana-int"
# Applications loaded and included into Kibana. Use the settings below to
# customize the applications and thier names.
apps:
- { id: "discover", name: "Discover" }
- { id: "visualize", name: "Visualize" }
- { id: "dashboard", name: "Dashboard" }
- { id: "settings", name: "Settings" }
# The default application to laad.
defaultAppId: "discover"

22
src/server/config/web.ru Normal file
View file

@ -0,0 +1,22 @@
require "rubygems"
require "bundler/setup"
ROOT = File.expand_path("#{File.dirname(__FILE__)}/../")
if ENV['RACK_ENV'] == ('development')
PUBLIC_ROOT = File.expand_path("#{File.dirname(__FILE__)}/../../kibana/")
CONFIG_PATH = File.expand_path("#{File.dirname(__FILE__)}/kibana.yml")
end
if ENV['RACK_ENV'] == ('production')
PUBLIC_ROOT = File.expand_path("#{File.dirname(__FILE__)}/../public/")
CONFIG_PATH = ENV["CONFIG_PATH"]
end
$LOAD_PATH.unshift(ROOT)
# Require the application
require "#{ROOT}/lib/app"
# Run the application
run Kibana::App

View file

@ -0,0 +1,44 @@
require "rack/commonlogger"
require "colorize"
class ColorLogger < Rack::CommonLogger
def log(env, status, header, begin_at)
now = Time.now
length = extract_content_length(header)
case status
when 300..399
statusColor = :yellow
when 400..499
statusColor = :red
when 500..599
statusColor = :magenta
else
statusColor = :green
end
msg = (now.strftime('%b %d, %Y @ %H:%M:%S.%L')).light_black << ' '
msg << env["REQUEST_METHOD"].light_blue << ' '
msg << env["PATH_INFO"]
msg << (env["QUERY_STRING"].empty? ? '' : "?#{env["QUERY_STRING"]}" ) << ' '
msg << status.to_s.send(statusColor) << ' '
msg << ((now - begin_at) * 1000).to_i.to_s << 'ms - ' << length
msg << "\n"
# If there is an error then we need to append the stack
if env['sinatra.error'] && status != 404
error = env['sinatra.error']
msg << "#{error.message}\n #{error.backtrace.join("\n ")}".send(statusColor)
msg << "\n"
end
logger = @logger || env['rack.errors']
if logger.respond_to?(:write)
logger.write(msg)
else
logger << msg
end
end
end

View file

@ -0,0 +1,46 @@
require "rack/commonlogger"
class JSONLogger < Rack::CommonLogger
def log(env, status, header, begin_at)
now = Time.now
length = extract_content_length(header)
data = {
"@timestamp" => now.iso8601,
:status => status.to_s[0..3],
:level => status < 399 ? "INFO" : 'ERROR',
:name => "Kibana",
:request_method => env["REQUEST_METHOD"],
:request => env["PATH_INFO"] + (env["QUERY_STRING"].empty? ? "" : "#{env['QUERY_STRING']}"),
:path => env["PATH_INFO"],
:query_string => env["QUERY_STRING"],
:remote_addr => env['HTTP_X_FORWARD_FOR'] || env["REMOTE_ADDR"],
:remote_user => env["REMOTE_USER"],
:http_version => env["HTTP_VERSION"],
:content_length => length,
:response_time => ((now - begin_at) * 1000).to_i # convert to milliseconds
}
# If there is an error then we need to append the stack
if env['sinatra.error']
error = env['sinatra.error']
data[:error] = {
:name => error.class.to_s,
:message => error.message,
:stack => error.backtrace
}
end
data[:message] = "#{data[:request_method]} #{data[:path]+(data[:query_string].empty? ? '' : '?'+data[:query_string])} #{data[:status]} #{data[:response_time]}ms - #{data[:content_length].empty? ? '-' : data[:content_length]}"
logger = @logger || env['rack.errors']
msg = data.to_json+"\n"
if logger.respond_to?(:write)
logger.write(msg)
else
logger << msg
end
end
end

68
src/server/lib/app.rb Normal file
View file

@ -0,0 +1,68 @@
# Add the root of the project to the $LOAD_PATH, For some reason it seems
# to be getting lost when we use warble to make the jar. This fixes it :D
$LOAD_PATH.unshift(Kibana.global_settings[:root])
require "logger"
require "json"
require "lib/JSONLogger"
require "lib/ColorLogger"
require "routes/home"
require "sinatra/json"
require "routes/proxy"
class Logger
alias_method :write, :<<
end
module Kibana
class App < Sinatra::Base
helpers Sinatra::JSON
configure do
logger = Logger.new(STDOUT)
logger.formatter = proc do |severity, datetime, progname, msg|
data = {
'@timestamp' => datetime.iso8601,
:level => severity,
:name => progname || "Kibana",
:message => msg
}
data.to_json + "\n"
end
set :logger, logger
disable :raise_errors
disable :show_exceptions
disable :dump_errors
end
configure :production do
use JSONLogger, settings.logger
end
configure :development do
use ColorLogger, settings.logger
end
error do
500
end
error 400 do
json :status => 500, :message => "Bad Request"
end
error 500 do
json :status => 500, :message => "Internal Server Error"
end
not_found do
json :status => 404, :message => "Not Found"
end
# Routes go here
use Routes::Home
use Routes::Proxy
end
end

60
src/server/lib/server.rb Normal file
View file

@ -0,0 +1,60 @@
require "rubygems"
require "bundler/setup"
require "puma"
require "colorize"
require "json"
require "#{Kibana.global_settings[:root]}/lib/app"
# Require the application
module Kibana
module Server
DEFAULTS = {
:host => '0.0.0.0',
:port => 5601,
:threads => '0:16',
:verbose => false
}
def self.log(msg)
if ENV['RACK_ENV'] == 'production'
data = {
"@timestamp" => Time.now.iso8601,
:level => 'INFO',
:name => 'Kibana',
:message => msg
}
puts data.to_json
else
message = (Time.now.strftime('%b %d, %Y @ %H:%M:%S.%L')).light_black << ' '
message << msg.yellow
puts message
end
end
def self.run(options = {})
options = DEFAULTS.merge(options)
min, max = options[:threads].split(':', 2)
app = Kibana::App.new()
server = Puma::Server.new(app)
# Configure server
server.add_tcp_listener(options[:host], options[:port])
server.min_threads = min
server.max_threads = max
begin
log("Kibana server started on tcp://#{options[:host]}:#{options[:port]} in #{ENV['RACK_ENV']} mode.")
server.run.join
rescue Interrupt
log("Kibana server gracefully stopping, waiting for requests to finish")
server.stop(true)
log("Kibana server stopped.")
end
end
end
end

21
src/server/routes/base.rb Normal file
View file

@ -0,0 +1,21 @@
require "sinatra/base"
require "sinatra/json"
require "yaml"
module Kibana
module Routes
class Base < Sinatra::Base
helpers Sinatra::JSON
configure do
config = Kibana.global_settings[:config].clone()
config['elasticsearch'] = Kibana.global_settings[:elasticsearch]
config['port'] = Kibana.global_settings[:port].to_i
set :root, Kibana.global_settings[:root]
set :public_folder, Kibana.global_settings[:public_folder]
set :httponly, true
set :config, config
end
end
end
end

21
src/server/routes/home.rb Normal file
View file

@ -0,0 +1,21 @@
require "routes/base"
module Kibana
module Routes
class Home < Base
get "/" do
File.read(File.join(settings.public_folder, 'index.html'))
end
get "/config" do
# Clone the settings object and change the elasticsearch attribute
# to the proxy for elasticsearch
data = settings.config.clone()
data['elasticsearch'] = "#{request.scheme}://#{request.host}:#{request.port}/elasticsearch"
json data
end
end
end
end

View file

@ -0,0 +1,14 @@
require "routes/base"
require "rack/reverse_proxy"
module Kibana
module Routes
class Proxy < Base
# Rack middleware goes here
config = settings.config
use Rack::ReverseProxy do
reverse_proxy(/^\/elasticsearch(.*)$/, "#{config["elasticsearch"]}$1")
end
end
end
end

View file

@ -4,9 +4,18 @@ module.exports = function (grunt) {
'clean:build',
'require_css_deps:copy',
'less',
'copy:kibana_src',
'touch_config',
'requirejs',
'clean:unneeded_source_in_build',
'copy:server_src',
'download_jruby',
'install_gems',
'warble',
'replace:dist',
'copy:dist',
'chmod_kibana',
'compress:build_zip',
'compress:build_tarball'
]);
};
};

9
tasks/chmod_kibana.js Normal file
View file

@ -0,0 +1,9 @@
var fs = require('fs');
var join = require('path').join;
module.exports = function (grunt) {
grunt.registerTask('chmod_kibana', 'Chmods bin/kibana', function () {
var done = this.async();
var path = join(grunt.config.get('build'), 'dist', 'bin', 'kibana');
fs.chmod(path, 0755, done);
});
};

View file

@ -6,20 +6,19 @@ module.exports = function (grunt) {
unneeded_source_in_build: {
src: [
// select all top level folders in bower_components
'<%= build %>/bower_components/*',
'<%= build %>/kibana/public/bower_components/*',
// exclude the following top level components
'!<%= build %>/bower_components/' + notIncludedComponents,
'!<%= build %>/kibana/public/bower_components/' + notIncludedComponents,
// remove the contents of K4D3, font-awesome, and requirejs except necessary files
'<%= build %>/bower_components/' + notIncludedComponents + '/*',
'!<%= build %>/bower_components/requirejs/require.js',
'!<%= build %>/bower_components/font-awesome/fonts',
'<%= build %>/**/_empty_',
'<%= build %>/**/*.less',
'<%= appBuild %>/{css-builder,normalize}.js',
'<%= app %>/{css-builder,normalize}.js',
'<%= build %>/kibana/public/bower_components/' + notIncludedComponents + '/*',
'!<%= build %>/kibana/public/bower_components/requirejs/require.js',
'!<%= build %>/kibana/public/bower_components/font-awesome/fonts',
'<%= build %>/kibana/public/**/_empty_',
'<%= build %>/kibana/public/**/*.less',
'<%= build %>/kibana/public/config',
'<%= build %>/kibana/public/{css-builder,normalize}.js',
'<%= app %>/public/{css-builder,normalize}.js',
]
}
};
};
};

View file

@ -6,7 +6,7 @@ module.exports = function (grunt) {
return _.mapValues({
build_zip: archiveName() + '.zip',
build_tarball: archiveName() + '.zip',
build_tarball: archiveName() + '.tar.gz',
plugin: archiveName(true) + '.tar.gz'
}, function (filename, task) {
return {
@ -16,11 +16,11 @@ module.exports = function (grunt) {
files: [
{
expand: true,
cwd: '<%= build %>',
cwd: '<%= build %>/dist',
src: ['**/*'],
dest: '<%= pkg.name %>' + (task === 'plugin' ? '/_site' : '')
}
]
};
});
};
};

71
tasks/config/copy.js Normal file
View file

@ -0,0 +1,71 @@
module.exports = function (grunt) {
var config = {
kibana_src: {
expand: true,
cwd: '<%= app %>',
src: '**',
dest: '<%= build %>/src/'
},
server_src: {
files: [
{
src: '<%= src %>/server/Gemfile',
dest: '<%= build %>/kibana/Gemfile'
},
{
src: '<%= src %>/server/Gemfile.lock',
dest: '<%= build %>/kibana/Gemfile.lock'
},
{
src: '<%= src %>/server/bin/initialize',
dest: '<%= build %>/kibana/bin/initialize'
},
{
expand: true,
cwd: '<%= src %>/server/config/',
src: '**',
dest: '<%= build %>/kibana/config'
},
{
expand: true,
cwd: '<%= src %>/server/lib/',
src: '**',
dest: '<%= build %>/kibana/lib'
},
{
expand: true,
cwd: '<%= src %>/server/routes/',
src: '**',
dest: '<%= build %>/kibana/routes'
}
]
},
dist: {
options: { mode: true },
files: [
{
src: '<%= root %>/LICENSE.md',
dest: '<%= build %>/dist/LICENSE.md'
},
{
expand: true,
cwd: '<%= build %>/kibana/',
src: '*.jar',
dest: '<%= build %>/dist/lib/'
},
{
expand: true,
cwd: '<%= src %>/server/config/',
src: 'kibana.yml',
dest: '<%= build %>/dist/config/'
}
]
}
};
return config;
};

View file

@ -17,8 +17,9 @@ module.exports = function (grunt) {
ignores: [
'node_modules/*',
'dist/*',
'sample/*'
'sample/*',
'<%= src %>/kibana/bower_components/**/*'
]
}
};
};
};

View file

@ -1,4 +1,4 @@
var bc = require('path').join(__dirname, '../../src/bower_components');
var bc = require('path').join(__dirname, '../../src/kibana/bower_components');
module.exports = {
src: {

31
tasks/config/replace.js Normal file
View file

@ -0,0 +1,31 @@
var join = require('path').join;
module.exports = function (grunt) {
var pkg = grunt.config.get('pkg');
var build = grunt.config.get('build');
var src = grunt.config.get('src');
var config = {
dist: {
options: {
patterns: [
{ match: 'version', replacement: pkg.version }
]
},
files: [
{
src: [join(src, 'server', 'DIST_README.md')],
dest: join(build, 'dist', 'README.md')
},
{
src: [join(src, 'server', 'bin', 'kibana.sh')],
dest: join(build, 'dist', 'bin', 'kibana')
},
{
src: [join(src, 'server', 'bin', 'kibana.bat')],
dest: join(build, 'dist', 'bin', 'kibana.bat')
}
]
}
};
return config;
};

View file

@ -2,14 +2,15 @@ module.exports = function (grunt) {
var config = {
build: {
options: {
appDir: '<%= src %>',
dir: '<%= build %>',
mainConfigFile: '<%= app %>/require.config.js',
appDir: '<%= build %>/src',
dir: '<%= build %>/kibana/public',
mainConfigFile: '<%= build %>/src/require.config.js',
modules: [
{
name: 'kibana',
excludeShallow: [
'../config',
'text!config'
],
include: [
'controllers/kibana'
@ -60,10 +61,10 @@ module.exports = function (grunt) {
// include each app
var main = config.build.options.modules[0];
var configFile = require('requirejs')(grunt.config.get('src') + '/config.js');
var configFile = require('requirejs')(grunt.config.get('app') + '/config.js');
configFile.apps.forEach(function (app) {
main.include.push('apps/' + app.id + '/index');
});
return config;
};
};

28
tasks/config/run.js Normal file
View file

@ -0,0 +1,28 @@
module.exports = function (grunt) {
var jrubyPath = grunt.config.get('jrubyPath');
var jruby = jrubyPath + '/bin/jruby';
var cmd = grunt.config.get('src') + '/server/bin/initialize';
var config = {
mri_server: {
options: {
wait: false
// quiet: true
},
cmd: cmd
},
jruby_server: {
options: {
wait: false
// quiet: true
},
cmd: jruby,
args: [
cmd
]
}
};
return config;
};

View file

@ -1,4 +1,16 @@
module.exports = function (grunt) {
var kibana_server_tasks = [];
if (grunt.option('use-mri')) {
kibana_server_tasks = [
'stop:mri_server',
'run:mri_server'
];
} else {
kibana_server_tasks = [
'stop:jruby_server',
'run:jruby_server'
];
}
var config = {
test: {
files: [
@ -26,6 +38,16 @@ module.exports = function (grunt) {
'<%= testUtilsDir %>/istanbul_reporter/report.clientside.jade'
],
tasks: ['jade:clientside']
},
kibana_server: {
files: [
'src/server/**/*.rb',
'src/server/**/*.yml'
],
tasks: kibana_server_tasks,
options: {
spawn: false
}
}
};

View file

@ -1,8 +1,21 @@
module.exports = function (grunt) {
grunt.registerTask('dev', [
var useJRuby = grunt.option('use-jruby');
var tasks = [
'less',
'jade',
'jade'
];
if (useJRuby) {
tasks = tasks.concat([
'download_jruby',
'install_gems',
'run:jruby_server',
'wait_for_jruby'
]);
} else {
tasks = tasks.concat(['run:mri_server']);
}
grunt.registerTask('dev', tasks.concat([
'maybe_start_server',
'watch'
]);
};
]));
};

46
tasks/download_jruby.js Normal file
View file

@ -0,0 +1,46 @@
var zlib = require('zlib');
var tar = require('tar');
var request = require('request');
var mkdirp = require('mkdirp');
var ProgressBar = require('progress');
var fs = require('fs');
module.exports = function (grunt) {
grunt.registerTask('download_jruby', 'Downloads and installs jruby', function () {
var done = this.async();
var jrubyPath = grunt.config.get('jrubyPath');
var jrubyVersion = grunt.config.get('jrubyVersion');
var url = 'http://jruby.org.s3.amazonaws.com/downloads/' + jrubyVersion + '/jruby-bin-' + jrubyVersion + '.tar.gz';
fs.stat(jrubyPath, function (err, stat) {
if (err) {
mkdirp(jrubyPath, function (err) {
if (err) return done(err);
var unzip = zlib.createGunzip();
var out = tar.Extract({ path: jrubyPath, strip: 1 });
out.on('close', done).on('error', done);
var req = request.get(url);
var bar;
if (!process.env.JENKINS_HOME) {
req.on('response', function (resp) {
var total = parseInt(resp.headers['content-length'], 10);
bar = new ProgressBar('[:bar] :percent :etas', {
complete: '=',
incomplete: ' ',
width: 80,
clear: true,
total: total
});
});
req.on('data', function (buffer) {
bar.tick(buffer.length);
});
}
req.pipe(unzip).pipe(out);
});
} else {
done();
}
});
});
};

20
tasks/install_gems.js Normal file
View file

@ -0,0 +1,20 @@
var child_process = require('child_process');
var join = require('path').join;
module.exports = function (grunt) {
grunt.registerTask('install_gems', 'Install Ruby Gems', function () {
var done = this.async();
var gemfile = join(grunt.config.get('src'), 'server', 'Gemfile');
var jrubyPath = grunt.config.get('jrubyPath');
var jruby = jrubyPath + '/bin/jruby -S';
var command = jruby + ' gem install bundler && ' + jruby + ' bundle install --gemfile ' + gemfile;
child_process.exec(command, function (err, stdout, stderr) {
if (err) {
grunt.log.error(stderr);
return done(err);
}
grunt.log.writeln(stdout);
return done();
});
});
};

Some files were not shown because too many files have changed in this diff Show more