mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
Merge branch 'master' of https://github.com/elasticsearch/kibana into add/jscs
This commit is contained in:
commit
10c4d3ad3b
88 changed files with 1492 additions and 307 deletions
4
FAQ.md
4
FAQ.md
|
@ -11,3 +11,7 @@
|
|||
|
||||
**Q:** What happened to templated/scripted dashboards?
|
||||
**A:** Check out the URL. The state of each app is stored there, including any filters, queries or columns. This should be a lot easier than constructing scripted dashboards. The encoding of the URL is RISON.
|
||||
|
||||
**Q:** I'm getting `bin/node/bin/node: not found` but I can see the node binary in the package?
|
||||
**A:** Kibana 4 packages are architecture specific. Ensure you are using the correct package for your architecture.
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
Copyright 2012-2014 Elasticsearch BV
|
||||
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
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
# Kibana <!--version-->4.1.0-snapshot<!--/version-->
|
||||
# Kibana 4.1.0-snapshot
|
||||
|
||||
[](https://travis-ci.org/elasticsearch/kibana?branch=master)
|
||||
|
||||
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.
|
||||
Kibana is an open source ([Apache Licensed](https://github.com/elasticsearch/kibana/blob/master/LICENSE.md)), 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.
|
||||
|
||||
## Requirements
|
||||
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
[[production]]
|
||||
== Using Kibana in a Production Environment
|
||||
When you set up Kibana in a production environment, rather than on your local
|
||||
machine, you need to consider:
|
||||
* <<configuring-kibana-shield, Configuring Kibana to Work with Shield>>
|
||||
* <<enabling-ssl, Enabling SSL>>
|
||||
* <<controlling-access, Controlling Access>>
|
||||
* <<load-balancing, Load Balancing Across Multiple Elasticsearch Nodes>>
|
||||
|
||||
* Where you are going to run Kibana.
|
||||
* Whether you need to encrypt communications to and from Kibana.
|
||||
* If you need to control access to your data.
|
||||
|
||||
=== Deployment Considerations
|
||||
How you deploy Kibana largely depends on your use case. If you are the only user,
|
||||
you can run Kibana on your local machine and configure it to point to whatever
|
||||
Elasticsearch instance you want to interact with. Conversely, if you have a large
|
||||
|
@ -15,35 +12,19 @@ number of heavy Kibana users, you might need to load balance across multiple
|
|||
Kibana instances that are all connected to the same Elasticsearch instance.
|
||||
|
||||
While Kibana isn't terribly resource intensive, we still recommend running Kibana
|
||||
on its own node, rather than on one of your Elasticsearch nodes.
|
||||
separate from your Elasticsearch data or master nodes. To distribute Kibana
|
||||
traffic across the nodes in your Elasticsearch cluster, you can run Kibana
|
||||
and an Elasticsearch client node on the same machine. For more information, see
|
||||
<<load-balancing, Load Balancing Across Multiple Elasticsearch Nodes>>.
|
||||
|
||||
[float]
|
||||
[[configuring-kibana-shield]]
|
||||
=== Configuring Kibana to Work with Shield
|
||||
If you are using Shield to authenticate Elasticsearch users, you need to provide
|
||||
Kibana with user credentials so it can access the `.kibana` index. The Kibana user
|
||||
needs permission to perform the following actions on the `.kibana` index:
|
||||
the Kibana server with credentials so it can access the `.kibana` index and monitor
|
||||
the cluster.
|
||||
|
||||
----
|
||||
'.kibana':
|
||||
- indices:admin/create
|
||||
- indices:admin/exists
|
||||
- indices:admin/mapping/put
|
||||
- indices:admin/mappings/fields/get
|
||||
- indices:admin/refresh
|
||||
- indices:admin/validate/query
|
||||
- indices:data/read/get
|
||||
- indices:data/read/mget
|
||||
- indices:data/read/search
|
||||
- indices:data/write/delete
|
||||
- indices:data/write/index
|
||||
- indices:data/write/update
|
||||
- indices:admin/create
|
||||
----
|
||||
|
||||
For more information about configuring access in Shield,
|
||||
see https://www.elasticsearch.org/guide/en/shield/current/authorization.html[Authorization]
|
||||
in the Shield documentation.
|
||||
|
||||
To configure credentials for Kibana, set the `kibana_elasticsearch_username` and
|
||||
To configure credentials the Kibana server, set the `kibana_elasticsearch_username` and
|
||||
`kibana_elasticsearch_password` properties in `kibana.yml`:
|
||||
|
||||
----
|
||||
|
@ -51,6 +32,13 @@ To configure credentials for Kibana, set the `kibana_elasticsearch_username` and
|
|||
kibana_elasticsearch_username: kibana4
|
||||
kibana_elasticsearch_password: kibana4
|
||||
----
|
||||
|
||||
For information about assigning the Kibana server the necessary permissions in Shield,
|
||||
see https://www.elasticsearch.org/guide/en/shield/current/_shield_with_kibana_4.html[Shield with Kibana 4]
|
||||
in the Shield documentation.
|
||||
|
||||
[float]
|
||||
[[enabling-ssl]]
|
||||
=== Enabling SSL
|
||||
Kibana supports SSL encryption for both client requests and the requests the Kibana server
|
||||
sends to Elasticsearch.
|
||||
|
@ -82,6 +70,8 @@ If you are using a self-signed certificate for Elasticsearch, set the `ca` prope
|
|||
ca: /path/to/your/ca/cacert.pem
|
||||
----
|
||||
|
||||
[float]
|
||||
[[controlling-access]]
|
||||
=== Controlling access
|
||||
You can use http://www.elasticsearch.org/overview/shield/[Elasticsearch Shield]
|
||||
(Shield) to control what Elasticsearch data users can access through Kibana.
|
||||
|
@ -89,6 +79,47 @@ Shield provides index-level access control. If a user isn't authorized to run
|
|||
the query that populates a Kibana visualization, the user just sees an empty
|
||||
visualization.
|
||||
|
||||
To configure access to Kibana using Shield, you create one or more Shield roles
|
||||
To configure access to Kibana using Shield, you create Shield roles
|
||||
for Kibana using the `kibana4` default role as a starting point. For more
|
||||
information, see http://www.elasticsearch.org/guide/en/shield/current/_shield_with_kibana_4.html[Using Shield with Kibana 4].
|
||||
information, see http://www.elasticsearch.org/guide/en/shield/current/_shield_with_kibana_4.html[Using Shield with Kibana 4].
|
||||
|
||||
[float]
|
||||
[[load-balancing]]
|
||||
=== Load Balancing Across Multiple Elasticsearch Nodes
|
||||
If you have multiple nodes in your Elasticsearch cluster, the easiest way to distribute Kibana requests
|
||||
across the nodes is to run an Elasticsearch _client_ node on the same machine as Kibana.
|
||||
Elasticsearch client nodes are essentially smart load balancers that are part of the cluster. They
|
||||
process incoming HTTP requests, redirect operations to the other nodes in the cluster as needed, and
|
||||
gather and return the results. For more information, see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/modules-node.html[Node] in the Elasticsearch reference.
|
||||
|
||||
To use a local client node to load balance Kibana requests:
|
||||
|
||||
. Install Elasticsearch on the same machine as Kibana.
|
||||
. Configure the node as a client node. In `elasticsearch.yml`, set both `node.data` and `node.master` to `false`:
|
||||
+
|
||||
--------
|
||||
# 3. You want this node to be neither master nor data node, but
|
||||
# to act as a "search load balancer" (fetching data from nodes,
|
||||
# aggregating results, etc.)
|
||||
#
|
||||
node.master: false
|
||||
node.data: false
|
||||
--------
|
||||
. Configure the client node to join your Elasticsearch cluster. In `elasticsearch.yml`, set the `cluster.name` to the
|
||||
name of your cluster.
|
||||
+
|
||||
--------
|
||||
cluster.name: "my_cluster"
|
||||
--------
|
||||
. Make sure Kibana is configured to point to your local client node. In `kibana.yml`, the `elasticsearch_url` should be set to
|
||||
`localhost:9200`.
|
||||
+
|
||||
--------
|
||||
# The Elasticsearch instance to use for all your queries.
|
||||
elasticsearch_url: "http://localhost:9200"
|
||||
--------
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -186,11 +186,6 @@ To create a scripted field:
|
|||
. Select the index pattern you want to add a scripted field to.
|
||||
. Go to the pattern's *Scripted Fields* tab.
|
||||
. Click *Add Scripted Field*.
|
||||
+
|
||||
TIP: If you are just getting started with scripted fields, you can click
|
||||
*create a few examples from your date fields* to add some scripted fields
|
||||
you can use as a starting point.
|
||||
|
||||
. Enter a name for the scripted field.
|
||||
. Enter the expression that you want to use to compute a value on the fly
|
||||
from your index data.
|
||||
|
|
|
@ -9,7 +9,7 @@ All you need is:
|
|||
** URL of the Elasticsearch instance you want to connect to.
|
||||
** Which Elasticsearch indices you want to search.
|
||||
|
||||
NOTE: If your Elasticsearch installation is protected by http://www.elasticsearch.org/overview/shield/[Shield] see the http://www.elasticsearch.org/guide/en/shield/current/_shield_with_kibana_4.html#_from_the_kibana_4_server_to_elasticsearch[Shield documentation] for additional setup instructions.
|
||||
NOTE: If your Elasticsearch installation is protected by http://www.elasticsearch.org/overview/shield/[Shield] see https://www.elasticsearch.org/guide/en/shield/current/_shield_with_kibana_4.html[Shield with Kibana 4] for additional setup instructions.
|
||||
|
||||
[float]
|
||||
[[install]]
|
||||
|
|
|
@ -77,7 +77,7 @@ the current visualization.
|
|||
[[aggregation-builder]]
|
||||
===== Aggregation Builder
|
||||
|
||||
Use the aggregation builder on the left of the page to configure the metric and bucket aggregations used in your
|
||||
Use the aggregation builder on the left of the page to configure the {ref}/search-aggregations.html#_metrics_aggregations[metric] and {ref}/search-aggregations.html#_bucket_aggregations[bucket] aggregations used in your
|
||||
visualization. Buckets are analogous to SQL `GROUP BY` statements. For more information on aggregations, see the main
|
||||
{ref}/search-aggregations.html[Elasticsearch aggregations reference].
|
||||
|
||||
|
|
|
@ -56,7 +56,8 @@
|
|||
"request": "^2.40.0",
|
||||
"requirefrom": "^0.2.0",
|
||||
"semver": "^4.2.0",
|
||||
"serve-favicon": "~2.2.0"
|
||||
"serve-favicon": "~2.2.0",
|
||||
"through": "^2.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"connect": "~2.19.5",
|
||||
|
@ -97,6 +98,8 @@
|
|||
"requirejs": "~2.1.14",
|
||||
"rjs-build-analysis": "0.0.3",
|
||||
"simple-git": "^0.11.0",
|
||||
"sinon": "^1.12.2",
|
||||
"sinon-as-promised": "^2.0.3",
|
||||
"tar": "^1.0.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,12 +16,20 @@ define(function (require) {
|
|||
while ((result = result.$parent) && result.aggConfig) {
|
||||
var agg = result.aggConfig;
|
||||
var value = result.value;
|
||||
if (agg === datum.aggConfigResult.aggConfig && datum.yScale != null) value *= datum.yScale;
|
||||
|
||||
details.push({
|
||||
var detail = {
|
||||
value: agg.fieldFormatter()(value),
|
||||
label: agg.makeLabel()
|
||||
});
|
||||
};
|
||||
|
||||
if (agg === datum.aggConfigResult.aggConfig) {
|
||||
detail.percent = event.percent;
|
||||
if (datum.yScale != null) {
|
||||
detail.value = agg.fieldFormatter()(value * datum.yScale);
|
||||
}
|
||||
}
|
||||
|
||||
details.push(detail);
|
||||
}
|
||||
|
||||
$tooltipScope.$apply();
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
define(function (require) {
|
||||
var CidrMask = require('utils/cidr_mask');
|
||||
var buildRangeFilter = require('components/filter_manager/lib/range');
|
||||
return function createIpRangeFilterProvider() {
|
||||
return function (aggConfig, key) {
|
||||
var range;
|
||||
if (aggConfig.params.ipRangeType === 'mask') {
|
||||
range = new CidrMask(key).getRange();
|
||||
} else {
|
||||
var addresses = key.split(/\-/);
|
||||
range = {
|
||||
from: addresses[0],
|
||||
to: addresses[1]
|
||||
};
|
||||
}
|
||||
|
||||
return buildRangeFilter(aggConfig.params.field, {gte: range.from, lte: range.to}, aggConfig.vis.indexPattern);
|
||||
};
|
||||
};
|
||||
});
|
46
src/kibana/components/agg_types/buckets/ip_range.js
Normal file
46
src/kibana/components/agg_types/buckets/ip_range.js
Normal file
|
@ -0,0 +1,46 @@
|
|||
define(function (require) {
|
||||
var _ = require('lodash');
|
||||
require('directives/validate_ip');
|
||||
require('directives/validate_cidr_mask');
|
||||
|
||||
return function RangeAggDefinition(Private) {
|
||||
var BucketAggType = Private(require('components/agg_types/buckets/_bucket_agg_type'));
|
||||
var createFilter = Private(require('components/agg_types/buckets/create_filter/ip_range'));
|
||||
|
||||
return new BucketAggType({
|
||||
name: 'ip_range',
|
||||
title: 'IPv4 Range',
|
||||
createFilter: createFilter,
|
||||
makeLabel: function (aggConfig) {
|
||||
return aggConfig.params.field.displayName + ' IP ranges';
|
||||
},
|
||||
params: [
|
||||
{
|
||||
name: 'field',
|
||||
filterFieldTypes: 'ip'
|
||||
}, {
|
||||
name: 'ipRangeType',
|
||||
default: 'fromTo',
|
||||
write: _.noop
|
||||
}, {
|
||||
name: 'ranges',
|
||||
default: {
|
||||
fromTo: [
|
||||
{from: '0.0.0.0', to: '127.255.255.255'},
|
||||
{from: '128.0.0.0', to: '191.255.255.255'}
|
||||
],
|
||||
mask: [
|
||||
{mask: '0.0.0.0/1'},
|
||||
{mask: '128.0.0.0/2'}
|
||||
]
|
||||
},
|
||||
editor: require('text!components/agg_types/controls/ip_ranges.html'),
|
||||
write: function (aggConfig, output) {
|
||||
var ipRangeType = aggConfig.params.ipRangeType;
|
||||
output.params.ranges = aggConfig.params.ranges[ipRangeType];
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
};
|
||||
});
|
88
src/kibana/components/agg_types/controls/ip_ranges.html
Normal file
88
src/kibana/components/agg_types/controls/ip_ranges.html
Normal file
|
@ -0,0 +1,88 @@
|
|||
<div>
|
||||
<p>
|
||||
<button type="button" class="btn btn-default" ng-show="agg.params.ipRangeType == 'mask'" ng-click="agg.params.ipRangeType = 'fromTo'">Use From/To</button>
|
||||
<button type="button" class="btn btn-default" ng-show="agg.params.ipRangeType != 'mask'" ng-click="agg.params.ipRangeType = 'mask'">Use CIDR Masks</button>
|
||||
</p>
|
||||
|
||||
<div ng-show="agg.params.ipRangeType != 'mask'">
|
||||
<table class="vis-editor-agg-editor-ranges form-group">
|
||||
<tr>
|
||||
<th>
|
||||
<label>From</label>
|
||||
</th>
|
||||
<th colspan="2">
|
||||
<label>To</label>
|
||||
</th>
|
||||
</tr>
|
||||
|
||||
<tr
|
||||
ng-repeat="range in agg.params.ranges.fromTo track by $index">
|
||||
<td>
|
||||
<input
|
||||
validate-ip
|
||||
ng-model="range.from"
|
||||
type="text"
|
||||
class="form-control"
|
||||
name="range.from" />
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
validate-ip
|
||||
ng-model="range.to"
|
||||
class="form-control"
|
||||
name="range.to" />
|
||||
</td>
|
||||
<td>
|
||||
<button
|
||||
type="button"
|
||||
ng-click="agg.params.ranges.fromTo.splice($index, 1)"
|
||||
class="btn btn-danger btn-xs">
|
||||
<i class="fa fa-ban" ></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div
|
||||
ng-click="agg.params.ranges.fromTo.push({})"
|
||||
class="sidebar-item-button primary">
|
||||
Add Range
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-show="agg.params.ipRangeType == 'mask'">
|
||||
<table class="vis-editor-agg-editor-ranges form-group">
|
||||
<tr>
|
||||
<th>
|
||||
<label>Mask</label>
|
||||
</th>
|
||||
</tr>
|
||||
|
||||
<tr
|
||||
ng-repeat="range in agg.params.ranges.mask track by $index">
|
||||
<td>
|
||||
<input
|
||||
validate-cidr-mask
|
||||
ng-model="range.mask"
|
||||
type="text"
|
||||
class="form-control"
|
||||
name="range.from" />
|
||||
</td>
|
||||
<td>
|
||||
<button
|
||||
type="button"
|
||||
ng-click="agg.params.ranges.mask.splice($index, 1)"
|
||||
class="btn btn-danger btn-xs">
|
||||
<i class="fa fa-ban" ></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div
|
||||
ng-click="agg.params.ranges.mask.push({})"
|
||||
class="sidebar-item-button primary">
|
||||
Add Range
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -29,7 +29,7 @@
|
|||
type="button"
|
||||
ng-click="agg.params.ranges.splice($index, 1)"
|
||||
class="btn btn-danger btn-xs">
|
||||
<i class="fa fa-ban" ></i>
|
||||
<i class="fa fa-times"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
@ -17,6 +17,7 @@ define(function (require) {
|
|||
Private(require('components/agg_types/buckets/date_histogram')),
|
||||
Private(require('components/agg_types/buckets/histogram')),
|
||||
Private(require('components/agg_types/buckets/range')),
|
||||
Private(require('components/agg_types/buckets/ip_range')),
|
||||
Private(require('components/agg_types/buckets/terms')),
|
||||
Private(require('components/agg_types/buckets/filters')),
|
||||
Private(require('components/agg_types/buckets/significant_terms')),
|
||||
|
|
|
@ -2,7 +2,7 @@ define(function (require) {
|
|||
var errors = require('errors');
|
||||
var qs = require('utils/query_string');
|
||||
|
||||
return function RedirectWhenMissingFn($location, kbnUrl, Notifier) {
|
||||
return function RedirectWhenMissingFn($location, kbnUrl, Notifier, Promise) {
|
||||
var SavedObjectNotFound = errors.SavedObjectNotFound;
|
||||
|
||||
var notify = new Notifier();
|
||||
|
@ -31,7 +31,7 @@ define(function (require) {
|
|||
|
||||
notify.error(err);
|
||||
kbnUrl.redirect(url);
|
||||
return;
|
||||
return Promise.halt();
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<td colspan="{{ columns.length + 2 }}">
|
||||
<a class="pull-right" ng-href="#/doc/{{indexPattern.id}}/{{row._index}}/{{row._type}}/{{row._id}}">
|
||||
<small>Link to /{{row._index}}/{{row._type}}/{{row._id}}</small></i>
|
||||
<a class="pull-right" ng-href="#/doc/{{indexPattern.id}}/{{row._index}}/{{row._type}}/?id={{row._id | uriescape}}">
|
||||
<small>Link to /{{row._index}}/{{row._type}}/{{row._id | uriescape}}</small></i>
|
||||
</a>
|
||||
<doc-viewer hit="row" filter="filter" index-pattern="indexPattern"></doc-viewer>
|
||||
</td>
|
|
@ -27,6 +27,9 @@ define(function (require) {
|
|||
$scope.formatted = _.mapValues($scope.flattened, function (value, name) {
|
||||
var mapping = $scope.mapping[name];
|
||||
var formatter = (mapping && mapping.format) ? mapping.format : defaultFormat;
|
||||
if (_.isArray(value) && typeof value[0] === 'object') {
|
||||
value = JSON.stringify(value, null, ' ');
|
||||
}
|
||||
return formatter.convert(value);
|
||||
});
|
||||
$scope.fields = _.keys($scope.flattened).sort();
|
||||
|
@ -39,4 +42,4 @@ define(function (require) {
|
|||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -91,12 +91,14 @@
|
|||
</div>
|
||||
<br>
|
||||
<div class="small">
|
||||
<input
|
||||
ng-model="relative.round"
|
||||
ng-checked="relative.round"
|
||||
ng-change="formatRelative()"
|
||||
type="checkbox">
|
||||
round to the {{units[relative.unit]}}
|
||||
<label>
|
||||
<input
|
||||
ng-model="relative.round"
|
||||
ng-checked="relative.round"
|
||||
ng-change="formatRelative()"
|
||||
type="checkbox">
|
||||
round to the {{units[relative.unit]}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -169,7 +171,11 @@
|
|||
<br>
|
||||
<div ng-repeat="list in refreshLists" class="kbn-refresh-section">
|
||||
<ul class="list-unstyled">
|
||||
<li ng-repeat="interval in list"><a ng-click="setRefreshInterval(interval)">{{interval.display}}</a></li>
|
||||
<li ng-repeat="inter in list">
|
||||
<a class="refresh-interval" ng-class="{ 'refresh-interval-active': interval.value == inter.value }" ng-click="setRefreshInterval(inter)">
|
||||
{{inter.display}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -6,6 +6,7 @@ define(function (require) {
|
|||
var orderKeys = Private(require('components/vislib/components/zero_injection/ordered_x_keys'));
|
||||
var getLabels = Private(require('components/vislib/components/labels/labels'));
|
||||
var color = Private(require('components/vislib/components/color/color'));
|
||||
var errors = require('errors');
|
||||
|
||||
/**
|
||||
* Provides an API for pulling values off the data
|
||||
|
@ -510,6 +511,33 @@ define(function (require) {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks whether all pie slices have zero values.
|
||||
* If so, an error is thrown.
|
||||
*/
|
||||
Data.prototype._validatePieData = function () {
|
||||
var visData = this.getVisData();
|
||||
|
||||
visData.forEach(function (chartData) {
|
||||
chartData.slices = (function withoutZeroSlices(slices) {
|
||||
if (!slices.children) return slices;
|
||||
|
||||
slices = _.clone(slices);
|
||||
slices.children = slices.children.reduce(function (children, child) {
|
||||
if (child.size !== 0) {
|
||||
children.push(withoutZeroSlices(child));
|
||||
}
|
||||
return children;
|
||||
}, []);
|
||||
return slices;
|
||||
}(chartData.slices));
|
||||
|
||||
if (chartData.slices.children.length === 0) {
|
||||
throw new errors.PieContainsAllZeros();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns an array of names ordered by appearance in the nested array
|
||||
* of objects
|
||||
|
@ -521,6 +549,8 @@ define(function (require) {
|
|||
var self = this;
|
||||
var names = [];
|
||||
|
||||
this._validatePieData();
|
||||
|
||||
_.forEach(this.getVisData(), function (obj) {
|
||||
var columns = obj.raw ? obj.raw.columns : undefined;
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ define(function (require) {
|
|||
|
||||
this.handler = handler;
|
||||
this.dispatch = d3.dispatch('brush', 'click', 'hover', 'mouseup',
|
||||
'mousedown', 'mouseover');
|
||||
'mousedown', 'mouseover', 'mouseout');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -43,19 +43,7 @@ define(function (require) {
|
|||
var color = handler.data.color;
|
||||
var isPercentage = (handler._attr.mode === 'percentage');
|
||||
|
||||
if (isSeries) {
|
||||
// Find object with the actual d value and add it to the point object
|
||||
var object = _.find(series, { 'label': d.label });
|
||||
d.value = +object.values[i].y;
|
||||
|
||||
if (isPercentage) {
|
||||
|
||||
// Add the formatted percentage to the point object
|
||||
d.percent = (100 * d.y).toFixed(1) + '%';
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
var eventData = {
|
||||
value: d.y,
|
||||
point: datum,
|
||||
datum: datum,
|
||||
|
@ -69,6 +57,19 @@ define(function (require) {
|
|||
e: d3.event,
|
||||
handler: handler
|
||||
};
|
||||
|
||||
if (isSeries) {
|
||||
// Find object with the actual d value and add it to the point object
|
||||
var object = _.find(series, { 'label': d.label });
|
||||
eventData.value = +object.values[i].y;
|
||||
|
||||
if (isPercentage) {
|
||||
// Add the formatted percentage to the point object
|
||||
eventData.percent = (100 * d.y).toFixed(1) + '%';
|
||||
}
|
||||
}
|
||||
|
||||
return eventData;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -91,6 +92,7 @@ define(function (require) {
|
|||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @method addHoverEvent
|
||||
|
@ -100,6 +102,7 @@ define(function (require) {
|
|||
var self = this;
|
||||
var isClickable = (this.dispatch.on('click'));
|
||||
var addEvent = this.addEvent;
|
||||
var $el = this.handler.el;
|
||||
|
||||
function hover(d, i) {
|
||||
d3.event.stopPropagation();
|
||||
|
@ -109,12 +112,32 @@ define(function (require) {
|
|||
self.addMousePointer.call(this, arguments);
|
||||
}
|
||||
|
||||
self.highlightLegend.call(this, $el);
|
||||
self.dispatch.hover.call(this, self.eventResponse(d, i));
|
||||
}
|
||||
|
||||
return addEvent('mouseover', hover);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @method addMouseoutEvent
|
||||
* @returns {Function}
|
||||
*/
|
||||
Dispatch.prototype.addMouseoutEvent = function () {
|
||||
var self = this;
|
||||
var addEvent = this.addEvent;
|
||||
var $el = this.handler.el;
|
||||
|
||||
function mouseout() {
|
||||
d3.event.stopPropagation();
|
||||
|
||||
self.unHighlightLegend.call(this, $el);
|
||||
}
|
||||
|
||||
return addEvent('mouseout', mouseout);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @method addClickEvent
|
||||
|
@ -188,7 +211,7 @@ define(function (require) {
|
|||
|
||||
|
||||
/**
|
||||
* Mouse over Behavior
|
||||
* Mouseover Behavior
|
||||
*
|
||||
* @method addMousePointer
|
||||
* @returns {D3.Selection}
|
||||
|
@ -197,6 +220,38 @@ define(function (require) {
|
|||
return d3.select(this).style('cursor', 'pointer');
|
||||
};
|
||||
|
||||
/**
|
||||
* Mouseover Behavior
|
||||
*
|
||||
* @param element {D3.Selection}
|
||||
* @method highlightLegend
|
||||
*/
|
||||
Dispatch.prototype.highlightLegend = function (element) {
|
||||
var classList = d3.select(this).node().classList;
|
||||
var liClass = d3.select(this).node().classList[1];
|
||||
|
||||
d3.select(element)
|
||||
.select('.legend-ul')
|
||||
.selectAll('li.color')
|
||||
.filter(function (d, i) {
|
||||
return d3.select(this).node().classList[1] !== liClass;
|
||||
})
|
||||
.classed('blur_shape', true);
|
||||
};
|
||||
|
||||
/**
|
||||
* Mouseout Behavior
|
||||
*
|
||||
* @param element {D3.Selection}
|
||||
* @method unHighlightLegend
|
||||
*/
|
||||
Dispatch.prototype.unHighlightLegend = function (element) {
|
||||
d3.select(element)
|
||||
.select('.legend-ul')
|
||||
.selectAll('li.color')
|
||||
.classed('blur_shape', false);
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds D3 brush to SVG and returns the brush function
|
||||
*
|
||||
|
|
|
@ -85,14 +85,16 @@ define(function (require) {
|
|||
};
|
||||
|
||||
/**
|
||||
* Creates a class name based on the colors assigned to each label
|
||||
* Creates a class name based on the hexColor assigned to each label
|
||||
*
|
||||
* @method colorToClass
|
||||
* @param name {String} Label
|
||||
* @param hexColor {String} Label
|
||||
* @returns {string} CSS class name
|
||||
*/
|
||||
Legend.prototype.colorToClass = function (name) {
|
||||
return 'c' + name.replace(/[#]/g, '');
|
||||
Legend.prototype.colorToClass = function (hexColor) {
|
||||
if (hexColor) {
|
||||
return 'c' + hexColor.replace(/[#]/g, '');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -131,35 +133,70 @@ define(function (require) {
|
|||
}
|
||||
});
|
||||
|
||||
visEl.selectAll('.color')
|
||||
legendDiv.select('.legend-ul').selectAll('li')
|
||||
.on('mouseover', function (d) {
|
||||
var liClass = '.' + self.colorToClass(self.color(d));
|
||||
visEl.selectAll('.color').classed('blur_shape', true);
|
||||
var liClass = self.colorToClass(self.color(d));
|
||||
var charts = visEl.selectAll('.chart');
|
||||
|
||||
// legend
|
||||
legendDiv.selectAll('li')
|
||||
.filter(function (d) {
|
||||
return d3.select(this).node().classList[1] !== liClass;
|
||||
})
|
||||
.classed('blur_shape', true);
|
||||
|
||||
// lines/area
|
||||
charts.selectAll('.color')
|
||||
.filter(function (d) {
|
||||
return d3.select(this).node().classList[1] !== liClass;
|
||||
})
|
||||
.classed('blur_shape', true);
|
||||
|
||||
// circles
|
||||
charts.selectAll('.line circle')
|
||||
.filter(function (d) {
|
||||
return d3.select(this).node().classList[1] !== liClass;
|
||||
})
|
||||
.classed('blur_shape', true);
|
||||
|
||||
// pie slices
|
||||
charts.selectAll('.slice')
|
||||
.filter(function (d) {
|
||||
return d3.select(this).node().classList[1] !== liClass;
|
||||
})
|
||||
.classed('blur_shape', true);
|
||||
|
||||
var eventEl = d3.select(this);
|
||||
eventEl.style('white-space', 'inherit');
|
||||
eventEl.style('word-break', 'break-all');
|
||||
|
||||
// select series on chart
|
||||
visEl.selectAll(liClass).classed('blur_shape', false);
|
||||
})
|
||||
.on('mouseout', function () {
|
||||
/*
|
||||
* The default opacity of elements in charts may be modified by the
|
||||
* chart constructor, and so may differ from that of the legend
|
||||
*/
|
||||
visEl.selectAll('.chart')
|
||||
.selectAll('.color')
|
||||
|
||||
var charts = visEl.selectAll('.chart');
|
||||
|
||||
// legend
|
||||
legendDiv.selectAll('li')
|
||||
.classed('blur_shape', false);
|
||||
|
||||
// lines/areas
|
||||
charts.selectAll('.color')
|
||||
.classed('blur_shape', false);
|
||||
|
||||
// circles
|
||||
charts.selectAll('.line circle')
|
||||
.classed('blur_shape', false);
|
||||
|
||||
// pie slices
|
||||
charts.selectAll('.slice')
|
||||
.classed('blur_shape', false);
|
||||
|
||||
var eventEl = d3.select(this);
|
||||
eventEl.style('white-space', 'nowrap');
|
||||
eventEl.style('word-break', 'inherit');
|
||||
|
||||
// Legend values should always return to their default opacity of 1
|
||||
visEl.select('.legend-ul')
|
||||
.selectAll('.color')
|
||||
.classed('blur_shape', false);
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -55,7 +55,7 @@ define(function (require) {
|
|||
* @returns {String} CSS class name
|
||||
*/
|
||||
Chart.prototype.colorToClass = function (label) {
|
||||
return 'color ' + Legend.prototype.colorToClass.call(null, label);
|
||||
return Legend.prototype.colorToClass.call(null, label);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -96,7 +96,7 @@ define(function (require) {
|
|||
// Append path
|
||||
path = layer.append('path')
|
||||
.attr('class', function (d) {
|
||||
return self.colorToClass(color(d[0].label));
|
||||
return 'color ' + self.colorToClass(color(d[0].label));
|
||||
})
|
||||
.style('fill', function (d) {
|
||||
return color(d[0].label);
|
||||
|
@ -125,8 +125,9 @@ define(function (require) {
|
|||
var isBrushable = events.isBrushable();
|
||||
var brush = isBrushable ? events.addBrushEvent(svg) : undefined;
|
||||
var hover = events.addHoverEvent();
|
||||
var mouseout = events.addMouseoutEvent();
|
||||
var click = events.addClickEvent();
|
||||
var attachedEvents = element.call(hover).call(click);
|
||||
var attachedEvents = element.call(hover).call(mouseout).call(click);
|
||||
|
||||
if (isBrushable) {
|
||||
attachedEvents.call(brush);
|
||||
|
@ -144,6 +145,7 @@ define(function (require) {
|
|||
* @returns {D3.UpdateSelection} SVG with circles added
|
||||
*/
|
||||
AreaChart.prototype.addCircles = function (svg, data) {
|
||||
var self = this;
|
||||
var color = this.handler.data.getColorFunc();
|
||||
var xScale = this.handler.xAxis.xScale;
|
||||
var yScale = this.handler.yAxis.yScale;
|
||||
|
@ -162,7 +164,7 @@ define(function (require) {
|
|||
.append('g')
|
||||
.attr('class', 'points area');
|
||||
|
||||
// Append the bars
|
||||
// append the bars
|
||||
circles = layer
|
||||
.selectAll('rect')
|
||||
.data(function appendData(data) {
|
||||
|
@ -179,7 +181,7 @@ define(function (require) {
|
|||
.enter()
|
||||
.append('circle')
|
||||
.attr('class', function circleClass(d) {
|
||||
return d.label;
|
||||
return d.label + ' ' + self.colorToClass(color(d.label));
|
||||
})
|
||||
.attr('stroke', function strokeColor(d) {
|
||||
return color(d.label);
|
||||
|
|
|
@ -69,7 +69,7 @@ define(function (require) {
|
|||
.enter()
|
||||
.append('rect')
|
||||
.attr('class', function (d) {
|
||||
return self.colorToClass(color(d.label));
|
||||
return 'color ' + self.colorToClass(color(d.label));
|
||||
})
|
||||
.attr('fill', function (d) {
|
||||
return color(d.label);
|
||||
|
@ -146,6 +146,13 @@ define(function (require) {
|
|||
return Math.abs(yScale(d.y0 + d.y) - yScale(d.y0));
|
||||
}
|
||||
|
||||
// Due to an issue with D3 not returning zeros correctly when using
|
||||
// an offset='expand', need to add conditional statement to handle zeros
|
||||
// appropriately
|
||||
if (d._input.y === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// for split bars or for one series,
|
||||
// last series will have d.y0 = 0
|
||||
if (d.y0 === 0 && yMin > 0) {
|
||||
|
@ -239,8 +246,9 @@ define(function (require) {
|
|||
var isBrushable = events.isBrushable();
|
||||
var brush = isBrushable ? events.addBrushEvent(svg) : undefined;
|
||||
var hover = events.addHoverEvent();
|
||||
var mouseout = events.addMouseoutEvent();
|
||||
var click = events.addClickEvent();
|
||||
var attachedEvents = element.call(hover).call(click);
|
||||
var attachedEvents = element.call(hover).call(mouseout).call(click);
|
||||
|
||||
if (isBrushable) {
|
||||
attachedEvents.call(brush);
|
||||
|
|
|
@ -45,8 +45,9 @@ define(function (require) {
|
|||
var isBrushable = events.isBrushable();
|
||||
var brush = isBrushable ? events.addBrushEvent(svg) : undefined;
|
||||
var hover = events.addHoverEvent();
|
||||
var mouseout = events.addMouseoutEvent();
|
||||
var click = events.addClickEvent();
|
||||
var attachedEvents = element.call(hover).call(click);
|
||||
var attachedEvents = element.call(hover).call(mouseout).call(click);
|
||||
|
||||
if (isBrushable) {
|
||||
attachedEvents.call(brush);
|
||||
|
@ -65,6 +66,7 @@ define(function (require) {
|
|||
*/
|
||||
LineChart.prototype.addCircles = function (svg, data) {
|
||||
var self = this;
|
||||
var showCircles = this._attr.showCircles;
|
||||
var color = this.handler.data.getColorFunc();
|
||||
var xScale = this.handler.xAxis.xScale;
|
||||
var yScale = this.handler.yAxis.yScale;
|
||||
|
@ -81,7 +83,7 @@ define(function (require) {
|
|||
.attr('class', 'points line');
|
||||
|
||||
var circles = layer
|
||||
.selectAll('rect')
|
||||
.selectAll('circle')
|
||||
.data(function appendData(d) {
|
||||
return d;
|
||||
});
|
||||
|
@ -105,14 +107,26 @@ define(function (require) {
|
|||
return color(d.label);
|
||||
}
|
||||
|
||||
function colorCircle(d) {
|
||||
var parent = d3.select(this).node().parentNode;
|
||||
var lengthOfParent = d3.select(parent).data()[0].length;
|
||||
var isVisible = (lengthOfParent === 1);
|
||||
|
||||
// If only 1 point exists, show circle
|
||||
if (!showCircles && !isVisible) return 'none';
|
||||
return cColor(d);
|
||||
}
|
||||
|
||||
circles
|
||||
.enter()
|
||||
.append('circle')
|
||||
.attr('r', visibleRadius)
|
||||
.attr('cx', cx)
|
||||
.attr('cy', cy)
|
||||
.attr('fill', cColor)
|
||||
.attr('class', 'circle-decoration');
|
||||
.attr('fill', colorCircle)
|
||||
.attr('class', function circleClass(d) {
|
||||
return 'circle-decoration ' + self.colorToClass(color(d.label));
|
||||
});
|
||||
|
||||
circles
|
||||
.enter()
|
||||
|
@ -172,7 +186,7 @@ define(function (require) {
|
|||
|
||||
lines.append('path')
|
||||
.attr('class', function lineClass(d) {
|
||||
return self.colorToClass(color(d.label));
|
||||
return 'color ' + self.colorToClass(color(d.label));
|
||||
})
|
||||
.attr('d', function lineD(d) {
|
||||
return line(d.values);
|
||||
|
@ -299,7 +313,6 @@ define(function (require) {
|
|||
.style('stroke', '#ddd')
|
||||
.style('stroke-width', lineStrokeWidth);
|
||||
|
||||
|
||||
return svg;
|
||||
});
|
||||
};
|
||||
|
|
|
@ -24,9 +24,6 @@ define(function (require) {
|
|||
}
|
||||
PieChart.Super.apply(this, arguments);
|
||||
|
||||
// Check whether pie chart should be rendered.
|
||||
this._validatePieData();
|
||||
|
||||
this._attr = _.defaults(handler._attr || {}, {
|
||||
isDonut: handler._attr.isDonut || false
|
||||
});
|
||||
|
@ -44,6 +41,7 @@ define(function (require) {
|
|||
|
||||
return element
|
||||
.call(events.addHoverEvent())
|
||||
.call(events.addMouseoutEvent())
|
||||
.call(events.addClickEvent());
|
||||
};
|
||||
|
||||
|
@ -150,29 +148,6 @@ define(function (require) {
|
|||
return path;
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks whether all pie slices have zero values.
|
||||
* If so, an error is thrown.
|
||||
*/
|
||||
PieChart.prototype._validatePieData = function () {
|
||||
this.chartData.slices = (function withoutZeroSlices(slices) {
|
||||
if (!slices.children) return slices;
|
||||
|
||||
slices = _.clone(slices);
|
||||
slices.children = slices.children.reduce(function (children, child) {
|
||||
if (child.size !== 0) {
|
||||
children.push(withoutZeroSlices(child));
|
||||
}
|
||||
return children;
|
||||
}, []);
|
||||
return slices;
|
||||
}(this.chartData.slices));
|
||||
|
||||
if (this.chartData.slices.children.length === 0) {
|
||||
throw new errors.PieContainsAllZeros();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders d3 visualization
|
||||
*
|
||||
|
|
18
src/kibana/directives/auto_select_if_only_one.js
Normal file
18
src/kibana/directives/auto_select_if_only_one.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
define(function (require) {
|
||||
var module = require('modules').get('kibana');
|
||||
|
||||
module.directive('autoSelectIfOnlyOne', function () {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: 'ngModel',
|
||||
link: function (scope, element, attributes, ngModelCtrl) {
|
||||
scope.$watch(attributes.autoSelectIfOnlyOne, function (options) {
|
||||
if (options && options.length === 1) {
|
||||
ngModelCtrl.$setViewValue(options[0]);
|
||||
ngModelCtrl.$render();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
|
@ -3,7 +3,7 @@ define(function (require) {
|
|||
var $ = require('jquery');
|
||||
var _ = require('lodash');
|
||||
|
||||
module.directive('cssTruncate', function ($compile) {
|
||||
module.directive('cssTruncate', function ($timeout) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
scope: {},
|
||||
|
@ -16,11 +16,18 @@ define(function (require) {
|
|||
'word-break': 'break-all',
|
||||
});
|
||||
|
||||
if (!_.isUndefined(attrs.cssTruncateExpandable)) {
|
||||
$elem.css({'cursor': 'pointer'});
|
||||
$elem.bind('click', function () {
|
||||
$scope.toggle();
|
||||
});
|
||||
if (attrs.cssTruncateExpandable != null) {
|
||||
$scope.$watch(
|
||||
function () { return $elem.html(); },
|
||||
function () {
|
||||
if ($elem[0].offsetWidth < $elem[0].scrollWidth) {
|
||||
$elem.css({'cursor': 'pointer'});
|
||||
$elem.bind('click', function () {
|
||||
$scope.toggle();
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
$scope.toggle = function () {
|
||||
|
|
|
@ -6,7 +6,8 @@ define(function (require) {
|
|||
restrict: 'A',
|
||||
link: function ($scope, $elem, attrs) {
|
||||
$timeout(function () {
|
||||
$elem[0].focus();
|
||||
$elem.focus();
|
||||
if (attrs.inputFocus === 'select') $elem.select();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
28
src/kibana/directives/validate_cidr_mask.js
Normal file
28
src/kibana/directives/validate_cidr_mask.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
define(function (require) {
|
||||
var _ = require('lodash');
|
||||
var CidrMask = require('utils/cidr_mask');
|
||||
|
||||
require('modules').get('kibana').directive('validateCidrMask', function () {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: 'ngModel',
|
||||
scope: {
|
||||
'ngModel': '='
|
||||
},
|
||||
link: function ($scope, elem, attr, ngModel) {
|
||||
ngModel.$parsers.unshift(validateCidrMask);
|
||||
ngModel.$formatters.unshift(validateCidrMask);
|
||||
|
||||
function validateCidrMask(mask) {
|
||||
try {
|
||||
mask = new CidrMask(mask);
|
||||
ngModel.$setValidity('cidrMaskInput', true);
|
||||
return mask.toString();
|
||||
} catch (e) {
|
||||
ngModel.$setValidity('cidrMaskInput', false);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
|
@ -1,5 +1,6 @@
|
|||
define(function (require) {
|
||||
var _ = require('lodash');
|
||||
var Ipv4Address = require('utils/ipv4_address');
|
||||
|
||||
require('modules')
|
||||
.get('kibana')
|
||||
|
@ -11,35 +12,21 @@ define(function (require) {
|
|||
'ngModel': '=',
|
||||
},
|
||||
link: function ($scope, elem, attr, ngModel) {
|
||||
|
||||
var isIP = function (value) {
|
||||
if (!value) return false;
|
||||
var parts = value.match(/([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/);
|
||||
|
||||
var valid = true;
|
||||
if (parts) {
|
||||
_.each(parts.slice(1, 5), function (octet) {
|
||||
if (octet > 255 || octet < 0) valid = false;
|
||||
});
|
||||
} else {
|
||||
valid = false;
|
||||
function validateIp(ipAddress) {
|
||||
try {
|
||||
ipAddress = new Ipv4Address(ipAddress);
|
||||
ngModel.$setValidity('ipInput', true);
|
||||
return ipAddress.toString();
|
||||
} catch (e) {
|
||||
ngModel.$setValidity('ipInput', false);
|
||||
}
|
||||
return valid;
|
||||
};
|
||||
}
|
||||
|
||||
// From User
|
||||
ngModel.$parsers.unshift(function (value) {
|
||||
var valid = isIP(value);
|
||||
ngModel.$setValidity('ipInput', valid);
|
||||
return valid ? value : undefined;
|
||||
});
|
||||
ngModel.$parsers.unshift(validateIp);
|
||||
|
||||
// To user
|
||||
ngModel.$formatters.unshift(function (value) {
|
||||
ngModel.$setValidity('ipInput', isIP(value));
|
||||
return value;
|
||||
});
|
||||
|
||||
ngModel.$formatters.unshift(validateIp);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
<img width="128" src="images/initial_load.gif">
|
||||
<h1>
|
||||
<strong>Kibana</strong>
|
||||
<small>is loading. Give me a moment here. I'm loading a whole bunch of code. Don't worry, all this good stuff will be cached up for next time!</small>
|
||||
<small id="cache-message">is loading. Give me a moment here. I'm loading a whole bunch of code. Don't worry, all this good stuff will be cached up for next time!</small>
|
||||
</h1>
|
||||
</center>
|
||||
</div>
|
||||
|
@ -33,6 +33,9 @@
|
|||
<script src="bower_components/requirejs/require.js?_b=@@buildNum"></script>
|
||||
<script src="require.config.js?_b=@@buildNum"></script>
|
||||
<script>
|
||||
var showCacheMessage = location.href.indexOf('?embed') < 0 && location.href.indexOf('&embed') < 0;
|
||||
if (!showCacheMessage) document.getElementById('cache-message').style.display = 'none';
|
||||
|
||||
if (window.KIBANA_BUILD_NUM.substr(0, 2) !== '@@') {
|
||||
// only cache bust if this is really the build number
|
||||
require.config({ urlArgs: '_b=' + window.KIBANA_BUILD_NUM });
|
||||
|
|
|
@ -10,10 +10,11 @@
|
|||
<div class="input-group"
|
||||
ng-class="queryInput.$invalid ? 'has-error' : ''">
|
||||
|
||||
<input type="text" input-focus
|
||||
<input type="text"
|
||||
placeholder="Filter..."
|
||||
class="form-control"
|
||||
ng-model="state.query"
|
||||
input-focus
|
||||
kbn-typeahead-input
|
||||
validate-query>
|
||||
<button type="submit" class="btn btn-default" ng-disabled="queryInput.$invalid">
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<form ng-submit="opts.save()">
|
||||
<div class="form-group">
|
||||
<label for="exampleInputEmail1">Save As</label>
|
||||
<input type="text" ng-model="opts.dashboard.title" class="form-control" placeholder="Dashboard title" input-focus>
|
||||
<input type="text" ng-model="opts.dashboard.title" class="form-control" placeholder="Dashboard title" input-focus="select">
|
||||
</div>
|
||||
<button type="submit" ng-disabled="!opts.dashboard.title" class="btn btn-primary">Save</button>
|
||||
</form>
|
|
@ -28,7 +28,7 @@ define(function (require) {
|
|||
var warnings = [];
|
||||
|
||||
if (!field.scripted) {
|
||||
if (!field.doc_values) {
|
||||
if (!field.doc_values && !(field.analyzed && field.type === 'string')) {
|
||||
warnings.push('Doc values are not enabled on this field. This may lead to excess heap consumption when visualizing.');
|
||||
}
|
||||
|
||||
|
|
|
@ -38,11 +38,11 @@
|
|||
class="sidebar-item-button primary">
|
||||
Visualize
|
||||
<span class="discover-field-vis-warning" ng-show="warnings.length" tooltip="{{warnings.join(' ')}}">
|
||||
( {{warnings.length}} warnings <i class="fa fa-warning"></i> )
|
||||
( {{warnings.length}} <ng-pluralize count="warnings.length" when="{'1':'warning', 'other':'warnings'}"></ng-pluralize> <i class="fa fa-warning"></i> )
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div ng-show="!field.indexed && !field.scripted"
|
||||
disabled="disabled"
|
||||
tooltip="This field is not indexed thus unavailable for visualization and search"
|
||||
class="sidebar-item-button primary">Not Indexed</div>
|
||||
class="sidebar-item-button primary">Not Indexed</div>
|
||||
|
|
|
@ -1,6 +1,23 @@
|
|||
<div class="sidebar-list">
|
||||
<div css-truncate css-truncate-expandable="true" class="index-pattern">
|
||||
{{ indexPattern.id }}
|
||||
<div ng-show="indexPatternList.length > 1">
|
||||
<div class="index-pattern" ng-click="showIndexPatternSelection = !showIndexPatternSelection">
|
||||
<div css-truncate>{{ indexPattern.id }}</div>
|
||||
<i ng-hide="showIndexPatternSelection" class="fa fa-caret-down"></i>
|
||||
<i ng-show="showIndexPatternSelection" class="fa fa-caret-up"></i>
|
||||
</div>
|
||||
<div ng-show="showIndexPatternSelection">
|
||||
<ul class="list-unstyled sidebar-item index-pattern-selection">
|
||||
<li css-truncate class="sidebar-item-title" ng-repeat="id in indexPatternList" ng-show="indexPattern.id != id" ng-click="setIndexPattern(id)">{{id}}</li>
|
||||
</ul>
|
||||
<div ng-click="showIndexPatternSelection = !showIndexPatternSelection" class="discover-field-details-close">
|
||||
<i class="fa fa-chevron-up"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-hide="indexPatternList.length > 1">
|
||||
<div class="index-pattern">
|
||||
<div css-truncate>{{ indexPattern.id }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-list-header">
|
||||
|
|
|
@ -21,17 +21,24 @@ define(function (require) {
|
|||
data: '=',
|
||||
state: '=',
|
||||
indexPattern: '=',
|
||||
indexPatternList: '=',
|
||||
updateFilterInQuery: '=filter'
|
||||
},
|
||||
template: html,
|
||||
controller: function ($scope) {
|
||||
controller: function ($scope, $route) {
|
||||
$scope.setIndexPattern = function (indexPattern) {
|
||||
$scope.state.index = indexPattern;
|
||||
$scope.state.save();
|
||||
$route.reload();
|
||||
};
|
||||
|
||||
var filter = $scope.filter = {
|
||||
props: [
|
||||
'type',
|
||||
'indexed',
|
||||
'analyzed',
|
||||
'missing'
|
||||
'missing',
|
||||
'name'
|
||||
],
|
||||
defaults: {
|
||||
missing: true
|
||||
|
|
|
@ -77,7 +77,6 @@ define(function (require) {
|
|||
|
||||
// config panel templates
|
||||
$scope.configTemplate = new ConfigTemplate({
|
||||
config: require('text!plugins/discover/partials/settings.html'),
|
||||
load: require('text!plugins/discover/partials/load_search.html'),
|
||||
save: require('text!plugins/discover/partials/save_search.html')
|
||||
});
|
||||
|
@ -139,7 +138,7 @@ define(function (require) {
|
|||
$scope.fields = _($scope.indexPattern.fields)
|
||||
.sortBy('name')
|
||||
.transform(function (fields, field) {
|
||||
// clone the field with Object.create so that it's getters
|
||||
// clone the field with Object.create so that its getters
|
||||
// and non-enumerable props are preserved
|
||||
var clone = Object.create(field);
|
||||
clone.display = _.contains($state.columns, field.name);
|
||||
|
@ -204,18 +203,6 @@ define(function (require) {
|
|||
timefilter.enabled = !!timefield;
|
||||
});
|
||||
|
||||
$scope.$watch('opts.index', changeIndexPattern($scope.opts, $state));
|
||||
$scope.$watch('state.index', changeIndexPattern($state, $scope.opts));
|
||||
function changeIndexPattern(from, to) {
|
||||
return function () {
|
||||
if (from.index === to.index) return;
|
||||
|
||||
to.index = from.index;
|
||||
if (to === $state) $state.save();
|
||||
$route.reload();
|
||||
};
|
||||
}
|
||||
|
||||
$scope.$watchMulti([
|
||||
'rows',
|
||||
'fetchStatus'
|
||||
|
|
|
@ -30,9 +30,6 @@
|
|||
<kbn-tooltip text="Load Saved Search" placement="bottom" append-to-body="1">
|
||||
<button ng-click="configTemplate.toggle('load')"><i class="fa fa-folder-open-o"></i></button>
|
||||
</kbn-tooltip>
|
||||
<kbn-tooltip text="Settings" placement="bottom" append-to-body="1">
|
||||
<button ng-click="configTemplate.toggle('config')"><i class="fa fa-gear"></i></button>
|
||||
</kbn-tooltip>
|
||||
</div>
|
||||
</navbar>
|
||||
|
||||
|
@ -51,6 +48,7 @@
|
|||
data="rows"
|
||||
filter="filterQuery"
|
||||
index-pattern="searchSource.get('index')"
|
||||
index-pattern-list="opts.indexPatternList"
|
||||
state="state">
|
||||
</disc-field-chooser>
|
||||
</div>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<div class="col-md-12">
|
||||
<div class="form-group">
|
||||
<label class="control-label">Save Search</label>
|
||||
<input ng-model="opts.savedSearch.title" input-focus class="form-control" placeholder="Name this search...">
|
||||
<input ng-model="opts.savedSearch.title" input-focus="select" class="form-control" placeholder="Name this search...">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button ng-click="opts.saveDataSource()" ng-disabled="!opts.savedSearch.title" type="submit" class="btn btn-primary">
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<label class="control-label">
|
||||
Index Pattern
|
||||
<a class="small" ng-href="#/settings/indices/{{opts.index | uriescape}}"><i class="fa fa-pencil"></i></a>
|
||||
</label>
|
||||
<select
|
||||
class="form-control"
|
||||
ng-model="opts.index"
|
||||
ng-options="id as id for id in opts.indexPatternList | orderBy:'toString()'"
|
||||
ng-change="opts.changeIndexAndReload()">
|
||||
</select>
|
||||
<small>
|
||||
Time field: <strong>{{opts.timefield || 'not configured'}}</strong>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -13,14 +13,22 @@ define(function (require) {
|
|||
'kibana/index_patterns'
|
||||
]);
|
||||
|
||||
var html = require('text!plugins/doc/index.html');
|
||||
|
||||
var resolveIndexPattern = {
|
||||
indexPattern: function (courier, savedSearches, $route) {
|
||||
return courier.indexPatterns.get($route.current.params.indexPattern);
|
||||
}
|
||||
};
|
||||
|
||||
require('routes')
|
||||
.when('/doc/:indexPattern/:index/:type/:id', {
|
||||
template: require('text!plugins/doc/index.html'),
|
||||
resolve: {
|
||||
indexPattern: function (courier, savedSearches, $route) {
|
||||
return courier.indexPatterns.get($route.current.params.indexPattern);
|
||||
}
|
||||
}
|
||||
template: html,
|
||||
resolve: resolveIndexPattern
|
||||
})
|
||||
.when('/doc/:indexPattern/:index/:type', {
|
||||
template: html,
|
||||
resolve: resolveIndexPattern
|
||||
});
|
||||
|
||||
app.controller('doc', function ($scope, $route, es, timefilter) {
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
|
||||
<!-- result -->
|
||||
<div class="col-md-12" ng-if="status === 'found'">
|
||||
<h2><b>Doc:</b> {{hit._index}}/{{hit._type}}/{{hit._id}}</h2>
|
||||
<h2><b>Doc:</b> {{hit._index}}/{{hit._type}}/{{hit._id | uriescape}}</h2>
|
||||
|
||||
<doc-viewer hit="hit" index-pattern="indexPattern"></doc-viewer>
|
||||
</div>
|
||||
|
|
|
@ -4,7 +4,7 @@ define(function (require) {
|
|||
$rootScope.globalState = globalState;
|
||||
|
||||
// and some local values
|
||||
$scope.appEmbedded = $location.search().embed;
|
||||
$scope.appEmbedded = $location.search().embed || false;
|
||||
$scope.httpActive = $http.pendingRequests;
|
||||
$scope.notifList = notify._notifs;
|
||||
|
||||
|
@ -13,4 +13,4 @@ define(function (require) {
|
|||
courier.start();
|
||||
});
|
||||
};
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<kbn-notifications list="notifList"></kbn-notifications>
|
||||
<div class="content" style="display: none;">
|
||||
<nav ng-hide="appEmbedded" bindonce class="navbar navbar-inverse navbar-static-top">
|
||||
<nav ng-class="{show: appEmbedded === false}" bindonce class="hide navbar navbar-inverse navbar-static-top">
|
||||
<div class="navbar-header">
|
||||
<button ng-click="showCollapsed = !showCollapsed" type="button" class="navbar-toggle">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
|
@ -51,4 +51,4 @@
|
|||
</config>
|
||||
|
||||
<div class="application" ng-view></div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
|
||||
</table>
|
||||
</p>
|
||||
<small>© 2014 All Rights Reserved - Elasticsearch</small>
|
||||
<small>© 2015 All Rights Reserved - Elasticsearch</small>
|
||||
</center>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -117,6 +117,7 @@
|
|||
ng-if="!index.fetchFieldsError"
|
||||
ng-options="field.name for field in index.dateFields"
|
||||
ng-model="index.timeField"
|
||||
auto-select-if-only-one="index.dateFields"
|
||||
class="form-control">
|
||||
</select>
|
||||
</div>
|
||||
|
|
|
@ -4,6 +4,7 @@ define(function (require) {
|
|||
var errors = require('errors');
|
||||
|
||||
require('directives/validate_index_name');
|
||||
require('directives/auto_select_if_only_one');
|
||||
|
||||
require('routes')
|
||||
.when('/settings/indices/', {
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
<label>Script <small>Please familiarize yourself with <a target="_window" href="http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-script-fields.html#search-request-script-fields">script fields <i class="fa-link fa"></i></a> and with
|
||||
<a target="_window" href="http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-aggregations-bucket-terms-aggregation.html#search-aggregations-bucket-terms-aggregation-script">scripts in aggregations <i class="fa-link fa"></i></a>
|
||||
before using scripted fields.</small></label>
|
||||
<textarea required class="form-control span12" ng-model="scriptedField.script"></textarea>
|
||||
<textarea required class="scripted-field-script form-control span12" ng-model="scriptedField.script"></textarea>
|
||||
</div>
|
||||
</form>
|
||||
<div ng-if="namingConflict" class="alert alert-danger">
|
||||
|
|
|
@ -149,6 +149,10 @@ kbn-settings-objects-view {
|
|||
.flex(4, 0, auto);
|
||||
}
|
||||
}
|
||||
|
||||
.scripted-field-script {
|
||||
font-family: @font-family-monospace;
|
||||
}
|
||||
}
|
||||
|
||||
kbn-settings-indices .fields {
|
||||
|
|
8
src/kibana/plugins/vis_types/vislib/editors/line.html
Normal file
8
src/kibana/plugins/vis_types/vislib/editors/line.html
Normal file
|
@ -0,0 +1,8 @@
|
|||
<!-- vis type specific options -->
|
||||
<vislib-basic-options></vislib-basic-options>
|
||||
<div class="vis-option-item form-group">
|
||||
<label>
|
||||
<input type="checkbox" value="{{showCircles}}" ng-model="vis.params.showCircles" name="showCircles" ng-checked="vis.params.showCircles">
|
||||
Show Circles
|
||||
</label>
|
||||
</div>
|
|
@ -14,9 +14,10 @@ define(function (require) {
|
|||
shareYAxis: true,
|
||||
addTooltip: true,
|
||||
addLegend: true,
|
||||
showCircles: true,
|
||||
defaultYExtents: false
|
||||
},
|
||||
editor: require('text!plugins/vis_types/vislib/editors/basic.html')
|
||||
editor: require('text!plugins/vis_types/vislib/editors/line.html')
|
||||
},
|
||||
schemas: new Schemas([
|
||||
{
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<form role="form" ng-submit="conf.doSave()">
|
||||
<div class="form-group">
|
||||
<label for="visTitle">Title</label>
|
||||
<input class="form-control" input-focus type="text" name="visTitle" ng-model="conf.savedVis.title" required>
|
||||
<input class="form-control" input-focus="select" type="text" name="visTitle" ng-model="conf.savedVis.title" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Save</button>
|
||||
</form>
|
|
@ -1,12 +1,12 @@
|
|||
<li class="sidebar-item" ng-show="vis.type.params.editor">
|
||||
<div class="sidebar-item-title" ng-click="showVisOptions = !showVisOptions">
|
||||
<div ng-hide="alwaysShowOptions" class="sidebar-item-title" ng-click="showVisOptions = !showVisOptions">
|
||||
view options
|
||||
<i
|
||||
class="fa fa-caret-down"
|
||||
ng-if="!alwaysShowOptions"
|
||||
ng-class="{'fa-caret-down': showVisOptions, 'fa-caret-right': !showVisOptions}">
|
||||
</i>
|
||||
</div>
|
||||
<div ng-show="alwaysShowOptions" class="sidebar-item-title">view options</div>
|
||||
|
||||
<div class="visualization-options" ng-show="alwaysShowOptions || showVisOptions"></div>
|
||||
</li>
|
||||
|
|
|
@ -57,6 +57,12 @@ define(function (require) {
|
|||
// https://github.com/angular/angular.js/blob/58f5da86645990ef984353418cd1ed83213b111e/src/ng/q.js#L335
|
||||
return obj && typeof obj.then === 'function';
|
||||
};
|
||||
Promise.halt = _.once(function () {
|
||||
var promise = new Promise();
|
||||
promise.then = _.constant(promise);
|
||||
promise.catch = _.constant(promise);
|
||||
return promise;
|
||||
});
|
||||
Promise.try = function (fn, args, ctx) {
|
||||
if (typeof fn !== 'function') {
|
||||
return Promise.reject('fn must be a function');
|
||||
|
|
|
@ -118,10 +118,19 @@
|
|||
}
|
||||
|
||||
.index-pattern {
|
||||
text-align: center;
|
||||
background-color: @sidebar-active-bg !important;
|
||||
background-color: @sidebar-active-bg;
|
||||
font-weight: bold;
|
||||
padding: 5px 10px;
|
||||
color: @sidebar-active-color;
|
||||
.display(flex);
|
||||
.justify-content(space-between);
|
||||
|
||||
> * {
|
||||
.flex;
|
||||
}
|
||||
}
|
||||
|
||||
.index-pattern-selection .sidebar-item-title {
|
||||
background-color: white;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -276,6 +276,16 @@ notifications {
|
|||
text-shadow: none;
|
||||
}
|
||||
|
||||
.kbn-timepicker .refresh-interval {
|
||||
padding: 0.2em 0.4em;
|
||||
border-radius: @border-radius-small;
|
||||
}
|
||||
|
||||
.kbn-timepicker .refresh-interval-active {
|
||||
background-color: @btn-info-bg;
|
||||
color: @btn-info-color;
|
||||
}
|
||||
|
||||
//== Table
|
||||
|
||||
kbn-table, .kbn-table {
|
||||
|
|
32
src/kibana/utils/cidr_mask.js
Normal file
32
src/kibana/utils/cidr_mask.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
define(function (require) {
|
||||
var Ipv4Address = require('utils/ipv4_address');
|
||||
var NUM_BITS = 32;
|
||||
|
||||
function throwError(mask) {
|
||||
throw Error('Invalid CIDR mask: ' + mask);
|
||||
}
|
||||
|
||||
function CidrMask(mask) {
|
||||
var splits = mask.split('\/');
|
||||
if (splits.length !== 2) throwError(mask);
|
||||
this.initialAddress = new Ipv4Address(splits[0]);
|
||||
this.prefixLength = Number(splits[1]);
|
||||
if (this.prefixLength < 1 || this.prefixLength > NUM_BITS) throwError(mask);
|
||||
}
|
||||
|
||||
CidrMask.prototype.getRange = function () {
|
||||
var variableBits = NUM_BITS - this.prefixLength;
|
||||
var fromAddress = this.initialAddress.valueOf() >> variableBits << variableBits >>> 0; // >>> 0 coerces to unsigned
|
||||
var numAddresses = Math.pow(2, variableBits);
|
||||
return {
|
||||
from: new Ipv4Address(fromAddress).toString(),
|
||||
to: new Ipv4Address(fromAddress + numAddresses - 1).toString()
|
||||
};
|
||||
};
|
||||
|
||||
CidrMask.prototype.toString = function () {
|
||||
return this.initialAddress.toString() + '/' + this.prefixLength;
|
||||
};
|
||||
|
||||
return CidrMask;
|
||||
});
|
50
src/kibana/utils/ipv4_address.js
Normal file
50
src/kibana/utils/ipv4_address.js
Normal file
|
@ -0,0 +1,50 @@
|
|||
define(function () {
|
||||
var NUM_BYTES = 4;
|
||||
var BYTE_SIZE = 256;
|
||||
|
||||
function throwError(ipAddress) {
|
||||
throw Error('Invalid IPv4 address: ' + ipAddress);
|
||||
}
|
||||
|
||||
function isIntegerInRange(integer, min, max) {
|
||||
return !isNaN(integer)
|
||||
&& integer >= min
|
||||
&& integer < max
|
||||
&& integer % 1 === 0;
|
||||
}
|
||||
|
||||
function Ipv4Address(ipAddress) {
|
||||
this.value = ipAddress;
|
||||
|
||||
if (typeof ipAddress === 'string') {
|
||||
this.value = 0;
|
||||
|
||||
var bytes = ipAddress.split('.');
|
||||
if (bytes.length !== NUM_BYTES) throwError(ipAddress);
|
||||
|
||||
for (var i = 0; i < bytes.length; i++) {
|
||||
var byte = Number(bytes[i]);
|
||||
if (!isIntegerInRange(byte, 0, BYTE_SIZE)) throwError(ipAddress);
|
||||
this.value += Math.pow(BYTE_SIZE, NUM_BYTES - 1 - i) * byte;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isIntegerInRange(this.value, 0, Math.pow(BYTE_SIZE, NUM_BYTES))) throwError(ipAddress);
|
||||
}
|
||||
|
||||
Ipv4Address.prototype.toString = function () {
|
||||
var value = this.value;
|
||||
var bytes = [];
|
||||
for (var i = 0; i < NUM_BYTES; i++) {
|
||||
bytes.unshift(value % 256);
|
||||
value = Math.floor(value / 256);
|
||||
}
|
||||
return bytes.join('.');
|
||||
};
|
||||
|
||||
Ipv4Address.prototype.valueOf = function () {
|
||||
return this.value;
|
||||
};
|
||||
|
||||
return Ipv4Address;
|
||||
});
|
|
@ -12,7 +12,7 @@ set CONFIG_PATH=%DIR%\config\kibana.yml
|
|||
|
||||
TITLE Kibana Server @@version
|
||||
|
||||
%NODE% %SERVER% %*
|
||||
"%NODE%" "%SERVER%" %*
|
||||
|
||||
:finally
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ program.option('-c, --config <path>', 'Path to the config file');
|
|||
program.option('-p, --port <port>', 'The port to bind to', parseInt);
|
||||
program.option('-q, --quiet', 'Turns off logging');
|
||||
program.option('-H, --host <host>', 'The host to bind to');
|
||||
program.option('-l, --log-file <path>', 'The file to log to');
|
||||
program.option('--plugins <path>', 'Path to scan for plugins');
|
||||
program.parse(process.argv);
|
||||
|
||||
|
@ -49,6 +50,10 @@ if (program.quiet) {
|
|||
config.quiet = program.quiet;
|
||||
}
|
||||
|
||||
if (program.logFile) {
|
||||
config.log_file = program.logFile;
|
||||
}
|
||||
|
||||
if (program.host) {
|
||||
config.host = program.host;
|
||||
}
|
||||
|
|
|
@ -44,7 +44,9 @@ var config = module.exports = {
|
|||
bundled_plugins_folder : path.resolve(public_folder, 'plugins'),
|
||||
kibana : kibana,
|
||||
package : require(packagePath),
|
||||
htpasswd : htpasswdPath
|
||||
htpasswd : htpasswdPath,
|
||||
buildNum : '@@buildNum',
|
||||
log_file : kibana.log_file || null
|
||||
};
|
||||
|
||||
config.plugins = listPlugins(config);
|
||||
|
|
|
@ -49,3 +49,6 @@ verify_ssl: true
|
|||
# Set the path to where you would like the process id file to be created.
|
||||
# pid_file: /var/run/kibana.pid
|
||||
|
||||
# If you would like to send the log output to a file you can set the path below.
|
||||
# This will also turn off the STDOUT log output.
|
||||
# log_file: ./kibana.log
|
|
@ -69,8 +69,8 @@ function onListening() {
|
|||
}
|
||||
|
||||
function start() {
|
||||
var port = parseInt(process.env.PORT, 10) || config.port || 3000;
|
||||
var host = process.env.HOST || config.host || '127.0.0.1';
|
||||
var port = config.port || 3000;
|
||||
var host = config.host || '127.0.0.1';
|
||||
var listen = Promise.promisify(server.listen.bind(server));
|
||||
app.set('port', port);
|
||||
return listen(port, host);
|
||||
|
@ -81,10 +81,16 @@ module.exports = {
|
|||
start: function (cb) {
|
||||
return initialization()
|
||||
.then(start)
|
||||
.catch(function (err) {
|
||||
throw err;
|
||||
})
|
||||
.nodeify(cb);
|
||||
.then(function () {
|
||||
cb && cb();
|
||||
}, function (err) {
|
||||
logger.error({ err: err });
|
||||
if (cb) {
|
||||
cb(err);
|
||||
} else {
|
||||
process.exit();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
var _ = require('lodash');
|
||||
var Writable = require('stream').Writable;
|
||||
var util = require('util');
|
||||
var through = require('through');
|
||||
|
||||
var levels = {
|
||||
10: 'trace',
|
||||
|
@ -11,14 +10,7 @@ var levels = {
|
|||
60: 'fatal'
|
||||
};
|
||||
|
||||
function JSONStream(options) {
|
||||
options = options || {};
|
||||
Writable.call(this, options);
|
||||
}
|
||||
|
||||
util.inherits(JSONStream, Writable);
|
||||
|
||||
JSONStream.prototype._write = function (entry, encoding, callback) {
|
||||
function write(entry) {
|
||||
entry = JSON.parse(entry.toString('utf8'));
|
||||
var env = process.env.NODE_ENV || 'development';
|
||||
|
||||
|
@ -36,8 +28,13 @@ JSONStream.prototype._write = function (entry, encoding, callback) {
|
|||
if (!output.message) output.message = output.error.message;
|
||||
}
|
||||
|
||||
process.stdout.write(JSON.stringify(output) + "\n");
|
||||
callback();
|
||||
};
|
||||
this.queue(JSON.stringify(output) + '\n');
|
||||
}
|
||||
|
||||
module.exports = JSONStream;
|
||||
function end() {
|
||||
this.queue(null);
|
||||
}
|
||||
|
||||
module.exports = function () {
|
||||
return through(write, end);
|
||||
};
|
|
@ -2,18 +2,33 @@ var _ = require('lodash');
|
|||
var morgan = require('morgan');
|
||||
var env = process.env.NODE_ENV || 'development';
|
||||
var bunyan = require('bunyan');
|
||||
var fs = require('fs');
|
||||
var StdOutStream = require('./StdOutStream');
|
||||
var JSONStream = require('./JSONStream');
|
||||
var createJSONStream = require('./createJSONStream');
|
||||
var config = require('../config');
|
||||
var stream = { stream: new JSONStream() };
|
||||
var streams = [];
|
||||
|
||||
// Set the default stream based on the enviroment. If we are on development then
|
||||
// then we are going to create a pretty stream. Everytyhing else will get the
|
||||
// JSON stream to stdout.
|
||||
var defaultStream;
|
||||
if (env === 'development') {
|
||||
stream.stream = new StdOutStream();
|
||||
defaultStream = new StdOutStream();
|
||||
} else {
|
||||
defaultStream = createJSONStream()
|
||||
.pipe(process.stdout);
|
||||
}
|
||||
|
||||
if (!config.quiet) {
|
||||
streams.push(stream);
|
||||
// If we are not being oppressed and we are not sending the output to a log file
|
||||
// push the default stream to the list of streams
|
||||
if (!config.quiet && !config.log_file) {
|
||||
streams.push({ stream: defaultStream });
|
||||
}
|
||||
|
||||
// Send the stream to a file using the json format.
|
||||
if (config.log_file) {
|
||||
var fileStream = fs.createWriteStream(config.log_file);
|
||||
streams.push({ stream: createJSONStream().pipe(fileStream) });
|
||||
}
|
||||
|
||||
var logger = module.exports = bunyan.createLogger({
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
var config = require('../config');
|
||||
var upgrade = require('./upgradeConfig');
|
||||
var client = require('./elasticsearch_client');
|
||||
|
||||
module.exports = function () {
|
||||
module.exports = function (client) {
|
||||
var options = {
|
||||
index: config.kibana.kibana_index,
|
||||
type: 'config',
|
||||
|
@ -22,7 +21,7 @@ module.exports = function () {
|
|||
};
|
||||
|
||||
return client.search(options)
|
||||
.then(upgrade)
|
||||
.then(upgrade(client))
|
||||
.catch(function (err) {
|
||||
if (!/SearchParseException.+mapping.+\[buildNum\]|^IndexMissingException/.test(err.message)) throw err;
|
||||
});
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
var Promise = require('bluebird');
|
||||
var waitForEs = require('./waitForEs');
|
||||
var migrateConfig = require('./migrateConfig');
|
||||
var client = require('./elasticsearch_client');
|
||||
|
||||
module.exports = function () {
|
||||
var tasks = [
|
||||
migrateConfig()
|
||||
];
|
||||
|
||||
return Promise.all(tasks);
|
||||
return waitForEs().then(function () {
|
||||
return migrateConfig(client);
|
||||
});
|
||||
};
|
||||
|
|
|
@ -3,22 +3,31 @@ var isUpgradeable = require('./isUpgradeable');
|
|||
var config = require('../config');
|
||||
var _ = require('lodash');
|
||||
var client = require('./elasticsearch_client');
|
||||
module.exports = function (response) {
|
||||
var newConfig = {};
|
||||
// Check to see if there are any doc. If not then we can assume
|
||||
// nothing needs to be done
|
||||
if (response.hits.hits.length === 0) return Promise.resolve();
|
||||
module.exports = function (client) {
|
||||
return function (response) {
|
||||
var newConfig = {};
|
||||
// Check to see if there are any doc. If not then we can assume
|
||||
// nothing needs to be done
|
||||
if (response.hits.hits.length === 0) return Promise.resolve();
|
||||
|
||||
// Look for upgradeable configs. If none of them are upgradeable
|
||||
// then resolve with null.
|
||||
var body = _.find(response.hits.hits, isUpgradeable);
|
||||
if (!body) return Promise.resolve();
|
||||
// if we already have a the current version in the index then we need to stop
|
||||
if (_.find(response.hits.hits, { _id: config.package.version })) return Promise.resolve();
|
||||
|
||||
return client.create({
|
||||
index: config.kibana.kibana_index,
|
||||
type: 'config',
|
||||
body: body._source,
|
||||
id: config.package.version
|
||||
});
|
||||
// Look for upgradeable configs. If none of them are upgradeable
|
||||
// then resolve with null.
|
||||
var body = _.find(response.hits.hits, isUpgradeable);
|
||||
if (!body) return Promise.resolve();
|
||||
|
||||
|
||||
// if the build number is still the template string (which it wil be in development)
|
||||
// then we need to set it to the max interger. Otherwise we will set it to the build num
|
||||
body._source.buildNum = (/^@@/.test(config.buildNum)) ? Math.pow(2, 53) - 1 : parseInt(config.buildNum, 10);
|
||||
|
||||
return client.create({
|
||||
index: config.kibana.kibana_index,
|
||||
type: 'config',
|
||||
body: body._source,
|
||||
id: config.package.version
|
||||
});
|
||||
};
|
||||
};
|
||||
|
|
28
src/server/lib/waitForEs.js
Normal file
28
src/server/lib/waitForEs.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
var Promise = require('bluebird');
|
||||
var NoConnections = require('elasticsearch').errors.NoConnections;
|
||||
|
||||
var client = require('./elasticsearch_client');
|
||||
var logger = require('./logger');
|
||||
var config = require('../config');
|
||||
|
||||
function waitForPong() {
|
||||
return client.ping({ requestTimeout: 1500 }).catch(function (err) {
|
||||
if (!(err instanceof NoConnections)) throw err;
|
||||
|
||||
logger.info('Unable to connect to elasticsearch at %s. Retrying in 2.5 seconds.', config.elasticsearch);
|
||||
return Promise.delay(2500).then(waitForPong);
|
||||
});
|
||||
}
|
||||
|
||||
function waitForShards() {
|
||||
return client.cluster.health().then(function (resp) {
|
||||
if (resp.initializing_shards <= 0) return;
|
||||
|
||||
logger.info('Elasticsearch is still initializaing... Trying again in 2500 seconds.');
|
||||
return Promise.delay(2500).then(waitForShards);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = function () {
|
||||
return waitForPong().then(waitForShards);
|
||||
};
|
|
@ -33,7 +33,7 @@ module.exports = function (grunt) {
|
|||
{
|
||||
expand: true,
|
||||
cwd: '<%= server %>/config/',
|
||||
src: '**',
|
||||
src: '*.yml',
|
||||
dest: '<%= build %>/kibana/config'
|
||||
},
|
||||
{
|
||||
|
|
|
@ -22,6 +22,10 @@ module.exports = function (grunt) {
|
|||
{
|
||||
src: [join(src, 'server', 'bin', 'kibana.bat')],
|
||||
dest: join(build, 'dist', 'kibana', 'bin', 'kibana.bat')
|
||||
},
|
||||
{
|
||||
src: [join(src, 'server', 'config', 'index.js')],
|
||||
dest: join(build, 'dist', 'kibana', 'src', 'config', 'index.js')
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -4,8 +4,10 @@ module.exports = function (grunt) {
|
|||
|
||||
var README_PATH = root('README.md');
|
||||
var PKG_JSON_PATH = root('package.json');
|
||||
var START = '<!--version-->';
|
||||
var END = '<!--/version-->';
|
||||
|
||||
function replace(source, from, to) {
|
||||
return String(source).split(from).join(to);
|
||||
}
|
||||
|
||||
grunt.registerTask('version', function (updateExpr) {
|
||||
var oldVersion = grunt.config.get('pkg.version');
|
||||
|
@ -17,32 +19,13 @@ module.exports = function (grunt) {
|
|||
|
||||
// write back to package.json
|
||||
var pkgJson = grunt.file.read(PKG_JSON_PATH);
|
||||
pkgJson = pkgJson.replace(JSON.stringify(oldVersion), JSON.stringify(version));
|
||||
pkgJson = replace(pkgJson, JSON.stringify(oldVersion), JSON.stringify(version));
|
||||
grunt.file.write(PKG_JSON_PATH, pkgJson);
|
||||
grunt.log.ok('updated package.json', version);
|
||||
|
||||
// write the readme
|
||||
var input = grunt.file.read(README_PATH);
|
||||
var readme = '';
|
||||
|
||||
var startI, endI, before;
|
||||
while (input.length) {
|
||||
startI = input.indexOf(START);
|
||||
endI = input.indexOf(END);
|
||||
if (endI < startI) throw new Error('version tag mismatch in ' + input);
|
||||
|
||||
if (startI < 0) {
|
||||
readme += input;
|
||||
break;
|
||||
}
|
||||
|
||||
before = input.substr(0, startI);
|
||||
input = input.substr(endI ? endI + END.length : startI);
|
||||
|
||||
readme += before + START + version + END;
|
||||
}
|
||||
|
||||
grunt.file.write(README_PATH, readme);
|
||||
var readme = grunt.file.read(README_PATH);
|
||||
grunt.file.write(README_PATH, replace(readme, oldVersion, version));
|
||||
grunt.log.ok('updated readme', version);
|
||||
});
|
||||
};
|
||||
|
|
25
test/unit/fixtures/config_upgrade_from_4.0.0.json
Normal file
25
test/unit/fixtures/config_upgrade_from_4.0.0.json
Normal file
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"took": 1,
|
||||
"timed_out": false,
|
||||
"_shards": {
|
||||
"total": 1,
|
||||
"successful": 1,
|
||||
"failed": 0
|
||||
},
|
||||
"hits": {
|
||||
"total": 1,
|
||||
"max_score": 1,
|
||||
"hits": [
|
||||
{
|
||||
"_index": ".kibana",
|
||||
"_type": "config",
|
||||
"_id": "4.0.0",
|
||||
"_score": 1,
|
||||
"_source": {
|
||||
"buildNum": 5888,
|
||||
"defaultIndex": "logstash-*"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"took": 1,
|
||||
"timed_out": false,
|
||||
"_shards": {
|
||||
"total": 1,
|
||||
"successful": 1,
|
||||
"failed": 0
|
||||
},
|
||||
"hits": {
|
||||
"total": 2,
|
||||
"max_score": 1,
|
||||
"hits": [
|
||||
{
|
||||
"_index": ".kibana",
|
||||
"_type": "config",
|
||||
"_id": "4.0.1-snapshot",
|
||||
"_score": 1,
|
||||
"_source": {
|
||||
"buildNum": 5921,
|
||||
"defaultIndex": "logstash-*"
|
||||
}
|
||||
},
|
||||
{
|
||||
"_index": ".kibana",
|
||||
"_type": "config",
|
||||
"_id": "4.0.0",
|
||||
"_score": 1,
|
||||
"_source": {
|
||||
"buildNum": 5888,
|
||||
"defaultIndex": "logstash-*"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
35
test/unit/fixtures/config_upgrade_from_4.0.0_to_4.0.1.json
Normal file
35
test/unit/fixtures/config_upgrade_from_4.0.0_to_4.0.1.json
Normal file
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"took": 1,
|
||||
"timed_out": false,
|
||||
"_shards": {
|
||||
"total": 1,
|
||||
"successful": 1,
|
||||
"failed": 0
|
||||
},
|
||||
"hits": {
|
||||
"total": 2,
|
||||
"max_score": 1,
|
||||
"hits": [
|
||||
{
|
||||
"_index": ".kibana",
|
||||
"_type": "config",
|
||||
"_id": "4.0.1",
|
||||
"_score": 1,
|
||||
"_source": {
|
||||
"buildNum": 5921,
|
||||
"defaultIndex": "logstash-*"
|
||||
}
|
||||
},
|
||||
{
|
||||
"_index": ".kibana",
|
||||
"_type": "config",
|
||||
"_id": "4.0.0",
|
||||
"_score": 1,
|
||||
"_source": {
|
||||
"buildNum": 5888,
|
||||
"defaultIndex": "logstash-*"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
75
test/unit/server/lib/upgradeConfig.js
Normal file
75
test/unit/server/lib/upgradeConfig.js
Normal file
|
@ -0,0 +1,75 @@
|
|||
var root = require('requirefrom')('');
|
||||
var upgradeConfig = root('src/server/lib/upgradeConfig');
|
||||
var expect = require('expect.js');
|
||||
var sinon = require('sinon');
|
||||
var sinonAsPromised = require('sinon-as-promised')(require('bluebird'));
|
||||
var util = require('util');
|
||||
var package = root('package.json');
|
||||
var config = root('src/server/config');
|
||||
|
||||
var upgradeFrom4_0_0_to_4_0_1 = root('test/unit/fixtures/config_upgrade_from_4.0.0_to_4.0.1.json');
|
||||
var upgradeFrom4_0_0_to_4_0_1_snapshot = root('test/unit/fixtures/config_upgrade_from_4.0.0_to_4.0.1-snapshot.json');
|
||||
var upgradeFrom4_0_0 = root('test/unit/fixtures/config_upgrade_from_4.0.0.json');
|
||||
|
||||
describe('lib/upgradeConfig', function () {
|
||||
|
||||
var client, oldPackageVersion, oldBuildNum;
|
||||
beforeEach(function () {
|
||||
oldPackageVersion = config.package.version;
|
||||
oldBuildNum = config.buildNum;
|
||||
client = { create: sinon.stub() };
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
config.package.version = oldPackageVersion;
|
||||
config.buildNum = oldBuildNum;
|
||||
});
|
||||
|
||||
it('should not upgrade if the current version of the config exits', function () {
|
||||
config.package.version = '4.0.1';
|
||||
var fn = upgradeConfig(client);
|
||||
client.create.rejects(new Error('DocumentAlreadyExistsException'));
|
||||
return fn(upgradeFrom4_0_0_to_4_0_1).finally(function () {
|
||||
sinon.assert.notCalled(client.create);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not upgrade if there are no hits', function () {
|
||||
config.package.version = '4.0.1';
|
||||
var fn = upgradeConfig(client);
|
||||
return fn({ hits: { hits: [] } }).finally(function () {
|
||||
sinon.assert.notCalled(client.create);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not upgrade even if a snapshot exists', function () {
|
||||
config.package.version = '4.0.1-snapshot';
|
||||
client.create.rejects(new Error('DocumentAlreadyExistsException'));
|
||||
var fn = upgradeConfig(client);
|
||||
return fn(upgradeFrom4_0_0_to_4_0_1_snapshot).finally(function () {
|
||||
sinon.assert.notCalled(client.create);
|
||||
});
|
||||
});
|
||||
|
||||
it('should upgrade from 4.0.0 to 4.0.1', function () {
|
||||
config.package.version = '4.0.1';
|
||||
config.buildNum = 5921;
|
||||
var fn = upgradeConfig(client);
|
||||
client.create.resolves({ _index: '.kibana', _type: 'config', _id: '4.0.1', _version: 1, created: true });
|
||||
return fn(upgradeFrom4_0_0).finally(function () {
|
||||
sinon.assert.calledOnce(client.create);
|
||||
var body = client.create.args[0][0];
|
||||
expect(body).to.eql({
|
||||
index: '.kibana',
|
||||
type: 'config',
|
||||
id: '4.0.1',
|
||||
body: {
|
||||
'buildNum': 5921,
|
||||
'defaultIndex': 'logstash-*'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
|
@ -0,0 +1,78 @@
|
|||
define(function (require) {
|
||||
describe('AggConfig Filters', function () {
|
||||
describe('IP range', function () {
|
||||
var AggConfig;
|
||||
var indexPattern;
|
||||
var Vis;
|
||||
var createFilter;
|
||||
|
||||
beforeEach(module('kibana'));
|
||||
beforeEach(inject(function (Private) {
|
||||
Vis = Private(require('components/vis/vis'));
|
||||
AggConfig = Private(require('components/vis/_agg_config'));
|
||||
indexPattern = Private(require('fixtures/stubbed_logstash_index_pattern'));
|
||||
createFilter = Private(require('components/agg_types/buckets/create_filter/ip_range'));
|
||||
}));
|
||||
|
||||
it('should return a range filter for ip_range agg', function () {
|
||||
var vis = new Vis(indexPattern, {
|
||||
type: 'histogram',
|
||||
aggs: [
|
||||
{
|
||||
type: 'ip_range',
|
||||
schema: 'segment',
|
||||
params: {
|
||||
field: 'ip',
|
||||
ipRangeType: 'fromTo',
|
||||
ranges: {
|
||||
fromTo: [
|
||||
{ from: '0.0.0.0', to: '1.1.1.1' }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
var aggConfig = vis.aggs.byTypeName.ip_range[0];
|
||||
var filter = createFilter(aggConfig, '0.0.0.0-1.1.1.1');
|
||||
expect(filter).to.have.property('range');
|
||||
expect(filter).to.have.property('meta');
|
||||
expect(filter.meta).to.have.property('index', indexPattern.id);
|
||||
expect(filter.range).to.have.property('ip');
|
||||
expect(filter.range.ip).to.have.property('gte', '0.0.0.0');
|
||||
expect(filter.range.ip).to.have.property('lte', '1.1.1.1');
|
||||
});
|
||||
|
||||
it('should return a range filter for ip_range agg using a CIDR mask', function () {
|
||||
var vis = new Vis(indexPattern, {
|
||||
type: 'histogram',
|
||||
aggs: [
|
||||
{
|
||||
type: 'ip_range',
|
||||
schema: 'segment',
|
||||
params: {
|
||||
field: 'ip',
|
||||
ipRangeType: 'mask',
|
||||
ranges: {
|
||||
mask: [
|
||||
{ mask: '67.129.65.201/27' }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
var aggConfig = vis.aggs.byTypeName.ip_range[0];
|
||||
var filter = createFilter(aggConfig, '67.129.65.201/27');
|
||||
expect(filter).to.have.property('range');
|
||||
expect(filter).to.have.property('meta');
|
||||
expect(filter.meta).to.have.property('index', indexPattern.id);
|
||||
expect(filter.range).to.have.property('ip');
|
||||
expect(filter.range.ip).to.have.property('gte', '67.129.65.192');
|
||||
expect(filter.range.ip).to.have.property('lte', '67.129.65.223');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
39
test/unit/specs/directives/auto_select_if_only_one.js
Normal file
39
test/unit/specs/directives/auto_select_if_only_one.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
define(function (require) {
|
||||
var angular = require('angular');
|
||||
require('directives/auto_select_if_only_one');
|
||||
|
||||
describe('Auto-select if only one directive', function () {
|
||||
var $compile, $rootScope;
|
||||
var html = '<select ng-model="value" ng-options="option.name for option in options" auto-select-if-only-one="options"></select>';
|
||||
var zeroOptions = [];
|
||||
var oneOption = [{label: 'foo'}];
|
||||
var multiOptions = [{label: 'foo'}, {label: 'bar'}];
|
||||
|
||||
beforeEach(module('kibana'));
|
||||
|
||||
beforeEach(inject(function (_$compile_, _$rootScope_) {
|
||||
$compile = _$compile_;
|
||||
$rootScope = _$rootScope_;
|
||||
$compile(html)($rootScope);
|
||||
$rootScope.value = null;
|
||||
}));
|
||||
|
||||
it('should not auto-select if there are no options', function () {
|
||||
$rootScope.options = zeroOptions;
|
||||
$rootScope.$digest();
|
||||
expect($rootScope.value).to.not.be.ok();
|
||||
});
|
||||
|
||||
it('should not auto-select if there are multiple options', function () {
|
||||
$rootScope.options = multiOptions;
|
||||
$rootScope.$digest();
|
||||
expect($rootScope.value).to.not.be.ok();
|
||||
});
|
||||
|
||||
it('should auto-select if there is only one option', function () {
|
||||
$rootScope.options = oneOption;
|
||||
$rootScope.$digest();
|
||||
expect($rootScope.value).to.be(oneOption[0]);
|
||||
});
|
||||
});
|
||||
});
|
51
test/unit/specs/directives/input_focus.js
Normal file
51
test/unit/specs/directives/input_focus.js
Normal file
|
@ -0,0 +1,51 @@
|
|||
define(function (require) {
|
||||
var angular = require('angular');
|
||||
var $ = require('jquery');
|
||||
require('directives/input_focus');
|
||||
|
||||
describe('Input focus directive', function () {
|
||||
var $compile, $rootScope, $timeout, element;
|
||||
var $el, selectedEl, selectedText;
|
||||
var inputValue = 'Input Text Value';
|
||||
|
||||
beforeEach(module('kibana'));
|
||||
|
||||
beforeEach(inject(function (_$compile_, _$rootScope_, _$timeout_) {
|
||||
$compile = _$compile_;
|
||||
$rootScope = _$rootScope_;
|
||||
$timeout = _$timeout_;
|
||||
|
||||
$el = $('<div>');
|
||||
$el.appendTo('body');
|
||||
}));
|
||||
|
||||
afterEach(function () {
|
||||
$el.remove();
|
||||
$el = null;
|
||||
});
|
||||
|
||||
function renderEl(html) {
|
||||
$rootScope.value = inputValue;
|
||||
element = $compile(html)($rootScope);
|
||||
element.appendTo($el);
|
||||
$rootScope.$digest();
|
||||
$timeout.flush();
|
||||
selectedEl = document.activeElement;
|
||||
selectedText = window.getSelection().toString();
|
||||
}
|
||||
|
||||
|
||||
it('should focus the input', function () {
|
||||
renderEl('<input type="text" ng-model="value" input-focus />');
|
||||
expect(selectedEl).to.equal(element[0]);
|
||||
expect(selectedText.length).to.equal(0);
|
||||
});
|
||||
|
||||
it('should select the text in the input', function () {
|
||||
renderEl('<input type="text" ng-model="value" input-focus="select" />');
|
||||
expect(selectedEl).to.equal(element[0]);
|
||||
expect(selectedText.length).to.equal(inputValue.length);
|
||||
expect(selectedText).to.equal(inputValue);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -111,6 +111,14 @@ define(function (require) {
|
|||
done();
|
||||
});
|
||||
|
||||
it('should highlight the current active interval', function (done) {
|
||||
$scope.setRefreshInterval({ value: 300000 });
|
||||
$elem.scope().$digest();
|
||||
expect($elem.find('.refresh-interval-active').length).to.be(1);
|
||||
expect($elem.find('.refresh-interval-active').text().trim()).to.be('5 minutes');
|
||||
done();
|
||||
});
|
||||
|
||||
it('should default the interval on the courier with incorrect values', function (done) {
|
||||
// Change refresh interval and digest
|
||||
$scope.setRefreshInterval('undefined');
|
||||
|
|
72
test/unit/specs/directives/validate_cidr_mask.js
Normal file
72
test/unit/specs/directives/validate_cidr_mask.js
Normal file
|
@ -0,0 +1,72 @@
|
|||
define(function (require) {
|
||||
var angular = require('angular');
|
||||
require('directives/validate_cidr_mask');
|
||||
|
||||
describe('Validate CIDR mask directive', function () {
|
||||
var $compile, $rootScope;
|
||||
var html = '<input type="text" ng-model="value" validate-cidr-mask />';
|
||||
|
||||
beforeEach(module('kibana'));
|
||||
|
||||
beforeEach(inject(function (_$compile_, _$rootScope_) {
|
||||
$compile = _$compile_;
|
||||
$rootScope = _$rootScope_;
|
||||
}));
|
||||
|
||||
it('should allow valid CIDR masks', function () {
|
||||
var element = $compile(html)($rootScope);
|
||||
|
||||
$rootScope.value = '0.0.0.0/1';
|
||||
$rootScope.$digest();
|
||||
expect(element.hasClass('ng-valid')).to.be.ok();
|
||||
|
||||
$rootScope.value = '128.0.0.1/31';
|
||||
$rootScope.$digest();
|
||||
expect(element.hasClass('ng-valid')).to.be.ok();
|
||||
|
||||
$rootScope.value = '1.2.3.4/2';
|
||||
$rootScope.$digest();
|
||||
expect(element.hasClass('ng-valid')).to.be.ok();
|
||||
|
||||
$rootScope.value = '67.129.65.201/27';
|
||||
$rootScope.$digest();
|
||||
expect(element.hasClass('ng-valid')).to.be.ok();
|
||||
});
|
||||
|
||||
it('should disallow invalid CIDR masks', function () {
|
||||
var element = $compile(html)($rootScope);
|
||||
|
||||
$rootScope.value = '';
|
||||
$rootScope.$digest();
|
||||
expect(element.hasClass('ng-invalid')).to.be.ok();
|
||||
|
||||
$rootScope.value = 'hello, world';
|
||||
$rootScope.$digest();
|
||||
expect(element.hasClass('ng-invalid')).to.be.ok();
|
||||
|
||||
$rootScope.value = '0.0.0.0';
|
||||
$rootScope.$digest();
|
||||
expect(element.hasClass('ng-invalid')).to.be.ok();
|
||||
|
||||
$rootScope.value = '0.0.0.0/0';
|
||||
$rootScope.$digest();
|
||||
expect(element.hasClass('ng-invalid')).to.be.ok();
|
||||
|
||||
$rootScope.value = '0.0.0.0/33';
|
||||
$rootScope.$digest();
|
||||
expect(element.hasClass('ng-invalid')).to.be.ok();
|
||||
|
||||
$rootScope.value = '256.0.0.0/32';
|
||||
$rootScope.$digest();
|
||||
expect(element.hasClass('ng-invalid')).to.be.ok();
|
||||
|
||||
$rootScope.value = '0.0.0.0/32/32';
|
||||
$rootScope.$digest();
|
||||
expect(element.hasClass('ng-invalid')).to.be.ok();
|
||||
|
||||
$rootScope.value = '1.2.3/1';
|
||||
$rootScope.$digest();
|
||||
expect(element.hasClass('ng-invalid')).to.be.ok();
|
||||
});
|
||||
});
|
||||
});
|
64
test/unit/specs/directives/validate_ip.js
Normal file
64
test/unit/specs/directives/validate_ip.js
Normal file
|
@ -0,0 +1,64 @@
|
|||
define(function (require) {
|
||||
var angular = require('angular');
|
||||
require('directives/validate_ip');
|
||||
|
||||
describe('Validate IP directive', function () {
|
||||
var $compile, $rootScope;
|
||||
var html = '<input type="text" ng-model="value" validate-ip />';
|
||||
|
||||
beforeEach(module('kibana'));
|
||||
|
||||
beforeEach(inject(function (_$compile_, _$rootScope_) {
|
||||
$compile = _$compile_;
|
||||
$rootScope = _$rootScope_;
|
||||
}));
|
||||
|
||||
it('should allow valid IP addresses', function () {
|
||||
var element = $compile(html)($rootScope);
|
||||
|
||||
$rootScope.value = '0.0.0.0';
|
||||
$rootScope.$digest();
|
||||
expect(element.hasClass('ng-valid')).to.be.ok();
|
||||
|
||||
$rootScope.value = '0.0.0.1';
|
||||
$rootScope.$digest();
|
||||
expect(element.hasClass('ng-valid')).to.be.ok();
|
||||
|
||||
$rootScope.value = '126.45.211.34';
|
||||
$rootScope.$digest();
|
||||
expect(element.hasClass('ng-valid')).to.be.ok();
|
||||
|
||||
$rootScope.value = '255.255.255.255';
|
||||
$rootScope.$digest();
|
||||
expect(element.hasClass('ng-valid')).to.be.ok();
|
||||
});
|
||||
|
||||
it('should disallow invalid IP addresses', function () {
|
||||
var element = $compile(html)($rootScope);
|
||||
|
||||
$rootScope.value = '';
|
||||
$rootScope.$digest();
|
||||
expect(element.hasClass('ng-invalid')).to.be.ok();
|
||||
|
||||
$rootScope.value = 'hello, world';
|
||||
$rootScope.$digest();
|
||||
expect(element.hasClass('ng-invalid')).to.be.ok();
|
||||
|
||||
$rootScope.value = '0.0.0';
|
||||
$rootScope.$digest();
|
||||
expect(element.hasClass('ng-invalid')).to.be.ok();
|
||||
|
||||
$rootScope.value = '256.0.0.0';
|
||||
$rootScope.$digest();
|
||||
expect(element.hasClass('ng-invalid')).to.be.ok();
|
||||
|
||||
$rootScope.value = '-1.0.0.0';
|
||||
$rootScope.$digest();
|
||||
expect(element.hasClass('ng-invalid')).to.be.ok();
|
||||
|
||||
$rootScope.value = Number.MAX_SAFE_INTEGER;
|
||||
$rootScope.$digest();
|
||||
expect(element.hasClass('ng-invalid')).to.be.ok();
|
||||
});
|
||||
});
|
||||
});
|
78
test/unit/specs/utils/cidr_mask.js
Normal file
78
test/unit/specs/utils/cidr_mask.js
Normal file
|
@ -0,0 +1,78 @@
|
|||
define(function (require) {
|
||||
var CidrMask = require('utils/cidr_mask');
|
||||
|
||||
describe('CidrMask', function () {
|
||||
it('should throw errors with invalid CIDR masks', function () {
|
||||
expect(function () {
|
||||
new CidrMask();
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
new CidrMask('');
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
new CidrMask('hello, world');
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
new CidrMask('0.0.0.0');
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
new CidrMask('0.0.0.0/0');
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
new CidrMask('0.0.0.0/33');
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
new CidrMask('256.0.0.0/32');
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
new CidrMask('0.0.0.0/32/32');
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
new CidrMask('1.2.3/1');
|
||||
}).to.throwError();
|
||||
});
|
||||
|
||||
it('should correctly grab IP address and prefix length', function () {
|
||||
var mask = new CidrMask('0.0.0.0/1');
|
||||
expect(mask.initialAddress.toString()).to.be('0.0.0.0');
|
||||
expect(mask.prefixLength).to.be(1);
|
||||
|
||||
mask = new CidrMask('128.0.0.1/31');
|
||||
expect(mask.initialAddress.toString()).to.be('128.0.0.1');
|
||||
expect(mask.prefixLength).to.be(31);
|
||||
});
|
||||
|
||||
it('should calculate a range of IP addresses', function () {
|
||||
var mask = new CidrMask('0.0.0.0/1');
|
||||
var range = mask.getRange();
|
||||
expect(range.from.toString()).to.be('0.0.0.0');
|
||||
expect(range.to.toString()).to.be('127.255.255.255');
|
||||
|
||||
mask = new CidrMask('1.2.3.4/2');
|
||||
range = mask.getRange();
|
||||
expect(range.from.toString()).to.be('0.0.0.0');
|
||||
expect(range.to.toString()).to.be('63.255.255.255');
|
||||
|
||||
mask = new CidrMask('67.129.65.201/27');
|
||||
range = mask.getRange();
|
||||
expect(range.from.toString()).to.be('67.129.65.192');
|
||||
expect(range.to.toString()).to.be('67.129.65.223');
|
||||
});
|
||||
|
||||
it('toString()', function () {
|
||||
var mask = new CidrMask('.../1');
|
||||
expect(mask.toString()).to.be('0.0.0.0/1');
|
||||
|
||||
mask = new CidrMask('128.0.0.1/31');
|
||||
expect(mask.toString()).to.be('128.0.0.1/31');
|
||||
});
|
||||
});
|
||||
});
|
58
test/unit/specs/utils/ipv4_address.js
Normal file
58
test/unit/specs/utils/ipv4_address.js
Normal file
|
@ -0,0 +1,58 @@
|
|||
define(function (require) {
|
||||
var Ipv4Address = require('utils/ipv4_address');
|
||||
|
||||
describe('Ipv4Address', function () {
|
||||
it('should throw errors with invalid IP addresses', function () {
|
||||
expect(function () {
|
||||
new Ipv4Address();
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
new Ipv4Address('');
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
new Ipv4Address('hello, world');
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
new Ipv4Address('0.0.0');
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
new Ipv4Address('256.0.0.0');
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
new Ipv4Address('-1.0.0.0');
|
||||
}).to.throwError();
|
||||
|
||||
expect(function () {
|
||||
new Ipv4Address(Number.MAX_SAFE_INTEGER);
|
||||
}).to.throwError();
|
||||
});
|
||||
|
||||
it('should allow creation with an integer or string', function () {
|
||||
expect(new Ipv4Address(2116932386).toString()).to.be(new Ipv4Address('126.45.211.34').toString());
|
||||
});
|
||||
|
||||
it('should correctly calculate the decimal representation of an IP address', function () {
|
||||
var ipAddress = new Ipv4Address('0.0.0.0');
|
||||
expect(ipAddress.valueOf()).to.be(0);
|
||||
|
||||
ipAddress = new Ipv4Address('0.0.0.1');
|
||||
expect(ipAddress.valueOf()).to.be(1);
|
||||
|
||||
ipAddress = new Ipv4Address('126.45.211.34');
|
||||
expect(ipAddress.valueOf()).to.be(2116932386);
|
||||
});
|
||||
|
||||
it('toString()', function () {
|
||||
var ipAddress = new Ipv4Address('0.000.00000.1');
|
||||
expect(ipAddress.toString()).to.be('0.0.0.1');
|
||||
|
||||
ipAddress = new Ipv4Address('123.123.123.123');
|
||||
expect(ipAddress.toString()).to.be('123.123.123.123');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -48,6 +48,33 @@ define(function (require) {
|
|||
vis = null;
|
||||
});
|
||||
|
||||
describe('legend item color matches slice color', function () {
|
||||
var items;
|
||||
var paths;
|
||||
var getColor;
|
||||
|
||||
if (chartTypes[i] === 'pie') {
|
||||
it('should match the slice color', function () {
|
||||
paths = $(vis.el).find('path').toArray();
|
||||
items = vis.handler.legend.labels;
|
||||
getColor = vis.handler.legend.color;
|
||||
|
||||
items.forEach(function (label) {
|
||||
var slices = paths.filter(function (path) {
|
||||
if (path.__data__.name === undefined) return false;
|
||||
return path.__data__.name.toString() === label;
|
||||
}).map(function (path) {
|
||||
return $(path).attr('class').split(/\s+/)[1].replace('c', '#');
|
||||
});
|
||||
|
||||
slices.forEach(function (hex) {
|
||||
expect(hex).to.be(getColor(label));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('header method', function () {
|
||||
it('should append the legend header', function () {
|
||||
expect($(vis.el).find('.header').length).to.be(1);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
var updateVersion = require('../../../../tasks/utils/updateVersion');
|
||||
var expect = require('expect.js');
|
||||
|
||||
describe('tasks/utils/updateVersion', function () {
|
||||
|
||||
|
@ -29,5 +30,4 @@ describe('tasks/utils/updateVersion', function () {
|
|||
it('changes a tag', function () {
|
||||
expect(updateVersion('4.1.0-snapshot', 'tag=rc1')).to.be('4.1.0-rc1');
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue