Remove other unused Add Data code

This commit is contained in:
Matthew Bargar 2016-09-29 15:14:19 -04:00
parent 0d087989f0
commit b7fd17b492
24 changed files with 0 additions and 1333 deletions

View file

@ -1,84 +0,0 @@
<h2><em>Follow these instructions to install Filebeat.</em>
Now that you've got a fresh pipeline and index pattern, let's throw some data at it!
</h2>
<div class="install-filebeat">
<ol>
<li>
<span>
<strong>Install Filebeat</strong> on all servers on which you want to tail logs &nbsp;
<a target="_blank" ng-href="{{installStep.docLinks.installation}}">
<i aria-hidden="true" class="fa fa-info-circle"></i> instructions
</a>
</span>
</li>
<li>
<span>
<strong>Point Filebeat</strong> at the log files you want to tail &nbsp;
<a target="_blank" ng-href="{{installStep.docLinks.configuration}}">
<i aria-hidden="true" class="fa fa-info-circle"></i> instructions
</a>
</span>
</li>
<li ng-if="installStep.results.pipeline.processors.length">
<span>
<strong>Configure Filebeat</strong> to send data through your new Elasticsearch pipeline &nbsp;
<a target="_blank" ng-href="{{installStep.docLinks.elasticsearchOutput}}">
<i aria-hidden="true" class="fa fa-info-circle"></i> instructions
</a><br/>
At minimum you'll need to configure Filebeat's Elasticsearch output with a hostname, an index name, and a
<a target="_blank"
ng-href="{{installStep.docLinks.elasticsearchOutputAnchorParameters}}">
<i aria-hidden="true" class="fa fa-info-circle"></i> paramaters
</a> block. Your config should end up looking something like this:<br/>
<pre>
output:
elasticsearch:
hosts: ["your-elasticsearch-host"]
index: "your-base-index-name"
parameters:
pipeline: "{{installStep.pipelineId}}"</pre>
<em>NOTE</em>: The Filebeat config takes a base index name and automatically rotates the target index by appending "-{date}"
to the end, so if your pattern was "filebeat-*" you would make the index name "filebeat" in filebeat.yml.<br />
</span>
</li>
<li ng-if="!installStep.results.pipeline.processors.length">
<span>
<strong>Configure Filebeat</strong> to send data to Elasticsearch &nbsp;
<a target="_blank" ng-href="{{installStep.docLinks.elasticsearchOutput}}">
<i aria-hidden="true" class="fa fa-info-circle"></i> instructions
</a><br/>
At minimum you'll need to configure Filebeat's Elasticsearch output with a hostname and an index name.
Your config should end up looking something like this:<br />
<pre>
output:
elasticsearch:
hosts: ["your-elasticsearch-host"]
index: "your-base-index-name"</pre>
<em>NOTE</em>: The Filebeat config takes a base index name and automatically rotates the target index by appending "-{date}"
to the end, so if your pattern was "filebeat-*" you would make the index name "filebeat" in filebeat.yml.<br />
</span>
</li>
<li>
<span>
<strong>Run Filebeat</strong> on each server &nbsp;
<a target="_blank" ng-href="{{installStep.docLinks.startup}}">
<i aria-hidden="true" class="fa fa-info-circle"></i> instructions
</a>
</span>
</li>
<li>
<span>
<strong>Verify your filebeat installation below.</strong> We'll poll your new index pattern for documents and let you know when
they show up. If you'd like to skip this step, simply click Done now.
</span>
</li>
</ol>
</div>
<pattern-checker pattern="installStep.results.indexPattern.id"/>

View file

@ -1,23 +0,0 @@
import modules from 'ui/modules';
import template from './install_filebeat_step.html';
import 'ui/pattern_checker';
import { patternToIngest } from '../../../../../../common/lib/convert_pattern_and_ingest_name';
import { filebeat as docLinks } from '../../../../../../../../ui/public/documentation_links/documentation_links';
import './styles/_add_data_install_filebeat_step.less';
modules.get('apps/management')
.directive('installFilebeatStep', function () {
return {
template: template,
scope: {
results: '='
},
bindToController: true,
controllerAs: 'installStep',
controller: function ($scope) {
this.pipelineId = patternToIngest(this.results.indexPattern.id);
this.docLinks = docLinks;
}
};
});

View file

@ -1,22 +0,0 @@
install-filebeat-step {
.install-filebeat {
> ol {
padding-left: 1em;
> li {
padding: 4px 0;
font-weight: bold;
> span {
font-weight: normal;
> pre {
margin: 7px 0;
}
}
}
}
}
}

View file

@ -1,11 +0,0 @@
<h2><em>Provide some sample logs.</em>
Paste in one or more lines from the file you intend to tail. We'll use these samples in the following steps to help
you build an ingest pipeline and configure a Kibana index pattern. Log lines can be raw strings or
formatted as JSON. If your logs are raw strings but you intend to use
<a target="_window" ng-href="{{pasteStep.docLinks.exportedFields}}">Filebeat's metadata</a>,
you'll want to paste the JSON as it will come out of Filebeat.
</h2>
<div class="paste-samples form-group">
<textarea class="form-control" ng-model="pasteStep.rawSamples" placeholder="Paste your sample log lines here, separated by a newline"></textarea>
</div>

View file

@ -1,41 +0,0 @@
import modules from 'ui/modules';
import template from './paste_samples_step.html';
import { filebeat as docLinks } from '../../../../../../../../ui/public/documentation_links/documentation_links';
import _ from 'lodash';
import './styles/_add_data_paste_samples_step.less';
modules.get('apps/management')
.directive('pasteSamplesStep', function () {
return {
template: template,
scope: {
samples: '=',
rawSamples: '='
},
bindToController: true,
controllerAs: 'pasteStep',
controller: function ($scope) {
this.docLinks = docLinks;
if (_.isUndefined(this.rawSamples)) {
this.rawSamples = '';
}
$scope.$watch('pasteStep.rawSamples', (newValue) => {
const splitRawSamples = newValue.split('\n');
try {
this.samples = _.map(splitRawSamples, (sample) => {
return JSON.parse(sample);
});
}
catch (error) {
this.samples = _.map(splitRawSamples, (sample) => {
return {message: sample};
});
}
});
}
};
});

View file

@ -1,6 +0,0 @@
.paste-samples {
textarea {
width: 100%;
height: 250px;
}
}

View file

@ -1,64 +0,0 @@
import forEachField from '../lib/for_each_field';
import sinon from 'auto-release-sinon';
import expect from 'expect.js';
describe('forEachField', function () {
let testDoc;
beforeEach(function () {
testDoc = {
foo: [
{bar: [{'baz': 1}]},
{bat: 'boo'}
]
};
});
it('should require a plain object argument', function () {
expect(forEachField).withArgs([], () => {}).to.throwException(/first argument must be a plain object/);
});
it('should not invoke iteratee if collection is null or empty', function () {
const iteratee = sinon.spy();
forEachField({}, iteratee);
expect(iteratee.called).to.not.be.ok();
});
it('should call iteratee for each item in an array field, but not for the array itself', function () {
const iteratee = sinon.spy();
forEachField({foo: [1, 2, 3]}, iteratee);
expect(iteratee.callCount).to.be(3);
expect(iteratee.calledWith(1, 'foo')).to.be.ok();
expect(iteratee.calledWith(2, 'foo')).to.be.ok();
expect(iteratee.calledWith(3, 'foo')).to.be.ok();
});
it('should call iteratee for flattened inner object properties, as well as the object itself', function () {
const iteratee = sinon.spy();
forEachField(testDoc, iteratee);
expect(iteratee.callCount).to.be(5);
expect(iteratee.calledWith(testDoc.foo[0], 'foo')).to.be.ok();
expect(iteratee.calledWith(testDoc.foo[1], 'foo')).to.be.ok();
expect(iteratee.calledWith(testDoc.foo[0].bar[0], 'foo.bar')).to.be.ok();
expect(iteratee.calledWith(1, 'foo.bar.baz')).to.be.ok();
expect(iteratee.calledWith('boo', 'foo.bat')).to.be.ok();
});
it('should detect geo_point fields and should not invoke iteratee for its lat and lon sub properties', function () {
const iteratee = sinon.spy();
const geo = {lat: 38.6631, lon: -90.5771};
forEachField({ geo }, iteratee);
expect(iteratee.callCount).to.be(1);
expect(iteratee.calledWith(geo, 'geo')).to.be.ok();
});
});

View file

@ -1,21 +0,0 @@
import isGeoPointObject from '../lib/is_geo_point_object';
import expect from 'expect.js';
describe('isGeoPointObject', function () {
it('should return true if an object has lat and lon properties', function () {
expect(isGeoPointObject({lat: 38.6631, lon: -90.5771})).to.be(true);
});
it('should return false if the value is not an object', function () {
expect(isGeoPointObject('foo')).to.be(false);
expect(isGeoPointObject(1)).to.be(false);
expect(isGeoPointObject(true)).to.be(false);
expect(isGeoPointObject(null)).to.be(false);
});
it('should return false if the value is an object without lat an lon properties', function () {
expect(isGeoPointObject({foo: 'bar'})).to.be(false);
});
});

View file

@ -1,91 +0,0 @@
import expect from 'expect.js';
import ngMock from 'ng_mock';
describe('pattern review directive', function () {
let $rootScope;
let $compile;
beforeEach(ngMock.module('kibana'));
beforeEach(ngMock.inject(function ($injector, Private) {
$compile = $injector.get('$compile');
$rootScope = $injector.get('$rootScope');
}));
describe('handling geopoints', function () {
it('should detect geo_point fields when they\'re expressed as an object', function () {
const scope = $rootScope.$new();
scope.sampleDoc = {
geoip: {
location: {
lat: 38.6631,
lon: -90.5771
}
}
};
$compile('<pattern-review-step sample-doc="sampleDoc" index-pattern="indexPattern"></pattern-review-step>')(scope);
scope.$digest();
expect(scope).to.have.property('indexPattern');
expect(scope.indexPattern.fields[0].type).to.be('geo_point');
});
it('should not count the lat and lon properties as their own fields', function () {
const scope = $rootScope.$new();
scope.sampleDoc = {
geoip: {
location: {
lat: 38.6631,
lon: -90.5771
}
}
};
$compile('<pattern-review-step sample-doc="sampleDoc" index-pattern="indexPattern"></pattern-review-step>')(scope);
scope.$digest();
expect(scope).to.have.property('indexPattern');
expect(scope.indexPattern.fields[0].type).to.be('geo_point');
expect(scope.indexPattern.fields.length).to.be(1);
});
});
describe('detecting date fields', function () {
it('should detect sample strings in ISO 8601 format as date fields', function () {
const scope = $rootScope.$new();
scope.sampleDoc = {
isodate: '2004-03-08T00:05:49.000Z'
};
$compile('<pattern-review-step sample-doc="sampleDoc" index-pattern="indexPattern"></pattern-review-step>')(scope);
scope.$digest();
expect(scope).to.have.property('indexPattern');
expect(scope.indexPattern.fields[0].type).to.be('date');
});
});
describe('conflicting array values', function () {
it('should detect heterogeneous arrays and flag them with an error message', function () {
const scope = $rootScope.$new();
scope.sampleDoc = {
badarray: ['foo', 42]
};
const element = $compile('<pattern-review-step sample-doc="sampleDoc" index-pattern="indexPattern"></pattern-review-step>')(scope);
const controller = element.controller('patternReviewStep');
scope.$digest();
expect(controller).to.have.property('errors');
// error message should mentioned the conflicting field
expect(controller.errors[0]).to.contain('badarray');
});
});
});

View file

@ -1,58 +0,0 @@
import _ from 'lodash';
import isGeoPointObject from './is_geo_point_object';
// This function recursively traverses an object, visiting each node that elasticsearch would index as a field.
// Iteratee is invoked with two arguments: (value, fieldName). fieldName is the name of the field as elasticsearch
// would see it. For example:
//
// const testDoc = {
// foo: [
// {bar: [{'baz': 1}]},
// {bat: 'boo'}
// ],
// geo: {
// lat: 38.6631,
// lon: -90.5771
// }
// };
//
// forEachField(testDoc, function(value, fieldName) { ... });
//
// The iteratee would be invoked six times, with the following parameters:
// 1. fieldName = 'foo' value = {bar: [{'baz': 1}]}
// 2. fieldName = 'foo' value = {bat: 'boo'}
// 3. fieldName = 'foo.bar' value = {'baz': 1}
// 4. fieldName = 'foo.bar.baz' value = 1
// 5. fieldName = 'foo.bat' value = 'boo'
// 6. fieldName = 'geo' value = {lat: 38.6631, lon: -90.5771}
//
// forEachField handles arrays, objects, and geo_points as elasticsearch would. It does not currently handle nested
// type fields.
function forEachFieldAux(value, iteratee, fieldName) {
if (!_.isObject(value) || isGeoPointObject(value)) {
iteratee(value, fieldName);
}
else if (_.isPlainObject(value)) {
if (!_.isEmpty(fieldName)) {
iteratee(value, fieldName);
fieldName += '.';
}
_.forEach(value, (subValue, key) => {
forEachFieldAux(subValue, iteratee, fieldName + key);
});
}
else if (_.isArray(value)) {
_.forEach(value, (subValue) => {
forEachFieldAux(subValue, iteratee, fieldName);
});
}
}
export default function forEachField(object, iteratee) {
if (!_.isPlainObject(object)) {
throw new Error('first argument must be a plain object');
}
forEachFieldAux(object, iteratee, '');
}

View file

@ -1,15 +0,0 @@
import _ from 'lodash';
export default function isGeoPointObject(object) {
let retVal = false;
if (_.isPlainObject(object)) {
const keys = _.keys(object);
if (keys.length === 2 && _.contains(keys, 'lat') && _.contains(keys, 'lon')) {
retVal = true;
}
}
return retVal;
}

View file

@ -1,50 +0,0 @@
<h2><em>Review the index pattern.</em>
Here we'll define how and where to store your parsed events. We've made some intelligent guesses for you, but most
fields can be changed if we got it wrong!
</h2>
<form name="reviewStep.form">
<div class="pattern-review form-inline">
<div ng-show="reviewStep.errors.length" class="alert alert-danger">
<div ng-repeat="error in reviewStep.errors">{{ error }}</div>
</div>
<div class="alert alert-danger"
ng-show="reviewStep.form.pattern.$dirty && reviewStep.form.pattern.$error.lowercase">
Index names must be all lowercase
</div>
<div class="alert alert-danger"
ng-show="reviewStep.form.pattern.$dirty && reviewStep.form.pattern.$error.indexNameInput">
An index name must not be empty and cannot contain whitespace or any of the following characters: ", *, \, <, |, ,, >, /, ?
</div>
<label>{{ reviewStep.patternInput.label }}</label>
<span id="pattern-help" class="help-block">{{ reviewStep.patternInput.helpText }}</span>
<input name="pattern" ng-model="reviewStep.indexPattern.id"
class="pattern-input form-control"
novalidate
required
validate-index-name
validate-lowercase
placeholder="{{reviewStep.patternInput.placeholder}}"
aria-describedby="pattern-help"/>
<label>
<input ng-model="reviewStep.isTimeBased" type="checkbox"/>
time based
</label>
<label ng-if="reviewStep.isTimeBased" class="time-field-input">
Time Field
<select ng-model="reviewStep.indexPattern.timeFieldName" name="time_field_name" class="form-control">
<option ng-repeat="field in reviewStep.dateFields" value="{{field}}">
{{field}}
</option>
</select>
</label>
</div>
<paginated-table
class="pattern-review-field-table"
columns="reviewStep.columns"
rows="reviewStep.rows"
per-page="10">
</paginated-table>
</form>

View file

@ -1,132 +0,0 @@
import modules from 'ui/modules';
import template from './pattern_review_step.html';
import _ from 'lodash';
import editFieldTypeHTML from '../../partials/_edit_field_type.html';
import isGeoPointObject from './lib/is_geo_point_object';
import forEachField from './lib/for_each_field';
import './styles/_add_data_pattern_review_step.less';
import moment from 'moment';
import '../../../../../../../../ui/public/directives/validate_lowercase';
function pickDefaultTimeFieldName(dateFields) {
if (_.isEmpty(dateFields)) {
return undefined;
}
return _.includes(dateFields, '@timestamp') ? '@timestamp' : dateFields[0];
}
function findFieldsByType(indexPatternFields, type) {
return _.map(_.filter(indexPatternFields, {type}), 'name');
}
modules.get('apps/management')
.directive('patternReviewStep', function () {
return {
template: template,
scope: {
indexPattern: '=',
pipeline: '=',
sampleDoc: '=',
defaultIndexInput: '='
},
controllerAs: 'reviewStep',
bindToController: true,
controller: function ($scope, Private) {
this.errors = [];
const sampleFields = {};
this.patternInput = {
label: 'Index name',
helpText: 'The name of the Elasticsearch index you want to create for your data.',
defaultValue: '',
placeholder: 'Name'
};
if (this.defaultIndexInput) {
this.patternInput.defaultValue = this.defaultIndexInput;
}
if (_.isUndefined(this.indexPattern)) {
this.indexPattern = {};
}
forEachField(this.sampleDoc, (value, fieldName) => {
let type = typeof value;
if (isGeoPointObject(value)) {
type = 'geo_point';
}
if (type === 'string' && moment(value, moment.ISO_8601).isValid()) {
type = 'date';
}
if (value === null) {
type = 'string';
}
if (!_.isUndefined(sampleFields[fieldName]) && (sampleFields[fieldName].type !== type)) {
this.errors.push(`Error in field ${fieldName} - conflicting types '${sampleFields[fieldName].type}' and '${type}'`);
}
else {
sampleFields[fieldName] = {type, value};
}
});
_.defaults(this.indexPattern, {
id: this.patternInput.defaultValue,
title: 'filebeat-*',
fields: _(sampleFields)
.map((field, fieldName) => {
return {name: fieldName, type: field.type};
})
.reject({type: 'object'})
.value()
});
$scope.$watch('reviewStep.indexPattern.id', (value) => {
this.indexPattern.title = value;
});
$scope.$watch('reviewStep.isTimeBased', (value) => {
if (value) {
this.indexPattern.timeFieldName = pickDefaultTimeFieldName(this.dateFields);
}
else {
delete this.indexPattern.timeFieldName;
}
});
$scope.$watch('reviewStep.indexPattern.fields', (fields) => {
this.dateFields = findFieldsByType(fields, 'date');
}, true);
this.dateFields = findFieldsByType(this.indexPattern.fields, 'date');
this.isTimeBased = !_.isEmpty(this.dateFields);
const buildRows = () => {
this.rows = _.map(this.indexPattern.fields, (field) => {
const {type: detectedType, value: sampleValue} = sampleFields[field.name];
return [
_.escape(field.name),
{
markup: editFieldTypeHTML,
scope: _.assign($scope.$new(), {field: field, detectedType: detectedType, buildRows: buildRows}),
value: field.type
},
typeof sampleValue === 'object' ? _.escape(JSON.stringify(sampleValue)) : _.escape(sampleValue)
];
});
};
this.columns = [
{title: 'Field'},
{title: 'Type'},
{title: 'Example', sortable: false}
];
buildRows();
}
};
});

View file

@ -1,71 +0,0 @@
@import (reference) "../../../styles/_add_data_wizard";
pattern-review-step {
margin-bottom: 14px;
.pattern-review {
margin-bottom: 15px;
label {
margin-bottom: 0;
}
.time-field-input {
padding-left: 14px;
margin-bottom: 0;
}
.pattern-input {
width: 300px;
margin-right: 7px;
}
> .help-block {
margin-top: 0;
}
}
paginated-table.pattern-review-field-table {
table {
border-bottom: 3px solid @settings-filebeat-wizard-panel-bg;
tr {
.form-group;
}
th {
border-bottom: 0;
padding-top: 10px;
padding-bottom: 10px;
background-color: @settings-filebeat-wizard-panel-bg;
font-weight: normal;
}
td {
border-top: 3px solid @settings-filebeat-wizard-panel-bg;
vertical-align: middle;
padding-right: 14px;
}
select {
.form-control;
.wizard-container.form-control;
min-width: 105px;
}
}
paginate-controls {
position: relative;
ul > li > a {
background-color: @settings-filebeat-wizard-panel-bg;
}
form.pagination-size {
position: absolute;
right: 0;
}
}
}
}

View file

@ -1,110 +0,0 @@
<div class="wizard-container">
<div class="wizard-step-headings" ng-class="{complete: wizard.complete}">
<span ng-class="{active: wizard.currentStep === 0}"
class="wizard-step-heading"
ng-click="wizard.setCurrentStep(0)">
1. Paste
</span>
<span ng-class="{active: wizard.currentStep === 1, aheadActive: wizard.currentStep < 1}"
class="wizard-step-heading"
ng-click="wizard.currentStep < 1 || wizard.setCurrentStep(1)">
2. Parse
</span>
<span ng-class="{active: wizard.currentStep === 2, aheadActive: wizard.currentStep < 2}"
class="wizard-step-heading"
ng-click="wizard.currentStep < 2 || wizard.setCurrentStep(2)">
3. Review
</span>
<span ng-class="{active: wizard.currentStep === 3, aheadActive: wizard.currentStep < 3}"
class="wizard-step-heading"
ng-click="wizard.currentStep < 3 || wizard.setCurrentStep(3)">
4. Install Filebeat
</span>
</div>
<div ng-switch="wizard.currentStep">
<div ng-switch-when="0">
<paste-samples-step samples="wizard.stepResults.samples" raw-samples="wizard.stepResults.rawSamples"></paste-samples-step>
<div class="wizard-nav-buttons">
<div></div>
<div>
<button
class="btn btn-primary"
ng-disabled="!wizard.stepResults.samples"
ng-click="wizard.nextStep()">
Next
</button>
</div>
<div></div>
</div>
</div>
<div ng-switch-when="1">
<pipeline-setup
pipeline="wizard.stepResults.pipeline"
samples="wizard.stepResults.samples">
</pipeline-setup>
<div class="wizard-nav-buttons">
<div>
<button
class="btn btn-secondary"
ng-click="wizard.prevStep()">
Prev
</button>
</div>
<div>
<button
class="btn btn-primary"
ng-click="wizard.nextStep()">
Next
</button>
</div>
<div></div>
</div>
</div>
<div ng-switch-when="2">
<pattern-review-step
index-pattern="wizard.stepResults.indexPattern"
pipeline="wizard.stepResults.pipeline"
sample-doc="wizard.stepResults.pipeline.output">
</pattern-review-step>
<div class="wizard-nav-buttons">
<div>
<button
class="btn btn-secondary"
ng-click="wizard.prevStep()">
Prev
</button>
</div>
<div>
<button
class="btn btn-primary"
ng-disabled="!wizard.stepResults.indexPattern || !wizard.stepResults.indexPattern.id"
ng-click="wizard.save()">
Save
</button>
</div>
<div></div>
</div>
</div>
<div ng-switch-when="3">
<install-filebeat-step results="wizard.stepResults"></install-filebeat-step>
<div class="wizard-nav-buttons">
<div></div>
<div>
<button
class="btn btn-primary"
ng-click="wizard.nextStep()">
Done
</button>
</div>
<div></div>
</div>
</div>
</div>
</div>

View file

@ -1,99 +0,0 @@
import modules from 'ui/modules';
import template from 'plugins/kibana/management/sections/indices/filebeat/directives/filebeat_wizard.html';
import IngestProvider from 'ui/ingest';
import 'plugins/kibana/management/sections/indices/add_data_steps/pattern_review_step';
import 'plugins/kibana/management/sections/indices/add_data_steps/paste_samples_step';
import 'plugins/kibana/management/sections/indices/add_data_steps/install_filebeat_step';
import '../../styles/_add_data_wizard.less';
// wrapper directive, which sets up the breadcrumb for all filebeat steps
modules.get('apps/management')
.directive('filebeatWizard', function () {
return {
restrict: 'E',
template: template,
scope: {},
bindToController: true,
controllerAs: 'wizard',
controller: function ($scope, AppState, safeConfirm, kbnUrl, Notifier, $window, Private) {
const ingest = Private(IngestProvider);
const $state = this.state = new AppState();
var notify = new Notifier({
location: 'Add Data'
});
var totalSteps = 4;
this.stepResults = {};
this.setCurrentStep = (step) => {
if (!this.complete) {
$state.currentStep = step;
$state.save();
}
};
this.setCurrentStep(0);
this.nextStep = () => {
if ($state.currentStep + 1 < totalSteps) {
this.setCurrentStep($state.currentStep + 1);
}
else if ($state.currentStep + 1 === totalSteps) {
kbnUrl.change('/discover');
}
};
this.prevStep = () => {
if ($state.currentStep > 0) {
this.setCurrentStep($state.currentStep - 1);
}
};
this.save = () => {
const processors = this.stepResults.pipeline.processors.map(processor => processor.model);
return ingest.save(this.stepResults.indexPattern, processors)
.then(
() => {
this.nextStep();
},
(err) => {
notify.error(err);
$window.scrollTo(0,0);
}
);
};
$scope.$watch('wizard.state.currentStep', (newValue, oldValue) => {
if (this.complete) {
$state.currentStep = totalSteps - 1;
$state.save();
return;
}
if (newValue + 1 === totalSteps) {
this.complete = true;
}
if (newValue < oldValue) {
return safeConfirm('Going back will reset any changes you\'ve made to this step, do you want to continue?')
.then(
() => {
if ($state.currentStep < 1) {
delete this.stepResults.pipeline;
}
if ($state.currentStep < 2) {
delete this.stepResults.indexPattern;
}
this.currentStep = newValue;
},
() => {
$state.currentStep = oldValue;
$state.save();
}
);
}
else {
this.currentStep = newValue;
}
});
}
};
});

View file

@ -1,3 +0,0 @@
<kbn-management-app section="data">
<filebeat-wizard/>
</kbn-management-app>

View file

@ -1,8 +0,0 @@
import routes from 'ui/routes';
import template from 'plugins/kibana/management/sections/indices/filebeat/index.html';
import 'plugins/kibana/management/sections/indices/filebeat/directives/filebeat_wizard';
routes.when('/management/data/filebeat', {
template: template
});

View file

@ -1,12 +1,8 @@
import { registerPost } from './register_post';
import { registerDelete } from './register_delete';
import { registerProcessors } from './register_processors';
import { registerSimulate } from './register_simulate';
import { registerFieldCapabilities } from './register_field_capabilities';
export default function (server) {
registerPost(server);
registerDelete(server);
registerProcessors(server);
registerSimulate(server);
registerFieldCapabilities(server);

View file

@ -1,32 +0,0 @@
import Promise from 'bluebird';
import handleESError from '../../../lib/handle_es_error';
import {ingestToPattern, patternToIngest} from '../../../../common/lib/convert_pattern_and_ingest_name';
export function registerDelete(server) {
server.route({
path: '/api/kibana/ingest/{id}',
method: 'DELETE',
handler: function (req, reply) {
const kibanaIndex = server.config().get('kibana.index');
const callWithRequest = server.plugins.elasticsearch.callWithRequest;
const deletePatternParams = {
index: kibanaIndex,
type: 'index-pattern',
id: req.params.id
};
Promise.all([
callWithRequest(req, 'delete', deletePatternParams),
callWithRequest(req, 'indices.deleteTemplate', {name: patternToIngest(req.params.id), ignore: [404]})
])
.then(
function (pattern) {
reply({success: true});
},
function (error) {
reply(handleESError(error));
}
);
}
});
};

View file

@ -1,105 +0,0 @@
import Boom from 'boom';
import _ from 'lodash';
import ingestConfigSchema from '../../../lib/schemas/resources/ingest_config_schema';
import handleESError from '../../../lib/handle_es_error';
import createMappingsFromPatternFields from '../../../lib/create_mappings_from_pattern_fields';
import initDefaultFieldProps from '../../../lib/init_default_field_props';
import {ingestToPattern, patternToIngest} from '../../../../common/lib/convert_pattern_and_ingest_name';
import { keysToCamelCaseShallow } from '../../../../common/lib/case_conversion';
export function registerPost(server) {
const kibanaIndex = server.config().get('kibana.index');
function patternRollback(rootError, indexPatternId, boundCallWithRequest) {
const deleteParams = {
index: kibanaIndex,
type: 'index-pattern',
id: indexPatternId
};
return boundCallWithRequest('delete', deleteParams)
.then(
() => {
throw rootError;
},
(patternDeletionError) => {
throw new Error(
`index-pattern ${indexPatternId} created successfully but index template
creation failed. Failed to rollback index-pattern creation, must delete manually.
${patternDeletionError.toString()}
${rootError.toString()}`
);
}
);
}
server.route({
path: '/api/kibana/ingest',
method: 'POST',
config: {
validate: {
payload: ingestConfigSchema
}
},
handler: async function (req, reply) {
const uiSettings = server.uiSettings();
const metaFields = await uiSettings.get('metaFields');
const boundCallWithRequest = _.partial(server.plugins.elasticsearch.callWithRequest, req);
const requestDocument = _.cloneDeep(req.payload);
const indexPattern = keysToCamelCaseShallow(requestDocument.index_pattern);
const indexPatternId = indexPattern.id;
const ingestConfigName = patternToIngest(indexPatternId);
delete indexPattern.id;
const mappings = createMappingsFromPatternFields(indexPattern.fields);
const indexPatternMetaFields = _.map(metaFields, name => ({name}));
indexPattern.fields = initDefaultFieldProps(indexPattern.fields.concat(indexPatternMetaFields));
indexPattern.fields = JSON.stringify(indexPattern.fields);
indexPattern.fieldFormatMap = JSON.stringify(indexPattern.fieldFormatMap);
// Set up call with request params
const patternCreateParams = {
index: kibanaIndex,
type: 'index-pattern',
id: indexPatternId,
body: indexPattern
};
const templateParams = {
order: 1,
create: true,
name: ingestConfigName,
body: {
template: indexPatternId,
mappings: {
_default_: {
properties: mappings
}
}
}
};
return boundCallWithRequest('indices.exists', {index: indexPatternId})
.then((matchingIndices) => {
if (matchingIndices) {
throw Boom.conflict('Cannot create an index pattern via this API if existing indices already match the pattern');
}
return boundCallWithRequest('create', patternCreateParams)
.then(() => {
return boundCallWithRequest('indices.putTemplate', templateParams)
.catch((templateError) => {return patternRollback(templateError, indexPatternId, boundCallWithRequest);});
});
})
.then(
function () {
reply().code(204);
},
function (error) {
reply(handleESError(error));
}
);
}
});
};

View file

@ -1,63 +0,0 @@
define(function (require) {
var Promise = require('bluebird');
var createTestData = require('intern/dojo/node!../../../unit/api/ingest/data');
var _ = require('intern/dojo/node!lodash');
var expect = require('intern/dojo/node!expect.js');
return function (bdd, scenarioManager, request) {
bdd.describe('DELETE ingest', function deleteIngestConfig() {
bdd.beforeEach(function () {
return scenarioManager.reload('emptyKibana')
.then(function () {
return request.post('/kibana/ingest')
.send(createTestData())
.expect(204);
});
});
bdd.afterEach(function () {
return request.del('/kibana/ingest/logstash-*')
.then(function () {
return scenarioManager.client.indices.deleteTemplate({name: 'kibana-logstash-*'})
.catch(function (err) {
if (err.status !== 404) {
throw err;
}
});
});
});
bdd.it('should return 200 for successful deletion of pattern and template', function () {
return request.del('/kibana/ingest/logstash-*')
.expect(200)
.then(function () {
return request.get('/kibana/ingest/logstash-*').expect(404);
})
.then(function () {
return scenarioManager.client.indices.getTemplate({name: 'kibana-logstash-*'})
.catch(function (error) {
expect(error.status).to.be(404);
});
})
.then(function () {
return scenarioManager.client.transport.request({
path: '_ingest/pipeline/kibana-logstash-*',
method: 'GET'
})
.catch(function (error) {
expect(error.status).to.be(404);
});
});
});
bdd.it('should return 404 for a non-existent id', function () {
return request.del('/kibana/ingest/doesnotexist')
.expect(404);
});
});
};
});

View file

@ -1,216 +0,0 @@
define(function (require) {
var Promise = require('bluebird');
var createTestData = require('intern/dojo/node!../../../unit/api/ingest/data');
var _ = require('intern/dojo/node!lodash');
var expect = require('intern/dojo/node!expect.js');
return function (bdd, scenarioManager, request) {
bdd.describe('POST ingest', function postIngest() {
bdd.beforeEach(function () {
return scenarioManager.reload('emptyKibana');
});
bdd.afterEach(function () {
return request.del('/kibana/ingest/logstash-*')
.then(function () {
return scenarioManager.client.indices.delete({
index: 'logstash-*'
});
});
});
bdd.it('should return 400 for an invalid payload', function invalidPayload() {
return Promise.all([
request.post('/kibana/ingest').expect(400),
request.post('/kibana/ingest')
.send({})
.expect(400),
request.post('/kibana/ingest')
.send(_.set(createTestData(), 'index_pattern.title', false))
.expect(400),
request.post('/kibana/ingest')
.send(_.set(createTestData(), 'index_pattern.fields', {}))
.expect(400),
request.post('/kibana/ingest')
.send(_.set(createTestData(), 'index_pattern.fields', []))
.expect(400),
// Fields must have a name and type
request.post('/kibana/ingest')
.send(_.set(createTestData(), 'index_pattern.fields', [{count: 0}]))
.expect(400)
]);
});
bdd.it('should return 204 when an ingest config is successfully created', function createIngestConfig() {
return request.post('/kibana/ingest')
.send(createTestData())
.expect(204);
});
bdd.it('should create an index template if a fields array is included', function createTemplate() {
return request.post('/kibana/ingest')
.send(createTestData())
.expect(204)
.then(function () {
return scenarioManager.client.indices.getTemplate({name: 'kibana-logstash-*'});
});
});
bdd.it('should successfully create new indices based on the template', function newIndices() {
return request.post('/kibana/ingest')
.send(createTestData())
.expect(204)
.then(function () {
return scenarioManager.client.create({
index: 'logstash-1',
type: 'foo',
id: '1',
body: {
ip: '192.168.1.1',
'@timestamp': '2015-09-20T10:28:22.684Z',
agent: 'Jack',
bytes: 9001,
geo: {coordinates: {lat: 43.07260861, lon: -92.61077833}}
}
})
.then(function (response) {
expect(response.created).to.be.ok();
});
});
});
bdd.it('should provide defaults for field properties', function createTemplate() {
return request.post('/kibana/ingest')
.send(createTestData())
.expect(204)
.then(function () {
return scenarioManager.client.get({
index: '.kibana',
type: 'index-pattern',
id: 'logstash-*'
})
.then(function (res) {
var fields = JSON.parse(res._source.fields);
// @timestamp was created with only name and type, all other fields should be set as defaults by API
expect(res._source.title).to.be('logstash-*');
expect(fields[1].name).to.be('@timestamp');
expect(fields[1].type).to.be('date');
expect(fields[1].count).to.be(0);
expect(fields[1].scripted).to.be(false);
expect(fields[1].indexed).to.be(true);
expect(fields[1].analyzed).to.be(false);
expect(fields[1].doc_values).to.be(true);
});
});
});
bdd.it('should include meta fields specified in uiSettings in the index pattern', function metaFields() {
return request.post('/kibana/ingest')
.send(createTestData())
.expect(204)
.then(function () {
return scenarioManager.client.get({
index: '.kibana',
type: 'index-pattern',
id: 'logstash-*'
})
.then(function (res) {
const fields = JSON.parse(res._source.fields);
const sourceField = _.find(fields, {name: '_source'});
expect(sourceField).to.be.ok();
expect(sourceField).to.have.property('name', '_source');
});
});
});
bdd.it('should create index template with _default_ mappings based on the info in the ingest config',
function createTemplate() {
return request.post('/kibana/ingest')
.send(createTestData())
.expect(204)
.then(function () {
return scenarioManager.client.indices.getTemplate({name: 'kibana-logstash-*'})
.then(function (template) {
var mappings = template['kibana-logstash-*'].mappings._default_.properties;
expect(mappings).to.be.ok();
expect(_.isEqual(mappings.ip, {index: true, type: 'ip', doc_values: true})).to.be.ok();
expect(_.isEqual(mappings['@timestamp'], {index: true, type: 'date', doc_values: true})).to.be.ok();
expect(_.isEqual(mappings.bytes, {index: true, type: 'double', doc_values: true})).to.be.ok();
// object fields are mapped as such, with individual mappings for each of their properties
expect(_.isEqual(mappings.geo, {
properties: {
coordinates: {
index: true,
type: 'geo_point',
doc_values: true
}
}
})).to.be.ok();
// strings should be mapped as multi fields
expect(mappings.agent).to.have.property('fields');
});
});
});
bdd.it('should return 409 conflict when a pattern with the given ID already exists', function patternConflict() {
return request.post('/kibana/ingest')
.send(createTestData())
.expect(204)
.then(function () {
return request.post('/kibana/ingest')
.send(createTestData())
.expect(409);
});
});
bdd.it('should return 409 conflict when an index template with the given ID already exists', function templateConflict() {
return scenarioManager.client.indices.putTemplate({
name: 'kibana-logstash-*', body: {
template: 'logstash-*'
}
}).then(function () {
return request.post('/kibana/ingest')
.send(createTestData())
.expect(409);
})
.then(function () {
return scenarioManager.client.indices.deleteTemplate({
name: 'kibana-logstash-*'
});
});
});
bdd.it('should return 409 conflict when the pattern matches existing indices',
function existingIndicesConflict() {
var ingestConfig = createTestData();
ingestConfig.index_pattern.id = ingestConfig.index_pattern.title = '.kib*';
return request.post('/kibana/ingest')
.send(ingestConfig)
.expect(409);
});
bdd.it('should enforce snake_case in the request body', function () {
var ingestConfig = createTestData();
ingestConfig.index_pattern = _.mapKeys(ingestConfig.index_pattern, function (value, key) {
return _.camelCase(key);
});
return request.post('/kibana/ingest')
.send(ingestConfig)
.expect(400);
});
});
};
});

View file

@ -6,8 +6,6 @@ define(function (require) {
var url = require('intern/dojo/node!url');
var _ = require('intern/dojo/node!lodash');
var expect = require('intern/dojo/node!expect.js');
var post = require('./_post');
var del = require('./_del');
var simulate = require('./_simulate');
var processors = require('./_processors');
var processorTypes = require('./processors/index');
@ -25,8 +23,6 @@ define(function (require) {
return scenarioManager.unload('emptyKibana');
});
post(bdd, scenarioManager, request);
del(bdd, scenarioManager, request);
simulate(bdd, scenarioManager, request);
processors(bdd, scenarioManager, request);
processorTypes(bdd, scenarioManager, request);