From 46135cd0675d70488afd6087a14b099a43c37430 Mon Sep 17 00:00:00 2001 From: Shaunak Kashyap Date: Fri, 11 May 2018 09:43:21 -0700 Subject: [PATCH 01/94] [Beats Management] Initial scaffolding for plugin (#18977) * Initial scaffolding for Beats plugin * Removing bits not (yet) necessary in initial scaffolding --- x-pack/index.js | 2 ++ x-pack/plugins/beats/common/constants/index.js | 7 +++++++ x-pack/plugins/beats/common/constants/plugin.js | 9 +++++++++ x-pack/plugins/beats/index.js | 16 ++++++++++++++++ 4 files changed, 34 insertions(+) create mode 100644 x-pack/plugins/beats/common/constants/index.js create mode 100644 x-pack/plugins/beats/common/constants/plugin.js create mode 100644 x-pack/plugins/beats/index.js diff --git a/x-pack/index.js b/x-pack/index.js index a98af06dde131..6f5c12814997a 100644 --- a/x-pack/index.js +++ b/x-pack/index.js @@ -16,6 +16,7 @@ import { watcher } from './plugins/watcher'; import { grokdebugger } from './plugins/grokdebugger'; import { dashboardMode } from './plugins/dashboard_mode'; import { logstash } from './plugins/logstash'; +import { beats } from './plugins/beats'; import { apm } from './plugins/apm'; import { licenseManagement } from './plugins/license_management'; import { cloud } from './plugins/cloud'; @@ -38,6 +39,7 @@ module.exports = function (kibana) { grokdebugger(kibana), dashboardMode(kibana), logstash(kibana), + beats(kibana), apm(kibana), licenseManagement(kibana), cloud(kibana), diff --git a/x-pack/plugins/beats/common/constants/index.js b/x-pack/plugins/beats/common/constants/index.js new file mode 100644 index 0000000000000..035454fd7b435 --- /dev/null +++ b/x-pack/plugins/beats/common/constants/index.js @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { PLUGIN } from './plugin'; diff --git a/x-pack/plugins/beats/common/constants/plugin.js b/x-pack/plugins/beats/common/constants/plugin.js new file mode 100644 index 0000000000000..289bc488c58a6 --- /dev/null +++ b/x-pack/plugins/beats/common/constants/plugin.js @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const PLUGIN = { + ID: 'beats' +}; diff --git a/x-pack/plugins/beats/index.js b/x-pack/plugins/beats/index.js new file mode 100644 index 0000000000000..8319485db1749 --- /dev/null +++ b/x-pack/plugins/beats/index.js @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { PLUGIN } from './common/constants'; + +export function beats(kibana) { + return new kibana.Plugin({ + id: PLUGIN.ID, + require: ['kibana', 'elasticsearch', 'xpack_main'], + init: function () { + } + }); +} From 40719d7031cd9134ecf155708f76d644c6258fda Mon Sep 17 00:00:00 2001 From: Shaunak Kashyap Date: Tue, 15 May 2018 11:29:46 -0700 Subject: [PATCH 02/94] [Beats Management] Install Beats index template on plugin init (#19072) * Install Beats index template on plugin init * Adding missing files --- x-pack/plugins/beats/index.js | 4 +- .../client/call_with_internal_user_factory.js | 16 ++++ .../plugins/beats/server/lib/client/index.js | 7 ++ .../lib/index_template/beats_template.json | 84 +++++++++++++++++++ .../beats/server/lib/index_template/index.js | 7 ++ .../index_template/install_index_template.js | 18 ++++ 6 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 x-pack/plugins/beats/server/lib/client/call_with_internal_user_factory.js create mode 100644 x-pack/plugins/beats/server/lib/client/index.js create mode 100644 x-pack/plugins/beats/server/lib/index_template/beats_template.json create mode 100644 x-pack/plugins/beats/server/lib/index_template/index.js create mode 100644 x-pack/plugins/beats/server/lib/index_template/install_index_template.js diff --git a/x-pack/plugins/beats/index.js b/x-pack/plugins/beats/index.js index 8319485db1749..8839a30fc5a2e 100644 --- a/x-pack/plugins/beats/index.js +++ b/x-pack/plugins/beats/index.js @@ -4,13 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ +import { installIndexTemplate } from './server/lib/index_template'; import { PLUGIN } from './common/constants'; export function beats(kibana) { return new kibana.Plugin({ id: PLUGIN.ID, require: ['kibana', 'elasticsearch', 'xpack_main'], - init: function () { + init: async function (server) { + await installIndexTemplate(server); } }); } diff --git a/x-pack/plugins/beats/server/lib/client/call_with_internal_user_factory.js b/x-pack/plugins/beats/server/lib/client/call_with_internal_user_factory.js new file mode 100644 index 0000000000000..8b5dbed773430 --- /dev/null +++ b/x-pack/plugins/beats/server/lib/client/call_with_internal_user_factory.js @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { once } from 'lodash'; + +const callWithInternalUser = once((server) => { + const { callWithInternalUser } = server.plugins.elasticsearch.getCluster('admin'); + return callWithInternalUser; +}); + +export const callWithInternalUserFactory = (server) => { + return callWithInternalUser(server); +}; diff --git a/x-pack/plugins/beats/server/lib/client/index.js b/x-pack/plugins/beats/server/lib/client/index.js new file mode 100644 index 0000000000000..a56a50e2864a5 --- /dev/null +++ b/x-pack/plugins/beats/server/lib/client/index.js @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { callWithInternalUserFactory } from './call_with_internal_user_factory'; diff --git a/x-pack/plugins/beats/server/lib/index_template/beats_template.json b/x-pack/plugins/beats/server/lib/index_template/beats_template.json new file mode 100644 index 0000000000000..6ef57b9c549ed --- /dev/null +++ b/x-pack/plugins/beats/server/lib/index_template/beats_template.json @@ -0,0 +1,84 @@ +{ + "index_patterns": [ + ".management-beats" + ], + "version": 65000, + "settings": { + "index": { + "number_of_shards": 1, + "auto_expand_replicas": "0-1", + "codec": "best_compression" + } + }, + "mappings": { + "_doc": { + "dynamic": "strict", + "properties": { + "type": { + "type": "keyword" + }, + "enrollment_token": { + "properties": { + "token": { + "type": "keyword" + }, + "expires_on": { + "type": "date" + } + } + }, + "configuration_block": { + "properties": { + "tag": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "block_yml": { + "type": "text" + } + } + }, + "beat": { + "properties": { + "id": { + "type": "keyword" + }, + "enrollment_token": { + "type": "keyword" + }, + "access_token": { + "type": "keyword" + }, + "verified_on": { + "type": "date" + }, + "type": { + "type": "keyword" + }, + "host_ip": { + "type": "keyword" + }, + "host_name": { + "type": "keyword" + }, + "ephemeral_id": { + "type": "keyword" + }, + "local_configuration_yml": { + "type": "text" + }, + "central_configuration_yml": { + "type": "text" + }, + "metadata": { + "dynamic": "true", + "type": "object" + } + } + } + } + } + } +} diff --git a/x-pack/plugins/beats/server/lib/index_template/index.js b/x-pack/plugins/beats/server/lib/index_template/index.js new file mode 100644 index 0000000000000..04128e46ff0ea --- /dev/null +++ b/x-pack/plugins/beats/server/lib/index_template/index.js @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { installIndexTemplate } from './install_index_template'; diff --git a/x-pack/plugins/beats/server/lib/index_template/install_index_template.js b/x-pack/plugins/beats/server/lib/index_template/install_index_template.js new file mode 100644 index 0000000000000..01b080903ccac --- /dev/null +++ b/x-pack/plugins/beats/server/lib/index_template/install_index_template.js @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import beatsIndexTemplate from './beats_template'; +import { callWithInternalUserFactory } from '../client'; + +const TEMPLATE_NAME = 'beats-template'; + +export function installIndexTemplate(server) { + const callWithInternalUser = callWithInternalUserFactory(server); + return callWithInternalUser('indices.putTemplate', { + name: TEMPLATE_NAME, + body: beatsIndexTemplate + }); +} From dd6ac3c2d5103d436919f6c44b9ad0002cf5d88b Mon Sep 17 00:00:00 2001 From: Shaunak Kashyap Date: Tue, 15 May 2018 12:46:32 -0700 Subject: [PATCH 03/94] [Beats Management] APIs: Create enrollment tokens (#19018) * WIP checkin * Register API routes * Fixing typo in index name * Adding TODOs * Removing commented out license checking code that isn't yet implemented * Remove unnecessary async/await * Don't return until indices have been refreshed * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Adding TODO * Fixing variable name * Using a single index * Adding expiration date field * Adding test for expiration date field * Ignore non-existent index * Fixing logic in test * Creating constant for default enrollment tokens TTL value * Updating test --- .../plugins/beats/common/constants/index.js | 1 + .../beats/common/constants/index_names.js | 9 ++ x-pack/plugins/beats/index.js | 9 ++ .../call_with_request_factory.js | 19 +++ .../lib/call_with_request_factory/index.js | 7 ++ .../beats/server/lib/error_wrappers/index.js | 7 ++ .../lib/error_wrappers/wrap_es_error.js | 22 ++++ .../lib/error_wrappers/wrap_es_error.test.js | 40 +++++++ .../plugins/beats/server/routes/api/index.js | 11 ++ ...register_create_enrollment_tokens_route.js | 70 +++++++++++ .../apis/beats/create_enrollment_token.js | 109 ++++++++++++++++++ .../test/api_integration/apis/beats/index.js | 11 ++ x-pack/test/api_integration/apis/index.js | 1 + x-pack/test/api_integration/config.js | 1 + 14 files changed, 317 insertions(+) create mode 100644 x-pack/plugins/beats/common/constants/index_names.js create mode 100644 x-pack/plugins/beats/server/lib/call_with_request_factory/call_with_request_factory.js create mode 100644 x-pack/plugins/beats/server/lib/call_with_request_factory/index.js create mode 100644 x-pack/plugins/beats/server/lib/error_wrappers/index.js create mode 100644 x-pack/plugins/beats/server/lib/error_wrappers/wrap_es_error.js create mode 100644 x-pack/plugins/beats/server/lib/error_wrappers/wrap_es_error.test.js create mode 100644 x-pack/plugins/beats/server/routes/api/index.js create mode 100644 x-pack/plugins/beats/server/routes/api/register_create_enrollment_tokens_route.js create mode 100644 x-pack/test/api_integration/apis/beats/create_enrollment_token.js create mode 100644 x-pack/test/api_integration/apis/beats/index.js diff --git a/x-pack/plugins/beats/common/constants/index.js b/x-pack/plugins/beats/common/constants/index.js index 035454fd7b435..9fb8dffacad92 100644 --- a/x-pack/plugins/beats/common/constants/index.js +++ b/x-pack/plugins/beats/common/constants/index.js @@ -5,3 +5,4 @@ */ export { PLUGIN } from './plugin'; +export { INDEX_NAMES } from './index_names'; diff --git a/x-pack/plugins/beats/common/constants/index_names.js b/x-pack/plugins/beats/common/constants/index_names.js new file mode 100644 index 0000000000000..e63e8b08a6ef4 --- /dev/null +++ b/x-pack/plugins/beats/common/constants/index_names.js @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const INDEX_NAMES = { + BEATS: '.management-beats' +}; diff --git a/x-pack/plugins/beats/index.js b/x-pack/plugins/beats/index.js index 8839a30fc5a2e..c105813e36ff6 100644 --- a/x-pack/plugins/beats/index.js +++ b/x-pack/plugins/beats/index.js @@ -5,14 +5,23 @@ */ import { installIndexTemplate } from './server/lib/index_template'; +import { registerApiRoutes } from './server/routes/api'; import { PLUGIN } from './common/constants'; +const DEFAULT_ENROLLMENT_TOKENS_TTL_S = 10 * 60; // 10 minutes + export function beats(kibana) { return new kibana.Plugin({ id: PLUGIN.ID, require: ['kibana', 'elasticsearch', 'xpack_main'], + configPrefix: 'xpack.beats', + config: Joi => Joi.object({ + enabled: Joi.boolean().default(true), + enrollmentTokensTtlInSeconds: Joi.number().integer().min(1).default(DEFAULT_ENROLLMENT_TOKENS_TTL_S) + }).default(), init: async function (server) { await installIndexTemplate(server); + registerApiRoutes(server); } }); } diff --git a/x-pack/plugins/beats/server/lib/call_with_request_factory/call_with_request_factory.js b/x-pack/plugins/beats/server/lib/call_with_request_factory/call_with_request_factory.js new file mode 100644 index 0000000000000..0c4f909d12f61 --- /dev/null +++ b/x-pack/plugins/beats/server/lib/call_with_request_factory/call_with_request_factory.js @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { once } from 'lodash'; + +const callWithRequest = once((server) => { + const config = server.config().get('elasticsearch'); + const cluster = server.plugins.elasticsearch.createCluster('beats', config); + return cluster.callWithRequest; +}); + +export const callWithRequestFactory = (server, request) => { + return (...args) => { + return callWithRequest(server)(request, ...args); + }; +}; diff --git a/x-pack/plugins/beats/server/lib/call_with_request_factory/index.js b/x-pack/plugins/beats/server/lib/call_with_request_factory/index.js new file mode 100644 index 0000000000000..787814d87dff9 --- /dev/null +++ b/x-pack/plugins/beats/server/lib/call_with_request_factory/index.js @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { callWithRequestFactory } from './call_with_request_factory'; diff --git a/x-pack/plugins/beats/server/lib/error_wrappers/index.js b/x-pack/plugins/beats/server/lib/error_wrappers/index.js new file mode 100644 index 0000000000000..3756b0c74fb10 --- /dev/null +++ b/x-pack/plugins/beats/server/lib/error_wrappers/index.js @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { wrapEsError } from './wrap_es_error'; diff --git a/x-pack/plugins/beats/server/lib/error_wrappers/wrap_es_error.js b/x-pack/plugins/beats/server/lib/error_wrappers/wrap_es_error.js new file mode 100644 index 0000000000000..d2abcab5c37dd --- /dev/null +++ b/x-pack/plugins/beats/server/lib/error_wrappers/wrap_es_error.js @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Boom from 'boom'; + +/** + * Wraps ES errors into a Boom error response and returns it + * This also handles the permissions issue gracefully + * + * @param err Object ES error + * @return Object Boom error response + */ +export function wrapEsError(err) { + const statusCode = err.statusCode; + if (statusCode === 403) { + return Boom.forbidden('Insufficient user permissions for managing Beats configuration'); + } + return Boom.wrap(err, err.statusCode); +} diff --git a/x-pack/plugins/beats/server/lib/error_wrappers/wrap_es_error.test.js b/x-pack/plugins/beats/server/lib/error_wrappers/wrap_es_error.test.js new file mode 100644 index 0000000000000..ec7338262844a --- /dev/null +++ b/x-pack/plugins/beats/server/lib/error_wrappers/wrap_es_error.test.js @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { wrapEsError } from './wrap_es_error'; + +describe('wrap_es_error', () => { + describe('#wrapEsError', () => { + + let originalError; + beforeEach(() => { + originalError = new Error('I am an error'); + originalError.statusCode = 404; + }); + + it('should return a Boom object', () => { + const wrappedError = wrapEsError(originalError); + + expect(wrappedError.isBoom).to.be(true); + }); + + it('should return the correct Boom object', () => { + const wrappedError = wrapEsError(originalError); + + expect(wrappedError.output.statusCode).to.be(originalError.statusCode); + expect(wrappedError.output.payload.message).to.be(originalError.message); + }); + + it('should return invalid permissions message for 403 errors', () => { + const securityError = new Error('I am an error'); + securityError.statusCode = 403; + const wrappedError = wrapEsError(securityError); + + expect(wrappedError.isBoom).to.be(true); + expect(wrappedError.message).to.be('Insufficient user permissions for managing Logstash pipelines'); + }); + }); +}); diff --git a/x-pack/plugins/beats/server/routes/api/index.js b/x-pack/plugins/beats/server/routes/api/index.js new file mode 100644 index 0000000000000..8bf546045fe40 --- /dev/null +++ b/x-pack/plugins/beats/server/routes/api/index.js @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { registerCreateEnrollmentTokensRoute } from './register_create_enrollment_tokens_route'; + +export function registerApiRoutes(server) { + registerCreateEnrollmentTokensRoute(server); +} diff --git a/x-pack/plugins/beats/server/routes/api/register_create_enrollment_tokens_route.js b/x-pack/plugins/beats/server/routes/api/register_create_enrollment_tokens_route.js new file mode 100644 index 0000000000000..582ae59062d8b --- /dev/null +++ b/x-pack/plugins/beats/server/routes/api/register_create_enrollment_tokens_route.js @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Joi from 'joi'; +import uuid from 'uuid'; +import moment from 'moment'; +import { + get, + flatten +} from 'lodash'; +import { INDEX_NAMES } from '../../../common/constants'; +import { callWithRequestFactory } from '../../lib/call_with_request_factory'; +import { wrapEsError } from '../../lib/error_wrappers'; + +function persistTokens(callWithRequest, tokens, enrollmentTokensTtlInSeconds) { + const enrollmentTokenExpiration = moment().add(enrollmentTokensTtlInSeconds, 'seconds').toJSON(); + const body = flatten(tokens.map(token => [ + { index: { _id: `enrollment_token:${token}` } }, + { type: 'enrollment_token', enrollment_token: { token, expires_on: enrollmentTokenExpiration } } + ])); + + const params = { + index: INDEX_NAMES.BEATS, + type: '_doc', + body, + refresh: 'wait_for' + }; + + return callWithRequest('bulk', params); +} + +// TODO: add license check pre-hook +// TODO: write to Kibana audit log file +export function registerCreateEnrollmentTokensRoute(server) { + const DEFAULT_NUM_TOKENS = 1; + const enrollmentTokensTtlInSeconds = server.config().get('xpack.beats.enrollmentTokensTtlInSeconds'); + + server.route({ + method: 'POST', + path: '/api/beats/enrollment_tokens', + config: { + validate: { + payload: Joi.object({ + num_tokens: Joi.number().optional().default(DEFAULT_NUM_TOKENS).min(1) + }).allow(null) + } + }, + handler: async (request, reply) => { + const callWithRequest = callWithRequestFactory(server, request); + const numTokens = get(request, 'payload.num_tokens', DEFAULT_NUM_TOKENS); + + const tokens = []; + while (tokens.length < numTokens) { + tokens.push(uuid.v4().replace(/-/g, "")); + } + + try { + await persistTokens(callWithRequest, tokens, enrollmentTokensTtlInSeconds); + } catch (err) { + return reply(wrapEsError(err)); + } + + const response = { tokens }; + reply(response); + } + }); +} diff --git a/x-pack/test/api_integration/apis/beats/create_enrollment_token.js b/x-pack/test/api_integration/apis/beats/create_enrollment_token.js new file mode 100644 index 0000000000000..6b446c7bf40e1 --- /dev/null +++ b/x-pack/test/api_integration/apis/beats/create_enrollment_token.js @@ -0,0 +1,109 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from 'expect.js'; +import moment from 'moment'; + +export default function ({ getService }) { + const supertest = getService('supertest'); + const chance = getService('chance'); + const es = getService('es'); + + const ES_INDEX_NAME = '.management-beats'; + const ES_TYPE_NAME = '_doc'; + + describe('create_enrollment_token', () => { + const cleanup = () => { + return es.indices.delete({ + index: ES_INDEX_NAME, + ignore: [ 404 ] + }); + }; + + beforeEach(cleanup); + afterEach(cleanup); + + it('should create one token by default', async () => { + const { body: apiResponse } = await supertest + .post( + '/api/beats/enrollment_tokens' + ) + .set('kbn-xsrf', 'xxx') + .send() + .expect(200); + + const tokensFromApi = apiResponse.tokens; + + const esResponse = await es.search({ + index: ES_INDEX_NAME, + type: ES_TYPE_NAME, + q: 'type:enrollment_token' + }); + + const tokensInEs = esResponse.hits.hits + .map(hit => hit._source.enrollment_token.token); + + expect(tokensFromApi.length).to.eql(1); + expect(tokensFromApi).to.eql(tokensInEs); + }); + + it('should create the specified number of tokens', async () => { + const numTokens = chance.integer({ min: 1, max: 2000 }); + + const { body: apiResponse } = await supertest + .post( + '/api/beats/enrollment_tokens' + ) + .set('kbn-xsrf', 'xxx') + .send({ + num_tokens: numTokens + }) + .expect(200); + + const tokensFromApi = apiResponse.tokens; + + const esResponse = await es.search({ + index: ES_INDEX_NAME, + type: ES_TYPE_NAME, + q: 'type:enrollment_token', + size: numTokens + }); + + const tokensInEs = esResponse.hits.hits + .map(hit => hit._source.enrollment_token.token); + + expect(tokensFromApi.length).to.eql(numTokens); + expect(tokensFromApi).to.eql(tokensInEs); + }); + + it('should set token expiration to 10 minutes from now by default', async () => { + await supertest + .post( + '/api/beats/enrollment_tokens' + ) + .set('kbn-xsrf', 'xxx') + .send() + .expect(200); + + const esResponse = await es.search({ + index: ES_INDEX_NAME, + type: ES_TYPE_NAME, + q: 'type:enrollment_token' + }); + + const tokenInEs = esResponse.hits.hits[0]._source.enrollment_token; + + // We do a fuzzy check to see if the token expires between 9 and 10 minutes + // from now because a bit of time has elapsed been the creation of the + // tokens and this check. + const tokenExpiresOn = moment(tokenInEs.expires_on).valueOf(); + const tenMinutesFromNow = moment().add('10', 'minutes').valueOf(); + const almostTenMinutesFromNow = moment(tenMinutesFromNow).subtract('2', 'seconds').valueOf(); + expect(tokenExpiresOn).to.be.lessThan(tenMinutesFromNow); + expect(tokenExpiresOn).to.be.greaterThan(almostTenMinutesFromNow); + }); + }); +} diff --git a/x-pack/test/api_integration/apis/beats/index.js b/x-pack/test/api_integration/apis/beats/index.js new file mode 100644 index 0000000000000..570393383bb8d --- /dev/null +++ b/x-pack/test/api_integration/apis/beats/index.js @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export default function ({ loadTestFile }) { + describe('beats', () => { + loadTestFile(require.resolve('./create_enrollment_token')); + }); +} diff --git a/x-pack/test/api_integration/apis/index.js b/x-pack/test/api_integration/apis/index.js index 7f105650141d9..a6a252ac4efdd 100644 --- a/x-pack/test/api_integration/apis/index.js +++ b/x-pack/test/api_integration/apis/index.js @@ -11,5 +11,6 @@ export default function ({ loadTestFile }) { loadTestFile(require.resolve('./xpack_main')); loadTestFile(require.resolve('./logstash')); loadTestFile(require.resolve('./kibana')); + loadTestFile(require.resolve('./beats')); }); } diff --git a/x-pack/test/api_integration/config.js b/x-pack/test/api_integration/config.js index fffb5307cb0a6..879b108f9f8b0 100644 --- a/x-pack/test/api_integration/config.js +++ b/x-pack/test/api_integration/config.js @@ -29,6 +29,7 @@ export default async function ({ readConfigFile }) { esArchiver: kibanaCommonConfig.get('services.esArchiver'), usageAPI: UsageAPIProvider, kibanaServer: kibanaCommonConfig.get('services.kibanaServer'), + chance: kibanaAPITestsConfig.get('services.chance'), }, esArchiver: xPackFunctionalTestsConfig.get('esArchiver'), junit: { From 692e668d89077b16f4c4cea22833d67936c45f19 Mon Sep 17 00:00:00 2001 From: Shaunak Kashyap Date: Wed, 16 May 2018 05:59:41 -0700 Subject: [PATCH 04/94] Fixing name of test file (#19100) --- .../{create_enrollment_token.js => create_enrollment_tokens.js} | 2 +- x-pack/test/api_integration/apis/beats/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename x-pack/test/api_integration/apis/beats/{create_enrollment_token.js => create_enrollment_tokens.js} (98%) diff --git a/x-pack/test/api_integration/apis/beats/create_enrollment_token.js b/x-pack/test/api_integration/apis/beats/create_enrollment_tokens.js similarity index 98% rename from x-pack/test/api_integration/apis/beats/create_enrollment_token.js rename to x-pack/test/api_integration/apis/beats/create_enrollment_tokens.js index 6b446c7bf40e1..a12849d7a1c34 100644 --- a/x-pack/test/api_integration/apis/beats/create_enrollment_token.js +++ b/x-pack/test/api_integration/apis/beats/create_enrollment_tokens.js @@ -15,7 +15,7 @@ export default function ({ getService }) { const ES_INDEX_NAME = '.management-beats'; const ES_TYPE_NAME = '_doc'; - describe('create_enrollment_token', () => { + describe('create_enrollment_tokens', () => { const cleanup = () => { return es.indices.delete({ index: ES_INDEX_NAME, diff --git a/x-pack/test/api_integration/apis/beats/index.js b/x-pack/test/api_integration/apis/beats/index.js index 570393383bb8d..f8344895f02aa 100644 --- a/x-pack/test/api_integration/apis/beats/index.js +++ b/x-pack/test/api_integration/apis/beats/index.js @@ -6,6 +6,6 @@ export default function ({ loadTestFile }) { describe('beats', () => { - loadTestFile(require.resolve('./create_enrollment_token')); + loadTestFile(require.resolve('./create_enrollment_tokens')); }); } From 4676f3b267515b708949315cc993742b143e8850 Mon Sep 17 00:00:00 2001 From: Shaunak Kashyap Date: Wed, 16 May 2018 08:31:57 -0700 Subject: [PATCH 05/94] [Beats Management] APIs: Enroll beat (#19056) * WIP checkin * Add API integration test * Converting to Jest test * Create API for enrolling a beat * Handle invalid or expired enrollment tokens * Use create instead of index to prevent same beat from being enrolled twice * Adding unit test for duplicate beat enrollment * Do not persist enrollment token with beat once token has been checked and used * Fix datatype of host_ip field * Make Kibana API guess host IP instead of requiring it in payload * Fixing error introduced in rebase conflict resolution --- .../call_with_request_factory.js | 5 +- .../plugins/beats/server/lib/client/index.js | 1 + .../lib/index_template/beats_template.json | 5 +- .../plugins/beats/server/routes/api/index.js | 2 + ...register_create_enrollment_tokens_route.js | 2 +- .../routes/api/register_enroll_beat_route.js | 104 ++++++++++ .../api_integration/apis/beats/constants.js} | 4 +- .../apis/beats/create_enrollment_tokens.js | 19 +- .../api_integration/apis/beats/enroll_beat.js | 183 ++++++++++++++++++ .../test/api_integration/apis/beats/index.js | 14 +- 10 files changed, 315 insertions(+), 24 deletions(-) rename x-pack/plugins/beats/server/lib/{call_with_request_factory => client}/call_with_request_factory.js (73%) create mode 100644 x-pack/plugins/beats/server/routes/api/register_enroll_beat_route.js rename x-pack/{plugins/beats/server/lib/call_with_request_factory/index.js => test/api_integration/apis/beats/constants.js} (73%) create mode 100644 x-pack/test/api_integration/apis/beats/enroll_beat.js diff --git a/x-pack/plugins/beats/server/lib/call_with_request_factory/call_with_request_factory.js b/x-pack/plugins/beats/server/lib/client/call_with_request_factory.js similarity index 73% rename from x-pack/plugins/beats/server/lib/call_with_request_factory/call_with_request_factory.js rename to x-pack/plugins/beats/server/lib/client/call_with_request_factory.js index 0c4f909d12f61..c81670ed0cdec 100644 --- a/x-pack/plugins/beats/server/lib/call_with_request_factory/call_with_request_factory.js +++ b/x-pack/plugins/beats/server/lib/client/call_with_request_factory.js @@ -7,9 +7,8 @@ import { once } from 'lodash'; const callWithRequest = once((server) => { - const config = server.config().get('elasticsearch'); - const cluster = server.plugins.elasticsearch.createCluster('beats', config); - return cluster.callWithRequest; + const { callWithRequest } = server.plugins.elasticsearch.getCluster('admin'); + return callWithRequest; }); export const callWithRequestFactory = (server, request) => { diff --git a/x-pack/plugins/beats/server/lib/client/index.js b/x-pack/plugins/beats/server/lib/client/index.js index a56a50e2864a5..cdeee091cc66f 100644 --- a/x-pack/plugins/beats/server/lib/client/index.js +++ b/x-pack/plugins/beats/server/lib/client/index.js @@ -4,4 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ +export { callWithRequestFactory } from './call_with_request_factory'; export { callWithInternalUserFactory } from './call_with_internal_user_factory'; diff --git a/x-pack/plugins/beats/server/lib/index_template/beats_template.json b/x-pack/plugins/beats/server/lib/index_template/beats_template.json index 6ef57b9c549ed..9b37b7e816bf8 100644 --- a/x-pack/plugins/beats/server/lib/index_template/beats_template.json +++ b/x-pack/plugins/beats/server/lib/index_template/beats_template.json @@ -45,9 +45,6 @@ "id": { "type": "keyword" }, - "enrollment_token": { - "type": "keyword" - }, "access_token": { "type": "keyword" }, @@ -58,7 +55,7 @@ "type": "keyword" }, "host_ip": { - "type": "keyword" + "type": "ip" }, "host_name": { "type": "keyword" diff --git a/x-pack/plugins/beats/server/routes/api/index.js b/x-pack/plugins/beats/server/routes/api/index.js index 8bf546045fe40..07d923876ee79 100644 --- a/x-pack/plugins/beats/server/routes/api/index.js +++ b/x-pack/plugins/beats/server/routes/api/index.js @@ -5,7 +5,9 @@ */ import { registerCreateEnrollmentTokensRoute } from './register_create_enrollment_tokens_route'; +import { registerEnrollBeatRoute } from './register_enroll_beat_route'; export function registerApiRoutes(server) { registerCreateEnrollmentTokensRoute(server); + registerEnrollBeatRoute(server); } diff --git a/x-pack/plugins/beats/server/routes/api/register_create_enrollment_tokens_route.js b/x-pack/plugins/beats/server/routes/api/register_create_enrollment_tokens_route.js index 582ae59062d8b..87ae30cd0e532 100644 --- a/x-pack/plugins/beats/server/routes/api/register_create_enrollment_tokens_route.js +++ b/x-pack/plugins/beats/server/routes/api/register_create_enrollment_tokens_route.js @@ -12,7 +12,7 @@ import { flatten } from 'lodash'; import { INDEX_NAMES } from '../../../common/constants'; -import { callWithRequestFactory } from '../../lib/call_with_request_factory'; +import { callWithRequestFactory } from '../../lib/client'; import { wrapEsError } from '../../lib/error_wrappers'; function persistTokens(callWithRequest, tokens, enrollmentTokensTtlInSeconds) { diff --git a/x-pack/plugins/beats/server/routes/api/register_enroll_beat_route.js b/x-pack/plugins/beats/server/routes/api/register_enroll_beat_route.js new file mode 100644 index 0000000000000..fb004fbb79e12 --- /dev/null +++ b/x-pack/plugins/beats/server/routes/api/register_enroll_beat_route.js @@ -0,0 +1,104 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Joi from 'joi'; +import uuid from 'uuid'; +import moment from 'moment'; +import { + get, + omit +} from 'lodash'; +import { INDEX_NAMES } from '../../../common/constants'; +import { callWithInternalUserFactory } from '../../lib/client'; +import { wrapEsError } from '../../lib/error_wrappers'; + +async function getEnrollmentToken(callWithInternalUser, enrollmentToken) { + const params = { + index: INDEX_NAMES.BEATS, + type: '_doc', + id: `enrollment_token:${enrollmentToken}`, + ignore: [ 404 ] + }; + + const response = await callWithInternalUser('get', params); + return get(response, '_source.enrollment_token', {}); +} + +function deleteUsedEnrollmentToken(callWithInternalUser, enrollmentToken) { + const params = { + index: INDEX_NAMES.BEATS, + type: '_doc', + id: `enrollment_token:${enrollmentToken}` + }; + + return callWithInternalUser('delete', params); +} + +function persistBeat(callWithInternalUser, beat, beatId, accessToken, remoteAddress) { + const body = { + type: 'beat', + beat: { + ...omit(beat, 'enrollment_token'), + id: beatId, + access_token: accessToken, + host_ip: remoteAddress + } + }; + + const params = { + index: INDEX_NAMES.BEATS, + type: '_doc', + id: `beat:${beatId}`, + body, + refresh: 'wait_for' + }; + return callWithInternalUser('create', params); +} + +// TODO: add license check pre-hook +// TODO: write to Kibana audit log file +export function registerEnrollBeatRoute(server) { + server.route({ + method: 'POST', + path: '/api/beats/agent/{beatId}', + config: { + validate: { + payload: Joi.object({ + enrollment_token: Joi.string().required(), + type: Joi.string().required(), + host_name: Joi.string().required() + }).required() + }, + auth: false + }, + handler: async (request, reply) => { + const callWithInternalUser = callWithInternalUserFactory(server); + let accessToken; + + try { + const enrollmentToken = request.payload.enrollment_token; + const { token, expires_on: expiresOn } = await getEnrollmentToken(callWithInternalUser, enrollmentToken); + if (!token || token !== enrollmentToken) { + return reply({ message: 'Invalid enrollment token' }).code(400); + } + if (moment(expiresOn).isBefore(moment())) { + return reply({ message: 'Expired enrollment token' }).code(400); + } + + accessToken = uuid.v4().replace(/-/g, ""); + const remoteAddress = request.info.remoteAddress; + await persistBeat(callWithInternalUser, request.payload, request.params.beatId, accessToken, remoteAddress); + + await deleteUsedEnrollmentToken(callWithInternalUser, enrollmentToken); + } catch (err) { + return reply(wrapEsError(err)); + } + + const response = { access_token: accessToken }; + reply(response).code(201); + } + }); +} diff --git a/x-pack/plugins/beats/server/lib/call_with_request_factory/index.js b/x-pack/test/api_integration/apis/beats/constants.js similarity index 73% rename from x-pack/plugins/beats/server/lib/call_with_request_factory/index.js rename to x-pack/test/api_integration/apis/beats/constants.js index 787814d87dff9..00327aface171 100644 --- a/x-pack/plugins/beats/server/lib/call_with_request_factory/index.js +++ b/x-pack/test/api_integration/apis/beats/constants.js @@ -4,4 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -export { callWithRequestFactory } from './call_with_request_factory'; +export const ES_INDEX_NAME = '.management-beats'; +export const ES_TYPE_NAME = '_doc'; + diff --git a/x-pack/test/api_integration/apis/beats/create_enrollment_tokens.js b/x-pack/test/api_integration/apis/beats/create_enrollment_tokens.js index a12849d7a1c34..86b80323773b4 100644 --- a/x-pack/test/api_integration/apis/beats/create_enrollment_tokens.js +++ b/x-pack/test/api_integration/apis/beats/create_enrollment_tokens.js @@ -6,26 +6,17 @@ import expect from 'expect.js'; import moment from 'moment'; +import { + ES_INDEX_NAME, + ES_TYPE_NAME +} from './constants'; export default function ({ getService }) { const supertest = getService('supertest'); const chance = getService('chance'); const es = getService('es'); - const ES_INDEX_NAME = '.management-beats'; - const ES_TYPE_NAME = '_doc'; - - describe('create_enrollment_tokens', () => { - const cleanup = () => { - return es.indices.delete({ - index: ES_INDEX_NAME, - ignore: [ 404 ] - }); - }; - - beforeEach(cleanup); - afterEach(cleanup); - + describe('create_enrollment_token', () => { it('should create one token by default', async () => { const { body: apiResponse } = await supertest .post( diff --git a/x-pack/test/api_integration/apis/beats/enroll_beat.js b/x-pack/test/api_integration/apis/beats/enroll_beat.js new file mode 100644 index 0000000000000..ec3785f8eb35d --- /dev/null +++ b/x-pack/test/api_integration/apis/beats/enroll_beat.js @@ -0,0 +1,183 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from 'expect.js'; +import moment from 'moment'; +import { + ES_INDEX_NAME, + ES_TYPE_NAME +} from './constants'; + +export default function ({ getService }) { + const supertest = getService('supertest'); + const chance = getService('chance'); + const es = getService('es'); + + describe('enroll_beat', () => { + let validEnrollmentToken; + let beatId; + let beat; + + beforeEach(async () => { + validEnrollmentToken = chance.word(); + beatId = chance.word(); + beat = { + enrollment_token: validEnrollmentToken, + type: 'filebeat', + host_name: 'foo.bar.com', + }; + + await es.index({ + index: ES_INDEX_NAME, + type: ES_TYPE_NAME, + id: `enrollment_token:${validEnrollmentToken}`, + body: { + type: 'enrollment_token', + enrollment_token: { + token: validEnrollmentToken, + expires_on: moment().add(4, 'hours').toJSON() + } + } + }); + }); + + it('should enroll beat in an unverified state', async () => { + await supertest + .post( + `/api/beats/agent/${beatId}` + ) + .set('kbn-xsrf', 'xxx') + .send(beat) + .expect(201); + + const esResponse = await es.get({ + index: ES_INDEX_NAME, + type: ES_TYPE_NAME, + id: `beat:${beatId}` + }); + + expect(esResponse._source.beat).to.not.have.property('verified_on'); + expect(esResponse._source.beat).to.have.property('host_ip'); + }); + + it('should contain an access token in the response', async () => { + const { body: apiResponse } = await supertest + .post( + `/api/beats/agent/${beatId}` + ) + .set('kbn-xsrf', 'xxx') + .send(beat) + .expect(201); + + const accessTokenFromApi = apiResponse.access_token; + + const esResponse = await es.get({ + index: ES_INDEX_NAME, + type: ES_TYPE_NAME, + id: `beat:${beatId}` + }); + + const accessTokenInEs = esResponse._source.beat.access_token; + + expect(accessTokenFromApi.length).to.be.greaterThan(0); + expect(accessTokenFromApi).to.eql(accessTokenInEs); + }); + + it('should reject an invalid enrollment token', async () => { + const invalidEnrollmentToken = chance.word(); + beat.enrollment_token = invalidEnrollmentToken; + + const { body: apiResponse } = await supertest + .post( + `/api/beats/agent/${beatId}` + ) + .set('kbn-xsrf', 'xxx') + .send(beat) + .expect(400); + + expect(apiResponse).to.eql({ message: 'Invalid enrollment token' }); + }); + + it('should reject an expired enrollment token', async () => { + const expiredEnrollmentToken = chance.word(); + + await es.index({ + index: ES_INDEX_NAME, + type: ES_TYPE_NAME, + id: `enrollment_token:${expiredEnrollmentToken}`, + body: { + type: 'enrollment_token', + enrollment_token: { + token: expiredEnrollmentToken, + expires_on: moment().subtract(1, 'minute').toJSON() + } + } + }); + + beat.enrollment_token = expiredEnrollmentToken; + + const { body: apiResponse } = await supertest + .post( + `/api/beats/agent/${beatId}` + ) + .set('kbn-xsrf', 'xxx') + .send(beat) + .expect(400); + + expect(apiResponse).to.eql({ message: 'Expired enrollment token' }); + }); + + it('should delete the given enrollment token so it may not be reused', async () => { + await supertest + .post( + `/api/beats/agent/${beatId}` + ) + .set('kbn-xsrf', 'xxx') + .send(beat) + .expect(201); + + const esResponse = await es.get({ + index: ES_INDEX_NAME, + type: ES_TYPE_NAME, + id: `enrollment_token:${validEnrollmentToken}`, + ignore: [ 404 ] + }); + + expect(esResponse.found).to.be(false); + }); + + it('should fail if the beat with the same ID is enrolled twice', async () => { + await supertest + .post( + `/api/beats/agent/${beatId}` + ) + .set('kbn-xsrf', 'xxx') + .send(beat) + .expect(201); + + await es.index({ + index: ES_INDEX_NAME, + type: ES_TYPE_NAME, + id: `enrollment_token:${validEnrollmentToken}`, + body: { + type: 'enrollment_token', + enrollment_token: { + token: validEnrollmentToken, + expires_on: moment().add(4, 'hours').toJSON() + } + } + }); + + await supertest + .post( + `/api/beats/agent/${beatId}` + ) + .set('kbn-xsrf', 'xxx') + .send(beat) + .expect(409); + }); + }); +} diff --git a/x-pack/test/api_integration/apis/beats/index.js b/x-pack/test/api_integration/apis/beats/index.js index f8344895f02aa..dc6137f979019 100644 --- a/x-pack/test/api_integration/apis/beats/index.js +++ b/x-pack/test/api_integration/apis/beats/index.js @@ -4,8 +4,20 @@ * you may not use this file except in compliance with the Elastic License. */ -export default function ({ loadTestFile }) { +import { ES_INDEX_NAME } from './constants'; + +export default function ({ getService, loadTestFile }) { + const es = getService('es'); + describe('beats', () => { + const cleanup = () => es.indices.delete({ + index: ES_INDEX_NAME, + ignore: [ 404 ] + }); + + beforeEach(cleanup); + loadTestFile(require.resolve('./create_enrollment_tokens')); + loadTestFile(require.resolve('./enroll_beat')); }); } From 2a6ebda08b552ff970c6ea3cb7b481517065d563 Mon Sep 17 00:00:00 2001 From: Shaunak Kashyap Date: Wed, 16 May 2018 13:40:39 -0700 Subject: [PATCH 06/94] [Beats Management] APIs: List beats (#19086) * WIP checkin * Add API integration test * Converting to Jest test * WIP checkin * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Updating mapping --- .../plugins/beats/server/routes/api/index.js | 2 + .../routes/api/register_list_beats_route.js | 47 ++++++++++ .../test/api_integration/apis/beats/index.js | 1 + .../api_integration/apis/beats/list_beats.js | 46 ++++++++++ .../es_archives/beats/list/data.json.gz | Bin 0 -> 343 bytes .../es_archives/beats/list/mappings.json | 82 ++++++++++++++++++ 6 files changed, 178 insertions(+) create mode 100644 x-pack/plugins/beats/server/routes/api/register_list_beats_route.js create mode 100644 x-pack/test/api_integration/apis/beats/list_beats.js create mode 100644 x-pack/test/functional/es_archives/beats/list/data.json.gz create mode 100644 x-pack/test/functional/es_archives/beats/list/mappings.json diff --git a/x-pack/plugins/beats/server/routes/api/index.js b/x-pack/plugins/beats/server/routes/api/index.js index 07d923876ee79..76cedde5cdf3d 100644 --- a/x-pack/plugins/beats/server/routes/api/index.js +++ b/x-pack/plugins/beats/server/routes/api/index.js @@ -6,8 +6,10 @@ import { registerCreateEnrollmentTokensRoute } from './register_create_enrollment_tokens_route'; import { registerEnrollBeatRoute } from './register_enroll_beat_route'; +import { registerListBeatsRoute } from './register_list_beats_route'; export function registerApiRoutes(server) { registerCreateEnrollmentTokensRoute(server); registerEnrollBeatRoute(server); + registerListBeatsRoute(server); } diff --git a/x-pack/plugins/beats/server/routes/api/register_list_beats_route.js b/x-pack/plugins/beats/server/routes/api/register_list_beats_route.js new file mode 100644 index 0000000000000..b84210988978f --- /dev/null +++ b/x-pack/plugins/beats/server/routes/api/register_list_beats_route.js @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + get, + omit +} from "lodash"; +import { INDEX_NAMES } from "../../../common/constants"; +import { callWithRequestFactory } from '../../lib/client'; +import { wrapEsError } from "../../lib/error_wrappers"; + +async function getBeats(callWithRequest) { + const params = { + index: INDEX_NAMES.BEATS, + type: '_doc', + q: 'type:beat' + }; + + const response = await callWithRequest('search', params); + return get(response, 'hits.hits', []); +} + +// TODO: add license check pre-hook +export function registerListBeatsRoute(server) { + server.route({ + method: 'GET', + path: '/api/beats/agents', + handler: async (request, reply) => { + const callWithRequest = callWithRequestFactory(server, request); + let beats; + + try { + beats = await getBeats(callWithRequest); + } catch (err) { + return reply(wrapEsError(err)); + } + + const response = { + beats: beats.map(beat => omit(beat._source.beat, ['access_token'])) + }; + reply(response); + } + }); +} diff --git a/x-pack/test/api_integration/apis/beats/index.js b/x-pack/test/api_integration/apis/beats/index.js index dc6137f979019..6b3562863a2b7 100644 --- a/x-pack/test/api_integration/apis/beats/index.js +++ b/x-pack/test/api_integration/apis/beats/index.js @@ -19,5 +19,6 @@ export default function ({ getService, loadTestFile }) { loadTestFile(require.resolve('./create_enrollment_tokens')); loadTestFile(require.resolve('./enroll_beat')); + loadTestFile(require.resolve('./list_beats')); }); } diff --git a/x-pack/test/api_integration/apis/beats/list_beats.js b/x-pack/test/api_integration/apis/beats/list_beats.js new file mode 100644 index 0000000000000..dfd0dccf32cc0 --- /dev/null +++ b/x-pack/test/api_integration/apis/beats/list_beats.js @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from 'expect.js'; + +export default function ({ getService }) { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + + describe('list_beats', () => { + const archive = 'beats/list'; + + beforeEach('load beats archive', () => esArchiver.load(archive)); + afterEach('unload beats archive', () => esArchiver.unload(archive)); + + it('should return all beats', async () => { + const { body: apiResponse } = await supertest + .get( + '/api/beats/agents' + ) + .expect(200); + + const beatsFromApi = apiResponse.beats; + + expect(beatsFromApi.length).to.be(3); + expect(beatsFromApi.filter(beat => beat.hasOwnProperty('verified_on')).length).to.be(1); + expect(beatsFromApi.find(beat => beat.hasOwnProperty('verified_on')).id).to.be('foo'); + }); + + it('should not return access tokens', async () => { + const { body: apiResponse } = await supertest + .get( + '/api/beats/agents' + ) + .expect(200); + + const beatsFromApi = apiResponse.beats; + + expect(beatsFromApi.length).to.be(3); + expect(beatsFromApi.filter(beat => beat.hasOwnProperty('access_token')).length).to.be(0); + }); + }); +} diff --git a/x-pack/test/functional/es_archives/beats/list/data.json.gz b/x-pack/test/functional/es_archives/beats/list/data.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..c5bcfc6fb14f91eaed3dd09d5fc8ec099ff637a3 GIT binary patch literal 343 zcmV-d0jT~TiwFqyEc;pj17u-zVJ>QOZ*Bm^Qp;|GFc7@+6^L_V*QP zHQ*FTls3x07v~|UN_uIPUM%fAR-^GAqBu^5_YEdRoH%cjhXCwgy$#4=9LBM39qxmG zG|<8`HrNg;gD~_b`D{aZT@hR^AVF5VZTDBS_uI}+yJy~@yr|;KG^u8~s$Sz4?a00O zekkirpczR?M))_jh30Jco*3we_03#!PCErXfnY86eL477Yy+)9TCD+T7VT>oK~%$LJVEhr5();N$N~ZgA*o`$Ns?*m6b~Bm8#NW1`ztPjMHkW=f?( zpb?S+=UkaQl|ov9T3bL_{cF|Z4c)QoUtRPRb@`$*%Yi#bm5|&rr6EVlkyn&UqjNF$ p?y#$?8eQp6)|4`}qGH9wBa=lcicArm@~7pW`2>YU+a_rQ0061trn&$C literal 0 HcmV?d00001 diff --git a/x-pack/test/functional/es_archives/beats/list/mappings.json b/x-pack/test/functional/es_archives/beats/list/mappings.json new file mode 100644 index 0000000000000..92d89fb159733 --- /dev/null +++ b/x-pack/test/functional/es_archives/beats/list/mappings.json @@ -0,0 +1,82 @@ +{ + "type": "index", + "value": { + "index": ".management-beats", + "settings": { + "index": { + "codec": "best_compression", + "number_of_shards": "1", + "auto_expand_replicas": "0-1", + "number_of_replicas": "0" + } + }, + "mappings": { + "_doc": { + "dynamic": "strict", + "properties": { + "type": { + "type": "keyword" + }, + "enrollment_token": { + "properties": { + "token": { + "type": "keyword" + }, + "expires_on": { + "type": "date" + } + } + }, + "configuration_block": { + "properties": { + "tag": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "block_yml": { + "type": "text" + } + } + }, + "beat": { + "properties": { + "id": { + "type": "keyword" + }, + "access_token": { + "type": "keyword" + }, + "verified_on": { + "type": "date" + }, + "type": { + "type": "keyword" + }, + "host_ip": { + "type": "ip" + }, + "host_name": { + "type": "keyword" + }, + "ephemeral_id": { + "type": "keyword" + }, + "local_configuration_yml": { + "type": "text" + }, + "central_configuration_yml": { + "type": "text" + }, + "metadata": { + "dynamic": "true", + "type": "object" + } + } + } + } + } + } + } +} From ebe3fac7465d996258ca0068a0cd0e48e3038209 Mon Sep 17 00:00:00 2001 From: Shaunak Kashyap Date: Thu, 17 May 2018 09:18:48 -0700 Subject: [PATCH 07/94] [Beats Management] APIs: Verify beats (#19103) * WIP checkin * WIP checkin * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Creating POST /api/beats/agents/verify API * Refactoring: extracting out helper functions * Fleshing out remaining tests * Expanding TODO note so I won't forget :) * Fixing file name * Updating mapping * Moving TODO comment to right file * Rename determine* helper functions to find* --- .../plugins/beats/server/routes/api/index.js | 2 + .../routes/api/register_verify_beats_route.js | 143 ++++++++++++++++++ .../test/api_integration/apis/beats/index.js | 1 + .../apis/beats/verify_beats.js | 81 ++++++++++ .../es_archives/beats/list/data.json.gz | Bin 343 -> 371 bytes 5 files changed, 227 insertions(+) create mode 100644 x-pack/plugins/beats/server/routes/api/register_verify_beats_route.js create mode 100644 x-pack/test/api_integration/apis/beats/verify_beats.js diff --git a/x-pack/plugins/beats/server/routes/api/index.js b/x-pack/plugins/beats/server/routes/api/index.js index 76cedde5cdf3d..def322f0e94eb 100644 --- a/x-pack/plugins/beats/server/routes/api/index.js +++ b/x-pack/plugins/beats/server/routes/api/index.js @@ -7,9 +7,11 @@ import { registerCreateEnrollmentTokensRoute } from './register_create_enrollment_tokens_route'; import { registerEnrollBeatRoute } from './register_enroll_beat_route'; import { registerListBeatsRoute } from './register_list_beats_route'; +import { registerVerifyBeatsRoute } from './register_verify_beats_route'; export function registerApiRoutes(server) { registerCreateEnrollmentTokensRoute(server); registerEnrollBeatRoute(server); registerListBeatsRoute(server); + registerVerifyBeatsRoute(server); } diff --git a/x-pack/plugins/beats/server/routes/api/register_verify_beats_route.js b/x-pack/plugins/beats/server/routes/api/register_verify_beats_route.js new file mode 100644 index 0000000000000..11a4aff1204dc --- /dev/null +++ b/x-pack/plugins/beats/server/routes/api/register_verify_beats_route.js @@ -0,0 +1,143 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Joi from 'joi'; +import moment from 'moment'; +import { + get, + flatten +} from 'lodash'; +import { INDEX_NAMES } from '../../../common/constants'; +import { callWithRequestFactory } from '../../lib/client'; +import { wrapEsError } from '../../lib/error_wrappers'; + +async function getBeats(callWithRequest, beatIds) { + const ids = beatIds.map(beatId => `beat:${beatId}`); + const params = { + index: INDEX_NAMES.BEATS, + type: '_doc', + body: { ids }, + _sourceInclude: [ 'beat.id', 'beat.verified_on' ] + }; + + const response = await callWithRequest('mget', params); + return get(response, 'docs', []); +} + +async function verifyBeats(callWithRequest, beatIds) { + if (!Array.isArray(beatIds) || (beatIds.length === 0)) { + return []; + } + + const verifiedOn = moment().toJSON(); + const body = flatten(beatIds.map(beatId => [ + { update: { _id: `beat:${beatId}` } }, + { doc: { beat: { verified_on: verifiedOn } } } + ])); + + const params = { + index: INDEX_NAMES.BEATS, + type: '_doc', + body, + refresh: 'wait_for' + }; + + const response = await callWithRequest('bulk', params); + return get(response, 'items', []); +} + +function findNonExistentBeatIds(beatsFromEs, beatIdsFromRequest) { + return beatsFromEs.reduce((nonExistentBeatIds, beatFromEs, idx) => { + if (!beatFromEs.found) { + nonExistentBeatIds.push(beatIdsFromRequest[idx]); + } + return nonExistentBeatIds; + }, []); +} + +function findAlreadyVerifiedBeatIds(beatsFromEs) { + return beatsFromEs + .filter(beat => beat.found) + .filter(beat => beat._source.beat.hasOwnProperty('verified_on')) + .map(beat => beat._source.beat.id); +} + +function findToBeVerifiedBeatIds(beatsFromEs) { + return beatsFromEs + .filter(beat => beat.found) + .filter(beat => !beat._source.beat.hasOwnProperty('verified_on')) + .map(beat => beat._source.beat.id); +} + +function findVerifiedBeatIds(verifications, toBeVerifiedBeatIds) { + return verifications.reduce((verifiedBeatIds, verification, idx) => { + if (verification.update.status === 200) { + verifiedBeatIds.push(toBeVerifiedBeatIds[idx]); + } + return verifiedBeatIds; + }, []); +} + +// TODO: add license check pre-hook +// TODO: write to Kibana audit log file (include who did the verification as well) +export function registerVerifyBeatsRoute(server) { + server.route({ + method: 'POST', + path: '/api/beats/agents/verify', + config: { + validate: { + payload: Joi.object({ + beats: Joi.array({ + id: Joi.string().required() + }).min(1) + }).required() + } + }, + handler: async (request, reply) => { + const callWithRequest = callWithRequestFactory(server, request); + + const beats = [...request.payload.beats]; + const beatIds = beats.map(beat => beat.id); + + let nonExistentBeatIds; + let alreadyVerifiedBeatIds; + let verifiedBeatIds; + + try { + const beatsFromEs = await getBeats(callWithRequest, beatIds); + + nonExistentBeatIds = findNonExistentBeatIds(beatsFromEs, beatIds); + alreadyVerifiedBeatIds = findAlreadyVerifiedBeatIds(beatsFromEs); + const toBeVerifiedBeatIds = findToBeVerifiedBeatIds(beatsFromEs); + + const verifications = await verifyBeats(callWithRequest, toBeVerifiedBeatIds); + verifiedBeatIds = findVerifiedBeatIds(verifications, toBeVerifiedBeatIds); + + } catch (err) { + return reply(wrapEsError(err)); + } + + beats.forEach(beat => { + if (nonExistentBeatIds.includes(beat.id)) { + beat.status = 404; + beat.result = 'not found'; + } else if (alreadyVerifiedBeatIds.includes(beat.id)) { + beat.status = 200; + beat.result = 'already verified'; + } else if (verifiedBeatIds.includes(beat.id)) { + beat.status = 200; + beat.result = 'verified'; + } else { + beat.status = 400; + beat.result = 'not verified'; + } + }); + + const response = { beats }; + reply(response); + } + }); +} diff --git a/x-pack/test/api_integration/apis/beats/index.js b/x-pack/test/api_integration/apis/beats/index.js index 6b3562863a2b7..abb97b3daed91 100644 --- a/x-pack/test/api_integration/apis/beats/index.js +++ b/x-pack/test/api_integration/apis/beats/index.js @@ -20,5 +20,6 @@ export default function ({ getService, loadTestFile }) { loadTestFile(require.resolve('./create_enrollment_tokens')); loadTestFile(require.resolve('./enroll_beat')); loadTestFile(require.resolve('./list_beats')); + loadTestFile(require.resolve('./verify_beats')); }); } diff --git a/x-pack/test/api_integration/apis/beats/verify_beats.js b/x-pack/test/api_integration/apis/beats/verify_beats.js new file mode 100644 index 0000000000000..2b085308b43d1 --- /dev/null +++ b/x-pack/test/api_integration/apis/beats/verify_beats.js @@ -0,0 +1,81 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from 'expect.js'; + +export default function ({ getService }) { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + const chance = getService('chance'); + + describe('verify_beats', () => { + const archive = 'beats/list'; + + beforeEach('load beats archive', () => esArchiver.load(archive)); + afterEach('unload beats archive', () => esArchiver.unload(archive)); + + it('verify the given beats', async () => { + const { body: apiResponse } = await supertest + .post( + '/api/beats/agents/verify' + ) + .set('kbn-xsrf', 'xxx') + .send({ + beats: [ + { id: 'bar' }, + { id: 'baz' } + ] + }) + .expect(200); + + expect(apiResponse.beats).to.eql([ + { id: 'bar', status: 200, result: 'verified' }, + { id: 'baz', status: 200, result: 'verified' }, + ]); + }); + + it('should not re-verify already-verified beats', async () => { + const { body: apiResponse } = await supertest + .post( + '/api/beats/agents/verify' + ) + .set('kbn-xsrf', 'xxx') + .send({ + beats: [ + { id: 'foo' }, + { id: 'bar' } + ] + }) + .expect(200); + + expect(apiResponse.beats).to.eql([ + { id: 'foo', status: 200, result: 'already verified' }, + { id: 'bar', status: 200, result: 'verified' } + ]); + }); + + it('should return errors for non-existent beats', async () => { + const nonExistentBeatId = chance.word(); + const { body: apiResponse } = await supertest + .post( + '/api/beats/agents/verify' + ) + .set('kbn-xsrf', 'xxx') + .send({ + beats: [ + { id: 'bar' }, + { id: nonExistentBeatId } + ] + }) + .expect(200); + + expect(apiResponse.beats).to.eql([ + { id: 'bar', status: 200, result: 'verified' }, + { id: nonExistentBeatId, status: 404, result: 'not found' }, + ]); + }); + }); +} diff --git a/x-pack/test/functional/es_archives/beats/list/data.json.gz b/x-pack/test/functional/es_archives/beats/list/data.json.gz index c5bcfc6fb14f91eaed3dd09d5fc8ec099ff637a3..f3ccd2687455692022cc5fb6622b906cd0ec6293 100644 GIT binary patch literal 371 zcmV-(0gV11iwFp7ua17u-zVJ>QOZ*Bm^RKae8Fbuu(6^Qd1C6J^E-?7s!$VtqG zHb@7w>Q?pNM`$aU)^>+Y?In`!_pGO9JG&^3lm26cNggN8+vFi6Ht@C%ncWZ!VbwU? z1^}s{foH6-=@$l}??(8nLvd;mST1A&EPr2bPub3|TRZihaRc&*ijUERn&Hao4ZmTB z+Kcb{qFRMABPq!U|50tAKG3}<23lf$J;xl>PC~~dSc_d(^!^o_P}U%=)_}BhiW@4G zVtPp#liE53+$2ZpK03YoXdgwpo0x3i^Z!h)v2QDT#pZNyIU|e_e%b0l(PgVAxo53r zN~J=e5t0JuT$yDmg|q^-wt%v{tJT8}-O%bkZS*Ad{6=S%19y%pA-QEr!xAk=UQ#ZN zUWz$)gKbq-=n6klQ_9qWiUkvoOy;S`GevaDpYD7F?d^V=VKCzrTseU-n+xmTUYpA= RW6|eL{sI(FHM`CQ003wLvN`|& literal 343 zcmV-d0jT~TiwFqyEc;pj17u-zVJ>QOZ*Bm^Qp;|GFc7@+6^L_V*QP zHQ*FTls3x07v~|UN_uIPUM%fAR-^GAqBu^5_YEdRoH%cjhXCwgy$#4=9LBM39qxmG zG|<8`HrNg;gD~_b`D{aZT@hR^AVF5VZTDBS_uI}+yJy~@yr|;KG^u8~s$Sz4?a00O zekkirpczR?M))_jh30Jco*3we_03#!PCErXfnY86eL477Yy+)9TCD+T7VT>oK~%$LJVEhr5();N$N~ZgA*o`$Ns?*m6b~Bm8#NW1`ztPjMHkW=f?( zpb?S+=UkaQl|ov9T3bL_{cF|Z4c)QoUtRPRb@`$*%Yi#bm5|&rr6EVlkyn&UqjNF$ p?y#$?8eQp6)|4`}qGH9wBa=lcicArm@~7pW`2>YU+a_rQ0061trn&$C From 2cea7a6a21bb4ca5e705679f034b4eb57b361b82 Mon Sep 17 00:00:00 2001 From: Shaunak Kashyap Date: Fri, 18 May 2018 07:51:55 -0700 Subject: [PATCH 08/94] Fixing assertions (#19194) --- x-pack/test/api_integration/apis/beats/list_beats.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/test/api_integration/apis/beats/list_beats.js b/x-pack/test/api_integration/apis/beats/list_beats.js index dfd0dccf32cc0..c8e1c754b622c 100644 --- a/x-pack/test/api_integration/apis/beats/list_beats.js +++ b/x-pack/test/api_integration/apis/beats/list_beats.js @@ -25,7 +25,7 @@ export default function ({ getService }) { const beatsFromApi = apiResponse.beats; - expect(beatsFromApi.length).to.be(3); + expect(beatsFromApi.length).to.be(4); expect(beatsFromApi.filter(beat => beat.hasOwnProperty('verified_on')).length).to.be(1); expect(beatsFromApi.find(beat => beat.hasOwnProperty('verified_on')).id).to.be('foo'); }); @@ -39,7 +39,7 @@ export default function ({ getService }) { const beatsFromApi = apiResponse.beats; - expect(beatsFromApi.length).to.be(3); + expect(beatsFromApi.length).to.be(4); expect(beatsFromApi.filter(beat => beat.hasOwnProperty('access_token')).length).to.be(0); }); }); From 6e2c1a32cdb50cb42c311660eb33740f5621e856 Mon Sep 17 00:00:00 2001 From: Shaunak Kashyap Date: Mon, 21 May 2018 05:32:18 -0700 Subject: [PATCH 09/94] [Beats Management] APIs: Update beat (#19148) * WIP checkin * WIP checkin * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Creating POST /api/beats/agents/verify API * Refactoring: extracting out helper functions * Expanding TODO note so I won't forget :) * Fixing file name * Updating mapping * Add API tests * Update template to allow version field for beat * Implement PUT /api/beats/agent/{beat ID} API * Make enroll beat code consistent with update beat code * Fixing minor typo in TODO comment * Allow version in request payload * Make sure beat is not updated in ES in error scenarios * Adding version as required field in Enroll Beat API payload * Using destructuring * Fixing rename that was accidentally reversed in conflict fixing --- .../lib/index_template/beats_template.json | 3 + .../plugins/beats/server/routes/api/index.js | 2 + .../routes/api/register_enroll_beat_route.js | 20 +-- .../routes/api/register_update_beat_route.js | 98 +++++++++++++ .../routes/api/register_verify_beats_route.js | 2 +- .../api_integration/apis/beats/enroll_beat.js | 7 + .../test/api_integration/apis/beats/index.js | 1 + .../api_integration/apis/beats/update_beat.js | 130 ++++++++++++++++++ .../es_archives/beats/list/mappings.json | 3 + 9 files changed, 256 insertions(+), 10 deletions(-) create mode 100644 x-pack/plugins/beats/server/routes/api/register_update_beat_route.js create mode 100644 x-pack/test/api_integration/apis/beats/update_beat.js diff --git a/x-pack/plugins/beats/server/lib/index_template/beats_template.json b/x-pack/plugins/beats/server/lib/index_template/beats_template.json index 9b37b7e816bf8..e293845f9a3a8 100644 --- a/x-pack/plugins/beats/server/lib/index_template/beats_template.json +++ b/x-pack/plugins/beats/server/lib/index_template/beats_template.json @@ -54,6 +54,9 @@ "type": { "type": "keyword" }, + "version": { + "type": "keyword" + }, "host_ip": { "type": "ip" }, diff --git a/x-pack/plugins/beats/server/routes/api/index.js b/x-pack/plugins/beats/server/routes/api/index.js index def322f0e94eb..4e6ee318668bf 100644 --- a/x-pack/plugins/beats/server/routes/api/index.js +++ b/x-pack/plugins/beats/server/routes/api/index.js @@ -8,10 +8,12 @@ import { registerCreateEnrollmentTokensRoute } from './register_create_enrollmen import { registerEnrollBeatRoute } from './register_enroll_beat_route'; import { registerListBeatsRoute } from './register_list_beats_route'; import { registerVerifyBeatsRoute } from './register_verify_beats_route'; +import { registerUpdateBeatRoute } from './register_update_beat_route'; export function registerApiRoutes(server) { registerCreateEnrollmentTokensRoute(server); registerEnrollBeatRoute(server); registerListBeatsRoute(server); registerVerifyBeatsRoute(server); + registerUpdateBeatRoute(server); } diff --git a/x-pack/plugins/beats/server/routes/api/register_enroll_beat_route.js b/x-pack/plugins/beats/server/routes/api/register_enroll_beat_route.js index fb004fbb79e12..2a86f33b0d28f 100644 --- a/x-pack/plugins/beats/server/routes/api/register_enroll_beat_route.js +++ b/x-pack/plugins/beats/server/routes/api/register_enroll_beat_route.js @@ -37,21 +37,16 @@ function deleteUsedEnrollmentToken(callWithInternalUser, enrollmentToken) { return callWithInternalUser('delete', params); } -function persistBeat(callWithInternalUser, beat, beatId, accessToken, remoteAddress) { +function persistBeat(callWithInternalUser, beat) { const body = { type: 'beat', - beat: { - ...omit(beat, 'enrollment_token'), - id: beatId, - access_token: accessToken, - host_ip: remoteAddress - } + beat }; const params = { index: INDEX_NAMES.BEATS, type: '_doc', - id: `beat:${beatId}`, + id: `beat:${beat.id}`, body, refresh: 'wait_for' }; @@ -69,6 +64,7 @@ export function registerEnrollBeatRoute(server) { payload: Joi.object({ enrollment_token: Joi.string().required(), type: Joi.string().required(), + version: Joi.string().required(), host_name: Joi.string().required() }).required() }, @@ -76,6 +72,7 @@ export function registerEnrollBeatRoute(server) { }, handler: async (request, reply) => { const callWithInternalUser = callWithInternalUserFactory(server); + const { beatId } = request.params; let accessToken; try { @@ -90,7 +87,12 @@ export function registerEnrollBeatRoute(server) { accessToken = uuid.v4().replace(/-/g, ""); const remoteAddress = request.info.remoteAddress; - await persistBeat(callWithInternalUser, request.payload, request.params.beatId, accessToken, remoteAddress); + await persistBeat(callWithInternalUser, { + ...omit(request.payload, 'enrollment_token'), + id: beatId, + access_token: accessToken, + host_ip: remoteAddress + }); await deleteUsedEnrollmentToken(callWithInternalUser, enrollmentToken); } catch (err) { diff --git a/x-pack/plugins/beats/server/routes/api/register_update_beat_route.js b/x-pack/plugins/beats/server/routes/api/register_update_beat_route.js new file mode 100644 index 0000000000000..c93eca7590454 --- /dev/null +++ b/x-pack/plugins/beats/server/routes/api/register_update_beat_route.js @@ -0,0 +1,98 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Joi from 'joi'; +import { get } from 'lodash'; +import { INDEX_NAMES } from '../../../common/constants'; +import { callWithInternalUserFactory } from '../../lib/client'; +import { wrapEsError } from '../../lib/error_wrappers'; + +async function getBeat(callWithInternalUser, beatId) { + const params = { + index: INDEX_NAMES.BEATS, + type: '_doc', + id: `beat:${beatId}`, + ignore: [ 404 ] + }; + + const response = await callWithInternalUser('get', params); + if (!response.found) { + return null; + } + + return get(response, '_source.beat'); +} + +function persistBeat(callWithInternalUser, beat) { + const body = { + type: 'beat', + beat + }; + + const params = { + index: INDEX_NAMES.BEATS, + type: '_doc', + id: `beat:${beat.id}`, + body, + refresh: 'wait_for' + }; + return callWithInternalUser('index', params); +} + +// TODO: add license check pre-hook +// TODO: write to Kibana audit log file (include who did the verification as well) +export function registerUpdateBeatRoute(server) { + server.route({ + method: 'PUT', + path: '/api/beats/agent/{beatId}', + config: { + validate: { + payload: Joi.object({ + access_token: Joi.string().required(), + type: Joi.string(), + version: Joi.string(), + host_name: Joi.string(), + ephemeral_id: Joi.string(), + local_configuration_yml: Joi.string(), + metadata: Joi.object() + }).required() + }, + auth: false + }, + handler: async (request, reply) => { + const callWithInternalUser = callWithInternalUserFactory(server); + const { beatId } = request.params; + + try { + const beat = await getBeat(callWithInternalUser, beatId); + if (beat === null) { + return reply({ message: 'Beat not found' }).code(404); + } + + const isAccessTokenValid = beat.access_token === request.payload.access_token; + if (!isAccessTokenValid) { + return reply({ message: 'Invalid access token' }).code(401); + } + + const isBeatVerified = beat.hasOwnProperty('verified_on'); + if (!isBeatVerified) { + return reply({ message: 'Beat has not been verified' }).code(400); + } + + const remoteAddress = request.info.remoteAddress; + await persistBeat(callWithInternalUser, { + ...beat, + ...request.payload, + host_ip: remoteAddress + }); + } catch (err) { + return reply(wrapEsError(err)); + } + + reply().code(204); + } + }); +} diff --git a/x-pack/plugins/beats/server/routes/api/register_verify_beats_route.js b/x-pack/plugins/beats/server/routes/api/register_verify_beats_route.js index 11a4aff1204dc..b2113029224a5 100644 --- a/x-pack/plugins/beats/server/routes/api/register_verify_beats_route.js +++ b/x-pack/plugins/beats/server/routes/api/register_verify_beats_route.js @@ -82,7 +82,7 @@ function findVerifiedBeatIds(verifications, toBeVerifiedBeatIds) { } // TODO: add license check pre-hook -// TODO: write to Kibana audit log file (include who did the verification as well) +// TODO: write to Kibana audit log file export function registerVerifyBeatsRoute(server) { server.route({ method: 'POST', diff --git a/x-pack/test/api_integration/apis/beats/enroll_beat.js b/x-pack/test/api_integration/apis/beats/enroll_beat.js index ec3785f8eb35d..388987f6d6d22 100644 --- a/x-pack/test/api_integration/apis/beats/enroll_beat.js +++ b/x-pack/test/api_integration/apis/beats/enroll_beat.js @@ -24,10 +24,17 @@ export default function ({ getService }) { beforeEach(async () => { validEnrollmentToken = chance.word(); beatId = chance.word(); + const version = chance.integer({ min: 1, max: 10 }) + + '.' + + chance.integer({ min: 1, max: 10 }) + + '.' + + chance.integer({ min: 1, max: 10 }); + beat = { enrollment_token: validEnrollmentToken, type: 'filebeat', host_name: 'foo.bar.com', + version }; await es.index({ diff --git a/x-pack/test/api_integration/apis/beats/index.js b/x-pack/test/api_integration/apis/beats/index.js index abb97b3daed91..b41f17ed749b3 100644 --- a/x-pack/test/api_integration/apis/beats/index.js +++ b/x-pack/test/api_integration/apis/beats/index.js @@ -21,5 +21,6 @@ export default function ({ getService, loadTestFile }) { loadTestFile(require.resolve('./enroll_beat')); loadTestFile(require.resolve('./list_beats')); loadTestFile(require.resolve('./verify_beats')); + loadTestFile(require.resolve('./update_beat')); }); } diff --git a/x-pack/test/api_integration/apis/beats/update_beat.js b/x-pack/test/api_integration/apis/beats/update_beat.js new file mode 100644 index 0000000000000..09d286be0fcf8 --- /dev/null +++ b/x-pack/test/api_integration/apis/beats/update_beat.js @@ -0,0 +1,130 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from 'expect.js'; +import { + ES_INDEX_NAME, + ES_TYPE_NAME +} from './constants'; + +export default function ({ getService }) { + const supertest = getService('supertest'); + const chance = getService('chance'); + const es = getService('es'); + const esArchiver = getService('esArchiver'); + + describe('update_beat', () => { + let beat; + const archive = 'beats/list'; + + beforeEach('load beats archive', () => esArchiver.load(archive)); + beforeEach(() => { + const version = chance.integer({ min: 1, max: 10 }) + + '.' + + chance.integer({ min: 1, max: 10 }) + + '.' + + chance.integer({ min: 1, max: 10 }); + + beat = { + access_token: '93c4a4dd08564c189a7ec4e4f046b975', + type: `${chance.word()}beat`, + host_name: `www.${chance.word()}.net`, + version, + ephemeral_id: chance.word() + }; + }); + + afterEach('unload beats archive', () => esArchiver.unload(archive)); + + it('should update an existing verified beat', async () => { + const beatId = 'foo'; + await supertest + .put( + `/api/beats/agent/${beatId}` + ) + .set('kbn-xsrf', 'xxx') + .send(beat) + .expect(204); + + const beatInEs = await es.get({ + index: ES_INDEX_NAME, + type: ES_TYPE_NAME, + id: `beat:${beatId}` + }); + + expect(beatInEs._source.beat.id).to.be(beatId); + expect(beatInEs._source.beat.type).to.be(beat.type); + expect(beatInEs._source.beat.host_name).to.be(beat.host_name); + expect(beatInEs._source.beat.version).to.be(beat.version); + expect(beatInEs._source.beat.ephemeral_id).to.be(beat.ephemeral_id); + }); + + it('should return an error for an invalid access token', async () => { + const beatId = 'foo'; + beat.access_token = chance.word(); + const { body } = await supertest + .put( + `/api/beats/agent/${beatId}` + ) + .set('kbn-xsrf', 'xxx') + .send(beat) + .expect(401); + + expect(body.message).to.be('Invalid access token'); + + const beatInEs = await es.get({ + index: ES_INDEX_NAME, + type: ES_TYPE_NAME, + id: `beat:${beatId}` + }); + + expect(beatInEs._source.beat.id).to.be(beatId); + expect(beatInEs._source.beat.type).to.not.be(beat.type); + expect(beatInEs._source.beat.host_name).to.not.be(beat.host_name); + expect(beatInEs._source.beat.version).to.not.be(beat.version); + expect(beatInEs._source.beat.ephemeral_id).to.not.be(beat.ephemeral_id); + }); + + it('should return an error for an existing but unverified beat', async () => { + const beatId = 'bar'; + beat.access_token = '3c4a4dd08564c189a7ec4e4f046b9759'; + const { body } = await supertest + .put( + `/api/beats/agent/${beatId}` + ) + .set('kbn-xsrf', 'xxx') + .send(beat) + .expect(400); + + expect(body.message).to.be('Beat has not been verified'); + + const beatInEs = await es.get({ + index: ES_INDEX_NAME, + type: ES_TYPE_NAME, + id: `beat:${beatId}` + }); + + expect(beatInEs._source.beat.id).to.be(beatId); + expect(beatInEs._source.beat.type).to.not.be(beat.type); + expect(beatInEs._source.beat.host_name).to.not.be(beat.host_name); + expect(beatInEs._source.beat.version).to.not.be(beat.version); + expect(beatInEs._source.beat.ephemeral_id).to.not.be(beat.ephemeral_id); + }); + + it('should return an error for a non-existent beat', async () => { + const beatId = chance.word(); + const { body } = await supertest + .put( + `/api/beats/agent/${beatId}` + ) + .set('kbn-xsrf', 'xxx') + .send(beat) + .expect(404); + + expect(body.message).to.be('Beat not found'); + }); + }); +} diff --git a/x-pack/test/functional/es_archives/beats/list/mappings.json b/x-pack/test/functional/es_archives/beats/list/mappings.json index 92d89fb159733..24aceed8a5b61 100644 --- a/x-pack/test/functional/es_archives/beats/list/mappings.json +++ b/x-pack/test/functional/es_archives/beats/list/mappings.json @@ -54,6 +54,9 @@ "type": { "type": "keyword" }, + "version": { + "type": "keyword" + }, "host_ip": { "type": "ip" }, From 36962f0e01b17f662538d225cdc38b85ec2cf0c3 Mon Sep 17 00:00:00 2001 From: Shaunak Kashyap Date: Tue, 22 May 2018 10:51:08 -0700 Subject: [PATCH 10/94] [Beats Management] APIs: take auth tokens via headers (#19210) * WIP checkin * WIP checkin * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Creating POST /api/beats/agents/verify API * Refactoring: extracting out helper functions * Expanding TODO note so I won't forget :) * Fixing file name * Updating mapping * Fixing minor typo in TODO comment * Make "Enroll Beat" API take enrollment token via header instead of request body * Make "Update Beat" API take access token via header instead of request body --- .../routes/api/register_enroll_beat_route.js | 8 +++++--- .../routes/api/register_update_beat_route.js | 8 +++++--- .../routes/api/register_verify_beats_route.js | 16 ++++++++-------- .../api_integration/apis/beats/enroll_beat.js | 13 +++++++------ .../api_integration/apis/beats/update_beat.js | 7 ++++--- 5 files changed, 29 insertions(+), 23 deletions(-) diff --git a/x-pack/plugins/beats/server/routes/api/register_enroll_beat_route.js b/x-pack/plugins/beats/server/routes/api/register_enroll_beat_route.js index 2a86f33b0d28f..07e336a1e091b 100644 --- a/x-pack/plugins/beats/server/routes/api/register_enroll_beat_route.js +++ b/x-pack/plugins/beats/server/routes/api/register_enroll_beat_route.js @@ -62,11 +62,13 @@ export function registerEnrollBeatRoute(server) { config: { validate: { payload: Joi.object({ - enrollment_token: Joi.string().required(), type: Joi.string().required(), version: Joi.string().required(), host_name: Joi.string().required() - }).required() + }).required(), + headers: Joi.object({ + 'kbn-beats-enrollment-token': Joi.string().required() + }).options({ allowUnknown: true }) }, auth: false }, @@ -76,7 +78,7 @@ export function registerEnrollBeatRoute(server) { let accessToken; try { - const enrollmentToken = request.payload.enrollment_token; + const enrollmentToken = request.headers['kbn-beats-enrollment-token']; const { token, expires_on: expiresOn } = await getEnrollmentToken(callWithInternalUser, enrollmentToken); if (!token || token !== enrollmentToken) { return reply({ message: 'Invalid enrollment token' }).code(400); diff --git a/x-pack/plugins/beats/server/routes/api/register_update_beat_route.js b/x-pack/plugins/beats/server/routes/api/register_update_beat_route.js index c93eca7590454..fe615ffe1a11c 100644 --- a/x-pack/plugins/beats/server/routes/api/register_update_beat_route.js +++ b/x-pack/plugins/beats/server/routes/api/register_update_beat_route.js @@ -51,14 +51,16 @@ export function registerUpdateBeatRoute(server) { config: { validate: { payload: Joi.object({ - access_token: Joi.string().required(), type: Joi.string(), version: Joi.string(), host_name: Joi.string(), ephemeral_id: Joi.string(), local_configuration_yml: Joi.string(), metadata: Joi.object() - }).required() + }).required(), + headers: Joi.object({ + 'kbn-beats-access-token': Joi.string().required() + }).options({ allowUnknown: true }) }, auth: false }, @@ -72,7 +74,7 @@ export function registerUpdateBeatRoute(server) { return reply({ message: 'Beat not found' }).code(404); } - const isAccessTokenValid = beat.access_token === request.payload.access_token; + const isAccessTokenValid = beat.access_token === request.headers['kbn-beats-access-token']; if (!isAccessTokenValid) { return reply({ message: 'Invalid access token' }).code(401); } diff --git a/x-pack/plugins/beats/server/routes/api/register_verify_beats_route.js b/x-pack/plugins/beats/server/routes/api/register_verify_beats_route.js index b2113029224a5..6aaa61b07c5f8 100644 --- a/x-pack/plugins/beats/server/routes/api/register_verify_beats_route.js +++ b/x-pack/plugins/beats/server/routes/api/register_verify_beats_route.js @@ -49,7 +49,7 @@ async function verifyBeats(callWithRequest, beatIds) { return get(response, 'items', []); } -function findNonExistentBeatIds(beatsFromEs, beatIdsFromRequest) { +function determineNonExistentBeatIds(beatsFromEs, beatIdsFromRequest) { return beatsFromEs.reduce((nonExistentBeatIds, beatFromEs, idx) => { if (!beatFromEs.found) { nonExistentBeatIds.push(beatIdsFromRequest[idx]); @@ -58,21 +58,21 @@ function findNonExistentBeatIds(beatsFromEs, beatIdsFromRequest) { }, []); } -function findAlreadyVerifiedBeatIds(beatsFromEs) { +function determineAlreadyVerifiedBeatIds(beatsFromEs) { return beatsFromEs .filter(beat => beat.found) .filter(beat => beat._source.beat.hasOwnProperty('verified_on')) .map(beat => beat._source.beat.id); } -function findToBeVerifiedBeatIds(beatsFromEs) { +function determineToBeVerifiedBeatIds(beatsFromEs) { return beatsFromEs .filter(beat => beat.found) .filter(beat => !beat._source.beat.hasOwnProperty('verified_on')) .map(beat => beat._source.beat.id); } -function findVerifiedBeatIds(verifications, toBeVerifiedBeatIds) { +function determineVerifiedBeatIds(verifications, toBeVerifiedBeatIds) { return verifications.reduce((verifiedBeatIds, verification, idx) => { if (verification.update.status === 200) { verifiedBeatIds.push(toBeVerifiedBeatIds[idx]); @@ -109,12 +109,12 @@ export function registerVerifyBeatsRoute(server) { try { const beatsFromEs = await getBeats(callWithRequest, beatIds); - nonExistentBeatIds = findNonExistentBeatIds(beatsFromEs, beatIds); - alreadyVerifiedBeatIds = findAlreadyVerifiedBeatIds(beatsFromEs); - const toBeVerifiedBeatIds = findToBeVerifiedBeatIds(beatsFromEs); + nonExistentBeatIds = determineNonExistentBeatIds(beatsFromEs, beatIds); + alreadyVerifiedBeatIds = determineAlreadyVerifiedBeatIds(beatsFromEs); + const toBeVerifiedBeatIds = determineToBeVerifiedBeatIds(beatsFromEs); const verifications = await verifyBeats(callWithRequest, toBeVerifiedBeatIds); - verifiedBeatIds = findVerifiedBeatIds(verifications, toBeVerifiedBeatIds); + verifiedBeatIds = determineVerifiedBeatIds(verifications, toBeVerifiedBeatIds); } catch (err) { return reply(wrapEsError(err)); diff --git a/x-pack/test/api_integration/apis/beats/enroll_beat.js b/x-pack/test/api_integration/apis/beats/enroll_beat.js index 388987f6d6d22..91317bca976ee 100644 --- a/x-pack/test/api_integration/apis/beats/enroll_beat.js +++ b/x-pack/test/api_integration/apis/beats/enroll_beat.js @@ -31,7 +31,6 @@ export default function ({ getService }) { + chance.integer({ min: 1, max: 10 }); beat = { - enrollment_token: validEnrollmentToken, type: 'filebeat', host_name: 'foo.bar.com', version @@ -57,6 +56,7 @@ export default function ({ getService }) { `/api/beats/agent/${beatId}` ) .set('kbn-xsrf', 'xxx') + .set('kbn-beats-enrollment-token', validEnrollmentToken) .send(beat) .expect(201); @@ -76,6 +76,7 @@ export default function ({ getService }) { `/api/beats/agent/${beatId}` ) .set('kbn-xsrf', 'xxx') + .set('kbn-beats-enrollment-token', validEnrollmentToken) .send(beat) .expect(201); @@ -94,14 +95,12 @@ export default function ({ getService }) { }); it('should reject an invalid enrollment token', async () => { - const invalidEnrollmentToken = chance.word(); - beat.enrollment_token = invalidEnrollmentToken; - const { body: apiResponse } = await supertest .post( `/api/beats/agent/${beatId}` ) .set('kbn-xsrf', 'xxx') + .set('kbn-beats-enrollment-token', chance.word()) .send(beat) .expect(400); @@ -124,13 +123,12 @@ export default function ({ getService }) { } }); - beat.enrollment_token = expiredEnrollmentToken; - const { body: apiResponse } = await supertest .post( `/api/beats/agent/${beatId}` ) .set('kbn-xsrf', 'xxx') + .set('kbn-beats-enrollment-token', expiredEnrollmentToken) .send(beat) .expect(400); @@ -143,6 +141,7 @@ export default function ({ getService }) { `/api/beats/agent/${beatId}` ) .set('kbn-xsrf', 'xxx') + .set('kbn-beats-enrollment-token', validEnrollmentToken) .send(beat) .expect(201); @@ -162,6 +161,7 @@ export default function ({ getService }) { `/api/beats/agent/${beatId}` ) .set('kbn-xsrf', 'xxx') + .set('kbn-beats-enrollment-token', validEnrollmentToken) .send(beat) .expect(201); @@ -183,6 +183,7 @@ export default function ({ getService }) { `/api/beats/agent/${beatId}` ) .set('kbn-xsrf', 'xxx') + .set('kbn-beats-enrollment-token', validEnrollmentToken) .send(beat) .expect(409); }); diff --git a/x-pack/test/api_integration/apis/beats/update_beat.js b/x-pack/test/api_integration/apis/beats/update_beat.js index 09d286be0fcf8..92e5771e0ef4b 100644 --- a/x-pack/test/api_integration/apis/beats/update_beat.js +++ b/x-pack/test/api_integration/apis/beats/update_beat.js @@ -29,7 +29,6 @@ export default function ({ getService }) { + chance.integer({ min: 1, max: 10 }); beat = { - access_token: '93c4a4dd08564c189a7ec4e4f046b975', type: `${chance.word()}beat`, host_name: `www.${chance.word()}.net`, version, @@ -46,6 +45,7 @@ export default function ({ getService }) { `/api/beats/agent/${beatId}` ) .set('kbn-xsrf', 'xxx') + .set('kbn-beats-access-token', '93c4a4dd08564c189a7ec4e4f046b975') .send(beat) .expect(204); @@ -64,12 +64,12 @@ export default function ({ getService }) { it('should return an error for an invalid access token', async () => { const beatId = 'foo'; - beat.access_token = chance.word(); const { body } = await supertest .put( `/api/beats/agent/${beatId}` ) .set('kbn-xsrf', 'xxx') + .set('kbn-beats-access-token', chance.word()) .send(beat) .expect(401); @@ -90,12 +90,12 @@ export default function ({ getService }) { it('should return an error for an existing but unverified beat', async () => { const beatId = 'bar'; - beat.access_token = '3c4a4dd08564c189a7ec4e4f046b9759'; const { body } = await supertest .put( `/api/beats/agent/${beatId}` ) .set('kbn-xsrf', 'xxx') + .set('kbn-beats-access-token', '3c4a4dd08564c189a7ec4e4f046b9759') .send(beat) .expect(400); @@ -121,6 +121,7 @@ export default function ({ getService }) { `/api/beats/agent/${beatId}` ) .set('kbn-xsrf', 'xxx') + .set('kbn-beats-access-token', chance.word()) .send(beat) .expect(404); From 07f78be7d2e0f374c1f0f36df21b1a8db8637ea8 Mon Sep 17 00:00:00 2001 From: Shaunak Kashyap Date: Tue, 22 May 2018 12:49:11 -0700 Subject: [PATCH 11/94] [Beats Management] APIs: Create configuration block (#19270) * WIP checkin * WIP checkin * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Creating POST /api/beats/agents/verify API * Refactoring: extracting out helper functions * Expanding TODO note so I won't forget :) * Fixing file name * Updating mapping * Fixing minor typo in TODO comment * Implementing POST /api/beats/configuration_blocks API * Removing unnecessary escaping * Fleshing out types + adding validation for them * Making output singular (was outputs) * Removing metricbeat.inputs --- .../common/constants/configuration_blocks.js | 19 +++ .../plugins/beats/common/constants/index.js | 1 + .../plugins/beats/server/routes/api/index.js | 2 + ...gister_create_configuration_block_route.js | 105 ++++++++++++ .../apis/beats/create_configuration_block.js | 149 ++++++++++++++++++ .../test/api_integration/apis/beats/index.js | 1 + 6 files changed, 277 insertions(+) create mode 100644 x-pack/plugins/beats/common/constants/configuration_blocks.js create mode 100644 x-pack/plugins/beats/server/routes/api/register_create_configuration_block_route.js create mode 100644 x-pack/test/api_integration/apis/beats/create_configuration_block.js diff --git a/x-pack/plugins/beats/common/constants/configuration_blocks.js b/x-pack/plugins/beats/common/constants/configuration_blocks.js new file mode 100644 index 0000000000000..1818b75335f3a --- /dev/null +++ b/x-pack/plugins/beats/common/constants/configuration_blocks.js @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const CONFIGURATION_BLOCKS = { + TYPES: { + OUTPUT: 'output', + PROCESSORS: 'processors', + FILEBEAT_INPUTS: 'filebeat.inputs', + FILEBEAT_MODULES: 'filebeat.modules', + METRICBEAT_MODULES: 'metricbeat.modules' + } +}; + +CONFIGURATION_BLOCKS.UNIQUENESS_ENFORCING_TYPES = [ + CONFIGURATION_BLOCKS.TYPES.OUTPUT +]; diff --git a/x-pack/plugins/beats/common/constants/index.js b/x-pack/plugins/beats/common/constants/index.js index 9fb8dffacad92..77c41be579c33 100644 --- a/x-pack/plugins/beats/common/constants/index.js +++ b/x-pack/plugins/beats/common/constants/index.js @@ -6,3 +6,4 @@ export { PLUGIN } from './plugin'; export { INDEX_NAMES } from './index_names'; +export { CONFIGURATION_BLOCKS } from './configuration_blocks'; diff --git a/x-pack/plugins/beats/server/routes/api/index.js b/x-pack/plugins/beats/server/routes/api/index.js index 4e6ee318668bf..3b0c96834aa3a 100644 --- a/x-pack/plugins/beats/server/routes/api/index.js +++ b/x-pack/plugins/beats/server/routes/api/index.js @@ -9,6 +9,7 @@ import { registerEnrollBeatRoute } from './register_enroll_beat_route'; import { registerListBeatsRoute } from './register_list_beats_route'; import { registerVerifyBeatsRoute } from './register_verify_beats_route'; import { registerUpdateBeatRoute } from './register_update_beat_route'; +import { registerCreateConfigurationBlockRoute } from './register_create_configuration_block_route'; export function registerApiRoutes(server) { registerCreateEnrollmentTokensRoute(server); @@ -16,4 +17,5 @@ export function registerApiRoutes(server) { registerListBeatsRoute(server); registerVerifyBeatsRoute(server); registerUpdateBeatRoute(server); + registerCreateConfigurationBlockRoute(server); } diff --git a/x-pack/plugins/beats/server/routes/api/register_create_configuration_block_route.js b/x-pack/plugins/beats/server/routes/api/register_create_configuration_block_route.js new file mode 100644 index 0000000000000..d259b705cf747 --- /dev/null +++ b/x-pack/plugins/beats/server/routes/api/register_create_configuration_block_route.js @@ -0,0 +1,105 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Joi from 'joi'; +import uuid from 'uuid'; +import { get } from 'lodash'; +import { + INDEX_NAMES, + CONFIGURATION_BLOCKS +} from '../../../common/constants'; +import { callWithRequestFactory } from '../../lib/client'; +import { wrapEsError } from '../../lib/error_wrappers'; + +async function getConfigurationBlocksForTag(callWithRequest, tag) { + const params = { + index: INDEX_NAMES.BEATS, + type: '_doc', + q: `type:configuration_block AND configuration_block.tag:${tag}`, + size: 10000, + ignore: [ 404 ] + }; + + const response = await callWithRequest('search', params); + return get(response, 'hits.hits', []).map(hit => hit._source.configuration_block); +} + +function validateUniquenessEnforcingTypes(configurationBlocks, configurationBlockBeingValidated) { + const { type, tag } = configurationBlockBeingValidated; + // If the configuration block being validated is not of a uniqueness-enforcing type, then + // we don't need to perform any further validation checks. + if (!CONFIGURATION_BLOCKS.UNIQUENESS_ENFORCING_TYPES.includes(type)) { + return { isValid: true }; + } + + const isValid = !configurationBlocks.map(block => block.type).includes(type); + return { + isValid, + message: isValid + ? null + : `Configuration block for tag = ${tag} and type = ${type} already exists` + }; +} + +async function validateConfigurationBlock(callWithRequest, configurationBlockBeingValidated) { + const configurationBlocks = await getConfigurationBlocksForTag(callWithRequest, configurationBlockBeingValidated.tag); + return validateUniquenessEnforcingTypes(configurationBlocks, configurationBlockBeingValidated); +} + +function persistConfigurationBlock(callWithRequest, configurationBlock, configurationBlockId) { + const body = { + type: 'configuration_block', + configuration_block: configurationBlock + }; + + const params = { + index: INDEX_NAMES.BEATS, + type: '_doc', + id: `configuration_block:${configurationBlockId}`, + body, + refresh: 'wait_for' + }; + + return callWithRequest('create', params); +} + +// TODO: add license check pre-hook +// TODO: write to Kibana audit log file +export function registerCreateConfigurationBlockRoute(server) { + server.route({ + method: 'POST', + path: '/api/beats/configuration_blocks', + config: { + validate: { + payload: Joi.object({ + type: Joi.string().required().valid(Object.values(CONFIGURATION_BLOCKS.TYPES)), + tag: Joi.string().required(), + block_yml: Joi.string().required() + }).required() + } + }, + handler: async (request, reply) => { + const callWithRequest = callWithRequestFactory(server, request); + + let configurationBlockId; + try { + const configurationBlock = request.payload; + const { isValid, message } = await validateConfigurationBlock(callWithRequest, configurationBlock); + if (!isValid) { + return reply({ message }).code(400); + } + + configurationBlockId = uuid.v4(); + await persistConfigurationBlock(callWithRequest, request.payload, configurationBlockId); + } catch (err) { + return reply(wrapEsError(err)); + } + + const response = { id: configurationBlockId }; + reply(response).code(201); + } + }); +} diff --git a/x-pack/test/api_integration/apis/beats/create_configuration_block.js b/x-pack/test/api_integration/apis/beats/create_configuration_block.js new file mode 100644 index 0000000000000..680ff8b2a6d21 --- /dev/null +++ b/x-pack/test/api_integration/apis/beats/create_configuration_block.js @@ -0,0 +1,149 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from 'expect.js'; +import { + ES_INDEX_NAME, + ES_TYPE_NAME +} from './constants'; + +export default function ({ getService }) { + const supertest = getService('supertest'); + const es = getService('es'); + const chance = getService('chance'); + + describe('create_configuration_block', () => { + it('should create the given configuration block', async () => { + const configurationBlock = { + type: 'output', + tag: 'production', + block_yml: 'elasticsearch:\n hosts: [\"localhost:9200\"]\n username: "..."' + }; + const { body: apiResponse } = await supertest + .post( + '/api/beats/configuration_blocks' + ) + .set('kbn-xsrf', 'xxx') + .send(configurationBlock) + .expect(201); + + const idFromApi = apiResponse.id; + + const esResponse = await es.get({ + index: ES_INDEX_NAME, + type: ES_TYPE_NAME, + id: `configuration_block:${idFromApi}` + }); + + const docInEs = esResponse._source; + + expect(docInEs.type).to.eql('configuration_block'); + expect(docInEs.configuration_block.type).to.eql(configurationBlock.type); + expect(docInEs.configuration_block.tag).to.eql(configurationBlock.tag); + expect(docInEs.configuration_block.block_yml).to.eql(configurationBlock.block_yml); + }); + + it('should not allow two "output" type configuration blocks with the same tag', async () => { + const firstConfigurationBlock = { + type: 'output', + tag: 'production', + block_yml: 'elasticsearch:\n hosts: ["localhost:9200"]\n username: "..."' + }; + await supertest + .post( + '/api/beats/configuration_blocks' + ) + .set('kbn-xsrf', 'xxx') + .send(firstConfigurationBlock) + .expect(201); + + const secondConfigurationBlock = { + type: 'output', + tag: 'production', + block_yml: 'logstash:\n hosts: ["localhost:9000"]\n' + }; + await supertest + .post( + '/api/beats/configuration_blocks' + ) + .set('kbn-xsrf', 'xxx') + .send(secondConfigurationBlock) + .expect(400); + }); + + it('should allow two "output" type configuration blocks with different tags', async () => { + const firstConfigurationBlock = { + type: 'output', + tag: 'production', + block_yml: 'elasticsearch:\n hosts: ["localhost:9200"]\n username: "..."' + }; + await supertest + .post( + '/api/beats/configuration_blocks' + ) + .set('kbn-xsrf', 'xxx') + .send(firstConfigurationBlock) + .expect(201); + + const secondConfigurationBlock = { + type: 'output', + tag: 'development', + block_yml: 'logstash:\n hosts: ["localhost:9000"]\n' + }; + await supertest + .post( + '/api/beats/configuration_blocks' + ) + .set('kbn-xsrf', 'xxx') + .send(secondConfigurationBlock) + .expect(201); + }); + + it('should allow two configuration blocks of different types with the same tag', async () => { + const firstConfigurationBlock = { + type: 'output', + tag: 'production', + block_yml: 'elasticsearch:\n hosts: ["localhost:9200"]\n username: "..."' + }; + await supertest + .post( + '/api/beats/configuration_blocks' + ) + .set('kbn-xsrf', 'xxx') + .send(firstConfigurationBlock) + .expect(201); + + const secondConfigurationBlock = { + type: 'filebeat.inputs', + tag: 'production', + block_yml: 'file:\n path: "/var/log/some.log"]\n' + }; + await supertest + .post( + '/api/beats/configuration_blocks' + ) + .set('kbn-xsrf', 'xxx') + .send(secondConfigurationBlock) + .expect(201); + }); + + + it('should reject a configuration block with an invalid type', async () => { + const firstConfigurationBlock = { + type: chance.word(), + tag: 'production', + block_yml: 'elasticsearch:\n hosts: ["localhost:9200"]\n username: "..."' + }; + await supertest + .post( + '/api/beats/configuration_blocks' + ) + .set('kbn-xsrf', 'xxx') + .send(firstConfigurationBlock) + .expect(400); + }); + }); +} diff --git a/x-pack/test/api_integration/apis/beats/index.js b/x-pack/test/api_integration/apis/beats/index.js index b41f17ed749b3..da94353aafeee 100644 --- a/x-pack/test/api_integration/apis/beats/index.js +++ b/x-pack/test/api_integration/apis/beats/index.js @@ -22,5 +22,6 @@ export default function ({ getService, loadTestFile }) { loadTestFile(require.resolve('./list_beats')); loadTestFile(require.resolve('./verify_beats')); loadTestFile(require.resolve('./update_beat')); + loadTestFile(require.resolve('./create_configuration_block')); }); } From 1bcf5ba9ac7974e481f73245bc528e9eab2ab733 Mon Sep 17 00:00:00 2001 From: Shaunak Kashyap Date: Wed, 23 May 2018 10:47:35 -0700 Subject: [PATCH 12/94] Revert implementation of `POST /api/beats/configuration_blocks` API (#19340) This API allowed the user to operate at a level of abstraction that is unnecessarily and dangerously too low. A better API would be at one level higher, where users can create, update, and delete tags (where a tag can contain multiple configuration blocks). --- .../plugins/beats/server/routes/api/index.js | 2 - ...gister_create_configuration_block_route.js | 105 ------------ .../apis/beats/create_configuration_block.js | 149 ------------------ .../test/api_integration/apis/beats/index.js | 1 - 4 files changed, 257 deletions(-) delete mode 100644 x-pack/plugins/beats/server/routes/api/register_create_configuration_block_route.js delete mode 100644 x-pack/test/api_integration/apis/beats/create_configuration_block.js diff --git a/x-pack/plugins/beats/server/routes/api/index.js b/x-pack/plugins/beats/server/routes/api/index.js index 3b0c96834aa3a..4e6ee318668bf 100644 --- a/x-pack/plugins/beats/server/routes/api/index.js +++ b/x-pack/plugins/beats/server/routes/api/index.js @@ -9,7 +9,6 @@ import { registerEnrollBeatRoute } from './register_enroll_beat_route'; import { registerListBeatsRoute } from './register_list_beats_route'; import { registerVerifyBeatsRoute } from './register_verify_beats_route'; import { registerUpdateBeatRoute } from './register_update_beat_route'; -import { registerCreateConfigurationBlockRoute } from './register_create_configuration_block_route'; export function registerApiRoutes(server) { registerCreateEnrollmentTokensRoute(server); @@ -17,5 +16,4 @@ export function registerApiRoutes(server) { registerListBeatsRoute(server); registerVerifyBeatsRoute(server); registerUpdateBeatRoute(server); - registerCreateConfigurationBlockRoute(server); } diff --git a/x-pack/plugins/beats/server/routes/api/register_create_configuration_block_route.js b/x-pack/plugins/beats/server/routes/api/register_create_configuration_block_route.js deleted file mode 100644 index d259b705cf747..0000000000000 --- a/x-pack/plugins/beats/server/routes/api/register_create_configuration_block_route.js +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import Joi from 'joi'; -import uuid from 'uuid'; -import { get } from 'lodash'; -import { - INDEX_NAMES, - CONFIGURATION_BLOCKS -} from '../../../common/constants'; -import { callWithRequestFactory } from '../../lib/client'; -import { wrapEsError } from '../../lib/error_wrappers'; - -async function getConfigurationBlocksForTag(callWithRequest, tag) { - const params = { - index: INDEX_NAMES.BEATS, - type: '_doc', - q: `type:configuration_block AND configuration_block.tag:${tag}`, - size: 10000, - ignore: [ 404 ] - }; - - const response = await callWithRequest('search', params); - return get(response, 'hits.hits', []).map(hit => hit._source.configuration_block); -} - -function validateUniquenessEnforcingTypes(configurationBlocks, configurationBlockBeingValidated) { - const { type, tag } = configurationBlockBeingValidated; - // If the configuration block being validated is not of a uniqueness-enforcing type, then - // we don't need to perform any further validation checks. - if (!CONFIGURATION_BLOCKS.UNIQUENESS_ENFORCING_TYPES.includes(type)) { - return { isValid: true }; - } - - const isValid = !configurationBlocks.map(block => block.type).includes(type); - return { - isValid, - message: isValid - ? null - : `Configuration block for tag = ${tag} and type = ${type} already exists` - }; -} - -async function validateConfigurationBlock(callWithRequest, configurationBlockBeingValidated) { - const configurationBlocks = await getConfigurationBlocksForTag(callWithRequest, configurationBlockBeingValidated.tag); - return validateUniquenessEnforcingTypes(configurationBlocks, configurationBlockBeingValidated); -} - -function persistConfigurationBlock(callWithRequest, configurationBlock, configurationBlockId) { - const body = { - type: 'configuration_block', - configuration_block: configurationBlock - }; - - const params = { - index: INDEX_NAMES.BEATS, - type: '_doc', - id: `configuration_block:${configurationBlockId}`, - body, - refresh: 'wait_for' - }; - - return callWithRequest('create', params); -} - -// TODO: add license check pre-hook -// TODO: write to Kibana audit log file -export function registerCreateConfigurationBlockRoute(server) { - server.route({ - method: 'POST', - path: '/api/beats/configuration_blocks', - config: { - validate: { - payload: Joi.object({ - type: Joi.string().required().valid(Object.values(CONFIGURATION_BLOCKS.TYPES)), - tag: Joi.string().required(), - block_yml: Joi.string().required() - }).required() - } - }, - handler: async (request, reply) => { - const callWithRequest = callWithRequestFactory(server, request); - - let configurationBlockId; - try { - const configurationBlock = request.payload; - const { isValid, message } = await validateConfigurationBlock(callWithRequest, configurationBlock); - if (!isValid) { - return reply({ message }).code(400); - } - - configurationBlockId = uuid.v4(); - await persistConfigurationBlock(callWithRequest, request.payload, configurationBlockId); - } catch (err) { - return reply(wrapEsError(err)); - } - - const response = { id: configurationBlockId }; - reply(response).code(201); - } - }); -} diff --git a/x-pack/test/api_integration/apis/beats/create_configuration_block.js b/x-pack/test/api_integration/apis/beats/create_configuration_block.js deleted file mode 100644 index 680ff8b2a6d21..0000000000000 --- a/x-pack/test/api_integration/apis/beats/create_configuration_block.js +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from 'expect.js'; -import { - ES_INDEX_NAME, - ES_TYPE_NAME -} from './constants'; - -export default function ({ getService }) { - const supertest = getService('supertest'); - const es = getService('es'); - const chance = getService('chance'); - - describe('create_configuration_block', () => { - it('should create the given configuration block', async () => { - const configurationBlock = { - type: 'output', - tag: 'production', - block_yml: 'elasticsearch:\n hosts: [\"localhost:9200\"]\n username: "..."' - }; - const { body: apiResponse } = await supertest - .post( - '/api/beats/configuration_blocks' - ) - .set('kbn-xsrf', 'xxx') - .send(configurationBlock) - .expect(201); - - const idFromApi = apiResponse.id; - - const esResponse = await es.get({ - index: ES_INDEX_NAME, - type: ES_TYPE_NAME, - id: `configuration_block:${idFromApi}` - }); - - const docInEs = esResponse._source; - - expect(docInEs.type).to.eql('configuration_block'); - expect(docInEs.configuration_block.type).to.eql(configurationBlock.type); - expect(docInEs.configuration_block.tag).to.eql(configurationBlock.tag); - expect(docInEs.configuration_block.block_yml).to.eql(configurationBlock.block_yml); - }); - - it('should not allow two "output" type configuration blocks with the same tag', async () => { - const firstConfigurationBlock = { - type: 'output', - tag: 'production', - block_yml: 'elasticsearch:\n hosts: ["localhost:9200"]\n username: "..."' - }; - await supertest - .post( - '/api/beats/configuration_blocks' - ) - .set('kbn-xsrf', 'xxx') - .send(firstConfigurationBlock) - .expect(201); - - const secondConfigurationBlock = { - type: 'output', - tag: 'production', - block_yml: 'logstash:\n hosts: ["localhost:9000"]\n' - }; - await supertest - .post( - '/api/beats/configuration_blocks' - ) - .set('kbn-xsrf', 'xxx') - .send(secondConfigurationBlock) - .expect(400); - }); - - it('should allow two "output" type configuration blocks with different tags', async () => { - const firstConfigurationBlock = { - type: 'output', - tag: 'production', - block_yml: 'elasticsearch:\n hosts: ["localhost:9200"]\n username: "..."' - }; - await supertest - .post( - '/api/beats/configuration_blocks' - ) - .set('kbn-xsrf', 'xxx') - .send(firstConfigurationBlock) - .expect(201); - - const secondConfigurationBlock = { - type: 'output', - tag: 'development', - block_yml: 'logstash:\n hosts: ["localhost:9000"]\n' - }; - await supertest - .post( - '/api/beats/configuration_blocks' - ) - .set('kbn-xsrf', 'xxx') - .send(secondConfigurationBlock) - .expect(201); - }); - - it('should allow two configuration blocks of different types with the same tag', async () => { - const firstConfigurationBlock = { - type: 'output', - tag: 'production', - block_yml: 'elasticsearch:\n hosts: ["localhost:9200"]\n username: "..."' - }; - await supertest - .post( - '/api/beats/configuration_blocks' - ) - .set('kbn-xsrf', 'xxx') - .send(firstConfigurationBlock) - .expect(201); - - const secondConfigurationBlock = { - type: 'filebeat.inputs', - tag: 'production', - block_yml: 'file:\n path: "/var/log/some.log"]\n' - }; - await supertest - .post( - '/api/beats/configuration_blocks' - ) - .set('kbn-xsrf', 'xxx') - .send(secondConfigurationBlock) - .expect(201); - }); - - - it('should reject a configuration block with an invalid type', async () => { - const firstConfigurationBlock = { - type: chance.word(), - tag: 'production', - block_yml: 'elasticsearch:\n hosts: ["localhost:9200"]\n username: "..."' - }; - await supertest - .post( - '/api/beats/configuration_blocks' - ) - .set('kbn-xsrf', 'xxx') - .send(firstConfigurationBlock) - .expect(400); - }); - }); -} diff --git a/x-pack/test/api_integration/apis/beats/index.js b/x-pack/test/api_integration/apis/beats/index.js index da94353aafeee..b41f17ed749b3 100644 --- a/x-pack/test/api_integration/apis/beats/index.js +++ b/x-pack/test/api_integration/apis/beats/index.js @@ -22,6 +22,5 @@ export default function ({ getService, loadTestFile }) { loadTestFile(require.resolve('./list_beats')); loadTestFile(require.resolve('./verify_beats')); loadTestFile(require.resolve('./update_beat')); - loadTestFile(require.resolve('./create_configuration_block')); }); } From 710e7c768b6c4eadb255da3825efe0de0b9c2d2b Mon Sep 17 00:00:00 2001 From: Shaunak Kashyap Date: Wed, 23 May 2018 11:22:40 -0700 Subject: [PATCH 13/94] [Beats Management] APIs: Create or update tag (#19342) * Updating mappings * Implementing PUT /api/beats/tag/{tag} API --- .../lib/index_template/beats_template.json | 22 +- .../plugins/beats/server/routes/api/index.js | 2 + .../routes/api/register_set_tag_route.js | 124 +++++++++++ .../test/api_integration/apis/beats/index.js | 1 + .../api_integration/apis/beats/set_tag.js | 207 ++++++++++++++++++ .../es_archives/beats/list/mappings.json | 22 +- 6 files changed, 364 insertions(+), 14 deletions(-) create mode 100644 x-pack/plugins/beats/server/routes/api/register_set_tag_route.js create mode 100644 x-pack/test/api_integration/apis/beats/set_tag.js diff --git a/x-pack/plugins/beats/server/lib/index_template/beats_template.json b/x-pack/plugins/beats/server/lib/index_template/beats_template.json index e293845f9a3a8..9f912f19b2a8d 100644 --- a/x-pack/plugins/beats/server/lib/index_template/beats_template.json +++ b/x-pack/plugins/beats/server/lib/index_template/beats_template.json @@ -27,16 +27,21 @@ } } }, - "configuration_block": { + "tag": { "properties": { - "tag": { - "type": "keyword" - }, - "type": { + "id": { "type": "keyword" }, - "block_yml": { - "type": "text" + "configuration_blocks": { + "type": "nested", + "properties": { + "type": { + "type": "keyword" + }, + "block_yml": { + "type": "text" + } + } } } }, @@ -69,6 +74,9 @@ "local_configuration_yml": { "type": "text" }, + "tags": { + "type": "keyword" + }, "central_configuration_yml": { "type": "text" }, diff --git a/x-pack/plugins/beats/server/routes/api/index.js b/x-pack/plugins/beats/server/routes/api/index.js index 4e6ee318668bf..5d7570807d682 100644 --- a/x-pack/plugins/beats/server/routes/api/index.js +++ b/x-pack/plugins/beats/server/routes/api/index.js @@ -9,6 +9,7 @@ import { registerEnrollBeatRoute } from './register_enroll_beat_route'; import { registerListBeatsRoute } from './register_list_beats_route'; import { registerVerifyBeatsRoute } from './register_verify_beats_route'; import { registerUpdateBeatRoute } from './register_update_beat_route'; +import { registerSetTagRoute } from './register_set_tag_route'; export function registerApiRoutes(server) { registerCreateEnrollmentTokensRoute(server); @@ -16,4 +17,5 @@ export function registerApiRoutes(server) { registerListBeatsRoute(server); registerVerifyBeatsRoute(server); registerUpdateBeatRoute(server); + registerSetTagRoute(server); } diff --git a/x-pack/plugins/beats/server/routes/api/register_set_tag_route.js b/x-pack/plugins/beats/server/routes/api/register_set_tag_route.js new file mode 100644 index 0000000000000..288fcade9929b --- /dev/null +++ b/x-pack/plugins/beats/server/routes/api/register_set_tag_route.js @@ -0,0 +1,124 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Joi from 'joi'; +import { + get, + uniq, + intersection +} from 'lodash'; +import { + INDEX_NAMES, + CONFIGURATION_BLOCKS +} from '../../../common/constants'; +import { callWithRequestFactory } from '../../lib/client'; +import { wrapEsError } from '../../lib/error_wrappers'; + +function validateUniquenessEnforcingTypes(configurationBlocks) { + const types = uniq(configurationBlocks.map(block => block.type)); + + // If none of the types in the given configuration blocks are uniqueness-enforcing, + // we don't need to perform any further validation checks. + const uniquenessEnforcingTypes = intersection(types, CONFIGURATION_BLOCKS.UNIQUENESS_ENFORCING_TYPES); + if (uniquenessEnforcingTypes.length === 0) { + return { isValid: true }; + } + + // Count the number of uniqueness-enforcing types in the given configuration blocks + const typeCountMap = configurationBlocks.reduce((typeCountMap, block) => { + const { type } = block; + if (!uniquenessEnforcingTypes.includes(type)) { + return typeCountMap; + } + + const count = typeCountMap[type] || 0; + return { + ...typeCountMap, + [type]: count + 1 + }; + }, {}); + + // If there is no more than one of any uniqueness-enforcing types in the given + // configuration blocks, we don't need to perform any further validation checks. + if (Object.values(typeCountMap).filter(count => count > 1).length === 0) { + return { isValid: true }; + } + + const message = Object.entries(typeCountMap) + .filter(([, count]) => count > 1) + .map(([type, count]) => `Expected only one configuration block of type '${type}' but found ${count}`) + .join(' '); + + return { + isValid: false, + message + }; +} + +async function validateConfigurationBlocks(configurationBlocks) { + return validateUniquenessEnforcingTypes(configurationBlocks); +} + +async function persistTag(callWithRequest, tag) { + const body = { + type: 'tag', + tag + }; + + const params = { + index: INDEX_NAMES.BEATS, + type: '_doc', + id: `tag:${tag.id}`, + body, + refresh: 'wait_for' + }; + + const response = await callWithRequest('index', params); + return response.result; +} + +// TODO: add license check pre-hook +// TODO: write to Kibana audit log file +export function registerSetTagRoute(server) { + server.route({ + method: 'PUT', + path: '/api/beats/tag/{tag}', + config: { + validate: { + payload: Joi.object({ + configuration_blocks: Joi.array().items( + Joi.object({ + type: Joi.string().required().valid(Object.values(CONFIGURATION_BLOCKS.TYPES)), + block_yml: Joi.string().required() + }) + ) + }).allow(null) + } + }, + handler: async (request, reply) => { + const callWithRequest = callWithRequestFactory(server, request); + + let result; + try { + const configurationBlocks = get(request, 'payload.configuration_blocks', []); + const { isValid, message } = await validateConfigurationBlocks(configurationBlocks); + if (!isValid) { + return reply({ message }).code(400); + } + + const tag = { + id: request.params.tag, + configuration_blocks: configurationBlocks + }; + result = await persistTag(callWithRequest, tag); + } catch (err) { + return reply(wrapEsError(err)); + } + + reply().code(result === 'created' ? 201 : 200); + } + }); +} diff --git a/x-pack/test/api_integration/apis/beats/index.js b/x-pack/test/api_integration/apis/beats/index.js index b41f17ed749b3..c3f07ecaa2926 100644 --- a/x-pack/test/api_integration/apis/beats/index.js +++ b/x-pack/test/api_integration/apis/beats/index.js @@ -22,5 +22,6 @@ export default function ({ getService, loadTestFile }) { loadTestFile(require.resolve('./list_beats')); loadTestFile(require.resolve('./verify_beats')); loadTestFile(require.resolve('./update_beat')); + loadTestFile(require.resolve('./set_tag')); }); } diff --git a/x-pack/test/api_integration/apis/beats/set_tag.js b/x-pack/test/api_integration/apis/beats/set_tag.js new file mode 100644 index 0000000000000..3af3c0372e847 --- /dev/null +++ b/x-pack/test/api_integration/apis/beats/set_tag.js @@ -0,0 +1,207 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from 'expect.js'; +import { + ES_INDEX_NAME, + ES_TYPE_NAME +} from './constants'; + +export default function ({ getService }) { + const supertest = getService('supertest'); + const chance = getService('chance'); + const es = getService('es'); + + describe('set_tag', () => { + it('should create an empty tag', async () => { + const tagId = 'production'; + await supertest + .put( + `/api/beats/tag/${tagId}` + ) + .set('kbn-xsrf', 'xxx') + .send() + .expect(201); + + const esResponse = await es.get({ + index: ES_INDEX_NAME, + type: ES_TYPE_NAME, + id: `tag:${tagId}` + }); + + const tagInEs = esResponse._source; + + expect(tagInEs.type).to.be('tag'); + expect(tagInEs.tag.id).to.be(tagId); + expect(tagInEs.tag.configuration_blocks).to.be.an(Array); + expect(tagInEs.tag.configuration_blocks.length).to.be(0); + }); + + it('should create a tag with one configuration block', async () => { + const tagId = 'production'; + await supertest + .put( + `/api/beats/tag/${tagId}` + ) + .set('kbn-xsrf', 'xxx') + .send({ + configuration_blocks: [ + { + type: 'output', + block_yml: 'elasticsearch:\n hosts: [\"localhost:9200\"]\n username: "..."' + } + ] + }) + .expect(201); + + const esResponse = await es.get({ + index: ES_INDEX_NAME, + type: ES_TYPE_NAME, + id: `tag:${tagId}` + }); + + const tagInEs = esResponse._source; + + expect(tagInEs.type).to.be('tag'); + expect(tagInEs.tag.id).to.be(tagId); + expect(tagInEs.tag.configuration_blocks).to.be.an(Array); + expect(tagInEs.tag.configuration_blocks.length).to.be(1); + expect(tagInEs.tag.configuration_blocks[0].type).to.be('output'); + expect(tagInEs.tag.configuration_blocks[0].block_yml).to.be('elasticsearch:\n hosts: [\"localhost:9200\"]\n username: "..."'); + }); + + it('should create a tag with two configuration blocks', async () => { + const tagId = 'production'; + await supertest + .put( + `/api/beats/tag/${tagId}` + ) + .set('kbn-xsrf', 'xxx') + .send({ + configuration_blocks: [ + { + type: 'filebeat.inputs', + block_yml: 'file:\n path: "/var/log/some.log"]\n' + }, + { + type: 'output', + block_yml: 'elasticsearch:\n hosts: [\"localhost:9200\"]\n username: "..."' + } + ] + }) + .expect(201); + + const esResponse = await es.get({ + index: ES_INDEX_NAME, + type: ES_TYPE_NAME, + id: `tag:${tagId}` + }); + + const tagInEs = esResponse._source; + + expect(tagInEs.type).to.be('tag'); + expect(tagInEs.tag.id).to.be(tagId); + expect(tagInEs.tag.configuration_blocks).to.be.an(Array); + expect(tagInEs.tag.configuration_blocks.length).to.be(2); + expect(tagInEs.tag.configuration_blocks[0].type).to.be('filebeat.inputs'); + expect(tagInEs.tag.configuration_blocks[0].block_yml).to.be('file:\n path: "/var/log/some.log"]\n'); + expect(tagInEs.tag.configuration_blocks[1].type).to.be('output'); + expect(tagInEs.tag.configuration_blocks[1].block_yml).to.be('elasticsearch:\n hosts: [\"localhost:9200\"]\n username: "..."'); + }); + + it('should fail when creating a tag with two configuration blocks of type output', async () => { + const tagId = 'production'; + await supertest + .put( + `/api/beats/tag/${tagId}` + ) + .set('kbn-xsrf', 'xxx') + .send({ + configuration_blocks: [ + { + type: 'output', + block_yml: 'logstash:\n hosts: ["localhost:9000"]\n' + }, + { + type: 'output', + block_yml: 'elasticsearch:\n hosts: [\"localhost:9200\"]\n username: "..."' + } + ] + }) + .expect(400); + }); + + it('should fail when creating a tag with an invalid configuration block type', async () => { + const tagId = 'production'; + await supertest + .put( + `/api/beats/tag/${tagId}` + ) + .set('kbn-xsrf', 'xxx') + .send({ + configuration_blocks: [ + { + type: chance.word(), + block_yml: 'logstash:\n hosts: ["localhost:9000"]\n' + } + ] + }) + .expect(400); + }); + + it('should update an existing tag', async () => { + const tagId = 'production'; + await supertest + .put( + `/api/beats/tag/${tagId}` + ) + .set('kbn-xsrf', 'xxx') + .send({ + configuration_blocks: [ + { + type: 'filebeat.inputs', + block_yml: 'file:\n path: "/var/log/some.log"]\n' + }, + { + type: 'output', + block_yml: 'elasticsearch:\n hosts: [\"localhost:9200\"]\n username: "..."' + } + ] + }) + .expect(201); + + await supertest + .put( + `/api/beats/tag/${tagId}` + ) + .set('kbn-xsrf', 'xxx') + .send({ + configuration_blocks: [ + { + type: 'output', + block_yml: 'logstash:\n hosts: ["localhost:9000"]\n' + } + ] + }) + .expect(200); + + const esResponse = await es.get({ + index: ES_INDEX_NAME, + type: ES_TYPE_NAME, + id: `tag:${tagId}` + }); + + const tagInEs = esResponse._source; + + expect(tagInEs.type).to.be('tag'); + expect(tagInEs.tag.id).to.be(tagId); + expect(tagInEs.tag.configuration_blocks).to.be.an(Array); + expect(tagInEs.tag.configuration_blocks.length).to.be(1); + expect(tagInEs.tag.configuration_blocks[0].type).to.be('output'); + expect(tagInEs.tag.configuration_blocks[0].block_yml).to.be('logstash:\n hosts: ["localhost:9000"]\n'); + }); + }); +} diff --git a/x-pack/test/functional/es_archives/beats/list/mappings.json b/x-pack/test/functional/es_archives/beats/list/mappings.json index 24aceed8a5b61..0057b0b765773 100644 --- a/x-pack/test/functional/es_archives/beats/list/mappings.json +++ b/x-pack/test/functional/es_archives/beats/list/mappings.json @@ -27,16 +27,21 @@ } } }, - "configuration_block": { + "tag": { "properties": { - "tag": { - "type": "keyword" - }, - "type": { + "id": { "type": "keyword" }, - "block_yml": { - "type": "text" + "configuration_blocks": { + "type": "nested", + "properties": { + "type": { + "type": "keyword" + }, + "block_yml": { + "type": "text" + } + } } } }, @@ -69,6 +74,9 @@ "local_configuration_yml": { "type": "text" }, + "tags": { + "type": "keyword" + }, "central_configuration_yml": { "type": "text" }, From 239de1c34d74a1f7ca154cd4ef222fb647225218 Mon Sep 17 00:00:00 2001 From: Shaunak Kashyap Date: Fri, 25 May 2018 05:44:21 -0700 Subject: [PATCH 14/94] [Beats Management] Prevent timing attacks when checking auth tokens (#19363) * Using crypto.timingSafeEqual() for comparing auth tokens * Prevent subtler timing attack in token comparison function * Introduce random delay after we try to find token in ES to mitigate timing attack * Remove random delay --- .../server/lib/crypto/are_tokens_equal.js | 21 +++++++++++++++++++ .../plugins/beats/server/lib/crypto/index.js | 7 +++++++ .../routes/api/register_enroll_beat_route.js | 2 +- .../routes/api/register_update_beat_route.js | 3 ++- 4 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 x-pack/plugins/beats/server/lib/crypto/are_tokens_equal.js create mode 100644 x-pack/plugins/beats/server/lib/crypto/index.js diff --git a/x-pack/plugins/beats/server/lib/crypto/are_tokens_equal.js b/x-pack/plugins/beats/server/lib/crypto/are_tokens_equal.js new file mode 100644 index 0000000000000..a6ed171d30e5e --- /dev/null +++ b/x-pack/plugins/beats/server/lib/crypto/are_tokens_equal.js @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { timingSafeEqual } from 'crypto'; + +const RANDOM_TOKEN_1 = 'b48c4bda384a40cb91c6eb9b8849e77f'; +const RANDOM_TOKEN_2 = '80a3819e3cd64f4399f1d4886be7a08b'; + +export function areTokensEqual(token1, token2) { + if ((typeof token1 !== 'string') || (typeof token2 !== 'string') || (token1.length !== token2.length)) { + // This prevents a more subtle timing attack where we know already the tokens aren't going to + // match but still we don't return fast. Instead we compare two pre-generated random tokens using + // the same comparison algorithm that we would use to compare two equal-length tokens. + return timingSafeEqual(Buffer.from(RANDOM_TOKEN_1, 'utf8'), Buffer.from(RANDOM_TOKEN_2, 'utf8')); + } + + return timingSafeEqual(Buffer.from(token1, 'utf8'), Buffer.from(token2, 'utf8')); +} diff --git a/x-pack/plugins/beats/server/lib/crypto/index.js b/x-pack/plugins/beats/server/lib/crypto/index.js new file mode 100644 index 0000000000000..31fa5de67b2ca --- /dev/null +++ b/x-pack/plugins/beats/server/lib/crypto/index.js @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { areTokensEqual } from './are_tokens_equal'; diff --git a/x-pack/plugins/beats/server/routes/api/register_enroll_beat_route.js b/x-pack/plugins/beats/server/routes/api/register_enroll_beat_route.js index 07e336a1e091b..77742c16cd401 100644 --- a/x-pack/plugins/beats/server/routes/api/register_enroll_beat_route.js +++ b/x-pack/plugins/beats/server/routes/api/register_enroll_beat_route.js @@ -80,7 +80,7 @@ export function registerEnrollBeatRoute(server) { try { const enrollmentToken = request.headers['kbn-beats-enrollment-token']; const { token, expires_on: expiresOn } = await getEnrollmentToken(callWithInternalUser, enrollmentToken); - if (!token || token !== enrollmentToken) { + if (!token) { return reply({ message: 'Invalid enrollment token' }).code(400); } if (moment(expiresOn).isBefore(moment())) { diff --git a/x-pack/plugins/beats/server/routes/api/register_update_beat_route.js b/x-pack/plugins/beats/server/routes/api/register_update_beat_route.js index fe615ffe1a11c..5955e65f6bbaf 100644 --- a/x-pack/plugins/beats/server/routes/api/register_update_beat_route.js +++ b/x-pack/plugins/beats/server/routes/api/register_update_beat_route.js @@ -9,6 +9,7 @@ import { get } from 'lodash'; import { INDEX_NAMES } from '../../../common/constants'; import { callWithInternalUserFactory } from '../../lib/client'; import { wrapEsError } from '../../lib/error_wrappers'; +import { areTokensEqual } from '../../lib/crypto'; async function getBeat(callWithInternalUser, beatId) { const params = { @@ -74,7 +75,7 @@ export function registerUpdateBeatRoute(server) { return reply({ message: 'Beat not found' }).code(404); } - const isAccessTokenValid = beat.access_token === request.headers['kbn-beats-access-token']; + const isAccessTokenValid = areTokensEqual(beat.access_token, request.headers['kbn-beats-access-token']); if (!isAccessTokenValid) { return reply({ message: 'Invalid access token' }).code(401); } From 5f97a92a8b396d07d7269ddd8857bb7c9b37fd95 Mon Sep 17 00:00:00 2001 From: Shaunak Kashyap Date: Thu, 31 May 2018 15:54:12 -0700 Subject: [PATCH 15/94] [Beats Management] APIs: Assign tag(s) to beat(s) (#19431) * Using crypto.timingSafeEqual() for comparing auth tokens * Introduce random delay after we try to find token in ES to mitigate timing attack * Rename "determine" to "find" * Remove random delay * Starting to implement POST /api/beats/beats_tags API * Changing API * Updating tests for changes to API * Updating ES archive * Renaming * Use destructuring * Moving start of script to own line to increase readability * Using destructuring --- .../plugins/beats/server/routes/api/index.js | 2 + .../register_assign_tags_to_beats_route.js | 169 ++++++++++ .../routes/api/register_verify_beats_route.js | 16 +- .../apis/beats/assign_tags_to_beats.js | 290 ++++++++++++++++++ .../test/api_integration/apis/beats/index.js | 1 + .../es_archives/beats/list/data.json.gz | Bin 371 -> 436 bytes 6 files changed, 470 insertions(+), 8 deletions(-) create mode 100644 x-pack/plugins/beats/server/routes/api/register_assign_tags_to_beats_route.js create mode 100644 x-pack/test/api_integration/apis/beats/assign_tags_to_beats.js diff --git a/x-pack/plugins/beats/server/routes/api/index.js b/x-pack/plugins/beats/server/routes/api/index.js index 5d7570807d682..aa1be44cd96ca 100644 --- a/x-pack/plugins/beats/server/routes/api/index.js +++ b/x-pack/plugins/beats/server/routes/api/index.js @@ -10,6 +10,7 @@ import { registerListBeatsRoute } from './register_list_beats_route'; import { registerVerifyBeatsRoute } from './register_verify_beats_route'; import { registerUpdateBeatRoute } from './register_update_beat_route'; import { registerSetTagRoute } from './register_set_tag_route'; +import { registerAssignTagsToBeatsRoute } from './register_assign_tags_to_beats_route'; export function registerApiRoutes(server) { registerCreateEnrollmentTokensRoute(server); @@ -18,4 +19,5 @@ export function registerApiRoutes(server) { registerVerifyBeatsRoute(server); registerUpdateBeatRoute(server); registerSetTagRoute(server); + registerAssignTagsToBeatsRoute(server); } diff --git a/x-pack/plugins/beats/server/routes/api/register_assign_tags_to_beats_route.js b/x-pack/plugins/beats/server/routes/api/register_assign_tags_to_beats_route.js new file mode 100644 index 0000000000000..5f6c5f7a7b906 --- /dev/null +++ b/x-pack/plugins/beats/server/routes/api/register_assign_tags_to_beats_route.js @@ -0,0 +1,169 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Joi from 'joi'; +import { + get, + flatten, + uniq +} from 'lodash'; +import { INDEX_NAMES } from '../../../common/constants'; +import { callWithRequestFactory } from '../../lib/client'; +import { wrapEsError } from '../../lib/error_wrappers'; + +async function getDocs(callWithRequest, ids) { + const params = { + index: INDEX_NAMES.BEATS, + type: '_doc', + body: { ids }, + _source: false + }; + + const response = await callWithRequest('mget', params); + return get(response, 'docs', []); +} + +function getBeats(callWithRequest, beatIds) { + const ids = beatIds.map(beatId => `beat:${beatId}`); + return getDocs(callWithRequest, ids); +} + +function getTags(callWithRequest, tags) { + const ids = tags.map(tag => `tag:${tag}`); + return getDocs(callWithRequest, ids); +} + +async function findNonExistentItems(callWithRequest, items, getFn) { + const itemsFromEs = await getFn.call(null, callWithRequest, items); + return itemsFromEs.reduce((nonExistentItems, itemFromEs, idx) => { + if (!itemFromEs.found) { + nonExistentItems.push(items[idx]); + } + return nonExistentItems; + }, []); +} + +function findNonExistentBeatIds(callWithRequest, beatIds) { + return findNonExistentItems(callWithRequest, beatIds, getBeats); +} + +function findNonExistentTags(callWithRequest, tags) { + return findNonExistentItems(callWithRequest, tags, getTags); +} + +async function persistAssignments(callWithRequest, assignments) { + const body = flatten(assignments.map(({ beatId, tag }) => { + const script = '' + + 'def beat = ctx._source.beat; ' + + 'if (beat.tags == null) { ' + + ' beat.tags = []; ' + + '} ' + + 'if (!beat.tags.contains(params.tag)) { ' + + ' beat.tags.add(params.tag); ' + + '}'; + + return [ + { update: { _id: `beat:${beatId}` } }, + { script: { source: script, params: { tag } } } + ]; + })); + + const params = { + index: INDEX_NAMES.BEATS, + type: '_doc', + body, + refresh: 'wait_for' + }; + + const response = await callWithRequest('bulk', params); + return get(response, 'items', []) + .map((item, resultIdx) => ({ + status: item.update.status, + result: item.update.result, + idxInRequest: assignments[resultIdx].idxInRequest + })); +} + +function addNonExistentItemAssignmentsToResponse(response, assignments, nonExistentBeatIds, nonExistentTags) { + assignments.forEach(({ beat_id: beatId, tag }, idx) => { + const isBeatNonExistent = nonExistentBeatIds.includes(beatId); + const isTagNonExistent = nonExistentTags.includes(tag); + + if (isBeatNonExistent && isTagNonExistent) { + response.assignments[idx].status = 404; + response.assignments[idx].result = `Beat ${beatId} and tag ${tag} not found`; + } else if (isBeatNonExistent) { + response.assignments[idx].status = 404; + response.assignments[idx].result = `Beat ${beatId} not found`; + } else if (isTagNonExistent) { + response.assignments[idx].status = 404; + response.assignments[idx].result = `Tag ${tag} not found`; + } + }); +} + +function addAssignmentResultsToResponse(response, assignmentResults) { + assignmentResults.forEach(assignmentResult => { + const { idxInRequest, status, result } = assignmentResult; + response.assignments[idxInRequest].status = status; + response.assignments[idxInRequest].result = result; + }); +} + +// TODO: add license check pre-hook +// TODO: write to Kibana audit log file +export function registerAssignTagsToBeatsRoute(server) { + server.route({ + method: 'POST', + path: '/api/beats/agents_tags/assignments', + config: { + validate: { + payload: Joi.object({ + assignments: Joi.array().items(Joi.object({ + beat_id: Joi.string().required(), + tag: Joi.string().required() + })) + }).required() + } + }, + handler: async (request, reply) => { + const callWithRequest = callWithRequestFactory(server, request); + + const { assignments } = request.payload; + const beatIds = uniq(assignments.map(assignment => assignment.beat_id)); + const tags = uniq(assignments.map(assignment => assignment.tag)); + + const response = { + assignments: assignments.map(() => ({ status: null })) + }; + + try { + // Handle assignments containing non-existing beat IDs or tags + const nonExistentBeatIds = await findNonExistentBeatIds(callWithRequest, beatIds); + const nonExistentTags = await findNonExistentTags(callWithRequest, tags); + + addNonExistentItemAssignmentsToResponse(response, assignments, nonExistentBeatIds, nonExistentTags); + + const validAssignments = assignments + .map((assignment, idxInRequest) => ({ + beatId: assignment.beat_id, + tag: assignment.tag, + idxInRequest // so we can add the result of this assignment to the correct place in the response + })) + .filter((assignment, idx) => response.assignments[idx].status === null); + + if (validAssignments.length > 0) { + const assignmentResults = await persistAssignments(callWithRequest, validAssignments); + addAssignmentResultsToResponse(response, assignmentResults); + } + } catch (err) { + return reply(wrapEsError(err)); + } + + reply(response); + } + }); +} diff --git a/x-pack/plugins/beats/server/routes/api/register_verify_beats_route.js b/x-pack/plugins/beats/server/routes/api/register_verify_beats_route.js index 6aaa61b07c5f8..b2113029224a5 100644 --- a/x-pack/plugins/beats/server/routes/api/register_verify_beats_route.js +++ b/x-pack/plugins/beats/server/routes/api/register_verify_beats_route.js @@ -49,7 +49,7 @@ async function verifyBeats(callWithRequest, beatIds) { return get(response, 'items', []); } -function determineNonExistentBeatIds(beatsFromEs, beatIdsFromRequest) { +function findNonExistentBeatIds(beatsFromEs, beatIdsFromRequest) { return beatsFromEs.reduce((nonExistentBeatIds, beatFromEs, idx) => { if (!beatFromEs.found) { nonExistentBeatIds.push(beatIdsFromRequest[idx]); @@ -58,21 +58,21 @@ function determineNonExistentBeatIds(beatsFromEs, beatIdsFromRequest) { }, []); } -function determineAlreadyVerifiedBeatIds(beatsFromEs) { +function findAlreadyVerifiedBeatIds(beatsFromEs) { return beatsFromEs .filter(beat => beat.found) .filter(beat => beat._source.beat.hasOwnProperty('verified_on')) .map(beat => beat._source.beat.id); } -function determineToBeVerifiedBeatIds(beatsFromEs) { +function findToBeVerifiedBeatIds(beatsFromEs) { return beatsFromEs .filter(beat => beat.found) .filter(beat => !beat._source.beat.hasOwnProperty('verified_on')) .map(beat => beat._source.beat.id); } -function determineVerifiedBeatIds(verifications, toBeVerifiedBeatIds) { +function findVerifiedBeatIds(verifications, toBeVerifiedBeatIds) { return verifications.reduce((verifiedBeatIds, verification, idx) => { if (verification.update.status === 200) { verifiedBeatIds.push(toBeVerifiedBeatIds[idx]); @@ -109,12 +109,12 @@ export function registerVerifyBeatsRoute(server) { try { const beatsFromEs = await getBeats(callWithRequest, beatIds); - nonExistentBeatIds = determineNonExistentBeatIds(beatsFromEs, beatIds); - alreadyVerifiedBeatIds = determineAlreadyVerifiedBeatIds(beatsFromEs); - const toBeVerifiedBeatIds = determineToBeVerifiedBeatIds(beatsFromEs); + nonExistentBeatIds = findNonExistentBeatIds(beatsFromEs, beatIds); + alreadyVerifiedBeatIds = findAlreadyVerifiedBeatIds(beatsFromEs); + const toBeVerifiedBeatIds = findToBeVerifiedBeatIds(beatsFromEs); const verifications = await verifyBeats(callWithRequest, toBeVerifiedBeatIds); - verifiedBeatIds = determineVerifiedBeatIds(verifications, toBeVerifiedBeatIds); + verifiedBeatIds = findVerifiedBeatIds(verifications, toBeVerifiedBeatIds); } catch (err) { return reply(wrapEsError(err)); diff --git a/x-pack/test/api_integration/apis/beats/assign_tags_to_beats.js b/x-pack/test/api_integration/apis/beats/assign_tags_to_beats.js new file mode 100644 index 0000000000000..a8f542239d22e --- /dev/null +++ b/x-pack/test/api_integration/apis/beats/assign_tags_to_beats.js @@ -0,0 +1,290 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from 'expect.js'; +import { + ES_INDEX_NAME, + ES_TYPE_NAME +} from './constants'; + +export default function ({ getService }) { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + const es = getService('es'); + const chance = getService('chance'); + + describe('assign_tags_to_beats', () => { + const archive = 'beats/list'; + + beforeEach('load beats archive', () => esArchiver.load(archive)); + afterEach('unload beats archive', () => esArchiver.unload(archive)); + + it('should add a single tag to a single beat', async () => { + const { body: apiResponse } = await supertest + .post( + '/api/beats/agents_tags/assignments' + ) + .set('kbn-xsrf', 'xxx') + .send({ + assignments: [ + { beat_id: 'bar', tag: 'production' } + ] + }) + .expect(200); + + expect(apiResponse.assignments).to.eql([ + { status: 200, result: 'updated' } + ]); + + const esResponse = await es.get({ + index: ES_INDEX_NAME, + type: ES_TYPE_NAME, + id: `beat:bar` + }); + + const beat = esResponse._source.beat; + expect(beat.tags).to.eql(['production']); + }); + + it('should not re-add an existing tag to a beat', async () => { + const tags = ['production']; + + let esResponse; + let beat; + + // Before adding the existing tag + esResponse = await es.get({ + index: ES_INDEX_NAME, + type: ES_TYPE_NAME, + id: `beat:foo` + }); + + beat = esResponse._source.beat; + expect(beat.tags).to.eql(tags); + + // Adding the existing tag + const { body: apiResponse } = await supertest + .post( + '/api/beats/agents_tags/assignments' + ) + .set('kbn-xsrf', 'xxx') + .send({ + assignments: [ + { beat_id: 'foo', tag: 'production' } + ] + }) + .expect(200); + + expect(apiResponse.assignments).to.eql([ + { status: 200, result: 'updated' } + ]); + + // After adding the existing tag + esResponse = await es.get({ + index: ES_INDEX_NAME, + type: ES_TYPE_NAME, + id: `beat:foo` + }); + + beat = esResponse._source.beat; + expect(beat.tags).to.eql(tags); + }); + + it('should add a single tag to a multiple beats', async () => { + const { body: apiResponse } = await supertest + .post( + '/api/beats/agents_tags/assignments' + ) + .set('kbn-xsrf', 'xxx') + .send({ + assignments: [ + { beat_id: 'foo', tag: 'development' }, + { beat_id: 'bar', tag: 'development' } + ] + }) + .expect(200); + + expect(apiResponse.assignments).to.eql([ + { status: 200, result: 'updated' }, + { status: 200, result: 'updated' } + ]); + + let esResponse; + let beat; + + // Beat foo + esResponse = await es.get({ + index: ES_INDEX_NAME, + type: ES_TYPE_NAME, + id: `beat:foo` + }); + + beat = esResponse._source.beat; + expect(beat.tags).to.eql(['production', 'development']); // as beat 'foo' already had 'production' tag attached to it + + // Beat bar + esResponse = await es.get({ + index: ES_INDEX_NAME, + type: ES_TYPE_NAME, + id: `beat:bar` + }); + + beat = esResponse._source.beat; + expect(beat.tags).to.eql(['development']); + }); + + it('should add multiple tags to a single beat', async () => { + const { body: apiResponse } = await supertest + .post( + '/api/beats/agents_tags/assignments' + ) + .set('kbn-xsrf', 'xxx') + .send({ + assignments: [ + { beat_id: 'bar', tag: 'development' }, + { beat_id: 'bar', tag: 'production' } + ] + }) + .expect(200); + + expect(apiResponse.assignments).to.eql([ + { status: 200, result: 'updated' }, + { status: 200, result: 'updated' } + ]); + + const esResponse = await es.get({ + index: ES_INDEX_NAME, + type: ES_TYPE_NAME, + id: `beat:bar` + }); + + const beat = esResponse._source.beat; + expect(beat.tags).to.eql(['development', 'production']); + }); + + it('should add multiple tags to a multiple beats', async () => { + const { body: apiResponse } = await supertest + .post( + '/api/beats/agents_tags/assignments' + ) + .set('kbn-xsrf', 'xxx') + .send({ + assignments: [ + { beat_id: 'foo', tag: 'development' }, + { beat_id: 'bar', tag: 'production' } + ] + }) + .expect(200); + + expect(apiResponse.assignments).to.eql([ + { status: 200, result: 'updated' }, + { status: 200, result: 'updated' } + ]); + + let esResponse; + let beat; + + // Beat foo + esResponse = await es.get({ + index: ES_INDEX_NAME, + type: ES_TYPE_NAME, + id: `beat:foo` + }); + + beat = esResponse._source.beat; + expect(beat.tags).to.eql(['production', 'development']); // as beat 'foo' already had 'production' tag attached to it + + // Beat bar + esResponse = await es.get({ + index: ES_INDEX_NAME, + type: ES_TYPE_NAME, + id: `beat:bar` + }); + + beat = esResponse._source.beat; + expect(beat.tags).to.eql(['production']); + }); + + it('should return errors for non-existent beats', async () => { + const nonExistentBeatId = chance.word(); + + const { body: apiResponse } = await supertest + .post( + '/api/beats/agents_tags/assignments' + ) + .set('kbn-xsrf', 'xxx') + .send({ + assignments: [ + { beat_id: nonExistentBeatId, tag: 'production' } + ] + }) + .expect(200); + + expect(apiResponse.assignments).to.eql([ + { status: 404, result: `Beat ${nonExistentBeatId} not found` } + ]); + }); + + it('should return errors for non-existent tags', async () => { + const nonExistentTag = chance.word(); + + const { body: apiResponse } = await supertest + .post( + '/api/beats/agents_tags/assignments' + ) + .set('kbn-xsrf', 'xxx') + .send({ + assignments: [ + { beat_id: 'bar', tag: nonExistentTag } + ] + }) + .expect(200); + + expect(apiResponse.assignments).to.eql([ + { status: 404, result: `Tag ${nonExistentTag} not found` } + ]); + + const esResponse = await es.get({ + index: ES_INDEX_NAME, + type: ES_TYPE_NAME, + id: `beat:bar` + }); + + const beat = esResponse._source.beat; + expect(beat).to.not.have.property('tags'); + }); + + it('should return errors for non-existent beats and tags', async () => { + const nonExistentBeatId = chance.word(); + const nonExistentTag = chance.word(); + + const { body: apiResponse } = await supertest + .post( + '/api/beats/agents_tags/assignments' + ) + .set('kbn-xsrf', 'xxx') + .send({ + assignments: [ + { beat_id: nonExistentBeatId, tag: nonExistentTag } + ] + }) + .expect(200); + + expect(apiResponse.assignments).to.eql([ + { status: 404, result: `Beat ${nonExistentBeatId} and tag ${nonExistentTag} not found` } + ]); + + const esResponse = await es.get({ + index: ES_INDEX_NAME, + type: ES_TYPE_NAME, + id: `beat:bar` + }); + + const beat = esResponse._source.beat; + expect(beat).to.not.have.property('tags'); + }); + }); +} diff --git a/x-pack/test/api_integration/apis/beats/index.js b/x-pack/test/api_integration/apis/beats/index.js index c3f07ecaa2926..76f712c05de44 100644 --- a/x-pack/test/api_integration/apis/beats/index.js +++ b/x-pack/test/api_integration/apis/beats/index.js @@ -23,5 +23,6 @@ export default function ({ getService, loadTestFile }) { loadTestFile(require.resolve('./verify_beats')); loadTestFile(require.resolve('./update_beat')); loadTestFile(require.resolve('./set_tag')); + loadTestFile(require.resolve('./assign_tags_to_beats')); }); } diff --git a/x-pack/test/functional/es_archives/beats/list/data.json.gz b/x-pack/test/functional/es_archives/beats/list/data.json.gz index f3ccd2687455692022cc5fb6622b906cd0ec6293..48094292337780ce8c10a685ddda5f9446160c5c 100644 GIT binary patch literal 436 zcmV;l0ZaZLiwFp-X$M;X17u-zVJ>QOZ*Bm^RLgGTFbur=D-53-!Ez);@^|d1MPX19 zoi!R;sUJbQLH>OuCxKmTlODQAE(S=>5Y!CmLlnnJ|FOj+j}z-m@)Qy~*bE_a@PQAj z#^OEzNDU3FvBsufXoS8S;j<3KrA1)bkO{E-eb`^Jof}#+^`3D9@{Eel(S(}e%4&n3 zu)g-&b$wB7Lz$9{ED8Ik+CY7xJ4ZCM#JGBnIZZnaIwrvw?7E_NZ`g#g0%4Q_OiL@7 zPKA_Itx`&bGFAf9$(eeLF5hExjH1I`MAq=<|A|~<-&W>^%$ZktNhl@ky3~H6>rzkS zeknDTGUFUI7b!5tq+Vr$3&T+<11QReO6_;(j#B?XbU$|vy{q3$`_RXq9V_DzLZ2|?0HV)S!C2LJ#d`O}C1 literal 371 zcmV-(0gV11iwFp7ua17u-zVJ>QOZ*Bm^RKae8Fbuu(6^Qd1C6J^E-?7s!$VtqG zHb@7w>Q?pNM`$aU)^>+Y?In`!_pGO9JG&^3lm26cNggN8+vFi6Ht@C%ncWZ!VbwU? z1^}s{foH6-=@$l}??(8nLvd;mST1A&EPr2bPub3|TRZihaRc&*ijUERn&Hao4ZmTB z+Kcb{qFRMABPq!U|50tAKG3}<23lf$J;xl>PC~~dSc_d(^!^o_P}U%=)_}BhiW@4G zVtPp#liE53+$2ZpK03YoXdgwpo0x3i^Z!h)v2QDT#pZNyIU|e_e%b0l(PgVAxo53r zN~J=e5t0JuT$yDmg|q^-wt%v{tJT8}-O%bkZS*Ad{6=S%19y%pA-QEr!xAk=UQ#ZN zUWz$)gKbq-=n6klQ_9qWiUkvoOy;S`GevaDpYD7F?d^V=VKCzrTseU-n+xmTUYpA= RW6|eL{sI(FHM`CQ003wLvN`|& From 8436fc333403af88e764667f709e9243d3ad97d8 Mon Sep 17 00:00:00 2001 From: Shaunak Kashyap Date: Thu, 31 May 2018 16:01:27 -0700 Subject: [PATCH 16/94] [Beats Management] APIs: Remove tag(s) from beat(s) (#19440) * Using crypto.timingSafeEqual() for comparing auth tokens * Introduce random delay after we try to find token in ES to mitigate timing attack * Remove random delay * Starting to implement POST /api/beats/beats_tags API * Changing API * Updating tests for changes to API * Renaming * Use destructuring * Using crypto.timingSafeEqual() for comparing auth tokens * Introduce random delay after we try to find token in ES to mitigate timing attack * Implementing `POST /api/beats/agents_tags/removals` API * Updating ES archive * Use destructuring * Moving start of script to own line to increase readability * Nothing to remove if there are no existing tags! * Updating tests to match changes in bulk update painless script * Use destructuring --- .../plugins/beats/server/routes/api/index.js | 2 + .../routes/api/register_enroll_beat_route.js | 9 +- .../register_remove_tags_from_beats_route.js | 166 ++++++++++++ .../apis/beats/assign_tags_to_beats.js | 8 +- .../test/api_integration/apis/beats/index.js | 1 + .../apis/beats/remove_tags_from_beats.js | 246 ++++++++++++++++++ .../es_archives/beats/list/data.json.gz | Bin 436 -> 447 bytes 7 files changed, 427 insertions(+), 5 deletions(-) create mode 100644 x-pack/plugins/beats/server/routes/api/register_remove_tags_from_beats_route.js create mode 100644 x-pack/test/api_integration/apis/beats/remove_tags_from_beats.js diff --git a/x-pack/plugins/beats/server/routes/api/index.js b/x-pack/plugins/beats/server/routes/api/index.js index aa1be44cd96ca..6ec0ad737352a 100644 --- a/x-pack/plugins/beats/server/routes/api/index.js +++ b/x-pack/plugins/beats/server/routes/api/index.js @@ -11,6 +11,7 @@ import { registerVerifyBeatsRoute } from './register_verify_beats_route'; import { registerUpdateBeatRoute } from './register_update_beat_route'; import { registerSetTagRoute } from './register_set_tag_route'; import { registerAssignTagsToBeatsRoute } from './register_assign_tags_to_beats_route'; +import { registerRemoveTagsFromBeatsRoute } from './register_remove_tags_from_beats_route'; export function registerApiRoutes(server) { registerCreateEnrollmentTokensRoute(server); @@ -20,4 +21,5 @@ export function registerApiRoutes(server) { registerUpdateBeatRoute(server); registerSetTagRoute(server); registerAssignTagsToBeatsRoute(server); + registerRemoveTagsFromBeatsRoute(server); } diff --git a/x-pack/plugins/beats/server/routes/api/register_enroll_beat_route.js b/x-pack/plugins/beats/server/routes/api/register_enroll_beat_route.js index 77742c16cd401..bad28c0ab9be5 100644 --- a/x-pack/plugins/beats/server/routes/api/register_enroll_beat_route.js +++ b/x-pack/plugins/beats/server/routes/api/register_enroll_beat_route.js @@ -24,7 +24,14 @@ async function getEnrollmentToken(callWithInternalUser, enrollmentToken) { }; const response = await callWithInternalUser('get', params); - return get(response, '_source.enrollment_token', {}); + const token = get(response, '_source.enrollment_token', {}); + + // Elasticsearch might return fast if the token is not found. OR it might return fast + // if the token *is* found. Either way, an attacker could using a timing attack to figure + // out whether a token is valid or not. So we introduce a random delay in returning from + // this function to obscure the actual time it took for Elasticsearch to find the token. + const randomDelayInMs = 25 + Math.round(Math.random() * 200); // between 25 and 225 ms + return new Promise(resolve => setTimeout(() => resolve(token), randomDelayInMs)); } function deleteUsedEnrollmentToken(callWithInternalUser, enrollmentToken) { diff --git a/x-pack/plugins/beats/server/routes/api/register_remove_tags_from_beats_route.js b/x-pack/plugins/beats/server/routes/api/register_remove_tags_from_beats_route.js new file mode 100644 index 0000000000000..b5e66267b2ea4 --- /dev/null +++ b/x-pack/plugins/beats/server/routes/api/register_remove_tags_from_beats_route.js @@ -0,0 +1,166 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Joi from 'joi'; +import { + get, + flatten, + uniq +} from 'lodash'; +import { INDEX_NAMES } from '../../../common/constants'; +import { callWithRequestFactory } from '../../lib/client'; +import { wrapEsError } from '../../lib/error_wrappers'; + +async function getDocs(callWithRequest, ids) { + const params = { + index: INDEX_NAMES.BEATS, + type: '_doc', + body: { ids }, + _source: false + }; + + const response = await callWithRequest('mget', params); + return get(response, 'docs', []); +} + +function getBeats(callWithRequest, beatIds) { + const ids = beatIds.map(beatId => `beat:${beatId}`); + return getDocs(callWithRequest, ids); +} + +function getTags(callWithRequest, tags) { + const ids = tags.map(tag => `tag:${tag}`); + return getDocs(callWithRequest, ids); +} + +async function findNonExistentItems(callWithRequest, items, getFn) { + const itemsFromEs = await getFn.call(null, callWithRequest, items); + return itemsFromEs.reduce((nonExistentItems, itemFromEs, idx) => { + if (!itemFromEs.found) { + nonExistentItems.push(items[idx]); + } + return nonExistentItems; + }, []); +} + +function findNonExistentBeatIds(callWithRequest, beatIds) { + return findNonExistentItems(callWithRequest, beatIds, getBeats); +} + +function findNonExistentTags(callWithRequest, tags) { + return findNonExistentItems(callWithRequest, tags, getTags); +} + +async function persistRemovals(callWithRequest, removals) { + const body = flatten(removals.map(({ beatId, tag }) => { + const script = '' + + 'def beat = ctx._source.beat; ' + + 'if (beat.tags != null) { ' + + ' beat.tags.removeAll([params.tag]); ' + + '}'; + + return [ + { update: { _id: `beat:${beatId}` } }, + { script: { source: script, params: { tag } } } + ]; + })); + + const params = { + index: INDEX_NAMES.BEATS, + type: '_doc', + body, + refresh: 'wait_for' + }; + + const response = await callWithRequest('bulk', params); + return get(response, 'items', []) + .map((item, resultIdx) => ({ + status: item.update.status, + result: item.update.result, + idxInRequest: removals[resultIdx].idxInRequest + })); +} + +function addNonExistentItemRemovalsToResponse(response, removals, nonExistentBeatIds, nonExistentTags) { + removals.forEach(({ beat_id: beatId, tag }, idx) => { + const isBeatNonExistent = nonExistentBeatIds.includes(beatId); + const isTagNonExistent = nonExistentTags.includes(tag); + + if (isBeatNonExistent && isTagNonExistent) { + response.removals[idx].status = 404; + response.removals[idx].result = `Beat ${beatId} and tag ${tag} not found`; + } else if (isBeatNonExistent) { + response.removals[idx].status = 404; + response.removals[idx].result = `Beat ${beatId} not found`; + } else if (isTagNonExistent) { + response.removals[idx].status = 404; + response.removals[idx].result = `Tag ${tag} not found`; + } + }); +} + +function addRemovalResultsToResponse(response, removalResults) { + removalResults.forEach(removalResult => { + const { idxInRequest, status, result } = removalResult; + response.removals[idxInRequest].status = status; + response.removals[idxInRequest].result = result; + }); +} + +// TODO: add license check pre-hook +// TODO: write to Kibana audit log file +export function registerRemoveTagsFromBeatsRoute(server) { + server.route({ + method: 'POST', + path: '/api/beats/agents_tags/removals', + config: { + validate: { + payload: Joi.object({ + removals: Joi.array().items(Joi.object({ + beat_id: Joi.string().required(), + tag: Joi.string().required() + })) + }).required() + } + }, + handler: async (request, reply) => { + const callWithRequest = callWithRequestFactory(server, request); + + const { removals } = request.payload; + const beatIds = uniq(removals.map(removal => removal.beat_id)); + const tags = uniq(removals.map(removal => removal.tag)); + + const response = { + removals: removals.map(() => ({ status: null })) + }; + + try { + // Handle removals containing non-existing beat IDs or tags + const nonExistentBeatIds = await findNonExistentBeatIds(callWithRequest, beatIds); + const nonExistentTags = await findNonExistentTags(callWithRequest, tags); + + addNonExistentItemRemovalsToResponse(response, removals, nonExistentBeatIds, nonExistentTags); + + const validRemovals = removals + .map((removal, idxInRequest) => ({ + beatId: removal.beat_id, + tag: removal.tag, + idxInRequest // so we can add the result of this removal to the correct place in the response + })) + .filter((removal, idx) => response.removals[idx].status === null); + + if (validRemovals.length > 0) { + const removalResults = await persistRemovals(callWithRequest, validRemovals); + addRemovalResultsToResponse(response, removalResults); + } + } catch (err) { + return reply(wrapEsError(err)); + } + + reply(response); + } + }); +} diff --git a/x-pack/test/api_integration/apis/beats/assign_tags_to_beats.js b/x-pack/test/api_integration/apis/beats/assign_tags_to_beats.js index a8f542239d22e..88b7b7c3feb3a 100644 --- a/x-pack/test/api_integration/apis/beats/assign_tags_to_beats.js +++ b/x-pack/test/api_integration/apis/beats/assign_tags_to_beats.js @@ -63,7 +63,7 @@ export default function ({ getService }) { }); beat = esResponse._source.beat; - expect(beat.tags).to.eql(tags); + expect(beat.tags).to.eql([...tags, 'qa']); // Adding the existing tag const { body: apiResponse } = await supertest @@ -90,7 +90,7 @@ export default function ({ getService }) { }); beat = esResponse._source.beat; - expect(beat.tags).to.eql(tags); + expect(beat.tags).to.eql([...tags, 'qa']); }); it('should add a single tag to a multiple beats', async () => { @@ -123,7 +123,7 @@ export default function ({ getService }) { }); beat = esResponse._source.beat; - expect(beat.tags).to.eql(['production', 'development']); // as beat 'foo' already had 'production' tag attached to it + expect(beat.tags).to.eql(['production', 'qa', 'development']); // as beat 'foo' already had 'production' and 'qa' tags attached to it // Beat bar esResponse = await es.get({ @@ -195,7 +195,7 @@ export default function ({ getService }) { }); beat = esResponse._source.beat; - expect(beat.tags).to.eql(['production', 'development']); // as beat 'foo' already had 'production' tag attached to it + expect(beat.tags).to.eql(['production', 'qa', 'development']); // as beat 'foo' already had 'production' and 'qa' tags attached to it // Beat bar esResponse = await es.get({ diff --git a/x-pack/test/api_integration/apis/beats/index.js b/x-pack/test/api_integration/apis/beats/index.js index 76f712c05de44..f8956d3e498ba 100644 --- a/x-pack/test/api_integration/apis/beats/index.js +++ b/x-pack/test/api_integration/apis/beats/index.js @@ -24,5 +24,6 @@ export default function ({ getService, loadTestFile }) { loadTestFile(require.resolve('./update_beat')); loadTestFile(require.resolve('./set_tag')); loadTestFile(require.resolve('./assign_tags_to_beats')); + loadTestFile(require.resolve('./remove_tags_from_beats')); }); } diff --git a/x-pack/test/api_integration/apis/beats/remove_tags_from_beats.js b/x-pack/test/api_integration/apis/beats/remove_tags_from_beats.js new file mode 100644 index 0000000000000..19583f9279732 --- /dev/null +++ b/x-pack/test/api_integration/apis/beats/remove_tags_from_beats.js @@ -0,0 +1,246 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from 'expect.js'; +import { + ES_INDEX_NAME, + ES_TYPE_NAME +} from './constants'; + +export default function ({ getService }) { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + const es = getService('es'); + const chance = getService('chance'); + + describe('remove_tags_from_beats', () => { + const archive = 'beats/list'; + + beforeEach('load beats archive', () => esArchiver.load(archive)); + afterEach('unload beats archive', () => esArchiver.unload(archive)); + + it('should remove a single tag from a single beat', async () => { + const { body: apiResponse } = await supertest + .post( + '/api/beats/agents_tags/removals' + ) + .set('kbn-xsrf', 'xxx') + .send({ + removals: [ + { beat_id: 'foo', tag: 'production' } + ] + }) + .expect(200); + + expect(apiResponse.removals).to.eql([ + { status: 200, result: 'updated' } + ]); + + const esResponse = await es.get({ + index: ES_INDEX_NAME, + type: ES_TYPE_NAME, + id: `beat:foo` + }); + + const beat = esResponse._source.beat; + expect(beat.tags).to.eql(['qa']); + }); + + it('should remove a single tag from a multiple beats', async () => { + const { body: apiResponse } = await supertest + .post( + '/api/beats/agents_tags/removals' + ) + .set('kbn-xsrf', 'xxx') + .send({ + removals: [ + { beat_id: 'foo', tag: 'development' }, + { beat_id: 'bar', tag: 'development' } + ] + }) + .expect(200); + + expect(apiResponse.removals).to.eql([ + { status: 200, result: 'updated' }, + { status: 200, result: 'updated' } + ]); + + let esResponse; + let beat; + + // Beat foo + esResponse = await es.get({ + index: ES_INDEX_NAME, + type: ES_TYPE_NAME, + id: `beat:foo` + }); + + beat = esResponse._source.beat; + expect(beat.tags).to.eql(['production', 'qa' ]); // as beat 'foo' already had 'production' and 'qa' tags attached to it + + // Beat bar + esResponse = await es.get({ + index: ES_INDEX_NAME, + type: ES_TYPE_NAME, + id: `beat:bar` + }); + + beat = esResponse._source.beat; + expect(beat).to.not.have.property('tags'); + }); + + it('should remove multiple tags from a single beat', async () => { + const { body: apiResponse } = await supertest + .post( + '/api/beats/agents_tags/removals' + ) + .set('kbn-xsrf', 'xxx') + .send({ + removals: [ + { beat_id: 'foo', tag: 'development' }, + { beat_id: 'foo', tag: 'production' } + ] + }) + .expect(200); + + expect(apiResponse.removals).to.eql([ + { status: 200, result: 'updated' }, + { status: 200, result: 'updated' } + ]); + + const esResponse = await es.get({ + index: ES_INDEX_NAME, + type: ES_TYPE_NAME, + id: `beat:foo` + }); + + const beat = esResponse._source.beat; + expect(beat.tags).to.eql(['qa']); // as beat 'foo' already had 'production' and 'qa' tags attached to it + }); + + it('should remove multiple tags from a multiple beats', async () => { + const { body: apiResponse } = await supertest + .post( + '/api/beats/agents_tags/removals' + ) + .set('kbn-xsrf', 'xxx') + .send({ + removals: [ + { beat_id: 'foo', tag: 'production' }, + { beat_id: 'bar', tag: 'development' } + ] + }) + .expect(200); + + expect(apiResponse.removals).to.eql([ + { status: 200, result: 'updated' }, + { status: 200, result: 'updated' } + ]); + + let esResponse; + let beat; + + // Beat foo + esResponse = await es.get({ + index: ES_INDEX_NAME, + type: ES_TYPE_NAME, + id: `beat:foo` + }); + + beat = esResponse._source.beat; + expect(beat.tags).to.eql(['qa']); // as beat 'foo' already had 'production' and 'qa' tags attached to it + + // Beat bar + esResponse = await es.get({ + index: ES_INDEX_NAME, + type: ES_TYPE_NAME, + id: `beat:bar` + }); + + beat = esResponse._source.beat; + expect(beat).to.not.have.property('tags'); + }); + + it('should return errors for non-existent beats', async () => { + const nonExistentBeatId = chance.word(); + + const { body: apiResponse } = await supertest + .post( + '/api/beats/agents_tags/removals' + ) + .set('kbn-xsrf', 'xxx') + .send({ + removals: [ + { beat_id: nonExistentBeatId, tag: 'production' } + ] + }) + .expect(200); + + expect(apiResponse.removals).to.eql([ + { status: 404, result: `Beat ${nonExistentBeatId} not found` } + ]); + }); + + it('should return errors for non-existent tags', async () => { + const nonExistentTag = chance.word(); + + const { body: apiResponse } = await supertest + .post( + '/api/beats/agents_tags/removals' + ) + .set('kbn-xsrf', 'xxx') + .send({ + removals: [ + { beat_id: 'bar', tag: nonExistentTag } + ] + }) + .expect(200); + + expect(apiResponse.removals).to.eql([ + { status: 404, result: `Tag ${nonExistentTag} not found` } + ]); + + const esResponse = await es.get({ + index: ES_INDEX_NAME, + type: ES_TYPE_NAME, + id: `beat:bar` + }); + + const beat = esResponse._source.beat; + expect(beat).to.not.have.property('tags'); + }); + + it('should return errors for non-existent beats and tags', async () => { + const nonExistentBeatId = chance.word(); + const nonExistentTag = chance.word(); + + const { body: apiResponse } = await supertest + .post( + '/api/beats/agents_tags/removals' + ) + .set('kbn-xsrf', 'xxx') + .send({ + removals: [ + { beat_id: nonExistentBeatId, tag: nonExistentTag } + ] + }) + .expect(200); + + expect(apiResponse.removals).to.eql([ + { status: 404, result: `Beat ${nonExistentBeatId} and tag ${nonExistentTag} not found` } + ]); + + const esResponse = await es.get({ + index: ES_INDEX_NAME, + type: ES_TYPE_NAME, + id: `beat:bar` + }); + + const beat = esResponse._source.beat; + expect(beat).to.not.have.property('tags'); + }); + }); +} diff --git a/x-pack/test/functional/es_archives/beats/list/data.json.gz b/x-pack/test/functional/es_archives/beats/list/data.json.gz index 48094292337780ce8c10a685ddda5f9446160c5c..b33ecf434c104716138ed08200cf49feaa9b73c7 100644 GIT binary patch literal 447 zcmV;w0YLsAiwFqO9SBQOZ*BnHRLgGTFbur=D-53-!Ez);@^|#qVqs7c zoi!R;sUJahgZ%qSP6E5wCOveK0DCb&a)zLW93P@MPWoS4O!7Ff&LmGEv4hPJG6x^{ zuxc#s1Ax@fz#408`h`a5yAeL?P+VFBmJOKz%io9nCEK~7HB;{yHz3cb_#92B8Lq50 z_yOx{KV8=s)i#tV$;gthzp4$?C%SV)LraXS=a|#9)1YG#jKQuediRD+C@T;~DZsR} zlIc`PN!2Q)R48L5FrA#K$LR7sM#m^R+(l#!zyF=cHTG>~ZpfT@m6wE4!mdm0C%P{6 zH14NTQz$e5lm^hVCf!w=Q}b_4A8f8V1bTRC2)#l?qm9 z7_u^DLaP;@b9dNQWrenIBQ;B@T%>$K`7;%H#`C2lDq}BmN)PMKV_)NB+d4a#zVh5B z?=wi^ACEOld%r)DU*PDL8&(NByke8~*8aX&pL&!{{NnTZ%DQOZ*Bm^RLgGTFbur=D-53-!Ez);@^|d1MPX19 zoi!R;sUJbQLH>OuCxKmTlODQAE(S=>5Y!CmLlnnJ|FOj+j}z-m@)Qy~*bE_a@PQAj z#^OEzNDU3FvBsufXoS8S;j<3KrA1)bkO{E-eb`^Jof}#+^`3D9@{Eel(S(}e%4&n3 zu)g-&b$wB7Lz$9{ED8Ik+CY7xJ4ZCM#JGBnIZZnaIwrvw?7E_NZ`g#g0%4Q_OiL@7 zPKA_Itx`&bGFAf9$(eeLF5hExjH1I`MAq=<|A|~<-&W>^%$ZktNhl@ky3~H6>rzkS zeknDTGUFUI7b!5tq+Vr$3&T+<11QReO6_;(j#B?XbU$|vy{q3$`_RXq9V_DzLZ2|?0HV)S!C2LJ#d`O}C1 From 0ffc218290a80cf3312c205db0562035771dcc99 Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Tue, 26 Jun 2018 10:48:27 -0400 Subject: [PATCH 17/94] [Beats Management] Move to Ingest UI arch and initial TS effort (#20039) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Beats Management] Initial scaffolding for plugin (#18977) * Initial scaffolding for Beats plugin * Removing bits not (yet) necessary in initial scaffolding * [Beats Management] Install Beats index template on plugin init (#19072) * Install Beats index template on plugin init * Adding missing files * [Beats Management] APIs: Create enrollment tokens (#19018) * WIP checkin * Register API routes * Fixing typo in index name * Adding TODOs * Removing commented out license checking code that isn't yet implemented * Remove unnecessary async/await * Don't return until indices have been refreshed * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Adding TODO * Fixing variable name * Using a single index * Adding expiration date field * Adding test for expiration date field * Ignore non-existent index * Fixing logic in test * Creating constant for default enrollment tokens TTL value * Updating test * Fixing name of test file (#19100) * [Beats Management] APIs: Enroll beat (#19056) * WIP checkin * Add API integration test * Converting to Jest test * Create API for enrolling a beat * Handle invalid or expired enrollment tokens * Use create instead of index to prevent same beat from being enrolled twice * Adding unit test for duplicate beat enrollment * Do not persist enrollment token with beat once token has been checked and used * Fix datatype of host_ip field * Make Kibana API guess host IP instead of requiring it in payload * Fixing error introduced in rebase conflict resolution * [Beats Management] APIs: List beats (#19086) * WIP checkin * Add API integration test * Converting to Jest test * WIP checkin * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Updating mapping * [Beats Management] APIs: Verify beats (#19103) * WIP checkin * WIP checkin * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Creating POST /api/beats/agents/verify API * Refactoring: extracting out helper functions * Fleshing out remaining tests * Expanding TODO note so I won't forget :) * Fixing file name * Updating mapping * Moving TODO comment to right file * Rename determine* helper functions to find* * Fixing assertions (#19194) * [Beats Management] APIs: Update beat (#19148) * WIP checkin * WIP checkin * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Creating POST /api/beats/agents/verify API * Refactoring: extracting out helper functions * Expanding TODO note so I won't forget :) * Fixing file name * Updating mapping * Add API tests * Update template to allow version field for beat * Implement PUT /api/beats/agent/{beat ID} API * Make enroll beat code consistent with update beat code * Fixing minor typo in TODO comment * Allow version in request payload * Make sure beat is not updated in ES in error scenarios * Adding version as required field in Enroll Beat API payload * Using destructuring * Fixing rename that was accidentally reversed in conflict fixing * [Beats Management] APIs: take auth tokens via headers (#19210) * WIP checkin * WIP checkin * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Creating POST /api/beats/agents/verify API * Refactoring: extracting out helper functions * Expanding TODO note so I won't forget :) * Fixing file name * Updating mapping * Fixing minor typo in TODO comment * Make "Enroll Beat" API take enrollment token via header instead of request body * Make "Update Beat" API take access token via header instead of request body * [Beats Management] APIs: Create configuration block (#19270) * WIP checkin * WIP checkin * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Creating POST /api/beats/agents/verify API * Refactoring: extracting out helper functions * Expanding TODO note so I won't forget :) * Fixing file name * Updating mapping * Fixing minor typo in TODO comment * Implementing POST /api/beats/configuration_blocks API * Removing unnecessary escaping * Fleshing out types + adding validation for them * Making output singular (was outputs) * Removing metricbeat.inputs * Revert implementation of `POST /api/beats/configuration_blocks` API (#19340) This API allowed the user to operate at a level of abstraction that is unnecessarily and dangerously too low. A better API would be at one level higher, where users can create, update, and delete tags (where a tag can contain multiple configuration blocks). * [Beats Management] APIs: Create or update tag (#19342) * Updating mappings * Implementing PUT /api/beats/tag/{tag} API * [Beats Management] Prevent timing attacks when checking auth tokens (#19363) * Using crypto.timingSafeEqual() for comparing auth tokens * Prevent subtler timing attack in token comparison function * Introduce random delay after we try to find token in ES to mitigate timing attack * Remove random delay * [Beats Management] APIs: Assign tag(s) to beat(s) (#19431) * Using crypto.timingSafeEqual() for comparing auth tokens * Introduce random delay after we try to find token in ES to mitigate timing attack * Rename "determine" to "find" * Remove random delay * Starting to implement POST /api/beats/beats_tags API * Changing API * Updating tests for changes to API * Updating ES archive * Renaming * Use destructuring * Moving start of script to own line to increase readability * Using destructuring * [Beats Management] APIs: Remove tag(s) from beat(s) (#19440) * Using crypto.timingSafeEqual() for comparing auth tokens * Introduce random delay after we try to find token in ES to mitigate timing attack * Remove random delay * Starting to implement POST /api/beats/beats_tags API * Changing API * Updating tests for changes to API * Renaming * Use destructuring * Using crypto.timingSafeEqual() for comparing auth tokens * Introduce random delay after we try to find token in ES to mitigate timing attack * Implementing `POST /api/beats/agents_tags/removals` API * Updating ES archive * Use destructuring * Moving start of script to own line to increase readability * Nothing to remove if there are no existing tags! * Updating tests to match changes in bulk update painless script * Use destructuring * Ported over base types and arch structure * move management of installIndexTemplate into the framework adapter * ts-lint fix * tslint fixes * more ts tweaks * fix paths * added several working endpoints * add more routes and bug fixes * fix linting * fix type remove CRUFT * remove more cruft * remove more CRUFT * added comments, change plurality * add tsconfig file * add extends path * fixed typo * serveral PR review fixes * fixed lodash type version * “fix” types by applying a lot of any --- package.json | 2 +- tsconfig.json | 15 +- x-pack/package.json | 7 +- .../common/constants/configuration_blocks.js | 19 -- .../common/constants/configuration_blocks.ts | 15 + .../common/constants/{index.js => index.ts} | 5 +- .../{index_names.js => index_names.ts} | 2 +- .../common/constants/{plugin.js => plugin.ts} | 2 +- x-pack/plugins/beats/{index.js => index.ts} | 27 +- x-pack/plugins/beats/server/kibana.index.ts | 14 + .../beats/elasticsearch_beats_adapter.ts | 218 +++++++++++++++ .../kibana/kibana_framework_adapter.ts | 82 ++++++ .../tags/elasticsearch_tags_adapter.ts | 57 ++++ .../tokens/elasticsearch_tokens_adapter.ts | 83 ++++++ .../client/call_with_internal_user_factory.js | 16 -- .../lib/client/call_with_request_factory.js | 18 -- .../plugins/beats/server/lib/client/index.js | 8 - .../beats/server/lib/compose/kibana.ts | 45 +++ .../server/lib/crypto/are_tokens_equal.js | 21 -- .../plugins/beats/server/lib/domains/beats.ts | 259 ++++++++++++++++++ .../plugins/beats/server/lib/domains/tags.ts | 90 ++++++ .../beats/server/lib/domains/tokens.ts | 80 ++++++ .../index_template/install_index_template.js | 18 -- x-pack/plugins/beats/server/lib/lib.ts | 212 ++++++++++++++ .../plugins/beats/server/management_server.ts | 30 ++ .../beats/server/rest_api/beats/enroll.ts | 63 +++++ .../beats/server/rest_api/beats/list.ts | 23 ++ .../server/rest_api/beats/tag_assignment.ts | 48 ++++ .../server/rest_api/beats/tag_removal.ts | 48 ++++ .../beats/server/rest_api/beats/update.ts | 62 +++++ .../beats/server/rest_api/beats/verify.ts | 73 +++++ .../plugins/beats/server/rest_api/tags/set.ts | 57 ++++ .../beats/server/rest_api/tokens/create.ts | 42 +++ .../plugins/beats/server/routes/api/index.js | 25 -- .../register_assign_tags_to_beats_route.js | 169 ------------ ...register_create_enrollment_tokens_route.js | 70 ----- .../routes/api/register_enroll_beat_route.js | 115 -------- .../routes/api/register_list_beats_route.js | 47 ---- .../register_remove_tags_from_beats_route.js | 166 ----------- .../routes/api/register_set_tag_route.js | 124 --------- .../routes/api/register_update_beat_route.js | 101 ------- .../routes/api/register_verify_beats_route.js | 143 ---------- x-pack/plugins/beats/server/utils/README.md | 1 + .../error_wrappers/index.ts} | 0 .../error_wrappers/wrap_es_error.test.js | 5 +- .../error_wrappers/wrap_es_error.ts} | 6 +- .../server/utils/find_non_existent_items.ts | 14 + .../index_templates}/beats_template.json | 4 +- .../index_templates/index.ts} | 3 +- .../plugins/beats/server/utils/polyfills.ts | 17 ++ .../beats/server/utils/wrap_request.ts | 24 ++ x-pack/plugins/beats/tsconfig.json | 3 + .../lib/crypto/index.js => types/json.t.ts} | 5 +- x-pack/plugins/beats/wallaby.js | 27 ++ x-pack/yarn.lock | 30 +- yarn.lock | 17 +- 56 files changed, 1771 insertions(+), 1106 deletions(-) delete mode 100644 x-pack/plugins/beats/common/constants/configuration_blocks.js create mode 100644 x-pack/plugins/beats/common/constants/configuration_blocks.ts rename x-pack/plugins/beats/common/constants/{index.js => index.ts} (76%) rename x-pack/plugins/beats/common/constants/{index_names.js => index_names.ts} (90%) rename x-pack/plugins/beats/common/constants/{plugin.js => plugin.ts} (94%) rename x-pack/plugins/beats/{index.js => index.ts} (51%) create mode 100644 x-pack/plugins/beats/server/kibana.index.ts create mode 100644 x-pack/plugins/beats/server/lib/adapters/beats/elasticsearch_beats_adapter.ts create mode 100644 x-pack/plugins/beats/server/lib/adapters/famework/kibana/kibana_framework_adapter.ts create mode 100644 x-pack/plugins/beats/server/lib/adapters/tags/elasticsearch_tags_adapter.ts create mode 100644 x-pack/plugins/beats/server/lib/adapters/tokens/elasticsearch_tokens_adapter.ts delete mode 100644 x-pack/plugins/beats/server/lib/client/call_with_internal_user_factory.js delete mode 100644 x-pack/plugins/beats/server/lib/client/call_with_request_factory.js delete mode 100644 x-pack/plugins/beats/server/lib/client/index.js create mode 100644 x-pack/plugins/beats/server/lib/compose/kibana.ts delete mode 100644 x-pack/plugins/beats/server/lib/crypto/are_tokens_equal.js create mode 100644 x-pack/plugins/beats/server/lib/domains/beats.ts create mode 100644 x-pack/plugins/beats/server/lib/domains/tags.ts create mode 100644 x-pack/plugins/beats/server/lib/domains/tokens.ts delete mode 100644 x-pack/plugins/beats/server/lib/index_template/install_index_template.js create mode 100644 x-pack/plugins/beats/server/lib/lib.ts create mode 100644 x-pack/plugins/beats/server/management_server.ts create mode 100644 x-pack/plugins/beats/server/rest_api/beats/enroll.ts create mode 100644 x-pack/plugins/beats/server/rest_api/beats/list.ts create mode 100644 x-pack/plugins/beats/server/rest_api/beats/tag_assignment.ts create mode 100644 x-pack/plugins/beats/server/rest_api/beats/tag_removal.ts create mode 100644 x-pack/plugins/beats/server/rest_api/beats/update.ts create mode 100644 x-pack/plugins/beats/server/rest_api/beats/verify.ts create mode 100644 x-pack/plugins/beats/server/rest_api/tags/set.ts create mode 100644 x-pack/plugins/beats/server/rest_api/tokens/create.ts delete mode 100644 x-pack/plugins/beats/server/routes/api/index.js delete mode 100644 x-pack/plugins/beats/server/routes/api/register_assign_tags_to_beats_route.js delete mode 100644 x-pack/plugins/beats/server/routes/api/register_create_enrollment_tokens_route.js delete mode 100644 x-pack/plugins/beats/server/routes/api/register_enroll_beat_route.js delete mode 100644 x-pack/plugins/beats/server/routes/api/register_list_beats_route.js delete mode 100644 x-pack/plugins/beats/server/routes/api/register_remove_tags_from_beats_route.js delete mode 100644 x-pack/plugins/beats/server/routes/api/register_set_tag_route.js delete mode 100644 x-pack/plugins/beats/server/routes/api/register_update_beat_route.js delete mode 100644 x-pack/plugins/beats/server/routes/api/register_verify_beats_route.js create mode 100644 x-pack/plugins/beats/server/utils/README.md rename x-pack/plugins/beats/server/{lib/error_wrappers/index.js => utils/error_wrappers/index.ts} (100%) rename x-pack/plugins/beats/server/{lib => utils}/error_wrappers/wrap_es_error.test.js (90%) rename x-pack/plugins/beats/server/{lib/error_wrappers/wrap_es_error.js => utils/error_wrappers/wrap_es_error.ts} (79%) create mode 100644 x-pack/plugins/beats/server/utils/find_non_existent_items.ts rename x-pack/plugins/beats/server/{lib/index_template => utils/index_templates}/beats_template.json (97%) rename x-pack/plugins/beats/server/{lib/index_template/index.js => utils/index_templates/index.ts} (73%) create mode 100644 x-pack/plugins/beats/server/utils/polyfills.ts create mode 100644 x-pack/plugins/beats/server/utils/wrap_request.ts create mode 100644 x-pack/plugins/beats/tsconfig.json rename x-pack/plugins/beats/{server/lib/crypto/index.js => types/json.t.ts} (77%) create mode 100644 x-pack/plugins/beats/wallaby.js diff --git a/package.json b/package.json index 4226f06b3599b..8041ee396c2d7 100644 --- a/package.json +++ b/package.json @@ -323,7 +323,7 @@ "tree-kill": "^1.1.0", "ts-jest": "^22.4.6", "ts-loader": "^3.5.0", - "ts-node": "^6.0.3", + "ts-node": "^6.1.1", "tslint": "^5.10.0", "tslint-config-prettier": "^1.12.0", "tslint-plugin-prettier": "^1.3.0", diff --git a/tsconfig.json b/tsconfig.json index 5a804d5bb96be..4af8f79d4f380 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,14 +2,14 @@ "compilerOptions": { "baseUrl": ".", "paths": { - "ui/*": ["src/ui/public/*"] + "ui/*": [ + "src/ui/public/*" + ] }, // Support .tsx files and transform JSX into calls to React.createElement "jsx": "react", - // Enables all strict type checking options. "strict": true, - // enables "core language features" "lib": [ // ESNext auto includes previous versions all the way back to es5 @@ -17,30 +17,23 @@ // includes support for browser APIs "dom" ], - // Node 8 should support everything output by esnext, we override this // in webpack with loader-level compiler options "target": "esnext", - // Use commonjs for node, overridden in webpack to keep import statements // to maintain support for things like `await import()` "module": "commonjs", - // Allows default imports from modules with no default export. This does not affect code emit, just type checking. // We have to enable this option explicitly since `esModuleInterop` doesn't enable it automatically when ES2015 or // ESNext module format is used. "allowSyntheticDefaultImports": true, - // Emits __importStar and __importDefault helpers for runtime babel ecosystem compatibility. "esModuleInterop": true, - // Resolve modules in the same way as Node.js. Aka make `require` works the // same in TypeScript as it does in Node.js. "moduleResolution": "node", - // Disallow inconsistently-cased references to the same file. "forceConsistentCasingInFileNames": true, - // Disable the breaking keyof behaviour introduced in TS 2.9.2 until EUI is updated to support that too "keyofStringsOnly": true }, @@ -55,4 +48,4 @@ // the tsconfig.json file for public files correctly. // "src/**/public/**/*" ] -} +} \ No newline at end of file diff --git a/x-pack/package.json b/x-pack/package.json index edb4d4c5313fd..405a499113089 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -27,8 +27,12 @@ "@kbn/es": "link:../packages/kbn-es", "@kbn/plugin-helpers": "link:../packages/kbn-plugin-helpers", "@kbn/test": "link:../packages/kbn-test", + "@types/boom": "^4.3.8", + "@types/hapi": "15.0.1", "@types/jest": "^22.2.3", - "@types/pngjs": "^3.3.1", + "@types/joi": "^10.4.0", + "@types/lodash": "^3.10.0", + "@types/pngjs": "^3.3.0", "abab": "^1.0.4", "ansicolors": "0.3.2", "aws-sdk": "2.2.33", @@ -88,6 +92,7 @@ "@kbn/ui-framework": "link:../packages/kbn-ui-framework", "@samverschueren/stream-to-observable": "^0.3.0", "@slack/client": "^4.2.2", + "@types/uuid": "^3.4.3", "angular-paging": "2.2.1", "angular-resource": "1.4.9", "angular-sanitize": "1.4.9", diff --git a/x-pack/plugins/beats/common/constants/configuration_blocks.js b/x-pack/plugins/beats/common/constants/configuration_blocks.js deleted file mode 100644 index 1818b75335f3a..0000000000000 --- a/x-pack/plugins/beats/common/constants/configuration_blocks.js +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export const CONFIGURATION_BLOCKS = { - TYPES: { - OUTPUT: 'output', - PROCESSORS: 'processors', - FILEBEAT_INPUTS: 'filebeat.inputs', - FILEBEAT_MODULES: 'filebeat.modules', - METRICBEAT_MODULES: 'metricbeat.modules' - } -}; - -CONFIGURATION_BLOCKS.UNIQUENESS_ENFORCING_TYPES = [ - CONFIGURATION_BLOCKS.TYPES.OUTPUT -]; diff --git a/x-pack/plugins/beats/common/constants/configuration_blocks.ts b/x-pack/plugins/beats/common/constants/configuration_blocks.ts new file mode 100644 index 0000000000000..e89e53e25b89d --- /dev/null +++ b/x-pack/plugins/beats/common/constants/configuration_blocks.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export enum ConfigurationBlockTypes { + FilebeatInputs = 'filebeat.inputs', + FilebeatModules = 'filebeat.modules', + MetricbeatModules = 'metricbeat.modules', + Output = 'output', + Processors = 'processors', +} + +export const UNIQUENESS_ENFORCING_TYPES = [ConfigurationBlockTypes.Output]; diff --git a/x-pack/plugins/beats/common/constants/index.js b/x-pack/plugins/beats/common/constants/index.ts similarity index 76% rename from x-pack/plugins/beats/common/constants/index.js rename to x-pack/plugins/beats/common/constants/index.ts index 77c41be579c33..4662865e208a7 100644 --- a/x-pack/plugins/beats/common/constants/index.js +++ b/x-pack/plugins/beats/common/constants/index.ts @@ -6,4 +6,7 @@ export { PLUGIN } from './plugin'; export { INDEX_NAMES } from './index_names'; -export { CONFIGURATION_BLOCKS } from './configuration_blocks'; +export { + UNIQUENESS_ENFORCING_TYPES, + ConfigurationBlockTypes, +} from './configuration_blocks'; diff --git a/x-pack/plugins/beats/common/constants/index_names.js b/x-pack/plugins/beats/common/constants/index_names.ts similarity index 90% rename from x-pack/plugins/beats/common/constants/index_names.js rename to x-pack/plugins/beats/common/constants/index_names.ts index e63e8b08a6ef4..f8d20fb79c360 100644 --- a/x-pack/plugins/beats/common/constants/index_names.js +++ b/x-pack/plugins/beats/common/constants/index_names.ts @@ -5,5 +5,5 @@ */ export const INDEX_NAMES = { - BEATS: '.management-beats' + BEATS: '.management-beats', }; diff --git a/x-pack/plugins/beats/common/constants/plugin.js b/x-pack/plugins/beats/common/constants/plugin.ts similarity index 94% rename from x-pack/plugins/beats/common/constants/plugin.js rename to x-pack/plugins/beats/common/constants/plugin.ts index 289bc488c58a6..ba12300075bf2 100644 --- a/x-pack/plugins/beats/common/constants/plugin.js +++ b/x-pack/plugins/beats/common/constants/plugin.ts @@ -5,5 +5,5 @@ */ export const PLUGIN = { - ID: 'beats' + ID: 'beats', }; diff --git a/x-pack/plugins/beats/index.js b/x-pack/plugins/beats/index.ts similarity index 51% rename from x-pack/plugins/beats/index.js rename to x-pack/plugins/beats/index.ts index c105813e36ff6..ce9b8147dbe4b 100644 --- a/x-pack/plugins/beats/index.js +++ b/x-pack/plugins/beats/index.ts @@ -4,24 +4,27 @@ * you may not use this file except in compliance with the Elastic License. */ -import { installIndexTemplate } from './server/lib/index_template'; -import { registerApiRoutes } from './server/routes/api'; +import Joi from 'joi'; import { PLUGIN } from './common/constants'; +import { initServerWithKibana } from './server/kibana.index'; const DEFAULT_ENROLLMENT_TOKENS_TTL_S = 10 * 60; // 10 minutes -export function beats(kibana) { +export function beats(kibana: any) { return new kibana.Plugin({ + config: () => + Joi.object({ + enabled: Joi.boolean().default(true), + enrollmentTokensTtlInSeconds: Joi.number() + .integer() + .min(1) + .default(DEFAULT_ENROLLMENT_TOKENS_TTL_S), + }).default(), + configPrefix: 'xpack.beats', id: PLUGIN.ID, require: ['kibana', 'elasticsearch', 'xpack_main'], - configPrefix: 'xpack.beats', - config: Joi => Joi.object({ - enabled: Joi.boolean().default(true), - enrollmentTokensTtlInSeconds: Joi.number().integer().min(1).default(DEFAULT_ENROLLMENT_TOKENS_TTL_S) - }).default(), - init: async function (server) { - await installIndexTemplate(server); - registerApiRoutes(server); - } + init(server: any) { + initServerWithKibana(server); + }, }); } diff --git a/x-pack/plugins/beats/server/kibana.index.ts b/x-pack/plugins/beats/server/kibana.index.ts new file mode 100644 index 0000000000000..c9bc9b8bf02f4 --- /dev/null +++ b/x-pack/plugins/beats/server/kibana.index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Server } from 'hapi'; +import { compose } from './lib/compose/kibana'; +import { initManagementServer } from './management_server'; + +export const initServerWithKibana = (hapiServer: Server) => { + const libs = compose(hapiServer); + initManagementServer(libs); +}; diff --git a/x-pack/plugins/beats/server/lib/adapters/beats/elasticsearch_beats_adapter.ts b/x-pack/plugins/beats/server/lib/adapters/beats/elasticsearch_beats_adapter.ts new file mode 100644 index 0000000000000..283f65c1258ae --- /dev/null +++ b/x-pack/plugins/beats/server/lib/adapters/beats/elasticsearch_beats_adapter.ts @@ -0,0 +1,218 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { flatten, get, omit } from 'lodash'; +import moment from 'moment'; +import { INDEX_NAMES } from '../../../../common/constants'; +import { + BackendFrameworkAdapter, + CMBeat, + CMBeatsAdapter, + CMTagAssignment, + FrameworkRequest, +} from '../../lib'; + +export class ElasticsearchBeatsAdapter implements CMBeatsAdapter { + private framework: BackendFrameworkAdapter; + + constructor(framework: BackendFrameworkAdapter) { + this.framework = framework; + } + + public async get(id: string) { + const params = { + id: `beat:${id}`, + ignore: [404], + index: INDEX_NAMES.BEATS, + type: '_doc', + }; + + const response = await this.framework.callWithInternalUser('get', params); + if (!response.found) { + return null; + } + + return get(response, '_source.beat'); + } + + public async insert(beat: CMBeat) { + const body = { + beat, + type: 'beat', + }; + + const params = { + body, + id: `beat:${beat.id}`, + index: INDEX_NAMES.BEATS, + refresh: 'wait_for', + type: '_doc', + }; + await this.framework.callWithInternalUser('create', params); + } + + public async update(beat: CMBeat) { + const body = { + beat, + type: 'beat', + }; + + const params = { + body, + id: `beat:${beat.id}`, + index: INDEX_NAMES.BEATS, + refresh: 'wait_for', + type: '_doc', + }; + return await this.framework.callWithInternalUser('index', params); + } + + public async getWithIds(req: FrameworkRequest, beatIds: string[]) { + const ids = beatIds.map(beatId => `beat:${beatId}`); + + const params = { + _source: false, + body: { + ids, + }, + index: INDEX_NAMES.BEATS, + type: '_doc', + }; + const response = await this.framework.callWithRequest(req, 'mget', params); + return get(response, 'docs', []); + } + + // TODO merge with getBeatsWithIds + public async getVerifiedWithIds(req: FrameworkRequest, beatIds: string[]) { + const ids = beatIds.map(beatId => `beat:${beatId}`); + + const params = { + _sourceInclude: ['beat.id', 'beat.verified_on'], + body: { + ids, + }, + index: INDEX_NAMES.BEATS, + type: '_doc', + }; + const response = await this.framework.callWithRequest(req, 'mget', params); + return get(response, 'docs', []); + } + + public async verifyBeats(req: FrameworkRequest, beatIds: string[]) { + if (!Array.isArray(beatIds) || beatIds.length === 0) { + return []; + } + + const verifiedOn = moment().toJSON(); + const body = flatten( + beatIds.map(beatId => [ + { update: { _id: `beat:${beatId}` } }, + { doc: { beat: { verified_on: verifiedOn } } }, + ]) + ); + + const params = { + body, + index: INDEX_NAMES.BEATS, + refresh: 'wait_for', + type: '_doc', + }; + + const response = await this.framework.callWithRequest(req, 'bulk', params); + return get(response, 'items', []); + } + + public async getAll(req: FrameworkRequest) { + const params = { + index: INDEX_NAMES.BEATS, + q: 'type:beat', + type: '_doc', + }; + const response = await this.framework.callWithRequest( + req, + 'search', + params + ); + + const beats = get(response, 'hits.hits', []); + return beats.map((beat: any) => omit(beat._source.beat, ['access_token'])); + } + + public async removeTagsFromBeats( + req: FrameworkRequest, + removals: CMTagAssignment[] + ): Promise { + const body = flatten( + removals.map(({ beatId, tag }) => { + const script = + '' + + 'def beat = ctx._source.beat; ' + + 'if (beat.tags != null) { ' + + ' beat.tags.removeAll([params.tag]); ' + + '}'; + + return [ + { update: { _id: `beat:${beatId}` } }, + { script: { source: script, params: { tag } } }, + ]; + }) + ); + + const params = { + body, + index: INDEX_NAMES.BEATS, + refresh: 'wait_for', + type: '_doc', + }; + + const response = await this.framework.callWithRequest(req, 'bulk', params); + return get(response, 'items', []).map( + (item: any, resultIdx: number) => ({ + idxInRequest: removals[resultIdx].idxInRequest, + result: item.update.result, + status: item.update.status, + }) + ); + } + + public async assignTagsToBeats( + req: FrameworkRequest, + assignments: CMTagAssignment[] + ): Promise { + const body = flatten( + assignments.map(({ beatId, tag }) => { + const script = + '' + + 'def beat = ctx._source.beat; ' + + 'if (beat.tags == null) { ' + + ' beat.tags = []; ' + + '} ' + + 'if (!beat.tags.contains(params.tag)) { ' + + ' beat.tags.add(params.tag); ' + + '}'; + + return [ + { update: { _id: `beat:${beatId}` } }, + { script: { source: script, params: { tag } } }, + ]; + }) + ); + + const params = { + body, + index: INDEX_NAMES.BEATS, + refresh: 'wait_for', + type: '_doc', + }; + + const response = await this.framework.callWithRequest(req, 'bulk', params); + return get(response, 'items', []).map((item: any, resultIdx: any) => ({ + idxInRequest: assignments[resultIdx].idxInRequest, + result: item.update.result, + status: item.update.status, + })); + } +} diff --git a/x-pack/plugins/beats/server/lib/adapters/famework/kibana/kibana_framework_adapter.ts b/x-pack/plugins/beats/server/lib/adapters/famework/kibana/kibana_framework_adapter.ts new file mode 100644 index 0000000000000..6fc2fc4853b03 --- /dev/null +++ b/x-pack/plugins/beats/server/lib/adapters/famework/kibana/kibana_framework_adapter.ts @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + BackendFrameworkAdapter, + FrameworkRequest, + FrameworkRouteOptions, + WrappableRequest, +} from '../../../lib'; + +import { IStrictReply, Request, Server } from 'hapi'; +import { + internalFrameworkRequest, + wrapRequest, +} from '../../../../utils/wrap_request'; + +export class KibanaBackendFrameworkAdapter implements BackendFrameworkAdapter { + public version: string; + + private server: Server; + + constructor(hapiServer: Server) { + this.server = hapiServer; + this.version = hapiServer.plugins.kibana.status.plugin.version; + } + + public getSetting(settingPath: string) { + // TODO type check this properly + // @ts-ignore + return this.server.config().get(settingPath); + } + + public exposeStaticDir(urlPath: string, dir: string): void { + this.server.route({ + handler: { + directory: { + path: dir, + }, + }, + method: 'GET', + path: urlPath, + }); + } + + public registerRoute( + route: FrameworkRouteOptions + ) { + const wrappedHandler = (request: any, reply: IStrictReply) => + route.handler(wrapRequest(request), reply); + + this.server.route({ + config: route.config, + handler: wrappedHandler, + method: route.method, + path: route.path, + }); + } + + public installIndexTemplate(name: string, template: {}) { + return this.callWithInternalUser('indices.putTemplate', { + body: template, + name, + }); + } + + public async callWithInternalUser(esMethod: string, options: {}) { + const { elasticsearch } = this.server.plugins; + const { callWithInternalUser } = elasticsearch.getCluster('admin'); + return await callWithInternalUser(esMethod, options); + } + + public async callWithRequest(req: FrameworkRequest, ...rest: any[]) { + const internalRequest = req[internalFrameworkRequest]; + const { elasticsearch } = internalRequest.server.plugins; + const { callWithRequest } = elasticsearch.getCluster('data'); + const fields = await callWithRequest(internalRequest, ...rest); + return fields; + } +} diff --git a/x-pack/plugins/beats/server/lib/adapters/tags/elasticsearch_tags_adapter.ts b/x-pack/plugins/beats/server/lib/adapters/tags/elasticsearch_tags_adapter.ts new file mode 100644 index 0000000000000..2293ba77677fd --- /dev/null +++ b/x-pack/plugins/beats/server/lib/adapters/tags/elasticsearch_tags_adapter.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { get } from 'lodash'; +import { INDEX_NAMES } from '../../../../common/constants'; +import { + BackendFrameworkAdapter, + BeatTag, + CMTagsAdapter, + FrameworkRequest, +} from '../../lib'; + +export class ElasticsearchTagsAdapter implements CMTagsAdapter { + private framework: BackendFrameworkAdapter; + + constructor(framework: BackendFrameworkAdapter) { + this.framework = framework; + } + + public async getTagsWithIds(req: FrameworkRequest, tagIds: string[]) { + const ids = tagIds.map(tag => `tag:${tag}`); + + // TODO abstract to kibana adapter as the more generic getDocs + const params = { + _source: false, + body: { + ids, + }, + index: INDEX_NAMES.BEATS, + type: '_doc', + }; + const response = await this.framework.callWithRequest(req, 'mget', params); + return get(response, 'docs', []); + } + + public async upsertTag(req: FrameworkRequest, tag: BeatTag) { + const body = { + tag, + type: 'tag', + }; + + const params = { + body, + id: `tag:${tag.id}`, + index: INDEX_NAMES.BEATS, + refresh: 'wait_for', + type: '_doc', + }; + const response = await this.framework.callWithRequest(req, 'index', params); + + // TODO this is not something that works for TS... change this return type + return get(response, 'result'); + } +} diff --git a/x-pack/plugins/beats/server/lib/adapters/tokens/elasticsearch_tokens_adapter.ts b/x-pack/plugins/beats/server/lib/adapters/tokens/elasticsearch_tokens_adapter.ts new file mode 100644 index 0000000000000..c8969c7ab08d0 --- /dev/null +++ b/x-pack/plugins/beats/server/lib/adapters/tokens/elasticsearch_tokens_adapter.ts @@ -0,0 +1,83 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { flatten, get } from 'lodash'; +import { INDEX_NAMES } from '../../../../common/constants'; +import { + BackendFrameworkAdapter, + CMTokensAdapter, + EnrollmentToken, + FrameworkRequest, +} from '../../lib'; + +export class ElasticsearchTokensAdapter implements CMTokensAdapter { + private framework: BackendFrameworkAdapter; + + constructor(framework: BackendFrameworkAdapter) { + this.framework = framework; + } + + public async deleteEnrollmentToken(enrollmentToken: string) { + const params = { + id: `enrollment_token:${enrollmentToken}`, + index: INDEX_NAMES.BEATS, + type: '_doc', + }; + + return this.framework.callWithInternalUser('delete', params); + } + + public async getEnrollmentToken( + tokenString: string + ): Promise { + const params = { + id: `enrollment_token:${tokenString}`, + ignore: [404], + index: INDEX_NAMES.BEATS, + type: '_doc', + }; + + const response = await this.framework.callWithInternalUser('get', params); + const tokenDetails = get( + response, + '_source.enrollment_token', + { + expires_on: '0', + token: null, + } + ); + + // Elasticsearch might return fast if the token is not found. OR it might return fast + // if the token *is* found. Either way, an attacker could using a timing attack to figure + // out whether a token is valid or not. So we introduce a random delay in returning from + // this function to obscure the actual time it took for Elasticsearch to find the token. + const randomDelayInMs = 25 + Math.round(Math.random() * 200); // between 25 and 225 ms + return new Promise(resolve => + setTimeout(() => resolve(tokenDetails), randomDelayInMs) + ); + } + + public async upsertTokens(req: FrameworkRequest, tokens: EnrollmentToken[]) { + const body = flatten( + tokens.map(token => [ + { index: { _id: `enrollment_token:${token.token}` } }, + { + enrollment_token: token, + type: 'enrollment_token', + }, + ]) + ); + + const params = { + body, + index: INDEX_NAMES.BEATS, + refresh: 'wait_for', + type: '_doc', + }; + + await this.framework.callWithRequest(req, 'bulk', params); + } +} diff --git a/x-pack/plugins/beats/server/lib/client/call_with_internal_user_factory.js b/x-pack/plugins/beats/server/lib/client/call_with_internal_user_factory.js deleted file mode 100644 index 8b5dbed773430..0000000000000 --- a/x-pack/plugins/beats/server/lib/client/call_with_internal_user_factory.js +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { once } from 'lodash'; - -const callWithInternalUser = once((server) => { - const { callWithInternalUser } = server.plugins.elasticsearch.getCluster('admin'); - return callWithInternalUser; -}); - -export const callWithInternalUserFactory = (server) => { - return callWithInternalUser(server); -}; diff --git a/x-pack/plugins/beats/server/lib/client/call_with_request_factory.js b/x-pack/plugins/beats/server/lib/client/call_with_request_factory.js deleted file mode 100644 index c81670ed0cdec..0000000000000 --- a/x-pack/plugins/beats/server/lib/client/call_with_request_factory.js +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { once } from 'lodash'; - -const callWithRequest = once((server) => { - const { callWithRequest } = server.plugins.elasticsearch.getCluster('admin'); - return callWithRequest; -}); - -export const callWithRequestFactory = (server, request) => { - return (...args) => { - return callWithRequest(server)(request, ...args); - }; -}; diff --git a/x-pack/plugins/beats/server/lib/client/index.js b/x-pack/plugins/beats/server/lib/client/index.js deleted file mode 100644 index cdeee091cc66f..0000000000000 --- a/x-pack/plugins/beats/server/lib/client/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { callWithRequestFactory } from './call_with_request_factory'; -export { callWithInternalUserFactory } from './call_with_internal_user_factory'; diff --git a/x-pack/plugins/beats/server/lib/compose/kibana.ts b/x-pack/plugins/beats/server/lib/compose/kibana.ts new file mode 100644 index 0000000000000..ff478646aea89 --- /dev/null +++ b/x-pack/plugins/beats/server/lib/compose/kibana.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ElasticsearchBeatsAdapter } from '../adapters/beats/elasticsearch_beats_adapter'; +import { ElasticsearchTagsAdapter } from '../adapters/tags/elasticsearch_tags_adapter'; +import { ElasticsearchTokensAdapter } from '../adapters/tokens/elasticsearch_tokens_adapter'; + +import { KibanaBackendFrameworkAdapter } from '../adapters/famework/kibana/kibana_framework_adapter'; + +import { CMBeatsDomain } from '../domains/beats'; +import { CMTagsDomain } from '../domains/tags'; +import { CMTokensDomain } from '../domains/tokens'; + +import { CMDomainLibs, CMServerLibs } from '../lib'; + +import { Server } from 'hapi'; + +export function compose(server: Server): CMServerLibs { + const framework = new KibanaBackendFrameworkAdapter(server); + + const tags = new CMTagsDomain(new ElasticsearchTagsAdapter(framework)); + const tokens = new CMTokensDomain(new ElasticsearchTokensAdapter(framework), { + framework, + }); + const beats = new CMBeatsDomain(new ElasticsearchBeatsAdapter(framework), { + tags, + tokens, + }); + + const domainLibs: CMDomainLibs = { + beats, + tags, + tokens, + }; + + const libs: CMServerLibs = { + framework, + ...domainLibs, + }; + + return libs; +} diff --git a/x-pack/plugins/beats/server/lib/crypto/are_tokens_equal.js b/x-pack/plugins/beats/server/lib/crypto/are_tokens_equal.js deleted file mode 100644 index a6ed171d30e5e..0000000000000 --- a/x-pack/plugins/beats/server/lib/crypto/are_tokens_equal.js +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { timingSafeEqual } from 'crypto'; - -const RANDOM_TOKEN_1 = 'b48c4bda384a40cb91c6eb9b8849e77f'; -const RANDOM_TOKEN_2 = '80a3819e3cd64f4399f1d4886be7a08b'; - -export function areTokensEqual(token1, token2) { - if ((typeof token1 !== 'string') || (typeof token2 !== 'string') || (token1.length !== token2.length)) { - // This prevents a more subtle timing attack where we know already the tokens aren't going to - // match but still we don't return fast. Instead we compare two pre-generated random tokens using - // the same comparison algorithm that we would use to compare two equal-length tokens. - return timingSafeEqual(Buffer.from(RANDOM_TOKEN_1, 'utf8'), Buffer.from(RANDOM_TOKEN_2, 'utf8')); - } - - return timingSafeEqual(Buffer.from(token1, 'utf8'), Buffer.from(token2, 'utf8')); -} diff --git a/x-pack/plugins/beats/server/lib/domains/beats.ts b/x-pack/plugins/beats/server/lib/domains/beats.ts new file mode 100644 index 0000000000000..c0d9ec704e2b1 --- /dev/null +++ b/x-pack/plugins/beats/server/lib/domains/beats.ts @@ -0,0 +1,259 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { uniq } from 'lodash'; +import uuid from 'uuid'; +import { findNonExistentItems } from '../../utils/find_non_existent_items'; + +import { + CMAssignmentReturn, + CMBeat, + CMBeatsAdapter, + CMDomainLibs, + CMRemovalReturn, + CMTagAssignment, + FrameworkRequest, +} from '../lib'; + +export class CMBeatsDomain { + private adapter: CMBeatsAdapter; + private tags: CMDomainLibs['tags']; + private tokens: CMDomainLibs['tokens']; + + constructor( + adapter: CMBeatsAdapter, + libs: { tags: CMDomainLibs['tags']; tokens: CMDomainLibs['tokens'] } + ) { + this.adapter = adapter; + this.tags = libs.tags; + this.tokens = libs.tokens; + } + + public async update( + beatId: string, + accessToken: string, + beatData: Partial + ) { + const beat = await this.adapter.get(beatId); + + // TODO make return type enum + if (beat === null) { + return 'beat-not-found'; + } + + const isAccessTokenValid = this.tokens.areTokensEqual( + beat.access_token, + accessToken + ); + if (!isAccessTokenValid) { + return 'invalid-access-token'; + } + const isBeatVerified = beat.hasOwnProperty('verified_on'); + if (!isBeatVerified) { + return 'beat-not-verified'; + } + + await this.adapter.update({ + ...beat, + ...beatData, + }); + } + + // TODO more strongly type this + public async enrollBeat( + beatId: string, + remoteAddress: string, + beat: Partial + ) { + // TODO move this to the token lib + const accessToken = uuid.v4().replace(/-/g, ''); + await this.adapter.insert({ + ...beat, + access_token: accessToken, + host_ip: remoteAddress, + id: beatId, + } as CMBeat); + return { accessToken }; + } + + public async removeTagsFromBeats( + req: FrameworkRequest, + removals: CMTagAssignment[] + ): Promise { + const beatIds = uniq(removals.map(removal => removal.beatId)); + const tagIds = uniq(removals.map(removal => removal.tag)); + + const response = { + removals: removals.map(() => ({ status: null })), + }; + + const beats = await this.adapter.getWithIds(req, beatIds); + const tags = await this.tags.getTagsWithIds(req, tagIds); + + // Handle assignments containing non-existing beat IDs or tags + const nonExistentBeatIds = findNonExistentItems(beats, beatIds); + const nonExistentTags = await findNonExistentItems(tags, tagIds); + + addNonExistentItemToResponse( + response, + removals, + nonExistentBeatIds, + nonExistentTags, + 'removals' + ); + + // TODO abstract this + const validRemovals = removals + .map((removal, idxInRequest) => ({ + beatId: removal.beatId, + idxInRequest, // so we can add the result of this removal to the correct place in the response + tag: removal.tag, + })) + .filter((removal, idx) => response.removals[idx].status === null); + + if (validRemovals.length > 0) { + const removalResults = await this.adapter.removeTagsFromBeats( + req, + validRemovals + ); + return addToResultsToResponse('removals', response, removalResults); + } + return response; + } + + public async getAllBeats(req: FrameworkRequest) { + return await this.adapter.getAll(req); + } + + // TODO cleanup return value, should return a status enum + public async verifyBeats(req: FrameworkRequest, beatIds: string[]) { + const beatsFromEs = await this.adapter.getVerifiedWithIds(req, beatIds); + + const nonExistentBeatIds = beatsFromEs.reduce( + (nonExistentIds: any, beatFromEs: any, idx: any) => { + if (!beatFromEs.found) { + nonExistentIds.push(beatIds[idx]); + } + return nonExistentIds; + }, + [] + ); + + const alreadyVerifiedBeatIds = beatsFromEs + .filter((beat: any) => beat.found) + .filter((beat: any) => beat._source.beat.hasOwnProperty('verified_on')) + .map((beat: any) => beat._source.beat.id); + + const toBeVerifiedBeatIds = beatsFromEs + .filter((beat: any) => beat.found) + .filter((beat: any) => !beat._source.beat.hasOwnProperty('verified_on')) + .map((beat: any) => beat._source.beat.id); + + const verifications = await this.adapter.verifyBeats( + req, + toBeVerifiedBeatIds + ); + return { + alreadyVerifiedBeatIds, + nonExistentBeatIds, + toBeVerifiedBeatIds, + verifications, + }; + } + + public async assignTagsToBeats( + req: FrameworkRequest, + assignments: CMTagAssignment[] + ): Promise { + const beatIds = uniq(assignments.map(assignment => assignment.beatId)); + const tagIds = uniq(assignments.map(assignment => assignment.tag)); + + const response = { + assignments: assignments.map(() => ({ status: null })), + }; + const beats = await this.adapter.getWithIds(req, beatIds); + const tags = await this.tags.getTagsWithIds(req, tagIds); + + // Handle assignments containing non-existing beat IDs or tags + const nonExistentBeatIds = findNonExistentItems(beats, beatIds); + const nonExistentTags = findNonExistentItems(tags, tagIds); + + // TODO break out back into route / function response + // TODO causes function to error if a beat or tag does not exist + addNonExistentItemToResponse( + response, + assignments, + nonExistentBeatIds, + nonExistentTags, + 'assignments' + ); + + // TODO abstract this + const validAssignments = assignments + .map((assignment, idxInRequest) => ({ + beatId: assignment.beatId, + idxInRequest, // so we can add the result of this assignment to the correct place in the response + tag: assignment.tag, + })) + .filter((assignment, idx) => response.assignments[idx].status === null); + + if (validAssignments.length > 0) { + const assignmentResults = await this.adapter.assignTagsToBeats( + req, + validAssignments + ); + + // TODO This should prob not mutate + return addToResultsToResponse('assignments', response, assignmentResults); + } + return response; + } +} + +// TODO abstract to the route, also the key arg is a temp fix +function addNonExistentItemToResponse( + response: any, + assignments: any, + nonExistentBeatIds: any, + nonExistentTags: any, + key: string +) { + assignments.forEach(({ beatId, tag }: CMTagAssignment, idx: any) => { + const isBeatNonExistent = nonExistentBeatIds.includes(beatId); + const isTagNonExistent = nonExistentTags.includes(tag); + + if (isBeatNonExistent && isTagNonExistent) { + response[key][idx].status = 404; + response[key][idx].result = `Beat ${beatId} and tag ${tag} not found`; + } else if (isBeatNonExistent) { + response[key][idx].status = 404; + response[key][idx].result = `Beat ${beatId} not found`; + } else if (isTagNonExistent) { + response[key][idx].status = 404; + response[key][idx].result = `Tag ${tag} not found`; + } + }); +} + +// TODO dont mutate response +function addToResultsToResponse( + key: string, + response: any, + assignmentResults: any +) { + assignmentResults.forEach((assignmentResult: any) => { + const { idxInRequest, status, result } = assignmentResult; + response[key][idxInRequest].status = status; + response[key][idxInRequest].result = result; + }); + return response; +} diff --git a/x-pack/plugins/beats/server/lib/domains/tags.ts b/x-pack/plugins/beats/server/lib/domains/tags.ts new file mode 100644 index 0000000000000..43bb8dfed15a1 --- /dev/null +++ b/x-pack/plugins/beats/server/lib/domains/tags.ts @@ -0,0 +1,90 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { intersection, uniq, values } from 'lodash'; +import { UNIQUENESS_ENFORCING_TYPES } from '../../../common/constants'; +import { CMTagsAdapter, ConfigurationBlock, FrameworkRequest } from '../lib'; +import { entries } from './../../utils/polyfills'; + +export class CMTagsDomain { + private adapter: CMTagsAdapter; + constructor(adapter: CMTagsAdapter) { + this.adapter = adapter; + } + + public async getTagsWithIds(req: FrameworkRequest, tagIds: string[]) { + return await this.adapter.getTagsWithIds(req, tagIds); + } + + public async saveTag( + req: FrameworkRequest, + tagId: string, + configs: ConfigurationBlock[] + ) { + const { isValid, message } = await this.validateConfigurationBlocks( + configs + ); + if (!isValid) { + return { isValid, result: message }; + } + + const tag = { + configuration_blocks: configs, + id: tagId, + }; + return { + isValid: true, + result: await this.adapter.upsertTag(req, tag), + }; + } + + private validateConfigurationBlocks(configurationBlocks: any) { + const types = uniq(configurationBlocks.map((block: any) => block.type)); + + // If none of the types in the given configuration blocks are uniqueness-enforcing, + // we don't need to perform any further validation checks. + const uniquenessEnforcingTypes = intersection( + types, + UNIQUENESS_ENFORCING_TYPES + ); + if (uniquenessEnforcingTypes.length === 0) { + return { isValid: true }; + } + + // Count the number of uniqueness-enforcing types in the given configuration blocks + const typeCountMap = configurationBlocks.reduce((map: any, block: any) => { + const { type } = block; + if (!uniquenessEnforcingTypes.includes(type)) { + return map; + } + + const count = map[type] || 0; + return { + ...map, + [type]: count + 1, + }; + }, {}); + + // If there is no more than one of any uniqueness-enforcing types in the given + // configuration blocks, we don't need to perform any further validation checks. + if (values(typeCountMap).filter(count => count > 1).length === 0) { + return { isValid: true }; + } + + const message = entries(typeCountMap) + .filter(([, count]) => count > 1) + .map( + ([type, count]) => + `Expected only one configuration block of type '${type}' but found ${count}` + ) + .join(' '); + + return { + isValid: false, + message, + }; + } +} diff --git a/x-pack/plugins/beats/server/lib/domains/tokens.ts b/x-pack/plugins/beats/server/lib/domains/tokens.ts new file mode 100644 index 0000000000000..6e55d78ecdcc8 --- /dev/null +++ b/x-pack/plugins/beats/server/lib/domains/tokens.ts @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { timingSafeEqual } from 'crypto'; +import moment from 'moment'; +import uuid from 'uuid'; +import { CMTokensAdapter, FrameworkRequest } from '../lib'; +import { BackendFrameworkAdapter } from '../lib'; + +const RANDOM_TOKEN_1 = 'b48c4bda384a40cb91c6eb9b8849e77f'; +const RANDOM_TOKEN_2 = '80a3819e3cd64f4399f1d4886be7a08b'; + +export class CMTokensDomain { + private adapter: CMTokensAdapter; + private framework: BackendFrameworkAdapter; + + constructor( + adapter: CMTokensAdapter, + libs: { framework: BackendFrameworkAdapter } + ) { + this.adapter = adapter; + this.framework = libs.framework; + } + + public async getEnrollmentToken(enrollmentToken: string) { + return await this.adapter.getEnrollmentToken(enrollmentToken); + } + + public async deleteEnrollmentToken(enrollmentToken: string) { + return await this.adapter.deleteEnrollmentToken(enrollmentToken); + } + + public areTokensEqual(token1: string, token2: string) { + if ( + typeof token1 !== 'string' || + typeof token2 !== 'string' || + token1.length !== token2.length + ) { + // This prevents a more subtle timing attack where we know already the tokens aren't going to + // match but still we don't return fast. Instead we compare two pre-generated random tokens using + // the same comparison algorithm that we would use to compare two equal-length tokens. + return timingSafeEqual( + Buffer.from(RANDOM_TOKEN_1, 'utf8'), + Buffer.from(RANDOM_TOKEN_2, 'utf8') + ); + } + + return timingSafeEqual( + Buffer.from(token1, 'utf8'), + Buffer.from(token2, 'utf8') + ); + } + + public async createEnrollmentTokens( + req: FrameworkRequest, + numTokens: number = 1 + ): Promise { + const tokens = []; + const enrollmentTokensTtlInSeconds = this.framework.getSetting( + 'xpack.beats.enrollmentTokensTtlInSeconds' + ); + const enrollmentTokenExpiration = moment() + .add(enrollmentTokensTtlInSeconds, 'seconds') + .toJSON(); + + while (tokens.length < numTokens) { + tokens.push({ + expires_on: enrollmentTokenExpiration, + token: uuid.v4().replace(/-/g, ''), + }); + } + + await this.adapter.upsertTokens(req, tokens); + + return tokens.map(token => token.token); + } +} diff --git a/x-pack/plugins/beats/server/lib/index_template/install_index_template.js b/x-pack/plugins/beats/server/lib/index_template/install_index_template.js deleted file mode 100644 index 01b080903ccac..0000000000000 --- a/x-pack/plugins/beats/server/lib/index_template/install_index_template.js +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import beatsIndexTemplate from './beats_template'; -import { callWithInternalUserFactory } from '../client'; - -const TEMPLATE_NAME = 'beats-template'; - -export function installIndexTemplate(server) { - const callWithInternalUser = callWithInternalUserFactory(server); - return callWithInternalUser('indices.putTemplate', { - name: TEMPLATE_NAME, - body: beatsIndexTemplate - }); -} diff --git a/x-pack/plugins/beats/server/lib/lib.ts b/x-pack/plugins/beats/server/lib/lib.ts new file mode 100644 index 0000000000000..37d0a989e4cf5 --- /dev/null +++ b/x-pack/plugins/beats/server/lib/lib.ts @@ -0,0 +1,212 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IRouteAdditionalConfigurationOptions, IStrictReply } from 'hapi'; +import { internalFrameworkRequest } from '../utils/wrap_request'; +import { CMBeatsDomain } from './domains/beats'; +import { CMTagsDomain } from './domains/tags'; +import { CMTokensDomain } from './domains/tokens'; + +import { ConfigurationBlockTypes } from '../../common/constants'; + +export interface CMDomainLibs { + beats: CMBeatsDomain; + tags: CMTagsDomain; + tokens: CMTokensDomain; +} + +export interface CMServerLibs extends CMDomainLibs { + framework: BackendFrameworkAdapter; +} + +interface CMReturnedTagAssignment { + status: number | null; + result?: string; +} + +export interface CMAssignmentReturn { + assignments: CMReturnedTagAssignment[]; +} + +export interface CMRemovalReturn { + removals: CMReturnedTagAssignment[]; +} + +export interface ConfigurationBlock { + type: ConfigurationBlockTypes; + block_yml: string; +} + +export interface CMBeat { + id: string; + access_token: string; + verified_on: string; + type: string; + version: string; + host_ip: string; + host_name: string; + ephemeral_id: string; + local_configuration_yml: string; + tags: string; + central_configuration_yml: string; + metadata: {}; +} + +export interface BeatTag { + id: string; + configuration_blocks: ConfigurationBlock[]; +} + +export interface EnrollmentToken { + token: string | null; + expires_on: string; +} + +export interface CMTokensAdapter { + deleteEnrollmentToken(enrollmentToken: string): Promise; + getEnrollmentToken(enrollmentToken: string): Promise; + upsertTokens(req: FrameworkRequest, tokens: EnrollmentToken[]): Promise; +} + +// FIXME: fix getTagsWithIds return type +export interface CMTagsAdapter { + getTagsWithIds(req: FrameworkRequest, tagIds: string[]): any; + upsertTag(req: FrameworkRequest, tag: BeatTag): Promise<{}>; +} + +// FIXME: fix getBeatsWithIds return type +export interface CMBeatsAdapter { + insert(beat: CMBeat): Promise; + update(beat: CMBeat): Promise; + get(id: string): any; + getAll(req: FrameworkRequest): any; + getWithIds(req: FrameworkRequest, beatIds: string[]): any; + getVerifiedWithIds(req: FrameworkRequest, beatIds: string[]): any; + verifyBeats(req: FrameworkRequest, beatIds: string[]): any; + removeTagsFromBeats( + req: FrameworkRequest, + removals: CMTagAssignment[] + ): Promise; + assignTagsToBeats( + req: FrameworkRequest, + assignments: CMTagAssignment[] + ): Promise; +} + +export interface CMTagAssignment { + beatId: string; + tag: string; + idxInRequest?: number; +} + +/** + * The following are generic types, sharable between projects + */ + +export interface BackendFrameworkAdapter { + version: string; + getSetting(settingPath: string): string | number; + exposeStaticDir(urlPath: string, dir: string): void; + installIndexTemplate(name: string, template: {}): void; + registerRoute( + route: FrameworkRouteOptions + ): void; + callWithInternalUser(esMethod: string, options: {}): Promise; + callWithRequest( + req: FrameworkRequest, + method: 'search', + options?: object + ): Promise>; + callWithRequest( + req: FrameworkRequest, + method: 'fieldCaps', + options?: object + ): Promise; + callWithRequest( + req: FrameworkRequest, + method: string, + options?: object + ): Promise; +} + +interface DatabaseFieldCapsResponse extends DatabaseResponse { + fields: FieldsResponse; +} + +export interface FieldsResponse { + [name: string]: FieldDef; +} + +export interface FieldDetails { + searchable: boolean; + aggregatable: boolean; + type: string; +} + +export interface FieldDef { + [type: string]: FieldDetails; +} + +export interface FrameworkRequest< + InternalRequest extends WrappableRequest = WrappableRequest +> { + [internalFrameworkRequest]: InternalRequest; + headers: InternalRequest['headers']; + info: InternalRequest['info']; + payload: InternalRequest['payload']; + params: InternalRequest['params']; + query: InternalRequest['query']; +} + +export interface FrameworkRouteOptions< + RouteRequest extends WrappableRequest, + RouteResponse +> { + path: string; + method: string | string[]; + vhost?: string; + handler: FrameworkRouteHandler; + config?: Pick< + IRouteAdditionalConfigurationOptions, + Exclude + >; +} + +export type FrameworkRouteHandler< + RouteRequest extends WrappableRequest, + RouteResponse +> = ( + request: FrameworkRequest, + reply: IStrictReply +) => void; + +export interface WrappableRequest< + Payload = any, + Params = any, + Query = any, + Headers = any, + Info = any +> { + headers: Headers; + info: Info; + payload: Payload; + params: Params; + query: Query; +} + +interface DatabaseResponse { + took: number; + timeout: boolean; +} + +interface DatabaseSearchResponse + extends DatabaseResponse { + aggregations?: Aggregations; + hits: { + total: number; + hits: Hit[]; + }; +} diff --git a/x-pack/plugins/beats/server/management_server.ts b/x-pack/plugins/beats/server/management_server.ts new file mode 100644 index 0000000000000..ed0917eda8ced --- /dev/null +++ b/x-pack/plugins/beats/server/management_server.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CMServerLibs } from './lib/lib'; +import { createBeatEnrollmentRoute } from './rest_api/beats/enroll'; +import { createListAgentsRoute } from './rest_api/beats/list'; +import { createTagAssignmentsRoute } from './rest_api/beats/tag_assignment'; +import { createTagRemovalsRoute } from './rest_api/beats/tag_removal'; +import { createBeatUpdateRoute } from './rest_api/beats/update'; +import { createBeatVerificationRoute } from './rest_api/beats/verify'; +import { createSetTagRoute } from './rest_api/tags/set'; +import { createTokensRoute } from './rest_api/tokens/create'; + +import { beatsIndexTemplate } from './utils/index_templates'; + +export const initManagementServer = (libs: CMServerLibs) => { + libs.framework.installIndexTemplate('beats-template', beatsIndexTemplate); + + libs.framework.registerRoute(createTagAssignmentsRoute(libs)); + libs.framework.registerRoute(createListAgentsRoute(libs)); + libs.framework.registerRoute(createTagRemovalsRoute(libs)); + libs.framework.registerRoute(createBeatEnrollmentRoute(libs)); + libs.framework.registerRoute(createSetTagRoute(libs)); + libs.framework.registerRoute(createTokensRoute(libs)); + libs.framework.registerRoute(createBeatVerificationRoute(libs)); + libs.framework.registerRoute(createBeatUpdateRoute(libs)); +}; diff --git a/x-pack/plugins/beats/server/rest_api/beats/enroll.ts b/x-pack/plugins/beats/server/rest_api/beats/enroll.ts new file mode 100644 index 0000000000000..fe154592564ae --- /dev/null +++ b/x-pack/plugins/beats/server/rest_api/beats/enroll.ts @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Joi from 'joi'; +import { omit } from 'lodash'; +import moment from 'moment'; +import { CMServerLibs } from '../../lib/lib'; +import { wrapEsError } from '../../utils/error_wrappers'; + +// TODO: add license check pre-hook +// TODO: write to Kibana audit log file +export const createBeatEnrollmentRoute = (libs: CMServerLibs) => ({ + config: { + auth: false, + validate: { + headers: Joi.object({ + 'kbn-beats-enrollment-token': Joi.string().required(), + }).options({ + allowUnknown: true, + }), + payload: Joi.object({ + host_name: Joi.string().required(), + type: Joi.string().required(), + version: Joi.string().required(), + }).required(), + }, + }, + handler: async (request: any, reply: any) => { + const { beatId } = request.params; + const enrollmentToken = request.headers['kbn-beats-enrollment-token']; + + try { + const { + token, + expires_on: expiresOn, + } = await libs.tokens.getEnrollmentToken(enrollmentToken); + + if (!token) { + return reply({ message: 'Invalid enrollment token' }).code(400); + } + if (moment(expiresOn).isBefore(moment())) { + return reply({ message: 'Expired enrollment token' }).code(400); + } + const { accessToken } = await libs.beats.enrollBeat( + beatId, + request.info.remoteAddress, + omit(request.payload, 'enrollment_token') + ); + + await libs.tokens.deleteEnrollmentToken(enrollmentToken); + + reply({ access_token: accessToken }).code(201); + } catch (err) { + // TODO move this to kibana route thing in adapter + return reply(wrapEsError(err)); + } + }, + method: 'POST', + path: '/api/beats/agent/{beatId}', +}); diff --git a/x-pack/plugins/beats/server/rest_api/beats/list.ts b/x-pack/plugins/beats/server/rest_api/beats/list.ts new file mode 100644 index 0000000000000..8263d1c0ff63f --- /dev/null +++ b/x-pack/plugins/beats/server/rest_api/beats/list.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CMServerLibs } from '../../lib/lib'; +import { wrapEsError } from '../../utils/error_wrappers'; + +// TODO: add license check pre-hook +export const createListAgentsRoute = (libs: CMServerLibs) => ({ + handler: async (request: any, reply: any) => { + try { + const beats = await libs.beats.getAllBeats(request); + reply({ beats }); + } catch (err) { + // TODO move this to kibana route thing in adapter + return reply(wrapEsError(err)); + } + }, + method: 'GET', + path: '/api/beats/agents', +}); diff --git a/x-pack/plugins/beats/server/rest_api/beats/tag_assignment.ts b/x-pack/plugins/beats/server/rest_api/beats/tag_assignment.ts new file mode 100644 index 0000000000000..d06c016ce6d12 --- /dev/null +++ b/x-pack/plugins/beats/server/rest_api/beats/tag_assignment.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Joi from 'joi'; +import { CMServerLibs } from '../../lib/lib'; +import { wrapEsError } from '../../utils/error_wrappers'; + +// TODO: add license check pre-hook +// TODO: write to Kibana audit log file +export const createTagAssignmentsRoute = (libs: CMServerLibs) => ({ + config: { + validate: { + payload: Joi.object({ + assignments: Joi.array().items( + Joi.object({ + beat_id: Joi.string().required(), + tag: Joi.string().required(), + }) + ), + }).required(), + }, + }, + handler: async (request: any, reply: any) => { + const { assignments } = request.payload; + + // TODO abstract or change API to keep beatId consistent + const tweakedAssignments = assignments.map((assignment: any) => ({ + beatId: assignment.beat_id, + tag: assignment.tag, + })); + + try { + const response = await libs.beats.assignTagsToBeats( + request, + tweakedAssignments + ); + reply(response); + } catch (err) { + // TODO move this to kibana route thing in adapter + return reply(wrapEsError(err)); + } + }, + method: 'POST', + path: '/api/beats/agents_tags/assignments', +}); diff --git a/x-pack/plugins/beats/server/rest_api/beats/tag_removal.ts b/x-pack/plugins/beats/server/rest_api/beats/tag_removal.ts new file mode 100644 index 0000000000000..4da33dbd50cfc --- /dev/null +++ b/x-pack/plugins/beats/server/rest_api/beats/tag_removal.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Joi from 'joi'; +import { CMServerLibs } from '../../lib/lib'; +import { wrapEsError } from '../../utils/error_wrappers'; + +// TODO: add license check pre-hook +// TODO: write to Kibana audit log file +export const createTagRemovalsRoute = (libs: CMServerLibs) => ({ + config: { + validate: { + payload: Joi.object({ + removals: Joi.array().items( + Joi.object({ + beat_id: Joi.string().required(), + tag: Joi.string().required(), + }) + ), + }).required(), + }, + }, + handler: async (request: any, reply: any) => { + const { removals } = request.payload; + + // TODO abstract or change API to keep beatId consistent + const tweakedRemovals = removals.map((removal: any) => ({ + beatId: removal.beat_id, + tag: removal.tag, + })); + + try { + const response = await libs.beats.removeTagsFromBeats( + request, + tweakedRemovals + ); + reply(response); + } catch (err) { + // TODO move this to kibana route thing in adapter + return reply(wrapEsError(err)); + } + }, + method: 'POST', + path: '/api/beats/agents_tags/removals', +}); diff --git a/x-pack/plugins/beats/server/rest_api/beats/update.ts b/x-pack/plugins/beats/server/rest_api/beats/update.ts new file mode 100644 index 0000000000000..41d403399d45f --- /dev/null +++ b/x-pack/plugins/beats/server/rest_api/beats/update.ts @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Joi from 'joi'; +import { CMServerLibs } from '../../lib/lib'; +import { wrapEsError } from '../../utils/error_wrappers'; + +// TODO: add license check pre-hook +// TODO: write to Kibana audit log file (include who did the verification as well) +export const createBeatUpdateRoute = (libs: CMServerLibs) => ({ + config: { + auth: false, + validate: { + headers: Joi.object({ + 'kbn-beats-access-token': Joi.string().required(), + }).options({ + allowUnknown: true, + }), + params: Joi.object({ + beatId: Joi.string(), + }), + payload: Joi.object({ + ephemeral_id: Joi.string(), + host_name: Joi.string(), + local_configuration_yml: Joi.string(), + metadata: Joi.object(), + type: Joi.string(), + version: Joi.string(), + }).required(), + }, + }, + handler: async (request: any, reply: any) => { + const { beatId } = request.params; + const accessToken = request.headers['kbn-beats-access-token']; + const remoteAddress = request.info.remoteAddress; + + try { + const status = await libs.beats.update(beatId, accessToken, { + ...request.payload, + host_ip: remoteAddress, + }); + + switch (status) { + case 'beat-not-found': + return reply({ message: 'Beat not found' }).code(404); + case 'invalid-access-token': + return reply({ message: 'Invalid access token' }).code(401); + case 'beat-not-verified': + return reply({ message: 'Beat has not been verified' }).code(400); + } + + reply().code(204); + } catch (err) { + return reply(wrapEsError(err)); + } + }, + method: 'PUT', + path: '/api/beats/agent/{beatId}', +}); diff --git a/x-pack/plugins/beats/server/rest_api/beats/verify.ts b/x-pack/plugins/beats/server/rest_api/beats/verify.ts new file mode 100644 index 0000000000000..866fa77d0c337 --- /dev/null +++ b/x-pack/plugins/beats/server/rest_api/beats/verify.ts @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Joi from 'joi'; +import { CMServerLibs } from '../../lib/lib'; +import { wrapEsError } from '../../utils/error_wrappers'; + +// TODO: add license check pre-hook +// TODO: write to Kibana audit log file +export const createBeatVerificationRoute = (libs: CMServerLibs) => ({ + config: { + auth: false, + validate: { + payload: Joi.object({ + beats: Joi.array() + .items({ + id: Joi.string().required(), + }) + .min(1), + }).required(), + }, + }, + handler: async (request: any, reply: any) => { + const beats = [...request.payload.beats]; + const beatIds = beats.map(beat => beat.id); + + try { + const { + verifications, + alreadyVerifiedBeatIds, + toBeVerifiedBeatIds, + nonExistentBeatIds, + } = await libs.beats.verifyBeats(request, beatIds); + + const verifiedBeatIds = verifications.reduce( + (verifiedBeatList: any, verification: any, idx: any) => { + if (verification.update.status === 200) { + verifiedBeatList.push(toBeVerifiedBeatIds[idx]); + } + return verifiedBeatList; + }, + [] + ); + + // TODO calculation of status should be done in-lib, w/switch statement here + beats.forEach(beat => { + if (nonExistentBeatIds.includes(beat.id)) { + beat.status = 404; + beat.result = 'not found'; + } else if (alreadyVerifiedBeatIds.includes(beat.id)) { + beat.status = 200; + beat.result = 'already verified'; + } else if (verifiedBeatIds.includes(beat.id)) { + beat.status = 200; + beat.result = 'verified'; + } else { + beat.status = 400; + beat.result = 'not verified'; + } + }); + + const response = { beats }; + reply(response); + } catch (err) { + return reply(wrapEsError(err)); + } + }, + method: 'POST', + path: '/api/beats/agents/verify', +}); diff --git a/x-pack/plugins/beats/server/rest_api/tags/set.ts b/x-pack/plugins/beats/server/rest_api/tags/set.ts new file mode 100644 index 0000000000000..3f7e579bd91ae --- /dev/null +++ b/x-pack/plugins/beats/server/rest_api/tags/set.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Joi from 'joi'; +import { get, values } from 'lodash'; +import { ConfigurationBlockTypes } from '../../../common/constants'; +import { CMServerLibs } from '../../lib/lib'; +import { wrapEsError } from '../../utils/error_wrappers'; + +// TODO: add license check pre-hook +// TODO: write to Kibana audit log file +export const createSetTagRoute = (libs: CMServerLibs) => ({ + config: { + validate: { + params: Joi.object({ + tag: Joi.string(), + }), + payload: Joi.object({ + configuration_blocks: Joi.array().items( + Joi.object({ + block_yml: Joi.string().required(), + type: Joi.string() + .only(values(ConfigurationBlockTypes)) + .required(), + }) + ), + }).allow(null), + }, + }, + handler: async (request: any, reply: any) => { + const configurationBlocks = get( + request, + 'payload.configuration_blocks', + [] + ); + try { + const { isValid, result } = await libs.tags.saveTag( + request, + request.params.tag, + configurationBlocks + ); + if (!isValid) { + return reply({ result }).code(400); + } + + reply().code(result === 'created' ? 201 : 200); + } catch (err) { + // TODO move this to kibana route thing in adapter + return reply(wrapEsError(err)); + } + }, + method: 'PUT', + path: '/api/beats/tag/{tag}', +}); diff --git a/x-pack/plugins/beats/server/rest_api/tokens/create.ts b/x-pack/plugins/beats/server/rest_api/tokens/create.ts new file mode 100644 index 0000000000000..b4f3e2c1a6246 --- /dev/null +++ b/x-pack/plugins/beats/server/rest_api/tokens/create.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Joi from 'joi'; +import { get } from 'lodash'; +import { CMServerLibs } from '../../lib/lib'; +import { wrapEsError } from '../../utils/error_wrappers'; + +// TODO: add license check pre-hook +// TODO: write to Kibana audit log file +const DEFAULT_NUM_TOKENS = 1; +export const createTokensRoute = (libs: CMServerLibs) => ({ + config: { + validate: { + payload: Joi.object({ + num_tokens: Joi.number() + .optional() + .default(DEFAULT_NUM_TOKENS) + .min(1), + }).allow(null), + }, + }, + handler: async (request: any, reply: any) => { + const numTokens = get(request, 'payload.num_tokens', DEFAULT_NUM_TOKENS); + + try { + const tokens = await libs.tokens.createEnrollmentTokens( + request, + numTokens + ); + reply({ tokens }); + } catch (err) { + // TODO move this to kibana route thing in adapter + return reply(wrapEsError(err)); + } + }, + method: 'POST', + path: '/api/beats/enrollment_tokens', +}); diff --git a/x-pack/plugins/beats/server/routes/api/index.js b/x-pack/plugins/beats/server/routes/api/index.js deleted file mode 100644 index 6ec0ad737352a..0000000000000 --- a/x-pack/plugins/beats/server/routes/api/index.js +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { registerCreateEnrollmentTokensRoute } from './register_create_enrollment_tokens_route'; -import { registerEnrollBeatRoute } from './register_enroll_beat_route'; -import { registerListBeatsRoute } from './register_list_beats_route'; -import { registerVerifyBeatsRoute } from './register_verify_beats_route'; -import { registerUpdateBeatRoute } from './register_update_beat_route'; -import { registerSetTagRoute } from './register_set_tag_route'; -import { registerAssignTagsToBeatsRoute } from './register_assign_tags_to_beats_route'; -import { registerRemoveTagsFromBeatsRoute } from './register_remove_tags_from_beats_route'; - -export function registerApiRoutes(server) { - registerCreateEnrollmentTokensRoute(server); - registerEnrollBeatRoute(server); - registerListBeatsRoute(server); - registerVerifyBeatsRoute(server); - registerUpdateBeatRoute(server); - registerSetTagRoute(server); - registerAssignTagsToBeatsRoute(server); - registerRemoveTagsFromBeatsRoute(server); -} diff --git a/x-pack/plugins/beats/server/routes/api/register_assign_tags_to_beats_route.js b/x-pack/plugins/beats/server/routes/api/register_assign_tags_to_beats_route.js deleted file mode 100644 index 5f6c5f7a7b906..0000000000000 --- a/x-pack/plugins/beats/server/routes/api/register_assign_tags_to_beats_route.js +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import Joi from 'joi'; -import { - get, - flatten, - uniq -} from 'lodash'; -import { INDEX_NAMES } from '../../../common/constants'; -import { callWithRequestFactory } from '../../lib/client'; -import { wrapEsError } from '../../lib/error_wrappers'; - -async function getDocs(callWithRequest, ids) { - const params = { - index: INDEX_NAMES.BEATS, - type: '_doc', - body: { ids }, - _source: false - }; - - const response = await callWithRequest('mget', params); - return get(response, 'docs', []); -} - -function getBeats(callWithRequest, beatIds) { - const ids = beatIds.map(beatId => `beat:${beatId}`); - return getDocs(callWithRequest, ids); -} - -function getTags(callWithRequest, tags) { - const ids = tags.map(tag => `tag:${tag}`); - return getDocs(callWithRequest, ids); -} - -async function findNonExistentItems(callWithRequest, items, getFn) { - const itemsFromEs = await getFn.call(null, callWithRequest, items); - return itemsFromEs.reduce((nonExistentItems, itemFromEs, idx) => { - if (!itemFromEs.found) { - nonExistentItems.push(items[idx]); - } - return nonExistentItems; - }, []); -} - -function findNonExistentBeatIds(callWithRequest, beatIds) { - return findNonExistentItems(callWithRequest, beatIds, getBeats); -} - -function findNonExistentTags(callWithRequest, tags) { - return findNonExistentItems(callWithRequest, tags, getTags); -} - -async function persistAssignments(callWithRequest, assignments) { - const body = flatten(assignments.map(({ beatId, tag }) => { - const script = '' - + 'def beat = ctx._source.beat; ' - + 'if (beat.tags == null) { ' - + ' beat.tags = []; ' - + '} ' - + 'if (!beat.tags.contains(params.tag)) { ' - + ' beat.tags.add(params.tag); ' - + '}'; - - return [ - { update: { _id: `beat:${beatId}` } }, - { script: { source: script, params: { tag } } } - ]; - })); - - const params = { - index: INDEX_NAMES.BEATS, - type: '_doc', - body, - refresh: 'wait_for' - }; - - const response = await callWithRequest('bulk', params); - return get(response, 'items', []) - .map((item, resultIdx) => ({ - status: item.update.status, - result: item.update.result, - idxInRequest: assignments[resultIdx].idxInRequest - })); -} - -function addNonExistentItemAssignmentsToResponse(response, assignments, nonExistentBeatIds, nonExistentTags) { - assignments.forEach(({ beat_id: beatId, tag }, idx) => { - const isBeatNonExistent = nonExistentBeatIds.includes(beatId); - const isTagNonExistent = nonExistentTags.includes(tag); - - if (isBeatNonExistent && isTagNonExistent) { - response.assignments[idx].status = 404; - response.assignments[idx].result = `Beat ${beatId} and tag ${tag} not found`; - } else if (isBeatNonExistent) { - response.assignments[idx].status = 404; - response.assignments[idx].result = `Beat ${beatId} not found`; - } else if (isTagNonExistent) { - response.assignments[idx].status = 404; - response.assignments[idx].result = `Tag ${tag} not found`; - } - }); -} - -function addAssignmentResultsToResponse(response, assignmentResults) { - assignmentResults.forEach(assignmentResult => { - const { idxInRequest, status, result } = assignmentResult; - response.assignments[idxInRequest].status = status; - response.assignments[idxInRequest].result = result; - }); -} - -// TODO: add license check pre-hook -// TODO: write to Kibana audit log file -export function registerAssignTagsToBeatsRoute(server) { - server.route({ - method: 'POST', - path: '/api/beats/agents_tags/assignments', - config: { - validate: { - payload: Joi.object({ - assignments: Joi.array().items(Joi.object({ - beat_id: Joi.string().required(), - tag: Joi.string().required() - })) - }).required() - } - }, - handler: async (request, reply) => { - const callWithRequest = callWithRequestFactory(server, request); - - const { assignments } = request.payload; - const beatIds = uniq(assignments.map(assignment => assignment.beat_id)); - const tags = uniq(assignments.map(assignment => assignment.tag)); - - const response = { - assignments: assignments.map(() => ({ status: null })) - }; - - try { - // Handle assignments containing non-existing beat IDs or tags - const nonExistentBeatIds = await findNonExistentBeatIds(callWithRequest, beatIds); - const nonExistentTags = await findNonExistentTags(callWithRequest, tags); - - addNonExistentItemAssignmentsToResponse(response, assignments, nonExistentBeatIds, nonExistentTags); - - const validAssignments = assignments - .map((assignment, idxInRequest) => ({ - beatId: assignment.beat_id, - tag: assignment.tag, - idxInRequest // so we can add the result of this assignment to the correct place in the response - })) - .filter((assignment, idx) => response.assignments[idx].status === null); - - if (validAssignments.length > 0) { - const assignmentResults = await persistAssignments(callWithRequest, validAssignments); - addAssignmentResultsToResponse(response, assignmentResults); - } - } catch (err) { - return reply(wrapEsError(err)); - } - - reply(response); - } - }); -} diff --git a/x-pack/plugins/beats/server/routes/api/register_create_enrollment_tokens_route.js b/x-pack/plugins/beats/server/routes/api/register_create_enrollment_tokens_route.js deleted file mode 100644 index 87ae30cd0e532..0000000000000 --- a/x-pack/plugins/beats/server/routes/api/register_create_enrollment_tokens_route.js +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import Joi from 'joi'; -import uuid from 'uuid'; -import moment from 'moment'; -import { - get, - flatten -} from 'lodash'; -import { INDEX_NAMES } from '../../../common/constants'; -import { callWithRequestFactory } from '../../lib/client'; -import { wrapEsError } from '../../lib/error_wrappers'; - -function persistTokens(callWithRequest, tokens, enrollmentTokensTtlInSeconds) { - const enrollmentTokenExpiration = moment().add(enrollmentTokensTtlInSeconds, 'seconds').toJSON(); - const body = flatten(tokens.map(token => [ - { index: { _id: `enrollment_token:${token}` } }, - { type: 'enrollment_token', enrollment_token: { token, expires_on: enrollmentTokenExpiration } } - ])); - - const params = { - index: INDEX_NAMES.BEATS, - type: '_doc', - body, - refresh: 'wait_for' - }; - - return callWithRequest('bulk', params); -} - -// TODO: add license check pre-hook -// TODO: write to Kibana audit log file -export function registerCreateEnrollmentTokensRoute(server) { - const DEFAULT_NUM_TOKENS = 1; - const enrollmentTokensTtlInSeconds = server.config().get('xpack.beats.enrollmentTokensTtlInSeconds'); - - server.route({ - method: 'POST', - path: '/api/beats/enrollment_tokens', - config: { - validate: { - payload: Joi.object({ - num_tokens: Joi.number().optional().default(DEFAULT_NUM_TOKENS).min(1) - }).allow(null) - } - }, - handler: async (request, reply) => { - const callWithRequest = callWithRequestFactory(server, request); - const numTokens = get(request, 'payload.num_tokens', DEFAULT_NUM_TOKENS); - - const tokens = []; - while (tokens.length < numTokens) { - tokens.push(uuid.v4().replace(/-/g, "")); - } - - try { - await persistTokens(callWithRequest, tokens, enrollmentTokensTtlInSeconds); - } catch (err) { - return reply(wrapEsError(err)); - } - - const response = { tokens }; - reply(response); - } - }); -} diff --git a/x-pack/plugins/beats/server/routes/api/register_enroll_beat_route.js b/x-pack/plugins/beats/server/routes/api/register_enroll_beat_route.js deleted file mode 100644 index bad28c0ab9be5..0000000000000 --- a/x-pack/plugins/beats/server/routes/api/register_enroll_beat_route.js +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import Joi from 'joi'; -import uuid from 'uuid'; -import moment from 'moment'; -import { - get, - omit -} from 'lodash'; -import { INDEX_NAMES } from '../../../common/constants'; -import { callWithInternalUserFactory } from '../../lib/client'; -import { wrapEsError } from '../../lib/error_wrappers'; - -async function getEnrollmentToken(callWithInternalUser, enrollmentToken) { - const params = { - index: INDEX_NAMES.BEATS, - type: '_doc', - id: `enrollment_token:${enrollmentToken}`, - ignore: [ 404 ] - }; - - const response = await callWithInternalUser('get', params); - const token = get(response, '_source.enrollment_token', {}); - - // Elasticsearch might return fast if the token is not found. OR it might return fast - // if the token *is* found. Either way, an attacker could using a timing attack to figure - // out whether a token is valid or not. So we introduce a random delay in returning from - // this function to obscure the actual time it took for Elasticsearch to find the token. - const randomDelayInMs = 25 + Math.round(Math.random() * 200); // between 25 and 225 ms - return new Promise(resolve => setTimeout(() => resolve(token), randomDelayInMs)); -} - -function deleteUsedEnrollmentToken(callWithInternalUser, enrollmentToken) { - const params = { - index: INDEX_NAMES.BEATS, - type: '_doc', - id: `enrollment_token:${enrollmentToken}` - }; - - return callWithInternalUser('delete', params); -} - -function persistBeat(callWithInternalUser, beat) { - const body = { - type: 'beat', - beat - }; - - const params = { - index: INDEX_NAMES.BEATS, - type: '_doc', - id: `beat:${beat.id}`, - body, - refresh: 'wait_for' - }; - return callWithInternalUser('create', params); -} - -// TODO: add license check pre-hook -// TODO: write to Kibana audit log file -export function registerEnrollBeatRoute(server) { - server.route({ - method: 'POST', - path: '/api/beats/agent/{beatId}', - config: { - validate: { - payload: Joi.object({ - type: Joi.string().required(), - version: Joi.string().required(), - host_name: Joi.string().required() - }).required(), - headers: Joi.object({ - 'kbn-beats-enrollment-token': Joi.string().required() - }).options({ allowUnknown: true }) - }, - auth: false - }, - handler: async (request, reply) => { - const callWithInternalUser = callWithInternalUserFactory(server); - const { beatId } = request.params; - let accessToken; - - try { - const enrollmentToken = request.headers['kbn-beats-enrollment-token']; - const { token, expires_on: expiresOn } = await getEnrollmentToken(callWithInternalUser, enrollmentToken); - if (!token) { - return reply({ message: 'Invalid enrollment token' }).code(400); - } - if (moment(expiresOn).isBefore(moment())) { - return reply({ message: 'Expired enrollment token' }).code(400); - } - - accessToken = uuid.v4().replace(/-/g, ""); - const remoteAddress = request.info.remoteAddress; - await persistBeat(callWithInternalUser, { - ...omit(request.payload, 'enrollment_token'), - id: beatId, - access_token: accessToken, - host_ip: remoteAddress - }); - - await deleteUsedEnrollmentToken(callWithInternalUser, enrollmentToken); - } catch (err) { - return reply(wrapEsError(err)); - } - - const response = { access_token: accessToken }; - reply(response).code(201); - } - }); -} diff --git a/x-pack/plugins/beats/server/routes/api/register_list_beats_route.js b/x-pack/plugins/beats/server/routes/api/register_list_beats_route.js deleted file mode 100644 index b84210988978f..0000000000000 --- a/x-pack/plugins/beats/server/routes/api/register_list_beats_route.js +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { - get, - omit -} from "lodash"; -import { INDEX_NAMES } from "../../../common/constants"; -import { callWithRequestFactory } from '../../lib/client'; -import { wrapEsError } from "../../lib/error_wrappers"; - -async function getBeats(callWithRequest) { - const params = { - index: INDEX_NAMES.BEATS, - type: '_doc', - q: 'type:beat' - }; - - const response = await callWithRequest('search', params); - return get(response, 'hits.hits', []); -} - -// TODO: add license check pre-hook -export function registerListBeatsRoute(server) { - server.route({ - method: 'GET', - path: '/api/beats/agents', - handler: async (request, reply) => { - const callWithRequest = callWithRequestFactory(server, request); - let beats; - - try { - beats = await getBeats(callWithRequest); - } catch (err) { - return reply(wrapEsError(err)); - } - - const response = { - beats: beats.map(beat => omit(beat._source.beat, ['access_token'])) - }; - reply(response); - } - }); -} diff --git a/x-pack/plugins/beats/server/routes/api/register_remove_tags_from_beats_route.js b/x-pack/plugins/beats/server/routes/api/register_remove_tags_from_beats_route.js deleted file mode 100644 index b5e66267b2ea4..0000000000000 --- a/x-pack/plugins/beats/server/routes/api/register_remove_tags_from_beats_route.js +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import Joi from 'joi'; -import { - get, - flatten, - uniq -} from 'lodash'; -import { INDEX_NAMES } from '../../../common/constants'; -import { callWithRequestFactory } from '../../lib/client'; -import { wrapEsError } from '../../lib/error_wrappers'; - -async function getDocs(callWithRequest, ids) { - const params = { - index: INDEX_NAMES.BEATS, - type: '_doc', - body: { ids }, - _source: false - }; - - const response = await callWithRequest('mget', params); - return get(response, 'docs', []); -} - -function getBeats(callWithRequest, beatIds) { - const ids = beatIds.map(beatId => `beat:${beatId}`); - return getDocs(callWithRequest, ids); -} - -function getTags(callWithRequest, tags) { - const ids = tags.map(tag => `tag:${tag}`); - return getDocs(callWithRequest, ids); -} - -async function findNonExistentItems(callWithRequest, items, getFn) { - const itemsFromEs = await getFn.call(null, callWithRequest, items); - return itemsFromEs.reduce((nonExistentItems, itemFromEs, idx) => { - if (!itemFromEs.found) { - nonExistentItems.push(items[idx]); - } - return nonExistentItems; - }, []); -} - -function findNonExistentBeatIds(callWithRequest, beatIds) { - return findNonExistentItems(callWithRequest, beatIds, getBeats); -} - -function findNonExistentTags(callWithRequest, tags) { - return findNonExistentItems(callWithRequest, tags, getTags); -} - -async function persistRemovals(callWithRequest, removals) { - const body = flatten(removals.map(({ beatId, tag }) => { - const script = '' - + 'def beat = ctx._source.beat; ' - + 'if (beat.tags != null) { ' - + ' beat.tags.removeAll([params.tag]); ' - + '}'; - - return [ - { update: { _id: `beat:${beatId}` } }, - { script: { source: script, params: { tag } } } - ]; - })); - - const params = { - index: INDEX_NAMES.BEATS, - type: '_doc', - body, - refresh: 'wait_for' - }; - - const response = await callWithRequest('bulk', params); - return get(response, 'items', []) - .map((item, resultIdx) => ({ - status: item.update.status, - result: item.update.result, - idxInRequest: removals[resultIdx].idxInRequest - })); -} - -function addNonExistentItemRemovalsToResponse(response, removals, nonExistentBeatIds, nonExistentTags) { - removals.forEach(({ beat_id: beatId, tag }, idx) => { - const isBeatNonExistent = nonExistentBeatIds.includes(beatId); - const isTagNonExistent = nonExistentTags.includes(tag); - - if (isBeatNonExistent && isTagNonExistent) { - response.removals[idx].status = 404; - response.removals[idx].result = `Beat ${beatId} and tag ${tag} not found`; - } else if (isBeatNonExistent) { - response.removals[idx].status = 404; - response.removals[idx].result = `Beat ${beatId} not found`; - } else if (isTagNonExistent) { - response.removals[idx].status = 404; - response.removals[idx].result = `Tag ${tag} not found`; - } - }); -} - -function addRemovalResultsToResponse(response, removalResults) { - removalResults.forEach(removalResult => { - const { idxInRequest, status, result } = removalResult; - response.removals[idxInRequest].status = status; - response.removals[idxInRequest].result = result; - }); -} - -// TODO: add license check pre-hook -// TODO: write to Kibana audit log file -export function registerRemoveTagsFromBeatsRoute(server) { - server.route({ - method: 'POST', - path: '/api/beats/agents_tags/removals', - config: { - validate: { - payload: Joi.object({ - removals: Joi.array().items(Joi.object({ - beat_id: Joi.string().required(), - tag: Joi.string().required() - })) - }).required() - } - }, - handler: async (request, reply) => { - const callWithRequest = callWithRequestFactory(server, request); - - const { removals } = request.payload; - const beatIds = uniq(removals.map(removal => removal.beat_id)); - const tags = uniq(removals.map(removal => removal.tag)); - - const response = { - removals: removals.map(() => ({ status: null })) - }; - - try { - // Handle removals containing non-existing beat IDs or tags - const nonExistentBeatIds = await findNonExistentBeatIds(callWithRequest, beatIds); - const nonExistentTags = await findNonExistentTags(callWithRequest, tags); - - addNonExistentItemRemovalsToResponse(response, removals, nonExistentBeatIds, nonExistentTags); - - const validRemovals = removals - .map((removal, idxInRequest) => ({ - beatId: removal.beat_id, - tag: removal.tag, - idxInRequest // so we can add the result of this removal to the correct place in the response - })) - .filter((removal, idx) => response.removals[idx].status === null); - - if (validRemovals.length > 0) { - const removalResults = await persistRemovals(callWithRequest, validRemovals); - addRemovalResultsToResponse(response, removalResults); - } - } catch (err) { - return reply(wrapEsError(err)); - } - - reply(response); - } - }); -} diff --git a/x-pack/plugins/beats/server/routes/api/register_set_tag_route.js b/x-pack/plugins/beats/server/routes/api/register_set_tag_route.js deleted file mode 100644 index 288fcade9929b..0000000000000 --- a/x-pack/plugins/beats/server/routes/api/register_set_tag_route.js +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import Joi from 'joi'; -import { - get, - uniq, - intersection -} from 'lodash'; -import { - INDEX_NAMES, - CONFIGURATION_BLOCKS -} from '../../../common/constants'; -import { callWithRequestFactory } from '../../lib/client'; -import { wrapEsError } from '../../lib/error_wrappers'; - -function validateUniquenessEnforcingTypes(configurationBlocks) { - const types = uniq(configurationBlocks.map(block => block.type)); - - // If none of the types in the given configuration blocks are uniqueness-enforcing, - // we don't need to perform any further validation checks. - const uniquenessEnforcingTypes = intersection(types, CONFIGURATION_BLOCKS.UNIQUENESS_ENFORCING_TYPES); - if (uniquenessEnforcingTypes.length === 0) { - return { isValid: true }; - } - - // Count the number of uniqueness-enforcing types in the given configuration blocks - const typeCountMap = configurationBlocks.reduce((typeCountMap, block) => { - const { type } = block; - if (!uniquenessEnforcingTypes.includes(type)) { - return typeCountMap; - } - - const count = typeCountMap[type] || 0; - return { - ...typeCountMap, - [type]: count + 1 - }; - }, {}); - - // If there is no more than one of any uniqueness-enforcing types in the given - // configuration blocks, we don't need to perform any further validation checks. - if (Object.values(typeCountMap).filter(count => count > 1).length === 0) { - return { isValid: true }; - } - - const message = Object.entries(typeCountMap) - .filter(([, count]) => count > 1) - .map(([type, count]) => `Expected only one configuration block of type '${type}' but found ${count}`) - .join(' '); - - return { - isValid: false, - message - }; -} - -async function validateConfigurationBlocks(configurationBlocks) { - return validateUniquenessEnforcingTypes(configurationBlocks); -} - -async function persistTag(callWithRequest, tag) { - const body = { - type: 'tag', - tag - }; - - const params = { - index: INDEX_NAMES.BEATS, - type: '_doc', - id: `tag:${tag.id}`, - body, - refresh: 'wait_for' - }; - - const response = await callWithRequest('index', params); - return response.result; -} - -// TODO: add license check pre-hook -// TODO: write to Kibana audit log file -export function registerSetTagRoute(server) { - server.route({ - method: 'PUT', - path: '/api/beats/tag/{tag}', - config: { - validate: { - payload: Joi.object({ - configuration_blocks: Joi.array().items( - Joi.object({ - type: Joi.string().required().valid(Object.values(CONFIGURATION_BLOCKS.TYPES)), - block_yml: Joi.string().required() - }) - ) - }).allow(null) - } - }, - handler: async (request, reply) => { - const callWithRequest = callWithRequestFactory(server, request); - - let result; - try { - const configurationBlocks = get(request, 'payload.configuration_blocks', []); - const { isValid, message } = await validateConfigurationBlocks(configurationBlocks); - if (!isValid) { - return reply({ message }).code(400); - } - - const tag = { - id: request.params.tag, - configuration_blocks: configurationBlocks - }; - result = await persistTag(callWithRequest, tag); - } catch (err) { - return reply(wrapEsError(err)); - } - - reply().code(result === 'created' ? 201 : 200); - } - }); -} diff --git a/x-pack/plugins/beats/server/routes/api/register_update_beat_route.js b/x-pack/plugins/beats/server/routes/api/register_update_beat_route.js deleted file mode 100644 index 5955e65f6bbaf..0000000000000 --- a/x-pack/plugins/beats/server/routes/api/register_update_beat_route.js +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import Joi from 'joi'; -import { get } from 'lodash'; -import { INDEX_NAMES } from '../../../common/constants'; -import { callWithInternalUserFactory } from '../../lib/client'; -import { wrapEsError } from '../../lib/error_wrappers'; -import { areTokensEqual } from '../../lib/crypto'; - -async function getBeat(callWithInternalUser, beatId) { - const params = { - index: INDEX_NAMES.BEATS, - type: '_doc', - id: `beat:${beatId}`, - ignore: [ 404 ] - }; - - const response = await callWithInternalUser('get', params); - if (!response.found) { - return null; - } - - return get(response, '_source.beat'); -} - -function persistBeat(callWithInternalUser, beat) { - const body = { - type: 'beat', - beat - }; - - const params = { - index: INDEX_NAMES.BEATS, - type: '_doc', - id: `beat:${beat.id}`, - body, - refresh: 'wait_for' - }; - return callWithInternalUser('index', params); -} - -// TODO: add license check pre-hook -// TODO: write to Kibana audit log file (include who did the verification as well) -export function registerUpdateBeatRoute(server) { - server.route({ - method: 'PUT', - path: '/api/beats/agent/{beatId}', - config: { - validate: { - payload: Joi.object({ - type: Joi.string(), - version: Joi.string(), - host_name: Joi.string(), - ephemeral_id: Joi.string(), - local_configuration_yml: Joi.string(), - metadata: Joi.object() - }).required(), - headers: Joi.object({ - 'kbn-beats-access-token': Joi.string().required() - }).options({ allowUnknown: true }) - }, - auth: false - }, - handler: async (request, reply) => { - const callWithInternalUser = callWithInternalUserFactory(server); - const { beatId } = request.params; - - try { - const beat = await getBeat(callWithInternalUser, beatId); - if (beat === null) { - return reply({ message: 'Beat not found' }).code(404); - } - - const isAccessTokenValid = areTokensEqual(beat.access_token, request.headers['kbn-beats-access-token']); - if (!isAccessTokenValid) { - return reply({ message: 'Invalid access token' }).code(401); - } - - const isBeatVerified = beat.hasOwnProperty('verified_on'); - if (!isBeatVerified) { - return reply({ message: 'Beat has not been verified' }).code(400); - } - - const remoteAddress = request.info.remoteAddress; - await persistBeat(callWithInternalUser, { - ...beat, - ...request.payload, - host_ip: remoteAddress - }); - } catch (err) { - return reply(wrapEsError(err)); - } - - reply().code(204); - } - }); -} diff --git a/x-pack/plugins/beats/server/routes/api/register_verify_beats_route.js b/x-pack/plugins/beats/server/routes/api/register_verify_beats_route.js deleted file mode 100644 index b2113029224a5..0000000000000 --- a/x-pack/plugins/beats/server/routes/api/register_verify_beats_route.js +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import Joi from 'joi'; -import moment from 'moment'; -import { - get, - flatten -} from 'lodash'; -import { INDEX_NAMES } from '../../../common/constants'; -import { callWithRequestFactory } from '../../lib/client'; -import { wrapEsError } from '../../lib/error_wrappers'; - -async function getBeats(callWithRequest, beatIds) { - const ids = beatIds.map(beatId => `beat:${beatId}`); - const params = { - index: INDEX_NAMES.BEATS, - type: '_doc', - body: { ids }, - _sourceInclude: [ 'beat.id', 'beat.verified_on' ] - }; - - const response = await callWithRequest('mget', params); - return get(response, 'docs', []); -} - -async function verifyBeats(callWithRequest, beatIds) { - if (!Array.isArray(beatIds) || (beatIds.length === 0)) { - return []; - } - - const verifiedOn = moment().toJSON(); - const body = flatten(beatIds.map(beatId => [ - { update: { _id: `beat:${beatId}` } }, - { doc: { beat: { verified_on: verifiedOn } } } - ])); - - const params = { - index: INDEX_NAMES.BEATS, - type: '_doc', - body, - refresh: 'wait_for' - }; - - const response = await callWithRequest('bulk', params); - return get(response, 'items', []); -} - -function findNonExistentBeatIds(beatsFromEs, beatIdsFromRequest) { - return beatsFromEs.reduce((nonExistentBeatIds, beatFromEs, idx) => { - if (!beatFromEs.found) { - nonExistentBeatIds.push(beatIdsFromRequest[idx]); - } - return nonExistentBeatIds; - }, []); -} - -function findAlreadyVerifiedBeatIds(beatsFromEs) { - return beatsFromEs - .filter(beat => beat.found) - .filter(beat => beat._source.beat.hasOwnProperty('verified_on')) - .map(beat => beat._source.beat.id); -} - -function findToBeVerifiedBeatIds(beatsFromEs) { - return beatsFromEs - .filter(beat => beat.found) - .filter(beat => !beat._source.beat.hasOwnProperty('verified_on')) - .map(beat => beat._source.beat.id); -} - -function findVerifiedBeatIds(verifications, toBeVerifiedBeatIds) { - return verifications.reduce((verifiedBeatIds, verification, idx) => { - if (verification.update.status === 200) { - verifiedBeatIds.push(toBeVerifiedBeatIds[idx]); - } - return verifiedBeatIds; - }, []); -} - -// TODO: add license check pre-hook -// TODO: write to Kibana audit log file -export function registerVerifyBeatsRoute(server) { - server.route({ - method: 'POST', - path: '/api/beats/agents/verify', - config: { - validate: { - payload: Joi.object({ - beats: Joi.array({ - id: Joi.string().required() - }).min(1) - }).required() - } - }, - handler: async (request, reply) => { - const callWithRequest = callWithRequestFactory(server, request); - - const beats = [...request.payload.beats]; - const beatIds = beats.map(beat => beat.id); - - let nonExistentBeatIds; - let alreadyVerifiedBeatIds; - let verifiedBeatIds; - - try { - const beatsFromEs = await getBeats(callWithRequest, beatIds); - - nonExistentBeatIds = findNonExistentBeatIds(beatsFromEs, beatIds); - alreadyVerifiedBeatIds = findAlreadyVerifiedBeatIds(beatsFromEs); - const toBeVerifiedBeatIds = findToBeVerifiedBeatIds(beatsFromEs); - - const verifications = await verifyBeats(callWithRequest, toBeVerifiedBeatIds); - verifiedBeatIds = findVerifiedBeatIds(verifications, toBeVerifiedBeatIds); - - } catch (err) { - return reply(wrapEsError(err)); - } - - beats.forEach(beat => { - if (nonExistentBeatIds.includes(beat.id)) { - beat.status = 404; - beat.result = 'not found'; - } else if (alreadyVerifiedBeatIds.includes(beat.id)) { - beat.status = 200; - beat.result = 'already verified'; - } else if (verifiedBeatIds.includes(beat.id)) { - beat.status = 200; - beat.result = 'verified'; - } else { - beat.status = 400; - beat.result = 'not verified'; - } - }); - - const response = { beats }; - reply(response); - } - }); -} diff --git a/x-pack/plugins/beats/server/utils/README.md b/x-pack/plugins/beats/server/utils/README.md new file mode 100644 index 0000000000000..8a6a27aa29867 --- /dev/null +++ b/x-pack/plugins/beats/server/utils/README.md @@ -0,0 +1 @@ +Utils should be data processing functions and other tools.... all in all utils is basicly everything that is not an adaptor, or presenter and yet too much to put in a lib. \ No newline at end of file diff --git a/x-pack/plugins/beats/server/lib/error_wrappers/index.js b/x-pack/plugins/beats/server/utils/error_wrappers/index.ts similarity index 100% rename from x-pack/plugins/beats/server/lib/error_wrappers/index.js rename to x-pack/plugins/beats/server/utils/error_wrappers/index.ts diff --git a/x-pack/plugins/beats/server/lib/error_wrappers/wrap_es_error.test.js b/x-pack/plugins/beats/server/utils/error_wrappers/wrap_es_error.test.js similarity index 90% rename from x-pack/plugins/beats/server/lib/error_wrappers/wrap_es_error.test.js rename to x-pack/plugins/beats/server/utils/error_wrappers/wrap_es_error.test.js index ec7338262844a..03b04a2ef61d2 100644 --- a/x-pack/plugins/beats/server/lib/error_wrappers/wrap_es_error.test.js +++ b/x-pack/plugins/beats/server/utils/error_wrappers/wrap_es_error.test.js @@ -8,7 +8,6 @@ import { wrapEsError } from './wrap_es_error'; describe('wrap_es_error', () => { describe('#wrapEsError', () => { - let originalError; beforeEach(() => { originalError = new Error('I am an error'); @@ -34,7 +33,9 @@ describe('wrap_es_error', () => { const wrappedError = wrapEsError(securityError); expect(wrappedError.isBoom).to.be(true); - expect(wrappedError.message).to.be('Insufficient user permissions for managing Logstash pipelines'); + expect(wrappedError.message).to.be( + 'Insufficient user permissions for managing Logstash pipelines' + ); }); }); }); diff --git a/x-pack/plugins/beats/server/lib/error_wrappers/wrap_es_error.js b/x-pack/plugins/beats/server/utils/error_wrappers/wrap_es_error.ts similarity index 79% rename from x-pack/plugins/beats/server/lib/error_wrappers/wrap_es_error.js rename to x-pack/plugins/beats/server/utils/error_wrappers/wrap_es_error.ts index d2abcab5c37dd..50ffbcb4a10c9 100644 --- a/x-pack/plugins/beats/server/lib/error_wrappers/wrap_es_error.js +++ b/x-pack/plugins/beats/server/utils/error_wrappers/wrap_es_error.ts @@ -13,10 +13,12 @@ import Boom from 'boom'; * @param err Object ES error * @return Object Boom error response */ -export function wrapEsError(err) { +export function wrapEsError(err: any) { const statusCode = err.statusCode; if (statusCode === 403) { - return Boom.forbidden('Insufficient user permissions for managing Beats configuration'); + return Boom.forbidden( + 'Insufficient user permissions for managing Beats configuration' + ); } return Boom.wrap(err, err.statusCode); } diff --git a/x-pack/plugins/beats/server/utils/find_non_existent_items.ts b/x-pack/plugins/beats/server/utils/find_non_existent_items.ts new file mode 100644 index 0000000000000..53e4066acc879 --- /dev/null +++ b/x-pack/plugins/beats/server/utils/find_non_existent_items.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export function findNonExistentItems(items: any, requestedItems: any) { + return items.reduce((nonExistentItems: any, item: any, idx: any) => { + if (!item.found) { + nonExistentItems.push(requestedItems[idx]); + } + return nonExistentItems; + }, []); +} diff --git a/x-pack/plugins/beats/server/lib/index_template/beats_template.json b/x-pack/plugins/beats/server/utils/index_templates/beats_template.json similarity index 97% rename from x-pack/plugins/beats/server/lib/index_template/beats_template.json rename to x-pack/plugins/beats/server/utils/index_templates/beats_template.json index 9f912f19b2a8d..0d00abbc5d759 100644 --- a/x-pack/plugins/beats/server/lib/index_template/beats_template.json +++ b/x-pack/plugins/beats/server/utils/index_templates/beats_template.json @@ -1,7 +1,5 @@ { - "index_patterns": [ - ".management-beats" - ], + "index_patterns": [".management-beats"], "version": 65000, "settings": { "index": { diff --git a/x-pack/plugins/beats/server/lib/index_template/index.js b/x-pack/plugins/beats/server/utils/index_templates/index.ts similarity index 73% rename from x-pack/plugins/beats/server/lib/index_template/index.js rename to x-pack/plugins/beats/server/utils/index_templates/index.ts index 04128e46ff0ea..eeaef7a68d49f 100644 --- a/x-pack/plugins/beats/server/lib/index_template/index.js +++ b/x-pack/plugins/beats/server/utils/index_templates/index.ts @@ -4,4 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ -export { installIndexTemplate } from './install_index_template'; +import beatsIndexTemplate from './beats_template.json'; +export { beatsIndexTemplate }; diff --git a/x-pack/plugins/beats/server/utils/polyfills.ts b/x-pack/plugins/beats/server/utils/polyfills.ts new file mode 100644 index 0000000000000..5291e2c72be7d --- /dev/null +++ b/x-pack/plugins/beats/server/utils/polyfills.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const entries = (obj: any) => { + const ownProps = Object.keys(obj); + let i = ownProps.length; + const resArray = new Array(i); // preallocate the Array + + while (i--) { + resArray[i] = [ownProps[i], obj[ownProps[i]]]; + } + + return resArray; +}; diff --git a/x-pack/plugins/beats/server/utils/wrap_request.ts b/x-pack/plugins/beats/server/utils/wrap_request.ts new file mode 100644 index 0000000000000..a29f9055f3688 --- /dev/null +++ b/x-pack/plugins/beats/server/utils/wrap_request.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FrameworkRequest, WrappableRequest } from '../lib/lib'; + +export const internalFrameworkRequest = Symbol('internalFrameworkRequest'); + +export function wrapRequest( + req: InternalRequest +): FrameworkRequest { + const { params, payload, query, headers, info } = req; + + return { + [internalFrameworkRequest]: req, + headers, + info, + params, + payload, + query, + }; +} diff --git a/x-pack/plugins/beats/tsconfig.json b/x-pack/plugins/beats/tsconfig.json new file mode 100644 index 0000000000000..4082f16a5d91c --- /dev/null +++ b/x-pack/plugins/beats/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tsconfig.json" +} diff --git a/x-pack/plugins/beats/server/lib/crypto/index.js b/x-pack/plugins/beats/types/json.t.ts similarity index 77% rename from x-pack/plugins/beats/server/lib/crypto/index.js rename to x-pack/plugins/beats/types/json.t.ts index 31fa5de67b2ca..46af99f7f740b 100644 --- a/x-pack/plugins/beats/server/lib/crypto/index.js +++ b/x-pack/plugins/beats/types/json.t.ts @@ -4,4 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -export { areTokensEqual } from './are_tokens_equal'; +declare module '*.json' { + const value: any; + export default value; +} diff --git a/x-pack/plugins/beats/wallaby.js b/x-pack/plugins/beats/wallaby.js new file mode 100644 index 0000000000000..c20488d35cfb6 --- /dev/null +++ b/x-pack/plugins/beats/wallaby.js @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +module.exports = function (wallaby) { + return { + debug: true, + files: [ + '../../tsconfig.json', + //'plugins/beats/public/**/*.+(js|jsx|ts|tsx|json|snap|css|less|sass|scss|jpg|jpeg|gif|png|svg)', + 'server/**/*.+(js|jsx|ts|tsx|json|snap|css|less|sass|scss|jpg|jpeg|gif|png|svg)', + 'common/**/*.+(js|jsx|ts|tsx|json|snap|css|less|sass|scss|jpg|jpeg|gif|png|svg)', + ], + + tests: ['**/*.test.ts'], + env: { + type: 'node', + runner: 'node', + }, + testFramework: 'jest', + compilers: { + '**/*.ts?(x)': wallaby.compilers.typeScript({ module: 'commonjs' }), + }, + }; +}; diff --git a/x-pack/yarn.lock b/x-pack/yarn.lock index f98f7fd87ee1b..238bac75499ea 100644 --- a/x-pack/yarn.lock +++ b/x-pack/yarn.lock @@ -124,6 +124,10 @@ url-join "^4.0.0" ws "^4.1.0" +"@types/boom@^4.3.8": + version "4.3.10" + resolved "https://registry.yarnpkg.com/@types/boom/-/boom-4.3.10.tgz#39dad8c0614c26b91ef016a57d7eee4ffe4f8a25" + "@types/delay@^2.0.1": version "2.0.1" resolved "https://registry.yarnpkg.com/@types/delay/-/delay-2.0.1.tgz#61bcf318a74b61e79d1658fbf054f984c90ef901" @@ -144,6 +148,12 @@ dependencies: "@types/node" "*" +"@types/hapi@15.0.1": + version "15.0.1" + resolved "https://registry.yarnpkg.com/@types/hapi/-/hapi-15.0.1.tgz#919e1d3a9160a080c9fdefaccc892239772e1258" + dependencies: + "@types/node" "*" + "@types/is-stream@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@types/is-stream/-/is-stream-1.1.0.tgz#b84d7bb207a210f2af9bed431dc0fbe9c4143be1" @@ -154,6 +164,14 @@ version "22.2.3" resolved "https://registry.yarnpkg.com/@types/jest/-/jest-22.2.3.tgz#0157c0316dc3722c43a7b71de3fdf3acbccef10d" +"@types/joi@^10.4.0": + version "10.6.2" + resolved "https://registry.yarnpkg.com/@types/joi/-/joi-10.6.2.tgz#0e7d632fe918c337784e87b16c7cc0098876179a" + +"@types/lodash@^3.10.0": + version "3.10.2" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-3.10.2.tgz#c1fbda1562ef5603c8192fe1fe65b017849d5873" + "@types/loglevel@^1.5.3": version "1.5.3" resolved "https://registry.yarnpkg.com/@types/loglevel/-/loglevel-1.5.3.tgz#adfce55383edc5998a2170ad581b3e23d6adb5b8" @@ -180,9 +198,9 @@ dependencies: "@types/retry" "*" -"@types/pngjs@^3.3.1": - version "3.3.1" - resolved "https://registry.yarnpkg.com/@types/pngjs/-/pngjs-3.3.1.tgz#47d97bd29dd6372856050e9e5e366517dd1ba2d8" +"@types/pngjs@^3.3.0": + version "3.3.2" + resolved "https://registry.yarnpkg.com/@types/pngjs/-/pngjs-3.3.2.tgz#8ed3bd655ab3a92ea32ada7a21f618e63b93b1d4" dependencies: "@types/node" "*" @@ -194,6 +212,12 @@ version "0.8.2" resolved "https://registry.yarnpkg.com/@types/url-join/-/url-join-0.8.2.tgz#1181ecbe1d97b7034e0ea1e35e62e86cc26b422d" +"@types/uuid@^3.4.3": + version "3.4.3" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-3.4.3.tgz#121ace265f5569ce40f4f6d0ff78a338c732a754" + dependencies: + "@types/node" "*" + "@types/ws@^4.0.1": version "4.0.2" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-4.0.2.tgz#b29037627dd7ba31ec49a4f1584840422efb856f" diff --git a/yarn.lock b/yarn.lock index e9a2db54979a6..35a8800d3483b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -555,6 +555,12 @@ version "0.8.2" resolved "https://registry.yarnpkg.com/@types/url-join/-/url-join-0.8.2.tgz#1181ecbe1d97b7034e0ea1e35e62e86cc26b422d" +"@types/uuid@^3.4.3": + version "3.4.3" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-3.4.3.tgz#121ace265f5569ce40f4f6d0ff78a338c732a754" + dependencies: + "@types/node" "*" + "@types/ws@^4.0.1": version "4.0.2" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-4.0.2.tgz#b29037627dd7ba31ec49a4f1584840422efb856f" @@ -12237,7 +12243,7 @@ source-map-support@^0.5.0: dependencies: source-map "^0.6.0" -source-map-support@^0.5.3, source-map-support@^0.5.5: +source-map-support@^0.5.5, source-map-support@^0.5.6: version "0.5.6" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.6.tgz#4435cee46b1aab62b8e8610ce60f788091c51c13" dependencies: @@ -13215,17 +13221,16 @@ ts-loader@^3.5.0: micromatch "^3.1.4" semver "^5.0.1" -ts-node@^6.0.3: - version "6.0.3" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-6.0.3.tgz#28bf74bcad134fad17f7469dad04638ece03f0f4" +ts-node@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-6.1.1.tgz#19607140acb06150441fcdb61be11f73f7b6657e" dependencies: arrify "^1.0.0" - chalk "^2.3.0" diff "^3.1.0" make-error "^1.1.1" minimist "^1.2.0" mkdirp "^0.5.1" - source-map-support "^0.5.3" + source-map-support "^0.5.6" yn "^2.0.0" tslib@^1.7.1, tslib@^1.8.0, tslib@^1.8.1: From ef4946a19859a1ff7ac808354e447dfe9933cc90 Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Fri, 6 Jul 2018 18:54:27 -0400 Subject: [PATCH 18/94] [Beats Management] Move tokens to use JWT, add more complete test suite (#20317) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * inital effort to move to JWT and added jest based tests on libs * assign beats tests all passing * token tests now pass * add more tests * all tests now green * fix broken test, this is beats CM not logstash 😊 * added readme * move enrollment token back to a hash * remove un-needed comment * alias lodash get to avoid confusion * isolated hash creation --- x-pack/package.json | 6 + x-pack/plugins/beats/index.ts | 1 + x-pack/plugins/beats/readme.md | 7 + .../beats/elasticsearch_beats_adapter.ts | 32 +-- .../adapters/beats/memory_beats_adapter.ts | 123 ++++++++++ .../kibana/kibana_framework_adapter.ts | 26 +- .../kibana/testing_framework_adapter.ts | 83 +++++++ .../tags/elasticsearch_tags_adapter.ts | 10 +- .../lib/adapters/tags/memory_tags_adapter.ts | 29 +++ .../tokens/elasticsearch_tokens_adapter.ts | 1 + .../adapters/tokens/memory_tokens_adapter.ts | 47 ++++ .../__tests__/beats/assign_tags.test.ts | 231 ++++++++++++++++++ .../domains/__tests__/beats/enroll.test.ts | 136 +++++++++++ .../domains/__tests__/beats/verify.test.ts | 192 +++++++++++++++ .../lib/domains/__tests__/tokens.test.ts | 87 +++++++ .../plugins/beats/server/lib/domains/beats.ts | 41 ++-- .../beats/server/lib/domains/tokens.ts | 94 ++++++- x-pack/plugins/beats/server/lib/lib.ts | 22 +- .../beats/server/rest_api/beats/enroll.ts | 13 +- .../beats/server/rest_api/beats/update.ts | 2 +- .../beats/server/rest_api/beats/verify.ts | 13 +- .../error_wrappers/wrap_es_error.test.js | 3 +- .../server/utils/find_non_existent_items.ts | 26 +- x-pack/plugins/beats/wallaby.js | 39 ++- .../api_integration/apis/beats/enroll_beat.js | 83 +++---- .../api_integration/apis/beats/update_beat.js | 75 +++--- .../apis/beats/verify_beats.js | 29 +-- .../es_archives/beats/list/data.json.gz | Bin 447 -> 521 bytes 28 files changed, 1254 insertions(+), 197 deletions(-) create mode 100644 x-pack/plugins/beats/readme.md create mode 100644 x-pack/plugins/beats/server/lib/adapters/beats/memory_beats_adapter.ts create mode 100644 x-pack/plugins/beats/server/lib/adapters/famework/kibana/testing_framework_adapter.ts create mode 100644 x-pack/plugins/beats/server/lib/adapters/tags/memory_tags_adapter.ts create mode 100644 x-pack/plugins/beats/server/lib/adapters/tokens/memory_tokens_adapter.ts create mode 100644 x-pack/plugins/beats/server/lib/domains/__tests__/beats/assign_tags.test.ts create mode 100644 x-pack/plugins/beats/server/lib/domains/__tests__/beats/enroll.test.ts create mode 100644 x-pack/plugins/beats/server/lib/domains/__tests__/beats/verify.test.ts create mode 100644 x-pack/plugins/beats/server/lib/domains/__tests__/tokens.test.ts diff --git a/x-pack/package.json b/x-pack/package.json index 405a499113089..40f22526ae5b6 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -28,11 +28,14 @@ "@kbn/plugin-helpers": "link:../packages/kbn-plugin-helpers", "@kbn/test": "link:../packages/kbn-test", "@types/boom": "^4.3.8", + "@types/chance": "^1.0.1", + "@types/expect.js": "^0.3.29", "@types/hapi": "15.0.1", "@types/jest": "^22.2.3", "@types/joi": "^10.4.0", "@types/lodash": "^3.10.0", "@types/pngjs": "^3.3.0", + "@types/sinon": "^5.0.1", "abab": "^1.0.4", "ansicolors": "0.3.2", "aws-sdk": "2.2.33", @@ -92,6 +95,8 @@ "@kbn/ui-framework": "link:../packages/kbn-ui-framework", "@samverschueren/stream-to-observable": "^0.3.0", "@slack/client": "^4.2.2", + "@types/elasticsearch": "^5.0.24", + "@types/jsonwebtoken": "^7.2.7", "@types/uuid": "^3.4.3", "angular-paging": "2.2.1", "angular-resource": "1.4.9", @@ -123,6 +128,7 @@ "isomorphic-fetch": "2.2.1", "joi": "6.10.1", "jquery": "^3.3.1", + "jsonwebtoken": "^8.3.0", "jstimezonedetect": "1.0.5", "lodash": "3.10.1", "lodash.mean": "^4.1.0", diff --git a/x-pack/plugins/beats/index.ts b/x-pack/plugins/beats/index.ts index ce9b8147dbe4b..ced89c186f73e 100644 --- a/x-pack/plugins/beats/index.ts +++ b/x-pack/plugins/beats/index.ts @@ -15,6 +15,7 @@ export function beats(kibana: any) { config: () => Joi.object({ enabled: Joi.boolean().default(true), + encryptionKey: Joi.string(), enrollmentTokensTtlInSeconds: Joi.number() .integer() .min(1) diff --git a/x-pack/plugins/beats/readme.md b/x-pack/plugins/beats/readme.md new file mode 100644 index 0000000000000..fdd56a393e573 --- /dev/null +++ b/x-pack/plugins/beats/readme.md @@ -0,0 +1,7 @@ +# Documentation for Beats CM in x-pack kibana + +### Run tests + +``` +node scripts/jest.js plugins/beats --watch +``` diff --git a/x-pack/plugins/beats/server/lib/adapters/beats/elasticsearch_beats_adapter.ts b/x-pack/plugins/beats/server/lib/adapters/beats/elasticsearch_beats_adapter.ts index 283f65c1258ae..76fbf956dafc9 100644 --- a/x-pack/plugins/beats/server/lib/adapters/beats/elasticsearch_beats_adapter.ts +++ b/x-pack/plugins/beats/server/lib/adapters/beats/elasticsearch_beats_adapter.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { flatten, get, omit } from 'lodash'; +import { flatten, get as _get, omit } from 'lodash'; import moment from 'moment'; import { INDEX_NAMES } from '../../../../common/constants'; import { @@ -35,7 +35,7 @@ export class ElasticsearchBeatsAdapter implements CMBeatsAdapter { return null; } - return get(response, '_source.beat'); + return _get(response, '_source.beat'); } public async insert(beat: CMBeat) { @@ -73,22 +73,6 @@ export class ElasticsearchBeatsAdapter implements CMBeatsAdapter { public async getWithIds(req: FrameworkRequest, beatIds: string[]) { const ids = beatIds.map(beatId => `beat:${beatId}`); - const params = { - _source: false, - body: { - ids, - }, - index: INDEX_NAMES.BEATS, - type: '_doc', - }; - const response = await this.framework.callWithRequest(req, 'mget', params); - return get(response, 'docs', []); - } - - // TODO merge with getBeatsWithIds - public async getVerifiedWithIds(req: FrameworkRequest, beatIds: string[]) { - const ids = beatIds.map(beatId => `beat:${beatId}`); - const params = { _sourceInclude: ['beat.id', 'beat.verified_on'], body: { @@ -98,7 +82,10 @@ export class ElasticsearchBeatsAdapter implements CMBeatsAdapter { type: '_doc', }; const response = await this.framework.callWithRequest(req, 'mget', params); - return get(response, 'docs', []); + + return get(response, 'docs', []) + .filter((b: any) => b.found) + .map((b: any) => b._source.beat); } public async verifyBeats(req: FrameworkRequest, beatIds: string[]) { @@ -115,6 +102,7 @@ export class ElasticsearchBeatsAdapter implements CMBeatsAdapter { ); const params = { + _sourceInclude: ['beat.id', 'beat.verified_on'], body, index: INDEX_NAMES.BEATS, refresh: 'wait_for', @@ -122,7 +110,11 @@ export class ElasticsearchBeatsAdapter implements CMBeatsAdapter { }; const response = await this.framework.callWithRequest(req, 'bulk', params); - return get(response, 'items', []); + + return _get(response, 'items', []).map(b => ({ + ..._get(b, 'update.get._source.beat', {}), + updateStatus: _get(b, 'update.result', 'unknown error'), + })); } public async getAll(req: FrameworkRequest) { diff --git a/x-pack/plugins/beats/server/lib/adapters/beats/memory_beats_adapter.ts b/x-pack/plugins/beats/server/lib/adapters/beats/memory_beats_adapter.ts new file mode 100644 index 0000000000000..9de8297c0f73e --- /dev/null +++ b/x-pack/plugins/beats/server/lib/adapters/beats/memory_beats_adapter.ts @@ -0,0 +1,123 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { omit } from 'lodash'; +import moment from 'moment'; + +import { + CMBeat, + CMBeatsAdapter, + CMTagAssignment, + FrameworkRequest, +} from '../../lib'; + +export class MemoryBeatsAdapter implements CMBeatsAdapter { + private beatsDB: CMBeat[]; + + constructor(beatsDB: CMBeat[]) { + this.beatsDB = beatsDB; + } + + public async get(id: string) { + return this.beatsDB.find(beat => beat.id === id); + } + + public async insert(beat: CMBeat) { + this.beatsDB.push(beat); + } + + public async update(beat: CMBeat) { + const beatIndex = this.beatsDB.findIndex(b => b.id === beat.id); + + this.beatsDB[beatIndex] = { + ...this.beatsDB[beatIndex], + ...beat, + }; + } + + public async getWithIds(req: FrameworkRequest, beatIds: string[]) { + return this.beatsDB.filter(beat => beatIds.includes(beat.id)); + } + + public async verifyBeats(req: FrameworkRequest, beatIds: string[]) { + if (!Array.isArray(beatIds) || beatIds.length === 0) { + return []; + } + + const verifiedOn = moment().toJSON(); + + this.beatsDB.forEach((beat, i) => { + if (beatIds.includes(beat.id)) { + this.beatsDB[i].verified_on = verifiedOn; + } + }); + + return this.beatsDB.filter(beat => beatIds.includes(beat.id)); + } + + public async getAll(req: FrameworkRequest) { + return this.beatsDB.map((beat: any) => omit(beat, ['access_token'])); + } + + public async removeTagsFromBeats( + req: FrameworkRequest, + removals: CMTagAssignment[] + ): Promise { + const beatIds = removals.map(r => r.beatId); + + const response = this.beatsDB + .filter(beat => beatIds.includes(beat.id)) + .map(beat => { + const tagData = removals.find(r => r.beatId === beat.id); + if (tagData) { + if (beat.tags) { + beat.tags = beat.tags.filter(tag => tag !== tagData.tag); + } + } + return beat; + }); + + return response.map((item: CMBeat, resultIdx: number) => ({ + idxInRequest: removals[resultIdx].idxInRequest, + result: 'updated', + status: 200, + })); + } + + public async assignTagsToBeats( + req: FrameworkRequest, + assignments: CMTagAssignment[] + ): Promise { + const beatIds = assignments.map(r => r.beatId); + + this.beatsDB.filter(beat => beatIds.includes(beat.id)).map(beat => { + // get tags that need to be assigned to this beat + const tags = assignments + .filter(a => a.beatId === beat.id) + .map((t: CMTagAssignment) => t.tag); + + if (tags.length > 0) { + if (!beat.tags) { + beat.tags = []; + } + const nonExistingTags = tags.filter( + (t: string) => beat.tags && !beat.tags.includes(t) + ); + + if (nonExistingTags.length > 0) { + beat.tags = beat.tags.concat(nonExistingTags); + } + } + return beat; + }); + + return assignments.map((item: CMTagAssignment, resultIdx: number) => ({ + idxInRequest: assignments[resultIdx].idxInRequest, + result: 'updated', + status: 200, + })); + } +} diff --git a/x-pack/plugins/beats/server/lib/adapters/famework/kibana/kibana_framework_adapter.ts b/x-pack/plugins/beats/server/lib/adapters/famework/kibana/kibana_framework_adapter.ts index 6fc2fc4853b03..a54997370ac5d 100644 --- a/x-pack/plugins/beats/server/lib/adapters/famework/kibana/kibana_framework_adapter.ts +++ b/x-pack/plugins/beats/server/lib/adapters/famework/kibana/kibana_framework_adapter.ts @@ -19,18 +19,25 @@ import { export class KibanaBackendFrameworkAdapter implements BackendFrameworkAdapter { public version: string; - private server: Server; + private cryptoHash: string | null; constructor(hapiServer: Server) { this.server = hapiServer; this.version = hapiServer.plugins.kibana.status.plugin.version; + this.cryptoHash = null; + + this.validateConfig(); } public getSetting(settingPath: string) { - // TODO type check this properly + // TODO type check server properly + if (settingPath === 'xpack.beats.encryptionKey') { + // @ts-ignore + return this.server.config().get(settingPath) || this.cryptoHash; + } // @ts-ignore - return this.server.config().get(settingPath); + return this.server.config().get(settingPath) || this.cryptoHash; } public exposeStaticDir(urlPath: string, dir: string): void { @@ -79,4 +86,17 @@ export class KibanaBackendFrameworkAdapter implements BackendFrameworkAdapter { const fields = await callWithRequest(internalRequest, ...rest); return fields; } + + private validateConfig() { + // @ts-ignore + const config = this.server.config(); + const encryptionKey = config.get('xpack.beats.encryptionKey'); + + if (!encryptionKey) { + this.server.log( + 'Using a default encryption key for xpack.beats.encryptionKey. It is recommended that you set xpack.beats.encryptionKey in kibana.yml with a unique token' + ); + this.cryptoHash = 'xpack_beats_default_encryptionKey'; + } + } } diff --git a/x-pack/plugins/beats/server/lib/adapters/famework/kibana/testing_framework_adapter.ts b/x-pack/plugins/beats/server/lib/adapters/famework/kibana/testing_framework_adapter.ts new file mode 100644 index 0000000000000..9c928a05cfd5a --- /dev/null +++ b/x-pack/plugins/beats/server/lib/adapters/famework/kibana/testing_framework_adapter.ts @@ -0,0 +1,83 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Client } from 'elasticsearch'; +import { Request } from 'hapi'; +import { get } from 'lodash'; +import { + BackendFrameworkAdapter, + FrameworkRequest, + FrameworkRouteOptions, + WrappableRequest, +} from '../../../lib'; + +interface TestSettings { + enrollmentTokensTtlInSeconds: number; + encryptionKey: string; +} + +export class TestingBackendFrameworkAdapter implements BackendFrameworkAdapter { + public version: string; + private client: Client | null; + private settings: TestSettings; + + constructor(client: Client | null, settings: TestSettings) { + this.client = client; + this.settings = settings || { + encryptionKey: 'something_who_cares', + enrollmentTokensTtlInSeconds: 10 * 60, // 10 minutes + }; + this.version = 'testing'; + } + + public getSetting(settingPath: string) { + switch (settingPath) { + case 'xpack.beats.enrollmentTokensTtlInSeconds': + return this.settings.enrollmentTokensTtlInSeconds; + case 'xpack.beats.encryptionKey': + return this.settings.encryptionKey; + } + } + + public exposeStaticDir(urlPath: string, dir: string): void { + // not yet testable + } + + public registerRoute( + route: FrameworkRouteOptions + ) { + // not yet testable + } + + public installIndexTemplate(name: string, template: {}) { + if (this.client) { + return this.client.indices.putTemplate({ + body: template, + name, + }); + } + } + + public async callWithInternalUser(esMethod: string, options: {}) { + const api = get(this.client, esMethod); + + api(options); + + return await api(options); + } + + public async callWithRequest( + req: FrameworkRequest, + esMethod: string, + options: {} + ) { + const api = get(this.client, esMethod); + + api(options); + + return await api(options); + } +} diff --git a/x-pack/plugins/beats/server/lib/adapters/tags/elasticsearch_tags_adapter.ts b/x-pack/plugins/beats/server/lib/adapters/tags/elasticsearch_tags_adapter.ts index 2293ba77677fd..44aea344151ca 100644 --- a/x-pack/plugins/beats/server/lib/adapters/tags/elasticsearch_tags_adapter.ts +++ b/x-pack/plugins/beats/server/lib/adapters/tags/elasticsearch_tags_adapter.ts @@ -25,7 +25,7 @@ export class ElasticsearchTagsAdapter implements CMTagsAdapter { // TODO abstract to kibana adapter as the more generic getDocs const params = { - _source: false, + _sourceInclude: ['tag.configuration_blocks'], body: { ids, }, @@ -33,7 +33,13 @@ export class ElasticsearchTagsAdapter implements CMTagsAdapter { type: '_doc', }; const response = await this.framework.callWithRequest(req, 'mget', params); - return get(response, 'docs', []); + + return get(response, 'docs', []) + .filter((b: any) => b.found) + .map((b: any) => ({ + ...b._source.tag, + id: b._id.replace('tag:', ''), + })); } public async upsertTag(req: FrameworkRequest, tag: BeatTag) { diff --git a/x-pack/plugins/beats/server/lib/adapters/tags/memory_tags_adapter.ts b/x-pack/plugins/beats/server/lib/adapters/tags/memory_tags_adapter.ts new file mode 100644 index 0000000000000..4d2a80e1b39c2 --- /dev/null +++ b/x-pack/plugins/beats/server/lib/adapters/tags/memory_tags_adapter.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { BeatTag, CMTagsAdapter, FrameworkRequest } from '../../lib'; + +export class MemoryTagsAdapter implements CMTagsAdapter { + private tagsDB: BeatTag[] = []; + + constructor(tagsDB: BeatTag[]) { + this.tagsDB = tagsDB; + } + + public async getTagsWithIds(req: FrameworkRequest, tagIds: string[]) { + return this.tagsDB.filter(tag => tagIds.includes(tag.id)); + } + + public async upsertTag(req: FrameworkRequest, tag: BeatTag) { + const existingTagIndex = this.tagsDB.findIndex(t => t.id === tag.id); + if (existingTagIndex !== -1) { + this.tagsDB[existingTagIndex] = tag; + } else { + this.tagsDB.push(tag); + } + return tag; + } +} diff --git a/x-pack/plugins/beats/server/lib/adapters/tokens/elasticsearch_tokens_adapter.ts b/x-pack/plugins/beats/server/lib/adapters/tokens/elasticsearch_tokens_adapter.ts index c8969c7ab08d0..7a63c784ecf6a 100644 --- a/x-pack/plugins/beats/server/lib/adapters/tokens/elasticsearch_tokens_adapter.ts +++ b/x-pack/plugins/beats/server/lib/adapters/tokens/elasticsearch_tokens_adapter.ts @@ -79,5 +79,6 @@ export class ElasticsearchTokensAdapter implements CMTokensAdapter { }; await this.framework.callWithRequest(req, 'bulk', params); + return tokens; } } diff --git a/x-pack/plugins/beats/server/lib/adapters/tokens/memory_tokens_adapter.ts b/x-pack/plugins/beats/server/lib/adapters/tokens/memory_tokens_adapter.ts new file mode 100644 index 0000000000000..1734327007e08 --- /dev/null +++ b/x-pack/plugins/beats/server/lib/adapters/tokens/memory_tokens_adapter.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CMTokensAdapter, EnrollmentToken, FrameworkRequest } from '../../lib'; + +export class MemoryTokensAdapter implements CMTokensAdapter { + private tokenDB: EnrollmentToken[]; + + constructor(tokenDB: EnrollmentToken[]) { + this.tokenDB = tokenDB; + } + + public async deleteEnrollmentToken(enrollmentToken: string) { + const index = this.tokenDB.findIndex( + token => token.token === enrollmentToken + ); + + if (index > -1) { + this.tokenDB.splice(index, 1); + } + } + + public async getEnrollmentToken( + tokenString: string + ): Promise { + return new Promise(resolve => { + return resolve(this.tokenDB.find(token => token.token === tokenString)); + }); + } + + public async upsertTokens(req: FrameworkRequest, tokens: EnrollmentToken[]) { + tokens.forEach(token => { + const existingIndex = this.tokenDB.findIndex( + t => t.token === token.token + ); + if (existingIndex !== -1) { + this.tokenDB[existingIndex] = token; + } else { + this.tokenDB.push(token); + } + }); + return tokens; + } +} diff --git a/x-pack/plugins/beats/server/lib/domains/__tests__/beats/assign_tags.test.ts b/x-pack/plugins/beats/server/lib/domains/__tests__/beats/assign_tags.test.ts new file mode 100644 index 0000000000000..c1e360ffd75f4 --- /dev/null +++ b/x-pack/plugins/beats/server/lib/domains/__tests__/beats/assign_tags.test.ts @@ -0,0 +1,231 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from 'expect.js'; +import { wrapRequest } from '../../../../utils/wrap_request'; +import { MemoryBeatsAdapter } from '../../../adapters/beats/memory_beats_adapter'; +import { TestingBackendFrameworkAdapter } from '../../../adapters/famework/kibana/testing_framework_adapter'; +import { MemoryTagsAdapter } from '../../../adapters/tags/memory_tags_adapter'; +import { MemoryTokensAdapter } from '../../../adapters/tokens/memory_tokens_adapter'; + +import { BeatTag, CMBeat } from './../../../lib'; + +import { CMBeatsDomain } from '../../beats'; +import { CMTagsDomain } from '../../tags'; +import { CMTokensDomain } from '../../tokens'; + +import Chance from 'chance'; + +const seed = Date.now(); +const chance = new Chance(seed); + +const fakeReq = wrapRequest({ + headers: {}, + info: {}, + params: {}, + payload: {}, + query: {}, +}); + +const settings = { + encryptionKey: 'something_who_cares', + enrollmentTokensTtlInSeconds: 10 * 60, // 10 minutes +}; + +describe('Beats Domain Lib', () => { + let beatsLib: CMBeatsDomain; + let beatsDB: CMBeat[] = []; + let tagsDB: BeatTag[] = []; + + describe('assign_tags_to_beats', () => { + beforeEach(async () => { + beatsDB = [ + { + access_token: '9a6c99ae0fd84b068819701169cd8a4b', + host_ip: '1.2.3.4', + host_name: 'foo.bar.com', + id: 'qux', + type: 'filebeat', + }, + { + access_token: '188255eb560a4448b72656c5e99cae6f', + host_ip: '22.33.11.44', + host_name: 'baz.bar.com', + id: 'baz', + type: 'metricbeat', + }, + { + access_token: '93c4a4dd08564c189a7ec4e4f046b975', + host_ip: '1.2.3.4', + host_name: 'foo.bar.com', + id: 'foo', + tags: ['production', 'qa'], + type: 'metricbeat', + verified_on: '2018-05-15T16:25:38.924Z', + }, + { + access_token: '3c4a4dd08564c189a7ec4e4f046b9759', + host_ip: '11.22.33.44', + host_name: 'foo.com', + id: 'bar', + type: 'filebeat', + }, + ]; + tagsDB = [ + { + configuration_blocks: [], + id: 'production', + }, + { + configuration_blocks: [], + id: 'development', + }, + { + configuration_blocks: [], + id: 'qa', + }, + ]; + const framework = new TestingBackendFrameworkAdapter(null, settings); + + const tokensLib = new CMTokensDomain(new MemoryTokensAdapter([]), { + framework, + }); + + const tagsLib = new CMTagsDomain(new MemoryTagsAdapter(tagsDB)); + + beatsLib = new CMBeatsDomain(new MemoryBeatsAdapter(beatsDB), { + tags: tagsLib, + tokens: tokensLib, + }); + }); + + it('should add a single tag to a single beat', async () => { + const apiResponse = await beatsLib.assignTagsToBeats(fakeReq, [ + { beatId: 'bar', tag: 'production' }, + ]); + + expect(apiResponse.assignments).to.eql([ + { status: 200, result: 'updated' }, + ]); + }); + + it('should not re-add an existing tag to a beat', async () => { + const tags = ['production']; + + let beat = beatsDB.find(b => b.id === 'foo') as any; + expect(beat.tags).to.eql([...tags, 'qa']); + + // Adding the existing tag + const apiResponse = await beatsLib.assignTagsToBeats(fakeReq, [ + { beatId: 'foo', tag: 'production' }, + ]); + + expect(apiResponse.assignments).to.eql([ + { status: 200, result: 'updated' }, + ]); + + beat = beatsDB.find(b => b.id === 'foo') as any; + expect(beat.tags).to.eql([...tags, 'qa']); + }); + + it('should add a single tag to a multiple beats', async () => { + const apiResponse = await beatsLib.assignTagsToBeats(fakeReq, [ + { beatId: 'foo', tag: 'development' }, + { beatId: 'bar', tag: 'development' }, + ]); + + expect(apiResponse.assignments).to.eql([ + { status: 200, result: 'updated' }, + { status: 200, result: 'updated' }, + ]); + + let beat = beatsDB.find(b => b.id === 'foo') as any; + expect(beat.tags).to.eql(['production', 'qa', 'development']); // as beat 'foo' already had 'production' and 'qa' tags attached to it + + beat = beatsDB.find(b => b.id === 'bar') as any; + expect(beat.tags).to.eql(['development']); + }); + + it('should add multiple tags to a single beat', async () => { + const apiResponse = await beatsLib.assignTagsToBeats(fakeReq, [ + { beatId: 'bar', tag: 'development' }, + { beatId: 'bar', tag: 'production' }, + ]); + + expect(apiResponse.assignments).to.eql([ + { status: 200, result: 'updated' }, + { status: 200, result: 'updated' }, + ]); + + const beat = beatsDB.find(b => b.id === 'bar') as any; + expect(beat.tags).to.eql(['development', 'production']); + }); + + it('should add multiple tags to a multiple beats', async () => { + const apiResponse = await beatsLib.assignTagsToBeats(fakeReq, [ + { beatId: 'foo', tag: 'development' }, + { beatId: 'bar', tag: 'production' }, + ]); + + expect(apiResponse.assignments).to.eql([ + { status: 200, result: 'updated' }, + { status: 200, result: 'updated' }, + ]); + + let beat = beatsDB.find(b => b.id === 'foo') as any; + expect(beat.tags).to.eql(['production', 'qa', 'development']); // as beat 'foo' already had 'production' and 'qa' tags attached to it + + beat = beatsDB.find(b => b.id === 'bar') as any; + expect(beat.tags).to.eql(['production']); + }); + + it('should return errors for non-existent beats', async () => { + const nonExistentBeatId = chance.word(); + + const apiResponse = await beatsLib.assignTagsToBeats(fakeReq, [ + { beatId: nonExistentBeatId, tag: 'production' }, + ]); + + expect(apiResponse.assignments).to.eql([ + { status: 404, result: `Beat ${nonExistentBeatId} not found` }, + ]); + }); + + it('should return errors for non-existent tags', async () => { + const nonExistentTag = chance.word(); + + const apiResponse = await beatsLib.assignTagsToBeats(fakeReq, [ + { beatId: 'bar', tag: nonExistentTag }, + ]); + + expect(apiResponse.assignments).to.eql([ + { status: 404, result: `Tag ${nonExistentTag} not found` }, + ]); + + const beat = beatsDB.find(b => b.id === 'bar') as any; + expect(beat).to.not.have.property('tags'); + }); + + it('should return errors for non-existent beats and tags', async () => { + const nonExistentBeatId = chance.word(); + const nonExistentTag = chance.word(); + + const apiResponse = await beatsLib.assignTagsToBeats(fakeReq, [ + { beatId: nonExistentBeatId, tag: nonExistentTag }, + ]); + + expect(apiResponse.assignments).to.eql([ + { + result: `Beat ${nonExistentBeatId} and tag ${nonExistentTag} not found`, + status: 404, + }, + ]); + + const beat = beatsDB.find(b => b.id === 'bar') as any; + expect(beat).to.not.have.property('tags'); + }); + }); +}); diff --git a/x-pack/plugins/beats/server/lib/domains/__tests__/beats/enroll.test.ts b/x-pack/plugins/beats/server/lib/domains/__tests__/beats/enroll.test.ts new file mode 100644 index 0000000000000..f52f1096227c0 --- /dev/null +++ b/x-pack/plugins/beats/server/lib/domains/__tests__/beats/enroll.test.ts @@ -0,0 +1,136 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from 'expect.js'; +import { MemoryBeatsAdapter } from '../../../adapters/beats/memory_beats_adapter'; +import { TestingBackendFrameworkAdapter } from '../../../adapters/famework/kibana/testing_framework_adapter'; +import { MemoryTagsAdapter } from '../../../adapters/tags/memory_tags_adapter'; +import { MemoryTokensAdapter } from '../../../adapters/tokens/memory_tokens_adapter'; + +import { BeatTag, CMBeat, EnrollmentToken } from './../../../lib'; + +import { CMBeatsDomain } from '../../beats'; +import { CMTagsDomain } from '../../tags'; +import { CMTokensDomain } from '../../tokens'; + +import Chance from 'chance'; +import { sign as signToken } from 'jsonwebtoken'; +import { omit } from 'lodash'; +import moment from 'moment'; + +const seed = Date.now(); +const chance = new Chance(seed); + +const settings = { + encryptionKey: 'something_who_cares', + enrollmentTokensTtlInSeconds: 10 * 60, // 10 minutes +}; + +describe('Beats Domain Lib', () => { + let beatsLib: CMBeatsDomain; + let tokensLib: CMTokensDomain; + + let beatsDB: CMBeat[] = []; + let tagsDB: BeatTag[] = []; + let tokensDB: EnrollmentToken[] = []; + let validEnrollmentToken: string; + let beatId: string; + let beat: Partial; + + describe('enroll_beat', () => { + beforeEach(async () => { + validEnrollmentToken = chance.word(); + beatId = chance.word(); + + beatsDB = []; + tagsDB = []; + tokensDB = [ + { + expires_on: moment() + .add(4, 'hours') + .toJSON(), + token: validEnrollmentToken, + }, + ]; + + const version = + chance.integer({ min: 1, max: 10 }) + + '.' + + chance.integer({ min: 1, max: 10 }) + + '.' + + chance.integer({ min: 1, max: 10 }); + + beat = { + host_name: 'foo.bar.com', + type: 'filebeat', + version, + }; + + const framework = new TestingBackendFrameworkAdapter(null, settings); + + tokensLib = new CMTokensDomain(new MemoryTokensAdapter(tokensDB), { + framework, + }); + + const tagsLib = new CMTagsDomain(new MemoryTagsAdapter(tagsDB)); + + beatsLib = new CMBeatsDomain(new MemoryBeatsAdapter(beatsDB), { + tags: tagsLib, + tokens: tokensLib, + }); + }); + + it('should enroll beat, returning an access token', async () => { + const { token } = await tokensLib.getEnrollmentToken( + validEnrollmentToken + ); + + expect(token).to.equal(validEnrollmentToken); + const { accessToken } = await beatsLib.enrollBeat( + beatId, + '192.168.1.1', + omit(beat, 'enrollment_token') + ); + + expect(beatsDB.length).to.eql(1); + expect(beatsDB[0]).to.have.property('host_ip'); + + expect(accessToken).to.eql(beatsDB[0].access_token); + + await tokensLib.deleteEnrollmentToken(validEnrollmentToken); + + expect(tokensDB.length).to.eql(0); + }); + + it('should reject an invalid enrollment token', async () => { + const { token } = await tokensLib.getEnrollmentToken(chance.word()); + + expect(token).to.eql(null); + }); + + it('should reject an expired enrollment token', async () => { + const { token } = await tokensLib.getEnrollmentToken( + signToken({}, settings.encryptionKey, { + expiresIn: '-1min', + }) + ); + + expect(token).to.eql(null); + }); + + it('should delete the given enrollment token so it may not be reused', async () => { + expect(tokensDB[0].token).to.eql(validEnrollmentToken); + await tokensLib.deleteEnrollmentToken(validEnrollmentToken); + expect(tokensDB.length).to.eql(0); + + const { token } = await tokensLib.getEnrollmentToken( + validEnrollmentToken + ); + + expect(token).to.eql(null); + }); + }); +}); diff --git a/x-pack/plugins/beats/server/lib/domains/__tests__/beats/verify.test.ts b/x-pack/plugins/beats/server/lib/domains/__tests__/beats/verify.test.ts new file mode 100644 index 0000000000000..7310b101a351a --- /dev/null +++ b/x-pack/plugins/beats/server/lib/domains/__tests__/beats/verify.test.ts @@ -0,0 +1,192 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from 'expect.js'; +import { wrapRequest } from '../../../../utils/wrap_request'; +import { MemoryBeatsAdapter } from '../../../adapters/beats/memory_beats_adapter'; +import { TestingBackendFrameworkAdapter } from '../../../adapters/famework/kibana/testing_framework_adapter'; +import { MemoryTagsAdapter } from '../../../adapters/tags/memory_tags_adapter'; +import { MemoryTokensAdapter } from '../../../adapters/tokens/memory_tokens_adapter'; + +import { BeatTag, CMBeat, EnrollmentToken } from './../../../lib'; + +import { CMBeatsDomain } from '../../beats'; +import { CMTagsDomain } from '../../tags'; +import { CMTokensDomain } from '../../tokens'; + +import Chance from 'chance'; + +const seed = Date.now(); +const chance = new Chance(seed); + +const settings = { + encryptionKey: 'something_who_cares', + enrollmentTokensTtlInSeconds: 10 * 60, // 10 minutes +}; + +const fakeReq = wrapRequest({ + headers: {}, + info: {}, + params: {}, + payload: {}, + query: {}, +}); + +describe('Beats Domain Lib', () => { + let beatsLib: CMBeatsDomain; + let tokensLib: CMTokensDomain; + + let beatsDB: CMBeat[] = []; + let tagsDB: BeatTag[] = []; + let tokensDB: EnrollmentToken[] = []; + + describe('verify_beat', () => { + beforeEach(async () => { + beatsDB = [ + { + access_token: '9a6c99ae0fd84b068819701169cd8a4b', + host_ip: '1.2.3.4', + host_name: 'foo.bar.com', + id: 'qux', + type: 'filebeat', + }, + { + access_token: '188255eb560a4448b72656c5e99cae6f', + host_ip: '22.33.11.44', + host_name: 'baz.bar.com', + id: 'baz', + type: 'metricbeat', + }, + { + access_token: '93c4a4dd08564c189a7ec4e4f046b975', + host_ip: '1.2.3.4', + host_name: 'foo.bar.com', + id: 'foo', + tags: ['production', 'qa'], + type: 'metricbeat', + verified_on: '2018-05-15T16:25:38.924Z', + }, + { + access_token: '3c4a4dd08564c189a7ec4e4f046b9759', + host_ip: '11.22.33.44', + host_name: 'foo.com', + id: 'bar', + type: 'filebeat', + }, + ]; + tagsDB = [ + { + configuration_blocks: [], + id: 'production', + }, + { + configuration_blocks: [], + id: 'development', + }, + { + configuration_blocks: [], + id: 'qa', + }, + ]; + tokensDB = []; + + const framework = new TestingBackendFrameworkAdapter(null, settings); + + tokensLib = new CMTokensDomain(new MemoryTokensAdapter(tokensDB), { + framework, + }); + + const tagsLib = new CMTagsDomain(new MemoryTagsAdapter(tagsDB)); + + beatsLib = new CMBeatsDomain(new MemoryBeatsAdapter(beatsDB), { + tags: tagsLib, + tokens: tokensLib, + }); + }); + + it('should return errors for non-existent beats', async () => { + const nonExistentBeatId = chance.word(); + + interface Beats { + id: string; + status?: number; + result?: string; + } + + const beats: Beats[] = [{ id: 'bar' }, { id: nonExistentBeatId }]; + const beatIds = beats.map(b => b.id); + + const { + verifiedBeatIds, + alreadyVerifiedBeatIds, + nonExistentBeatIds, + } = await beatsLib.verifyBeats(fakeReq, beatIds); + + // TODO calculation of status should be done in-lib, w/switch statement here + beats.forEach(b => { + if (nonExistentBeatIds.includes(b.id)) { + b.status = 404; + b.result = 'not found'; + } else if (alreadyVerifiedBeatIds.includes(b.id)) { + b.status = 200; + b.result = 'already verified'; + } else if (verifiedBeatIds.includes(b.id)) { + b.status = 200; + b.result = 'verified'; + } else { + b.status = 400; + b.result = 'not verified'; + } + }); + + const response = { beats }; + expect(response.beats).to.eql([ + { id: 'bar', status: 200, result: 'verified' }, + { id: nonExistentBeatId, status: 404, result: 'not found' }, + ]); + }); + + it('should not re-verify already-verified beats', async () => { + interface Beats { + id: string; + status?: number; + result?: string; + } + + const beats: Beats[] = [{ id: 'foo' }, { id: 'bar' }]; + const beatIds = beats.map(b => b.id); + + const { + verifiedBeatIds, + alreadyVerifiedBeatIds, + nonExistentBeatIds, + } = await beatsLib.verifyBeats(fakeReq, beatIds); + + // TODO calculation of status should be done in-lib, w/switch statement here + beats.forEach(beat => { + if (nonExistentBeatIds.includes(beat.id)) { + beat.status = 404; + beat.result = 'not found'; + } else if (alreadyVerifiedBeatIds.includes(beat.id)) { + beat.status = 200; + beat.result = 'already verified'; + } else if (verifiedBeatIds.includes(beat.id)) { + beat.status = 200; + beat.result = 'verified'; + } else { + beat.status = 400; + beat.result = 'not verified'; + } + }); + + const response = { beats }; + expect(response.beats).to.eql([ + { id: 'foo', status: 200, result: 'already verified' }, + { id: 'bar', status: 200, result: 'verified' }, + ]); + }); + }); +}); diff --git a/x-pack/plugins/beats/server/lib/domains/__tests__/tokens.test.ts b/x-pack/plugins/beats/server/lib/domains/__tests__/tokens.test.ts new file mode 100644 index 0000000000000..174c7d628778c --- /dev/null +++ b/x-pack/plugins/beats/server/lib/domains/__tests__/tokens.test.ts @@ -0,0 +1,87 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from 'expect.js'; +import { wrapRequest } from '../../../utils/wrap_request'; +import { TestingBackendFrameworkAdapter } from '../../adapters/famework/kibana/testing_framework_adapter'; +import { MemoryTokensAdapter } from '../../adapters/tokens/memory_tokens_adapter'; +import { EnrollmentToken } from '../../lib'; +import { CMTokensDomain } from '../tokens'; + +import Chance from 'chance'; +import moment from 'moment'; + +const seed = Date.now(); +const chance = new Chance(seed); + +const fakeReq = wrapRequest({ + headers: {}, + info: {}, + params: {}, + payload: {}, + query: {}, +}); + +const settings = { + encryptionKey: 'something_who_cares', + enrollmentTokensTtlInSeconds: 10 * 60, // 10 minutes +}; + +describe('Token Domain Lib', () => { + let tokensLib: CMTokensDomain; + let tokensDB: EnrollmentToken[] = []; + + beforeEach(async () => { + tokensDB = []; + const framework = new TestingBackendFrameworkAdapter(null, settings); + + tokensLib = new CMTokensDomain(new MemoryTokensAdapter(tokensDB), { + framework, + }); + }); + + it('should generate webtokens with a qty of 1', async () => { + const tokens = await tokensLib.createEnrollmentTokens(fakeReq, 1); + + expect(tokens.length).to.be(1); + + expect(typeof tokens[0]).to.be('string'); + }); + + it('should create the specified number of tokens', async () => { + const numTokens = chance.integer({ min: 1, max: 20 }); + const tokensFromApi = await tokensLib.createEnrollmentTokens( + fakeReq, + numTokens + ); + + expect(tokensFromApi.length).to.eql(numTokens); + expect(tokensFromApi).to.eql(tokensDB.map((t: EnrollmentToken) => t.token)); + }); + + it('should set token expiration to 10 minutes from now by default', async () => { + await tokensLib.createEnrollmentTokens(fakeReq, 1); + + const token = tokensDB[0]; + + // We do a fuzzy check to see if the token expires between 9 and 10 minutes + // from now because a bit of time has elapsed been the creation of the + // tokens and this check. + const tokenExpiresOn = moment(token.expires_on).valueOf(); + + // Because sometimes the test runs so fast it it equal, and we dont use expect.js version that has toBeLessThanOrEqualTo + const tenMinutesFromNow = moment() + .add('10', 'minutes') + .add('1', 'seconds') + .valueOf(); + + const almostTenMinutesFromNow = moment(tenMinutesFromNow) + .subtract('2', 'seconds') + .valueOf(); + expect(tokenExpiresOn).to.be.lessThan(tenMinutesFromNow); + expect(tokenExpiresOn).to.be.greaterThan(almostTenMinutesFromNow); + }); +}); diff --git a/x-pack/plugins/beats/server/lib/domains/beats.ts b/x-pack/plugins/beats/server/lib/domains/beats.ts index c0d9ec704e2b1..0d5e068ff4ff7 100644 --- a/x-pack/plugins/beats/server/lib/domains/beats.ts +++ b/x-pack/plugins/beats/server/lib/domains/beats.ts @@ -11,7 +11,6 @@ */ import { uniq } from 'lodash'; -import uuid from 'uuid'; import { findNonExistentItems } from '../../utils/find_non_existent_items'; import { @@ -45,15 +44,16 @@ export class CMBeatsDomain { ) { const beat = await this.adapter.get(beatId); + const { verified: isAccessTokenValid } = this.tokens.verifyToken( + beat ? beat.access_token : '', + accessToken + ); + // TODO make return type enum if (beat === null) { return 'beat-not-found'; } - const isAccessTokenValid = this.tokens.areTokensEqual( - beat.access_token, - accessToken - ); if (!isAccessTokenValid) { return 'invalid-access-token'; } @@ -74,8 +74,7 @@ export class CMBeatsDomain { remoteAddress: string, beat: Partial ) { - // TODO move this to the token lib - const accessToken = uuid.v4().replace(/-/g, ''); + const accessToken = this.tokens.generateAccessToken(); await this.adapter.insert({ ...beat, access_token: accessToken, @@ -136,37 +135,28 @@ export class CMBeatsDomain { // TODO cleanup return value, should return a status enum public async verifyBeats(req: FrameworkRequest, beatIds: string[]) { - const beatsFromEs = await this.adapter.getVerifiedWithIds(req, beatIds); - - const nonExistentBeatIds = beatsFromEs.reduce( - (nonExistentIds: any, beatFromEs: any, idx: any) => { - if (!beatFromEs.found) { - nonExistentIds.push(beatIds[idx]); - } - return nonExistentIds; - }, - [] - ); + const beatsFromEs = await this.adapter.getWithIds(req, beatIds); + + const nonExistentBeatIds = findNonExistentItems(beatsFromEs, beatIds); const alreadyVerifiedBeatIds = beatsFromEs - .filter((beat: any) => beat.found) - .filter((beat: any) => beat._source.beat.hasOwnProperty('verified_on')) - .map((beat: any) => beat._source.beat.id); + .filter((beat: any) => beat.hasOwnProperty('verified_on')) + .map((beat: any) => beat.id); const toBeVerifiedBeatIds = beatsFromEs - .filter((beat: any) => beat.found) - .filter((beat: any) => !beat._source.beat.hasOwnProperty('verified_on')) - .map((beat: any) => beat._source.beat.id); + .filter((beat: any) => !beat.hasOwnProperty('verified_on')) + .map((beat: any) => beat.id); const verifications = await this.adapter.verifyBeats( req, toBeVerifiedBeatIds ); + return { alreadyVerifiedBeatIds, nonExistentBeatIds, toBeVerifiedBeatIds, - verifications, + verifiedBeatIds: verifications.map((v: any) => v.id), }; } @@ -182,7 +172,6 @@ export class CMBeatsDomain { }; const beats = await this.adapter.getWithIds(req, beatIds); const tags = await this.tags.getTagsWithIds(req, tagIds); - // Handle assignments containing non-existing beat IDs or tags const nonExistentBeatIds = findNonExistentItems(beats, beatIds); const nonExistentTags = findNonExistentItems(tags, tagIds); diff --git a/x-pack/plugins/beats/server/lib/domains/tokens.ts b/x-pack/plugins/beats/server/lib/domains/tokens.ts index 6e55d78ecdcc8..b2a9d283e484a 100644 --- a/x-pack/plugins/beats/server/lib/domains/tokens.ts +++ b/x-pack/plugins/beats/server/lib/domains/tokens.ts @@ -5,6 +5,7 @@ */ import { timingSafeEqual } from 'crypto'; +import { sign as signToken, verify as verifyToken } from 'jsonwebtoken'; import moment from 'moment'; import uuid from 'uuid'; import { CMTokensAdapter, FrameworkRequest } from '../lib'; @@ -26,32 +27,96 @@ export class CMTokensDomain { } public async getEnrollmentToken(enrollmentToken: string) { - return await this.adapter.getEnrollmentToken(enrollmentToken); + const fullToken = await this.adapter.getEnrollmentToken(enrollmentToken); + + if (!fullToken) { + return { + token: null, + expired: true, + expires_on: null, + }; + } + + const { verified, expired } = this.verifyToken( + enrollmentToken, + fullToken.token || '', + false + ); + + if (!verified) { + return { + expired, + token: null, + expires_on: null, + }; + } + + return { ...fullToken, expired }; } public async deleteEnrollmentToken(enrollmentToken: string) { return await this.adapter.deleteEnrollmentToken(enrollmentToken); } - public areTokensEqual(token1: string, token2: string) { + public verifyToken(recivedToken: string, token2: string, decode = true) { + let tokenDecoded = true; + let expired = false; + + if (decode) { + const enrollmentTokenSecret = this.framework.getSetting( + 'xpack.beats.encryptionKey' + ); + + try { + verifyToken(recivedToken, enrollmentTokenSecret); + tokenDecoded = true; + } catch (err) { + if (err.name === 'TokenExpiredError') { + expired = true; + } + tokenDecoded = false; + } + } + if ( - typeof token1 !== 'string' || + typeof recivedToken !== 'string' || typeof token2 !== 'string' || - token1.length !== token2.length + recivedToken.length !== token2.length ) { // This prevents a more subtle timing attack where we know already the tokens aren't going to // match but still we don't return fast. Instead we compare two pre-generated random tokens using // the same comparison algorithm that we would use to compare two equal-length tokens. - return timingSafeEqual( - Buffer.from(RANDOM_TOKEN_1, 'utf8'), - Buffer.from(RANDOM_TOKEN_2, 'utf8') - ); + return { + expired, + verified: + timingSafeEqual( + Buffer.from(RANDOM_TOKEN_1, 'utf8'), + Buffer.from(RANDOM_TOKEN_2, 'utf8') + ) && tokenDecoded, + }; } - return timingSafeEqual( - Buffer.from(token1, 'utf8'), - Buffer.from(token2, 'utf8') + return { + expired, + verified: + timingSafeEqual( + Buffer.from(recivedToken, 'utf8'), + Buffer.from(token2, 'utf8') + ) && tokenDecoded, + }; + } + + public generateAccessToken() { + const enrollmentTokenSecret = this.framework.getSetting( + 'xpack.beats.encryptionKey' ); + + const tokenData = { + created: moment().toJSON(), + randomHash: this.createRandomHash(), + }; + + return signToken(tokenData, enrollmentTokenSecret); } public async createEnrollmentTokens( @@ -62,6 +127,7 @@ export class CMTokensDomain { const enrollmentTokensTtlInSeconds = this.framework.getSetting( 'xpack.beats.enrollmentTokensTtlInSeconds' ); + const enrollmentTokenExpiration = moment() .add(enrollmentTokensTtlInSeconds, 'seconds') .toJSON(); @@ -69,7 +135,7 @@ export class CMTokensDomain { while (tokens.length < numTokens) { tokens.push({ expires_on: enrollmentTokenExpiration, - token: uuid.v4().replace(/-/g, ''), + token: this.createRandomHash(), }); } @@ -77,4 +143,8 @@ export class CMTokensDomain { return tokens.map(token => token.token); } + + private createRandomHash() { + return uuid.v4().replace(/-/g, ''); + } } diff --git a/x-pack/plugins/beats/server/lib/lib.ts b/x-pack/plugins/beats/server/lib/lib.ts index 37d0a989e4cf5..6aab0acd733d8 100644 --- a/x-pack/plugins/beats/server/lib/lib.ts +++ b/x-pack/plugins/beats/server/lib/lib.ts @@ -43,16 +43,16 @@ export interface ConfigurationBlock { export interface CMBeat { id: string; access_token: string; - verified_on: string; + verified_on?: string; type: string; - version: string; + version?: string; host_ip: string; host_name: string; - ephemeral_id: string; - local_configuration_yml: string; - tags: string; - central_configuration_yml: string; - metadata: {}; + ephemeral_id?: string; + local_configuration_yml?: string; + tags?: string[]; + central_configuration_yml?: string; + metadata?: {}; } export interface BeatTag { @@ -68,7 +68,10 @@ export interface EnrollmentToken { export interface CMTokensAdapter { deleteEnrollmentToken(enrollmentToken: string): Promise; getEnrollmentToken(enrollmentToken: string): Promise; - upsertTokens(req: FrameworkRequest, tokens: EnrollmentToken[]): Promise; + upsertTokens( + req: FrameworkRequest, + tokens: EnrollmentToken[] + ): Promise; } // FIXME: fix getTagsWithIds return type @@ -84,7 +87,6 @@ export interface CMBeatsAdapter { get(id: string): any; getAll(req: FrameworkRequest): any; getWithIds(req: FrameworkRequest, beatIds: string[]): any; - getVerifiedWithIds(req: FrameworkRequest, beatIds: string[]): any; verifyBeats(req: FrameworkRequest, beatIds: string[]): any; removeTagsFromBeats( req: FrameworkRequest, @@ -108,7 +110,7 @@ export interface CMTagAssignment { export interface BackendFrameworkAdapter { version: string; - getSetting(settingPath: string): string | number; + getSetting(settingPath: string): any; exposeStaticDir(urlPath: string, dir: string): void; installIndexTemplate(name: string, template: {}): void; registerRoute( diff --git a/x-pack/plugins/beats/server/rest_api/beats/enroll.ts b/x-pack/plugins/beats/server/rest_api/beats/enroll.ts index fe154592564ae..c86e5272e1e23 100644 --- a/x-pack/plugins/beats/server/rest_api/beats/enroll.ts +++ b/x-pack/plugins/beats/server/rest_api/beats/enroll.ts @@ -33,17 +33,16 @@ export const createBeatEnrollmentRoute = (libs: CMServerLibs) => ({ const enrollmentToken = request.headers['kbn-beats-enrollment-token']; try { - const { - token, - expires_on: expiresOn, - } = await libs.tokens.getEnrollmentToken(enrollmentToken); + const { token, expires_on } = await libs.tokens.getEnrollmentToken( + enrollmentToken + ); + if (expires_on && moment(expires_on).isBefore(moment())) { + return reply({ message: 'Expired enrollment token' }).code(400); + } if (!token) { return reply({ message: 'Invalid enrollment token' }).code(400); } - if (moment(expiresOn).isBefore(moment())) { - return reply({ message: 'Expired enrollment token' }).code(400); - } const { accessToken } = await libs.beats.enrollBeat( beatId, request.info.remoteAddress, diff --git a/x-pack/plugins/beats/server/rest_api/beats/update.ts b/x-pack/plugins/beats/server/rest_api/beats/update.ts index 41d403399d45f..3683c02ca2ccb 100644 --- a/x-pack/plugins/beats/server/rest_api/beats/update.ts +++ b/x-pack/plugins/beats/server/rest_api/beats/update.ts @@ -15,7 +15,7 @@ export const createBeatUpdateRoute = (libs: CMServerLibs) => ({ auth: false, validate: { headers: Joi.object({ - 'kbn-beats-access-token': Joi.string().required(), + 'kbn-beats-access-token': Joi.string(), }).options({ allowUnknown: true, }), diff --git a/x-pack/plugins/beats/server/rest_api/beats/verify.ts b/x-pack/plugins/beats/server/rest_api/beats/verify.ts index 866fa77d0c337..7dba7f4e20692 100644 --- a/x-pack/plugins/beats/server/rest_api/beats/verify.ts +++ b/x-pack/plugins/beats/server/rest_api/beats/verify.ts @@ -29,22 +29,11 @@ export const createBeatVerificationRoute = (libs: CMServerLibs) => ({ try { const { - verifications, + verifiedBeatIds, alreadyVerifiedBeatIds, - toBeVerifiedBeatIds, nonExistentBeatIds, } = await libs.beats.verifyBeats(request, beatIds); - const verifiedBeatIds = verifications.reduce( - (verifiedBeatList: any, verification: any, idx: any) => { - if (verification.update.status === 200) { - verifiedBeatList.push(toBeVerifiedBeatIds[idx]); - } - return verifiedBeatList; - }, - [] - ); - // TODO calculation of status should be done in-lib, w/switch statement here beats.forEach(beat => { if (nonExistentBeatIds.includes(beat.id)) { diff --git a/x-pack/plugins/beats/server/utils/error_wrappers/wrap_es_error.test.js b/x-pack/plugins/beats/server/utils/error_wrappers/wrap_es_error.test.js index 03b04a2ef61d2..de79815258f7a 100644 --- a/x-pack/plugins/beats/server/utils/error_wrappers/wrap_es_error.test.js +++ b/x-pack/plugins/beats/server/utils/error_wrappers/wrap_es_error.test.js @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import expect from 'expect.js'; import { wrapEsError } from './wrap_es_error'; describe('wrap_es_error', () => { @@ -34,7 +35,7 @@ describe('wrap_es_error', () => { expect(wrappedError.isBoom).to.be(true); expect(wrappedError.message).to.be( - 'Insufficient user permissions for managing Logstash pipelines' + 'Insufficient user permissions for managing Beats configuration' ); }); }); diff --git a/x-pack/plugins/beats/server/utils/find_non_existent_items.ts b/x-pack/plugins/beats/server/utils/find_non_existent_items.ts index 53e4066acc879..d6b2a0c9e143b 100644 --- a/x-pack/plugins/beats/server/utils/find_non_existent_items.ts +++ b/x-pack/plugins/beats/server/utils/find_non_existent_items.ts @@ -4,11 +4,23 @@ * you may not use this file except in compliance with the Elastic License. */ -export function findNonExistentItems(items: any, requestedItems: any) { - return items.reduce((nonExistentItems: any, item: any, idx: any) => { - if (!item.found) { - nonExistentItems.push(requestedItems[idx]); - } - return nonExistentItems; - }, []); +interface RandomItem { + id: string; + [key: string]: any; +} + +export function findNonExistentItems(items: RandomItem[], requestedItems: any) { + return requestedItems.reduce( + (nonExistentItems: string[], requestedItem: string, idx: number) => { + if ( + items.findIndex( + (item: RandomItem) => item && item.id === requestedItem + ) === -1 + ) { + nonExistentItems.push(requestedItems[idx]); + } + return nonExistentItems; + }, + [] + ); } diff --git a/x-pack/plugins/beats/wallaby.js b/x-pack/plugins/beats/wallaby.js index c20488d35cfb6..8c0c4aa355925 100644 --- a/x-pack/plugins/beats/wallaby.js +++ b/x-pack/plugins/beats/wallaby.js @@ -3,15 +3,18 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +const path = require('path'); +process.env.NODE_PATH = path.join(__dirname, '..', '..', 'node_modules'); module.exports = function (wallaby) { return { debug: true, files: [ - '../../tsconfig.json', + './tsconfig.json', //'plugins/beats/public/**/*.+(js|jsx|ts|tsx|json|snap|css|less|sass|scss|jpg|jpeg|gif|png|svg)', 'server/**/*.+(js|jsx|ts|tsx|json|snap|css|less|sass|scss|jpg|jpeg|gif|png|svg)', 'common/**/*.+(js|jsx|ts|tsx|json|snap|css|less|sass|scss|jpg|jpeg|gif|png|svg)', + '!**/*.test.ts', ], tests: ['**/*.test.ts'], @@ -22,6 +25,40 @@ module.exports = function (wallaby) { testFramework: 'jest', compilers: { '**/*.ts?(x)': wallaby.compilers.typeScript({ module: 'commonjs' }), + '**/*.js': wallaby.compilers.babel({ + babelrc: false, + presets: [require.resolve('@kbn/babel-preset/node_preset')], + }), + }, + setup: wallaby => { + const path = require('path'); + + const kibanaDirectory = path.resolve( + wallaby.localProjectDir, + '..', + '..', + '..' + ); + wallaby.testFramework.configure({ + rootDir: wallaby.localProjectDir, + moduleNameMapper: { + '^ui/(.*)': `${kibanaDirectory}/src/ui/public/$1`, + // eslint-disable-next-line + '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': `${kibanaDirectory}/src/dev/jest/mocks/file_mock.js`, + '\\.(css|less|scss)$': `${kibanaDirectory}/src/dev/jest/mocks/style_mock.js`, + }, + + setupFiles: [ + `${kibanaDirectory}/x-pack/dev-tools/jest/setup/enzyme.js`, + ], + snapshotSerializers: [ + `${kibanaDirectory}/node_modules/enzyme-to-json/serializer`, + ], + transform: { + '^.+\\.js$': `${kibanaDirectory}/src/dev/jest/babel_transform.js`, + //"^.+\\.tsx?$": `${kibanaDirectory}/src/dev/jest/ts_transform.js`, + }, + }); }, }; }; diff --git a/x-pack/test/api_integration/apis/beats/enroll_beat.js b/x-pack/test/api_integration/apis/beats/enroll_beat.js index 91317bca976ee..1dde64c9ee1d8 100644 --- a/x-pack/test/api_integration/apis/beats/enroll_beat.js +++ b/x-pack/test/api_integration/apis/beats/enroll_beat.js @@ -6,10 +6,8 @@ import expect from 'expect.js'; import moment from 'moment'; -import { - ES_INDEX_NAME, - ES_TYPE_NAME -} from './constants'; + +import { ES_INDEX_NAME, ES_TYPE_NAME } from './constants'; export default function ({ getService }) { const supertest = getService('supertest'); @@ -23,17 +21,19 @@ export default function ({ getService }) { beforeEach(async () => { validEnrollmentToken = chance.word(); + beatId = chance.word(); - const version = chance.integer({ min: 1, max: 10 }) - + '.' - + chance.integer({ min: 1, max: 10 }) - + '.' - + chance.integer({ min: 1, max: 10 }); + const version = + chance.integer({ min: 1, max: 10 }) + + '.' + + chance.integer({ min: 1, max: 10 }) + + '.' + + chance.integer({ min: 1, max: 10 }); beat = { type: 'filebeat', host_name: 'foo.bar.com', - version + version, }; await es.index({ @@ -44,17 +44,17 @@ export default function ({ getService }) { type: 'enrollment_token', enrollment_token: { token: validEnrollmentToken, - expires_on: moment().add(4, 'hours').toJSON() - } - } + expires_on: moment() + .add(4, 'hours') + .toJSON(), + }, + }, }); }); it('should enroll beat in an unverified state', async () => { await supertest - .post( - `/api/beats/agent/${beatId}` - ) + .post(`/api/beats/agent/${beatId}`) .set('kbn-xsrf', 'xxx') .set('kbn-beats-enrollment-token', validEnrollmentToken) .send(beat) @@ -63,7 +63,7 @@ export default function ({ getService }) { const esResponse = await es.get({ index: ES_INDEX_NAME, type: ES_TYPE_NAME, - id: `beat:${beatId}` + id: `beat:${beatId}`, }); expect(esResponse._source.beat).to.not.have.property('verified_on'); @@ -72,9 +72,7 @@ export default function ({ getService }) { it('should contain an access token in the response', async () => { const { body: apiResponse } = await supertest - .post( - `/api/beats/agent/${beatId}` - ) + .post(`/api/beats/agent/${beatId}`) .set('kbn-xsrf', 'xxx') .set('kbn-beats-enrollment-token', validEnrollmentToken) .send(beat) @@ -85,7 +83,7 @@ export default function ({ getService }) { const esResponse = await es.get({ index: ES_INDEX_NAME, type: ES_TYPE_NAME, - id: `beat:${beatId}` + id: `beat:${beatId}`, }); const accessTokenInEs = esResponse._source.beat.access_token; @@ -96,9 +94,7 @@ export default function ({ getService }) { it('should reject an invalid enrollment token', async () => { const { body: apiResponse } = await supertest - .post( - `/api/beats/agent/${beatId}` - ) + .post(`/api/beats/agent/${beatId}`) .set('kbn-xsrf', 'xxx') .set('kbn-beats-enrollment-token', chance.word()) .send(beat) @@ -108,7 +104,10 @@ export default function ({ getService }) { }); it('should reject an expired enrollment token', async () => { - const expiredEnrollmentToken = chance.word(); + const expiredEnrollmentToken = + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.' + + 'eyJjcmVhdGVkIjoiMjAxOC0wNi0zMFQwMzo0MjoxNS4yMzBaIiwiaWF0IjoxNTMwMzMwMTM1LCJleHAiOjE1MzAzMzAxMzV9.' + + 'Azf4czAwWZEflR7Pf8pi-DUTcve9xyxWyViNYeUSGog'; await es.index({ index: ES_INDEX_NAME, @@ -118,15 +117,15 @@ export default function ({ getService }) { type: 'enrollment_token', enrollment_token: { token: expiredEnrollmentToken, - expires_on: moment().subtract(1, 'minute').toJSON() - } - } + expires_on: moment() + .subtract(1, 'minute') + .toJSON(), + }, + }, }); const { body: apiResponse } = await supertest - .post( - `/api/beats/agent/${beatId}` - ) + .post(`/api/beats/agent/${beatId}`) .set('kbn-xsrf', 'xxx') .set('kbn-beats-enrollment-token', expiredEnrollmentToken) .send(beat) @@ -137,9 +136,7 @@ export default function ({ getService }) { it('should delete the given enrollment token so it may not be reused', async () => { await supertest - .post( - `/api/beats/agent/${beatId}` - ) + .post(`/api/beats/agent/${beatId}`) .set('kbn-xsrf', 'xxx') .set('kbn-beats-enrollment-token', validEnrollmentToken) .send(beat) @@ -149,7 +146,7 @@ export default function ({ getService }) { index: ES_INDEX_NAME, type: ES_TYPE_NAME, id: `enrollment_token:${validEnrollmentToken}`, - ignore: [ 404 ] + ignore: [404], }); expect(esResponse.found).to.be(false); @@ -157,9 +154,7 @@ export default function ({ getService }) { it('should fail if the beat with the same ID is enrolled twice', async () => { await supertest - .post( - `/api/beats/agent/${beatId}` - ) + .post(`/api/beats/agent/${beatId}`) .set('kbn-xsrf', 'xxx') .set('kbn-beats-enrollment-token', validEnrollmentToken) .send(beat) @@ -173,15 +168,15 @@ export default function ({ getService }) { type: 'enrollment_token', enrollment_token: { token: validEnrollmentToken, - expires_on: moment().add(4, 'hours').toJSON() - } - } + expires_on: moment() + .add(4, 'hours') + .toJSON(), + }, + }, }); await supertest - .post( - `/api/beats/agent/${beatId}` - ) + .post(`/api/beats/agent/${beatId}`) .set('kbn-xsrf', 'xxx') .set('kbn-beats-enrollment-token', validEnrollmentToken) .send(beat) diff --git a/x-pack/test/api_integration/apis/beats/update_beat.js b/x-pack/test/api_integration/apis/beats/update_beat.js index 92e5771e0ef4b..fb30970d3cc7f 100644 --- a/x-pack/test/api_integration/apis/beats/update_beat.js +++ b/x-pack/test/api_integration/apis/beats/update_beat.js @@ -5,10 +5,8 @@ */ import expect from 'expect.js'; -import { - ES_INDEX_NAME, - ES_TYPE_NAME -} from './constants'; +import { ES_INDEX_NAME, ES_TYPE_NAME } from './constants'; +import moment from 'moment'; export default function ({ getService }) { const supertest = getService('supertest'); @@ -17,23 +15,44 @@ export default function ({ getService }) { const esArchiver = getService('esArchiver'); describe('update_beat', () => { + let validEnrollmentToken; let beat; const archive = 'beats/list'; beforeEach('load beats archive', () => esArchiver.load(archive)); - beforeEach(() => { - const version = chance.integer({ min: 1, max: 10 }) - + '.' - + chance.integer({ min: 1, max: 10 }) - + '.' - + chance.integer({ min: 1, max: 10 }); + beforeEach(async () => { + validEnrollmentToken = + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.' + + 'eyJjcmVhdGVkIjoiMjAxOC0wNi0zMFQwMzo0MjoxNS4yMzBaIiwiaWF0IjoxNTMwMzMwMTM1fQ.' + + 'SSsX2Byyo1B1bGxV8C3G4QldhE5iH87EY_1r21-bwbI'; + const version = + chance.integer({ min: 1, max: 10 }) + + '.' + + chance.integer({ min: 1, max: 10 }) + + '.' + + chance.integer({ min: 1, max: 10 }); beat = { type: `${chance.word()}beat`, host_name: `www.${chance.word()}.net`, version, - ephemeral_id: chance.word() + ephemeral_id: chance.word(), }; + + await es.index({ + index: ES_INDEX_NAME, + type: ES_TYPE_NAME, + id: `enrollment_token:${validEnrollmentToken}`, + body: { + type: 'enrollment_token', + enrollment_token: { + token: validEnrollmentToken, + expires_on: moment() + .add(4, 'hours') + .toJSON(), + }, + }, + }); }); afterEach('unload beats archive', () => esArchiver.unload(archive)); @@ -41,18 +60,16 @@ export default function ({ getService }) { it('should update an existing verified beat', async () => { const beatId = 'foo'; await supertest - .put( - `/api/beats/agent/${beatId}` - ) + .put(`/api/beats/agent/${beatId}`) .set('kbn-xsrf', 'xxx') - .set('kbn-beats-access-token', '93c4a4dd08564c189a7ec4e4f046b975') + .set('kbn-beats-access-token', validEnrollmentToken) .send(beat) .expect(204); const beatInEs = await es.get({ index: ES_INDEX_NAME, type: ES_TYPE_NAME, - id: `beat:${beatId}` + id: `beat:${beatId}`, }); expect(beatInEs._source.beat.id).to.be(beatId); @@ -65,9 +82,7 @@ export default function ({ getService }) { it('should return an error for an invalid access token', async () => { const beatId = 'foo'; const { body } = await supertest - .put( - `/api/beats/agent/${beatId}` - ) + .put(`/api/beats/agent/${beatId}`) .set('kbn-xsrf', 'xxx') .set('kbn-beats-access-token', chance.word()) .send(beat) @@ -78,7 +93,7 @@ export default function ({ getService }) { const beatInEs = await es.get({ index: ES_INDEX_NAME, type: ES_TYPE_NAME, - id: `beat:${beatId}` + id: `beat:${beatId}`, }); expect(beatInEs._source.beat.id).to.be(beatId); @@ -90,12 +105,16 @@ export default function ({ getService }) { it('should return an error for an existing but unverified beat', async () => { const beatId = 'bar'; + const { body } = await supertest - .put( - `/api/beats/agent/${beatId}` - ) + .put(`/api/beats/agent/${beatId}`) .set('kbn-xsrf', 'xxx') - .set('kbn-beats-access-token', '3c4a4dd08564c189a7ec4e4f046b9759') + .set( + 'kbn-beats-access-token', + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.' + + 'eyJjcmVhdGVkIjoiMjAxOC0wNi0zMFQwMzo0MjoxNS4yMzBaIiwiaWF0IjoxNTMwMzMwMTM1fQ.' + + 'SSsX2Byyo1B1bGxV8C3G4QldhE5iH87EY_1r21-bwbI' + ) .send(beat) .expect(400); @@ -104,7 +123,7 @@ export default function ({ getService }) { const beatInEs = await es.get({ index: ES_INDEX_NAME, type: ES_TYPE_NAME, - id: `beat:${beatId}` + id: `beat:${beatId}`, }); expect(beatInEs._source.beat.id).to.be(beatId); @@ -117,11 +136,9 @@ export default function ({ getService }) { it('should return an error for a non-existent beat', async () => { const beatId = chance.word(); const { body } = await supertest - .put( - `/api/beats/agent/${beatId}` - ) + .put(`/api/beats/agent/${beatId}`) .set('kbn-xsrf', 'xxx') - .set('kbn-beats-access-token', chance.word()) + .set('kbn-beats-access-token', validEnrollmentToken) .send(beat) .expect(404); diff --git a/x-pack/test/api_integration/apis/beats/verify_beats.js b/x-pack/test/api_integration/apis/beats/verify_beats.js index 2b085308b43d1..30521d0483c2d 100644 --- a/x-pack/test/api_integration/apis/beats/verify_beats.js +++ b/x-pack/test/api_integration/apis/beats/verify_beats.js @@ -19,15 +19,10 @@ export default function ({ getService }) { it('verify the given beats', async () => { const { body: apiResponse } = await supertest - .post( - '/api/beats/agents/verify' - ) + .post('/api/beats/agents/verify') .set('kbn-xsrf', 'xxx') .send({ - beats: [ - { id: 'bar' }, - { id: 'baz' } - ] + beats: [{ id: 'bar' }, { id: 'baz' }], }) .expect(200); @@ -39,36 +34,26 @@ export default function ({ getService }) { it('should not re-verify already-verified beats', async () => { const { body: apiResponse } = await supertest - .post( - '/api/beats/agents/verify' - ) + .post('/api/beats/agents/verify') .set('kbn-xsrf', 'xxx') .send({ - beats: [ - { id: 'foo' }, - { id: 'bar' } - ] + beats: [{ id: 'foo' }, { id: 'bar' }], }) .expect(200); expect(apiResponse.beats).to.eql([ { id: 'foo', status: 200, result: 'already verified' }, - { id: 'bar', status: 200, result: 'verified' } + { id: 'bar', status: 200, result: 'verified' }, ]); }); it('should return errors for non-existent beats', async () => { const nonExistentBeatId = chance.word(); const { body: apiResponse } = await supertest - .post( - '/api/beats/agents/verify' - ) + .post('/api/beats/agents/verify') .set('kbn-xsrf', 'xxx') .send({ - beats: [ - { id: 'bar' }, - { id: nonExistentBeatId } - ] + beats: [{ id: 'bar' }, { id: nonExistentBeatId }], }) .expect(200); diff --git a/x-pack/test/functional/es_archives/beats/list/data.json.gz b/x-pack/test/functional/es_archives/beats/list/data.json.gz index b33ecf434c104716138ed08200cf49feaa9b73c7..6af0e1b8aeb47b3ac9c5bea7dba0abc92e3fe9e4 100644 GIT binary patch literal 521 zcmV+k0`~nMiwFp){WeQOZ*BnXRb6k|Fcf{yuRwV&RveO!=53&s87Wi@ z#@bF(7IM;)v?a?%|>Z7)U=4GYBf}k6ZJ|0 zE9_?y*@!@dEc9qD2_V2Bp3#7YY15@RO?LEJ2j|d2R(TSUH0wFb4`{-(m{h%MwUW7K zHTF@(s_~}Gr*F6-H|I&}ut=sM&_N3r@3J8dUdlNKE{*}=L7nrWwi3DnF(EWboRlwV zDATm)&)ptj_pFb;l?VKRKBwcikmeIqc+rI&Vv>?G`?)4^1wBXEMe9rH?+IqmW z(!Lw6?UHNug6D&gQP^b%BerJv`<;d)Hnv4xd}A9X{SOE;}n8{Fu| zQ@td`vqW%zydnaNV(w)mWunVf9e>8^YxwiB>mxqj??KGfZhxFu9&)sfEROi2j@56N$h=!*J0l4bwm^~91m=dFGL LEy(|MkO}|*NeBrP literal 447 zcmV;w0YLsAiwFqO9SBQOZ*BnHRLgGTFbur=D-53-!Ez);@^|#qVqs7c zoi!R;sUJahgZ%qSP6E5wCOveK0DCb&a)zLW93P@MPWoS4O!7Ff&LmGEv4hPJG6x^{ zuxc#s1Ax@fz#408`h`a5yAeL?P+VFBmJOKz%io9nCEK~7HB;{yHz3cb_#92B8Lq50 z_yOx{KV8=s)i#tV$;gthzp4$?C%SV)LraXS=a|#9)1YG#jKQuediRD+C@T;~DZsR} zlIc`PN!2Q)R48L5FrA#K$LR7sM#m^R+(l#!zyF=cHTG>~ZpfT@m6wE4!mdm0C%P{6 zH14NTQz$e5lm^hVCf!w=Q}b_4A8f8V1bTRC2)#l?qm9 z7_u^DLaP;@b9dNQWrenIBQ;B@T%>$K`7;%H#`C2lDq}BmN)PMKV_)NB+d4a#zVh5B z?=wi^ACEOld%r)DU*PDL8&(NByke8~*8aX&pL&!{{NnTZ%D Date: Thu, 12 Jul 2018 17:18:45 -0400 Subject: [PATCH 19/94] [Beats Management] add more tests, update types, break out ES into it's own adapter (#20566) * inital effort to move to JWT and added jest based tests on libs * assign beats tests all passing * token tests now pass * add more tests * all tests now green * move enrollment token back to a hash * remove un-needed comment * alias lodash get to avoid confusion * isolated hash creation * Add initial efforts for backend framework adapter testing * move ES code to a DatabaseAdapter from BackendAdapter and add a TON of types for ES * re-typed * renamed types to match pattern * aditional renames * adapter tests should always just use adapterSetup(); * database now uses InternalRequest * corrected spelling of framework * fix typings * remove CRUFT * RequestOrInternal * Dont pass around request objects everywhere, just pass the user. Also, removed hapi types as they were not compatible * fix tests, add test, removed extra comment * fix auth * updated lock file --- x-pack/package.json | 2 - x-pack/plugins/beats/common/domain_types.ts | 31 ++ x-pack/plugins/beats/index.ts | 22 +- x-pack/plugins/beats/readme.md | 10 +- x-pack/plugins/beats/server/kibana.index.ts | 3 +- .../lib/adapters/beats/adapter_types.ts | 45 +++ .../beats/elasticsearch_beats_adapter.ts | 90 +++-- .../adapters/beats/memory_beats_adapter.ts | 41 ++- .../database/__tests__/kibana.test.ts | 34 ++ .../database/__tests__/test_contract.ts | 75 ++++ .../lib/adapters/database/adapter_types.ts | 327 ++++++++++++++++++ .../database/kibana_database_adapter.ts | 116 +++++++ .../framework/__tests__/kibana.test.ts | 36 ++ .../framework/__tests__/test_contract.ts | 36 ++ .../lib/adapters/framework/adapter_types.ts | 74 ++++ .../kibana_framework_adapter.ts | 59 ++-- .../testing_framework_adapter.ts | 27 +- .../server/lib/adapters/tags/adapter_types.ts | 12 + .../tags/elasticsearch_tags_adapter.ts | 25 +- .../lib/adapters/tags/memory_tags_adapter.ts | 8 +- .../lib/adapters/tokens/adapter_types.ts | 20 ++ .../tokens/elasticsearch_tokens_adapter.ts | 36 +- .../adapters/tokens/memory_tokens_adapter.ts | 16 +- .../beats/server/lib/compose/kibana.ts | 33 +- .../__tests__/beats/assign_tags.test.ts | 66 ++-- .../domains/__tests__/beats/enroll.test.ts | 28 +- .../domains/__tests__/beats/verify.test.ts | 25 +- .../lib/domains/__tests__/tokens.test.ts | 43 ++- .../plugins/beats/server/lib/domains/beats.ts | 55 ++- .../plugins/beats/server/lib/domains/tags.ts | 13 +- .../beats/server/lib/domains/tokens.ts | 10 +- x-pack/plugins/beats/server/lib/lib.ts | 198 +---------- .../plugins/beats/server/management_server.ts | 5 +- .../beats/server/rest_api/beats/enroll.ts | 3 +- .../beats/server/rest_api/beats/list.ts | 5 +- .../server/rest_api/beats/tag_assignment.ts | 5 +- .../server/rest_api/beats/tag_removal.ts | 5 +- .../beats/server/rest_api/beats/update.ts | 3 +- .../beats/server/rest_api/beats/verify.ts | 5 +- .../plugins/beats/server/rest_api/tags/set.ts | 5 +- .../beats/server/rest_api/tokens/create.ts | 6 +- ...es_error.test.js => wrap_es_error.test.ts} | 16 +- .../beats/server/utils/wrap_request.ts | 20 +- x-pack/plugins/beats/wallaby.js | 8 +- x-pack/yarn.lock | 6 - 45 files changed, 1161 insertions(+), 547 deletions(-) create mode 100644 x-pack/plugins/beats/common/domain_types.ts create mode 100644 x-pack/plugins/beats/server/lib/adapters/beats/adapter_types.ts create mode 100644 x-pack/plugins/beats/server/lib/adapters/database/__tests__/kibana.test.ts create mode 100644 x-pack/plugins/beats/server/lib/adapters/database/__tests__/test_contract.ts create mode 100644 x-pack/plugins/beats/server/lib/adapters/database/adapter_types.ts create mode 100644 x-pack/plugins/beats/server/lib/adapters/database/kibana_database_adapter.ts create mode 100644 x-pack/plugins/beats/server/lib/adapters/framework/__tests__/kibana.test.ts create mode 100644 x-pack/plugins/beats/server/lib/adapters/framework/__tests__/test_contract.ts create mode 100644 x-pack/plugins/beats/server/lib/adapters/framework/adapter_types.ts rename x-pack/plugins/beats/server/lib/adapters/{famework/kibana => framework}/kibana_framework_adapter.ts (56%) rename x-pack/plugins/beats/server/lib/adapters/{famework/kibana => framework}/testing_framework_adapter.ts (81%) create mode 100644 x-pack/plugins/beats/server/lib/adapters/tags/adapter_types.ts create mode 100644 x-pack/plugins/beats/server/lib/adapters/tokens/adapter_types.ts rename x-pack/plugins/beats/server/utils/error_wrappers/{wrap_es_error.test.js => wrap_es_error.test.ts} (73%) diff --git a/x-pack/package.json b/x-pack/package.json index 40f22526ae5b6..2f580180776fb 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -29,8 +29,6 @@ "@kbn/test": "link:../packages/kbn-test", "@types/boom": "^4.3.8", "@types/chance": "^1.0.1", - "@types/expect.js": "^0.3.29", - "@types/hapi": "15.0.1", "@types/jest": "^22.2.3", "@types/joi": "^10.4.0", "@types/lodash": "^3.10.0", diff --git a/x-pack/plugins/beats/common/domain_types.ts b/x-pack/plugins/beats/common/domain_types.ts new file mode 100644 index 0000000000000..9411aca413840 --- /dev/null +++ b/x-pack/plugins/beats/common/domain_types.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { ConfigurationBlockTypes } from './constants'; + +export interface ConfigurationBlock { + type: ConfigurationBlockTypes; + block_yml: string; +} + +export interface CMBeat { + id: string; + access_token: string; + verified_on?: string; + type: string; + version?: string; + host_ip: string; + host_name: string; + ephemeral_id?: string; + local_configuration_yml?: string; + tags?: string[]; + central_configuration_yml?: string; + metadata?: {}; +} + +export interface BeatTag { + id: string; + configuration_blocks: ConfigurationBlock[]; +} diff --git a/x-pack/plugins/beats/index.ts b/x-pack/plugins/beats/index.ts index ced89c186f73e..25be5728c93bb 100644 --- a/x-pack/plugins/beats/index.ts +++ b/x-pack/plugins/beats/index.ts @@ -10,18 +10,20 @@ import { initServerWithKibana } from './server/kibana.index'; const DEFAULT_ENROLLMENT_TOKENS_TTL_S = 10 * 60; // 10 minutes +export const config = Joi.object({ + enabled: Joi.boolean().default(true), + encryptionKey: Joi.string(), + enrollmentTokensTtlInSeconds: Joi.number() + .integer() + .min(1) + .default(DEFAULT_ENROLLMENT_TOKENS_TTL_S), +}).default(); +export const configPrefix = 'xpack.beats'; + export function beats(kibana: any) { return new kibana.Plugin({ - config: () => - Joi.object({ - enabled: Joi.boolean().default(true), - encryptionKey: Joi.string(), - enrollmentTokensTtlInSeconds: Joi.number() - .integer() - .min(1) - .default(DEFAULT_ENROLLMENT_TOKENS_TTL_S), - }).default(), - configPrefix: 'xpack.beats', + config: () => config, + configPrefix, id: PLUGIN.ID, require: ['kibana', 'elasticsearch', 'xpack_main'], init(server: any) { diff --git a/x-pack/plugins/beats/readme.md b/x-pack/plugins/beats/readme.md index fdd56a393e573..725fe587c5aa8 100644 --- a/x-pack/plugins/beats/readme.md +++ b/x-pack/plugins/beats/readme.md @@ -1,7 +1,15 @@ # Documentation for Beats CM in x-pack kibana -### Run tests +### Run tests (from x-pack dir) + +Functional tests ``` node scripts/jest.js plugins/beats --watch ``` + +Functional API tests + +``` +node scripts/functional_tests --config test/api_integration/config +``` diff --git a/x-pack/plugins/beats/server/kibana.index.ts b/x-pack/plugins/beats/server/kibana.index.ts index c9bc9b8bf02f4..dd7bc443bc603 100644 --- a/x-pack/plugins/beats/server/kibana.index.ts +++ b/x-pack/plugins/beats/server/kibana.index.ts @@ -4,11 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Server } from 'hapi'; import { compose } from './lib/compose/kibana'; import { initManagementServer } from './management_server'; -export const initServerWithKibana = (hapiServer: Server) => { +export const initServerWithKibana = (hapiServer: any) => { const libs = compose(hapiServer); initManagementServer(libs); }; diff --git a/x-pack/plugins/beats/server/lib/adapters/beats/adapter_types.ts b/x-pack/plugins/beats/server/lib/adapters/beats/adapter_types.ts new file mode 100644 index 0000000000000..8ce36456a703d --- /dev/null +++ b/x-pack/plugins/beats/server/lib/adapters/beats/adapter_types.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CMBeat } from '../../../../common/domain_types'; +import { FrameworkUser } from '../framework/adapter_types'; + +// FIXME: fix getBeatsWithIds return type +export interface CMBeatsAdapter { + insert(beat: CMBeat): Promise; + update(beat: CMBeat): Promise; + get(id: string): any; + getAll(user: FrameworkUser): any; + getWithIds(user: FrameworkUser, beatIds: string[]): any; + verifyBeats(user: FrameworkUser, beatIds: string[]): any; + removeTagsFromBeats( + user: FrameworkUser, + removals: BeatsTagAssignment[] + ): Promise; + assignTagsToBeats( + user: FrameworkUser, + assignments: BeatsTagAssignment[] + ): Promise; +} + +export interface BeatsTagAssignment { + beatId: string; + tag: string; + idxInRequest?: number; +} + +interface BeatsReturnedTagAssignment { + status: number | null; + result?: string; +} + +export interface CMAssignmentReturn { + assignments: BeatsReturnedTagAssignment[]; +} + +export interface BeatsRemovalReturn { + removals: BeatsReturnedTagAssignment[]; +} diff --git a/x-pack/plugins/beats/server/lib/adapters/beats/elasticsearch_beats_adapter.ts b/x-pack/plugins/beats/server/lib/adapters/beats/elasticsearch_beats_adapter.ts index 76fbf956dafc9..e2321ac6739a8 100644 --- a/x-pack/plugins/beats/server/lib/adapters/beats/elasticsearch_beats_adapter.ts +++ b/x-pack/plugins/beats/server/lib/adapters/beats/elasticsearch_beats_adapter.ts @@ -7,18 +7,18 @@ import { flatten, get as _get, omit } from 'lodash'; import moment from 'moment'; import { INDEX_NAMES } from '../../../../common/constants'; -import { - BackendFrameworkAdapter, - CMBeat, - CMBeatsAdapter, - CMTagAssignment, - FrameworkRequest, -} from '../../lib'; +import { CMBeat } from '../../../../common/domain_types'; +import { DatabaseAdapter } from '../database/adapter_types'; +import { BackendFrameworkAdapter } from '../framework/adapter_types'; +import { FrameworkUser } from '../framework/adapter_types'; +import { BeatsTagAssignment, CMBeatsAdapter } from './adapter_types'; export class ElasticsearchBeatsAdapter implements CMBeatsAdapter { + private database: DatabaseAdapter; private framework: BackendFrameworkAdapter; - constructor(framework: BackendFrameworkAdapter) { + constructor(database: DatabaseAdapter, framework: BackendFrameworkAdapter) { + this.database = database; this.framework = framework; } @@ -30,7 +30,10 @@ export class ElasticsearchBeatsAdapter implements CMBeatsAdapter { type: '_doc', }; - const response = await this.framework.callWithInternalUser('get', params); + const response = await this.database.get( + this.framework.internalUser, + params + ); if (!response.found) { return null; } @@ -44,14 +47,13 @@ export class ElasticsearchBeatsAdapter implements CMBeatsAdapter { type: 'beat', }; - const params = { + await this.database.create(this.framework.internalUser, { body, id: `beat:${beat.id}`, index: INDEX_NAMES.BEATS, refresh: 'wait_for', type: '_doc', - }; - await this.framework.callWithInternalUser('create', params); + }); } public async update(beat: CMBeat) { @@ -67,10 +69,10 @@ export class ElasticsearchBeatsAdapter implements CMBeatsAdapter { refresh: 'wait_for', type: '_doc', }; - return await this.framework.callWithInternalUser('index', params); + await this.database.index(this.framework.internalUser, params); } - public async getWithIds(req: FrameworkRequest, beatIds: string[]) { + public async getWithIds(user: FrameworkUser, beatIds: string[]) { const ids = beatIds.map(beatId => `beat:${beatId}`); const params = { @@ -81,14 +83,14 @@ export class ElasticsearchBeatsAdapter implements CMBeatsAdapter { index: INDEX_NAMES.BEATS, type: '_doc', }; - const response = await this.framework.callWithRequest(req, 'mget', params); + const response = await this.database.mget(user, params); - return get(response, 'docs', []) + return _get(response, 'docs', []) .filter((b: any) => b.found) .map((b: any) => b._source.beat); } - public async verifyBeats(req: FrameworkRequest, beatIds: string[]) { + public async verifyBeats(user: FrameworkUser, beatIds: string[]) { if (!Array.isArray(beatIds) || beatIds.length === 0) { return []; } @@ -101,15 +103,13 @@ export class ElasticsearchBeatsAdapter implements CMBeatsAdapter { ]) ); - const params = { + const response = await this.database.bulk(user, { _sourceInclude: ['beat.id', 'beat.verified_on'], body, index: INDEX_NAMES.BEATS, refresh: 'wait_for', type: '_doc', - }; - - const response = await this.framework.callWithRequest(req, 'bulk', params); + }); return _get(response, 'items', []).map(b => ({ ..._get(b, 'update.get._source.beat', {}), @@ -117,26 +117,22 @@ export class ElasticsearchBeatsAdapter implements CMBeatsAdapter { })); } - public async getAll(req: FrameworkRequest) { + public async getAll(user: FrameworkUser) { const params = { index: INDEX_NAMES.BEATS, q: 'type:beat', type: '_doc', }; - const response = await this.framework.callWithRequest( - req, - 'search', - params - ); + const response = await this.database.search(user, params); - const beats = get(response, 'hits.hits', []); + const beats = _get(response, 'hits.hits', []); return beats.map((beat: any) => omit(beat._source.beat, ['access_token'])); } public async removeTagsFromBeats( - req: FrameworkRequest, - removals: CMTagAssignment[] - ): Promise { + user: FrameworkUser, + removals: BeatsTagAssignment[] + ): Promise { const body = flatten( removals.map(({ beatId, tag }) => { const script = @@ -153,15 +149,13 @@ export class ElasticsearchBeatsAdapter implements CMBeatsAdapter { }) ); - const params = { + const response = await this.database.bulk(user, { body, index: INDEX_NAMES.BEATS, refresh: 'wait_for', type: '_doc', - }; - - const response = await this.framework.callWithRequest(req, 'bulk', params); - return get(response, 'items', []).map( + }); + return _get(response, 'items', []).map( (item: any, resultIdx: number) => ({ idxInRequest: removals[resultIdx].idxInRequest, result: item.update.result, @@ -171,9 +165,9 @@ export class ElasticsearchBeatsAdapter implements CMBeatsAdapter { } public async assignTagsToBeats( - req: FrameworkRequest, - assignments: CMTagAssignment[] - ): Promise { + user: FrameworkUser, + assignments: BeatsTagAssignment[] + ): Promise { const body = flatten( assignments.map(({ beatId, tag }) => { const script = @@ -193,18 +187,18 @@ export class ElasticsearchBeatsAdapter implements CMBeatsAdapter { }) ); - const params = { + const response = await this.database.bulk(user, { body, index: INDEX_NAMES.BEATS, refresh: 'wait_for', type: '_doc', - }; - - const response = await this.framework.callWithRequest(req, 'bulk', params); - return get(response, 'items', []).map((item: any, resultIdx: any) => ({ - idxInRequest: assignments[resultIdx].idxInRequest, - result: item.update.result, - status: item.update.status, - })); + }); + return _get(response, 'items', []).map( + (item: any, resultIdx: any) => ({ + idxInRequest: assignments[resultIdx].idxInRequest, + result: item.update.result, + status: item.update.status, + }) + ); } } diff --git a/x-pack/plugins/beats/server/lib/adapters/beats/memory_beats_adapter.ts b/x-pack/plugins/beats/server/lib/adapters/beats/memory_beats_adapter.ts index 9de8297c0f73e..a904b1d6831cd 100644 --- a/x-pack/plugins/beats/server/lib/adapters/beats/memory_beats_adapter.ts +++ b/x-pack/plugins/beats/server/lib/adapters/beats/memory_beats_adapter.ts @@ -7,12 +7,9 @@ import { omit } from 'lodash'; import moment from 'moment'; -import { - CMBeat, - CMBeatsAdapter, - CMTagAssignment, - FrameworkRequest, -} from '../../lib'; +import { CMBeat } from '../../../../common/domain_types'; +import { FrameworkUser } from '../framework/adapter_types'; +import { BeatsTagAssignment, CMBeatsAdapter } from './adapter_types'; export class MemoryBeatsAdapter implements CMBeatsAdapter { private beatsDB: CMBeat[]; @@ -38,11 +35,11 @@ export class MemoryBeatsAdapter implements CMBeatsAdapter { }; } - public async getWithIds(req: FrameworkRequest, beatIds: string[]) { + public async getWithIds(user: FrameworkUser, beatIds: string[]) { return this.beatsDB.filter(beat => beatIds.includes(beat.id)); } - public async verifyBeats(req: FrameworkRequest, beatIds: string[]) { + public async verifyBeats(user: FrameworkUser, beatIds: string[]) { if (!Array.isArray(beatIds) || beatIds.length === 0) { return []; } @@ -58,14 +55,14 @@ export class MemoryBeatsAdapter implements CMBeatsAdapter { return this.beatsDB.filter(beat => beatIds.includes(beat.id)); } - public async getAll(req: FrameworkRequest) { + public async getAll(user: FrameworkUser) { return this.beatsDB.map((beat: any) => omit(beat, ['access_token'])); } public async removeTagsFromBeats( - req: FrameworkRequest, - removals: CMTagAssignment[] - ): Promise { + user: FrameworkUser, + removals: BeatsTagAssignment[] + ): Promise { const beatIds = removals.map(r => r.beatId); const response = this.beatsDB @@ -88,16 +85,16 @@ export class MemoryBeatsAdapter implements CMBeatsAdapter { } public async assignTagsToBeats( - req: FrameworkRequest, - assignments: CMTagAssignment[] - ): Promise { + user: FrameworkUser, + assignments: BeatsTagAssignment[] + ): Promise { const beatIds = assignments.map(r => r.beatId); this.beatsDB.filter(beat => beatIds.includes(beat.id)).map(beat => { // get tags that need to be assigned to this beat const tags = assignments .filter(a => a.beatId === beat.id) - .map((t: CMTagAssignment) => t.tag); + .map((t: BeatsTagAssignment) => t.tag); if (tags.length > 0) { if (!beat.tags) { @@ -114,10 +111,12 @@ export class MemoryBeatsAdapter implements CMBeatsAdapter { return beat; }); - return assignments.map((item: CMTagAssignment, resultIdx: number) => ({ - idxInRequest: assignments[resultIdx].idxInRequest, - result: 'updated', - status: 200, - })); + return assignments.map( + (item: BeatsTagAssignment, resultIdx: number) => ({ + idxInRequest: assignments[resultIdx].idxInRequest, + result: 'updated', + status: 200, + }) + ); } } diff --git a/x-pack/plugins/beats/server/lib/adapters/database/__tests__/kibana.test.ts b/x-pack/plugins/beats/server/lib/adapters/database/__tests__/kibana.test.ts new file mode 100644 index 0000000000000..19bf05c3c777e --- /dev/null +++ b/x-pack/plugins/beats/server/lib/adapters/database/__tests__/kibana.test.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +// file.skip + +// @ts-ignore +import { createEsTestCluster } from '@kbn/test'; +// @ts-ignore +import * as kbnTestServer from '../../../../../../../../src/test_utils/kbn_server'; +import { DatabaseKbnESPlugin } from '../adapter_types'; +import { KibanaDatabaseAdapter } from '../kibana_database_adapter'; +import { contractTests } from './test_contract'; + +const kbnServer = kbnTestServer.createServerWithCorePlugins(); +const es = createEsTestCluster({}); + +contractTests('Kibana Database Adapter', { + before: async () => { + await es.start(); + await kbnServer.ready(); + + return await kbnServer.server.plugins.elasticsearch.waitUntilReady(); + }, + after: async () => { + await kbnServer.close(); + return await es.cleanup(); + }, + adapterSetup: () => { + return new KibanaDatabaseAdapter(kbnServer.server.plugins + .elasticsearch as DatabaseKbnESPlugin); + }, +}); diff --git a/x-pack/plugins/beats/server/lib/adapters/database/__tests__/test_contract.ts b/x-pack/plugins/beats/server/lib/adapters/database/__tests__/test_contract.ts new file mode 100644 index 0000000000000..ec30d4578e938 --- /dev/null +++ b/x-pack/plugins/beats/server/lib/adapters/database/__tests__/test_contract.ts @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { beatsIndexTemplate } from '../../../../utils/index_templates'; +import { DatabaseAdapter } from '../adapter_types'; + +interface ContractConfig { + before?(): Promise; + after?(): Promise; + adapterSetup(): DatabaseAdapter; +} + +export const contractTests = (testName: string, config: ContractConfig) => { + describe(testName, () => { + let database: DatabaseAdapter; + beforeAll(async () => { + jest.setTimeout(100000); // 1 second + + if (config.before) { + await config.before(); + } + }); + afterAll(async () => config.after && (await config.after())); + beforeEach(async () => { + database = config.adapterSetup(); + }); + + it('Should inject template into ES', async () => { + try { + await database.putTemplate( + { kind: 'internal' }, + { + name: 'beats-template', + body: beatsIndexTemplate, + } + ); + } catch (e) { + expect(e).toEqual(null); + } + }); + + it('Unauthorized users cant query', async () => { + const params = { + id: `beat:foo`, + ignore: [404], + index: '.management-beats', + type: '_doc', + }; + let ranWithoutError = false; + try { + await database.get({ kind: 'unauthenticated' }, params); + ranWithoutError = true; + } catch (e) { + expect(e).not.toEqual(null); + } + expect(ranWithoutError).toEqual(false); + }); + + it('Should query ES', async () => { + const params = { + id: `beat:foo`, + ignore: [404], + index: '.management-beats', + type: '_doc', + }; + const response = await database.get({ kind: 'internal' }, params); + + expect(response).not.toEqual(undefined); + // @ts-ignore + expect(response.found).toEqual(undefined); + }); + }); +}; diff --git a/x-pack/plugins/beats/server/lib/adapters/database/adapter_types.ts b/x-pack/plugins/beats/server/lib/adapters/database/adapter_types.ts new file mode 100644 index 0000000000000..36b5a35742bc9 --- /dev/null +++ b/x-pack/plugins/beats/server/lib/adapters/database/adapter_types.ts @@ -0,0 +1,327 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { FrameworkRequest, FrameworkUser } from '../framework/adapter_types'; +export interface DatabaseAdapter { + putTemplate( + user: FrameworkUser, + params: DatabasePutTemplateParams + ): Promise; + get( + user: FrameworkUser, + params: DatabaseGetParams + ): Promise>; + create( + user: FrameworkUser, + params: DatabaseCreateDocumentParams + ): Promise; + index( + user: FrameworkUser, + params: DatabaseIndexDocumentParams + ): Promise; + delete( + user: FrameworkUser, + params: DatabaseDeleteDocumentParams + ): Promise; + mget( + user: FrameworkUser, + params: DatabaseMGetParams + ): Promise>; + bulk( + user: FrameworkUser, + params: DatabaseBulkIndexDocumentsParams + ): Promise; + search( + user: FrameworkUser, + params: DatabaseSearchParams + ): Promise>; +} + +export interface DatabaseKbnESCluster { + callWithInternalUser(esMethod: string, options: {}): Promise; + callWithRequest( + req: FrameworkRequest, + esMethod: string, + options: {} + ): Promise; +} + +export interface DatabaseKbnESPlugin { + getCluster(clusterName: string): DatabaseKbnESCluster; +} + +export interface DatabaseSearchParams extends DatabaseGenericParams { + analyzer?: string; + analyzeWildcard?: boolean; + defaultOperator?: DefaultOperator; + df?: string; + explain?: boolean; + storedFields?: DatabaseNameList; + docvalueFields?: DatabaseNameList; + fielddataFields?: DatabaseNameList; + from?: number; + ignoreUnavailable?: boolean; + allowNoIndices?: boolean; + expandWildcards?: ExpandWildcards; + lenient?: boolean; + lowercaseExpandedTerms?: boolean; + preference?: string; + q?: string; + routing?: DatabaseNameList; + scroll?: string; + searchType?: 'query_then_fetch' | 'dfs_query_then_fetch'; + size?: number; + sort?: DatabaseNameList; + _source?: DatabaseNameList; + _sourceExclude?: DatabaseNameList; + _sourceInclude?: DatabaseNameList; + terminateAfter?: number; + stats?: DatabaseNameList; + suggestField?: string; + suggestMode?: 'missing' | 'popular' | 'always'; + suggestSize?: number; + suggestText?: string; + timeout?: string; + trackScores?: boolean; + version?: boolean; + requestCache?: boolean; + index?: DatabaseNameList; + type?: DatabaseNameList; +} + +export interface DatabaseSearchResponse { + took: number; + timed_out: boolean; + _scroll_id?: string; + _shards: DatabaseShardsResponse; + hits: { + total: number; + max_score: number; + hits: Array<{ + _index: string; + _type: string; + _id: string; + _score: number; + _source: T; + _version?: number; + _explanation?: DatabaseExplanation; + fields?: any; + highlight?: any; + inner_hits?: any; + sort?: string[]; + }>; + }; + aggregations?: any; +} + +export interface DatabaseExplanation { + value: number; + description: string; + details: DatabaseExplanation[]; +} + +export interface DatabaseShardsResponse { + total: number; + successful: number; + failed: number; + skipped: number; +} + +export interface DatabaseGetDocumentResponse { + _index: string; + _type: string; + _id: string; + _version: number; + found: boolean; + _source: Source; +} + +export interface DatabaseBulkResponse { + took: number; + errors: boolean; + items: Array< + | DatabaseDeleteDocumentResponse + | DatabaseIndexDocumentResponse + | DatabaseUpdateDocumentResponse + >; +} + +export interface DatabaseBulkIndexDocumentsParams + extends DatabaseGenericParams { + waitForActiveShards?: string; + refresh?: DatabaseRefresh; + routing?: string; + timeout?: string; + type?: string; + fields?: DatabaseNameList; + _source?: DatabaseNameList; + _sourceExclude?: DatabaseNameList; + _sourceInclude?: DatabaseNameList; + pipeline?: string; + index?: string; +} + +export interface DatabaseMGetParams extends DatabaseGenericParams { + storedFields?: DatabaseNameList; + preference?: string; + realtime?: boolean; + refresh?: boolean; + _source?: DatabaseNameList; + _sourceExclude?: DatabaseNameList; + _sourceInclude?: DatabaseNameList; + index: string; + type?: string; +} + +export interface DatabaseMGetResponse { + docs?: Array>; +} + +export interface DatabasePutTemplateParams extends DatabaseGenericParams { + name: string; + body: any; +} + +export interface DatabaseDeleteDocumentParams extends DatabaseGenericParams { + waitForActiveShards?: string; + parent?: string; + refresh?: DatabaseRefresh; + routing?: string; + timeout?: string; + version?: number; + versionType?: DatabaseVersionType; + index: string; + type: string; + id: string; +} + +export interface DatabaseIndexDocumentResponse { + found: boolean; + _index: string; + _type: string; + _id: string; + _version: number; + result: string; +} + +export interface DatabaseUpdateDocumentResponse { + found: boolean; + _index: string; + _type: string; + _id: string; + _version: number; + result: string; +} + +export interface DatabaseDeleteDocumentResponse { + found: boolean; + _index: string; + _type: string; + _id: string; + _version: number; + result: string; +} + +export interface DatabaseIndexDocumentParams extends DatabaseGenericParams { + waitForActiveShards?: string; + opType?: 'index' | 'create'; + parent?: string; + refresh?: string; + routing?: string; + timeout?: string; + timestamp?: Date | number; + ttl?: string; + version?: number; + versionType?: DatabaseVersionType; + pipeline?: string; + id?: string; + index: string; + type: string; + body: T; +} + +export interface DatabaseGetResponse { + found: boolean; + _source: T; +} +export interface DatabaseCreateDocumentParams extends DatabaseGenericParams { + waitForActiveShards?: string; + parent?: string; + refresh?: DatabaseRefresh; + routing?: string; + timeout?: string; + timestamp?: Date | number; + ttl?: string; + version?: number; + versionType?: DatabaseVersionType; + pipeline?: string; + id?: string; + index: string; + type: string; +} + +export interface DatabaseCreateDocumentResponse { + created: boolean; + result: string; +} + +export interface DatabaseDeleteDocumentParams extends DatabaseGenericParams { + waitForActiveShards?: string; + parent?: string; + refresh?: DatabaseRefresh; + routing?: string; + timeout?: string; + version?: number; + versionType?: DatabaseVersionType; + index: string; + type: string; + id: string; +} + +export interface DatabaseGetParams extends DatabaseGenericParams { + storedFields?: DatabaseNameList; + parent?: string; + preference?: string; + realtime?: boolean; + refresh?: boolean; + routing?: string; + _source?: DatabaseNameList; + _sourceExclude?: DatabaseNameList; + _sourceInclude?: DatabaseNameList; + version?: number; + versionType?: DatabaseVersionType; + id: string; + index: string; + type: string; +} + +export type DatabaseNameList = string | string[] | boolean; +export type DatabaseRefresh = boolean | 'true' | 'false' | 'wait_for' | ''; +export type DatabaseVersionType = + | 'internal' + | 'external' + | 'external_gte' + | 'force'; +export type ExpandWildcards = 'open' | 'closed' | 'none' | 'all'; +export type DefaultOperator = 'AND' | 'OR'; +export type DatabaseConflicts = 'abort' | 'proceed'; + +export interface DatabaseGenericParams { + requestTimeout?: number; + maxRetries?: number; + method?: string; + body?: any; + ignore?: number | number[]; + filterPath?: string | string[]; +} + +export interface DatabaseDeleteDocumentResponse { + found: boolean; + _index: string; + _type: string; + _id: string; + _version: number; + result: string; +} diff --git a/x-pack/plugins/beats/server/lib/adapters/database/kibana_database_adapter.ts b/x-pack/plugins/beats/server/lib/adapters/database/kibana_database_adapter.ts new file mode 100644 index 0000000000000..6c4d96446abd4 --- /dev/null +++ b/x-pack/plugins/beats/server/lib/adapters/database/kibana_database_adapter.ts @@ -0,0 +1,116 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { internalAuthData } from '../../../utils/wrap_request'; +import { FrameworkUser } from '../framework/adapter_types'; +import { + DatabaseAdapter, + DatabaseBulkIndexDocumentsParams, + DatabaseCreateDocumentParams, + DatabaseCreateDocumentResponse, + DatabaseDeleteDocumentParams, + DatabaseDeleteDocumentResponse, + DatabaseGetDocumentResponse, + DatabaseGetParams, + DatabaseIndexDocumentParams, + DatabaseKbnESCluster, + DatabaseKbnESPlugin, + DatabaseMGetParams, + DatabaseMGetResponse, + DatabasePutTemplateParams, + DatabaseSearchParams, + DatabaseSearchResponse, +} from './adapter_types'; + +export class KibanaDatabaseAdapter implements DatabaseAdapter { + private es: DatabaseKbnESCluster; + + constructor(kbnElasticSearch: DatabaseKbnESPlugin) { + this.es = kbnElasticSearch.getCluster('admin'); + } + public async putTemplate( + user: FrameworkUser, + params: DatabasePutTemplateParams + ): Promise { + const callES = this.getCallType(user); + const result = await callES('indices.putTemplate', params); + return result; + } + + public async get( + user: FrameworkUser, + params: DatabaseGetParams + ): Promise> { + const callES = this.getCallType(user); + const result = await callES('get', params); + return result; + // todo + } + + public async mget( + user: FrameworkUser, + params: DatabaseMGetParams + ): Promise> { + const callES = this.getCallType(user); + const result = await callES('mget', params); + return result; + // todo + } + + public async bulk( + user: FrameworkUser, + params: DatabaseBulkIndexDocumentsParams + ): Promise { + const callES = this.getCallType(user); + const result = await callES('bulk', params); + return result; + } + + public async create( + user: FrameworkUser, + params: DatabaseCreateDocumentParams + ): Promise { + const callES = this.getCallType(user); + const result = await callES('create', params); + return result; + } + public async index( + user: FrameworkUser, + params: DatabaseIndexDocumentParams + ): Promise { + const callES = this.getCallType(user); + const result = await callES('index', params); + return result; + } + public async delete( + user: FrameworkUser, + params: DatabaseDeleteDocumentParams + ): Promise { + const callES = this.getCallType(user); + const result = await callES('delete', params); + return result; + } + + public async search( + user: FrameworkUser, + params: DatabaseSearchParams + ): Promise> { + const callES = this.getCallType(user); + const result = await callES('search', params); + return result; + } + + private getCallType(user: FrameworkUser): any { + if (user.kind === 'authenticated') { + return this.es.callWithRequest.bind(null, { + headers: user[internalAuthData], + }); + } else if (user.kind === 'internal') { + return this.es.callWithInternalUser; + } else { + throw new Error('Invalid user type'); + } + } +} diff --git a/x-pack/plugins/beats/server/lib/adapters/framework/__tests__/kibana.test.ts b/x-pack/plugins/beats/server/lib/adapters/framework/__tests__/kibana.test.ts new file mode 100644 index 0000000000000..c87a7374837e3 --- /dev/null +++ b/x-pack/plugins/beats/server/lib/adapters/framework/__tests__/kibana.test.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +// file.skip + +// @ts-ignore +import { createEsTestCluster } from '@kbn/test'; +// @ts-ignore +import * as kbnTestServer from '../../../../../../../../src/test_utils/kbn_server'; +import { + config as beatsPluginConfig, + configPrefix, +} from '../../../../../index'; +import { KibanaBackendFrameworkAdapter } from '../kibana_framework_adapter'; +import { contractTests } from './test_contract'; + +const kbnServer = kbnTestServer.createServerWithCorePlugins(); + +contractTests('Kibana Framework Adapter', { + before: async () => { + await kbnServer.ready(); + + const config = kbnServer.server.config(); + config.extendSchema(beatsPluginConfig, {}, configPrefix); + + config.set('xpack.beats.encryptionKey', 'foo'); + }, + after: async () => { + await kbnServer.close(); + }, + adapterSetup: () => { + return new KibanaBackendFrameworkAdapter(kbnServer.server); + }, +}); diff --git a/x-pack/plugins/beats/server/lib/adapters/framework/__tests__/test_contract.ts b/x-pack/plugins/beats/server/lib/adapters/framework/__tests__/test_contract.ts new file mode 100644 index 0000000000000..b17d424e1baf2 --- /dev/null +++ b/x-pack/plugins/beats/server/lib/adapters/framework/__tests__/test_contract.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { BackendFrameworkAdapter } from '../adapter_types'; + +interface ContractConfig { + before?(): Promise; + after?(): Promise; + adapterSetup(): BackendFrameworkAdapter; +} + +export const contractTests = (testName: string, config: ContractConfig) => { + describe(testName, () => { + // let frameworkAdapter: BackendFrameworkAdapter; + beforeAll(async () => { + jest.setTimeout(100000); // 1 second + + if (config.before) { + await config.before(); + } + }); + afterAll(async () => config.after && (await config.after())); + beforeEach(async () => { + // FIXME: one of these always should exist, type ContractConfig as such + // frameworkAdapter = (config.adapterSetup + // ? config.adapterSetup() + // : config.adapter) as BackendFrameworkAdapter; + }); + + it('Should have tests here', () => { + expect(true).toEqual(true); + }); + }); +}; diff --git a/x-pack/plugins/beats/server/lib/adapters/framework/adapter_types.ts b/x-pack/plugins/beats/server/lib/adapters/framework/adapter_types.ts new file mode 100644 index 0000000000000..4be3589a6f043 --- /dev/null +++ b/x-pack/plugins/beats/server/lib/adapters/framework/adapter_types.ts @@ -0,0 +1,74 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { internalAuthData } from '../../../utils/wrap_request'; +export interface BackendFrameworkAdapter { + internalUser: FrameworkInternalUser; + version: string; + getSetting(settingPath: string): any; + exposeStaticDir(urlPath: string, dir: string): void; + registerRoute( + route: FrameworkRouteOptions + ): void; +} + +export interface FrameworkAuthenticatedUser { + kind: 'authenticated'; + [internalAuthData]: AuthDataType; +} + +export interface FrameworkUnAuthenticatedUser { + kind: 'unauthenticated'; +} + +export interface FrameworkInternalUser { + kind: 'internal'; +} + +export type FrameworkUser = + | FrameworkAuthenticatedUser + | FrameworkUnAuthenticatedUser + | FrameworkInternalUser; + +export interface FrameworkRequest< + InternalRequest extends FrameworkWrappableRequest = FrameworkWrappableRequest +> { + user: FrameworkUser; + headers: InternalRequest['headers']; + info: InternalRequest['info']; + payload: InternalRequest['payload']; + params: InternalRequest['params']; + query: InternalRequest['query']; +} + +export interface FrameworkRouteOptions< + RouteRequest extends FrameworkWrappableRequest, + RouteResponse +> { + path: string; + method: string | string[]; + vhost?: string; + handler: FrameworkRouteHandler; + config?: {}; +} + +export type FrameworkRouteHandler< + RouteRequest extends FrameworkWrappableRequest, + RouteResponse +> = (request: FrameworkRequest, reply: any) => void; + +export interface FrameworkWrappableRequest< + Payload = any, + Params = any, + Query = any, + Headers = any, + Info = any +> { + headers: Headers; + info: Info; + payload: Payload; + params: Params; + query: Query; +} diff --git a/x-pack/plugins/beats/server/lib/adapters/famework/kibana/kibana_framework_adapter.ts b/x-pack/plugins/beats/server/lib/adapters/framework/kibana_framework_adapter.ts similarity index 56% rename from x-pack/plugins/beats/server/lib/adapters/famework/kibana/kibana_framework_adapter.ts rename to x-pack/plugins/beats/server/lib/adapters/framework/kibana_framework_adapter.ts index a54997370ac5d..7113baf5c26e6 100644 --- a/x-pack/plugins/beats/server/lib/adapters/famework/kibana/kibana_framework_adapter.ts +++ b/x-pack/plugins/beats/server/lib/adapters/framework/kibana_framework_adapter.ts @@ -4,29 +4,30 @@ * you may not use this file except in compliance with the Elastic License. */ +import { wrapRequest } from '../../../utils/wrap_request'; import { BackendFrameworkAdapter, - FrameworkRequest, + FrameworkInternalUser, FrameworkRouteOptions, - WrappableRequest, -} from '../../../lib'; - -import { IStrictReply, Request, Server } from 'hapi'; -import { - internalFrameworkRequest, - wrapRequest, -} from '../../../../utils/wrap_request'; + FrameworkWrappableRequest, +} from './adapter_types'; export class KibanaBackendFrameworkAdapter implements BackendFrameworkAdapter { + public readonly internalUser: FrameworkInternalUser = { + kind: 'internal', + }; public version: string; - private server: Server; + private server: any; private cryptoHash: string | null; - constructor(hapiServer: Server) { + constructor(hapiServer: any) { this.server = hapiServer; - this.version = hapiServer.plugins.kibana.status.plugin.version; + if (hapiServer.plugins.kibana) { + this.version = hapiServer.plugins.kibana.status.plugin.version; + } else { + this.version = 'unknown'; + } this.cryptoHash = null; - this.validateConfig(); } @@ -52,41 +53,21 @@ export class KibanaBackendFrameworkAdapter implements BackendFrameworkAdapter { }); } - public registerRoute( - route: FrameworkRouteOptions - ) { - const wrappedHandler = (request: any, reply: IStrictReply) => + public registerRoute< + RouteRequest extends FrameworkWrappableRequest, + RouteResponse + >(route: FrameworkRouteOptions) { + const wrappedHandler = (request: any, reply: any) => route.handler(wrapRequest(request), reply); this.server.route({ - config: route.config, handler: wrappedHandler, method: route.method, path: route.path, + config: route.config, }); } - public installIndexTemplate(name: string, template: {}) { - return this.callWithInternalUser('indices.putTemplate', { - body: template, - name, - }); - } - - public async callWithInternalUser(esMethod: string, options: {}) { - const { elasticsearch } = this.server.plugins; - const { callWithInternalUser } = elasticsearch.getCluster('admin'); - return await callWithInternalUser(esMethod, options); - } - - public async callWithRequest(req: FrameworkRequest, ...rest: any[]) { - const internalRequest = req[internalFrameworkRequest]; - const { elasticsearch } = internalRequest.server.plugins; - const { callWithRequest } = elasticsearch.getCluster('data'); - const fields = await callWithRequest(internalRequest, ...rest); - return fields; - } - private validateConfig() { // @ts-ignore const config = this.server.config(); diff --git a/x-pack/plugins/beats/server/lib/adapters/famework/kibana/testing_framework_adapter.ts b/x-pack/plugins/beats/server/lib/adapters/framework/testing_framework_adapter.ts similarity index 81% rename from x-pack/plugins/beats/server/lib/adapters/famework/kibana/testing_framework_adapter.ts rename to x-pack/plugins/beats/server/lib/adapters/framework/testing_framework_adapter.ts index 9c928a05cfd5a..3bffe274fba8d 100644 --- a/x-pack/plugins/beats/server/lib/adapters/famework/kibana/testing_framework_adapter.ts +++ b/x-pack/plugins/beats/server/lib/adapters/framework/testing_framework_adapter.ts @@ -3,16 +3,16 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - import { Client } from 'elasticsearch'; -import { Request } from 'hapi'; import { get } from 'lodash'; +import { FrameworkInternalUser } from './adapter_types'; + import { BackendFrameworkAdapter, FrameworkRequest, FrameworkRouteOptions, - WrappableRequest, -} from '../../../lib'; + FrameworkWrappableRequest, +} from './adapter_types'; interface TestSettings { enrollmentTokensTtlInSeconds: number; @@ -20,6 +20,9 @@ interface TestSettings { } export class TestingBackendFrameworkAdapter implements BackendFrameworkAdapter { + public readonly internalUser: FrameworkInternalUser = { + kind: 'internal', + }; public version: string; private client: Client | null; private settings: TestSettings; @@ -46,19 +49,15 @@ export class TestingBackendFrameworkAdapter implements BackendFrameworkAdapter { // not yet testable } - public registerRoute( - route: FrameworkRouteOptions - ) { + public registerRoute< + RouteRequest extends FrameworkWrappableRequest, + RouteResponse + >(route: FrameworkRouteOptions) { // not yet testable } public installIndexTemplate(name: string, template: {}) { - if (this.client) { - return this.client.indices.putTemplate({ - body: template, - name, - }); - } + return; } public async callWithInternalUser(esMethod: string, options: {}) { @@ -70,7 +69,7 @@ export class TestingBackendFrameworkAdapter implements BackendFrameworkAdapter { } public async callWithRequest( - req: FrameworkRequest, + req: FrameworkRequest, esMethod: string, options: {} ) { diff --git a/x-pack/plugins/beats/server/lib/adapters/tags/adapter_types.ts b/x-pack/plugins/beats/server/lib/adapters/tags/adapter_types.ts new file mode 100644 index 0000000000000..19333c831d594 --- /dev/null +++ b/x-pack/plugins/beats/server/lib/adapters/tags/adapter_types.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { BeatTag } from '../../../../common/domain_types'; +import { FrameworkUser } from '../framework/adapter_types'; + +export interface CMTagsAdapter { + getTagsWithIds(user: FrameworkUser, tagIds: string[]): any; + upsertTag(user: FrameworkUser, tag: BeatTag): Promise<{}>; +} diff --git a/x-pack/plugins/beats/server/lib/adapters/tags/elasticsearch_tags_adapter.ts b/x-pack/plugins/beats/server/lib/adapters/tags/elasticsearch_tags_adapter.ts index 44aea344151ca..3f982f5edbb09 100644 --- a/x-pack/plugins/beats/server/lib/adapters/tags/elasticsearch_tags_adapter.ts +++ b/x-pack/plugins/beats/server/lib/adapters/tags/elasticsearch_tags_adapter.ts @@ -6,21 +6,20 @@ import { get } from 'lodash'; import { INDEX_NAMES } from '../../../../common/constants'; -import { - BackendFrameworkAdapter, - BeatTag, - CMTagsAdapter, - FrameworkRequest, -} from '../../lib'; +import { FrameworkUser } from './../framework/adapter_types'; + +import { BeatTag } from '../../../../common/domain_types'; +import { DatabaseAdapter } from '../database/adapter_types'; +import { CMTagsAdapter } from './adapter_types'; export class ElasticsearchTagsAdapter implements CMTagsAdapter { - private framework: BackendFrameworkAdapter; + private database: DatabaseAdapter; - constructor(framework: BackendFrameworkAdapter) { - this.framework = framework; + constructor(database: DatabaseAdapter) { + this.database = database; } - public async getTagsWithIds(req: FrameworkRequest, tagIds: string[]) { + public async getTagsWithIds(user: FrameworkUser, tagIds: string[]) { const ids = tagIds.map(tag => `tag:${tag}`); // TODO abstract to kibana adapter as the more generic getDocs @@ -32,7 +31,7 @@ export class ElasticsearchTagsAdapter implements CMTagsAdapter { index: INDEX_NAMES.BEATS, type: '_doc', }; - const response = await this.framework.callWithRequest(req, 'mget', params); + const response = await this.database.mget(user, params); return get(response, 'docs', []) .filter((b: any) => b.found) @@ -42,7 +41,7 @@ export class ElasticsearchTagsAdapter implements CMTagsAdapter { })); } - public async upsertTag(req: FrameworkRequest, tag: BeatTag) { + public async upsertTag(user: FrameworkUser, tag: BeatTag) { const body = { tag, type: 'tag', @@ -55,7 +54,7 @@ export class ElasticsearchTagsAdapter implements CMTagsAdapter { refresh: 'wait_for', type: '_doc', }; - const response = await this.framework.callWithRequest(req, 'index', params); + const response = await this.database.index(user, params); // TODO this is not something that works for TS... change this return type return get(response, 'result'); diff --git a/x-pack/plugins/beats/server/lib/adapters/tags/memory_tags_adapter.ts b/x-pack/plugins/beats/server/lib/adapters/tags/memory_tags_adapter.ts index 4d2a80e1b39c2..64ef401008ae1 100644 --- a/x-pack/plugins/beats/server/lib/adapters/tags/memory_tags_adapter.ts +++ b/x-pack/plugins/beats/server/lib/adapters/tags/memory_tags_adapter.ts @@ -4,7 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { BeatTag, CMTagsAdapter, FrameworkRequest } from '../../lib'; +import { BeatTag } from '../../../../common/domain_types'; +import { FrameworkUser } from './../framework/adapter_types'; +import { CMTagsAdapter } from './adapter_types'; export class MemoryTagsAdapter implements CMTagsAdapter { private tagsDB: BeatTag[] = []; @@ -13,11 +15,11 @@ export class MemoryTagsAdapter implements CMTagsAdapter { this.tagsDB = tagsDB; } - public async getTagsWithIds(req: FrameworkRequest, tagIds: string[]) { + public async getTagsWithIds(user: FrameworkUser, tagIds: string[]) { return this.tagsDB.filter(tag => tagIds.includes(tag.id)); } - public async upsertTag(req: FrameworkRequest, tag: BeatTag) { + public async upsertTag(user: FrameworkUser, tag: BeatTag) { const existingTagIndex = this.tagsDB.findIndex(t => t.id === tag.id); if (existingTagIndex !== -1) { this.tagsDB[existingTagIndex] = tag; diff --git a/x-pack/plugins/beats/server/lib/adapters/tokens/adapter_types.ts b/x-pack/plugins/beats/server/lib/adapters/tokens/adapter_types.ts new file mode 100644 index 0000000000000..71fb719d9952a --- /dev/null +++ b/x-pack/plugins/beats/server/lib/adapters/tokens/adapter_types.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { FrameworkUser } from '../framework/adapter_types'; + +export interface TokenEnrollmentData { + token: string | null; + expires_on: string; +} + +export interface CMTokensAdapter { + deleteEnrollmentToken(enrollmentToken: string): Promise; + getEnrollmentToken(enrollmentToken: string): Promise; + upsertTokens( + user: FrameworkUser, + tokens: TokenEnrollmentData[] + ): Promise; +} diff --git a/x-pack/plugins/beats/server/lib/adapters/tokens/elasticsearch_tokens_adapter.ts b/x-pack/plugins/beats/server/lib/adapters/tokens/elasticsearch_tokens_adapter.ts index 7a63c784ecf6a..6dbefce514052 100644 --- a/x-pack/plugins/beats/server/lib/adapters/tokens/elasticsearch_tokens_adapter.ts +++ b/x-pack/plugins/beats/server/lib/adapters/tokens/elasticsearch_tokens_adapter.ts @@ -6,17 +6,19 @@ import { flatten, get } from 'lodash'; import { INDEX_NAMES } from '../../../../common/constants'; +import { DatabaseAdapter } from '../database/adapter_types'; import { BackendFrameworkAdapter, - CMTokensAdapter, - EnrollmentToken, - FrameworkRequest, -} from '../../lib'; + FrameworkUser, +} from '../framework/adapter_types'; +import { CMTokensAdapter, TokenEnrollmentData } from './adapter_types'; export class ElasticsearchTokensAdapter implements CMTokensAdapter { + private database: DatabaseAdapter; private framework: BackendFrameworkAdapter; - constructor(framework: BackendFrameworkAdapter) { + constructor(database: DatabaseAdapter, framework: BackendFrameworkAdapter) { + this.database = database; this.framework = framework; } @@ -27,12 +29,12 @@ export class ElasticsearchTokensAdapter implements CMTokensAdapter { type: '_doc', }; - return this.framework.callWithInternalUser('delete', params); + await this.database.delete(this.framework.internalUser, params); } public async getEnrollmentToken( tokenString: string - ): Promise { + ): Promise { const params = { id: `enrollment_token:${tokenString}`, ignore: [404], @@ -40,8 +42,11 @@ export class ElasticsearchTokensAdapter implements CMTokensAdapter { type: '_doc', }; - const response = await this.framework.callWithInternalUser('get', params); - const tokenDetails = get( + const response = await this.database.get( + this.framework.internalUser, + params + ); + const tokenDetails = get( response, '_source.enrollment_token', { @@ -55,12 +60,15 @@ export class ElasticsearchTokensAdapter implements CMTokensAdapter { // out whether a token is valid or not. So we introduce a random delay in returning from // this function to obscure the actual time it took for Elasticsearch to find the token. const randomDelayInMs = 25 + Math.round(Math.random() * 200); // between 25 and 225 ms - return new Promise(resolve => + return new Promise(resolve => setTimeout(() => resolve(tokenDetails), randomDelayInMs) ); } - public async upsertTokens(req: FrameworkRequest, tokens: EnrollmentToken[]) { + public async upsertTokens( + user: FrameworkUser, + tokens: TokenEnrollmentData[] + ) { const body = flatten( tokens.map(token => [ { index: { _id: `enrollment_token:${token.token}` } }, @@ -71,14 +79,12 @@ export class ElasticsearchTokensAdapter implements CMTokensAdapter { ]) ); - const params = { + await this.database.bulk(user, { body, index: INDEX_NAMES.BEATS, refresh: 'wait_for', type: '_doc', - }; - - await this.framework.callWithRequest(req, 'bulk', params); + }); return tokens; } } diff --git a/x-pack/plugins/beats/server/lib/adapters/tokens/memory_tokens_adapter.ts b/x-pack/plugins/beats/server/lib/adapters/tokens/memory_tokens_adapter.ts index 1734327007e08..1eed188d3f2e9 100644 --- a/x-pack/plugins/beats/server/lib/adapters/tokens/memory_tokens_adapter.ts +++ b/x-pack/plugins/beats/server/lib/adapters/tokens/memory_tokens_adapter.ts @@ -4,12 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { CMTokensAdapter, EnrollmentToken, FrameworkRequest } from '../../lib'; +import { FrameworkAuthenticatedUser } from './../framework/adapter_types'; +import { CMTokensAdapter, TokenEnrollmentData } from './adapter_types'; export class MemoryTokensAdapter implements CMTokensAdapter { - private tokenDB: EnrollmentToken[]; + private tokenDB: TokenEnrollmentData[]; - constructor(tokenDB: EnrollmentToken[]) { + constructor(tokenDB: TokenEnrollmentData[]) { this.tokenDB = tokenDB; } @@ -25,13 +26,16 @@ export class MemoryTokensAdapter implements CMTokensAdapter { public async getEnrollmentToken( tokenString: string - ): Promise { - return new Promise(resolve => { + ): Promise { + return new Promise(resolve => { return resolve(this.tokenDB.find(token => token.token === tokenString)); }); } - public async upsertTokens(req: FrameworkRequest, tokens: EnrollmentToken[]) { + public async upsertTokens( + user: FrameworkAuthenticatedUser, + tokens: TokenEnrollmentData[] + ) { tokens.forEach(token => { const existingIndex = this.tokenDB.findIndex( t => t.token === token.token diff --git a/x-pack/plugins/beats/server/lib/compose/kibana.ts b/x-pack/plugins/beats/server/lib/compose/kibana.ts index ff478646aea89..7c46f82b84bf3 100644 --- a/x-pack/plugins/beats/server/lib/compose/kibana.ts +++ b/x-pack/plugins/beats/server/lib/compose/kibana.ts @@ -7,8 +7,9 @@ import { ElasticsearchBeatsAdapter } from '../adapters/beats/elasticsearch_beats_adapter'; import { ElasticsearchTagsAdapter } from '../adapters/tags/elasticsearch_tags_adapter'; import { ElasticsearchTokensAdapter } from '../adapters/tokens/elasticsearch_tokens_adapter'; +import { KibanaDatabaseAdapter } from './../adapters/database/kibana_database_adapter'; -import { KibanaBackendFrameworkAdapter } from '../adapters/famework/kibana/kibana_framework_adapter'; +import { KibanaBackendFrameworkAdapter } from '../adapters/framework/kibana_framework_adapter'; import { CMBeatsDomain } from '../domains/beats'; import { CMTagsDomain } from '../domains/tags'; @@ -16,19 +17,24 @@ import { CMTokensDomain } from '../domains/tokens'; import { CMDomainLibs, CMServerLibs } from '../lib'; -import { Server } from 'hapi'; - -export function compose(server: Server): CMServerLibs { +export function compose(server: any): CMServerLibs { const framework = new KibanaBackendFrameworkAdapter(server); - - const tags = new CMTagsDomain(new ElasticsearchTagsAdapter(framework)); - const tokens = new CMTokensDomain(new ElasticsearchTokensAdapter(framework), { - framework, - }); - const beats = new CMBeatsDomain(new ElasticsearchBeatsAdapter(framework), { - tags, - tokens, - }); + const database = new KibanaDatabaseAdapter(server.plugins.elasticsearch); + + const tags = new CMTagsDomain(new ElasticsearchTagsAdapter(database)); + const tokens = new CMTokensDomain( + new ElasticsearchTokensAdapter(database, framework), + { + framework, + } + ); + const beats = new CMBeatsDomain( + new ElasticsearchBeatsAdapter(database, framework), + { + tags, + tokens, + } + ); const domainLibs: CMDomainLibs = { beats, @@ -38,6 +44,7 @@ export function compose(server: Server): CMServerLibs { const libs: CMServerLibs = { framework, + database, ...domainLibs, }; diff --git a/x-pack/plugins/beats/server/lib/domains/__tests__/beats/assign_tags.test.ts b/x-pack/plugins/beats/server/lib/domains/__tests__/beats/assign_tags.test.ts index c1e360ffd75f4..f28fcda8004e4 100644 --- a/x-pack/plugins/beats/server/lib/domains/__tests__/beats/assign_tags.test.ts +++ b/x-pack/plugins/beats/server/lib/domains/__tests__/beats/assign_tags.test.ts @@ -4,14 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import expect from 'expect.js'; -import { wrapRequest } from '../../../../utils/wrap_request'; +import { FrameworkInternalUser } from './../../../adapters/framework/adapter_types'; + import { MemoryBeatsAdapter } from '../../../adapters/beats/memory_beats_adapter'; -import { TestingBackendFrameworkAdapter } from '../../../adapters/famework/kibana/testing_framework_adapter'; +import { TestingBackendFrameworkAdapter } from '../../../adapters/framework/testing_framework_adapter'; import { MemoryTagsAdapter } from '../../../adapters/tags/memory_tags_adapter'; import { MemoryTokensAdapter } from '../../../adapters/tokens/memory_tokens_adapter'; -import { BeatTag, CMBeat } from './../../../lib'; +import { BeatTag, CMBeat } from '../../../../../common/domain_types'; import { CMBeatsDomain } from '../../beats'; import { CMTagsDomain } from '../../tags'; @@ -22,13 +22,7 @@ import Chance from 'chance'; const seed = Date.now(); const chance = new Chance(seed); -const fakeReq = wrapRequest({ - headers: {}, - info: {}, - params: {}, - payload: {}, - query: {}, -}); +const internalUser: FrameworkInternalUser = { kind: 'internal' }; const settings = { encryptionKey: 'something_who_cares', @@ -103,11 +97,11 @@ describe('Beats Domain Lib', () => { }); it('should add a single tag to a single beat', async () => { - const apiResponse = await beatsLib.assignTagsToBeats(fakeReq, [ + const apiResponse = await beatsLib.assignTagsToBeats(internalUser, [ { beatId: 'bar', tag: 'production' }, ]); - expect(apiResponse.assignments).to.eql([ + expect(apiResponse.assignments).toEqual([ { status: 200, result: 'updated' }, ]); }); @@ -116,80 +110,80 @@ describe('Beats Domain Lib', () => { const tags = ['production']; let beat = beatsDB.find(b => b.id === 'foo') as any; - expect(beat.tags).to.eql([...tags, 'qa']); + expect(beat.tags).toEqual([...tags, 'qa']); // Adding the existing tag - const apiResponse = await beatsLib.assignTagsToBeats(fakeReq, [ + const apiResponse = await beatsLib.assignTagsToBeats(internalUser, [ { beatId: 'foo', tag: 'production' }, ]); - expect(apiResponse.assignments).to.eql([ + expect(apiResponse.assignments).toEqual([ { status: 200, result: 'updated' }, ]); beat = beatsDB.find(b => b.id === 'foo') as any; - expect(beat.tags).to.eql([...tags, 'qa']); + expect(beat.tags).toEqual([...tags, 'qa']); }); it('should add a single tag to a multiple beats', async () => { - const apiResponse = await beatsLib.assignTagsToBeats(fakeReq, [ + const apiResponse = await beatsLib.assignTagsToBeats(internalUser, [ { beatId: 'foo', tag: 'development' }, { beatId: 'bar', tag: 'development' }, ]); - expect(apiResponse.assignments).to.eql([ + expect(apiResponse.assignments).toEqual([ { status: 200, result: 'updated' }, { status: 200, result: 'updated' }, ]); let beat = beatsDB.find(b => b.id === 'foo') as any; - expect(beat.tags).to.eql(['production', 'qa', 'development']); // as beat 'foo' already had 'production' and 'qa' tags attached to it + expect(beat.tags).toEqual(['production', 'qa', 'development']); // as beat 'foo' already had 'production' and 'qa' tags attached to it beat = beatsDB.find(b => b.id === 'bar') as any; - expect(beat.tags).to.eql(['development']); + expect(beat.tags).toEqual(['development']); }); it('should add multiple tags to a single beat', async () => { - const apiResponse = await beatsLib.assignTagsToBeats(fakeReq, [ + const apiResponse = await beatsLib.assignTagsToBeats(internalUser, [ { beatId: 'bar', tag: 'development' }, { beatId: 'bar', tag: 'production' }, ]); - expect(apiResponse.assignments).to.eql([ + expect(apiResponse.assignments).toEqual([ { status: 200, result: 'updated' }, { status: 200, result: 'updated' }, ]); const beat = beatsDB.find(b => b.id === 'bar') as any; - expect(beat.tags).to.eql(['development', 'production']); + expect(beat.tags).toEqual(['development', 'production']); }); it('should add multiple tags to a multiple beats', async () => { - const apiResponse = await beatsLib.assignTagsToBeats(fakeReq, [ + const apiResponse = await beatsLib.assignTagsToBeats(internalUser, [ { beatId: 'foo', tag: 'development' }, { beatId: 'bar', tag: 'production' }, ]); - expect(apiResponse.assignments).to.eql([ + expect(apiResponse.assignments).toEqual([ { status: 200, result: 'updated' }, { status: 200, result: 'updated' }, ]); let beat = beatsDB.find(b => b.id === 'foo') as any; - expect(beat.tags).to.eql(['production', 'qa', 'development']); // as beat 'foo' already had 'production' and 'qa' tags attached to it + expect(beat.tags).toEqual(['production', 'qa', 'development']); // as beat 'foo' already had 'production' and 'qa' tags attached to it beat = beatsDB.find(b => b.id === 'bar') as any; - expect(beat.tags).to.eql(['production']); + expect(beat.tags).toEqual(['production']); }); it('should return errors for non-existent beats', async () => { const nonExistentBeatId = chance.word(); - const apiResponse = await beatsLib.assignTagsToBeats(fakeReq, [ + const apiResponse = await beatsLib.assignTagsToBeats(internalUser, [ { beatId: nonExistentBeatId, tag: 'production' }, ]); - expect(apiResponse.assignments).to.eql([ + expect(apiResponse.assignments).toEqual([ { status: 404, result: `Beat ${nonExistentBeatId} not found` }, ]); }); @@ -197,27 +191,27 @@ describe('Beats Domain Lib', () => { it('should return errors for non-existent tags', async () => { const nonExistentTag = chance.word(); - const apiResponse = await beatsLib.assignTagsToBeats(fakeReq, [ + const apiResponse = await beatsLib.assignTagsToBeats(internalUser, [ { beatId: 'bar', tag: nonExistentTag }, ]); - expect(apiResponse.assignments).to.eql([ + expect(apiResponse.assignments).toEqual([ { status: 404, result: `Tag ${nonExistentTag} not found` }, ]); const beat = beatsDB.find(b => b.id === 'bar') as any; - expect(beat).to.not.have.property('tags'); + expect(beat).not.toHaveProperty('tags'); }); it('should return errors for non-existent beats and tags', async () => { const nonExistentBeatId = chance.word(); const nonExistentTag = chance.word(); - const apiResponse = await beatsLib.assignTagsToBeats(fakeReq, [ + const apiResponse = await beatsLib.assignTagsToBeats(internalUser, [ { beatId: nonExistentBeatId, tag: nonExistentTag }, ]); - expect(apiResponse.assignments).to.eql([ + expect(apiResponse.assignments).toEqual([ { result: `Beat ${nonExistentBeatId} and tag ${nonExistentTag} not found`, status: 404, @@ -225,7 +219,7 @@ describe('Beats Domain Lib', () => { ]); const beat = beatsDB.find(b => b.id === 'bar') as any; - expect(beat).to.not.have.property('tags'); + expect(beat).not.toHaveProperty('tags'); }); }); }); diff --git a/x-pack/plugins/beats/server/lib/domains/__tests__/beats/enroll.test.ts b/x-pack/plugins/beats/server/lib/domains/__tests__/beats/enroll.test.ts index f52f1096227c0..1466e9ed926c7 100644 --- a/x-pack/plugins/beats/server/lib/domains/__tests__/beats/enroll.test.ts +++ b/x-pack/plugins/beats/server/lib/domains/__tests__/beats/enroll.test.ts @@ -4,13 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import expect from 'expect.js'; import { MemoryBeatsAdapter } from '../../../adapters/beats/memory_beats_adapter'; -import { TestingBackendFrameworkAdapter } from '../../../adapters/famework/kibana/testing_framework_adapter'; +import { TestingBackendFrameworkAdapter } from '../../../adapters/framework/testing_framework_adapter'; import { MemoryTagsAdapter } from '../../../adapters/tags/memory_tags_adapter'; import { MemoryTokensAdapter } from '../../../adapters/tokens/memory_tokens_adapter'; -import { BeatTag, CMBeat, EnrollmentToken } from './../../../lib'; +import { BeatTag, CMBeat } from '../../../../../common/domain_types'; +import { TokenEnrollmentData } from '../../../adapters/tokens/adapter_types'; import { CMBeatsDomain } from '../../beats'; import { CMTagsDomain } from '../../tags'; @@ -35,7 +35,7 @@ describe('Beats Domain Lib', () => { let beatsDB: CMBeat[] = []; let tagsDB: BeatTag[] = []; - let tokensDB: EnrollmentToken[] = []; + let tokensDB: TokenEnrollmentData[] = []; let validEnrollmentToken: string; let beatId: string; let beat: Partial; @@ -88,27 +88,27 @@ describe('Beats Domain Lib', () => { validEnrollmentToken ); - expect(token).to.equal(validEnrollmentToken); + expect(token).toEqual(validEnrollmentToken); const { accessToken } = await beatsLib.enrollBeat( beatId, '192.168.1.1', omit(beat, 'enrollment_token') ); - expect(beatsDB.length).to.eql(1); - expect(beatsDB[0]).to.have.property('host_ip'); + expect(beatsDB.length).toEqual(1); + expect(beatsDB[0]).toHaveProperty('host_ip'); - expect(accessToken).to.eql(beatsDB[0].access_token); + expect(accessToken).toEqual(beatsDB[0].access_token); await tokensLib.deleteEnrollmentToken(validEnrollmentToken); - expect(tokensDB.length).to.eql(0); + expect(tokensDB.length).toEqual(0); }); it('should reject an invalid enrollment token', async () => { const { token } = await tokensLib.getEnrollmentToken(chance.word()); - expect(token).to.eql(null); + expect(token).toEqual(null); }); it('should reject an expired enrollment token', async () => { @@ -118,19 +118,19 @@ describe('Beats Domain Lib', () => { }) ); - expect(token).to.eql(null); + expect(token).toEqual(null); }); it('should delete the given enrollment token so it may not be reused', async () => { - expect(tokensDB[0].token).to.eql(validEnrollmentToken); + expect(tokensDB[0].token).toEqual(validEnrollmentToken); await tokensLib.deleteEnrollmentToken(validEnrollmentToken); - expect(tokensDB.length).to.eql(0); + expect(tokensDB.length).toEqual(0); const { token } = await tokensLib.getEnrollmentToken( validEnrollmentToken ); - expect(token).to.eql(null); + expect(token).toEqual(null); }); }); }); diff --git a/x-pack/plugins/beats/server/lib/domains/__tests__/beats/verify.test.ts b/x-pack/plugins/beats/server/lib/domains/__tests__/beats/verify.test.ts index 7310b101a351a..140a17917684d 100644 --- a/x-pack/plugins/beats/server/lib/domains/__tests__/beats/verify.test.ts +++ b/x-pack/plugins/beats/server/lib/domains/__tests__/beats/verify.test.ts @@ -4,14 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import expect from 'expect.js'; -import { wrapRequest } from '../../../../utils/wrap_request'; import { MemoryBeatsAdapter } from '../../../adapters/beats/memory_beats_adapter'; -import { TestingBackendFrameworkAdapter } from '../../../adapters/famework/kibana/testing_framework_adapter'; +import { TestingBackendFrameworkAdapter } from '../../../adapters/framework/testing_framework_adapter'; import { MemoryTagsAdapter } from '../../../adapters/tags/memory_tags_adapter'; import { MemoryTokensAdapter } from '../../../adapters/tokens/memory_tokens_adapter'; -import { BeatTag, CMBeat, EnrollmentToken } from './../../../lib'; +import { BeatTag, CMBeat } from '../../../../../common/domain_types'; +import { TokenEnrollmentData } from '../../../adapters/tokens/adapter_types'; import { CMBeatsDomain } from '../../beats'; import { CMTagsDomain } from '../../tags'; @@ -27,21 +26,13 @@ const settings = { enrollmentTokensTtlInSeconds: 10 * 60, // 10 minutes }; -const fakeReq = wrapRequest({ - headers: {}, - info: {}, - params: {}, - payload: {}, - query: {}, -}); - describe('Beats Domain Lib', () => { let beatsLib: CMBeatsDomain; let tokensLib: CMTokensDomain; let beatsDB: CMBeat[] = []; let tagsDB: BeatTag[] = []; - let tokensDB: EnrollmentToken[] = []; + let tokensDB: TokenEnrollmentData[] = []; describe('verify_beat', () => { beforeEach(async () => { @@ -123,7 +114,7 @@ describe('Beats Domain Lib', () => { verifiedBeatIds, alreadyVerifiedBeatIds, nonExistentBeatIds, - } = await beatsLib.verifyBeats(fakeReq, beatIds); + } = await beatsLib.verifyBeats({ kind: 'unauthenticated' }, beatIds); // TODO calculation of status should be done in-lib, w/switch statement here beats.forEach(b => { @@ -143,7 +134,7 @@ describe('Beats Domain Lib', () => { }); const response = { beats }; - expect(response.beats).to.eql([ + expect(response.beats).toEqual([ { id: 'bar', status: 200, result: 'verified' }, { id: nonExistentBeatId, status: 404, result: 'not found' }, ]); @@ -163,7 +154,7 @@ describe('Beats Domain Lib', () => { verifiedBeatIds, alreadyVerifiedBeatIds, nonExistentBeatIds, - } = await beatsLib.verifyBeats(fakeReq, beatIds); + } = await beatsLib.verifyBeats({ kind: 'unauthenticated' }, beatIds); // TODO calculation of status should be done in-lib, w/switch statement here beats.forEach(beat => { @@ -183,7 +174,7 @@ describe('Beats Domain Lib', () => { }); const response = { beats }; - expect(response.beats).to.eql([ + expect(response.beats).toEqual([ { id: 'foo', status: 200, result: 'already verified' }, { id: 'bar', status: 200, result: 'verified' }, ]); diff --git a/x-pack/plugins/beats/server/lib/domains/__tests__/tokens.test.ts b/x-pack/plugins/beats/server/lib/domains/__tests__/tokens.test.ts index 174c7d628778c..8002013fa4795 100644 --- a/x-pack/plugins/beats/server/lib/domains/__tests__/tokens.test.ts +++ b/x-pack/plugins/beats/server/lib/domains/__tests__/tokens.test.ts @@ -4,27 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ -import expect from 'expect.js'; -import { wrapRequest } from '../../../utils/wrap_request'; -import { TestingBackendFrameworkAdapter } from '../../adapters/famework/kibana/testing_framework_adapter'; +import { TestingBackendFrameworkAdapter } from '../../adapters/framework/testing_framework_adapter'; +import { TokenEnrollmentData } from '../../adapters/tokens/adapter_types'; import { MemoryTokensAdapter } from '../../adapters/tokens/memory_tokens_adapter'; -import { EnrollmentToken } from '../../lib'; import { CMTokensDomain } from '../tokens'; import Chance from 'chance'; import moment from 'moment'; +import { BackendFrameworkAdapter } from '../../adapters/framework/adapter_types'; const seed = Date.now(); const chance = new Chance(seed); -const fakeReq = wrapRequest({ - headers: {}, - info: {}, - params: {}, - payload: {}, - query: {}, -}); - const settings = { encryptionKey: 'something_who_cares', enrollmentTokensTtlInSeconds: 10 * 60, // 10 minutes @@ -32,11 +23,12 @@ const settings = { describe('Token Domain Lib', () => { let tokensLib: CMTokensDomain; - let tokensDB: EnrollmentToken[] = []; + let tokensDB: TokenEnrollmentData[] = []; + let framework: BackendFrameworkAdapter; beforeEach(async () => { tokensDB = []; - const framework = new TestingBackendFrameworkAdapter(null, settings); + framework = new TestingBackendFrameworkAdapter(null, settings); tokensLib = new CMTokensDomain(new MemoryTokensAdapter(tokensDB), { framework, @@ -44,26 +36,31 @@ describe('Token Domain Lib', () => { }); it('should generate webtokens with a qty of 1', async () => { - const tokens = await tokensLib.createEnrollmentTokens(fakeReq, 1); + const tokens = await tokensLib.createEnrollmentTokens( + framework.internalUser, + 1 + ); - expect(tokens.length).to.be(1); + expect(tokens.length).toBe(1); - expect(typeof tokens[0]).to.be('string'); + expect(typeof tokens[0]).toBe('string'); }); it('should create the specified number of tokens', async () => { const numTokens = chance.integer({ min: 1, max: 20 }); const tokensFromApi = await tokensLib.createEnrollmentTokens( - fakeReq, + framework.internalUser, numTokens ); - expect(tokensFromApi.length).to.eql(numTokens); - expect(tokensFromApi).to.eql(tokensDB.map((t: EnrollmentToken) => t.token)); + expect(tokensFromApi.length).toEqual(numTokens); + expect(tokensFromApi).toEqual( + tokensDB.map((t: TokenEnrollmentData) => t.token) + ); }); it('should set token expiration to 10 minutes from now by default', async () => { - await tokensLib.createEnrollmentTokens(fakeReq, 1); + await tokensLib.createEnrollmentTokens(framework.internalUser, 1); const token = tokensDB[0]; @@ -81,7 +78,7 @@ describe('Token Domain Lib', () => { const almostTenMinutesFromNow = moment(tenMinutesFromNow) .subtract('2', 'seconds') .valueOf(); - expect(tokenExpiresOn).to.be.lessThan(tenMinutesFromNow); - expect(tokenExpiresOn).to.be.greaterThan(almostTenMinutesFromNow); + expect(tokenExpiresOn).toBeLessThan(tenMinutesFromNow); + expect(tokenExpiresOn).toBeGreaterThan(almostTenMinutesFromNow); }); }); diff --git a/x-pack/plugins/beats/server/lib/domains/beats.ts b/x-pack/plugins/beats/server/lib/domains/beats.ts index 0d5e068ff4ff7..9a580e2291ee7 100644 --- a/x-pack/plugins/beats/server/lib/domains/beats.ts +++ b/x-pack/plugins/beats/server/lib/domains/beats.ts @@ -4,24 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - import { uniq } from 'lodash'; import { findNonExistentItems } from '../../utils/find_non_existent_items'; +import { CMBeat } from '../../../common/domain_types'; import { - CMAssignmentReturn, - CMBeat, + BeatsTagAssignment, CMBeatsAdapter, - CMDomainLibs, - CMRemovalReturn, - CMTagAssignment, - FrameworkRequest, -} from '../lib'; +} from '../adapters/beats/adapter_types'; +import { FrameworkUser } from '../adapters/framework/adapter_types'; + +import { CMAssignmentReturn } from '../adapters/beats/adapter_types'; +import { CMDomainLibs } from '../lib'; +import { BeatsRemovalReturn } from './../adapters/beats/adapter_types'; export class CMBeatsDomain { private adapter: CMBeatsAdapter; @@ -85,9 +80,9 @@ export class CMBeatsDomain { } public async removeTagsFromBeats( - req: FrameworkRequest, - removals: CMTagAssignment[] - ): Promise { + user: FrameworkUser, + removals: BeatsTagAssignment[] + ): Promise { const beatIds = uniq(removals.map(removal => removal.beatId)); const tagIds = uniq(removals.map(removal => removal.tag)); @@ -95,8 +90,8 @@ export class CMBeatsDomain { removals: removals.map(() => ({ status: null })), }; - const beats = await this.adapter.getWithIds(req, beatIds); - const tags = await this.tags.getTagsWithIds(req, tagIds); + const beats = await this.adapter.getWithIds(user, beatIds); + const tags = await this.tags.getTagsWithIds(user, tagIds); // Handle assignments containing non-existing beat IDs or tags const nonExistentBeatIds = findNonExistentItems(beats, beatIds); @@ -121,7 +116,7 @@ export class CMBeatsDomain { if (validRemovals.length > 0) { const removalResults = await this.adapter.removeTagsFromBeats( - req, + user, validRemovals ); return addToResultsToResponse('removals', response, removalResults); @@ -129,13 +124,13 @@ export class CMBeatsDomain { return response; } - public async getAllBeats(req: FrameworkRequest) { - return await this.adapter.getAll(req); + public async getAllBeats(user: FrameworkUser) { + return await this.adapter.getAll(user); } // TODO cleanup return value, should return a status enum - public async verifyBeats(req: FrameworkRequest, beatIds: string[]) { - const beatsFromEs = await this.adapter.getWithIds(req, beatIds); + public async verifyBeats(user: FrameworkUser, beatIds: string[]) { + const beatsFromEs = await this.adapter.getWithIds(user, beatIds); const nonExistentBeatIds = findNonExistentItems(beatsFromEs, beatIds); @@ -148,7 +143,7 @@ export class CMBeatsDomain { .map((beat: any) => beat.id); const verifications = await this.adapter.verifyBeats( - req, + user, toBeVerifiedBeatIds ); @@ -161,8 +156,8 @@ export class CMBeatsDomain { } public async assignTagsToBeats( - req: FrameworkRequest, - assignments: CMTagAssignment[] + user: FrameworkUser, + assignments: BeatsTagAssignment[] ): Promise { const beatIds = uniq(assignments.map(assignment => assignment.beatId)); const tagIds = uniq(assignments.map(assignment => assignment.tag)); @@ -170,8 +165,8 @@ export class CMBeatsDomain { const response = { assignments: assignments.map(() => ({ status: null })), }; - const beats = await this.adapter.getWithIds(req, beatIds); - const tags = await this.tags.getTagsWithIds(req, tagIds); + const beats = await this.adapter.getWithIds(user, beatIds); + const tags = await this.tags.getTagsWithIds(user, tagIds); // Handle assignments containing non-existing beat IDs or tags const nonExistentBeatIds = findNonExistentItems(beats, beatIds); const nonExistentTags = findNonExistentItems(tags, tagIds); @@ -197,7 +192,7 @@ export class CMBeatsDomain { if (validAssignments.length > 0) { const assignmentResults = await this.adapter.assignTagsToBeats( - req, + user, validAssignments ); @@ -216,7 +211,7 @@ function addNonExistentItemToResponse( nonExistentTags: any, key: string ) { - assignments.forEach(({ beatId, tag }: CMTagAssignment, idx: any) => { + assignments.forEach(({ beatId, tag }: BeatsTagAssignment, idx: any) => { const isBeatNonExistent = nonExistentBeatIds.includes(beatId); const isTagNonExistent = nonExistentTags.includes(tag); diff --git a/x-pack/plugins/beats/server/lib/domains/tags.ts b/x-pack/plugins/beats/server/lib/domains/tags.ts index 43bb8dfed15a1..194551721ab33 100644 --- a/x-pack/plugins/beats/server/lib/domains/tags.ts +++ b/x-pack/plugins/beats/server/lib/domains/tags.ts @@ -6,7 +6,10 @@ import { intersection, uniq, values } from 'lodash'; import { UNIQUENESS_ENFORCING_TYPES } from '../../../common/constants'; -import { CMTagsAdapter, ConfigurationBlock, FrameworkRequest } from '../lib'; +import { ConfigurationBlock } from '../../../common/domain_types'; +import { FrameworkUser } from '../adapters/framework/adapter_types'; + +import { CMTagsAdapter } from '../adapters/tags/adapter_types'; import { entries } from './../../utils/polyfills'; export class CMTagsDomain { @@ -15,12 +18,12 @@ export class CMTagsDomain { this.adapter = adapter; } - public async getTagsWithIds(req: FrameworkRequest, tagIds: string[]) { - return await this.adapter.getTagsWithIds(req, tagIds); + public async getTagsWithIds(user: FrameworkUser, tagIds: string[]) { + return await this.adapter.getTagsWithIds(user, tagIds); } public async saveTag( - req: FrameworkRequest, + user: FrameworkUser, tagId: string, configs: ConfigurationBlock[] ) { @@ -37,7 +40,7 @@ export class CMTagsDomain { }; return { isValid: true, - result: await this.adapter.upsertTag(req, tag), + result: await this.adapter.upsertTag(user, tag), }; } diff --git a/x-pack/plugins/beats/server/lib/domains/tokens.ts b/x-pack/plugins/beats/server/lib/domains/tokens.ts index b2a9d283e484a..6cfb922c34d57 100644 --- a/x-pack/plugins/beats/server/lib/domains/tokens.ts +++ b/x-pack/plugins/beats/server/lib/domains/tokens.ts @@ -3,13 +3,13 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - import { timingSafeEqual } from 'crypto'; import { sign as signToken, verify as verifyToken } from 'jsonwebtoken'; import moment from 'moment'; import uuid from 'uuid'; -import { CMTokensAdapter, FrameworkRequest } from '../lib'; -import { BackendFrameworkAdapter } from '../lib'; +import { BackendFrameworkAdapter } from '../adapters/framework/adapter_types'; +import { CMTokensAdapter } from '../adapters/tokens/adapter_types'; +import { FrameworkUser } from './../adapters/framework/adapter_types'; const RANDOM_TOKEN_1 = 'b48c4bda384a40cb91c6eb9b8849e77f'; const RANDOM_TOKEN_2 = '80a3819e3cd64f4399f1d4886be7a08b'; @@ -120,7 +120,7 @@ export class CMTokensDomain { } public async createEnrollmentTokens( - req: FrameworkRequest, + user: FrameworkUser, numTokens: number = 1 ): Promise { const tokens = []; @@ -139,7 +139,7 @@ export class CMTokensDomain { }); } - await this.adapter.upsertTokens(req, tokens); + await this.adapter.upsertTokens(user, tokens); return tokens.map(token => token.token); } diff --git a/x-pack/plugins/beats/server/lib/lib.ts b/x-pack/plugins/beats/server/lib/lib.ts index 6aab0acd733d8..d916c18aa4e4a 100644 --- a/x-pack/plugins/beats/server/lib/lib.ts +++ b/x-pack/plugins/beats/server/lib/lib.ts @@ -4,14 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouteAdditionalConfigurationOptions, IStrictReply } from 'hapi'; -import { internalFrameworkRequest } from '../utils/wrap_request'; +import { DatabaseAdapter } from './adapters/database/adapter_types'; +import { BackendFrameworkAdapter } from './adapters/framework/adapter_types'; import { CMBeatsDomain } from './domains/beats'; import { CMTagsDomain } from './domains/tags'; import { CMTokensDomain } from './domains/tokens'; -import { ConfigurationBlockTypes } from '../../common/constants'; - export interface CMDomainLibs { beats: CMBeatsDomain; tags: CMTagsDomain; @@ -20,195 +18,5 @@ export interface CMDomainLibs { export interface CMServerLibs extends CMDomainLibs { framework: BackendFrameworkAdapter; -} - -interface CMReturnedTagAssignment { - status: number | null; - result?: string; -} - -export interface CMAssignmentReturn { - assignments: CMReturnedTagAssignment[]; -} - -export interface CMRemovalReturn { - removals: CMReturnedTagAssignment[]; -} - -export interface ConfigurationBlock { - type: ConfigurationBlockTypes; - block_yml: string; -} - -export interface CMBeat { - id: string; - access_token: string; - verified_on?: string; - type: string; - version?: string; - host_ip: string; - host_name: string; - ephemeral_id?: string; - local_configuration_yml?: string; - tags?: string[]; - central_configuration_yml?: string; - metadata?: {}; -} - -export interface BeatTag { - id: string; - configuration_blocks: ConfigurationBlock[]; -} - -export interface EnrollmentToken { - token: string | null; - expires_on: string; -} - -export interface CMTokensAdapter { - deleteEnrollmentToken(enrollmentToken: string): Promise; - getEnrollmentToken(enrollmentToken: string): Promise; - upsertTokens( - req: FrameworkRequest, - tokens: EnrollmentToken[] - ): Promise; -} - -// FIXME: fix getTagsWithIds return type -export interface CMTagsAdapter { - getTagsWithIds(req: FrameworkRequest, tagIds: string[]): any; - upsertTag(req: FrameworkRequest, tag: BeatTag): Promise<{}>; -} - -// FIXME: fix getBeatsWithIds return type -export interface CMBeatsAdapter { - insert(beat: CMBeat): Promise; - update(beat: CMBeat): Promise; - get(id: string): any; - getAll(req: FrameworkRequest): any; - getWithIds(req: FrameworkRequest, beatIds: string[]): any; - verifyBeats(req: FrameworkRequest, beatIds: string[]): any; - removeTagsFromBeats( - req: FrameworkRequest, - removals: CMTagAssignment[] - ): Promise; - assignTagsToBeats( - req: FrameworkRequest, - assignments: CMTagAssignment[] - ): Promise; -} - -export interface CMTagAssignment { - beatId: string; - tag: string; - idxInRequest?: number; -} - -/** - * The following are generic types, sharable between projects - */ - -export interface BackendFrameworkAdapter { - version: string; - getSetting(settingPath: string): any; - exposeStaticDir(urlPath: string, dir: string): void; - installIndexTemplate(name: string, template: {}): void; - registerRoute( - route: FrameworkRouteOptions - ): void; - callWithInternalUser(esMethod: string, options: {}): Promise; - callWithRequest( - req: FrameworkRequest, - method: 'search', - options?: object - ): Promise>; - callWithRequest( - req: FrameworkRequest, - method: 'fieldCaps', - options?: object - ): Promise; - callWithRequest( - req: FrameworkRequest, - method: string, - options?: object - ): Promise; -} - -interface DatabaseFieldCapsResponse extends DatabaseResponse { - fields: FieldsResponse; -} - -export interface FieldsResponse { - [name: string]: FieldDef; -} - -export interface FieldDetails { - searchable: boolean; - aggregatable: boolean; - type: string; -} - -export interface FieldDef { - [type: string]: FieldDetails; -} - -export interface FrameworkRequest< - InternalRequest extends WrappableRequest = WrappableRequest -> { - [internalFrameworkRequest]: InternalRequest; - headers: InternalRequest['headers']; - info: InternalRequest['info']; - payload: InternalRequest['payload']; - params: InternalRequest['params']; - query: InternalRequest['query']; -} - -export interface FrameworkRouteOptions< - RouteRequest extends WrappableRequest, - RouteResponse -> { - path: string; - method: string | string[]; - vhost?: string; - handler: FrameworkRouteHandler; - config?: Pick< - IRouteAdditionalConfigurationOptions, - Exclude - >; -} - -export type FrameworkRouteHandler< - RouteRequest extends WrappableRequest, - RouteResponse -> = ( - request: FrameworkRequest, - reply: IStrictReply -) => void; - -export interface WrappableRequest< - Payload = any, - Params = any, - Query = any, - Headers = any, - Info = any -> { - headers: Headers; - info: Info; - payload: Payload; - params: Params; - query: Query; -} - -interface DatabaseResponse { - took: number; - timeout: boolean; -} - -interface DatabaseSearchResponse - extends DatabaseResponse { - aggregations?: Aggregations; - hits: { - total: number; - hits: Hit[]; - }; + database: DatabaseAdapter; } diff --git a/x-pack/plugins/beats/server/management_server.ts b/x-pack/plugins/beats/server/management_server.ts index ed0917eda8ced..637da2e37bd07 100644 --- a/x-pack/plugins/beats/server/management_server.ts +++ b/x-pack/plugins/beats/server/management_server.ts @@ -17,7 +17,10 @@ import { createTokensRoute } from './rest_api/tokens/create'; import { beatsIndexTemplate } from './utils/index_templates'; export const initManagementServer = (libs: CMServerLibs) => { - libs.framework.installIndexTemplate('beats-template', beatsIndexTemplate); + libs.database.putTemplate(libs.framework.internalUser, { + name: 'beats-template', + body: beatsIndexTemplate, + }); libs.framework.registerRoute(createTagAssignmentsRoute(libs)); libs.framework.registerRoute(createListAgentsRoute(libs)); diff --git a/x-pack/plugins/beats/server/rest_api/beats/enroll.ts b/x-pack/plugins/beats/server/rest_api/beats/enroll.ts index c86e5272e1e23..d909b64810360 100644 --- a/x-pack/plugins/beats/server/rest_api/beats/enroll.ts +++ b/x-pack/plugins/beats/server/rest_api/beats/enroll.ts @@ -7,6 +7,7 @@ import Joi from 'joi'; import { omit } from 'lodash'; import moment from 'moment'; +import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types'; import { CMServerLibs } from '../../lib/lib'; import { wrapEsError } from '../../utils/error_wrappers'; @@ -28,7 +29,7 @@ export const createBeatEnrollmentRoute = (libs: CMServerLibs) => ({ }).required(), }, }, - handler: async (request: any, reply: any) => { + handler: async (request: FrameworkRequest, reply: any) => { const { beatId } = request.params; const enrollmentToken = request.headers['kbn-beats-enrollment-token']; diff --git a/x-pack/plugins/beats/server/rest_api/beats/list.ts b/x-pack/plugins/beats/server/rest_api/beats/list.ts index 8263d1c0ff63f..a47bfcfbf3853 100644 --- a/x-pack/plugins/beats/server/rest_api/beats/list.ts +++ b/x-pack/plugins/beats/server/rest_api/beats/list.ts @@ -4,14 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ +import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types'; import { CMServerLibs } from '../../lib/lib'; import { wrapEsError } from '../../utils/error_wrappers'; // TODO: add license check pre-hook export const createListAgentsRoute = (libs: CMServerLibs) => ({ - handler: async (request: any, reply: any) => { + handler: async (request: FrameworkRequest, reply: any) => { try { - const beats = await libs.beats.getAllBeats(request); + const beats = await libs.beats.getAllBeats(request.user); reply({ beats }); } catch (err) { // TODO move this to kibana route thing in adapter diff --git a/x-pack/plugins/beats/server/rest_api/beats/tag_assignment.ts b/x-pack/plugins/beats/server/rest_api/beats/tag_assignment.ts index d06c016ce6d12..a6654250d196f 100644 --- a/x-pack/plugins/beats/server/rest_api/beats/tag_assignment.ts +++ b/x-pack/plugins/beats/server/rest_api/beats/tag_assignment.ts @@ -5,6 +5,7 @@ */ import Joi from 'joi'; +import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types'; import { CMServerLibs } from '../../lib/lib'; import { wrapEsError } from '../../utils/error_wrappers'; @@ -23,7 +24,7 @@ export const createTagAssignmentsRoute = (libs: CMServerLibs) => ({ }).required(), }, }, - handler: async (request: any, reply: any) => { + handler: async (request: FrameworkRequest, reply: any) => { const { assignments } = request.payload; // TODO abstract or change API to keep beatId consistent @@ -34,7 +35,7 @@ export const createTagAssignmentsRoute = (libs: CMServerLibs) => ({ try { const response = await libs.beats.assignTagsToBeats( - request, + request.user, tweakedAssignments ); reply(response); diff --git a/x-pack/plugins/beats/server/rest_api/beats/tag_removal.ts b/x-pack/plugins/beats/server/rest_api/beats/tag_removal.ts index 4da33dbd50cfc..1b495b906a364 100644 --- a/x-pack/plugins/beats/server/rest_api/beats/tag_removal.ts +++ b/x-pack/plugins/beats/server/rest_api/beats/tag_removal.ts @@ -5,6 +5,7 @@ */ import Joi from 'joi'; +import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types'; import { CMServerLibs } from '../../lib/lib'; import { wrapEsError } from '../../utils/error_wrappers'; @@ -23,7 +24,7 @@ export const createTagRemovalsRoute = (libs: CMServerLibs) => ({ }).required(), }, }, - handler: async (request: any, reply: any) => { + handler: async (request: FrameworkRequest, reply: any) => { const { removals } = request.payload; // TODO abstract or change API to keep beatId consistent @@ -34,7 +35,7 @@ export const createTagRemovalsRoute = (libs: CMServerLibs) => ({ try { const response = await libs.beats.removeTagsFromBeats( - request, + request.user, tweakedRemovals ); reply(response); diff --git a/x-pack/plugins/beats/server/rest_api/beats/update.ts b/x-pack/plugins/beats/server/rest_api/beats/update.ts index 3683c02ca2ccb..c86cf74b0c744 100644 --- a/x-pack/plugins/beats/server/rest_api/beats/update.ts +++ b/x-pack/plugins/beats/server/rest_api/beats/update.ts @@ -5,6 +5,7 @@ */ import Joi from 'joi'; +import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types'; import { CMServerLibs } from '../../lib/lib'; import { wrapEsError } from '../../utils/error_wrappers'; @@ -32,7 +33,7 @@ export const createBeatUpdateRoute = (libs: CMServerLibs) => ({ }).required(), }, }, - handler: async (request: any, reply: any) => { + handler: async (request: FrameworkRequest, reply: any) => { const { beatId } = request.params; const accessToken = request.headers['kbn-beats-access-token']; const remoteAddress = request.info.remoteAddress; diff --git a/x-pack/plugins/beats/server/rest_api/beats/verify.ts b/x-pack/plugins/beats/server/rest_api/beats/verify.ts index 7dba7f4e20692..d15b0374f445f 100644 --- a/x-pack/plugins/beats/server/rest_api/beats/verify.ts +++ b/x-pack/plugins/beats/server/rest_api/beats/verify.ts @@ -5,6 +5,7 @@ */ import Joi from 'joi'; +import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types'; import { CMServerLibs } from '../../lib/lib'; import { wrapEsError } from '../../utils/error_wrappers'; @@ -23,7 +24,7 @@ export const createBeatVerificationRoute = (libs: CMServerLibs) => ({ }).required(), }, }, - handler: async (request: any, reply: any) => { + handler: async (request: FrameworkRequest, reply: any) => { const beats = [...request.payload.beats]; const beatIds = beats.map(beat => beat.id); @@ -32,7 +33,7 @@ export const createBeatVerificationRoute = (libs: CMServerLibs) => ({ verifiedBeatIds, alreadyVerifiedBeatIds, nonExistentBeatIds, - } = await libs.beats.verifyBeats(request, beatIds); + } = await libs.beats.verifyBeats(request.user, beatIds); // TODO calculation of status should be done in-lib, w/switch statement here beats.forEach(beat => { diff --git a/x-pack/plugins/beats/server/rest_api/tags/set.ts b/x-pack/plugins/beats/server/rest_api/tags/set.ts index 3f7e579bd91ae..bdc49ce62a9a9 100644 --- a/x-pack/plugins/beats/server/rest_api/tags/set.ts +++ b/x-pack/plugins/beats/server/rest_api/tags/set.ts @@ -7,6 +7,7 @@ import Joi from 'joi'; import { get, values } from 'lodash'; import { ConfigurationBlockTypes } from '../../../common/constants'; +import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types'; import { CMServerLibs } from '../../lib/lib'; import { wrapEsError } from '../../utils/error_wrappers'; @@ -30,7 +31,7 @@ export const createSetTagRoute = (libs: CMServerLibs) => ({ }).allow(null), }, }, - handler: async (request: any, reply: any) => { + handler: async (request: FrameworkRequest, reply: any) => { const configurationBlocks = get( request, 'payload.configuration_blocks', @@ -38,7 +39,7 @@ export const createSetTagRoute = (libs: CMServerLibs) => ({ ); try { const { isValid, result } = await libs.tags.saveTag( - request, + request.user, request.params.tag, configurationBlocks ); diff --git a/x-pack/plugins/beats/server/rest_api/tokens/create.ts b/x-pack/plugins/beats/server/rest_api/tokens/create.ts index b4f3e2c1a6246..9e20735d640e5 100644 --- a/x-pack/plugins/beats/server/rest_api/tokens/create.ts +++ b/x-pack/plugins/beats/server/rest_api/tokens/create.ts @@ -6,9 +6,9 @@ import Joi from 'joi'; import { get } from 'lodash'; +import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types'; import { CMServerLibs } from '../../lib/lib'; import { wrapEsError } from '../../utils/error_wrappers'; - // TODO: add license check pre-hook // TODO: write to Kibana audit log file const DEFAULT_NUM_TOKENS = 1; @@ -23,12 +23,12 @@ export const createTokensRoute = (libs: CMServerLibs) => ({ }).allow(null), }, }, - handler: async (request: any, reply: any) => { + handler: async (request: FrameworkRequest, reply: any) => { const numTokens = get(request, 'payload.num_tokens', DEFAULT_NUM_TOKENS); try { const tokens = await libs.tokens.createEnrollmentTokens( - request, + request.user, numTokens ); reply({ tokens }); diff --git a/x-pack/plugins/beats/server/utils/error_wrappers/wrap_es_error.test.js b/x-pack/plugins/beats/server/utils/error_wrappers/wrap_es_error.test.ts similarity index 73% rename from x-pack/plugins/beats/server/utils/error_wrappers/wrap_es_error.test.js rename to x-pack/plugins/beats/server/utils/error_wrappers/wrap_es_error.test.ts index de79815258f7a..5087bf3224c42 100644 --- a/x-pack/plugins/beats/server/utils/error_wrappers/wrap_es_error.test.js +++ b/x-pack/plugins/beats/server/utils/error_wrappers/wrap_es_error.test.ts @@ -4,12 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import expect from 'expect.js'; import { wrapEsError } from './wrap_es_error'; describe('wrap_es_error', () => { describe('#wrapEsError', () => { - let originalError; + let originalError: any; beforeEach(() => { originalError = new Error('I am an error'); originalError.statusCode = 404; @@ -18,23 +17,26 @@ describe('wrap_es_error', () => { it('should return a Boom object', () => { const wrappedError = wrapEsError(originalError); - expect(wrappedError.isBoom).to.be(true); + expect(wrappedError.isBoom).toEqual(true); }); it('should return the correct Boom object', () => { const wrappedError = wrapEsError(originalError); - expect(wrappedError.output.statusCode).to.be(originalError.statusCode); - expect(wrappedError.output.payload.message).to.be(originalError.message); + expect(wrappedError.output.statusCode).toEqual(originalError.statusCode); + expect(wrappedError.output.payload.message).toEqual( + originalError.message + ); }); it('should return invalid permissions message for 403 errors', () => { const securityError = new Error('I am an error'); + // @ts-ignore securityError.statusCode = 403; const wrappedError = wrapEsError(securityError); - expect(wrappedError.isBoom).to.be(true); - expect(wrappedError.message).to.be( + expect(wrappedError.isBoom).toEqual(true); + expect(wrappedError.message).toEqual( 'Insufficient user permissions for managing Beats configuration' ); }); diff --git a/x-pack/plugins/beats/server/utils/wrap_request.ts b/x-pack/plugins/beats/server/utils/wrap_request.ts index a29f9055f3688..66bdbf578e141 100644 --- a/x-pack/plugins/beats/server/utils/wrap_request.ts +++ b/x-pack/plugins/beats/server/utils/wrap_request.ts @@ -4,17 +4,29 @@ * you may not use this file except in compliance with the Elastic License. */ -import { FrameworkRequest, WrappableRequest } from '../lib/lib'; +import { + FrameworkRequest, + FrameworkWrappableRequest, +} from '../lib/adapters/framework/adapter_types'; -export const internalFrameworkRequest = Symbol('internalFrameworkRequest'); +export const internalAuthData = Symbol('internalAuthData'); -export function wrapRequest( +export function wrapRequest( req: InternalRequest ): FrameworkRequest { const { params, payload, query, headers, info } = req; + const isAuthenticated = headers.authorization != null; + return { - [internalFrameworkRequest]: req, + user: isAuthenticated + ? { + kind: 'authenticated', + [internalAuthData]: headers, + } + : { + kind: 'unauthenticated', + }, headers, info, params, diff --git a/x-pack/plugins/beats/wallaby.js b/x-pack/plugins/beats/wallaby.js index 8c0c4aa355925..79b1438a5374d 100644 --- a/x-pack/plugins/beats/wallaby.js +++ b/x-pack/plugins/beats/wallaby.js @@ -4,10 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ const path = require('path'); -process.env.NODE_PATH = path.join(__dirname, '..', '..', 'node_modules'); +process.env.NODE_PATH += + path.delimiter + path.join(__dirname, '..', '..', '..', 'node_modules'); module.exports = function (wallaby) { return { + hints: { + commentAutoLog: 'testOutputWith:', + }, debug: true, files: [ './tsconfig.json', @@ -39,6 +43,7 @@ module.exports = function (wallaby) { '..', '..' ); + wallaby.testFramework.configure({ rootDir: wallaby.localProjectDir, moduleNameMapper: { @@ -56,7 +61,6 @@ module.exports = function (wallaby) { ], transform: { '^.+\\.js$': `${kibanaDirectory}/src/dev/jest/babel_transform.js`, - //"^.+\\.tsx?$": `${kibanaDirectory}/src/dev/jest/ts_transform.js`, }, }); }, diff --git a/x-pack/yarn.lock b/x-pack/yarn.lock index 238bac75499ea..42080936ded39 100644 --- a/x-pack/yarn.lock +++ b/x-pack/yarn.lock @@ -148,12 +148,6 @@ dependencies: "@types/node" "*" -"@types/hapi@15.0.1": - version "15.0.1" - resolved "https://registry.yarnpkg.com/@types/hapi/-/hapi-15.0.1.tgz#919e1d3a9160a080c9fdefaccc892239772e1258" - dependencies: - "@types/node" "*" - "@types/is-stream@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@types/is-stream/-/is-stream-1.1.0.tgz#b84d7bb207a210f2af9bed431dc0fbe9c4143be1" From 13d212d01f187b907a74c2c40c06c5e0af3a90bf Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Fri, 13 Jul 2018 11:32:10 -0400 Subject: [PATCH 20/94] [Beats Management] add get beat endpoint (#20603) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Beats Management] Move tokens to use JWT, add more complete test suite (#20317) * inital effort to move to JWT and added jest based tests on libs * assign beats tests all passing * token tests now pass * add more tests * all tests now green * fix broken test, this is beats CM not logstash 😊 * added readme * move enrollment token back to a hash * remove un-needed comment * alias lodash get to avoid confusion * isolated hash creation * inital effort to move to JWT and added jest based tests on libs * assign beats tests all passing * token tests now pass * add more tests * all tests now green * move enrollment token back to a hash * remove un-needed comment * alias lodash get to avoid confusion * isolated hash creation * Add initial efforts for backend framework adapter testing * move ES code to a DatabaseAdapter from BackendAdapter and add a TON of types for ES * re-typed * renamed types to match pattern * aditional renames * adapter tests should always just use adapterSetup(); * database now uses InternalRequest * corrected spelling of framework * fix typings * remove CRUFT * RequestOrInternal * Dont pass around request objects everywhere, just pass the user. Also, removed hapi types as they were not compatible * fix tests, add test, removed extra comment * Moved critical path code from route, to more easeley tested domain * fix auth * remove beat verification, added get beat endpoint to return configs * fix type * update createGetBeatConfigurationRoute URL * rename method * update to match PR #20566 * updated lock file * fix bad merge * update TSLinting --- .../plugins/beats/common/constants/index.ts | 5 +- x-pack/plugins/beats/index.ts | 22 +-- x-pack/plugins/beats/readme.md | 10 +- .../beats/elasticsearch_beats_adapter.ts | 29 ++- .../adapters/beats/memory_beats_adapter.ts | 36 ++-- .../database/__tests__/kibana.test.ts | 3 +- .../lib/adapters/database/adapter_types.ts | 34 +--- .../database/kibana_database_adapter.ts | 15 +- .../kibana/kibana_framework_adapter.ts | 99 ++++++++++ .../kibana/testing_framework_adapter.ts | 79 ++++++++ .../framework/__tests__/kibana.test.ts | 5 +- .../lib/adapters/framework/adapter_types.ts | 8 +- .../framework/kibana_framework_adapter.ts | 10 +- .../framework/testing_framework_adapter.ts | 13 +- .../lib/adapters/tokens/adapter_types.ts | 5 +- .../tokens/elasticsearch_tokens_adapter.ts | 31 +-- .../adapters/tokens/memory_tokens_adapter.ts | 17 +- .../beats/server/lib/compose/kibana.ts | 22 +-- .../__tests__/beats/assign_tags.test.ts | 8 +- .../domains/__tests__/beats/enroll.test.ts | 13 +- .../domains/__tests__/beats/verify.test.ts | 183 ------------------ .../lib/domains/__tests__/tokens.test.ts | 14 +- .../plugins/beats/server/lib/domains/beats.ts | 85 +++----- .../plugins/beats/server/lib/domains/tags.ts | 15 +- .../beats/server/lib/domains/tokens.ts | 25 +-- x-pack/plugins/beats/server/lib/lib.ts | 6 + .../plugins/beats/server/management_server.ts | 5 +- .../server/rest_api/beats/configuration.ts | 54 ++++++ .../beats/server/rest_api/beats/enroll.ts | 32 +-- .../server/rest_api/beats/tag_assignment.ts | 5 +- .../server/rest_api/beats/tag_removal.ts | 5 +- .../beats/server/rest_api/beats/update.ts | 2 - .../beats/server/rest_api/beats/verify.ts | 63 ------ .../plugins/beats/server/rest_api/tags/set.ts | 6 +- .../beats/server/rest_api/tokens/create.ts | 5 +- .../error_wrappers/wrap_es_error.test.ts | 4 +- .../utils/error_wrappers/wrap_es_error.ts | 4 +- .../server/utils/find_non_existent_items.ts | 19 +- x-pack/plugins/beats/wallaby.js | 8 +- .../error_wrappers/__tests__/wrap_es_error.js | 41 ---- .../api_integration/apis/beats/enroll_beat.js | 2 +- .../api_integration/apis/beats/get_beat.js | 51 +++++ .../test/api_integration/apis/beats/index.js | 4 +- .../api_integration/apis/beats/update_beat.js | 30 --- .../apis/beats/verify_beats.js | 66 ------- .../es_archives/beats/list/data.json | 139 +++++++++++++ .../es_archives/beats/list/data.json.gz | Bin 521 -> 0 bytes .../es_archives/beats/list/mappings.json | 5 +- x-pack/yarn.lock | 4 + 49 files changed, 600 insertions(+), 746 deletions(-) create mode 100644 x-pack/plugins/beats/server/lib/adapters/famework/kibana/kibana_framework_adapter.ts create mode 100644 x-pack/plugins/beats/server/lib/adapters/famework/kibana/testing_framework_adapter.ts delete mode 100644 x-pack/plugins/beats/server/lib/domains/__tests__/beats/verify.test.ts create mode 100644 x-pack/plugins/beats/server/rest_api/beats/configuration.ts delete mode 100644 x-pack/plugins/beats/server/rest_api/beats/verify.ts delete mode 100644 x-pack/plugins/logstash/server/lib/error_wrappers/__tests__/wrap_es_error.js create mode 100644 x-pack/test/api_integration/apis/beats/get_beat.js delete mode 100644 x-pack/test/api_integration/apis/beats/verify_beats.js create mode 100644 x-pack/test/functional/es_archives/beats/list/data.json delete mode 100644 x-pack/test/functional/es_archives/beats/list/data.json.gz diff --git a/x-pack/plugins/beats/common/constants/index.ts b/x-pack/plugins/beats/common/constants/index.ts index 4662865e208a7..756ffcf07e3ea 100644 --- a/x-pack/plugins/beats/common/constants/index.ts +++ b/x-pack/plugins/beats/common/constants/index.ts @@ -6,7 +6,4 @@ export { PLUGIN } from './plugin'; export { INDEX_NAMES } from './index_names'; -export { - UNIQUENESS_ENFORCING_TYPES, - ConfigurationBlockTypes, -} from './configuration_blocks'; +export { UNIQUENESS_ENFORCING_TYPES, ConfigurationBlockTypes } from './configuration_blocks'; diff --git a/x-pack/plugins/beats/index.ts b/x-pack/plugins/beats/index.ts index 25be5728c93bb..ced89c186f73e 100644 --- a/x-pack/plugins/beats/index.ts +++ b/x-pack/plugins/beats/index.ts @@ -10,20 +10,18 @@ import { initServerWithKibana } from './server/kibana.index'; const DEFAULT_ENROLLMENT_TOKENS_TTL_S = 10 * 60; // 10 minutes -export const config = Joi.object({ - enabled: Joi.boolean().default(true), - encryptionKey: Joi.string(), - enrollmentTokensTtlInSeconds: Joi.number() - .integer() - .min(1) - .default(DEFAULT_ENROLLMENT_TOKENS_TTL_S), -}).default(); -export const configPrefix = 'xpack.beats'; - export function beats(kibana: any) { return new kibana.Plugin({ - config: () => config, - configPrefix, + config: () => + Joi.object({ + enabled: Joi.boolean().default(true), + encryptionKey: Joi.string(), + enrollmentTokensTtlInSeconds: Joi.number() + .integer() + .min(1) + .default(DEFAULT_ENROLLMENT_TOKENS_TTL_S), + }).default(), + configPrefix: 'xpack.beats', id: PLUGIN.ID, require: ['kibana', 'elasticsearch', 'xpack_main'], init(server: any) { diff --git a/x-pack/plugins/beats/readme.md b/x-pack/plugins/beats/readme.md index 725fe587c5aa8..fdd56a393e573 100644 --- a/x-pack/plugins/beats/readme.md +++ b/x-pack/plugins/beats/readme.md @@ -1,15 +1,7 @@ # Documentation for Beats CM in x-pack kibana -### Run tests (from x-pack dir) - -Functional tests +### Run tests ``` node scripts/jest.js plugins/beats --watch ``` - -Functional API tests - -``` -node scripts/functional_tests --config test/api_integration/config -``` diff --git a/x-pack/plugins/beats/server/lib/adapters/beats/elasticsearch_beats_adapter.ts b/x-pack/plugins/beats/server/lib/adapters/beats/elasticsearch_beats_adapter.ts index e2321ac6739a8..c0be74e1629f6 100644 --- a/x-pack/plugins/beats/server/lib/adapters/beats/elasticsearch_beats_adapter.ts +++ b/x-pack/plugins/beats/server/lib/adapters/beats/elasticsearch_beats_adapter.ts @@ -30,10 +30,7 @@ export class ElasticsearchBeatsAdapter implements CMBeatsAdapter { type: '_doc', }; - const response = await this.database.get( - this.framework.internalUser, - params - ); + const response = await this.database.get(this.framework.internalUser, params); if (!response.found) { return null; } @@ -155,13 +152,11 @@ export class ElasticsearchBeatsAdapter implements CMBeatsAdapter { refresh: 'wait_for', type: '_doc', }); - return _get(response, 'items', []).map( - (item: any, resultIdx: number) => ({ - idxInRequest: removals[resultIdx].idxInRequest, - result: item.update.result, - status: item.update.status, - }) - ); + return _get(response, 'items', []).map((item: any, resultIdx: number) => ({ + idxInRequest: removals[resultIdx].idxInRequest, + result: item.update.result, + status: item.update.status, + })); } public async assignTagsToBeats( @@ -193,12 +188,10 @@ export class ElasticsearchBeatsAdapter implements CMBeatsAdapter { refresh: 'wait_for', type: '_doc', }); - return _get(response, 'items', []).map( - (item: any, resultIdx: any) => ({ - idxInRequest: assignments[resultIdx].idxInRequest, - result: item.update.result, - status: item.update.status, - }) - ); + return _get(response, 'items', []).map((item: any, resultIdx: any) => ({ + idxInRequest: assignments[resultIdx].idxInRequest, + result: item.update.result, + status: item.update.status, + })); } } diff --git a/x-pack/plugins/beats/server/lib/adapters/beats/memory_beats_adapter.ts b/x-pack/plugins/beats/server/lib/adapters/beats/memory_beats_adapter.ts index a904b1d6831cd..041b34d29b49e 100644 --- a/x-pack/plugins/beats/server/lib/adapters/beats/memory_beats_adapter.ts +++ b/x-pack/plugins/beats/server/lib/adapters/beats/memory_beats_adapter.ts @@ -19,7 +19,7 @@ export class MemoryBeatsAdapter implements CMBeatsAdapter { } public async get(id: string) { - return this.beatsDB.find(beat => beat.id === id); + return this.beatsDB.find(beat => beat.id === id) || null; } public async insert(beat: CMBeat) { @@ -65,17 +65,15 @@ export class MemoryBeatsAdapter implements CMBeatsAdapter { ): Promise { const beatIds = removals.map(r => r.beatId); - const response = this.beatsDB - .filter(beat => beatIds.includes(beat.id)) - .map(beat => { - const tagData = removals.find(r => r.beatId === beat.id); - if (tagData) { - if (beat.tags) { - beat.tags = beat.tags.filter(tag => tag !== tagData.tag); - } + const response = this.beatsDB.filter(beat => beatIds.includes(beat.id)).map(beat => { + const tagData = removals.find(r => r.beatId === beat.id); + if (tagData) { + if (beat.tags) { + beat.tags = beat.tags.filter(tag => tag !== tagData.tag); } - return beat; - }); + } + return beat; + }); return response.map((item: CMBeat, resultIdx: number) => ({ idxInRequest: removals[resultIdx].idxInRequest, @@ -100,9 +98,7 @@ export class MemoryBeatsAdapter implements CMBeatsAdapter { if (!beat.tags) { beat.tags = []; } - const nonExistingTags = tags.filter( - (t: string) => beat.tags && !beat.tags.includes(t) - ); + const nonExistingTags = tags.filter((t: string) => beat.tags && !beat.tags.includes(t)); if (nonExistingTags.length > 0) { beat.tags = beat.tags.concat(nonExistingTags); @@ -111,12 +107,10 @@ export class MemoryBeatsAdapter implements CMBeatsAdapter { return beat; }); - return assignments.map( - (item: BeatsTagAssignment, resultIdx: number) => ({ - idxInRequest: assignments[resultIdx].idxInRequest, - result: 'updated', - status: 200, - }) - ); + return assignments.map((item: BeatsTagAssignment, resultIdx: number) => ({ + idxInRequest: assignments[resultIdx].idxInRequest, + result: 'updated', + status: 200, + })); } } diff --git a/x-pack/plugins/beats/server/lib/adapters/database/__tests__/kibana.test.ts b/x-pack/plugins/beats/server/lib/adapters/database/__tests__/kibana.test.ts index 19bf05c3c777e..c18c5b5bc4c76 100644 --- a/x-pack/plugins/beats/server/lib/adapters/database/__tests__/kibana.test.ts +++ b/x-pack/plugins/beats/server/lib/adapters/database/__tests__/kibana.test.ts @@ -28,7 +28,6 @@ contractTests('Kibana Database Adapter', { return await es.cleanup(); }, adapterSetup: () => { - return new KibanaDatabaseAdapter(kbnServer.server.plugins - .elasticsearch as DatabaseKbnESPlugin); + return new KibanaDatabaseAdapter(kbnServer.server.plugins.elasticsearch as DatabaseKbnESPlugin); }, }); diff --git a/x-pack/plugins/beats/server/lib/adapters/database/adapter_types.ts b/x-pack/plugins/beats/server/lib/adapters/database/adapter_types.ts index 36b5a35742bc9..22ee7a34066b3 100644 --- a/x-pack/plugins/beats/server/lib/adapters/database/adapter_types.ts +++ b/x-pack/plugins/beats/server/lib/adapters/database/adapter_types.ts @@ -5,10 +5,7 @@ */ import { FrameworkRequest, FrameworkUser } from '../framework/adapter_types'; export interface DatabaseAdapter { - putTemplate( - user: FrameworkUser, - params: DatabasePutTemplateParams - ): Promise; + putTemplate(user: FrameworkUser, params: DatabasePutTemplateParams): Promise; get( user: FrameworkUser, params: DatabaseGetParams @@ -25,27 +22,17 @@ export interface DatabaseAdapter { user: FrameworkUser, params: DatabaseDeleteDocumentParams ): Promise; - mget( - user: FrameworkUser, - params: DatabaseMGetParams - ): Promise>; + mget(user: FrameworkUser, params: DatabaseMGetParams): Promise>; bulk( user: FrameworkUser, params: DatabaseBulkIndexDocumentsParams ): Promise; - search( - user: FrameworkUser, - params: DatabaseSearchParams - ): Promise>; + search(user: FrameworkUser, params: DatabaseSearchParams): Promise>; } export interface DatabaseKbnESCluster { callWithInternalUser(esMethod: string, options: {}): Promise; - callWithRequest( - req: FrameworkRequest, - esMethod: string, - options: {} - ): Promise; + callWithRequest(req: FrameworkRequest, esMethod: string, options: {}): Promise; } export interface DatabaseKbnESPlugin { @@ -142,14 +129,11 @@ export interface DatabaseBulkResponse { took: number; errors: boolean; items: Array< - | DatabaseDeleteDocumentResponse - | DatabaseIndexDocumentResponse - | DatabaseUpdateDocumentResponse + DatabaseDeleteDocumentResponse | DatabaseIndexDocumentResponse | DatabaseUpdateDocumentResponse >; } -export interface DatabaseBulkIndexDocumentsParams - extends DatabaseGenericParams { +export interface DatabaseBulkIndexDocumentsParams extends DatabaseGenericParams { waitForActiveShards?: string; refresh?: DatabaseRefresh; routing?: string; @@ -299,11 +283,7 @@ export interface DatabaseGetParams extends DatabaseGenericParams { export type DatabaseNameList = string | string[] | boolean; export type DatabaseRefresh = boolean | 'true' | 'false' | 'wait_for' | ''; -export type DatabaseVersionType = - | 'internal' - | 'external' - | 'external_gte' - | 'force'; +export type DatabaseVersionType = 'internal' | 'external' | 'external_gte' | 'force'; export type ExpandWildcards = 'open' | 'closed' | 'none' | 'all'; export type DefaultOperator = 'AND' | 'OR'; export type DatabaseConflicts = 'abort' | 'proceed'; diff --git a/x-pack/plugins/beats/server/lib/adapters/database/kibana_database_adapter.ts b/x-pack/plugins/beats/server/lib/adapters/database/kibana_database_adapter.ts index 6c4d96446abd4..ba593a793e34b 100644 --- a/x-pack/plugins/beats/server/lib/adapters/database/kibana_database_adapter.ts +++ b/x-pack/plugins/beats/server/lib/adapters/database/kibana_database_adapter.ts @@ -30,10 +30,7 @@ export class KibanaDatabaseAdapter implements DatabaseAdapter { constructor(kbnElasticSearch: DatabaseKbnESPlugin) { this.es = kbnElasticSearch.getCluster('admin'); } - public async putTemplate( - user: FrameworkUser, - params: DatabasePutTemplateParams - ): Promise { + public async putTemplate(user: FrameworkUser, params: DatabasePutTemplateParams): Promise { const callES = this.getCallType(user); const result = await callES('indices.putTemplate', params); return result; @@ -59,10 +56,7 @@ export class KibanaDatabaseAdapter implements DatabaseAdapter { // todo } - public async bulk( - user: FrameworkUser, - params: DatabaseBulkIndexDocumentsParams - ): Promise { + public async bulk(user: FrameworkUser, params: DatabaseBulkIndexDocumentsParams): Promise { const callES = this.getCallType(user); const result = await callES('bulk', params); return result; @@ -76,10 +70,7 @@ export class KibanaDatabaseAdapter implements DatabaseAdapter { const result = await callES('create', params); return result; } - public async index( - user: FrameworkUser, - params: DatabaseIndexDocumentParams - ): Promise { + public async index(user: FrameworkUser, params: DatabaseIndexDocumentParams): Promise { const callES = this.getCallType(user); const result = await callES('index', params); return result; diff --git a/x-pack/plugins/beats/server/lib/adapters/famework/kibana/kibana_framework_adapter.ts b/x-pack/plugins/beats/server/lib/adapters/famework/kibana/kibana_framework_adapter.ts new file mode 100644 index 0000000000000..71703da8632ec --- /dev/null +++ b/x-pack/plugins/beats/server/lib/adapters/famework/kibana/kibana_framework_adapter.ts @@ -0,0 +1,99 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + BackendFrameworkAdapter, + FrameworkRequest, + FrameworkRouteOptions, + WrappableRequest, +} from '../../../lib'; + +import { IStrictReply, Request, Server } from 'hapi'; +import { internalFrameworkRequest, wrapRequest } from '../../../../utils/wrap_request'; + +export class KibanaBackendFrameworkAdapter implements BackendFrameworkAdapter { + public version: string; + private server: Server; + private cryptoHash: string | null; + + constructor(hapiServer: Server) { + this.server = hapiServer; + this.version = hapiServer.plugins.kibana.status.plugin.version; + this.cryptoHash = null; + + this.validateConfig(); + } + + public getSetting(settingPath: string) { + // TODO type check server properly + if (settingPath === 'xpack.beats.encryptionKey') { + // @ts-ignore + return this.server.config().get(settingPath) || this.cryptoHash; + } + // @ts-ignore + return this.server.config().get(settingPath) || this.cryptoHash; + } + + public exposeStaticDir(urlPath: string, dir: string): void { + this.server.route({ + handler: { + directory: { + path: dir, + }, + }, + method: 'GET', + path: urlPath, + }); + } + + public registerRoute( + route: FrameworkRouteOptions + ) { + const wrappedHandler = (request: any, reply: IStrictReply) => + route.handler(wrapRequest(request), reply); + + this.server.route({ + config: route.config, + handler: wrappedHandler, + method: route.method, + path: route.path, + }); + } + + public installIndexTemplate(name: string, template: {}) { + return this.callWithInternalUser('indices.putTemplate', { + body: template, + name, + }); + } + + public async callWithInternalUser(esMethod: string, options: {}) { + const { elasticsearch } = this.server.plugins; + const { callWithInternalUser } = elasticsearch.getCluster('admin'); + return await callWithInternalUser(esMethod, options); + } + + public async callWithRequest(req: FrameworkRequest, ...rest: any[]) { + const internalRequest = req[internalFrameworkRequest]; + const { elasticsearch } = internalRequest.server.plugins; + const { callWithRequest } = elasticsearch.getCluster('data'); + const fields = await callWithRequest(internalRequest, ...rest); + return fields; + } + + private validateConfig() { + // @ts-ignore + const config = this.server.config(); + const encryptionKey = config.get('xpack.beats.encryptionKey'); + + if (!encryptionKey) { + this.server.log( + 'Using a default encryption key for xpack.beats.encryptionKey. It is recommended that you set xpack.beats.encryptionKey in kibana.yml with a unique token' + ); + this.cryptoHash = 'xpack_beats_default_encryptionKey'; + } + } +} diff --git a/x-pack/plugins/beats/server/lib/adapters/famework/kibana/testing_framework_adapter.ts b/x-pack/plugins/beats/server/lib/adapters/famework/kibana/testing_framework_adapter.ts new file mode 100644 index 0000000000000..757464fa6cdc7 --- /dev/null +++ b/x-pack/plugins/beats/server/lib/adapters/famework/kibana/testing_framework_adapter.ts @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Client } from 'elasticsearch'; +import { Request } from 'hapi'; +import { get } from 'lodash'; +import { + BackendFrameworkAdapter, + FrameworkRequest, + FrameworkRouteOptions, + WrappableRequest, +} from '../../../lib'; + +interface TestSettings { + enrollmentTokensTtlInSeconds: number; + encryptionKey: string; +} + +export class TestingBackendFrameworkAdapter implements BackendFrameworkAdapter { + public version: string; + private client: Client | null; + private settings: TestSettings; + + constructor(client: Client | null, settings: TestSettings) { + this.client = client; + this.settings = settings || { + encryptionKey: 'something_who_cares', + enrollmentTokensTtlInSeconds: 10 * 60, // 10 minutes + }; + this.version = 'testing'; + } + + public getSetting(settingPath: string) { + switch (settingPath) { + case 'xpack.beats.enrollmentTokensTtlInSeconds': + return this.settings.enrollmentTokensTtlInSeconds; + case 'xpack.beats.encryptionKey': + return this.settings.encryptionKey; + } + } + + public exposeStaticDir(urlPath: string, dir: string): void { + // not yet testable + } + + public registerRoute( + route: FrameworkRouteOptions + ) { + // not yet testable + } + + public installIndexTemplate(name: string, template: {}) { + if (this.client) { + return this.client.indices.putTemplate({ + body: template, + name, + }); + } + } + + public async callWithInternalUser(esMethod: string, options: {}) { + const api = get(this.client, esMethod); + + api(options); + + return await api(options); + } + + public async callWithRequest(req: FrameworkRequest, esMethod: string, options: {}) { + const api = get(this.client, esMethod); + + api(options); + + return await api(options); + } +} diff --git a/x-pack/plugins/beats/server/lib/adapters/framework/__tests__/kibana.test.ts b/x-pack/plugins/beats/server/lib/adapters/framework/__tests__/kibana.test.ts index c87a7374837e3..5a539ebe6e5e7 100644 --- a/x-pack/plugins/beats/server/lib/adapters/framework/__tests__/kibana.test.ts +++ b/x-pack/plugins/beats/server/lib/adapters/framework/__tests__/kibana.test.ts @@ -7,12 +7,9 @@ // @ts-ignore import { createEsTestCluster } from '@kbn/test'; +import { config as beatsPluginConfig, configPrefix } from '../../../../..'; // @ts-ignore import * as kbnTestServer from '../../../../../../../../src/test_utils/kbn_server'; -import { - config as beatsPluginConfig, - configPrefix, -} from '../../../../../index'; import { KibanaBackendFrameworkAdapter } from '../kibana_framework_adapter'; import { contractTests } from './test_contract'; diff --git a/x-pack/plugins/beats/server/lib/adapters/framework/adapter_types.ts b/x-pack/plugins/beats/server/lib/adapters/framework/adapter_types.ts index 4be3589a6f043..014c041b11f0a 100644 --- a/x-pack/plugins/beats/server/lib/adapters/framework/adapter_types.ts +++ b/x-pack/plugins/beats/server/lib/adapters/framework/adapter_types.ts @@ -54,10 +54,10 @@ export interface FrameworkRouteOptions< config?: {}; } -export type FrameworkRouteHandler< - RouteRequest extends FrameworkWrappableRequest, - RouteResponse -> = (request: FrameworkRequest, reply: any) => void; +export type FrameworkRouteHandler = ( + request: FrameworkRequest, + reply: any +) => void; export interface FrameworkWrappableRequest< Payload = any, diff --git a/x-pack/plugins/beats/server/lib/adapters/framework/kibana_framework_adapter.ts b/x-pack/plugins/beats/server/lib/adapters/framework/kibana_framework_adapter.ts index 7113baf5c26e6..d07170acb7050 100644 --- a/x-pack/plugins/beats/server/lib/adapters/framework/kibana_framework_adapter.ts +++ b/x-pack/plugins/beats/server/lib/adapters/framework/kibana_framework_adapter.ts @@ -53,12 +53,10 @@ export class KibanaBackendFrameworkAdapter implements BackendFrameworkAdapter { }); } - public registerRoute< - RouteRequest extends FrameworkWrappableRequest, - RouteResponse - >(route: FrameworkRouteOptions) { - const wrappedHandler = (request: any, reply: any) => - route.handler(wrapRequest(request), reply); + public registerRoute( + route: FrameworkRouteOptions + ) { + const wrappedHandler = (request: any, reply: any) => route.handler(wrapRequest(request), reply); this.server.route({ handler: wrappedHandler, diff --git a/x-pack/plugins/beats/server/lib/adapters/framework/testing_framework_adapter.ts b/x-pack/plugins/beats/server/lib/adapters/framework/testing_framework_adapter.ts index 3bffe274fba8d..3280220893892 100644 --- a/x-pack/plugins/beats/server/lib/adapters/framework/testing_framework_adapter.ts +++ b/x-pack/plugins/beats/server/lib/adapters/framework/testing_framework_adapter.ts @@ -49,10 +49,9 @@ export class TestingBackendFrameworkAdapter implements BackendFrameworkAdapter { // not yet testable } - public registerRoute< - RouteRequest extends FrameworkWrappableRequest, - RouteResponse - >(route: FrameworkRouteOptions) { + public registerRoute( + route: FrameworkRouteOptions + ) { // not yet testable } @@ -68,11 +67,7 @@ export class TestingBackendFrameworkAdapter implements BackendFrameworkAdapter { return await api(options); } - public async callWithRequest( - req: FrameworkRequest, - esMethod: string, - options: {} - ) { + public async callWithRequest(req: FrameworkRequest, esMethod: string, options: {}) { const api = get(this.client, esMethod); api(options); diff --git a/x-pack/plugins/beats/server/lib/adapters/tokens/adapter_types.ts b/x-pack/plugins/beats/server/lib/adapters/tokens/adapter_types.ts index 71fb719d9952a..2fe8c811c396e 100644 --- a/x-pack/plugins/beats/server/lib/adapters/tokens/adapter_types.ts +++ b/x-pack/plugins/beats/server/lib/adapters/tokens/adapter_types.ts @@ -13,8 +13,5 @@ export interface TokenEnrollmentData { export interface CMTokensAdapter { deleteEnrollmentToken(enrollmentToken: string): Promise; getEnrollmentToken(enrollmentToken: string): Promise; - upsertTokens( - user: FrameworkUser, - tokens: TokenEnrollmentData[] - ): Promise; + upsertTokens(user: FrameworkUser, tokens: TokenEnrollmentData[]): Promise; } diff --git a/x-pack/plugins/beats/server/lib/adapters/tokens/elasticsearch_tokens_adapter.ts b/x-pack/plugins/beats/server/lib/adapters/tokens/elasticsearch_tokens_adapter.ts index 6dbefce514052..6aa9ceff46629 100644 --- a/x-pack/plugins/beats/server/lib/adapters/tokens/elasticsearch_tokens_adapter.ts +++ b/x-pack/plugins/beats/server/lib/adapters/tokens/elasticsearch_tokens_adapter.ts @@ -7,10 +7,7 @@ import { flatten, get } from 'lodash'; import { INDEX_NAMES } from '../../../../common/constants'; import { DatabaseAdapter } from '../database/adapter_types'; -import { - BackendFrameworkAdapter, - FrameworkUser, -} from '../framework/adapter_types'; +import { BackendFrameworkAdapter, FrameworkUser } from '../framework/adapter_types'; import { CMTokensAdapter, TokenEnrollmentData } from './adapter_types'; export class ElasticsearchTokensAdapter implements CMTokensAdapter { @@ -32,9 +29,7 @@ export class ElasticsearchTokensAdapter implements CMTokensAdapter { await this.database.delete(this.framework.internalUser, params); } - public async getEnrollmentToken( - tokenString: string - ): Promise { + public async getEnrollmentToken(tokenString: string): Promise { const params = { id: `enrollment_token:${tokenString}`, ignore: [404], @@ -42,18 +37,11 @@ export class ElasticsearchTokensAdapter implements CMTokensAdapter { type: '_doc', }; - const response = await this.database.get( - this.framework.internalUser, - params - ); - const tokenDetails = get( - response, - '_source.enrollment_token', - { - expires_on: '0', - token: null, - } - ); + const response = await this.database.get(this.framework.internalUser, params); + const tokenDetails = get(response, '_source.enrollment_token', { + expires_on: '0', + token: null, + }); // Elasticsearch might return fast if the token is not found. OR it might return fast // if the token *is* found. Either way, an attacker could using a timing attack to figure @@ -65,10 +53,7 @@ export class ElasticsearchTokensAdapter implements CMTokensAdapter { ); } - public async upsertTokens( - user: FrameworkUser, - tokens: TokenEnrollmentData[] - ) { + public async upsertTokens(user: FrameworkUser, tokens: TokenEnrollmentData[]) { const body = flatten( tokens.map(token => [ { index: { _id: `enrollment_token:${token.token}` } }, diff --git a/x-pack/plugins/beats/server/lib/adapters/tokens/memory_tokens_adapter.ts b/x-pack/plugins/beats/server/lib/adapters/tokens/memory_tokens_adapter.ts index 1eed188d3f2e9..7cf132bb6ba31 100644 --- a/x-pack/plugins/beats/server/lib/adapters/tokens/memory_tokens_adapter.ts +++ b/x-pack/plugins/beats/server/lib/adapters/tokens/memory_tokens_adapter.ts @@ -15,31 +15,22 @@ export class MemoryTokensAdapter implements CMTokensAdapter { } public async deleteEnrollmentToken(enrollmentToken: string) { - const index = this.tokenDB.findIndex( - token => token.token === enrollmentToken - ); + const index = this.tokenDB.findIndex(token => token.token === enrollmentToken); if (index > -1) { this.tokenDB.splice(index, 1); } } - public async getEnrollmentToken( - tokenString: string - ): Promise { + public async getEnrollmentToken(tokenString: string): Promise { return new Promise(resolve => { return resolve(this.tokenDB.find(token => token.token === tokenString)); }); } - public async upsertTokens( - user: FrameworkAuthenticatedUser, - tokens: TokenEnrollmentData[] - ) { + public async upsertTokens(user: FrameworkAuthenticatedUser, tokens: TokenEnrollmentData[]) { tokens.forEach(token => { - const existingIndex = this.tokenDB.findIndex( - t => t.token === token.token - ); + const existingIndex = this.tokenDB.findIndex(t => t.token === token.token); if (existingIndex !== -1) { this.tokenDB[existingIndex] = token; } else { diff --git a/x-pack/plugins/beats/server/lib/compose/kibana.ts b/x-pack/plugins/beats/server/lib/compose/kibana.ts index 7c46f82b84bf3..685171669a887 100644 --- a/x-pack/plugins/beats/server/lib/compose/kibana.ts +++ b/x-pack/plugins/beats/server/lib/compose/kibana.ts @@ -5,9 +5,9 @@ */ import { ElasticsearchBeatsAdapter } from '../adapters/beats/elasticsearch_beats_adapter'; +import { KibanaDatabaseAdapter } from '../adapters/database/kibana_database_adapter'; import { ElasticsearchTagsAdapter } from '../adapters/tags/elasticsearch_tags_adapter'; import { ElasticsearchTokensAdapter } from '../adapters/tokens/elasticsearch_tokens_adapter'; -import { KibanaDatabaseAdapter } from './../adapters/database/kibana_database_adapter'; import { KibanaBackendFrameworkAdapter } from '../adapters/framework/kibana_framework_adapter'; @@ -22,19 +22,13 @@ export function compose(server: any): CMServerLibs { const database = new KibanaDatabaseAdapter(server.plugins.elasticsearch); const tags = new CMTagsDomain(new ElasticsearchTagsAdapter(database)); - const tokens = new CMTokensDomain( - new ElasticsearchTokensAdapter(database, framework), - { - framework, - } - ); - const beats = new CMBeatsDomain( - new ElasticsearchBeatsAdapter(database, framework), - { - tags, - tokens, - } - ); + const tokens = new CMTokensDomain(new ElasticsearchTokensAdapter(database, framework), { + framework, + }); + const beats = new CMBeatsDomain(new ElasticsearchBeatsAdapter(database, framework), { + tags, + tokens, + }); const domainLibs: CMDomainLibs = { beats, diff --git a/x-pack/plugins/beats/server/lib/domains/__tests__/beats/assign_tags.test.ts b/x-pack/plugins/beats/server/lib/domains/__tests__/beats/assign_tags.test.ts index f28fcda8004e4..c9f9f15256d43 100644 --- a/x-pack/plugins/beats/server/lib/domains/__tests__/beats/assign_tags.test.ts +++ b/x-pack/plugins/beats/server/lib/domains/__tests__/beats/assign_tags.test.ts @@ -101,9 +101,7 @@ describe('Beats Domain Lib', () => { { beatId: 'bar', tag: 'production' }, ]); - expect(apiResponse.assignments).toEqual([ - { status: 200, result: 'updated' }, - ]); + expect(apiResponse.assignments).toEqual([{ status: 200, result: 'updated' }]); }); it('should not re-add an existing tag to a beat', async () => { @@ -117,9 +115,7 @@ describe('Beats Domain Lib', () => { { beatId: 'foo', tag: 'production' }, ]); - expect(apiResponse.assignments).toEqual([ - { status: 200, result: 'updated' }, - ]); + expect(apiResponse.assignments).toEqual([{ status: 200, result: 'updated' }]); beat = beatsDB.find(b => b.id === 'foo') as any; expect(beat.tags).toEqual([...tags, 'qa']); diff --git a/x-pack/plugins/beats/server/lib/domains/__tests__/beats/enroll.test.ts b/x-pack/plugins/beats/server/lib/domains/__tests__/beats/enroll.test.ts index 1466e9ed926c7..f60c1ed0e009e 100644 --- a/x-pack/plugins/beats/server/lib/domains/__tests__/beats/enroll.test.ts +++ b/x-pack/plugins/beats/server/lib/domains/__tests__/beats/enroll.test.ts @@ -8,6 +8,7 @@ import { MemoryBeatsAdapter } from '../../../adapters/beats/memory_beats_adapter import { TestingBackendFrameworkAdapter } from '../../../adapters/framework/testing_framework_adapter'; import { MemoryTagsAdapter } from '../../../adapters/tags/memory_tags_adapter'; import { MemoryTokensAdapter } from '../../../adapters/tokens/memory_tokens_adapter'; +import { BeatEnrollmentStatus } from '../../../lib'; import { BeatTag, CMBeat } from '../../../../../common/domain_types'; import { TokenEnrollmentData } from '../../../adapters/tokens/adapter_types'; @@ -84,16 +85,16 @@ describe('Beats Domain Lib', () => { }); it('should enroll beat, returning an access token', async () => { - const { token } = await tokensLib.getEnrollmentToken( - validEnrollmentToken - ); + const { token } = await tokensLib.getEnrollmentToken(validEnrollmentToken); expect(token).toEqual(validEnrollmentToken); - const { accessToken } = await beatsLib.enrollBeat( + const { accessToken, status } = await beatsLib.enrollBeat( + validEnrollmentToken, beatId, '192.168.1.1', omit(beat, 'enrollment_token') ); + expect(status).toEqual(BeatEnrollmentStatus.Success); expect(beatsDB.length).toEqual(1); expect(beatsDB[0]).toHaveProperty('host_ip'); @@ -126,9 +127,7 @@ describe('Beats Domain Lib', () => { await tokensLib.deleteEnrollmentToken(validEnrollmentToken); expect(tokensDB.length).toEqual(0); - const { token } = await tokensLib.getEnrollmentToken( - validEnrollmentToken - ); + const { token } = await tokensLib.getEnrollmentToken(validEnrollmentToken); expect(token).toEqual(null); }); diff --git a/x-pack/plugins/beats/server/lib/domains/__tests__/beats/verify.test.ts b/x-pack/plugins/beats/server/lib/domains/__tests__/beats/verify.test.ts deleted file mode 100644 index 140a17917684d..0000000000000 --- a/x-pack/plugins/beats/server/lib/domains/__tests__/beats/verify.test.ts +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { MemoryBeatsAdapter } from '../../../adapters/beats/memory_beats_adapter'; -import { TestingBackendFrameworkAdapter } from '../../../adapters/framework/testing_framework_adapter'; -import { MemoryTagsAdapter } from '../../../adapters/tags/memory_tags_adapter'; -import { MemoryTokensAdapter } from '../../../adapters/tokens/memory_tokens_adapter'; - -import { BeatTag, CMBeat } from '../../../../../common/domain_types'; -import { TokenEnrollmentData } from '../../../adapters/tokens/adapter_types'; - -import { CMBeatsDomain } from '../../beats'; -import { CMTagsDomain } from '../../tags'; -import { CMTokensDomain } from '../../tokens'; - -import Chance from 'chance'; - -const seed = Date.now(); -const chance = new Chance(seed); - -const settings = { - encryptionKey: 'something_who_cares', - enrollmentTokensTtlInSeconds: 10 * 60, // 10 minutes -}; - -describe('Beats Domain Lib', () => { - let beatsLib: CMBeatsDomain; - let tokensLib: CMTokensDomain; - - let beatsDB: CMBeat[] = []; - let tagsDB: BeatTag[] = []; - let tokensDB: TokenEnrollmentData[] = []; - - describe('verify_beat', () => { - beforeEach(async () => { - beatsDB = [ - { - access_token: '9a6c99ae0fd84b068819701169cd8a4b', - host_ip: '1.2.3.4', - host_name: 'foo.bar.com', - id: 'qux', - type: 'filebeat', - }, - { - access_token: '188255eb560a4448b72656c5e99cae6f', - host_ip: '22.33.11.44', - host_name: 'baz.bar.com', - id: 'baz', - type: 'metricbeat', - }, - { - access_token: '93c4a4dd08564c189a7ec4e4f046b975', - host_ip: '1.2.3.4', - host_name: 'foo.bar.com', - id: 'foo', - tags: ['production', 'qa'], - type: 'metricbeat', - verified_on: '2018-05-15T16:25:38.924Z', - }, - { - access_token: '3c4a4dd08564c189a7ec4e4f046b9759', - host_ip: '11.22.33.44', - host_name: 'foo.com', - id: 'bar', - type: 'filebeat', - }, - ]; - tagsDB = [ - { - configuration_blocks: [], - id: 'production', - }, - { - configuration_blocks: [], - id: 'development', - }, - { - configuration_blocks: [], - id: 'qa', - }, - ]; - tokensDB = []; - - const framework = new TestingBackendFrameworkAdapter(null, settings); - - tokensLib = new CMTokensDomain(new MemoryTokensAdapter(tokensDB), { - framework, - }); - - const tagsLib = new CMTagsDomain(new MemoryTagsAdapter(tagsDB)); - - beatsLib = new CMBeatsDomain(new MemoryBeatsAdapter(beatsDB), { - tags: tagsLib, - tokens: tokensLib, - }); - }); - - it('should return errors for non-existent beats', async () => { - const nonExistentBeatId = chance.word(); - - interface Beats { - id: string; - status?: number; - result?: string; - } - - const beats: Beats[] = [{ id: 'bar' }, { id: nonExistentBeatId }]; - const beatIds = beats.map(b => b.id); - - const { - verifiedBeatIds, - alreadyVerifiedBeatIds, - nonExistentBeatIds, - } = await beatsLib.verifyBeats({ kind: 'unauthenticated' }, beatIds); - - // TODO calculation of status should be done in-lib, w/switch statement here - beats.forEach(b => { - if (nonExistentBeatIds.includes(b.id)) { - b.status = 404; - b.result = 'not found'; - } else if (alreadyVerifiedBeatIds.includes(b.id)) { - b.status = 200; - b.result = 'already verified'; - } else if (verifiedBeatIds.includes(b.id)) { - b.status = 200; - b.result = 'verified'; - } else { - b.status = 400; - b.result = 'not verified'; - } - }); - - const response = { beats }; - expect(response.beats).toEqual([ - { id: 'bar', status: 200, result: 'verified' }, - { id: nonExistentBeatId, status: 404, result: 'not found' }, - ]); - }); - - it('should not re-verify already-verified beats', async () => { - interface Beats { - id: string; - status?: number; - result?: string; - } - - const beats: Beats[] = [{ id: 'foo' }, { id: 'bar' }]; - const beatIds = beats.map(b => b.id); - - const { - verifiedBeatIds, - alreadyVerifiedBeatIds, - nonExistentBeatIds, - } = await beatsLib.verifyBeats({ kind: 'unauthenticated' }, beatIds); - - // TODO calculation of status should be done in-lib, w/switch statement here - beats.forEach(beat => { - if (nonExistentBeatIds.includes(beat.id)) { - beat.status = 404; - beat.result = 'not found'; - } else if (alreadyVerifiedBeatIds.includes(beat.id)) { - beat.status = 200; - beat.result = 'already verified'; - } else if (verifiedBeatIds.includes(beat.id)) { - beat.status = 200; - beat.result = 'verified'; - } else { - beat.status = 400; - beat.result = 'not verified'; - } - }); - - const response = { beats }; - expect(response.beats).toEqual([ - { id: 'foo', status: 200, result: 'already verified' }, - { id: 'bar', status: 200, result: 'verified' }, - ]); - }); - }); -}); diff --git a/x-pack/plugins/beats/server/lib/domains/__tests__/tokens.test.ts b/x-pack/plugins/beats/server/lib/domains/__tests__/tokens.test.ts index 8002013fa4795..c89962dca7d3b 100644 --- a/x-pack/plugins/beats/server/lib/domains/__tests__/tokens.test.ts +++ b/x-pack/plugins/beats/server/lib/domains/__tests__/tokens.test.ts @@ -36,10 +36,7 @@ describe('Token Domain Lib', () => { }); it('should generate webtokens with a qty of 1', async () => { - const tokens = await tokensLib.createEnrollmentTokens( - framework.internalUser, - 1 - ); + const tokens = await tokensLib.createEnrollmentTokens(framework.internalUser, 1); expect(tokens.length).toBe(1); @@ -48,15 +45,10 @@ describe('Token Domain Lib', () => { it('should create the specified number of tokens', async () => { const numTokens = chance.integer({ min: 1, max: 20 }); - const tokensFromApi = await tokensLib.createEnrollmentTokens( - framework.internalUser, - numTokens - ); + const tokensFromApi = await tokensLib.createEnrollmentTokens(framework.internalUser, numTokens); expect(tokensFromApi.length).toEqual(numTokens); - expect(tokensFromApi).toEqual( - tokensDB.map((t: TokenEnrollmentData) => t.token) - ); + expect(tokensFromApi).toEqual(tokensDB.map((t: TokenEnrollmentData) => t.token)); }); it('should set token expiration to 10 minutes from now by default', async () => { diff --git a/x-pack/plugins/beats/server/lib/domains/beats.ts b/x-pack/plugins/beats/server/lib/domains/beats.ts index 9a580e2291ee7..07a084e54b53d 100644 --- a/x-pack/plugins/beats/server/lib/domains/beats.ts +++ b/x-pack/plugins/beats/server/lib/domains/beats.ts @@ -5,17 +5,15 @@ */ import { uniq } from 'lodash'; +import moment from 'moment'; import { findNonExistentItems } from '../../utils/find_non_existent_items'; import { CMBeat } from '../../../common/domain_types'; -import { - BeatsTagAssignment, - CMBeatsAdapter, -} from '../adapters/beats/adapter_types'; +import { BeatsTagAssignment, CMBeatsAdapter } from '../adapters/beats/adapter_types'; import { FrameworkUser } from '../adapters/framework/adapter_types'; import { CMAssignmentReturn } from '../adapters/beats/adapter_types'; -import { CMDomainLibs } from '../lib'; +import { BeatEnrollmentStatus, CMDomainLibs } from '../lib'; import { BeatsRemovalReturn } from './../adapters/beats/adapter_types'; export class CMBeatsDomain { @@ -32,11 +30,11 @@ export class CMBeatsDomain { this.tokens = libs.tokens; } - public async update( - beatId: string, - accessToken: string, - beatData: Partial - ) { + public async getById(beatId: string) { + return await this.adapter.get(beatId); + } + + public async update(beatId: string, accessToken: string, beatData: Partial) { const beat = await this.adapter.get(beatId); const { verified: isAccessTokenValid } = this.tokens.verifyToken( @@ -52,10 +50,6 @@ export class CMBeatsDomain { if (!isAccessTokenValid) { return 'invalid-access-token'; } - const isBeatVerified = beat.hasOwnProperty('verified_on'); - if (!isBeatVerified) { - return 'beat-not-verified'; - } await this.adapter.update({ ...beat, @@ -65,18 +59,34 @@ export class CMBeatsDomain { // TODO more strongly type this public async enrollBeat( + enrollmentToken: string, beatId: string, remoteAddress: string, beat: Partial - ) { + ): Promise<{ status: string; accessToken?: string }> { + const { token, expires_on } = await this.tokens.getEnrollmentToken(enrollmentToken); + + if (expires_on && moment(expires_on).isBefore(moment())) { + return { status: BeatEnrollmentStatus.ExpiredEnrollmentToken }; + } + if (!token) { + return { status: BeatEnrollmentStatus.InvalidEnrollmentToken }; + } + const accessToken = this.tokens.generateAccessToken(); + const verifiedOn = moment().toJSON(); + await this.adapter.insert({ ...beat, + verified_on: verifiedOn, access_token: accessToken, host_ip: remoteAddress, id: beatId, } as CMBeat); - return { accessToken }; + + await this.tokens.deleteEnrollmentToken(enrollmentToken); + + return { status: BeatEnrollmentStatus.Success, accessToken }; } public async removeTagsFromBeats( @@ -115,10 +125,7 @@ export class CMBeatsDomain { .filter((removal, idx) => response.removals[idx].status === null); if (validRemovals.length > 0) { - const removalResults = await this.adapter.removeTagsFromBeats( - user, - validRemovals - ); + const removalResults = await this.adapter.removeTagsFromBeats(user, validRemovals); return addToResultsToResponse('removals', response, removalResults); } return response; @@ -128,33 +135,6 @@ export class CMBeatsDomain { return await this.adapter.getAll(user); } - // TODO cleanup return value, should return a status enum - public async verifyBeats(user: FrameworkUser, beatIds: string[]) { - const beatsFromEs = await this.adapter.getWithIds(user, beatIds); - - const nonExistentBeatIds = findNonExistentItems(beatsFromEs, beatIds); - - const alreadyVerifiedBeatIds = beatsFromEs - .filter((beat: any) => beat.hasOwnProperty('verified_on')) - .map((beat: any) => beat.id); - - const toBeVerifiedBeatIds = beatsFromEs - .filter((beat: any) => !beat.hasOwnProperty('verified_on')) - .map((beat: any) => beat.id); - - const verifications = await this.adapter.verifyBeats( - user, - toBeVerifiedBeatIds - ); - - return { - alreadyVerifiedBeatIds, - nonExistentBeatIds, - toBeVerifiedBeatIds, - verifiedBeatIds: verifications.map((v: any) => v.id), - }; - } - public async assignTagsToBeats( user: FrameworkUser, assignments: BeatsTagAssignment[] @@ -191,10 +171,7 @@ export class CMBeatsDomain { .filter((assignment, idx) => response.assignments[idx].status === null); if (validAssignments.length > 0) { - const assignmentResults = await this.adapter.assignTagsToBeats( - user, - validAssignments - ); + const assignmentResults = await this.adapter.assignTagsToBeats(user, validAssignments); // TODO This should prob not mutate return addToResultsToResponse('assignments', response, assignmentResults); @@ -229,11 +206,7 @@ function addNonExistentItemToResponse( } // TODO dont mutate response -function addToResultsToResponse( - key: string, - response: any, - assignmentResults: any -) { +function addToResultsToResponse(key: string, response: any, assignmentResults: any) { assignmentResults.forEach((assignmentResult: any) => { const { idxInRequest, status, result } = assignmentResult; response[key][idxInRequest].status = status; diff --git a/x-pack/plugins/beats/server/lib/domains/tags.ts b/x-pack/plugins/beats/server/lib/domains/tags.ts index 194551721ab33..b6ce9f7e14821 100644 --- a/x-pack/plugins/beats/server/lib/domains/tags.ts +++ b/x-pack/plugins/beats/server/lib/domains/tags.ts @@ -22,14 +22,8 @@ export class CMTagsDomain { return await this.adapter.getTagsWithIds(user, tagIds); } - public async saveTag( - user: FrameworkUser, - tagId: string, - configs: ConfigurationBlock[] - ) { - const { isValid, message } = await this.validateConfigurationBlocks( - configs - ); + public async saveTag(user: FrameworkUser, tagId: string, configs: ConfigurationBlock[]) { + const { isValid, message } = await this.validateConfigurationBlocks(configs); if (!isValid) { return { isValid, result: message }; } @@ -49,10 +43,7 @@ export class CMTagsDomain { // If none of the types in the given configuration blocks are uniqueness-enforcing, // we don't need to perform any further validation checks. - const uniquenessEnforcingTypes = intersection( - types, - UNIQUENESS_ENFORCING_TYPES - ); + const uniquenessEnforcingTypes = intersection(types, UNIQUENESS_ENFORCING_TYPES); if (uniquenessEnforcingTypes.length === 0) { return { isValid: true }; } diff --git a/x-pack/plugins/beats/server/lib/domains/tokens.ts b/x-pack/plugins/beats/server/lib/domains/tokens.ts index 6cfb922c34d57..dc7ffa9a63356 100644 --- a/x-pack/plugins/beats/server/lib/domains/tokens.ts +++ b/x-pack/plugins/beats/server/lib/domains/tokens.ts @@ -18,10 +18,7 @@ export class CMTokensDomain { private adapter: CMTokensAdapter; private framework: BackendFrameworkAdapter; - constructor( - adapter: CMTokensAdapter, - libs: { framework: BackendFrameworkAdapter } - ) { + constructor(adapter: CMTokensAdapter, libs: { framework: BackendFrameworkAdapter }) { this.adapter = adapter; this.framework = libs.framework; } @@ -37,11 +34,7 @@ export class CMTokensDomain { }; } - const { verified, expired } = this.verifyToken( - enrollmentToken, - fullToken.token || '', - false - ); + const { verified, expired } = this.verifyToken(enrollmentToken, fullToken.token || '', false); if (!verified) { return { @@ -63,9 +56,7 @@ export class CMTokensDomain { let expired = false; if (decode) { - const enrollmentTokenSecret = this.framework.getSetting( - 'xpack.beats.encryptionKey' - ); + const enrollmentTokenSecret = this.framework.getSetting('xpack.beats.encryptionKey'); try { verifyToken(recivedToken, enrollmentTokenSecret); @@ -99,17 +90,13 @@ export class CMTokensDomain { return { expired, verified: - timingSafeEqual( - Buffer.from(recivedToken, 'utf8'), - Buffer.from(token2, 'utf8') - ) && tokenDecoded, + timingSafeEqual(Buffer.from(recivedToken, 'utf8'), Buffer.from(token2, 'utf8')) && + tokenDecoded, }; } public generateAccessToken() { - const enrollmentTokenSecret = this.framework.getSetting( - 'xpack.beats.encryptionKey' - ); + const enrollmentTokenSecret = this.framework.getSetting('xpack.beats.encryptionKey'); const tokenData = { created: moment().toJSON(), diff --git a/x-pack/plugins/beats/server/lib/lib.ts b/x-pack/plugins/beats/server/lib/lib.ts index d916c18aa4e4a..495093842b496 100644 --- a/x-pack/plugins/beats/server/lib/lib.ts +++ b/x-pack/plugins/beats/server/lib/lib.ts @@ -20,3 +20,9 @@ export interface CMServerLibs extends CMDomainLibs { framework: BackendFrameworkAdapter; database: DatabaseAdapter; } + +export enum BeatEnrollmentStatus { + Success = 'Success', + ExpiredEnrollmentToken = 'Expired enrollment token', + InvalidEnrollmentToken = 'Invalid enrollment token', +} diff --git a/x-pack/plugins/beats/server/management_server.ts b/x-pack/plugins/beats/server/management_server.ts index 637da2e37bd07..1c8a1d727175e 100644 --- a/x-pack/plugins/beats/server/management_server.ts +++ b/x-pack/plugins/beats/server/management_server.ts @@ -5,15 +5,14 @@ */ import { CMServerLibs } from './lib/lib'; +import { createGetBeatConfigurationRoute } from './rest_api/beats/configuration'; import { createBeatEnrollmentRoute } from './rest_api/beats/enroll'; import { createListAgentsRoute } from './rest_api/beats/list'; import { createTagAssignmentsRoute } from './rest_api/beats/tag_assignment'; import { createTagRemovalsRoute } from './rest_api/beats/tag_removal'; import { createBeatUpdateRoute } from './rest_api/beats/update'; -import { createBeatVerificationRoute } from './rest_api/beats/verify'; import { createSetTagRoute } from './rest_api/tags/set'; import { createTokensRoute } from './rest_api/tokens/create'; - import { beatsIndexTemplate } from './utils/index_templates'; export const initManagementServer = (libs: CMServerLibs) => { @@ -22,12 +21,12 @@ export const initManagementServer = (libs: CMServerLibs) => { body: beatsIndexTemplate, }); + libs.framework.registerRoute(createGetBeatConfigurationRoute(libs)); libs.framework.registerRoute(createTagAssignmentsRoute(libs)); libs.framework.registerRoute(createListAgentsRoute(libs)); libs.framework.registerRoute(createTagRemovalsRoute(libs)); libs.framework.registerRoute(createBeatEnrollmentRoute(libs)); libs.framework.registerRoute(createSetTagRoute(libs)); libs.framework.registerRoute(createTokensRoute(libs)); - libs.framework.registerRoute(createBeatVerificationRoute(libs)); libs.framework.registerRoute(createBeatUpdateRoute(libs)); }; diff --git a/x-pack/plugins/beats/server/rest_api/beats/configuration.ts b/x-pack/plugins/beats/server/rest_api/beats/configuration.ts new file mode 100644 index 0000000000000..81906ea9473e6 --- /dev/null +++ b/x-pack/plugins/beats/server/rest_api/beats/configuration.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Joi from 'joi'; +import { BeatTag, ConfigurationBlock } from '../../../common/domain_types'; +import { CMServerLibs } from '../../lib/lib'; +import { wrapEsError } from '../../utils/error_wrappers'; + +export const createGetBeatConfigurationRoute = (libs: CMServerLibs) => ({ + method: 'GET', + path: '/api/beats/agent/{beatId}/configuration', + config: { + validate: { + headers: Joi.object({ + 'kbn-beats-access-token': Joi.string().required(), + }).options({ allowUnknown: true }), + }, + auth: false, + }, + handler: async (request: any, reply: any) => { + const beatId = request.params.beatId; + const accessToken = request.headers['kbn-beats-access-token']; + + let beat; + let tags; + try { + beat = await libs.beats.getById(beatId); + if (beat === null) { + return reply({ message: 'Beat not found' }).code(404); + } + + const isAccessTokenValid = beat.access_token === accessToken; + if (!isAccessTokenValid) { + return reply({ message: 'Invalid access token' }).code(401); + } + + tags = await libs.tags.getTagsWithIds(libs.framework.internalUser, beat.tags || []); + } catch (err) { + return reply(wrapEsError(err)); + } + + const configurationBlocks = tags.reduce((blocks: ConfigurationBlock[], tag: BeatTag) => { + blocks = blocks.concat(tag.configuration_blocks); + return blocks; + }, []); + + reply({ + configuration_blocks: configurationBlocks, + }); + }, +}); diff --git a/x-pack/plugins/beats/server/rest_api/beats/enroll.ts b/x-pack/plugins/beats/server/rest_api/beats/enroll.ts index d909b64810360..c5b01fdbd4cc3 100644 --- a/x-pack/plugins/beats/server/rest_api/beats/enroll.ts +++ b/x-pack/plugins/beats/server/rest_api/beats/enroll.ts @@ -3,13 +3,12 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - import Joi from 'joi'; import { omit } from 'lodash'; -import moment from 'moment'; import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types'; import { CMServerLibs } from '../../lib/lib'; import { wrapEsError } from '../../utils/error_wrappers'; +import { BeatEnrollmentStatus } from './../../lib/lib'; // TODO: add license check pre-hook // TODO: write to Kibana audit log file @@ -34,25 +33,26 @@ export const createBeatEnrollmentRoute = (libs: CMServerLibs) => ({ const enrollmentToken = request.headers['kbn-beats-enrollment-token']; try { - const { token, expires_on } = await libs.tokens.getEnrollmentToken( - enrollmentToken - ); - - if (expires_on && moment(expires_on).isBefore(moment())) { - return reply({ message: 'Expired enrollment token' }).code(400); - } - if (!token) { - return reply({ message: 'Invalid enrollment token' }).code(400); - } - const { accessToken } = await libs.beats.enrollBeat( + const { status, accessToken } = await libs.beats.enrollBeat( + enrollmentToken, beatId, request.info.remoteAddress, omit(request.payload, 'enrollment_token') ); - await libs.tokens.deleteEnrollmentToken(enrollmentToken); - - reply({ access_token: accessToken }).code(201); + switch (status) { + case BeatEnrollmentStatus.ExpiredEnrollmentToken: + return reply({ + message: BeatEnrollmentStatus.ExpiredEnrollmentToken, + }).code(400); + case BeatEnrollmentStatus.InvalidEnrollmentToken: + return reply({ + message: BeatEnrollmentStatus.InvalidEnrollmentToken, + }).code(400); + case BeatEnrollmentStatus.Success: + default: + return reply({ access_token: accessToken }).code(201); + } } catch (err) { // TODO move this to kibana route thing in adapter return reply(wrapEsError(err)); diff --git a/x-pack/plugins/beats/server/rest_api/beats/tag_assignment.ts b/x-pack/plugins/beats/server/rest_api/beats/tag_assignment.ts index a6654250d196f..9c832ee3226b8 100644 --- a/x-pack/plugins/beats/server/rest_api/beats/tag_assignment.ts +++ b/x-pack/plugins/beats/server/rest_api/beats/tag_assignment.ts @@ -34,10 +34,7 @@ export const createTagAssignmentsRoute = (libs: CMServerLibs) => ({ })); try { - const response = await libs.beats.assignTagsToBeats( - request.user, - tweakedAssignments - ); + const response = await libs.beats.assignTagsToBeats(request.user, tweakedAssignments); reply(response); } catch (err) { // TODO move this to kibana route thing in adapter diff --git a/x-pack/plugins/beats/server/rest_api/beats/tag_removal.ts b/x-pack/plugins/beats/server/rest_api/beats/tag_removal.ts index 1b495b906a364..6a7af3b42d407 100644 --- a/x-pack/plugins/beats/server/rest_api/beats/tag_removal.ts +++ b/x-pack/plugins/beats/server/rest_api/beats/tag_removal.ts @@ -34,10 +34,7 @@ export const createTagRemovalsRoute = (libs: CMServerLibs) => ({ })); try { - const response = await libs.beats.removeTagsFromBeats( - request.user, - tweakedRemovals - ); + const response = await libs.beats.removeTagsFromBeats(request.user, tweakedRemovals); reply(response); } catch (err) { // TODO move this to kibana route thing in adapter diff --git a/x-pack/plugins/beats/server/rest_api/beats/update.ts b/x-pack/plugins/beats/server/rest_api/beats/update.ts index c86cf74b0c744..60d7151731cd3 100644 --- a/x-pack/plugins/beats/server/rest_api/beats/update.ts +++ b/x-pack/plugins/beats/server/rest_api/beats/update.ts @@ -49,8 +49,6 @@ export const createBeatUpdateRoute = (libs: CMServerLibs) => ({ return reply({ message: 'Beat not found' }).code(404); case 'invalid-access-token': return reply({ message: 'Invalid access token' }).code(401); - case 'beat-not-verified': - return reply({ message: 'Beat has not been verified' }).code(400); } reply().code(204); diff --git a/x-pack/plugins/beats/server/rest_api/beats/verify.ts b/x-pack/plugins/beats/server/rest_api/beats/verify.ts deleted file mode 100644 index d15b0374f445f..0000000000000 --- a/x-pack/plugins/beats/server/rest_api/beats/verify.ts +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import Joi from 'joi'; -import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types'; -import { CMServerLibs } from '../../lib/lib'; -import { wrapEsError } from '../../utils/error_wrappers'; - -// TODO: add license check pre-hook -// TODO: write to Kibana audit log file -export const createBeatVerificationRoute = (libs: CMServerLibs) => ({ - config: { - auth: false, - validate: { - payload: Joi.object({ - beats: Joi.array() - .items({ - id: Joi.string().required(), - }) - .min(1), - }).required(), - }, - }, - handler: async (request: FrameworkRequest, reply: any) => { - const beats = [...request.payload.beats]; - const beatIds = beats.map(beat => beat.id); - - try { - const { - verifiedBeatIds, - alreadyVerifiedBeatIds, - nonExistentBeatIds, - } = await libs.beats.verifyBeats(request.user, beatIds); - - // TODO calculation of status should be done in-lib, w/switch statement here - beats.forEach(beat => { - if (nonExistentBeatIds.includes(beat.id)) { - beat.status = 404; - beat.result = 'not found'; - } else if (alreadyVerifiedBeatIds.includes(beat.id)) { - beat.status = 200; - beat.result = 'already verified'; - } else if (verifiedBeatIds.includes(beat.id)) { - beat.status = 200; - beat.result = 'verified'; - } else { - beat.status = 400; - beat.result = 'not verified'; - } - }); - - const response = { beats }; - reply(response); - } catch (err) { - return reply(wrapEsError(err)); - } - }, - method: 'POST', - path: '/api/beats/agents/verify', -}); diff --git a/x-pack/plugins/beats/server/rest_api/tags/set.ts b/x-pack/plugins/beats/server/rest_api/tags/set.ts index bdc49ce62a9a9..6c01959e75311 100644 --- a/x-pack/plugins/beats/server/rest_api/tags/set.ts +++ b/x-pack/plugins/beats/server/rest_api/tags/set.ts @@ -32,11 +32,7 @@ export const createSetTagRoute = (libs: CMServerLibs) => ({ }, }, handler: async (request: FrameworkRequest, reply: any) => { - const configurationBlocks = get( - request, - 'payload.configuration_blocks', - [] - ); + const configurationBlocks = get(request, 'payload.configuration_blocks', []); try { const { isValid, result } = await libs.tags.saveTag( request.user, diff --git a/x-pack/plugins/beats/server/rest_api/tokens/create.ts b/x-pack/plugins/beats/server/rest_api/tokens/create.ts index 9e20735d640e5..74278703347c3 100644 --- a/x-pack/plugins/beats/server/rest_api/tokens/create.ts +++ b/x-pack/plugins/beats/server/rest_api/tokens/create.ts @@ -27,10 +27,7 @@ export const createTokensRoute = (libs: CMServerLibs) => ({ const numTokens = get(request, 'payload.num_tokens', DEFAULT_NUM_TOKENS); try { - const tokens = await libs.tokens.createEnrollmentTokens( - request.user, - numTokens - ); + const tokens = await libs.tokens.createEnrollmentTokens(request.user, numTokens); reply({ tokens }); } catch (err) { // TODO move this to kibana route thing in adapter diff --git a/x-pack/plugins/beats/server/utils/error_wrappers/wrap_es_error.test.ts b/x-pack/plugins/beats/server/utils/error_wrappers/wrap_es_error.test.ts index 5087bf3224c42..e7fb1c2adda21 100644 --- a/x-pack/plugins/beats/server/utils/error_wrappers/wrap_es_error.test.ts +++ b/x-pack/plugins/beats/server/utils/error_wrappers/wrap_es_error.test.ts @@ -24,9 +24,7 @@ describe('wrap_es_error', () => { const wrappedError = wrapEsError(originalError); expect(wrappedError.output.statusCode).toEqual(originalError.statusCode); - expect(wrappedError.output.payload.message).toEqual( - originalError.message - ); + expect(wrappedError.output.payload.message).toEqual(originalError.message); }); it('should return invalid permissions message for 403 errors', () => { diff --git a/x-pack/plugins/beats/server/utils/error_wrappers/wrap_es_error.ts b/x-pack/plugins/beats/server/utils/error_wrappers/wrap_es_error.ts index 50ffbcb4a10c9..30328f2c6a833 100644 --- a/x-pack/plugins/beats/server/utils/error_wrappers/wrap_es_error.ts +++ b/x-pack/plugins/beats/server/utils/error_wrappers/wrap_es_error.ts @@ -16,9 +16,7 @@ import Boom from 'boom'; export function wrapEsError(err: any) { const statusCode = err.statusCode; if (statusCode === 403) { - return Boom.forbidden( - 'Insufficient user permissions for managing Beats configuration' - ); + return Boom.forbidden('Insufficient user permissions for managing Beats configuration'); } return Boom.wrap(err, err.statusCode); } diff --git a/x-pack/plugins/beats/server/utils/find_non_existent_items.ts b/x-pack/plugins/beats/server/utils/find_non_existent_items.ts index d6b2a0c9e143b..0e9b4f0b6fa5e 100644 --- a/x-pack/plugins/beats/server/utils/find_non_existent_items.ts +++ b/x-pack/plugins/beats/server/utils/find_non_existent_items.ts @@ -10,17 +10,10 @@ interface RandomItem { } export function findNonExistentItems(items: RandomItem[], requestedItems: any) { - return requestedItems.reduce( - (nonExistentItems: string[], requestedItem: string, idx: number) => { - if ( - items.findIndex( - (item: RandomItem) => item && item.id === requestedItem - ) === -1 - ) { - nonExistentItems.push(requestedItems[idx]); - } - return nonExistentItems; - }, - [] - ); + return requestedItems.reduce((nonExistentItems: string[], requestedItem: string, idx: number) => { + if (items.findIndex((item: RandomItem) => item && item.id === requestedItem) === -1) { + nonExistentItems.push(requestedItems[idx]); + } + return nonExistentItems; + }, []); } diff --git a/x-pack/plugins/beats/wallaby.js b/x-pack/plugins/beats/wallaby.js index 79b1438a5374d..8c0c4aa355925 100644 --- a/x-pack/plugins/beats/wallaby.js +++ b/x-pack/plugins/beats/wallaby.js @@ -4,14 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ const path = require('path'); -process.env.NODE_PATH += - path.delimiter + path.join(__dirname, '..', '..', '..', 'node_modules'); +process.env.NODE_PATH = path.join(__dirname, '..', '..', 'node_modules'); module.exports = function (wallaby) { return { - hints: { - commentAutoLog: 'testOutputWith:', - }, debug: true, files: [ './tsconfig.json', @@ -43,7 +39,6 @@ module.exports = function (wallaby) { '..', '..' ); - wallaby.testFramework.configure({ rootDir: wallaby.localProjectDir, moduleNameMapper: { @@ -61,6 +56,7 @@ module.exports = function (wallaby) { ], transform: { '^.+\\.js$': `${kibanaDirectory}/src/dev/jest/babel_transform.js`, + //"^.+\\.tsx?$": `${kibanaDirectory}/src/dev/jest/ts_transform.js`, }, }); }, diff --git a/x-pack/plugins/logstash/server/lib/error_wrappers/__tests__/wrap_es_error.js b/x-pack/plugins/logstash/server/lib/error_wrappers/__tests__/wrap_es_error.js deleted file mode 100644 index f1b956bdcc3bb..0000000000000 --- a/x-pack/plugins/logstash/server/lib/error_wrappers/__tests__/wrap_es_error.js +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from 'expect.js'; -import { wrapEsError } from '../wrap_es_error'; - -describe('wrap_es_error', () => { - describe('#wrapEsError', () => { - - let originalError; - beforeEach(() => { - originalError = new Error('I am an error'); - originalError.statusCode = 404; - }); - - it('should return a Boom object', () => { - const wrappedError = wrapEsError(originalError); - - expect(wrappedError.isBoom).to.be(true); - }); - - it('should return the correct Boom object', () => { - const wrappedError = wrapEsError(originalError); - - expect(wrappedError.output.statusCode).to.be(originalError.statusCode); - expect(wrappedError.output.payload.message).to.be(originalError.message); - }); - - it('should return invalid permissions message for 403 errors', () => { - const securityError = new Error('I am an error'); - securityError.statusCode = 403; - const wrappedError = wrapEsError(securityError); - - expect(wrappedError.isBoom).to.be(true); - expect(wrappedError.message).to.be('Insufficient user permissions for managing Logstash pipelines'); - }); - }); -}); diff --git a/x-pack/test/api_integration/apis/beats/enroll_beat.js b/x-pack/test/api_integration/apis/beats/enroll_beat.js index 1dde64c9ee1d8..0234359b4f9ff 100644 --- a/x-pack/test/api_integration/apis/beats/enroll_beat.js +++ b/x-pack/test/api_integration/apis/beats/enroll_beat.js @@ -66,7 +66,7 @@ export default function ({ getService }) { id: `beat:${beatId}`, }); - expect(esResponse._source.beat).to.not.have.property('verified_on'); + expect(esResponse._source.beat).to.have.property('verified_on'); expect(esResponse._source.beat).to.have.property('host_ip'); }); diff --git a/x-pack/test/api_integration/apis/beats/get_beat.js b/x-pack/test/api_integration/apis/beats/get_beat.js new file mode 100644 index 0000000000000..8d6a81d100eb4 --- /dev/null +++ b/x-pack/test/api_integration/apis/beats/get_beat.js @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from 'expect.js'; + +export default function ({ getService }) { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + + describe('get_beat_configuration', () => { + const archive = 'beats/list'; + + beforeEach('load beats archive', () => esArchiver.load(archive)); + afterEach('unload beats archive', () => esArchiver.unload(archive)); + + it('should return merged configuration for the beat', async () => { + const { body: apiResponse } = await supertest + .get('/api/beats/agent/foo/configuration') + .set( + 'kbn-beats-access-token', + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.' + + 'eyJjcmVhdGVkIjoiMjAxOC0wNi0zMFQwMzo0MjoxNS4yMzBaIiwiaWF0IjoxNTMwMzMwMTM1fQ.' + + 'SSsX2Byyo1B1bGxV8C3G4QldhE5iH87EY_1r21-bwbI' + ) + .expect(200); + + const configurationBlocks = apiResponse.configuration_blocks; + + expect(configurationBlocks).to.be.an(Array); + expect(configurationBlocks.length).to.be(3); + + expect(configurationBlocks[0].type).to.be('output'); + expect(configurationBlocks[0].block_yml).to.be( + 'elasticsearch:\n hosts: ["localhost:9200"]\n username: "..."' + ); + + expect(configurationBlocks[1].type).to.be('metricbeat.modules'); + expect(configurationBlocks[1].block_yml).to.be( + 'module: memcached\nhosts: ["localhost:11211"]' + ); + + expect(configurationBlocks[2].type).to.be('metricbeat.modules'); + expect(configurationBlocks[2].block_yml).to.be( + 'module: munin\nhosts: ["localhost:4949"]\nnode.namespace: node' + ); + }); + }); +} diff --git a/x-pack/test/api_integration/apis/beats/index.js b/x-pack/test/api_integration/apis/beats/index.js index f8956d3e498ba..a6552a383dbd8 100644 --- a/x-pack/test/api_integration/apis/beats/index.js +++ b/x-pack/test/api_integration/apis/beats/index.js @@ -12,7 +12,7 @@ export default function ({ getService, loadTestFile }) { describe('beats', () => { const cleanup = () => es.indices.delete({ index: ES_INDEX_NAME, - ignore: [ 404 ] + ignore: [404] }); beforeEach(cleanup); @@ -20,10 +20,10 @@ export default function ({ getService, loadTestFile }) { loadTestFile(require.resolve('./create_enrollment_tokens')); loadTestFile(require.resolve('./enroll_beat')); loadTestFile(require.resolve('./list_beats')); - loadTestFile(require.resolve('./verify_beats')); loadTestFile(require.resolve('./update_beat')); loadTestFile(require.resolve('./set_tag')); loadTestFile(require.resolve('./assign_tags_to_beats')); loadTestFile(require.resolve('./remove_tags_from_beats')); + loadTestFile(require.resolve('./get_beat')); }); } diff --git a/x-pack/test/api_integration/apis/beats/update_beat.js b/x-pack/test/api_integration/apis/beats/update_beat.js index fb30970d3cc7f..697e644cb2221 100644 --- a/x-pack/test/api_integration/apis/beats/update_beat.js +++ b/x-pack/test/api_integration/apis/beats/update_beat.js @@ -103,36 +103,6 @@ export default function ({ getService }) { expect(beatInEs._source.beat.ephemeral_id).to.not.be(beat.ephemeral_id); }); - it('should return an error for an existing but unverified beat', async () => { - const beatId = 'bar'; - - const { body } = await supertest - .put(`/api/beats/agent/${beatId}`) - .set('kbn-xsrf', 'xxx') - .set( - 'kbn-beats-access-token', - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.' + - 'eyJjcmVhdGVkIjoiMjAxOC0wNi0zMFQwMzo0MjoxNS4yMzBaIiwiaWF0IjoxNTMwMzMwMTM1fQ.' + - 'SSsX2Byyo1B1bGxV8C3G4QldhE5iH87EY_1r21-bwbI' - ) - .send(beat) - .expect(400); - - expect(body.message).to.be('Beat has not been verified'); - - const beatInEs = await es.get({ - index: ES_INDEX_NAME, - type: ES_TYPE_NAME, - id: `beat:${beatId}`, - }); - - expect(beatInEs._source.beat.id).to.be(beatId); - expect(beatInEs._source.beat.type).to.not.be(beat.type); - expect(beatInEs._source.beat.host_name).to.not.be(beat.host_name); - expect(beatInEs._source.beat.version).to.not.be(beat.version); - expect(beatInEs._source.beat.ephemeral_id).to.not.be(beat.ephemeral_id); - }); - it('should return an error for a non-existent beat', async () => { const beatId = chance.word(); const { body } = await supertest diff --git a/x-pack/test/api_integration/apis/beats/verify_beats.js b/x-pack/test/api_integration/apis/beats/verify_beats.js deleted file mode 100644 index 30521d0483c2d..0000000000000 --- a/x-pack/test/api_integration/apis/beats/verify_beats.js +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from 'expect.js'; - -export default function ({ getService }) { - const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - const chance = getService('chance'); - - describe('verify_beats', () => { - const archive = 'beats/list'; - - beforeEach('load beats archive', () => esArchiver.load(archive)); - afterEach('unload beats archive', () => esArchiver.unload(archive)); - - it('verify the given beats', async () => { - const { body: apiResponse } = await supertest - .post('/api/beats/agents/verify') - .set('kbn-xsrf', 'xxx') - .send({ - beats: [{ id: 'bar' }, { id: 'baz' }], - }) - .expect(200); - - expect(apiResponse.beats).to.eql([ - { id: 'bar', status: 200, result: 'verified' }, - { id: 'baz', status: 200, result: 'verified' }, - ]); - }); - - it('should not re-verify already-verified beats', async () => { - const { body: apiResponse } = await supertest - .post('/api/beats/agents/verify') - .set('kbn-xsrf', 'xxx') - .send({ - beats: [{ id: 'foo' }, { id: 'bar' }], - }) - .expect(200); - - expect(apiResponse.beats).to.eql([ - { id: 'foo', status: 200, result: 'already verified' }, - { id: 'bar', status: 200, result: 'verified' }, - ]); - }); - - it('should return errors for non-existent beats', async () => { - const nonExistentBeatId = chance.word(); - const { body: apiResponse } = await supertest - .post('/api/beats/agents/verify') - .set('kbn-xsrf', 'xxx') - .send({ - beats: [{ id: 'bar' }, { id: nonExistentBeatId }], - }) - .expect(200); - - expect(apiResponse.beats).to.eql([ - { id: 'bar', status: 200, result: 'verified' }, - { id: nonExistentBeatId, status: 404, result: 'not found' }, - ]); - }); - }); -} diff --git a/x-pack/test/functional/es_archives/beats/list/data.json b/x-pack/test/functional/es_archives/beats/list/data.json new file mode 100644 index 0000000000000..bbe5bc8dbe3e5 --- /dev/null +++ b/x-pack/test/functional/es_archives/beats/list/data.json @@ -0,0 +1,139 @@ +{ + "type": "doc", + "value": { + "index": ".management-beats", + "type": "_doc", + "id": "beat:qux", + "source": { + "type": "beat", + "beat": { + "type": "filebeat", + "host_ip": "1.2.3.4", + "host_name": "foo.bar.com", + "id": "qux", + "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjcmVhdGVkIjoiMjAxOC0wNi0zMFQwMzo0MjoxNS4yMzBaIiwiaWF0IjoxNTMwMzMwMTM1fQ.SSsX2Byyo1B1bGxV8C3G4QldhE5iH87EY_1r21-bwbI" + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".management-beats", + "type": "_doc", + "id": "beat:baz", + "source": { + "type": "beat", + "beat": { + "type": "metricbeat", + "host_ip": "22.33.11.44", + "host_name": "baz.bar.com", + "id": "baz", + "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjcmVhdGVkIjoiMjAxOC0wNi0zMFQwMzo0MjoxNS4yMzBaIiwiaWF0IjoxNTMwMzMwMTM1fQ.SSsX2Byyo1B1bGxV8C3G4QldhE5iH87EY_1r21-bwbI" + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".management-beats", + "type": "_doc", + "id": "beat:foo", + "source": { + "type": "beat", + "beat": { + "type": "metricbeat", + "host_ip": "1.2.3.4", + "host_name": "foo.bar.com", + "id": "foo", + "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjcmVhdGVkIjoiMjAxOC0wNi0zMFQwMzo0MjoxNS4yMzBaIiwiaWF0IjoxNTMwMzMwMTM1fQ.SSsX2Byyo1B1bGxV8C3G4QldhE5iH87EY_1r21-bwbI", + "verified_on": "2018-05-15T16:25:38.924Z", + "tags": [ + "production", + "qa" + ] + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".management-beats", + "type": "_doc", + "id": "beat:bar", + "source": { + "type": "beat", + "beat": { + "type": "filebeat", + "host_ip": "11.22.33.44", + "host_name": "foo.com", + "id": "bar", + "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjcmVhdGVkIjoiMjAxOC0wNi0zMFQwMzo0MjoxNS4yMzBaIiwiaWF0IjoxNTMwMzMwMTM1fQ.SSsX2Byyo1B1bGxV8C3G4QldhE5iH87EY_1r21-bwbI" + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".management-beats", + "type": "_doc", + "id": "tag:production", + "source": { + "type": "tag", + "tag": { + "configuration_blocks": [ + { + "type": "output", + "block_yml": "elasticsearch:\n hosts: [\"localhost:9200\"]\n username: \"...\"" + }, + { + "type": "metricbeat.modules", + "block_yml": "module: memcached\nhosts: [\"localhost:11211\"]" + } + ] + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".management-beats", + "type": "_doc", + "id": "tag:development", + "source": { + "type": "tag", + "tag": { + "configuration_blocks": [] + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".management-beats", + "type": "_doc", + "id": "tag:qa", + "source": { + "type": "tag", + "tag": { + "configuration_blocks": [ + { + "type": "metricbeat.modules", + "block_yml": "module: munin\nhosts: [\"localhost:4949\"]\nnode.namespace: node" + } + ] + } + } + } +} \ No newline at end of file diff --git a/x-pack/test/functional/es_archives/beats/list/data.json.gz b/x-pack/test/functional/es_archives/beats/list/data.json.gz deleted file mode 100644 index 6af0e1b8aeb47b3ac9c5bea7dba0abc92e3fe9e4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 521 zcmV+k0`~nMiwFp){WeQOZ*BnXRb6k|Fcf{yuRwV&RveO!=53&s87Wi@ z#@bF(7IM;)v?a?%|>Z7)U=4GYBf}k6ZJ|0 zE9_?y*@!@dEc9qD2_V2Bp3#7YY15@RO?LEJ2j|d2R(TSUH0wFb4`{-(m{h%MwUW7K zHTF@(s_~}Gr*F6-H|I&}ut=sM&_N3r@3J8dUdlNKE{*}=L7nrWwi3DnF(EWboRlwV zDATm)&)ptj_pFb;l?VKRKBwcikmeIqc+rI&Vv>?G`?)4^1wBXEMe9rH?+IqmW z(!Lw6?UHNug6D&gQP^b%BerJv`<;d)Hnv4xd}A9X{SOE;}n8{Fu| zQ@td`vqW%zydnaNV(w)mWunVf9e>8^YxwiB>mxqj??KGfZhxFu9&)sfEROi2j@56N$h=!*J0l4bwm^~91m=dFGL LEy(|MkO}|*NeBrP diff --git a/x-pack/test/functional/es_archives/beats/list/mappings.json b/x-pack/test/functional/es_archives/beats/list/mappings.json index 0057b0b765773..caabcc13a9353 100644 --- a/x-pack/test/functional/es_archives/beats/list/mappings.json +++ b/x-pack/test/functional/es_archives/beats/list/mappings.json @@ -77,9 +77,6 @@ "tags": { "type": "keyword" }, - "central_configuration_yml": { - "type": "text" - }, "metadata": { "dynamic": "true", "type": "object" @@ -90,4 +87,4 @@ } } } -} +} \ No newline at end of file diff --git a/x-pack/yarn.lock b/x-pack/yarn.lock index 42080936ded39..32a1d35628c7b 100644 --- a/x-pack/yarn.lock +++ b/x-pack/yarn.lock @@ -136,6 +136,10 @@ version "1.2.0" resolved "https://registry.yarnpkg.com/@types/events/-/events-1.2.0.tgz#81a6731ce4df43619e5c8c945383b3e62a89ea86" +"@types/expect.js@^0.3.29": + version "0.3.29" + resolved "https://registry.yarnpkg.com/@types/expect.js/-/expect.js-0.3.29.tgz#28dd359155b84b8ecb094afc3f4b74c3222dca3b" + "@types/form-data@^2.2.1": version "2.2.1" resolved "https://registry.yarnpkg.com/@types/form-data/-/form-data-2.2.1.tgz#ee2b3b8eaa11c0938289953606b745b738c54b1e" From 22bfd0fd5081d3623a84790cf7dfb8d8473faee5 Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Fri, 13 Jul 2018 12:16:37 -0400 Subject: [PATCH 21/94] fix bad rebase --- x-pack/plugins/beats/index.ts | 23 ++--- .../lib/adapters/beats/adapter_types.ts | 1 - .../beats/elasticsearch_beats_adapter.ts | 28 ------ .../adapters/beats/memory_beats_adapter.ts | 17 ---- .../kibana/kibana_framework_adapter.ts | 99 ------------------- .../kibana/testing_framework_adapter.ts | 79 --------------- .../framework/__tests__/kibana.test.ts | 2 +- .../domains/__tests__/beats/enroll.test.ts | 1 + x-pack/yarn.lock | 4 - 9 files changed, 14 insertions(+), 240 deletions(-) delete mode 100644 x-pack/plugins/beats/server/lib/adapters/famework/kibana/kibana_framework_adapter.ts delete mode 100644 x-pack/plugins/beats/server/lib/adapters/famework/kibana/testing_framework_adapter.ts diff --git a/x-pack/plugins/beats/index.ts b/x-pack/plugins/beats/index.ts index ced89c186f73e..c62d1a6805225 100644 --- a/x-pack/plugins/beats/index.ts +++ b/x-pack/plugins/beats/index.ts @@ -3,25 +3,26 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - import Joi from 'joi'; import { PLUGIN } from './common/constants'; import { initServerWithKibana } from './server/kibana.index'; const DEFAULT_ENROLLMENT_TOKENS_TTL_S = 10 * 60; // 10 minutes +export const config = Joi.object({ + enabled: Joi.boolean().default(true), + encryptionKey: Joi.string(), + enrollmentTokensTtlInSeconds: Joi.number() + .integer() + .min(1) + .default(DEFAULT_ENROLLMENT_TOKENS_TTL_S), +}).default(); +export const configPrefix = 'xpack.beats'; + export function beats(kibana: any) { return new kibana.Plugin({ - config: () => - Joi.object({ - enabled: Joi.boolean().default(true), - encryptionKey: Joi.string(), - enrollmentTokensTtlInSeconds: Joi.number() - .integer() - .min(1) - .default(DEFAULT_ENROLLMENT_TOKENS_TTL_S), - }).default(), - configPrefix: 'xpack.beats', + config: () => config, + configPrefix, id: PLUGIN.ID, require: ['kibana', 'elasticsearch', 'xpack_main'], init(server: any) { diff --git a/x-pack/plugins/beats/server/lib/adapters/beats/adapter_types.ts b/x-pack/plugins/beats/server/lib/adapters/beats/adapter_types.ts index 8ce36456a703d..8812d9d39bd41 100644 --- a/x-pack/plugins/beats/server/lib/adapters/beats/adapter_types.ts +++ b/x-pack/plugins/beats/server/lib/adapters/beats/adapter_types.ts @@ -14,7 +14,6 @@ export interface CMBeatsAdapter { get(id: string): any; getAll(user: FrameworkUser): any; getWithIds(user: FrameworkUser, beatIds: string[]): any; - verifyBeats(user: FrameworkUser, beatIds: string[]): any; removeTagsFromBeats( user: FrameworkUser, removals: BeatsTagAssignment[] diff --git a/x-pack/plugins/beats/server/lib/adapters/beats/elasticsearch_beats_adapter.ts b/x-pack/plugins/beats/server/lib/adapters/beats/elasticsearch_beats_adapter.ts index c0be74e1629f6..9a3d067ffa35f 100644 --- a/x-pack/plugins/beats/server/lib/adapters/beats/elasticsearch_beats_adapter.ts +++ b/x-pack/plugins/beats/server/lib/adapters/beats/elasticsearch_beats_adapter.ts @@ -5,7 +5,6 @@ */ import { flatten, get as _get, omit } from 'lodash'; -import moment from 'moment'; import { INDEX_NAMES } from '../../../../common/constants'; import { CMBeat } from '../../../../common/domain_types'; import { DatabaseAdapter } from '../database/adapter_types'; @@ -87,33 +86,6 @@ export class ElasticsearchBeatsAdapter implements CMBeatsAdapter { .map((b: any) => b._source.beat); } - public async verifyBeats(user: FrameworkUser, beatIds: string[]) { - if (!Array.isArray(beatIds) || beatIds.length === 0) { - return []; - } - - const verifiedOn = moment().toJSON(); - const body = flatten( - beatIds.map(beatId => [ - { update: { _id: `beat:${beatId}` } }, - { doc: { beat: { verified_on: verifiedOn } } }, - ]) - ); - - const response = await this.database.bulk(user, { - _sourceInclude: ['beat.id', 'beat.verified_on'], - body, - index: INDEX_NAMES.BEATS, - refresh: 'wait_for', - type: '_doc', - }); - - return _get(response, 'items', []).map(b => ({ - ..._get(b, 'update.get._source.beat', {}), - updateStatus: _get(b, 'update.result', 'unknown error'), - })); - } - public async getAll(user: FrameworkUser) { const params = { index: INDEX_NAMES.BEATS, diff --git a/x-pack/plugins/beats/server/lib/adapters/beats/memory_beats_adapter.ts b/x-pack/plugins/beats/server/lib/adapters/beats/memory_beats_adapter.ts index 041b34d29b49e..1762ff93fc185 100644 --- a/x-pack/plugins/beats/server/lib/adapters/beats/memory_beats_adapter.ts +++ b/x-pack/plugins/beats/server/lib/adapters/beats/memory_beats_adapter.ts @@ -5,7 +5,6 @@ */ import { omit } from 'lodash'; -import moment from 'moment'; import { CMBeat } from '../../../../common/domain_types'; import { FrameworkUser } from '../framework/adapter_types'; @@ -39,22 +38,6 @@ export class MemoryBeatsAdapter implements CMBeatsAdapter { return this.beatsDB.filter(beat => beatIds.includes(beat.id)); } - public async verifyBeats(user: FrameworkUser, beatIds: string[]) { - if (!Array.isArray(beatIds) || beatIds.length === 0) { - return []; - } - - const verifiedOn = moment().toJSON(); - - this.beatsDB.forEach((beat, i) => { - if (beatIds.includes(beat.id)) { - this.beatsDB[i].verified_on = verifiedOn; - } - }); - - return this.beatsDB.filter(beat => beatIds.includes(beat.id)); - } - public async getAll(user: FrameworkUser) { return this.beatsDB.map((beat: any) => omit(beat, ['access_token'])); } diff --git a/x-pack/plugins/beats/server/lib/adapters/famework/kibana/kibana_framework_adapter.ts b/x-pack/plugins/beats/server/lib/adapters/famework/kibana/kibana_framework_adapter.ts deleted file mode 100644 index 71703da8632ec..0000000000000 --- a/x-pack/plugins/beats/server/lib/adapters/famework/kibana/kibana_framework_adapter.ts +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { - BackendFrameworkAdapter, - FrameworkRequest, - FrameworkRouteOptions, - WrappableRequest, -} from '../../../lib'; - -import { IStrictReply, Request, Server } from 'hapi'; -import { internalFrameworkRequest, wrapRequest } from '../../../../utils/wrap_request'; - -export class KibanaBackendFrameworkAdapter implements BackendFrameworkAdapter { - public version: string; - private server: Server; - private cryptoHash: string | null; - - constructor(hapiServer: Server) { - this.server = hapiServer; - this.version = hapiServer.plugins.kibana.status.plugin.version; - this.cryptoHash = null; - - this.validateConfig(); - } - - public getSetting(settingPath: string) { - // TODO type check server properly - if (settingPath === 'xpack.beats.encryptionKey') { - // @ts-ignore - return this.server.config().get(settingPath) || this.cryptoHash; - } - // @ts-ignore - return this.server.config().get(settingPath) || this.cryptoHash; - } - - public exposeStaticDir(urlPath: string, dir: string): void { - this.server.route({ - handler: { - directory: { - path: dir, - }, - }, - method: 'GET', - path: urlPath, - }); - } - - public registerRoute( - route: FrameworkRouteOptions - ) { - const wrappedHandler = (request: any, reply: IStrictReply) => - route.handler(wrapRequest(request), reply); - - this.server.route({ - config: route.config, - handler: wrappedHandler, - method: route.method, - path: route.path, - }); - } - - public installIndexTemplate(name: string, template: {}) { - return this.callWithInternalUser('indices.putTemplate', { - body: template, - name, - }); - } - - public async callWithInternalUser(esMethod: string, options: {}) { - const { elasticsearch } = this.server.plugins; - const { callWithInternalUser } = elasticsearch.getCluster('admin'); - return await callWithInternalUser(esMethod, options); - } - - public async callWithRequest(req: FrameworkRequest, ...rest: any[]) { - const internalRequest = req[internalFrameworkRequest]; - const { elasticsearch } = internalRequest.server.plugins; - const { callWithRequest } = elasticsearch.getCluster('data'); - const fields = await callWithRequest(internalRequest, ...rest); - return fields; - } - - private validateConfig() { - // @ts-ignore - const config = this.server.config(); - const encryptionKey = config.get('xpack.beats.encryptionKey'); - - if (!encryptionKey) { - this.server.log( - 'Using a default encryption key for xpack.beats.encryptionKey. It is recommended that you set xpack.beats.encryptionKey in kibana.yml with a unique token' - ); - this.cryptoHash = 'xpack_beats_default_encryptionKey'; - } - } -} diff --git a/x-pack/plugins/beats/server/lib/adapters/famework/kibana/testing_framework_adapter.ts b/x-pack/plugins/beats/server/lib/adapters/famework/kibana/testing_framework_adapter.ts deleted file mode 100644 index 757464fa6cdc7..0000000000000 --- a/x-pack/plugins/beats/server/lib/adapters/famework/kibana/testing_framework_adapter.ts +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { Client } from 'elasticsearch'; -import { Request } from 'hapi'; -import { get } from 'lodash'; -import { - BackendFrameworkAdapter, - FrameworkRequest, - FrameworkRouteOptions, - WrappableRequest, -} from '../../../lib'; - -interface TestSettings { - enrollmentTokensTtlInSeconds: number; - encryptionKey: string; -} - -export class TestingBackendFrameworkAdapter implements BackendFrameworkAdapter { - public version: string; - private client: Client | null; - private settings: TestSettings; - - constructor(client: Client | null, settings: TestSettings) { - this.client = client; - this.settings = settings || { - encryptionKey: 'something_who_cares', - enrollmentTokensTtlInSeconds: 10 * 60, // 10 minutes - }; - this.version = 'testing'; - } - - public getSetting(settingPath: string) { - switch (settingPath) { - case 'xpack.beats.enrollmentTokensTtlInSeconds': - return this.settings.enrollmentTokensTtlInSeconds; - case 'xpack.beats.encryptionKey': - return this.settings.encryptionKey; - } - } - - public exposeStaticDir(urlPath: string, dir: string): void { - // not yet testable - } - - public registerRoute( - route: FrameworkRouteOptions - ) { - // not yet testable - } - - public installIndexTemplate(name: string, template: {}) { - if (this.client) { - return this.client.indices.putTemplate({ - body: template, - name, - }); - } - } - - public async callWithInternalUser(esMethod: string, options: {}) { - const api = get(this.client, esMethod); - - api(options); - - return await api(options); - } - - public async callWithRequest(req: FrameworkRequest, esMethod: string, options: {}) { - const api = get(this.client, esMethod); - - api(options); - - return await api(options); - } -} diff --git a/x-pack/plugins/beats/server/lib/adapters/framework/__tests__/kibana.test.ts b/x-pack/plugins/beats/server/lib/adapters/framework/__tests__/kibana.test.ts index 5a539ebe6e5e7..74063e6316ceb 100644 --- a/x-pack/plugins/beats/server/lib/adapters/framework/__tests__/kibana.test.ts +++ b/x-pack/plugins/beats/server/lib/adapters/framework/__tests__/kibana.test.ts @@ -7,9 +7,9 @@ // @ts-ignore import { createEsTestCluster } from '@kbn/test'; -import { config as beatsPluginConfig, configPrefix } from '../../../../..'; // @ts-ignore import * as kbnTestServer from '../../../../../../../../src/test_utils/kbn_server'; +import { config as beatsPluginConfig, configPrefix } from '../../../../../index'; import { KibanaBackendFrameworkAdapter } from '../kibana_framework_adapter'; import { contractTests } from './test_contract'; diff --git a/x-pack/plugins/beats/server/lib/domains/__tests__/beats/enroll.test.ts b/x-pack/plugins/beats/server/lib/domains/__tests__/beats/enroll.test.ts index f60c1ed0e009e..9f42ad3c89f86 100644 --- a/x-pack/plugins/beats/server/lib/domains/__tests__/beats/enroll.test.ts +++ b/x-pack/plugins/beats/server/lib/domains/__tests__/beats/enroll.test.ts @@ -98,6 +98,7 @@ describe('Beats Domain Lib', () => { expect(beatsDB.length).toEqual(1); expect(beatsDB[0]).toHaveProperty('host_ip'); + expect(beatsDB[0]).toHaveProperty('verified_on'); expect(accessToken).toEqual(beatsDB[0].access_token); diff --git a/x-pack/yarn.lock b/x-pack/yarn.lock index 32a1d35628c7b..42080936ded39 100644 --- a/x-pack/yarn.lock +++ b/x-pack/yarn.lock @@ -136,10 +136,6 @@ version "1.2.0" resolved "https://registry.yarnpkg.com/@types/events/-/events-1.2.0.tgz#81a6731ce4df43619e5c8c945383b3e62a89ea86" -"@types/expect.js@^0.3.29": - version "0.3.29" - resolved "https://registry.yarnpkg.com/@types/expect.js/-/expect.js-0.3.29.tgz#28dd359155b84b8ecb094afc3f4b74c3222dca3b" - "@types/form-data@^2.2.1": version "2.2.1" resolved "https://registry.yarnpkg.com/@types/form-data/-/form-data-2.2.1.tgz#ee2b3b8eaa11c0938289953606b745b738c54b1e" From e31856edffdeb43f40b76c17be74aff41c748ba1 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Tue, 17 Jul 2018 14:58:32 -0400 Subject: [PATCH 22/94] [Beats Management] [WIP] Create public resources for management plugin (#20864) * Init plugin public resources. * rename beats to beats_management * rendering react now --- x-pack/index.js | 2 +- x-pack/package.json | 2 + .../common/constants/configuration_blocks.ts | 0 .../common/constants/index.ts | 1 + .../common/constants/index_names.ts | 0 .../common/constants/plugin.ts | 2 +- .../common/domain_types.ts | 0 .../{beats => beats_management}/index.ts | 10 +- .../plugins/beats_management/public/index.tsx | 22 +++ .../framework/kibana_framework_adapter.ts | 149 ++++++++++++++++++ .../public/lib/compose/kibana.ts | 29 ++++ .../beats_management/public/lib/lib.ts | 62 ++++++++ .../beats_management/public/pages/404.tsx | 13 ++ .../beats_management/public/pages/home.tsx | 13 ++ .../beats_management/public/routes.tsx | 22 +++ .../{beats => beats_management}/readme.md | 0 .../server/kibana.index.ts | 0 .../lib/adapters/beats/adapter_types.ts | 0 .../beats/elasticsearch_beats_adapter.ts | 0 .../adapters/beats/memory_beats_adapter.ts | 0 .../database/__tests__/kibana.test.ts | 0 .../database/__tests__/test_contract.ts | 0 .../lib/adapters/database/adapter_types.ts | 0 .../database/kibana_database_adapter.ts | 0 .../framework/__tests__/kibana.test.ts | 2 +- .../framework/__tests__/test_contract.ts | 0 .../lib/adapters/framework/adapter_types.ts | 0 .../framework/kibana_framework_adapter.ts | 0 .../framework/testing_framework_adapter.ts | 0 .../server/lib/adapters/tags/adapter_types.ts | 0 .../tags/elasticsearch_tags_adapter.ts | 2 +- .../lib/adapters/tags/memory_tags_adapter.ts | 0 .../lib/adapters/tokens/adapter_types.ts | 0 .../tokens/elasticsearch_tokens_adapter.ts | 0 .../adapters/tokens/memory_tokens_adapter.ts | 0 .../server/lib/compose/kibana.ts | 0 .../__tests__/beats/assign_tags.test.ts | 0 .../domains/__tests__/beats/enroll.test.ts | 0 .../lib/domains/__tests__/tokens.test.ts | 0 .../server/lib/domains/beats.ts | 2 +- .../server/lib/domains/tags.ts | 2 +- .../server/lib/domains/tokens.ts | 2 +- .../server/lib/lib.ts | 0 .../server/management_server.ts | 0 .../server/rest_api/beats/configuration.ts | 0 .../server/rest_api/beats/enroll.ts | 2 +- .../server/rest_api/beats/list.ts | 0 .../server/rest_api/beats/tag_assignment.ts | 0 .../server/rest_api/beats/tag_removal.ts | 0 .../server/rest_api/beats/update.ts | 0 .../server/rest_api/tags/set.ts | 0 .../server/rest_api/tokens/create.ts | 0 .../server/utils/README.md | 0 .../server/utils/error_wrappers/index.ts | 0 .../error_wrappers/wrap_es_error.test.ts | 0 .../utils/error_wrappers/wrap_es_error.ts | 0 .../server/utils/find_non_existent_items.ts | 0 .../utils/index_templates/beats_template.json | 0 .../server/utils/index_templates/index.ts | 0 .../server/utils/polyfills.ts | 0 .../server/utils/wrap_request.ts | 0 .../{beats => beats_management}/tsconfig.json | 0 .../types/json.t.ts | 0 .../{beats => beats_management}/wallaby.js | 0 x-pack/plugins/index_management/index.js | 8 +- x-pack/yarn.lock | 29 ++++ 66 files changed, 361 insertions(+), 15 deletions(-) rename x-pack/plugins/{beats => beats_management}/common/constants/configuration_blocks.ts (100%) rename x-pack/plugins/{beats => beats_management}/common/constants/index.ts (87%) rename x-pack/plugins/{beats => beats_management}/common/constants/index_names.ts (100%) rename x-pack/plugins/{beats => beats_management}/common/constants/plugin.ts (91%) rename x-pack/plugins/{beats => beats_management}/common/domain_types.ts (100%) rename x-pack/plugins/{beats => beats_management}/index.ts (86%) create mode 100644 x-pack/plugins/beats_management/public/index.tsx create mode 100644 x-pack/plugins/beats_management/public/lib/adapters/framework/kibana_framework_adapter.ts create mode 100644 x-pack/plugins/beats_management/public/lib/compose/kibana.ts create mode 100644 x-pack/plugins/beats_management/public/lib/lib.ts create mode 100644 x-pack/plugins/beats_management/public/pages/404.tsx create mode 100644 x-pack/plugins/beats_management/public/pages/home.tsx create mode 100644 x-pack/plugins/beats_management/public/routes.tsx rename x-pack/plugins/{beats => beats_management}/readme.md (100%) rename x-pack/plugins/{beats => beats_management}/server/kibana.index.ts (100%) rename x-pack/plugins/{beats => beats_management}/server/lib/adapters/beats/adapter_types.ts (100%) rename x-pack/plugins/{beats => beats_management}/server/lib/adapters/beats/elasticsearch_beats_adapter.ts (100%) rename x-pack/plugins/{beats => beats_management}/server/lib/adapters/beats/memory_beats_adapter.ts (100%) rename x-pack/plugins/{beats => beats_management}/server/lib/adapters/database/__tests__/kibana.test.ts (100%) rename x-pack/plugins/{beats => beats_management}/server/lib/adapters/database/__tests__/test_contract.ts (100%) rename x-pack/plugins/{beats => beats_management}/server/lib/adapters/database/adapter_types.ts (100%) rename x-pack/plugins/{beats => beats_management}/server/lib/adapters/database/kibana_database_adapter.ts (100%) rename x-pack/plugins/{beats => beats_management}/server/lib/adapters/framework/__tests__/kibana.test.ts (98%) rename x-pack/plugins/{beats => beats_management}/server/lib/adapters/framework/__tests__/test_contract.ts (100%) rename x-pack/plugins/{beats => beats_management}/server/lib/adapters/framework/adapter_types.ts (100%) rename x-pack/plugins/{beats => beats_management}/server/lib/adapters/framework/kibana_framework_adapter.ts (100%) rename x-pack/plugins/{beats => beats_management}/server/lib/adapters/framework/testing_framework_adapter.ts (100%) rename x-pack/plugins/{beats => beats_management}/server/lib/adapters/tags/adapter_types.ts (100%) rename x-pack/plugins/{beats => beats_management}/server/lib/adapters/tags/elasticsearch_tags_adapter.ts (96%) rename x-pack/plugins/{beats => beats_management}/server/lib/adapters/tags/memory_tags_adapter.ts (100%) rename x-pack/plugins/{beats => beats_management}/server/lib/adapters/tokens/adapter_types.ts (100%) rename x-pack/plugins/{beats => beats_management}/server/lib/adapters/tokens/elasticsearch_tokens_adapter.ts (100%) rename x-pack/plugins/{beats => beats_management}/server/lib/adapters/tokens/memory_tokens_adapter.ts (100%) rename x-pack/plugins/{beats => beats_management}/server/lib/compose/kibana.ts (100%) rename x-pack/plugins/{beats => beats_management}/server/lib/domains/__tests__/beats/assign_tags.test.ts (100%) rename x-pack/plugins/{beats => beats_management}/server/lib/domains/__tests__/beats/enroll.test.ts (100%) rename x-pack/plugins/{beats => beats_management}/server/lib/domains/__tests__/tokens.test.ts (100%) rename x-pack/plugins/{beats => beats_management}/server/lib/domains/beats.ts (98%) rename x-pack/plugins/{beats => beats_management}/server/lib/domains/tags.ts (98%) rename x-pack/plugins/{beats => beats_management}/server/lib/domains/tokens.ts (98%) rename x-pack/plugins/{beats => beats_management}/server/lib/lib.ts (100%) rename x-pack/plugins/{beats => beats_management}/server/management_server.ts (100%) rename x-pack/plugins/{beats => beats_management}/server/rest_api/beats/configuration.ts (100%) rename x-pack/plugins/{beats => beats_management}/server/rest_api/beats/enroll.ts (97%) rename x-pack/plugins/{beats => beats_management}/server/rest_api/beats/list.ts (100%) rename x-pack/plugins/{beats => beats_management}/server/rest_api/beats/tag_assignment.ts (100%) rename x-pack/plugins/{beats => beats_management}/server/rest_api/beats/tag_removal.ts (100%) rename x-pack/plugins/{beats => beats_management}/server/rest_api/beats/update.ts (100%) rename x-pack/plugins/{beats => beats_management}/server/rest_api/tags/set.ts (100%) rename x-pack/plugins/{beats => beats_management}/server/rest_api/tokens/create.ts (100%) rename x-pack/plugins/{beats => beats_management}/server/utils/README.md (100%) rename x-pack/plugins/{beats => beats_management}/server/utils/error_wrappers/index.ts (100%) rename x-pack/plugins/{beats => beats_management}/server/utils/error_wrappers/wrap_es_error.test.ts (100%) rename x-pack/plugins/{beats => beats_management}/server/utils/error_wrappers/wrap_es_error.ts (100%) rename x-pack/plugins/{beats => beats_management}/server/utils/find_non_existent_items.ts (100%) rename x-pack/plugins/{beats => beats_management}/server/utils/index_templates/beats_template.json (100%) rename x-pack/plugins/{beats => beats_management}/server/utils/index_templates/index.ts (100%) rename x-pack/plugins/{beats => beats_management}/server/utils/polyfills.ts (100%) rename x-pack/plugins/{beats => beats_management}/server/utils/wrap_request.ts (100%) rename x-pack/plugins/{beats => beats_management}/tsconfig.json (100%) rename x-pack/plugins/{beats => beats_management}/types/json.t.ts (100%) rename x-pack/plugins/{beats => beats_management}/wallaby.js (100%) diff --git a/x-pack/index.js b/x-pack/index.js index 6f5c12814997a..2a91738c67a32 100644 --- a/x-pack/index.js +++ b/x-pack/index.js @@ -16,7 +16,7 @@ import { watcher } from './plugins/watcher'; import { grokdebugger } from './plugins/grokdebugger'; import { dashboardMode } from './plugins/dashboard_mode'; import { logstash } from './plugins/logstash'; -import { beats } from './plugins/beats'; +import { beats } from './plugins/beats_management'; import { apm } from './plugins/apm'; import { licenseManagement } from './plugins/license_management'; import { cloud } from './plugins/cloud'; diff --git a/x-pack/package.json b/x-pack/package.json index 2f580180776fb..01d5e7cdfedb3 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -29,10 +29,12 @@ "@kbn/test": "link:../packages/kbn-test", "@types/boom": "^4.3.8", "@types/chance": "^1.0.1", + "@types/history": "^4.6.2", "@types/jest": "^22.2.3", "@types/joi": "^10.4.0", "@types/lodash": "^3.10.0", "@types/pngjs": "^3.3.0", + "@types/react-router-dom": "^4.2.7", "@types/sinon": "^5.0.1", "abab": "^1.0.4", "ansicolors": "0.3.2", diff --git a/x-pack/plugins/beats/common/constants/configuration_blocks.ts b/x-pack/plugins/beats_management/common/constants/configuration_blocks.ts similarity index 100% rename from x-pack/plugins/beats/common/constants/configuration_blocks.ts rename to x-pack/plugins/beats_management/common/constants/configuration_blocks.ts diff --git a/x-pack/plugins/beats/common/constants/index.ts b/x-pack/plugins/beats_management/common/constants/index.ts similarity index 87% rename from x-pack/plugins/beats/common/constants/index.ts rename to x-pack/plugins/beats_management/common/constants/index.ts index 756ffcf07e3ea..b4e919607c604 100644 --- a/x-pack/plugins/beats/common/constants/index.ts +++ b/x-pack/plugins/beats_management/common/constants/index.ts @@ -7,3 +7,4 @@ export { PLUGIN } from './plugin'; export { INDEX_NAMES } from './index_names'; export { UNIQUENESS_ENFORCING_TYPES, ConfigurationBlockTypes } from './configuration_blocks'; +export const BASE_PATH = '/management/beats_management/'; diff --git a/x-pack/plugins/beats/common/constants/index_names.ts b/x-pack/plugins/beats_management/common/constants/index_names.ts similarity index 100% rename from x-pack/plugins/beats/common/constants/index_names.ts rename to x-pack/plugins/beats_management/common/constants/index_names.ts diff --git a/x-pack/plugins/beats/common/constants/plugin.ts b/x-pack/plugins/beats_management/common/constants/plugin.ts similarity index 91% rename from x-pack/plugins/beats/common/constants/plugin.ts rename to x-pack/plugins/beats_management/common/constants/plugin.ts index ba12300075bf2..dc7cd85300341 100644 --- a/x-pack/plugins/beats/common/constants/plugin.ts +++ b/x-pack/plugins/beats_management/common/constants/plugin.ts @@ -5,5 +5,5 @@ */ export const PLUGIN = { - ID: 'beats', + ID: 'beats_management', }; diff --git a/x-pack/plugins/beats/common/domain_types.ts b/x-pack/plugins/beats_management/common/domain_types.ts similarity index 100% rename from x-pack/plugins/beats/common/domain_types.ts rename to x-pack/plugins/beats_management/common/domain_types.ts diff --git a/x-pack/plugins/beats/index.ts b/x-pack/plugins/beats_management/index.ts similarity index 86% rename from x-pack/plugins/beats/index.ts rename to x-pack/plugins/beats_management/index.ts index c62d1a6805225..e6677d6bbcd57 100644 --- a/x-pack/plugins/beats/index.ts +++ b/x-pack/plugins/beats_management/index.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import Joi from 'joi'; +import { resolve } from 'path'; import { PLUGIN } from './common/constants'; import { initServerWithKibana } from './server/kibana.index'; @@ -21,10 +22,15 @@ export const configPrefix = 'xpack.beats'; export function beats(kibana: any) { return new kibana.Plugin({ - config: () => config, - configPrefix, id: PLUGIN.ID, require: ['kibana', 'elasticsearch', 'xpack_main'], + publicDir: resolve(__dirname, 'public'), + uiExports: { + managementSections: ['plugins/beats_management'], + }, + config: () => config, + configPrefix, + init(server: any) { initServerWithKibana(server); }, diff --git a/x-pack/plugins/beats_management/public/index.tsx b/x-pack/plugins/beats_management/public/index.tsx new file mode 100644 index 0000000000000..1334deb1524e6 --- /dev/null +++ b/x-pack/plugins/beats_management/public/index.tsx @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { BASE_PATH } from '../common/constants'; +import { compose } from './lib/compose/kibana'; +// import * as euiVars from '@elastic/eui/dist/eui_theme_k6_light.json'; +// import { ThemeProvider } from 'styled-components'; +import { PageRouter } from './routes'; + +// TODO use theme provided from parentApp when kibana supports it +import '@elastic/eui/dist/eui_theme_light.css'; + +function startApp(libs: any) { + libs.framework.registerManagementSection('beats', 'Beats Management', BASE_PATH); + libs.framework.render(); +} + +startApp(compose()); diff --git a/x-pack/plugins/beats_management/public/lib/adapters/framework/kibana_framework_adapter.ts b/x-pack/plugins/beats_management/public/lib/adapters/framework/kibana_framework_adapter.ts new file mode 100644 index 0000000000000..042ebd71f9862 --- /dev/null +++ b/x-pack/plugins/beats_management/public/lib/adapters/framework/kibana_framework_adapter.ts @@ -0,0 +1,149 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IModule, IScope } from 'angular'; +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; + +import { + BufferedKibanaServiceCall, + FrameworkAdapter, + KibanaAdapterServiceRefs, + KibanaUIConfig, +} from '../../lib'; + +export class KibanaFrameworkAdapter implements FrameworkAdapter { + public appState: object; + public kbnVersion?: string; + + private management: any; + private adapterService: KibanaAdapterServiceProvider; + private rootComponent: React.ReactElement | null = null; + private uiModule: IModule; + private routes: any; + + constructor(uiModule: IModule, management: any, routes: any) { + this.adapterService = new KibanaAdapterServiceProvider(); + this.management = management; + this.uiModule = uiModule; + this.routes = routes; + this.appState = {}; + } + + public setUISettings = (key: string, value: any) => { + this.adapterService.callOrBuffer(({ config }) => { + config.set(key, value); + }); + }; + + public render = (component: React.ReactElement) => { + this.rootComponent = component; + }; + + public registerManagementSection(pluginId: string, displayName: string, basePath: string) { + const registerSection = () => + this.management.register(pluginId, { + display: displayName, + order: 30, + }); + const getSection = () => this.management.getSection(pluginId); + + const section = this.management.hasItem(pluginId) ? getSection() : registerSection(); + + section.register(pluginId, { + visible: true, + display: displayName, + order: 30, + url: `#${basePath}`, + }); + + this.register(this.uiModule); + } + + private manageAngularLifecycle($scope: any, $route: any, elem: any) { + const lastRoute = $route.current; + const deregister = $scope.$on('$locationChangeSuccess', () => { + const currentRoute = $route.current; + // if templates are the same we are on the same route + if (lastRoute.$$route.template === currentRoute.$$route.template) { + // this prevents angular from destroying scope + $route.current = lastRoute; + } + }); + $scope.$on('$destroy', () => { + if (deregister) { + deregister(); + } + // manually unmount component when scope is destroyed + if (elem) { + ReactDOM.unmountComponentAtNode(elem); + } + }); + } + + private register = (adapterModule: IModule) => { + const adapter = this; + this.routes.when(`/management/beats_management/?`, { + template: '
', + controllerAs: 'beatsManagement', + // tslint:disable-next-line: max-classes-per-file + controller: class BeatsManagementController { + constructor($scope: any, $route: any) { + $scope.$$postDigest(() => { + const elem = document.getElementById('beatsReactRoot'); + ReactDOM.render(adapter.rootComponent as React.ReactElement, elem); + adapter.manageAngularLifecycle($scope, $route, elem); + }); + $scope.$onInit = () => { + $scope.topNavMenu = []; + }; + } + }, + }); + }; +} + +// tslint:disable-next-line: max-classes-per-file +class KibanaAdapterServiceProvider { + public serviceRefs: KibanaAdapterServiceRefs | null = null; + public bufferedCalls: Array> = []; + + public $get($rootScope: IScope, config: KibanaUIConfig) { + this.serviceRefs = { + config, + rootScope: $rootScope, + }; + + this.applyBufferedCalls(this.bufferedCalls); + + return this; + } + + public callOrBuffer(serviceCall: (serviceRefs: KibanaAdapterServiceRefs) => void) { + if (this.serviceRefs !== null) { + this.applyBufferedCalls([serviceCall]); + } else { + this.bufferedCalls.push(serviceCall); + } + } + + public applyBufferedCalls( + bufferedCalls: Array> + ) { + if (!this.serviceRefs) { + return; + } + + this.serviceRefs.rootScope.$apply(() => { + bufferedCalls.forEach(serviceCall => { + if (!this.serviceRefs) { + return; + } + return serviceCall(this.serviceRefs); + }); + }); + } +} diff --git a/x-pack/plugins/beats_management/public/lib/compose/kibana.ts b/x-pack/plugins/beats_management/public/lib/compose/kibana.ts new file mode 100644 index 0000000000000..7a98e62c18459 --- /dev/null +++ b/x-pack/plugins/beats_management/public/lib/compose/kibana.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import 'ui/autoload/all'; +// @ts-ignore: path dynamic for kibana +import { management } from 'ui/management'; +// @ts-ignore: path dynamic for kibana +import { uiModules } from 'ui/modules'; +// @ts-ignore: path dynamic for kibana +import routes from 'ui/routes'; +// @ts-ignore: path dynamic for kibana +import { KibanaFrameworkAdapter } from '../adapters/framework/kibana_framework_adapter'; +import { FrontendLibs } from '../lib'; + +export function compose(): FrontendLibs { + // const kbnVersion = (window as any).__KBN__.version; + + const pluginUIModule = uiModules.get('app/beats_management'); + + const framework = new KibanaFrameworkAdapter(pluginUIModule, management, routes); + + const libs: FrontendLibs = { + framework, + }; + return libs; +} diff --git a/x-pack/plugins/beats_management/public/lib/lib.ts b/x-pack/plugins/beats_management/public/lib/lib.ts new file mode 100644 index 0000000000000..085efc3e53201 --- /dev/null +++ b/x-pack/plugins/beats_management/public/lib/lib.ts @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IModule, IScope } from 'angular'; +import { AxiosRequestConfig } from 'axios'; +import React from 'react'; + +export interface FrontendLibs { + framework: FrameworkAdapter; + // api: ApiAdapter; +} + +export interface FrameworkAdapter { + // Insstance vars + appState?: object; + kbnVersion?: string; + registerManagementSection(pluginId: string, displayName: string, basePath: string): void; + + // Methods + setUISettings(key: string, value: any): void; + render(component: React.ReactElement): void; +} + +export interface FramworkAdapterConstructable { + new (uiModule: IModule): FrameworkAdapter; +} + +// TODO: replace AxiosRequestConfig with something more defined +export type RequestConfig = AxiosRequestConfig; + +export interface ApiAdapter { + kbnVersion: string; + + get(url: string, config?: RequestConfig | undefined): Promise; + post(url: string, data?: any, config?: AxiosRequestConfig | undefined): Promise; + delete(url: string, config?: RequestConfig | undefined): Promise; + put(url: string, data?: any, config?: RequestConfig | undefined): Promise; +} + +export interface UiKibanaAdapterScope extends IScope { + breadcrumbs: any[]; + topNavMenu: any[]; +} + +export interface KibanaUIConfig { + get(key: string): any; + set(key: string, value: any): Promise; +} + +export interface KibanaAdapterServiceRefs { + config: KibanaUIConfig; + rootScope: IScope; +} + +export type BufferedKibanaServiceCall = (serviceRefs: ServiceRefs) => void; + +export interface Chrome { + setRootTemplate(template: string): void; +} diff --git a/x-pack/plugins/beats_management/public/pages/404.tsx b/x-pack/plugins/beats_management/public/pages/404.tsx new file mode 100644 index 0000000000000..956bf90e84927 --- /dev/null +++ b/x-pack/plugins/beats_management/public/pages/404.tsx @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +export class NotFoundPage extends React.PureComponent { + public render() { + return
No content found
; + } +} diff --git a/x-pack/plugins/beats_management/public/pages/home.tsx b/x-pack/plugins/beats_management/public/pages/home.tsx new file mode 100644 index 0000000000000..505015f18af16 --- /dev/null +++ b/x-pack/plugins/beats_management/public/pages/home.tsx @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +export class HomePage extends React.PureComponent { + public render() { + return
Home
; + } +} diff --git a/x-pack/plugins/beats_management/public/routes.tsx b/x-pack/plugins/beats_management/public/routes.tsx new file mode 100644 index 0000000000000..f5863b28aaafa --- /dev/null +++ b/x-pack/plugins/beats_management/public/routes.tsx @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { HashRouter, Route, Switch } from 'react-router-dom'; + +import { NotFoundPage } from './pages/404'; +import { HomePage } from './pages/home'; + +export const PageRouter: React.SFC<{}> = () => { + return ( + + + + + + + ); +}; diff --git a/x-pack/plugins/beats/readme.md b/x-pack/plugins/beats_management/readme.md similarity index 100% rename from x-pack/plugins/beats/readme.md rename to x-pack/plugins/beats_management/readme.md diff --git a/x-pack/plugins/beats/server/kibana.index.ts b/x-pack/plugins/beats_management/server/kibana.index.ts similarity index 100% rename from x-pack/plugins/beats/server/kibana.index.ts rename to x-pack/plugins/beats_management/server/kibana.index.ts diff --git a/x-pack/plugins/beats/server/lib/adapters/beats/adapter_types.ts b/x-pack/plugins/beats_management/server/lib/adapters/beats/adapter_types.ts similarity index 100% rename from x-pack/plugins/beats/server/lib/adapters/beats/adapter_types.ts rename to x-pack/plugins/beats_management/server/lib/adapters/beats/adapter_types.ts diff --git a/x-pack/plugins/beats/server/lib/adapters/beats/elasticsearch_beats_adapter.ts b/x-pack/plugins/beats_management/server/lib/adapters/beats/elasticsearch_beats_adapter.ts similarity index 100% rename from x-pack/plugins/beats/server/lib/adapters/beats/elasticsearch_beats_adapter.ts rename to x-pack/plugins/beats_management/server/lib/adapters/beats/elasticsearch_beats_adapter.ts diff --git a/x-pack/plugins/beats/server/lib/adapters/beats/memory_beats_adapter.ts b/x-pack/plugins/beats_management/server/lib/adapters/beats/memory_beats_adapter.ts similarity index 100% rename from x-pack/plugins/beats/server/lib/adapters/beats/memory_beats_adapter.ts rename to x-pack/plugins/beats_management/server/lib/adapters/beats/memory_beats_adapter.ts diff --git a/x-pack/plugins/beats/server/lib/adapters/database/__tests__/kibana.test.ts b/x-pack/plugins/beats_management/server/lib/adapters/database/__tests__/kibana.test.ts similarity index 100% rename from x-pack/plugins/beats/server/lib/adapters/database/__tests__/kibana.test.ts rename to x-pack/plugins/beats_management/server/lib/adapters/database/__tests__/kibana.test.ts diff --git a/x-pack/plugins/beats/server/lib/adapters/database/__tests__/test_contract.ts b/x-pack/plugins/beats_management/server/lib/adapters/database/__tests__/test_contract.ts similarity index 100% rename from x-pack/plugins/beats/server/lib/adapters/database/__tests__/test_contract.ts rename to x-pack/plugins/beats_management/server/lib/adapters/database/__tests__/test_contract.ts diff --git a/x-pack/plugins/beats/server/lib/adapters/database/adapter_types.ts b/x-pack/plugins/beats_management/server/lib/adapters/database/adapter_types.ts similarity index 100% rename from x-pack/plugins/beats/server/lib/adapters/database/adapter_types.ts rename to x-pack/plugins/beats_management/server/lib/adapters/database/adapter_types.ts diff --git a/x-pack/plugins/beats/server/lib/adapters/database/kibana_database_adapter.ts b/x-pack/plugins/beats_management/server/lib/adapters/database/kibana_database_adapter.ts similarity index 100% rename from x-pack/plugins/beats/server/lib/adapters/database/kibana_database_adapter.ts rename to x-pack/plugins/beats_management/server/lib/adapters/database/kibana_database_adapter.ts diff --git a/x-pack/plugins/beats/server/lib/adapters/framework/__tests__/kibana.test.ts b/x-pack/plugins/beats_management/server/lib/adapters/framework/__tests__/kibana.test.ts similarity index 98% rename from x-pack/plugins/beats/server/lib/adapters/framework/__tests__/kibana.test.ts rename to x-pack/plugins/beats_management/server/lib/adapters/framework/__tests__/kibana.test.ts index 74063e6316ceb..5a539ebe6e5e7 100644 --- a/x-pack/plugins/beats/server/lib/adapters/framework/__tests__/kibana.test.ts +++ b/x-pack/plugins/beats_management/server/lib/adapters/framework/__tests__/kibana.test.ts @@ -7,9 +7,9 @@ // @ts-ignore import { createEsTestCluster } from '@kbn/test'; +import { config as beatsPluginConfig, configPrefix } from '../../../../..'; // @ts-ignore import * as kbnTestServer from '../../../../../../../../src/test_utils/kbn_server'; -import { config as beatsPluginConfig, configPrefix } from '../../../../../index'; import { KibanaBackendFrameworkAdapter } from '../kibana_framework_adapter'; import { contractTests } from './test_contract'; diff --git a/x-pack/plugins/beats/server/lib/adapters/framework/__tests__/test_contract.ts b/x-pack/plugins/beats_management/server/lib/adapters/framework/__tests__/test_contract.ts similarity index 100% rename from x-pack/plugins/beats/server/lib/adapters/framework/__tests__/test_contract.ts rename to x-pack/plugins/beats_management/server/lib/adapters/framework/__tests__/test_contract.ts diff --git a/x-pack/plugins/beats/server/lib/adapters/framework/adapter_types.ts b/x-pack/plugins/beats_management/server/lib/adapters/framework/adapter_types.ts similarity index 100% rename from x-pack/plugins/beats/server/lib/adapters/framework/adapter_types.ts rename to x-pack/plugins/beats_management/server/lib/adapters/framework/adapter_types.ts diff --git a/x-pack/plugins/beats/server/lib/adapters/framework/kibana_framework_adapter.ts b/x-pack/plugins/beats_management/server/lib/adapters/framework/kibana_framework_adapter.ts similarity index 100% rename from x-pack/plugins/beats/server/lib/adapters/framework/kibana_framework_adapter.ts rename to x-pack/plugins/beats_management/server/lib/adapters/framework/kibana_framework_adapter.ts diff --git a/x-pack/plugins/beats/server/lib/adapters/framework/testing_framework_adapter.ts b/x-pack/plugins/beats_management/server/lib/adapters/framework/testing_framework_adapter.ts similarity index 100% rename from x-pack/plugins/beats/server/lib/adapters/framework/testing_framework_adapter.ts rename to x-pack/plugins/beats_management/server/lib/adapters/framework/testing_framework_adapter.ts diff --git a/x-pack/plugins/beats/server/lib/adapters/tags/adapter_types.ts b/x-pack/plugins/beats_management/server/lib/adapters/tags/adapter_types.ts similarity index 100% rename from x-pack/plugins/beats/server/lib/adapters/tags/adapter_types.ts rename to x-pack/plugins/beats_management/server/lib/adapters/tags/adapter_types.ts diff --git a/x-pack/plugins/beats/server/lib/adapters/tags/elasticsearch_tags_adapter.ts b/x-pack/plugins/beats_management/server/lib/adapters/tags/elasticsearch_tags_adapter.ts similarity index 96% rename from x-pack/plugins/beats/server/lib/adapters/tags/elasticsearch_tags_adapter.ts rename to x-pack/plugins/beats_management/server/lib/adapters/tags/elasticsearch_tags_adapter.ts index 3f982f5edbb09..2c2c988189283 100644 --- a/x-pack/plugins/beats/server/lib/adapters/tags/elasticsearch_tags_adapter.ts +++ b/x-pack/plugins/beats_management/server/lib/adapters/tags/elasticsearch_tags_adapter.ts @@ -6,7 +6,7 @@ import { get } from 'lodash'; import { INDEX_NAMES } from '../../../../common/constants'; -import { FrameworkUser } from './../framework/adapter_types'; +import { FrameworkUser } from '../framework/adapter_types'; import { BeatTag } from '../../../../common/domain_types'; import { DatabaseAdapter } from '../database/adapter_types'; diff --git a/x-pack/plugins/beats/server/lib/adapters/tags/memory_tags_adapter.ts b/x-pack/plugins/beats_management/server/lib/adapters/tags/memory_tags_adapter.ts similarity index 100% rename from x-pack/plugins/beats/server/lib/adapters/tags/memory_tags_adapter.ts rename to x-pack/plugins/beats_management/server/lib/adapters/tags/memory_tags_adapter.ts diff --git a/x-pack/plugins/beats/server/lib/adapters/tokens/adapter_types.ts b/x-pack/plugins/beats_management/server/lib/adapters/tokens/adapter_types.ts similarity index 100% rename from x-pack/plugins/beats/server/lib/adapters/tokens/adapter_types.ts rename to x-pack/plugins/beats_management/server/lib/adapters/tokens/adapter_types.ts diff --git a/x-pack/plugins/beats/server/lib/adapters/tokens/elasticsearch_tokens_adapter.ts b/x-pack/plugins/beats_management/server/lib/adapters/tokens/elasticsearch_tokens_adapter.ts similarity index 100% rename from x-pack/plugins/beats/server/lib/adapters/tokens/elasticsearch_tokens_adapter.ts rename to x-pack/plugins/beats_management/server/lib/adapters/tokens/elasticsearch_tokens_adapter.ts diff --git a/x-pack/plugins/beats/server/lib/adapters/tokens/memory_tokens_adapter.ts b/x-pack/plugins/beats_management/server/lib/adapters/tokens/memory_tokens_adapter.ts similarity index 100% rename from x-pack/plugins/beats/server/lib/adapters/tokens/memory_tokens_adapter.ts rename to x-pack/plugins/beats_management/server/lib/adapters/tokens/memory_tokens_adapter.ts diff --git a/x-pack/plugins/beats/server/lib/compose/kibana.ts b/x-pack/plugins/beats_management/server/lib/compose/kibana.ts similarity index 100% rename from x-pack/plugins/beats/server/lib/compose/kibana.ts rename to x-pack/plugins/beats_management/server/lib/compose/kibana.ts diff --git a/x-pack/plugins/beats/server/lib/domains/__tests__/beats/assign_tags.test.ts b/x-pack/plugins/beats_management/server/lib/domains/__tests__/beats/assign_tags.test.ts similarity index 100% rename from x-pack/plugins/beats/server/lib/domains/__tests__/beats/assign_tags.test.ts rename to x-pack/plugins/beats_management/server/lib/domains/__tests__/beats/assign_tags.test.ts diff --git a/x-pack/plugins/beats/server/lib/domains/__tests__/beats/enroll.test.ts b/x-pack/plugins/beats_management/server/lib/domains/__tests__/beats/enroll.test.ts similarity index 100% rename from x-pack/plugins/beats/server/lib/domains/__tests__/beats/enroll.test.ts rename to x-pack/plugins/beats_management/server/lib/domains/__tests__/beats/enroll.test.ts diff --git a/x-pack/plugins/beats/server/lib/domains/__tests__/tokens.test.ts b/x-pack/plugins/beats_management/server/lib/domains/__tests__/tokens.test.ts similarity index 100% rename from x-pack/plugins/beats/server/lib/domains/__tests__/tokens.test.ts rename to x-pack/plugins/beats_management/server/lib/domains/__tests__/tokens.test.ts diff --git a/x-pack/plugins/beats/server/lib/domains/beats.ts b/x-pack/plugins/beats_management/server/lib/domains/beats.ts similarity index 98% rename from x-pack/plugins/beats/server/lib/domains/beats.ts rename to x-pack/plugins/beats_management/server/lib/domains/beats.ts index 07a084e54b53d..618de8ab0b446 100644 --- a/x-pack/plugins/beats/server/lib/domains/beats.ts +++ b/x-pack/plugins/beats_management/server/lib/domains/beats.ts @@ -13,8 +13,8 @@ import { BeatsTagAssignment, CMBeatsAdapter } from '../adapters/beats/adapter_ty import { FrameworkUser } from '../adapters/framework/adapter_types'; import { CMAssignmentReturn } from '../adapters/beats/adapter_types'; +import { BeatsRemovalReturn } from '../adapters/beats/adapter_types'; import { BeatEnrollmentStatus, CMDomainLibs } from '../lib'; -import { BeatsRemovalReturn } from './../adapters/beats/adapter_types'; export class CMBeatsDomain { private adapter: CMBeatsAdapter; diff --git a/x-pack/plugins/beats/server/lib/domains/tags.ts b/x-pack/plugins/beats_management/server/lib/domains/tags.ts similarity index 98% rename from x-pack/plugins/beats/server/lib/domains/tags.ts rename to x-pack/plugins/beats_management/server/lib/domains/tags.ts index b6ce9f7e14821..36cf9a5e6d7f0 100644 --- a/x-pack/plugins/beats/server/lib/domains/tags.ts +++ b/x-pack/plugins/beats_management/server/lib/domains/tags.ts @@ -9,8 +9,8 @@ import { UNIQUENESS_ENFORCING_TYPES } from '../../../common/constants'; import { ConfigurationBlock } from '../../../common/domain_types'; import { FrameworkUser } from '../adapters/framework/adapter_types'; +import { entries } from '../../utils/polyfills'; import { CMTagsAdapter } from '../adapters/tags/adapter_types'; -import { entries } from './../../utils/polyfills'; export class CMTagsDomain { private adapter: CMTagsAdapter; diff --git a/x-pack/plugins/beats/server/lib/domains/tokens.ts b/x-pack/plugins/beats_management/server/lib/domains/tokens.ts similarity index 98% rename from x-pack/plugins/beats/server/lib/domains/tokens.ts rename to x-pack/plugins/beats_management/server/lib/domains/tokens.ts index dc7ffa9a63356..529a526bea75d 100644 --- a/x-pack/plugins/beats/server/lib/domains/tokens.ts +++ b/x-pack/plugins/beats_management/server/lib/domains/tokens.ts @@ -8,8 +8,8 @@ import { sign as signToken, verify as verifyToken } from 'jsonwebtoken'; import moment from 'moment'; import uuid from 'uuid'; import { BackendFrameworkAdapter } from '../adapters/framework/adapter_types'; +import { FrameworkUser } from '../adapters/framework/adapter_types'; import { CMTokensAdapter } from '../adapters/tokens/adapter_types'; -import { FrameworkUser } from './../adapters/framework/adapter_types'; const RANDOM_TOKEN_1 = 'b48c4bda384a40cb91c6eb9b8849e77f'; const RANDOM_TOKEN_2 = '80a3819e3cd64f4399f1d4886be7a08b'; diff --git a/x-pack/plugins/beats/server/lib/lib.ts b/x-pack/plugins/beats_management/server/lib/lib.ts similarity index 100% rename from x-pack/plugins/beats/server/lib/lib.ts rename to x-pack/plugins/beats_management/server/lib/lib.ts diff --git a/x-pack/plugins/beats/server/management_server.ts b/x-pack/plugins/beats_management/server/management_server.ts similarity index 100% rename from x-pack/plugins/beats/server/management_server.ts rename to x-pack/plugins/beats_management/server/management_server.ts diff --git a/x-pack/plugins/beats/server/rest_api/beats/configuration.ts b/x-pack/plugins/beats_management/server/rest_api/beats/configuration.ts similarity index 100% rename from x-pack/plugins/beats/server/rest_api/beats/configuration.ts rename to x-pack/plugins/beats_management/server/rest_api/beats/configuration.ts diff --git a/x-pack/plugins/beats/server/rest_api/beats/enroll.ts b/x-pack/plugins/beats_management/server/rest_api/beats/enroll.ts similarity index 97% rename from x-pack/plugins/beats/server/rest_api/beats/enroll.ts rename to x-pack/plugins/beats_management/server/rest_api/beats/enroll.ts index c5b01fdbd4cc3..c0f0185fbde25 100644 --- a/x-pack/plugins/beats/server/rest_api/beats/enroll.ts +++ b/x-pack/plugins/beats_management/server/rest_api/beats/enroll.ts @@ -7,8 +7,8 @@ import Joi from 'joi'; import { omit } from 'lodash'; import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types'; import { CMServerLibs } from '../../lib/lib'; +import { BeatEnrollmentStatus } from '../../lib/lib'; import { wrapEsError } from '../../utils/error_wrappers'; -import { BeatEnrollmentStatus } from './../../lib/lib'; // TODO: add license check pre-hook // TODO: write to Kibana audit log file diff --git a/x-pack/plugins/beats/server/rest_api/beats/list.ts b/x-pack/plugins/beats_management/server/rest_api/beats/list.ts similarity index 100% rename from x-pack/plugins/beats/server/rest_api/beats/list.ts rename to x-pack/plugins/beats_management/server/rest_api/beats/list.ts diff --git a/x-pack/plugins/beats/server/rest_api/beats/tag_assignment.ts b/x-pack/plugins/beats_management/server/rest_api/beats/tag_assignment.ts similarity index 100% rename from x-pack/plugins/beats/server/rest_api/beats/tag_assignment.ts rename to x-pack/plugins/beats_management/server/rest_api/beats/tag_assignment.ts diff --git a/x-pack/plugins/beats/server/rest_api/beats/tag_removal.ts b/x-pack/plugins/beats_management/server/rest_api/beats/tag_removal.ts similarity index 100% rename from x-pack/plugins/beats/server/rest_api/beats/tag_removal.ts rename to x-pack/plugins/beats_management/server/rest_api/beats/tag_removal.ts diff --git a/x-pack/plugins/beats/server/rest_api/beats/update.ts b/x-pack/plugins/beats_management/server/rest_api/beats/update.ts similarity index 100% rename from x-pack/plugins/beats/server/rest_api/beats/update.ts rename to x-pack/plugins/beats_management/server/rest_api/beats/update.ts diff --git a/x-pack/plugins/beats/server/rest_api/tags/set.ts b/x-pack/plugins/beats_management/server/rest_api/tags/set.ts similarity index 100% rename from x-pack/plugins/beats/server/rest_api/tags/set.ts rename to x-pack/plugins/beats_management/server/rest_api/tags/set.ts diff --git a/x-pack/plugins/beats/server/rest_api/tokens/create.ts b/x-pack/plugins/beats_management/server/rest_api/tokens/create.ts similarity index 100% rename from x-pack/plugins/beats/server/rest_api/tokens/create.ts rename to x-pack/plugins/beats_management/server/rest_api/tokens/create.ts diff --git a/x-pack/plugins/beats/server/utils/README.md b/x-pack/plugins/beats_management/server/utils/README.md similarity index 100% rename from x-pack/plugins/beats/server/utils/README.md rename to x-pack/plugins/beats_management/server/utils/README.md diff --git a/x-pack/plugins/beats/server/utils/error_wrappers/index.ts b/x-pack/plugins/beats_management/server/utils/error_wrappers/index.ts similarity index 100% rename from x-pack/plugins/beats/server/utils/error_wrappers/index.ts rename to x-pack/plugins/beats_management/server/utils/error_wrappers/index.ts diff --git a/x-pack/plugins/beats/server/utils/error_wrappers/wrap_es_error.test.ts b/x-pack/plugins/beats_management/server/utils/error_wrappers/wrap_es_error.test.ts similarity index 100% rename from x-pack/plugins/beats/server/utils/error_wrappers/wrap_es_error.test.ts rename to x-pack/plugins/beats_management/server/utils/error_wrappers/wrap_es_error.test.ts diff --git a/x-pack/plugins/beats/server/utils/error_wrappers/wrap_es_error.ts b/x-pack/plugins/beats_management/server/utils/error_wrappers/wrap_es_error.ts similarity index 100% rename from x-pack/plugins/beats/server/utils/error_wrappers/wrap_es_error.ts rename to x-pack/plugins/beats_management/server/utils/error_wrappers/wrap_es_error.ts diff --git a/x-pack/plugins/beats/server/utils/find_non_existent_items.ts b/x-pack/plugins/beats_management/server/utils/find_non_existent_items.ts similarity index 100% rename from x-pack/plugins/beats/server/utils/find_non_existent_items.ts rename to x-pack/plugins/beats_management/server/utils/find_non_existent_items.ts diff --git a/x-pack/plugins/beats/server/utils/index_templates/beats_template.json b/x-pack/plugins/beats_management/server/utils/index_templates/beats_template.json similarity index 100% rename from x-pack/plugins/beats/server/utils/index_templates/beats_template.json rename to x-pack/plugins/beats_management/server/utils/index_templates/beats_template.json diff --git a/x-pack/plugins/beats/server/utils/index_templates/index.ts b/x-pack/plugins/beats_management/server/utils/index_templates/index.ts similarity index 100% rename from x-pack/plugins/beats/server/utils/index_templates/index.ts rename to x-pack/plugins/beats_management/server/utils/index_templates/index.ts diff --git a/x-pack/plugins/beats/server/utils/polyfills.ts b/x-pack/plugins/beats_management/server/utils/polyfills.ts similarity index 100% rename from x-pack/plugins/beats/server/utils/polyfills.ts rename to x-pack/plugins/beats_management/server/utils/polyfills.ts diff --git a/x-pack/plugins/beats/server/utils/wrap_request.ts b/x-pack/plugins/beats_management/server/utils/wrap_request.ts similarity index 100% rename from x-pack/plugins/beats/server/utils/wrap_request.ts rename to x-pack/plugins/beats_management/server/utils/wrap_request.ts diff --git a/x-pack/plugins/beats/tsconfig.json b/x-pack/plugins/beats_management/tsconfig.json similarity index 100% rename from x-pack/plugins/beats/tsconfig.json rename to x-pack/plugins/beats_management/tsconfig.json diff --git a/x-pack/plugins/beats/types/json.t.ts b/x-pack/plugins/beats_management/types/json.t.ts similarity index 100% rename from x-pack/plugins/beats/types/json.t.ts rename to x-pack/plugins/beats_management/types/json.t.ts diff --git a/x-pack/plugins/beats/wallaby.js b/x-pack/plugins/beats_management/wallaby.js similarity index 100% rename from x-pack/plugins/beats/wallaby.js rename to x-pack/plugins/beats_management/wallaby.js diff --git a/x-pack/plugins/index_management/index.js b/x-pack/plugins/index_management/index.js index c3d3e8fe1caf5..9a78cfef48c13 100644 --- a/x-pack/plugins/index_management/index.js +++ b/x-pack/plugins/index_management/index.js @@ -12,15 +12,13 @@ import { registerStatsRoute } from './server/routes/api/stats'; import { registerLicenseChecker } from './server/lib/register_license_checker'; import { PLUGIN } from './common/constants'; -export function indexManagement(kibana) { +export function indexManagement(kibana) { return new kibana.Plugin({ id: PLUGIN.ID, publicDir: resolve(__dirname, 'public'), require: ['kibana', 'elasticsearch', 'xpack_main'], uiExports: { - managementSections: [ - 'plugins/index_management', - ] + managementSections: ['plugins/index_management'], }, init: function (server) { registerLicenseChecker(server); @@ -28,6 +26,6 @@ export function indexManagement(kibana) { registerSettingsRoutes(server); registerStatsRoute(server); registerMappingRoute(server); - } + }, }); } diff --git a/x-pack/yarn.lock b/x-pack/yarn.lock index 42080936ded39..deb9b57e3acc0 100644 --- a/x-pack/yarn.lock +++ b/x-pack/yarn.lock @@ -148,6 +148,10 @@ dependencies: "@types/node" "*" +"@types/history@*", "@types/history@^4.6.2": + version "4.6.2" + resolved "https://registry.yarnpkg.com/@types/history/-/history-4.6.2.tgz#12cfaba693ba20f114ed5765467ff25fdf67ddb0" + "@types/is-stream@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@types/is-stream/-/is-stream-1.1.0.tgz#b84d7bb207a210f2af9bed431dc0fbe9c4143be1" @@ -198,6 +202,27 @@ dependencies: "@types/node" "*" +"@types/react-router-dom@^4.2.7": + version "4.2.7" + resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-4.2.7.tgz#9d36bfe175f916dd8d7b6b0237feed6cce376b4c" + dependencies: + "@types/history" "*" + "@types/react" "*" + "@types/react-router" "*" + +"@types/react-router@*": + version "4.0.29" + resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-4.0.29.tgz#1a906dd99abf21297a5b7cf003d1fd36e7a92069" + dependencies: + "@types/history" "*" + "@types/react" "*" + +"@types/react@*": + version "16.4.6" + resolved "https://registry.yarnpkg.com/@types/react/-/react-16.4.6.tgz#5024957c6bcef4f02823accf5974faba2e54fada" + dependencies: + csstype "^2.2.0" + "@types/retry@*", "@types/retry@^0.10.2": version "0.10.2" resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.10.2.tgz#bd1740c4ad51966609b058803ee6874577848b37" @@ -1709,6 +1734,10 @@ cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0": dependencies: cssom "0.3.x" +csstype@^2.2.0: + version "2.5.5" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.5.5.tgz#4125484a3d42189a863943f23b9e4b80fedfa106" + currently-unhandled@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" From d18218fa33df97db1f1c5a2f954a60672c228d49 Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Thu, 26 Jul 2018 11:40:02 -0400 Subject: [PATCH 23/94] Beats/initial ui (#20994) * initial layout and main nav * modal UI and pattern for UI established * fix path * wire up in-memroy adapters * tweak adapters --- .../public/components/layouts/primary.tsx | 43 ++++++ .../plugins/beats_management/public/index.tsx | 10 +- .../lib/adapters/beats/adapter_types.ts | 34 +++++ .../adapters/beats/memory_beats_adapter.ts | 79 ++++++++++++ .../framework/kibana_framework_adapter.ts | 5 +- .../public/lib/adapters/tags/adapter_types.ts | 11 ++ .../lib/adapters/tags/memory_tags_adapter.ts | 30 +++++ .../lib/adapters/tokens/adapter_types.ts | 13 ++ .../adapters/tokens/memory_tokens_adapter.ts | 16 +++ .../public/lib/compose/kibana.ts | 14 +- .../public/lib/compose/memory.ts | 40 ++++++ .../beats_management/public/lib/lib.ts | 12 +- .../public/pages/main/activity.tsx | 13 ++ .../public/pages/main/beats.tsx | 88 +++++++++++++ .../public/pages/main/index.tsx | 122 ++++++++++++++++++ .../public/pages/{home.tsx => main/tags.tsx} | 4 +- .../public/{routes.tsx => router.tsx} | 12 +- .../server/lib/adapters/tags/adapter_types.ts | 2 +- .../lib/adapters/tags/memory_tags_adapter.ts | 2 +- .../adapters/tokens/memory_tokens_adapter.ts | 2 +- .../__tests__/beats/assign_tags.test.ts | 2 +- 21 files changed, 531 insertions(+), 23 deletions(-) create mode 100644 x-pack/plugins/beats_management/public/components/layouts/primary.tsx create mode 100644 x-pack/plugins/beats_management/public/lib/adapters/beats/adapter_types.ts create mode 100644 x-pack/plugins/beats_management/public/lib/adapters/beats/memory_beats_adapter.ts create mode 100644 x-pack/plugins/beats_management/public/lib/adapters/tags/adapter_types.ts create mode 100644 x-pack/plugins/beats_management/public/lib/adapters/tags/memory_tags_adapter.ts create mode 100644 x-pack/plugins/beats_management/public/lib/adapters/tokens/adapter_types.ts create mode 100644 x-pack/plugins/beats_management/public/lib/adapters/tokens/memory_tokens_adapter.ts create mode 100644 x-pack/plugins/beats_management/public/lib/compose/memory.ts create mode 100644 x-pack/plugins/beats_management/public/pages/main/activity.tsx create mode 100644 x-pack/plugins/beats_management/public/pages/main/beats.tsx create mode 100644 x-pack/plugins/beats_management/public/pages/main/index.tsx rename x-pack/plugins/beats_management/public/pages/{home.tsx => main/tags.tsx} (75%) rename x-pack/plugins/beats_management/public/{routes.tsx => router.tsx} (52%) diff --git a/x-pack/plugins/beats_management/public/components/layouts/primary.tsx b/x-pack/plugins/beats_management/public/components/layouts/primary.tsx new file mode 100644 index 0000000000000..cd9e8076dd092 --- /dev/null +++ b/x-pack/plugins/beats_management/public/components/layouts/primary.tsx @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import styled from 'styled-components'; + +import { EuiPage, EuiPageBody, EuiPageContent, EuiPageContentBody, EuiTitle } from '@elastic/eui'; + +interface PrimaryLayoutProps { + title: string; + actionSection: React.ReactNode; +} + +const HeaderContainer = styled.div` + display: flex; + justify-content: space-between; + align-items: flex-start; + padding: 24px 24px 0; + margin-bottom: 16px; +`; + +export const PrimaryLayout: React.SFC = ({ + actionSection, + title, + children, +}) => ( + + + + + +

{title}

+
+ {actionSection} +
+ {children} +
+
+
+); diff --git a/x-pack/plugins/beats_management/public/index.tsx b/x-pack/plugins/beats_management/public/index.tsx index 1334deb1524e6..670aa23f8695b 100644 --- a/x-pack/plugins/beats_management/public/index.tsx +++ b/x-pack/plugins/beats_management/public/index.tsx @@ -6,17 +6,19 @@ import React from 'react'; import { BASE_PATH } from '../common/constants'; -import { compose } from './lib/compose/kibana'; +import { compose } from './lib/compose/memory'; +import { FrontendLibs } from './lib/lib'; + // import * as euiVars from '@elastic/eui/dist/eui_theme_k6_light.json'; // import { ThemeProvider } from 'styled-components'; -import { PageRouter } from './routes'; +import { PageRouter } from './router'; // TODO use theme provided from parentApp when kibana supports it import '@elastic/eui/dist/eui_theme_light.css'; -function startApp(libs: any) { +function startApp(libs: FrontendLibs) { libs.framework.registerManagementSection('beats', 'Beats Management', BASE_PATH); - libs.framework.render(); + libs.framework.render(); } startApp(compose()); diff --git a/x-pack/plugins/beats_management/public/lib/adapters/beats/adapter_types.ts b/x-pack/plugins/beats_management/public/lib/adapters/beats/adapter_types.ts new file mode 100644 index 0000000000000..444ae817088e7 --- /dev/null +++ b/x-pack/plugins/beats_management/public/lib/adapters/beats/adapter_types.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CMBeat } from '../../../../common/domain_types'; + +export interface CMBeatsAdapter { + get(id: string): Promise; + getAll(): Promise; + getWithIds(beatIds: string[]): Promise; + removeTagsFromBeats(removals: BeatsTagAssignment[]): Promise; + assignTagsToBeats(assignments: BeatsTagAssignment[]): Promise; +} + +export interface BeatsTagAssignment { + beatId: string; + tag: string; + idxInRequest?: number; +} + +interface BeatsReturnedTagAssignment { + status: number | null; + result?: string; +} + +export interface CMAssignmentReturn { + assignments: BeatsReturnedTagAssignment[]; +} + +export interface BeatsRemovalReturn { + removals: BeatsReturnedTagAssignment[]; +} diff --git a/x-pack/plugins/beats_management/public/lib/adapters/beats/memory_beats_adapter.ts b/x-pack/plugins/beats_management/public/lib/adapters/beats/memory_beats_adapter.ts new file mode 100644 index 0000000000000..e1929a7d6c69a --- /dev/null +++ b/x-pack/plugins/beats_management/public/lib/adapters/beats/memory_beats_adapter.ts @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { omit } from 'lodash'; + +import { CMBeat } from '../../../../common/domain_types'; +import { BeatsTagAssignment, CMBeatsAdapter } from './adapter_types'; + +export class MemoryBeatsAdapter implements CMBeatsAdapter { + private beatsDB: CMBeat[]; + + constructor(beatsDB: CMBeat[]) { + this.beatsDB = beatsDB; + } + + public async get(id: string) { + return this.beatsDB.find(beat => beat.id === id) || null; + } + + public async getWithIds(beatIds: string[]) { + return this.beatsDB.filter(beat => beatIds.includes(beat.id)); + } + + public async getAll() { + return this.beatsDB.map((beat: any) => omit(beat, ['access_token'])); + } + + public async removeTagsFromBeats(removals: BeatsTagAssignment[]): Promise { + const beatIds = removals.map(r => r.beatId); + + const response = this.beatsDB.filter(beat => beatIds.includes(beat.id)).map(beat => { + const tagData = removals.find(r => r.beatId === beat.id); + if (tagData) { + if (beat.tags) { + beat.tags = beat.tags.filter(tag => tag !== tagData.tag); + } + } + return beat; + }); + + return response.map((item: CMBeat, resultIdx: number) => ({ + idxInRequest: removals[resultIdx].idxInRequest, + result: 'updated', + status: 200, + })); + } + + public async assignTagsToBeats(assignments: BeatsTagAssignment[]): Promise { + const beatIds = assignments.map(r => r.beatId); + + this.beatsDB.filter(beat => beatIds.includes(beat.id)).map(beat => { + // get tags that need to be assigned to this beat + const tags = assignments + .filter(a => a.beatId === beat.id) + .map((t: BeatsTagAssignment) => t.tag); + + if (tags.length > 0) { + if (!beat.tags) { + beat.tags = []; + } + const nonExistingTags = tags.filter((t: string) => beat.tags && !beat.tags.includes(t)); + + if (nonExistingTags.length > 0) { + beat.tags = beat.tags.concat(nonExistingTags); + } + } + return beat; + }); + + return assignments.map((item: BeatsTagAssignment, resultIdx: number) => ({ + idxInRequest: assignments[resultIdx].idxInRequest, + result: 'updated', + status: 200, + })); + } +} diff --git a/x-pack/plugins/beats_management/public/lib/adapters/framework/kibana_framework_adapter.ts b/x-pack/plugins/beats_management/public/lib/adapters/framework/kibana_framework_adapter.ts index 042ebd71f9862..1a61a5581ce4e 100644 --- a/x-pack/plugins/beats_management/public/lib/adapters/framework/kibana_framework_adapter.ts +++ b/x-pack/plugins/beats_management/public/lib/adapters/framework/kibana_framework_adapter.ts @@ -86,8 +86,9 @@ export class KibanaFrameworkAdapter implements FrameworkAdapter { private register = (adapterModule: IModule) => { const adapter = this; - this.routes.when(`/management/beats_management/?`, { - template: '
', + this.routes.when(`/management/beats_management/:view?/:id?/:other?/:other2?`, { + template: + '
', controllerAs: 'beatsManagement', // tslint:disable-next-line: max-classes-per-file controller: class BeatsManagementController { diff --git a/x-pack/plugins/beats_management/public/lib/adapters/tags/adapter_types.ts b/x-pack/plugins/beats_management/public/lib/adapters/tags/adapter_types.ts new file mode 100644 index 0000000000000..7fceea057d4f9 --- /dev/null +++ b/x-pack/plugins/beats_management/public/lib/adapters/tags/adapter_types.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { BeatTag } from '../../../../common/domain_types'; + +export interface CMTagsAdapter { + getTagsWithIds(tagIds: string[]): Promise; + upsertTag(tag: BeatTag): Promise<{}>; +} diff --git a/x-pack/plugins/beats_management/public/lib/adapters/tags/memory_tags_adapter.ts b/x-pack/plugins/beats_management/public/lib/adapters/tags/memory_tags_adapter.ts new file mode 100644 index 0000000000000..1baf2a65bb46f --- /dev/null +++ b/x-pack/plugins/beats_management/public/lib/adapters/tags/memory_tags_adapter.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { BeatTag } from '../../../../common/domain_types'; +import { CMTagsAdapter } from './adapter_types'; + +export class MemoryTagsAdapter implements CMTagsAdapter { + private tagsDB: BeatTag[] = []; + + constructor(tagsDB: BeatTag[]) { + this.tagsDB = tagsDB; + } + + public async getTagsWithIds(tagIds: string[]) { + return this.tagsDB.filter(tag => tagIds.includes(tag.id)); + } + + public async upsertTag(tag: BeatTag) { + const existingTagIndex = this.tagsDB.findIndex(t => t.id === tag.id); + if (existingTagIndex !== -1) { + this.tagsDB[existingTagIndex] = tag; + } else { + this.tagsDB.push(tag); + } + return tag; + } +} diff --git a/x-pack/plugins/beats_management/public/lib/adapters/tokens/adapter_types.ts b/x-pack/plugins/beats_management/public/lib/adapters/tokens/adapter_types.ts new file mode 100644 index 0000000000000..43f2d95b02b2c --- /dev/null +++ b/x-pack/plugins/beats_management/public/lib/adapters/tokens/adapter_types.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +export interface TokenEnrollmentData { + token: string | null; + expires_on: string; +} + +export interface CMTokensAdapter { + createEnrollmentToken(): Promise; +} diff --git a/x-pack/plugins/beats_management/public/lib/adapters/tokens/memory_tokens_adapter.ts b/x-pack/plugins/beats_management/public/lib/adapters/tokens/memory_tokens_adapter.ts new file mode 100644 index 0000000000000..4e80bc234d186 --- /dev/null +++ b/x-pack/plugins/beats_management/public/lib/adapters/tokens/memory_tokens_adapter.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CMTokensAdapter, TokenEnrollmentData } from './adapter_types'; + +export class MemoryTokensAdapter implements CMTokensAdapter { + public async createEnrollmentToken(): Promise { + return { + token: '2jnwkrhkwuehriauhweair', + expires_on: new Date().toJSON(), + }; + } +} diff --git a/x-pack/plugins/beats_management/public/lib/compose/kibana.ts b/x-pack/plugins/beats_management/public/lib/compose/kibana.ts index 7a98e62c18459..f27f553c9f327 100644 --- a/x-pack/plugins/beats_management/public/lib/compose/kibana.ts +++ b/x-pack/plugins/beats_management/public/lib/compose/kibana.ts @@ -12,18 +12,30 @@ import { uiModules } from 'ui/modules'; // @ts-ignore: path dynamic for kibana import routes from 'ui/routes'; // @ts-ignore: path dynamic for kibana +import { MemoryBeatsAdapter } from '../adapters/beats/memory_beats_adapter'; import { KibanaFrameworkAdapter } from '../adapters/framework/kibana_framework_adapter'; -import { FrontendLibs } from '../lib'; +import { MemoryTagsAdapter } from '../adapters/tags/memory_tags_adapter'; +import { MemoryTokensAdapter } from '../adapters/tokens/memory_tokens_adapter'; +import { FrontendDomainLibs, FrontendLibs } from '../lib'; export function compose(): FrontendLibs { // const kbnVersion = (window as any).__KBN__.version; + const tags = new MemoryTagsAdapter([]); + const tokens = new MemoryTokensAdapter(); + const beats = new MemoryBeatsAdapter([]); + const domainLibs: FrontendDomainLibs = { + tags, + tokens, + beats, + }; const pluginUIModule = uiModules.get('app/beats_management'); const framework = new KibanaFrameworkAdapter(pluginUIModule, management, routes); const libs: FrontendLibs = { framework, + ...domainLibs, }; return libs; } diff --git a/x-pack/plugins/beats_management/public/lib/compose/memory.ts b/x-pack/plugins/beats_management/public/lib/compose/memory.ts new file mode 100644 index 0000000000000..ef65d77229ec9 --- /dev/null +++ b/x-pack/plugins/beats_management/public/lib/compose/memory.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import 'ui/autoload/all'; +// @ts-ignore: path dynamic for kibana +import { management } from 'ui/management'; +// @ts-ignore: path dynamic for kibana +import { uiModules } from 'ui/modules'; +// @ts-ignore: path dynamic for kibana +import routes from 'ui/routes'; +// @ts-ignore: path dynamic for kibana +import { MemoryBeatsAdapter } from '../adapters/beats/memory_beats_adapter'; +import { KibanaFrameworkAdapter } from '../adapters/framework/kibana_framework_adapter'; +import { MemoryTagsAdapter } from '../adapters/tags/memory_tags_adapter'; +import { MemoryTokensAdapter } from '../adapters/tokens/memory_tokens_adapter'; + +import { FrontendDomainLibs, FrontendLibs } from '../lib'; + +export function compose(): FrontendLibs { + const tags = new MemoryTagsAdapter([]); + const tokens = new MemoryTokensAdapter(); + const beats = new MemoryBeatsAdapter([]); + + const domainLibs: FrontendDomainLibs = { + tags, + tokens, + beats, + }; + const pluginUIModule = uiModules.get('app/beats_management'); + + const framework = new KibanaFrameworkAdapter(pluginUIModule, management, routes); + const libs: FrontendLibs = { + ...domainLibs, + framework, + }; + return libs; +} diff --git a/x-pack/plugins/beats_management/public/lib/lib.ts b/x-pack/plugins/beats_management/public/lib/lib.ts index 085efc3e53201..03dc74122abcb 100644 --- a/x-pack/plugins/beats_management/public/lib/lib.ts +++ b/x-pack/plugins/beats_management/public/lib/lib.ts @@ -7,10 +7,18 @@ import { IModule, IScope } from 'angular'; import { AxiosRequestConfig } from 'axios'; import React from 'react'; +import { CMBeatsAdapter } from './adapters/beats/adapter_types'; +import { CMTagsAdapter } from './adapters/tags/adapter_types'; +import { CMTokensAdapter } from './adapters/tokens/adapter_types'; -export interface FrontendLibs { +export interface FrontendDomainLibs { + beats: CMBeatsAdapter; + tags: CMTagsAdapter; + tokens: CMTokensAdapter; +} + +export interface FrontendLibs extends FrontendDomainLibs { framework: FrameworkAdapter; - // api: ApiAdapter; } export interface FrameworkAdapter { diff --git a/x-pack/plugins/beats_management/public/pages/main/activity.tsx b/x-pack/plugins/beats_management/public/pages/main/activity.tsx new file mode 100644 index 0000000000000..5aa523e3a32f8 --- /dev/null +++ b/x-pack/plugins/beats_management/public/pages/main/activity.tsx @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +export class ActivityPage extends React.PureComponent { + public render() { + return
activity logs view
; + } +} diff --git a/x-pack/plugins/beats_management/public/pages/main/beats.tsx b/x-pack/plugins/beats_management/public/pages/main/beats.tsx new file mode 100644 index 0000000000000..a8c7518a3e27c --- /dev/null +++ b/x-pack/plugins/beats_management/public/pages/main/beats.tsx @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + EuiButton, + EuiButtonEmpty, + EuiModal, + EuiModalBody, + EuiModalFooter, + EuiModalHeader, + EuiModalHeaderTitle, + EuiOverlayMask, +} from '@elastic/eui'; + +import { CMBeat } from '../../../common/domain_types'; +import { FrontendLibs } from '../../lib/lib'; + +import React from 'react'; +interface BeatsPageProps { + libs: FrontendLibs; +} + +interface BeatsPageState { + beats: CMBeat[]; +} + +export class BeatsPage extends React.PureComponent { + public static ActionArea = ({ match, history }: { match: any; history: any }) => ( +
+ { + window.alert('This will later go to more general beats install instructions.'); + window.location.href = '#/home/tutorial/dockerMetrics'; + }} + > + Learn how to install beats + + { + history.push('/beats/enroll/foobar'); + }} + > + Enroll Beats + + + {match.params.enrollmentToken != null && ( + + history.push('/beats')} style={{ width: '800px' }}> + + Enroll Beats + + + + Enrollment UI here for enrollment token of: {match.params.enrollmentToken} + + + + history.push('/beats')}>Cancel + + + + )} +
+ ); + constructor(props: BeatsPageProps) { + super(props); + + this.state = { + beats: [], + }; + + this.loadBeats(); + } + public render() { + return
beats table and stuff - {this.state.beats.length}
; + } + private async loadBeats() { + const beats = await this.props.libs.beats.getAll(); + this.setState({ + beats, + }); + } +} diff --git a/x-pack/plugins/beats_management/public/pages/main/index.tsx b/x-pack/plugins/beats_management/public/pages/main/index.tsx new file mode 100644 index 0000000000000..f18e5e05b5747 --- /dev/null +++ b/x-pack/plugins/beats_management/public/pages/main/index.tsx @@ -0,0 +1,122 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + // @ts-ignore + EuiTab, + // @ts-ignore + EuiTabs, +} from '@elastic/eui'; +import React from 'react'; +import { Redirect, Route, Switch } from 'react-router-dom'; +import { PrimaryLayout } from '../../components/layouts/primary'; +import { FrontendLibs } from '../../lib/lib'; +import { ActivityPage } from './activity'; +import { BeatsPage } from './beats'; +import { TagsPage } from './tags'; + +interface MainPagesProps { + history: any; + libs: FrontendLibs; +} + +interface MainPagesState { + selectedTabId: string; + enrollBeat?: { + enrollmentToken: string; + } | null; +} + +export class MainPages extends React.PureComponent { + constructor(props: any) { + super(props); + + this.state = { + selectedTabId: '/', + }; + } + public toggleEnrollBeat = () => { + if (this.state.enrollBeat) { + return this.setState({ + enrollBeat: null, + }); + } + + // TODO: create a real enromment token + return this.setState({ + enrollBeat: { enrollmentToken: '5g3i4ug5uy34g' }, + }); + }; + + public onSelectedTabChanged = (id: string) => { + this.props.history.push(id); + }; + + public render() { + const tabs = [ + { + id: '/beats', + name: 'Beats List', + disabled: false, + }, + { + id: '/activity', + name: 'Beats Activity', + disabled: false, + }, + { + id: '/tags', + name: 'Tags', + disabled: false, + }, + ]; + + const renderedTabs = tabs.map((tab, index) => ( + this.onSelectedTabChanged(tab.id)} + isSelected={tab.id === this.props.history.location.pathname} + disabled={tab.disabled} + key={index} + > + {tab.name} + + )); + return ( + + + + } + > + {renderedTabs} + + + } + /> + } + /> + } + /> + } + /> + + + ); + } +} diff --git a/x-pack/plugins/beats_management/public/pages/home.tsx b/x-pack/plugins/beats_management/public/pages/main/tags.tsx similarity index 75% rename from x-pack/plugins/beats_management/public/pages/home.tsx rename to x-pack/plugins/beats_management/public/pages/main/tags.tsx index 505015f18af16..66dab3c1b3550 100644 --- a/x-pack/plugins/beats_management/public/pages/home.tsx +++ b/x-pack/plugins/beats_management/public/pages/main/tags.tsx @@ -6,8 +6,8 @@ import React from 'react'; -export class HomePage extends React.PureComponent { +export class TagsPage extends React.PureComponent { public render() { - return
Home
; + return
tags table and stuff
; } } diff --git a/x-pack/plugins/beats_management/public/routes.tsx b/x-pack/plugins/beats_management/public/router.tsx similarity index 52% rename from x-pack/plugins/beats_management/public/routes.tsx rename to x-pack/plugins/beats_management/public/router.tsx index f5863b28aaafa..7fb283c33da86 100644 --- a/x-pack/plugins/beats_management/public/routes.tsx +++ b/x-pack/plugins/beats_management/public/router.tsx @@ -5,18 +5,14 @@ */ import React from 'react'; -import { HashRouter, Route, Switch } from 'react-router-dom'; +import { HashRouter, Route } from 'react-router-dom'; -import { NotFoundPage } from './pages/404'; -import { HomePage } from './pages/home'; +import { MainPages } from './pages/main'; -export const PageRouter: React.SFC<{}> = () => { +export const PageRouter: React.SFC<{ libs: any }> = ({ libs }) => { return ( - - - - + } /> ); }; diff --git a/x-pack/plugins/beats_management/server/lib/adapters/tags/adapter_types.ts b/x-pack/plugins/beats_management/server/lib/adapters/tags/adapter_types.ts index 19333c831d594..7bbb42d415f52 100644 --- a/x-pack/plugins/beats_management/server/lib/adapters/tags/adapter_types.ts +++ b/x-pack/plugins/beats_management/server/lib/adapters/tags/adapter_types.ts @@ -7,6 +7,6 @@ import { BeatTag } from '../../../../common/domain_types'; import { FrameworkUser } from '../framework/adapter_types'; export interface CMTagsAdapter { - getTagsWithIds(user: FrameworkUser, tagIds: string[]): any; + getTagsWithIds(user: FrameworkUser, tagIds: string[]): Promise; upsertTag(user: FrameworkUser, tag: BeatTag): Promise<{}>; } diff --git a/x-pack/plugins/beats_management/server/lib/adapters/tags/memory_tags_adapter.ts b/x-pack/plugins/beats_management/server/lib/adapters/tags/memory_tags_adapter.ts index 64ef401008ae1..c3470011f6a4e 100644 --- a/x-pack/plugins/beats_management/server/lib/adapters/tags/memory_tags_adapter.ts +++ b/x-pack/plugins/beats_management/server/lib/adapters/tags/memory_tags_adapter.ts @@ -5,7 +5,7 @@ */ import { BeatTag } from '../../../../common/domain_types'; -import { FrameworkUser } from './../framework/adapter_types'; +import { FrameworkUser } from '../framework/adapter_types'; import { CMTagsAdapter } from './adapter_types'; export class MemoryTagsAdapter implements CMTagsAdapter { diff --git a/x-pack/plugins/beats_management/server/lib/adapters/tokens/memory_tokens_adapter.ts b/x-pack/plugins/beats_management/server/lib/adapters/tokens/memory_tokens_adapter.ts index 7cf132bb6ba31..767f161615ef8 100644 --- a/x-pack/plugins/beats_management/server/lib/adapters/tokens/memory_tokens_adapter.ts +++ b/x-pack/plugins/beats_management/server/lib/adapters/tokens/memory_tokens_adapter.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { FrameworkAuthenticatedUser } from './../framework/adapter_types'; +import { FrameworkAuthenticatedUser } from '../framework/adapter_types'; import { CMTokensAdapter, TokenEnrollmentData } from './adapter_types'; export class MemoryTokensAdapter implements CMTokensAdapter { diff --git a/x-pack/plugins/beats_management/server/lib/domains/__tests__/beats/assign_tags.test.ts b/x-pack/plugins/beats_management/server/lib/domains/__tests__/beats/assign_tags.test.ts index c9f9f15256d43..d0d888d2d3d1f 100644 --- a/x-pack/plugins/beats_management/server/lib/domains/__tests__/beats/assign_tags.test.ts +++ b/x-pack/plugins/beats_management/server/lib/domains/__tests__/beats/assign_tags.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { FrameworkInternalUser } from './../../../adapters/framework/adapter_types'; +import { FrameworkInternalUser } from '../../../adapters/framework/adapter_types'; import { MemoryBeatsAdapter } from '../../../adapters/beats/memory_beats_adapter'; import { TestingBackendFrameworkAdapter } from '../../../adapters/framework/testing_framework_adapter'; From a0d814ab87040a10bbfc95c6b43be286c2600c10 Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Thu, 26 Jul 2018 12:40:21 -0400 Subject: [PATCH 24/94] add getAll method to tags adapter (#21287) --- .../public/lib/adapters/tags/adapter_types.ts | 1 + .../public/lib/adapters/tags/memory_tags_adapter.ts | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/x-pack/plugins/beats_management/public/lib/adapters/tags/adapter_types.ts b/x-pack/plugins/beats_management/public/lib/adapters/tags/adapter_types.ts index 7fceea057d4f9..ab0931ab597bf 100644 --- a/x-pack/plugins/beats_management/public/lib/adapters/tags/adapter_types.ts +++ b/x-pack/plugins/beats_management/public/lib/adapters/tags/adapter_types.ts @@ -7,5 +7,6 @@ import { BeatTag } from '../../../../common/domain_types'; export interface CMTagsAdapter { getTagsWithIds(tagIds: string[]): Promise; + getAll(): Promise; upsertTag(tag: BeatTag): Promise<{}>; } diff --git a/x-pack/plugins/beats_management/public/lib/adapters/tags/memory_tags_adapter.ts b/x-pack/plugins/beats_management/public/lib/adapters/tags/memory_tags_adapter.ts index 1baf2a65bb46f..6b7ddd1c98e8c 100644 --- a/x-pack/plugins/beats_management/public/lib/adapters/tags/memory_tags_adapter.ts +++ b/x-pack/plugins/beats_management/public/lib/adapters/tags/memory_tags_adapter.ts @@ -18,6 +18,10 @@ export class MemoryTagsAdapter implements CMTagsAdapter { return this.tagsDB.filter(tag => tagIds.includes(tag.id)); } + public async getAll() { + return this.tagsDB; + } + public async upsertTag(tag: BeatTag) { const existingTagIndex = this.tagsDB.findIndex(t => t.id === tag.id); if (existingTagIndex !== -1) { From 3953fb4304903331b895244f300c206e9295327a Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Thu, 2 Aug 2018 15:10:47 -0400 Subject: [PATCH 25/94] Beats/real adapters (#21481) * add initial real adapters, and nulled data where we need endpoints * UI adapters and needed endpoints added (though not tested) * prep for route tests and some cleanup * move files --- .../lib/adapters/beats/adapter_types.ts | 5 +- .../adapters/beats/memory_beats_adapter.ts | 15 +-- .../lib/adapters/beats/rest_beats_adapter.ts | 37 ++++++ .../lib/adapters/rest_api/adapter_types.ts | 12 ++ .../rest_api/axios_rest_api_adapter.ts | 81 ++++++++++++ .../public/lib/adapters/tags/adapter_types.ts | 2 +- .../lib/adapters/tags/rest_tags_adapter.ts | 27 ++++ .../adapters/tokens/rest_tokens_adapter.ts | 17 +++ .../public/lib/compose/kibana.ts | 19 +-- .../lib/adapters/beats/adapter_types.ts | 2 +- .../beats/elasticsearch_beats_adapter.ts | 4 +- .../adapters/beats/memory_beats_adapter.ts | 2 +- .../framework/testing_framework_adapter.ts | 34 +----- .../server/lib/adapters/tags/adapter_types.ts | 1 + .../tags/elasticsearch_tags_adapter.ts | 11 ++ .../lib/adapters/tags/memory_tags_adapter.ts | 4 + .../server/lib/compose/testing.ts | 53 ++++++++ .../__tests__/beats/assign_tags.test.ts | 2 +- .../domains/__tests__/beats/enroll.test.ts | 2 +- .../__tests__/beats/remove_tags.test.ts | 100 +++++++++++++++ .../lib/domains/__tests__/tokens.test.ts | 2 +- .../server/lib/domains/beats.ts | 20 +-- .../server/lib/domains/tags.ts | 4 + .../beats_management/server/lib/lib.ts | 2 +- .../server/management_server.ts | 10 +- .../server/rest_api/beats/configuration.ts | 2 +- .../server/rest_api/beats/enroll.ts | 4 +- .../server/rest_api/beats/get.ts | 39 ++++++ .../server/rest_api/beats/list.ts | 4 +- .../server/rest_api/beats/tag_assignment.ts | 18 ++- .../server/rest_api/beats/tag_removal.ts | 4 +- .../server/rest_api/beats/update.ts | 4 +- .../server/rest_api/tags/get.ts | 27 ++++ .../server/rest_api/tags/list.ts | 24 ++++ .../server/rest_api/tags/set.ts | 4 +- .../server/rest_api/tokens/create.ts | 4 +- .../apis/beats/assign_tags_to_beats.js | 115 +++++++----------- 37 files changed, 554 insertions(+), 163 deletions(-) create mode 100644 x-pack/plugins/beats_management/public/lib/adapters/beats/rest_beats_adapter.ts create mode 100644 x-pack/plugins/beats_management/public/lib/adapters/rest_api/adapter_types.ts create mode 100644 x-pack/plugins/beats_management/public/lib/adapters/rest_api/axios_rest_api_adapter.ts create mode 100644 x-pack/plugins/beats_management/public/lib/adapters/tags/rest_tags_adapter.ts create mode 100644 x-pack/plugins/beats_management/public/lib/adapters/tokens/rest_tokens_adapter.ts create mode 100644 x-pack/plugins/beats_management/server/lib/compose/testing.ts create mode 100644 x-pack/plugins/beats_management/server/lib/domains/__tests__/beats/remove_tags.test.ts create mode 100644 x-pack/plugins/beats_management/server/rest_api/beats/get.ts create mode 100644 x-pack/plugins/beats_management/server/rest_api/tags/get.ts create mode 100644 x-pack/plugins/beats_management/server/rest_api/tags/list.ts diff --git a/x-pack/plugins/beats_management/public/lib/adapters/beats/adapter_types.ts b/x-pack/plugins/beats_management/public/lib/adapters/beats/adapter_types.ts index 444ae817088e7..b703edb971299 100644 --- a/x-pack/plugins/beats_management/public/lib/adapters/beats/adapter_types.ts +++ b/x-pack/plugins/beats_management/public/lib/adapters/beats/adapter_types.ts @@ -9,9 +9,8 @@ import { CMBeat } from '../../../../common/domain_types'; export interface CMBeatsAdapter { get(id: string): Promise; getAll(): Promise; - getWithIds(beatIds: string[]): Promise; - removeTagsFromBeats(removals: BeatsTagAssignment[]): Promise; - assignTagsToBeats(assignments: BeatsTagAssignment[]): Promise; + removeTagsFromBeats(removals: BeatsTagAssignment[]): Promise; + assignTagsToBeats(assignments: BeatsTagAssignment[]): Promise; } export interface BeatsTagAssignment { diff --git a/x-pack/plugins/beats_management/public/lib/adapters/beats/memory_beats_adapter.ts b/x-pack/plugins/beats_management/public/lib/adapters/beats/memory_beats_adapter.ts index e1929a7d6c69a..1940ec2ada4b0 100644 --- a/x-pack/plugins/beats_management/public/lib/adapters/beats/memory_beats_adapter.ts +++ b/x-pack/plugins/beats_management/public/lib/adapters/beats/memory_beats_adapter.ts @@ -7,7 +7,12 @@ import { omit } from 'lodash'; import { CMBeat } from '../../../../common/domain_types'; -import { BeatsTagAssignment, CMBeatsAdapter } from './adapter_types'; +import { + BeatsRemovalReturn, + BeatsTagAssignment, + CMAssignmentReturn, + CMBeatsAdapter, +} from './adapter_types'; export class MemoryBeatsAdapter implements CMBeatsAdapter { private beatsDB: CMBeat[]; @@ -20,15 +25,11 @@ export class MemoryBeatsAdapter implements CMBeatsAdapter { return this.beatsDB.find(beat => beat.id === id) || null; } - public async getWithIds(beatIds: string[]) { - return this.beatsDB.filter(beat => beatIds.includes(beat.id)); - } - public async getAll() { return this.beatsDB.map((beat: any) => omit(beat, ['access_token'])); } - public async removeTagsFromBeats(removals: BeatsTagAssignment[]): Promise { + public async removeTagsFromBeats(removals: BeatsTagAssignment[]): Promise { const beatIds = removals.map(r => r.beatId); const response = this.beatsDB.filter(beat => beatIds.includes(beat.id)).map(beat => { @@ -48,7 +49,7 @@ export class MemoryBeatsAdapter implements CMBeatsAdapter { })); } - public async assignTagsToBeats(assignments: BeatsTagAssignment[]): Promise { + public async assignTagsToBeats(assignments: BeatsTagAssignment[]): Promise { const beatIds = assignments.map(r => r.beatId); this.beatsDB.filter(beat => beatIds.includes(beat.id)).map(beat => { diff --git a/x-pack/plugins/beats_management/public/lib/adapters/beats/rest_beats_adapter.ts b/x-pack/plugins/beats_management/public/lib/adapters/beats/rest_beats_adapter.ts new file mode 100644 index 0000000000000..9da760be307db --- /dev/null +++ b/x-pack/plugins/beats_management/public/lib/adapters/beats/rest_beats_adapter.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CMBeat } from '../../../../common/domain_types'; +import { RestAPIAdapter } from '../rest_api/adapter_types'; +import { + BeatsRemovalReturn, + BeatsTagAssignment, + CMAssignmentReturn, + CMBeatsAdapter, +} from './adapter_types'; +export class RestBeatsAdapter implements CMBeatsAdapter { + constructor(private readonly REST: RestAPIAdapter) {} + + public async get(id: string): Promise { + return await this.REST.get(`/api/beats/agent/${id}`); + } + + public async getAll(): Promise { + return await this.REST.get('/api/beats/agents'); + } + + public async removeTagsFromBeats(removals: BeatsTagAssignment[]): Promise { + return await this.REST.post(`/api/beats/agents_tags/removals`, { + removals, + }); + } + + public async assignTagsToBeats(assignments: BeatsTagAssignment[]): Promise { + return await this.REST.post(`/api/beats/agents_tags/assignments`, { + assignments, + }); + } +} diff --git a/x-pack/plugins/beats_management/public/lib/adapters/rest_api/adapter_types.ts b/x-pack/plugins/beats_management/public/lib/adapters/rest_api/adapter_types.ts new file mode 100644 index 0000000000000..222807e7f6948 --- /dev/null +++ b/x-pack/plugins/beats_management/public/lib/adapters/rest_api/adapter_types.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface RestAPIAdapter { + get(url: string): Promise; + post(url: string, body?: { [key: string]: any }): Promise; + delete(url: string): Promise; + put(url: string, body?: any): Promise; +} diff --git a/x-pack/plugins/beats_management/public/lib/adapters/rest_api/axios_rest_api_adapter.ts b/x-pack/plugins/beats_management/public/lib/adapters/rest_api/axios_rest_api_adapter.ts new file mode 100644 index 0000000000000..b05f2b41e30b3 --- /dev/null +++ b/x-pack/plugins/beats_management/public/lib/adapters/rest_api/axios_rest_api_adapter.ts @@ -0,0 +1,81 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import axios, { AxiosInstance } from 'axios'; +import { RestAPIAdapter } from './adapter_types'; +let globalAPI: AxiosInstance; + +export class AxiosRestAPIAdapter implements RestAPIAdapter { + constructor( + private readonly kbnVersion: string, + private readonly xsrfToken: string, + private readonly basePath: string + ) {} + + public async get(url: string): Promise { + return await this.REST.get(url).then(resp => resp.data); + } + + public async post( + url: string, + body?: { [key: string]: any } + ): Promise { + return await this.REST.post(url, body).then(resp => resp.data); + } + + public async delete(url: string): Promise { + return await this.REST.delete(url).then(resp => resp.data); + } + + public async put(url: string, body?: any): Promise { + return await this.REST.put(url, body).then(resp => resp.data); + } + + private get REST() { + if (globalAPI) { + return globalAPI; + } + + globalAPI = axios.create({ + baseURL: this.basePath, + withCredentials: true, + responseType: 'json', + timeout: 30000, + headers: { + Accept: 'application/json', + credentials: 'same-origin', + 'Content-Type': 'application/json', + 'kbn-version': this.kbnVersion, + 'kbn-xsrf': this.xsrfToken, + }, + }); + // Add a request interceptor + globalAPI.interceptors.request.use( + config => { + // Do something before request is sent + return config; + }, + error => { + // Do something with request error + return Promise.reject(error); + } + ); + + // Add a response interceptor + globalAPI.interceptors.response.use( + response => { + // Do something with response data + return response; + }, + error => { + // Do something with response error + return Promise.reject(error); + } + ); + + return globalAPI; + } +} diff --git a/x-pack/plugins/beats_management/public/lib/adapters/tags/adapter_types.ts b/x-pack/plugins/beats_management/public/lib/adapters/tags/adapter_types.ts index ab0931ab597bf..57bdf2592baa3 100644 --- a/x-pack/plugins/beats_management/public/lib/adapters/tags/adapter_types.ts +++ b/x-pack/plugins/beats_management/public/lib/adapters/tags/adapter_types.ts @@ -8,5 +8,5 @@ import { BeatTag } from '../../../../common/domain_types'; export interface CMTagsAdapter { getTagsWithIds(tagIds: string[]): Promise; getAll(): Promise; - upsertTag(tag: BeatTag): Promise<{}>; + upsertTag(tag: BeatTag): Promise; } diff --git a/x-pack/plugins/beats_management/public/lib/adapters/tags/rest_tags_adapter.ts b/x-pack/plugins/beats_management/public/lib/adapters/tags/rest_tags_adapter.ts new file mode 100644 index 0000000000000..5eb16762ba825 --- /dev/null +++ b/x-pack/plugins/beats_management/public/lib/adapters/tags/rest_tags_adapter.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { BeatTag } from '../../../../common/domain_types'; +import { RestAPIAdapter } from '../rest_api/adapter_types'; +import { CMTagsAdapter } from './adapter_types'; + +export class RestTagsAdapter implements CMTagsAdapter { + constructor(private readonly REST: RestAPIAdapter) {} + + public async getTagsWithIds(tagIds: string[]): Promise { + return await this.REST.get(`/api/beats/tags/${tagIds.join(',')}`); + } + + public async getAll(): Promise { + return await this.REST.get(`/api/beats/tags`); + } + + public async upsertTag(tag: BeatTag): Promise { + return await this.REST.put(`/api/beats/tag/{tag}`, { + configuration_blocks: tag.configuration_blocks, + }); + } +} diff --git a/x-pack/plugins/beats_management/public/lib/adapters/tokens/rest_tokens_adapter.ts b/x-pack/plugins/beats_management/public/lib/adapters/tokens/rest_tokens_adapter.ts new file mode 100644 index 0000000000000..de5890fdaebf5 --- /dev/null +++ b/x-pack/plugins/beats_management/public/lib/adapters/tokens/rest_tokens_adapter.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { RestAPIAdapter } from '../rest_api/adapter_types'; +import { CMTokensAdapter, TokenEnrollmentData } from './adapter_types'; + +export class RestTokensAdapter implements CMTokensAdapter { + constructor(private readonly REST: RestAPIAdapter) {} + + public async createEnrollmentToken(): Promise { + const tokens = await this.REST.post('/api/beats/enrollment_tokens'); + return tokens[0]; + } +} diff --git a/x-pack/plugins/beats_management/public/lib/compose/kibana.ts b/x-pack/plugins/beats_management/public/lib/compose/kibana.ts index f27f553c9f327..9f8f183170fd1 100644 --- a/x-pack/plugins/beats_management/public/lib/compose/kibana.ts +++ b/x-pack/plugins/beats_management/public/lib/compose/kibana.ts @@ -6,23 +6,28 @@ import 'ui/autoload/all'; // @ts-ignore: path dynamic for kibana +import chrome from 'ui/chrome'; +// @ts-ignore: path dynamic for kibana import { management } from 'ui/management'; // @ts-ignore: path dynamic for kibana import { uiModules } from 'ui/modules'; // @ts-ignore: path dynamic for kibana import routes from 'ui/routes'; // @ts-ignore: path dynamic for kibana -import { MemoryBeatsAdapter } from '../adapters/beats/memory_beats_adapter'; +import { RestBeatsAdapter } from '../adapters/beats/rest_beats_adapter'; import { KibanaFrameworkAdapter } from '../adapters/framework/kibana_framework_adapter'; -import { MemoryTagsAdapter } from '../adapters/tags/memory_tags_adapter'; -import { MemoryTokensAdapter } from '../adapters/tokens/memory_tokens_adapter'; +import { AxiosRestAPIAdapter } from '../adapters/rest_api/axios_rest_api_adapter'; +import { RestTagsAdapter } from '../adapters/tags/rest_tags_adapter'; +import { RestTokensAdapter } from '../adapters/tokens/rest_tokens_adapter'; import { FrontendDomainLibs, FrontendLibs } from '../lib'; export function compose(): FrontendLibs { - // const kbnVersion = (window as any).__KBN__.version; - const tags = new MemoryTagsAdapter([]); - const tokens = new MemoryTokensAdapter(); - const beats = new MemoryBeatsAdapter([]); + const kbnVersion = (window as any).__KBN__.version; + const api = new AxiosRestAPIAdapter(kbnVersion, chrome.getXsrfToken(), chrome.getBasePath()); + + const tags = new RestTagsAdapter(api); + const tokens = new RestTokensAdapter(api); + const beats = new RestBeatsAdapter(api); const domainLibs: FrontendDomainLibs = { tags, diff --git a/x-pack/plugins/beats_management/server/lib/adapters/beats/adapter_types.ts b/x-pack/plugins/beats_management/server/lib/adapters/beats/adapter_types.ts index 8812d9d39bd41..0d68fd528d42a 100644 --- a/x-pack/plugins/beats_management/server/lib/adapters/beats/adapter_types.ts +++ b/x-pack/plugins/beats_management/server/lib/adapters/beats/adapter_types.ts @@ -11,7 +11,7 @@ import { FrameworkUser } from '../framework/adapter_types'; export interface CMBeatsAdapter { insert(beat: CMBeat): Promise; update(beat: CMBeat): Promise; - get(id: string): any; + get(user: FrameworkUser, id: string): any; getAll(user: FrameworkUser): any; getWithIds(user: FrameworkUser, beatIds: string[]): any; removeTagsFromBeats( diff --git a/x-pack/plugins/beats_management/server/lib/adapters/beats/elasticsearch_beats_adapter.ts b/x-pack/plugins/beats_management/server/lib/adapters/beats/elasticsearch_beats_adapter.ts index 9a3d067ffa35f..1d00f95e3a4e0 100644 --- a/x-pack/plugins/beats_management/server/lib/adapters/beats/elasticsearch_beats_adapter.ts +++ b/x-pack/plugins/beats_management/server/lib/adapters/beats/elasticsearch_beats_adapter.ts @@ -21,7 +21,7 @@ export class ElasticsearchBeatsAdapter implements CMBeatsAdapter { this.framework = framework; } - public async get(id: string) { + public async get(user: FrameworkUser, id: string) { const params = { id: `beat:${id}`, ignore: [404], @@ -29,7 +29,7 @@ export class ElasticsearchBeatsAdapter implements CMBeatsAdapter { type: '_doc', }; - const response = await this.database.get(this.framework.internalUser, params); + const response = await this.database.get(user, params); if (!response.found) { return null; } diff --git a/x-pack/plugins/beats_management/server/lib/adapters/beats/memory_beats_adapter.ts b/x-pack/plugins/beats_management/server/lib/adapters/beats/memory_beats_adapter.ts index 1762ff93fc185..1de1fe71e54ea 100644 --- a/x-pack/plugins/beats_management/server/lib/adapters/beats/memory_beats_adapter.ts +++ b/x-pack/plugins/beats_management/server/lib/adapters/beats/memory_beats_adapter.ts @@ -17,7 +17,7 @@ export class MemoryBeatsAdapter implements CMBeatsAdapter { this.beatsDB = beatsDB; } - public async get(id: string) { + public async get(user: FrameworkUser, id: string) { return this.beatsDB.find(beat => beat.id === id) || null; } diff --git a/x-pack/plugins/beats_management/server/lib/adapters/framework/testing_framework_adapter.ts b/x-pack/plugins/beats_management/server/lib/adapters/framework/testing_framework_adapter.ts index 3280220893892..5462bd36b48ae 100644 --- a/x-pack/plugins/beats_management/server/lib/adapters/framework/testing_framework_adapter.ts +++ b/x-pack/plugins/beats_management/server/lib/adapters/framework/testing_framework_adapter.ts @@ -3,13 +3,11 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { Client } from 'elasticsearch'; -import { get } from 'lodash'; + import { FrameworkInternalUser } from './adapter_types'; import { BackendFrameworkAdapter, - FrameworkRequest, FrameworkRouteOptions, FrameworkWrappableRequest, } from './adapter_types'; @@ -24,15 +22,15 @@ export class TestingBackendFrameworkAdapter implements BackendFrameworkAdapter { kind: 'internal', }; public version: string; - private client: Client | null; private settings: TestSettings; - constructor(client: Client | null, settings: TestSettings) { - this.client = client; - this.settings = settings || { + constructor( + settings: TestSettings = { encryptionKey: 'something_who_cares', enrollmentTokensTtlInSeconds: 10 * 60, // 10 minutes - }; + } + ) { + this.settings = settings; this.version = 'testing'; } @@ -54,24 +52,4 @@ export class TestingBackendFrameworkAdapter implements BackendFrameworkAdapter { ) { // not yet testable } - - public installIndexTemplate(name: string, template: {}) { - return; - } - - public async callWithInternalUser(esMethod: string, options: {}) { - const api = get(this.client, esMethod); - - api(options); - - return await api(options); - } - - public async callWithRequest(req: FrameworkRequest, esMethod: string, options: {}) { - const api = get(this.client, esMethod); - - api(options); - - return await api(options); - } } diff --git a/x-pack/plugins/beats_management/server/lib/adapters/tags/adapter_types.ts b/x-pack/plugins/beats_management/server/lib/adapters/tags/adapter_types.ts index 7bbb42d415f52..77ae4ff8ad095 100644 --- a/x-pack/plugins/beats_management/server/lib/adapters/tags/adapter_types.ts +++ b/x-pack/plugins/beats_management/server/lib/adapters/tags/adapter_types.ts @@ -7,6 +7,7 @@ import { BeatTag } from '../../../../common/domain_types'; import { FrameworkUser } from '../framework/adapter_types'; export interface CMTagsAdapter { + getAll(user: FrameworkUser): Promise; getTagsWithIds(user: FrameworkUser, tagIds: string[]): Promise; upsertTag(user: FrameworkUser, tag: BeatTag): Promise<{}>; } diff --git a/x-pack/plugins/beats_management/server/lib/adapters/tags/elasticsearch_tags_adapter.ts b/x-pack/plugins/beats_management/server/lib/adapters/tags/elasticsearch_tags_adapter.ts index 2c2c988189283..645ed37f5ff8f 100644 --- a/x-pack/plugins/beats_management/server/lib/adapters/tags/elasticsearch_tags_adapter.ts +++ b/x-pack/plugins/beats_management/server/lib/adapters/tags/elasticsearch_tags_adapter.ts @@ -19,6 +19,17 @@ export class ElasticsearchTagsAdapter implements CMTagsAdapter { this.database = database; } + public async getAll(user: FrameworkUser) { + const params = { + index: INDEX_NAMES.BEATS, + q: 'type:tag', + type: '_doc', + }; + const response = await this.database.search(user, params); + + return get(response, 'hits.hits', []); + } + public async getTagsWithIds(user: FrameworkUser, tagIds: string[]) { const ids = tagIds.map(tag => `tag:${tag}`); diff --git a/x-pack/plugins/beats_management/server/lib/adapters/tags/memory_tags_adapter.ts b/x-pack/plugins/beats_management/server/lib/adapters/tags/memory_tags_adapter.ts index c3470011f6a4e..7623c8aaa21d8 100644 --- a/x-pack/plugins/beats_management/server/lib/adapters/tags/memory_tags_adapter.ts +++ b/x-pack/plugins/beats_management/server/lib/adapters/tags/memory_tags_adapter.ts @@ -15,6 +15,10 @@ export class MemoryTagsAdapter implements CMTagsAdapter { this.tagsDB = tagsDB; } + public async getAll(user: FrameworkUser) { + return this.tagsDB; + } + public async getTagsWithIds(user: FrameworkUser, tagIds: string[]) { return this.tagsDB.filter(tag => tagIds.includes(tag.id)); } diff --git a/x-pack/plugins/beats_management/server/lib/compose/testing.ts b/x-pack/plugins/beats_management/server/lib/compose/testing.ts new file mode 100644 index 0000000000000..25b6776b6908b --- /dev/null +++ b/x-pack/plugins/beats_management/server/lib/compose/testing.ts @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { MemoryBeatsAdapter } from '../adapters/beats/memory_beats_adapter'; +import { MemoryTagsAdapter } from '../adapters/tags/memory_tags_adapter'; +import { MemoryTokensAdapter } from '../adapters/tokens/memory_tokens_adapter'; + +import { TestingBackendFrameworkAdapter } from '../adapters/framework/testing_framework_adapter'; + +import { CMBeatsDomain } from '../domains/beats'; +import { CMTagsDomain } from '../domains/tags'; +import { CMTokensDomain } from '../domains/tokens'; + +import { BeatTag, CMBeat } from '../../../common/domain_types'; +import { TokenEnrollmentData } from '../adapters/tokens/adapter_types'; +import { CMDomainLibs, CMServerLibs } from '../lib'; + +export function compose({ + tagsDB = [], + tokensDB = [], + beatsDB = [], +}: { + tagsDB?: BeatTag[]; + tokensDB?: TokenEnrollmentData[]; + beatsDB?: CMBeat[]; +}): CMServerLibs { + const framework = new TestingBackendFrameworkAdapter(); + + const tags = new CMTagsDomain(new MemoryTagsAdapter(tagsDB)); + const tokens = new CMTokensDomain(new MemoryTokensAdapter(tokensDB), { + framework, + }); + const beats = new CMBeatsDomain(new MemoryBeatsAdapter(beatsDB), { + tags, + tokens, + }); + + const domainLibs: CMDomainLibs = { + beats, + tags, + tokens, + }; + + const libs: CMServerLibs = { + framework, + ...domainLibs, + }; + + return libs; +} diff --git a/x-pack/plugins/beats_management/server/lib/domains/__tests__/beats/assign_tags.test.ts b/x-pack/plugins/beats_management/server/lib/domains/__tests__/beats/assign_tags.test.ts index d0d888d2d3d1f..20f4a7d36e4f0 100644 --- a/x-pack/plugins/beats_management/server/lib/domains/__tests__/beats/assign_tags.test.ts +++ b/x-pack/plugins/beats_management/server/lib/domains/__tests__/beats/assign_tags.test.ts @@ -82,7 +82,7 @@ describe('Beats Domain Lib', () => { id: 'qa', }, ]; - const framework = new TestingBackendFrameworkAdapter(null, settings); + const framework = new TestingBackendFrameworkAdapter(settings); const tokensLib = new CMTokensDomain(new MemoryTokensAdapter([]), { framework, diff --git a/x-pack/plugins/beats_management/server/lib/domains/__tests__/beats/enroll.test.ts b/x-pack/plugins/beats_management/server/lib/domains/__tests__/beats/enroll.test.ts index 9f42ad3c89f86..c5bc0935dc6cc 100644 --- a/x-pack/plugins/beats_management/server/lib/domains/__tests__/beats/enroll.test.ts +++ b/x-pack/plugins/beats_management/server/lib/domains/__tests__/beats/enroll.test.ts @@ -70,7 +70,7 @@ describe('Beats Domain Lib', () => { version, }; - const framework = new TestingBackendFrameworkAdapter(null, settings); + const framework = new TestingBackendFrameworkAdapter(settings); tokensLib = new CMTokensDomain(new MemoryTokensAdapter(tokensDB), { framework, diff --git a/x-pack/plugins/beats_management/server/lib/domains/__tests__/beats/remove_tags.test.ts b/x-pack/plugins/beats_management/server/lib/domains/__tests__/beats/remove_tags.test.ts new file mode 100644 index 0000000000000..f75334e917c2b --- /dev/null +++ b/x-pack/plugins/beats_management/server/lib/domains/__tests__/beats/remove_tags.test.ts @@ -0,0 +1,100 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { BeatTag, CMBeat } from '../../../../../common/domain_types'; +import { compose } from '../../../compose/testing'; +import { CMServerLibs } from '../../../lib'; +import { FrameworkInternalUser } from './../../../adapters/framework/adapter_types'; + +const internalUser: FrameworkInternalUser = { kind: 'internal' }; + +describe('Beats Domain Lib', () => { + let libs: CMServerLibs; + let beatsDB: CMBeat[] = []; + let tagsDB: BeatTag[] = []; + + describe('remove_tags_from_beats', () => { + beforeEach(async () => { + beatsDB = [ + { + access_token: '9a6c99ae0fd84b068819701169cd8a4b', + host_ip: '1.2.3.4', + host_name: 'foo.bar.com', + id: 'qux', + type: 'filebeat', + }, + { + access_token: '188255eb560a4448b72656c5e99cae6f', + host_ip: '22.33.11.44', + host_name: 'baz.bar.com', + id: 'baz', + type: 'metricbeat', + }, + { + access_token: '93c4a4dd08564c189a7ec4e4f046b975', + host_ip: '1.2.3.4', + host_name: 'foo.bar.com', + id: 'foo', + tags: ['production', 'qa'], + type: 'metricbeat', + verified_on: '2018-05-15T16:25:38.924Z', + }, + { + access_token: '3c4a4dd08564c189a7ec4e4f046b9759', + host_ip: '11.22.33.44', + host_name: 'foo.com', + id: 'bar', + type: 'filebeat', + }, + ]; + tagsDB = [ + { + configuration_blocks: [], + id: 'production', + }, + { + configuration_blocks: [], + id: 'development', + }, + { + configuration_blocks: [], + id: 'qa', + }, + ]; + + libs = compose({ + tagsDB, + beatsDB, + }); + }); + + it('should remove a single tag from a single beat', async () => { + const apiResponse = await libs.beats.removeTagsFromBeats(internalUser, [ + { beatId: 'foo', tag: 'production' }, + ]); + + expect(apiResponse.removals).toEqual([{ status: 200, result: 'updated' }]); + // @ts-ignore + expect(beatsDB.find(b => b.id === 'foo').tags).toEqual(['qa']); + }); + + it('should remove a single tag from a multiple beats', async () => { + const apiResponse = await libs.beats.removeTagsFromBeats(internalUser, [ + { beatId: 'foo', tag: 'development' }, + { beatId: 'bar', tag: 'development' }, + ]); + + expect(apiResponse.removals).toEqual([ + { status: 200, result: 'updated' }, + { status: 200, result: 'updated' }, + ]); + + // @ts-ignore + expect(beatsDB.find(b => b.id === 'foo').tags).toEqual(['production', 'qa']); + expect(beatsDB.find(b => b.id === 'bar')).not.toHaveProperty('tags'); + }); + }); +}); diff --git a/x-pack/plugins/beats_management/server/lib/domains/__tests__/tokens.test.ts b/x-pack/plugins/beats_management/server/lib/domains/__tests__/tokens.test.ts index c89962dca7d3b..eca217b3c8ed1 100644 --- a/x-pack/plugins/beats_management/server/lib/domains/__tests__/tokens.test.ts +++ b/x-pack/plugins/beats_management/server/lib/domains/__tests__/tokens.test.ts @@ -28,7 +28,7 @@ describe('Token Domain Lib', () => { beforeEach(async () => { tokensDB = []; - framework = new TestingBackendFrameworkAdapter(null, settings); + framework = new TestingBackendFrameworkAdapter(settings); tokensLib = new CMTokensDomain(new MemoryTokensAdapter(tokensDB), { framework, diff --git a/x-pack/plugins/beats_management/server/lib/domains/beats.ts b/x-pack/plugins/beats_management/server/lib/domains/beats.ts index 618de8ab0b446..f3c98b056d5e1 100644 --- a/x-pack/plugins/beats_management/server/lib/domains/beats.ts +++ b/x-pack/plugins/beats_management/server/lib/domains/beats.ts @@ -14,28 +14,33 @@ import { FrameworkUser } from '../adapters/framework/adapter_types'; import { CMAssignmentReturn } from '../adapters/beats/adapter_types'; import { BeatsRemovalReturn } from '../adapters/beats/adapter_types'; -import { BeatEnrollmentStatus, CMDomainLibs } from '../lib'; +import { BeatEnrollmentStatus, CMDomainLibs, CMServerLibs } from '../lib'; export class CMBeatsDomain { - private adapter: CMBeatsAdapter; private tags: CMDomainLibs['tags']; private tokens: CMDomainLibs['tokens']; + private framework: CMServerLibs['framework']; constructor( - adapter: CMBeatsAdapter, - libs: { tags: CMDomainLibs['tags']; tokens: CMDomainLibs['tokens'] } + private readonly adapter: CMBeatsAdapter, + libs: { + tags: CMDomainLibs['tags']; + tokens: CMDomainLibs['tokens']; + framework: CMServerLibs['framework']; + } ) { this.adapter = adapter; this.tags = libs.tags; this.tokens = libs.tokens; + this.framework = libs.framework; } - public async getById(beatId: string) { - return await this.adapter.get(beatId); + public async getById(user: FrameworkUser, beatId: string) { + return await this.adapter.get(user, beatId); } public async update(beatId: string, accessToken: string, beatData: Partial) { - const beat = await this.adapter.get(beatId); + const beat = await this.adapter.get(this.framework.internalUser, beatId); const { verified: isAccessTokenValid } = this.tokens.verifyToken( beat ? beat.access_token : '', @@ -190,6 +195,7 @@ function addNonExistentItemToResponse( ) { assignments.forEach(({ beatId, tag }: BeatsTagAssignment, idx: any) => { const isBeatNonExistent = nonExistentBeatIds.includes(beatId); + const isTagNonExistent = nonExistentTags.includes(tag); if (isBeatNonExistent && isTagNonExistent) { diff --git a/x-pack/plugins/beats_management/server/lib/domains/tags.ts b/x-pack/plugins/beats_management/server/lib/domains/tags.ts index 36cf9a5e6d7f0..39bc2c147ea03 100644 --- a/x-pack/plugins/beats_management/server/lib/domains/tags.ts +++ b/x-pack/plugins/beats_management/server/lib/domains/tags.ts @@ -18,6 +18,10 @@ export class CMTagsDomain { this.adapter = adapter; } + public async getAll(user: FrameworkUser) { + return await this.adapter.getAll(user); + } + public async getTagsWithIds(user: FrameworkUser, tagIds: string[]) { return await this.adapter.getTagsWithIds(user, tagIds); } diff --git a/x-pack/plugins/beats_management/server/lib/lib.ts b/x-pack/plugins/beats_management/server/lib/lib.ts index 495093842b496..824c5722a4bd8 100644 --- a/x-pack/plugins/beats_management/server/lib/lib.ts +++ b/x-pack/plugins/beats_management/server/lib/lib.ts @@ -18,7 +18,7 @@ export interface CMDomainLibs { export interface CMServerLibs extends CMDomainLibs { framework: BackendFrameworkAdapter; - database: DatabaseAdapter; + database?: DatabaseAdapter; } export enum BeatEnrollmentStatus { diff --git a/x-pack/plugins/beats_management/server/management_server.ts b/x-pack/plugins/beats_management/server/management_server.ts index 1c8a1d727175e..f7316ea0887ff 100644 --- a/x-pack/plugins/beats_management/server/management_server.ts +++ b/x-pack/plugins/beats_management/server/management_server.ts @@ -16,10 +16,12 @@ import { createTokensRoute } from './rest_api/tokens/create'; import { beatsIndexTemplate } from './utils/index_templates'; export const initManagementServer = (libs: CMServerLibs) => { - libs.database.putTemplate(libs.framework.internalUser, { - name: 'beats-template', - body: beatsIndexTemplate, - }); + if (libs.database) { + libs.database.putTemplate(libs.framework.internalUser, { + name: 'beats-template', + body: beatsIndexTemplate, + }); + } libs.framework.registerRoute(createGetBeatConfigurationRoute(libs)); libs.framework.registerRoute(createTagAssignmentsRoute(libs)); diff --git a/x-pack/plugins/beats_management/server/rest_api/beats/configuration.ts b/x-pack/plugins/beats_management/server/rest_api/beats/configuration.ts index 81906ea9473e6..dad9909ddd1e0 100644 --- a/x-pack/plugins/beats_management/server/rest_api/beats/configuration.ts +++ b/x-pack/plugins/beats_management/server/rest_api/beats/configuration.ts @@ -27,7 +27,7 @@ export const createGetBeatConfigurationRoute = (libs: CMServerLibs) => ({ let beat; let tags; try { - beat = await libs.beats.getById(beatId); + beat = await libs.beats.getById(libs.framework.internalUser, beatId); if (beat === null) { return reply({ message: 'Beat not found' }).code(404); } diff --git a/x-pack/plugins/beats_management/server/rest_api/beats/enroll.ts b/x-pack/plugins/beats_management/server/rest_api/beats/enroll.ts index c0f0185fbde25..c1c23537218bd 100644 --- a/x-pack/plugins/beats_management/server/rest_api/beats/enroll.ts +++ b/x-pack/plugins/beats_management/server/rest_api/beats/enroll.ts @@ -13,6 +13,8 @@ import { wrapEsError } from '../../utils/error_wrappers'; // TODO: add license check pre-hook // TODO: write to Kibana audit log file export const createBeatEnrollmentRoute = (libs: CMServerLibs) => ({ + method: 'POST', + path: '/api/beats/agent/{beatId}', config: { auth: false, validate: { @@ -58,6 +60,4 @@ export const createBeatEnrollmentRoute = (libs: CMServerLibs) => ({ return reply(wrapEsError(err)); } }, - method: 'POST', - path: '/api/beats/agent/{beatId}', }); diff --git a/x-pack/plugins/beats_management/server/rest_api/beats/get.ts b/x-pack/plugins/beats_management/server/rest_api/beats/get.ts new file mode 100644 index 0000000000000..e7ed5ef5c2cc8 --- /dev/null +++ b/x-pack/plugins/beats_management/server/rest_api/beats/get.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Joi from 'joi'; +import { CMBeat } from '../../../common/domain_types'; +import { CMServerLibs } from '../../lib/lib'; +import { wrapEsError } from '../../utils/error_wrappers'; + +export const createGetBeatRoute = (libs: CMServerLibs) => ({ + method: 'GET', + path: '/api/beats/agent/{beatId}', + config: { + validate: { + headers: Joi.object({ + 'kbn-beats-access-token': Joi.string().required(), + }).options({ allowUnknown: true }), + }, + }, + handler: async (request: any, reply: any) => { + const beatId = request.params.beatId; + + let beat: CMBeat; + try { + beat = await libs.beats.getById(request.user, beatId); + if (beat === null) { + return reply({ message: 'Beat not found' }).code(404); + } + } catch (err) { + return reply(wrapEsError(err)); + } + + delete beat.access_token; + + reply(beat); + }, +}); diff --git a/x-pack/plugins/beats_management/server/rest_api/beats/list.ts b/x-pack/plugins/beats_management/server/rest_api/beats/list.ts index a47bfcfbf3853..72105fc2f5440 100644 --- a/x-pack/plugins/beats_management/server/rest_api/beats/list.ts +++ b/x-pack/plugins/beats_management/server/rest_api/beats/list.ts @@ -10,6 +10,8 @@ import { wrapEsError } from '../../utils/error_wrappers'; // TODO: add license check pre-hook export const createListAgentsRoute = (libs: CMServerLibs) => ({ + method: 'GET', + path: '/api/beats/agents', handler: async (request: FrameworkRequest, reply: any) => { try { const beats = await libs.beats.getAllBeats(request.user); @@ -19,6 +21,4 @@ export const createListAgentsRoute = (libs: CMServerLibs) => ({ return reply(wrapEsError(err)); } }, - method: 'GET', - path: '/api/beats/agents', }); diff --git a/x-pack/plugins/beats_management/server/rest_api/beats/tag_assignment.ts b/x-pack/plugins/beats_management/server/rest_api/beats/tag_assignment.ts index 9c832ee3226b8..857b68a921597 100644 --- a/x-pack/plugins/beats_management/server/rest_api/beats/tag_assignment.ts +++ b/x-pack/plugins/beats_management/server/rest_api/beats/tag_assignment.ts @@ -5,19 +5,23 @@ */ import Joi from 'joi'; +import { BeatsTagAssignment } from '../../../public/lib/adapters/beats/adapter_types'; import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types'; + import { CMServerLibs } from '../../lib/lib'; import { wrapEsError } from '../../utils/error_wrappers'; // TODO: add license check pre-hook // TODO: write to Kibana audit log file export const createTagAssignmentsRoute = (libs: CMServerLibs) => ({ + method: 'POST', + path: '/api/beats/agents_tags/assignments', config: { validate: { payload: Joi.object({ assignments: Joi.array().items( Joi.object({ - beat_id: Joi.string().required(), + beatId: Joi.string().required(), tag: Joi.string().required(), }) ), @@ -25,22 +29,14 @@ export const createTagAssignmentsRoute = (libs: CMServerLibs) => ({ }, }, handler: async (request: FrameworkRequest, reply: any) => { - const { assignments } = request.payload; - - // TODO abstract or change API to keep beatId consistent - const tweakedAssignments = assignments.map((assignment: any) => ({ - beatId: assignment.beat_id, - tag: assignment.tag, - })); + const { assignments }: { assignments: BeatsTagAssignment[] } = request.payload; try { - const response = await libs.beats.assignTagsToBeats(request.user, tweakedAssignments); + const response = await libs.beats.assignTagsToBeats(request.user, assignments); reply(response); } catch (err) { // TODO move this to kibana route thing in adapter return reply(wrapEsError(err)); } }, - method: 'POST', - path: '/api/beats/agents_tags/assignments', }); diff --git a/x-pack/plugins/beats_management/server/rest_api/beats/tag_removal.ts b/x-pack/plugins/beats_management/server/rest_api/beats/tag_removal.ts index 6a7af3b42d407..eaec8f2872172 100644 --- a/x-pack/plugins/beats_management/server/rest_api/beats/tag_removal.ts +++ b/x-pack/plugins/beats_management/server/rest_api/beats/tag_removal.ts @@ -12,6 +12,8 @@ import { wrapEsError } from '../../utils/error_wrappers'; // TODO: add license check pre-hook // TODO: write to Kibana audit log file export const createTagRemovalsRoute = (libs: CMServerLibs) => ({ + method: 'POST', + path: '/api/beats/agents_tags/removals', config: { validate: { payload: Joi.object({ @@ -41,6 +43,4 @@ export const createTagRemovalsRoute = (libs: CMServerLibs) => ({ return reply(wrapEsError(err)); } }, - method: 'POST', - path: '/api/beats/agents_tags/removals', }); diff --git a/x-pack/plugins/beats_management/server/rest_api/beats/update.ts b/x-pack/plugins/beats_management/server/rest_api/beats/update.ts index 60d7151731cd3..9c8e7daf475f8 100644 --- a/x-pack/plugins/beats_management/server/rest_api/beats/update.ts +++ b/x-pack/plugins/beats_management/server/rest_api/beats/update.ts @@ -12,6 +12,8 @@ import { wrapEsError } from '../../utils/error_wrappers'; // TODO: add license check pre-hook // TODO: write to Kibana audit log file (include who did the verification as well) export const createBeatUpdateRoute = (libs: CMServerLibs) => ({ + method: 'PUT', + path: '/api/beats/agent/{beatId}', config: { auth: false, validate: { @@ -56,6 +58,4 @@ export const createBeatUpdateRoute = (libs: CMServerLibs) => ({ return reply(wrapEsError(err)); } }, - method: 'PUT', - path: '/api/beats/agent/{beatId}', }); diff --git a/x-pack/plugins/beats_management/server/rest_api/tags/get.ts b/x-pack/plugins/beats_management/server/rest_api/tags/get.ts new file mode 100644 index 0000000000000..97aa6b413aeb3 --- /dev/null +++ b/x-pack/plugins/beats_management/server/rest_api/tags/get.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { BeatTag } from '../../../common/domain_types'; +import { CMServerLibs } from '../../lib/lib'; +import { wrapEsError } from '../../utils/error_wrappers'; + +export const createGetTagsWithIdsRoute = (libs: CMServerLibs) => ({ + method: 'GET', + path: '/api/beats/tags/{tagIds}', + handler: async (request: any, reply: any) => { + const tagIdString: string = request.params.tagIds; + const tagIds = tagIdString.split(',').filter((id: string) => id.length > 0); + + let tags: BeatTag[]; + try { + tags = await libs.tags.getTagsWithIds(request.user, tagIds); + } catch (err) { + return reply(wrapEsError(err)); + } + + reply(tags); + }, +}); diff --git a/x-pack/plugins/beats_management/server/rest_api/tags/list.ts b/x-pack/plugins/beats_management/server/rest_api/tags/list.ts new file mode 100644 index 0000000000000..41874a77eef0f --- /dev/null +++ b/x-pack/plugins/beats_management/server/rest_api/tags/list.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { BeatTag } from '../../../common/domain_types'; +import { CMServerLibs } from '../../lib/lib'; +import { wrapEsError } from '../../utils/error_wrappers'; + +export const createListTagsRoute = (libs: CMServerLibs) => ({ + method: 'GET', + path: '/api/beats/tags/', + handler: async (request: any, reply: any) => { + let tags: BeatTag[]; + try { + tags = await libs.tags.getAll(request.user); + } catch (err) { + return reply(wrapEsError(err)); + } + + reply(tags); + }, +}); diff --git a/x-pack/plugins/beats_management/server/rest_api/tags/set.ts b/x-pack/plugins/beats_management/server/rest_api/tags/set.ts index 6c01959e75311..f50721e764ef3 100644 --- a/x-pack/plugins/beats_management/server/rest_api/tags/set.ts +++ b/x-pack/plugins/beats_management/server/rest_api/tags/set.ts @@ -14,6 +14,8 @@ import { wrapEsError } from '../../utils/error_wrappers'; // TODO: add license check pre-hook // TODO: write to Kibana audit log file export const createSetTagRoute = (libs: CMServerLibs) => ({ + method: 'PUT', + path: '/api/beats/tag/{tag}', config: { validate: { params: Joi.object({ @@ -49,6 +51,4 @@ export const createSetTagRoute = (libs: CMServerLibs) => ({ return reply(wrapEsError(err)); } }, - method: 'PUT', - path: '/api/beats/tag/{tag}', }); diff --git a/x-pack/plugins/beats_management/server/rest_api/tokens/create.ts b/x-pack/plugins/beats_management/server/rest_api/tokens/create.ts index 74278703347c3..15ab7b872df8a 100644 --- a/x-pack/plugins/beats_management/server/rest_api/tokens/create.ts +++ b/x-pack/plugins/beats_management/server/rest_api/tokens/create.ts @@ -13,6 +13,8 @@ import { wrapEsError } from '../../utils/error_wrappers'; // TODO: write to Kibana audit log file const DEFAULT_NUM_TOKENS = 1; export const createTokensRoute = (libs: CMServerLibs) => ({ + method: 'POST', + path: '/api/beats/enrollment_tokens', config: { validate: { payload: Joi.object({ @@ -34,6 +36,4 @@ export const createTokensRoute = (libs: CMServerLibs) => ({ return reply(wrapEsError(err)); } }, - method: 'POST', - path: '/api/beats/enrollment_tokens', }); diff --git a/x-pack/test/api_integration/apis/beats/assign_tags_to_beats.js b/x-pack/test/api_integration/apis/beats/assign_tags_to_beats.js index 88b7b7c3feb3a..621a6187693bd 100644 --- a/x-pack/test/api_integration/apis/beats/assign_tags_to_beats.js +++ b/x-pack/test/api_integration/apis/beats/assign_tags_to_beats.js @@ -5,10 +5,7 @@ */ import expect from 'expect.js'; -import { - ES_INDEX_NAME, - ES_TYPE_NAME -} from './constants'; +import { ES_INDEX_NAME, ES_TYPE_NAME } from './constants'; export default function ({ getService }) { const supertest = getService('supertest'); @@ -24,25 +21,19 @@ export default function ({ getService }) { it('should add a single tag to a single beat', async () => { const { body: apiResponse } = await supertest - .post( - '/api/beats/agents_tags/assignments' - ) + .post('/api/beats/agents_tags/assignments') .set('kbn-xsrf', 'xxx') .send({ - assignments: [ - { beat_id: 'bar', tag: 'production' } - ] + assignments: [{ beatId: 'bar', tag: 'production' }], }) .expect(200); - expect(apiResponse.assignments).to.eql([ - { status: 200, result: 'updated' } - ]); + expect(apiResponse.assignments).to.eql([{ status: 200, result: 'updated' }]); const esResponse = await es.get({ index: ES_INDEX_NAME, type: ES_TYPE_NAME, - id: `beat:bar` + id: `beat:bar`, }); const beat = esResponse._source.beat; @@ -59,7 +50,7 @@ export default function ({ getService }) { esResponse = await es.get({ index: ES_INDEX_NAME, type: ES_TYPE_NAME, - id: `beat:foo` + id: `beat:foo`, }); beat = esResponse._source.beat; @@ -67,26 +58,20 @@ export default function ({ getService }) { // Adding the existing tag const { body: apiResponse } = await supertest - .post( - '/api/beats/agents_tags/assignments' - ) + .post('/api/beats/agents_tags/assignments') .set('kbn-xsrf', 'xxx') .send({ - assignments: [ - { beat_id: 'foo', tag: 'production' } - ] + assignments: [{ beatId: 'foo', tag: 'production' }], }) .expect(200); - expect(apiResponse.assignments).to.eql([ - { status: 200, result: 'updated' } - ]); + expect(apiResponse.assignments).to.eql([{ status: 200, result: 'updated' }]); // After adding the existing tag esResponse = await es.get({ index: ES_INDEX_NAME, type: ES_TYPE_NAME, - id: `beat:foo` + id: `beat:foo`, }); beat = esResponse._source.beat; @@ -95,21 +80,19 @@ export default function ({ getService }) { it('should add a single tag to a multiple beats', async () => { const { body: apiResponse } = await supertest - .post( - '/api/beats/agents_tags/assignments' - ) + .post('/api/beats/agents_tags/assignments') .set('kbn-xsrf', 'xxx') .send({ assignments: [ - { beat_id: 'foo', tag: 'development' }, - { beat_id: 'bar', tag: 'development' } - ] + { beatId: 'foo', tag: 'development' }, + { beatId: 'bar', tag: 'development' }, + ], }) .expect(200); expect(apiResponse.assignments).to.eql([ { status: 200, result: 'updated' }, - { status: 200, result: 'updated' } + { status: 200, result: 'updated' }, ]); let esResponse; @@ -119,7 +102,7 @@ export default function ({ getService }) { esResponse = await es.get({ index: ES_INDEX_NAME, type: ES_TYPE_NAME, - id: `beat:foo` + id: `beat:foo`, }); beat = esResponse._source.beat; @@ -129,7 +112,7 @@ export default function ({ getService }) { esResponse = await es.get({ index: ES_INDEX_NAME, type: ES_TYPE_NAME, - id: `beat:bar` + id: `beat:bar`, }); beat = esResponse._source.beat; @@ -138,27 +121,25 @@ export default function ({ getService }) { it('should add multiple tags to a single beat', async () => { const { body: apiResponse } = await supertest - .post( - '/api/beats/agents_tags/assignments' - ) + .post('/api/beats/agents_tags/assignments') .set('kbn-xsrf', 'xxx') .send({ assignments: [ - { beat_id: 'bar', tag: 'development' }, - { beat_id: 'bar', tag: 'production' } - ] + { beatId: 'bar', tag: 'development' }, + { beatId: 'bar', tag: 'production' }, + ], }) .expect(200); expect(apiResponse.assignments).to.eql([ { status: 200, result: 'updated' }, - { status: 200, result: 'updated' } + { status: 200, result: 'updated' }, ]); const esResponse = await es.get({ index: ES_INDEX_NAME, type: ES_TYPE_NAME, - id: `beat:bar` + id: `beat:bar`, }); const beat = esResponse._source.beat; @@ -167,21 +148,19 @@ export default function ({ getService }) { it('should add multiple tags to a multiple beats', async () => { const { body: apiResponse } = await supertest - .post( - '/api/beats/agents_tags/assignments' - ) + .post('/api/beats/agents_tags/assignments') .set('kbn-xsrf', 'xxx') .send({ assignments: [ - { beat_id: 'foo', tag: 'development' }, - { beat_id: 'bar', tag: 'production' } - ] + { beatId: 'foo', tag: 'development' }, + { beatId: 'bar', tag: 'production' }, + ], }) .expect(200); expect(apiResponse.assignments).to.eql([ { status: 200, result: 'updated' }, - { status: 200, result: 'updated' } + { status: 200, result: 'updated' }, ]); let esResponse; @@ -191,7 +170,7 @@ export default function ({ getService }) { esResponse = await es.get({ index: ES_INDEX_NAME, type: ES_TYPE_NAME, - id: `beat:foo` + id: `beat:foo`, }); beat = esResponse._source.beat; @@ -201,7 +180,7 @@ export default function ({ getService }) { esResponse = await es.get({ index: ES_INDEX_NAME, type: ES_TYPE_NAME, - id: `beat:bar` + id: `beat:bar`, }); beat = esResponse._source.beat; @@ -212,19 +191,15 @@ export default function ({ getService }) { const nonExistentBeatId = chance.word(); const { body: apiResponse } = await supertest - .post( - '/api/beats/agents_tags/assignments' - ) + .post('/api/beats/agents_tags/assignments') .set('kbn-xsrf', 'xxx') .send({ - assignments: [ - { beat_id: nonExistentBeatId, tag: 'production' } - ] + assignments: [{ beatId: nonExistentBeatId, tag: 'production' }], }) .expect(200); expect(apiResponse.assignments).to.eql([ - { status: 404, result: `Beat ${nonExistentBeatId} not found` } + { status: 404, result: `Beat ${nonExistentBeatId} not found` }, ]); }); @@ -232,25 +207,21 @@ export default function ({ getService }) { const nonExistentTag = chance.word(); const { body: apiResponse } = await supertest - .post( - '/api/beats/agents_tags/assignments' - ) + .post('/api/beats/agents_tags/assignments') .set('kbn-xsrf', 'xxx') .send({ - assignments: [ - { beat_id: 'bar', tag: nonExistentTag } - ] + assignments: [{ beatID: 'bar', tag: nonExistentTag }], }) .expect(200); expect(apiResponse.assignments).to.eql([ - { status: 404, result: `Tag ${nonExistentTag} not found` } + { status: 404, result: `Tag ${nonExistentTag} not found` }, ]); const esResponse = await es.get({ index: ES_INDEX_NAME, type: ES_TYPE_NAME, - id: `beat:bar` + id: `beat:bar`, }); const beat = esResponse._source.beat; @@ -262,25 +233,21 @@ export default function ({ getService }) { const nonExistentTag = chance.word(); const { body: apiResponse } = await supertest - .post( - '/api/beats/agents_tags/assignments' - ) + .post('/api/beats/agents_tags/assignments') .set('kbn-xsrf', 'xxx') .send({ - assignments: [ - { beat_id: nonExistentBeatId, tag: nonExistentTag } - ] + assignments: [{ beatID: nonExistentBeatId, tag: nonExistentTag }], }) .expect(200); expect(apiResponse.assignments).to.eql([ - { status: 404, result: `Beat ${nonExistentBeatId} and tag ${nonExistentTag} not found` } + { status: 404, result: `Beat ${nonExistentBeatId} and tag ${nonExistentTag} not found` }, ]); const esResponse = await es.get({ index: ES_INDEX_NAME, type: ES_TYPE_NAME, - id: `beat:bar` + id: `beat:bar`, }); const beat = esResponse._source.beat; From b68dab8c9753a49d975a24248f4bd4ffb1656a7c Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Fri, 3 Aug 2018 10:28:55 -0400 Subject: [PATCH 26/94] [Beats Management] Add BeatsTable/Bulk Action Search Component (#21182) * Add BeatsTable and control bar components. * Clean yarn.lock. * Move raw numbers/strings to constants. Remove obsolete state/props. * Update/add tests. * Change prop name from "items" to "beats". * Rename some variables. * Move search bar filter definitions to table render. * Update table to support assignment options. * Update action control position. * Refactor split render function into custom components. --- .../common/constants/index.ts | 1 + .../common/constants/table.ts | 10 ++ .../beats_management/common/domain_types.ts | 7 ++ .../public/components/table/action_button.tsx | 63 ++++++++++ .../components/table/assignment_options.tsx | 103 ++++++++++++++++ .../public/components/table/controls.tsx | 48 ++++++++ .../public/components/table/index.ts | 9 ++ .../public/components/table/table.tsx | 88 +++++++++++++ .../components/table/table_type_configs.tsx | 116 ++++++++++++++++++ .../public/pages/main/beats.tsx | 105 +++++++++++++++- .../lib/adapters/beats/adapter_types.ts | 7 +- x-pack/plugins/beats_management/wallaby.js | 3 +- 12 files changed, 552 insertions(+), 8 deletions(-) create mode 100644 x-pack/plugins/beats_management/common/constants/table.ts create mode 100644 x-pack/plugins/beats_management/public/components/table/action_button.tsx create mode 100644 x-pack/plugins/beats_management/public/components/table/assignment_options.tsx create mode 100644 x-pack/plugins/beats_management/public/components/table/controls.tsx create mode 100644 x-pack/plugins/beats_management/public/components/table/index.ts create mode 100644 x-pack/plugins/beats_management/public/components/table/table.tsx create mode 100644 x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx diff --git a/x-pack/plugins/beats_management/common/constants/index.ts b/x-pack/plugins/beats_management/common/constants/index.ts index b4e919607c604..50851dcef947e 100644 --- a/x-pack/plugins/beats_management/common/constants/index.ts +++ b/x-pack/plugins/beats_management/common/constants/index.ts @@ -8,3 +8,4 @@ export { PLUGIN } from './plugin'; export { INDEX_NAMES } from './index_names'; export { UNIQUENESS_ENFORCING_TYPES, ConfigurationBlockTypes } from './configuration_blocks'; export const BASE_PATH = '/management/beats_management/'; +export { TABLE_CONFIG } from './table'; diff --git a/x-pack/plugins/beats_management/common/constants/table.ts b/x-pack/plugins/beats_management/common/constants/table.ts new file mode 100644 index 0000000000000..801a60082d1b8 --- /dev/null +++ b/x-pack/plugins/beats_management/common/constants/table.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const TABLE_CONFIG = { + INITIAL_ROW_SIZE: 5, + PAGE_SIZE_OPTIONS: [3, 5, 10, 20], +}; diff --git a/x-pack/plugins/beats_management/common/domain_types.ts b/x-pack/plugins/beats_management/common/domain_types.ts index 9411aca413840..a553f987a233e 100644 --- a/x-pack/plugins/beats_management/common/domain_types.ts +++ b/x-pack/plugins/beats_management/common/domain_types.ts @@ -19,13 +19,20 @@ export interface CMBeat { host_ip: string; host_name: string; ephemeral_id?: string; + last_updated?: string; + event_rate?: string; local_configuration_yml?: string; tags?: string[]; central_configuration_yml?: string; metadata?: {}; } +export interface CMPopulatedBeat extends CMBeat { + full_tags: BeatTag[]; +} + export interface BeatTag { id: string; configuration_blocks: ConfigurationBlock[]; + color?: string; } diff --git a/x-pack/plugins/beats_management/public/components/table/action_button.tsx b/x-pack/plugins/beats_management/public/components/table/action_button.tsx new file mode 100644 index 0000000000000..e91e620ed8d8c --- /dev/null +++ b/x-pack/plugins/beats_management/public/components/table/action_button.tsx @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiButton, EuiContextMenu, EuiPopover } from '@elastic/eui'; +import React from 'react'; +import { ActionDefinition } from './table_type_configs'; + +interface ActionButtonProps { + actions: ActionDefinition[]; + isPopoverVisible: boolean; + actionHandler(action: string, payload?: any): void; + hidePopover(): void; + showPopover(): void; +} + +export function ActionButton(props: ActionButtonProps) { + const { actions, actionHandler, hidePopover, isPopoverVisible, showPopover } = props; + if (actions.length === 0) { + return null; + } else if (actions.length === 1) { + const action = actions[0]; + return ( + actionHandler(action.action)} + > + {action.name} + + ); + } + return ( + + Bulk Action + + } + closePopover={hidePopover} + id="contextMenu" + isOpen={isPopoverVisible} + panelPaddingSize="none" + withTitle + > + ({ + ...action, + onClick: () => actionHandler(action.action), + })), + }, + ]} + /> + + ); +} diff --git a/x-pack/plugins/beats_management/public/components/table/assignment_options.tsx b/x-pack/plugins/beats_management/public/components/table/assignment_options.tsx new file mode 100644 index 0000000000000..60a6bf2b46952 --- /dev/null +++ b/x-pack/plugins/beats_management/public/components/table/assignment_options.tsx @@ -0,0 +1,103 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner, EuiPopover } from '@elastic/eui'; +import React from 'react'; +import { ActionButton } from './action_button'; +import { ControlDefinitions } from './table_type_configs'; + +interface AssignmentOptionsProps { + assignmentOptions: any[] | null; + assignmentTitle: string | null; + controlDefinitions: ControlDefinitions; + selectionCount: number; + actionHandler(action: string, payload?: any): void; +} + +interface AssignmentOptionsState { + isAssignmentPopoverVisible: boolean; + isActionPopoverVisible: boolean; +} + +export class AssignmentOptions extends React.Component< + AssignmentOptionsProps, + AssignmentOptionsState +> { + constructor(props: AssignmentOptionsProps) { + super(props); + + this.state = { + isAssignmentPopoverVisible: false, + isActionPopoverVisible: false, + }; + } + + public render() { + const { + actionHandler, + assignmentOptions, + assignmentTitle, + controlDefinitions: { actions }, + selectionCount, + } = this.props; + const { isActionPopoverVisible, isAssignmentPopoverVisible } = this.state; + return ( + + {selectionCount} selected + + { + this.setState({ isActionPopoverVisible: false }); + }} + isPopoverVisible={isActionPopoverVisible} + showPopover={() => { + this.setState({ isActionPopoverVisible: true }); + }} + /> + + + { + this.setState({ + isAssignmentPopoverVisible: true, + }); + actionHandler('loadAssignmentOptions'); + }} + > + {assignmentTitle} + + } + closePopover={() => { + this.setState({ isAssignmentPopoverVisible: false }); + }} + id="assignmentList" + isOpen={isAssignmentPopoverVisible} + panelPaddingSize="s" + withTitle + > + {assignmentOptions ? ( + // @ts-ignore direction prop not available on current typing + + {assignmentOptions} + + ) : ( +
+ Loading +
+ )} +
+
+
+ ); + } +} diff --git a/x-pack/plugins/beats_management/public/components/table/controls.tsx b/x-pack/plugins/beats_management/public/components/table/controls.tsx new file mode 100644 index 0000000000000..5182c10c71722 --- /dev/null +++ b/x-pack/plugins/beats_management/public/components/table/controls.tsx @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + // @ts-ignore typings for EuiSearchar not included in EUI + EuiSearchBar, +} from '@elastic/eui'; +import React from 'react'; +import { AssignmentOptions } from './assignment_options'; +import { ControlDefinitions } from './table_type_configs'; + +interface ControlBarProps { + assignmentOptions: any[] | null; + assignmentTitle: string | null; + showAssignmentOptions: boolean; + controlDefinitions: ControlDefinitions; + selectionCount: number; + actionHandler(actionType: string, payload?: any): void; +} + +export function ControlBar(props: ControlBarProps) { + const { + actionHandler, + assignmentOptions, + assignmentTitle, + controlDefinitions, + selectionCount, + showAssignmentOptions, + } = props; + return selectionCount !== 0 && showAssignmentOptions ? ( + + ) : ( + actionHandler('search', query)} + /> + ); +} diff --git a/x-pack/plugins/beats_management/public/components/table/index.ts b/x-pack/plugins/beats_management/public/components/table/index.ts new file mode 100644 index 0000000000000..0789cad5c3022 --- /dev/null +++ b/x-pack/plugins/beats_management/public/components/table/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { Table } from './table'; +export { ControlBar } from './controls'; +export { BeatsTableType } from './table_type_configs'; diff --git a/x-pack/plugins/beats_management/public/components/table/table.tsx b/x-pack/plugins/beats_management/public/components/table/table.tsx new file mode 100644 index 0000000000000..c66c290b2f5f9 --- /dev/null +++ b/x-pack/plugins/beats_management/public/components/table/table.tsx @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + // @ts-ignore no typings for EuiInMemoryTable in EUI + EuiInMemoryTable, + EuiSpacer, +} from '@elastic/eui'; +import React from 'react'; +import styled from 'styled-components'; +import { TABLE_CONFIG } from '../../../common/constants'; +import { CMPopulatedBeat } from '../../../common/domain_types'; +import { ControlBar } from './controls'; +import { TableType } from './table_type_configs'; + +interface BeatsTableProps { + assignmentOptions: any[] | null; + assignmentTitle: string | null; + items: any[]; + type: TableType; + actionHandler(action: string, payload?: any): void; +} + +interface BeatsTableState { + selection: CMPopulatedBeat[]; +} + +const TableContainer = styled.div` + padding: 16px; +`; + +export class Table extends React.Component { + constructor(props: BeatsTableProps) { + super(props); + + this.state = { + selection: [], + }; + } + + public render() { + const { actionHandler, assignmentOptions, assignmentTitle, items, type } = this.props; + + const pagination = { + initialPageSize: TABLE_CONFIG.INITIAL_ROW_SIZE, + pageSizeOptions: TABLE_CONFIG.PAGE_SIZE_OPTIONS, + }; + + const selectionOptions = { + onSelectionChange: this.setSelection, + selectable: () => true, + selectableMessage: () => 'Select this beat', + selection: this.state.selection, + }; + + return ( + + actionHandler(action, payload)} + assignmentOptions={assignmentOptions} + assignmentTitle={assignmentTitle} + controlDefinitions={type.controlDefinitions(items)} + selectionCount={this.state.selection.length} + showAssignmentOptions={true} + /> + + + + ); + } + + private setSelection = (selection: any) => { + this.setState({ + selection, + }); + }; +} diff --git a/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx b/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx new file mode 100644 index 0000000000000..fb4529da171cb --- /dev/null +++ b/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx @@ -0,0 +1,116 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +// @ts-ignore +import { EuiBadge, EuiFlexGroup, EuiIcon, EuiLink } from '@elastic/eui'; +import { flatten, uniq } from 'lodash'; +import moment from 'moment'; +import React from 'react'; +import { CMPopulatedBeat } from '../../../common/domain_types'; + +export interface ColumnDefinition { + field: string; + name: string; + sortable?: boolean; + render?(value: any, object?: any): any; +} + +export interface ActionDefinition { + action: string; + danger?: boolean; + icon?: any; + name: string; +} + +interface FilterOption { + value: string; +} + +export interface FilterDefinition { + field: string; + name: string; + options?: FilterOption[]; + type: string; +} + +export interface ControlDefinitions { + actions: ActionDefinition[]; + filters: FilterDefinition[]; +} + +export interface TableType { + columnDefinitions: ColumnDefinition[]; + controlDefinitions(items: any[]): ControlDefinitions; +} + +export const BeatsTableType: TableType = { + columnDefinitions: [ + { + field: 'id', + name: 'Beat name', + render: (id: string) => {id}, + sortable: true, + }, + { + field: 'type', + name: 'Type', + sortable: true, + }, + { + field: 'full_tags', + name: 'Tags', + render: (value: string, beat: CMPopulatedBeat) => ( + + {beat.full_tags.map(tag => ( + + {tag.id} + + ))} + + ), + sortable: false, + }, + { + // TODO: update to use actual metadata field + field: 'event_rate', + name: 'Event rate', + sortable: true, + }, + { + // TODO: update to use actual metadata field + field: 'last_updated', + name: 'Last config update', + render: (value: Date) =>
{moment(value).fromNow()}
, + sortable: true, + }, + ], + controlDefinitions: (data: any) => ({ + actions: [ + { + name: 'Disenroll Selected', + action: 'delete', + danger: true, + }, + ], + filters: [ + { + type: 'field_value_selection', + field: 'type', + name: 'Type', + options: uniq(data.map(({ type }: { type: any }) => ({ value: type })), 'value'), + }, + { + type: 'field_value_selection', + field: 'full_tags', + name: 'Tags', + options: uniq( + flatten(data.map((item: any) => item.full_tags)).map((tag: any) => ({ value: tag.id })), + 'value' + ), + }, + ], + }), +}; diff --git a/x-pack/plugins/beats_management/public/pages/main/beats.tsx b/x-pack/plugins/beats_management/public/pages/main/beats.tsx index a8c7518a3e27c..1d8a9e6717605 100644 --- a/x-pack/plugins/beats_management/public/pages/main/beats.tsx +++ b/x-pack/plugins/beats_management/public/pages/main/beats.tsx @@ -5,8 +5,11 @@ */ import { + // @ts-ignore typings for EuiBadge not present in current version + EuiBadge, EuiButton, EuiButtonEmpty, + EuiFlexItem, EuiModal, EuiModalBody, EuiModalFooter, @@ -15,16 +18,20 @@ import { EuiOverlayMask, } from '@elastic/eui'; -import { CMBeat } from '../../../common/domain_types'; +import React from 'react'; +import { BeatTag, CMBeat, CMPopulatedBeat } from '../../../common/domain_types'; +import { BeatsTagAssignment } from '../../../server/lib/adapters/beats/adapter_types'; +import { BeatsTableType, Table } from '../../components/table'; import { FrontendLibs } from '../../lib/lib'; -import React from 'react'; interface BeatsPageProps { libs: FrontendLibs; } interface BeatsPageState { beats: CMBeat[]; + tags: any[] | null; + tableRef: any; } export class BeatsPage extends React.PureComponent { @@ -72,17 +79,109 @@ export class BeatsPage extends React.PureComponentbeats table and stuff - {this.state.beats.length}; + return ( + + ); } + + private handleBeatsActions = (action: string, payload: any) => { + switch (action) { + case 'edit': + // TODO: navigate to edit page + break; + case 'delete': + this.deleteSelected(); + break; + case 'search': + this.handleSearchQuery(payload); + break; + case 'loadAssignmentOptions': + this.loadTags(); + break; + } + + this.loadBeats(); + }; + + // TODO: call delete endpoint + private deleteSelected = async () => { + // const selected = this.getSelectedBeats(); + // await this.props.libs.beats.delete(selected); + }; + private async loadBeats() { const beats = await this.props.libs.beats.getAll(); this.setState({ beats, }); } + + // todo: add reference to ES filter endpoint + private handleSearchQuery = (query: any) => { + // await this.props.libs.beats.searach(query); + }; + + private loadTags = async () => { + const tags = await this.props.libs.tags.getAll(); + const selectedBeats = this.getSelectedBeats(); + + const renderedTags = tags.map((tag: BeatTag) => { + const hasMatches = selectedBeats.some((beat: any) => + beat.full_tags.some((t: any) => t.id === tag.id) + ); + + return ( + + this.removeTagsFromBeats(selectedBeats, tag) + : () => this.assignTagsToBeats(selectedBeats, tag) + } + onClickAriaLabel={tag.id} + > + {tag.id} + + + ); + }); + this.setState({ + tags: renderedTags, + }); + }; + + private createBeatTagAssignments = ( + beats: CMPopulatedBeat[], + tag: BeatTag + ): BeatsTagAssignment[] => beats.map(({ id }) => ({ beatId: id, tag: tag.id })); + + private removeTagsFromBeats = async (beats: CMPopulatedBeat[], tag: BeatTag) => { + await this.props.libs.beats.removeTagsFromBeats(this.createBeatTagAssignments(beats, tag)); + this.loadBeats(); + }; + + private assignTagsToBeats = async (beats: CMPopulatedBeat[], tag: BeatTag) => { + await this.props.libs.beats.assignTagsToBeats(this.createBeatTagAssignments(beats, tag)); + this.loadBeats(); + }; + + private getSelectedBeats = () => { + return this.state.tableRef.current.state.selection; + }; } diff --git a/x-pack/plugins/beats_management/server/lib/adapters/beats/adapter_types.ts b/x-pack/plugins/beats_management/server/lib/adapters/beats/adapter_types.ts index 0d68fd528d42a..1f1f2fd13e3bc 100644 --- a/x-pack/plugins/beats_management/server/lib/adapters/beats/adapter_types.ts +++ b/x-pack/plugins/beats_management/server/lib/adapters/beats/adapter_types.ts @@ -7,13 +7,12 @@ import { CMBeat } from '../../../../common/domain_types'; import { FrameworkUser } from '../framework/adapter_types'; -// FIXME: fix getBeatsWithIds return type export interface CMBeatsAdapter { insert(beat: CMBeat): Promise; update(beat: CMBeat): Promise; - get(user: FrameworkUser, id: string): any; - getAll(user: FrameworkUser): any; - getWithIds(user: FrameworkUser, beatIds: string[]): any; + get(user: FrameworkUser, id: string): Promise; + getAll(user: FrameworkUser): Promise; + getWithIds(user: FrameworkUser, beatIds: string[]): Promise; removeTagsFromBeats( user: FrameworkUser, removals: BeatsTagAssignment[] diff --git a/x-pack/plugins/beats_management/wallaby.js b/x-pack/plugins/beats_management/wallaby.js index 8c0c4aa355925..e9901ff5bf23e 100644 --- a/x-pack/plugins/beats_management/wallaby.js +++ b/x-pack/plugins/beats_management/wallaby.js @@ -14,10 +14,11 @@ module.exports = function (wallaby) { //'plugins/beats/public/**/*.+(js|jsx|ts|tsx|json|snap|css|less|sass|scss|jpg|jpeg|gif|png|svg)', 'server/**/*.+(js|jsx|ts|tsx|json|snap|css|less|sass|scss|jpg|jpeg|gif|png|svg)', 'common/**/*.+(js|jsx|ts|tsx|json|snap|css|less|sass|scss|jpg|jpeg|gif|png|svg)', + 'public/**/*.+(js|jsx|ts|tsx|json|snap|css|less|sass|scss|jpg|jpeg|gif|png|svg)', '!**/*.test.ts', ], - tests: ['**/*.test.ts'], + tests: ['**/*.test.ts', '**/*.test.tsx'], env: { type: 'node', runner: 'node', From 2089fe80624177cc73e148afa1be83b9fe315feb Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Fri, 3 Aug 2018 17:58:03 -0400 Subject: [PATCH 27/94] Beats/basic use cases (#21660) * tweak adapter responses / types. re-add enroll ui * routes enabled, enroll now pings the server * full enrollment path now working * improved pinging for beat enrollment * fix location of history call * reload beats list on beat enrollment completion --- .../beats_management/common/domain_types.ts | 1 + .../components/table/table_type_configs.tsx | 8 +- .../plugins/beats_management/public/index.tsx | 2 +- .../lib/adapters/beats/adapter_types.ts | 1 + .../adapters/beats/memory_beats_adapter.ts | 4 +- .../lib/adapters/beats/rest_beats_adapter.ts | 25 ++- .../lib/adapters/tags/rest_tags_adapter.ts | 8 +- .../lib/adapters/tokens/adapter_types.ts | 6 +- .../adapters/tokens/memory_tokens_adapter.ts | 9 +- .../adapters/tokens/rest_tokens_adapter.ts | 7 +- .../public/pages/main/beats.tsx | 57 +----- .../public/pages/main/beats_action_area.tsx | 162 ++++++++++++++++++ .../public/pages/main/index.tsx | 17 +- x-pack/plugins/beats_management/readme.md | 9 + .../beats_management/scripts/enroll.js | 34 ++++ .../lib/adapters/beats/adapter_types.ts | 7 +- .../beats/elasticsearch_beats_adapter.ts | 27 ++- .../adapters/beats/memory_beats_adapter.ts | 9 +- .../server/lib/domains/beats.ts | 5 + .../server/management_server.ts | 6 + .../server/rest_api/beats/get.ts | 36 ++-- .../utils/index_templates/beats_template.json | 8 +- .../api_integration/apis/beats/enroll_beat.js | 2 +- 23 files changed, 336 insertions(+), 114 deletions(-) create mode 100644 x-pack/plugins/beats_management/public/pages/main/beats_action_area.tsx create mode 100644 x-pack/plugins/beats_management/scripts/enroll.js diff --git a/x-pack/plugins/beats_management/common/domain_types.ts b/x-pack/plugins/beats_management/common/domain_types.ts index a553f987a233e..b943cc2cedc2b 100644 --- a/x-pack/plugins/beats_management/common/domain_types.ts +++ b/x-pack/plugins/beats_management/common/domain_types.ts @@ -12,6 +12,7 @@ export interface ConfigurationBlock { export interface CMBeat { id: string; + enrollment_token: string; access_token: string; verified_on?: string; type: string; diff --git a/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx b/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx index fb4529da171cb..d167d9aac3e22 100644 --- a/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx +++ b/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx @@ -64,7 +64,7 @@ export const BeatsTableType: TableType = { name: 'Tags', render: (value: string, beat: CMPopulatedBeat) => ( - {beat.full_tags.map(tag => ( + {(beat.full_tags || []).map(tag => ( {tag.id} @@ -87,7 +87,7 @@ export const BeatsTableType: TableType = { sortable: true, }, ], - controlDefinitions: (data: any) => ({ + controlDefinitions: (data: any[]) => ({ actions: [ { name: 'Disenroll Selected', @@ -107,7 +107,9 @@ export const BeatsTableType: TableType = { field: 'full_tags', name: 'Tags', options: uniq( - flatten(data.map((item: any) => item.full_tags)).map((tag: any) => ({ value: tag.id })), + flatten(data.map((item: any) => item.full_tags || [])).map((tag: any) => ({ + value: tag.id, + })), 'value' ), }, diff --git a/x-pack/plugins/beats_management/public/index.tsx b/x-pack/plugins/beats_management/public/index.tsx index 670aa23f8695b..9da5a99fc7028 100644 --- a/x-pack/plugins/beats_management/public/index.tsx +++ b/x-pack/plugins/beats_management/public/index.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { BASE_PATH } from '../common/constants'; -import { compose } from './lib/compose/memory'; +import { compose } from './lib/compose/kibana'; import { FrontendLibs } from './lib/lib'; // import * as euiVars from '@elastic/eui/dist/eui_theme_k6_light.json'; diff --git a/x-pack/plugins/beats_management/public/lib/adapters/beats/adapter_types.ts b/x-pack/plugins/beats_management/public/lib/adapters/beats/adapter_types.ts index b703edb971299..38129f27ab38f 100644 --- a/x-pack/plugins/beats_management/public/lib/adapters/beats/adapter_types.ts +++ b/x-pack/plugins/beats_management/public/lib/adapters/beats/adapter_types.ts @@ -11,6 +11,7 @@ export interface CMBeatsAdapter { getAll(): Promise; removeTagsFromBeats(removals: BeatsTagAssignment[]): Promise; assignTagsToBeats(assignments: BeatsTagAssignment[]): Promise; + getBeatWithToken(enrollmentToken: string): Promise; } export interface BeatsTagAssignment { diff --git a/x-pack/plugins/beats_management/public/lib/adapters/beats/memory_beats_adapter.ts b/x-pack/plugins/beats_management/public/lib/adapters/beats/memory_beats_adapter.ts index 1940ec2ada4b0..565ed0932214f 100644 --- a/x-pack/plugins/beats_management/public/lib/adapters/beats/memory_beats_adapter.ts +++ b/x-pack/plugins/beats_management/public/lib/adapters/beats/memory_beats_adapter.ts @@ -28,7 +28,9 @@ export class MemoryBeatsAdapter implements CMBeatsAdapter { public async getAll() { return this.beatsDB.map((beat: any) => omit(beat, ['access_token'])); } - + public async getBeatWithToken(enrollmentToken: string): Promise { + return this.beatsDB.map((beat: any) => omit(beat, ['access_token']))[0]; + } public async removeTagsFromBeats(removals: BeatsTagAssignment[]): Promise { const beatIds = removals.map(r => r.beatId); diff --git a/x-pack/plugins/beats_management/public/lib/adapters/beats/rest_beats_adapter.ts b/x-pack/plugins/beats_management/public/lib/adapters/beats/rest_beats_adapter.ts index 9da760be307db..aaf9d545dffee 100644 --- a/x-pack/plugins/beats_management/public/lib/adapters/beats/rest_beats_adapter.ts +++ b/x-pack/plugins/beats_management/public/lib/adapters/beats/rest_beats_adapter.ts @@ -19,19 +19,30 @@ export class RestBeatsAdapter implements CMBeatsAdapter { return await this.REST.get(`/api/beats/agent/${id}`); } + public async getBeatWithToken(enrollmentToken: string): Promise { + const beat = await this.REST.get(`/api/beats/agent/unknown/${enrollmentToken}`); + return beat; + } + public async getAll(): Promise { - return await this.REST.get('/api/beats/agents'); + return (await this.REST.get<{ beats: CMBeat[] }>('/api/beats/agents')).beats; } public async removeTagsFromBeats(removals: BeatsTagAssignment[]): Promise { - return await this.REST.post(`/api/beats/agents_tags/removals`, { - removals, - }); + return (await this.REST.post<{ removals: BeatsRemovalReturn[] }>( + `/api/beats/agents_tags/removals`, + { + removals, + } + )).removals; } public async assignTagsToBeats(assignments: BeatsTagAssignment[]): Promise { - return await this.REST.post(`/api/beats/agents_tags/assignments`, { - assignments, - }); + return (await this.REST.post<{ assignments: CMAssignmentReturn[] }>( + `/api/beats/agents_tags/assignments`, + { + assignments, + } + )).assignments; } } diff --git a/x-pack/plugins/beats_management/public/lib/adapters/tags/rest_tags_adapter.ts b/x-pack/plugins/beats_management/public/lib/adapters/tags/rest_tags_adapter.ts index 5eb16762ba825..fc4a5a157ed71 100644 --- a/x-pack/plugins/beats_management/public/lib/adapters/tags/rest_tags_adapter.ts +++ b/x-pack/plugins/beats_management/public/lib/adapters/tags/rest_tags_adapter.ts @@ -12,16 +12,16 @@ export class RestTagsAdapter implements CMTagsAdapter { constructor(private readonly REST: RestAPIAdapter) {} public async getTagsWithIds(tagIds: string[]): Promise { - return await this.REST.get(`/api/beats/tags/${tagIds.join(',')}`); + return (await this.REST.get<{ tags: BeatTag[] }>(`/api/beats/tags/${tagIds.join(',')}`)).tags; } public async getAll(): Promise { - return await this.REST.get(`/api/beats/tags`); + return (await this.REST.get<{ tags: BeatTag[] }>(`/api/beats/tags`)).tags; } public async upsertTag(tag: BeatTag): Promise { - return await this.REST.put(`/api/beats/tag/{tag}`, { + return (await this.REST.put<{ tag: BeatTag }>(`/api/beats/tag/{tag}`, { configuration_blocks: tag.configuration_blocks, - }); + })).tag; } } diff --git a/x-pack/plugins/beats_management/public/lib/adapters/tokens/adapter_types.ts b/x-pack/plugins/beats_management/public/lib/adapters/tokens/adapter_types.ts index 43f2d95b02b2c..55b7e6f94fe04 100644 --- a/x-pack/plugins/beats_management/public/lib/adapters/tokens/adapter_types.ts +++ b/x-pack/plugins/beats_management/public/lib/adapters/tokens/adapter_types.ts @@ -3,11 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -export interface TokenEnrollmentData { - token: string | null; - expires_on: string; -} export interface CMTokensAdapter { - createEnrollmentToken(): Promise; + createEnrollmentToken(): Promise; } diff --git a/x-pack/plugins/beats_management/public/lib/adapters/tokens/memory_tokens_adapter.ts b/x-pack/plugins/beats_management/public/lib/adapters/tokens/memory_tokens_adapter.ts index 4e80bc234d186..f329e491c9ad0 100644 --- a/x-pack/plugins/beats_management/public/lib/adapters/tokens/memory_tokens_adapter.ts +++ b/x-pack/plugins/beats_management/public/lib/adapters/tokens/memory_tokens_adapter.ts @@ -4,13 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { CMTokensAdapter, TokenEnrollmentData } from './adapter_types'; +import { CMTokensAdapter } from './adapter_types'; export class MemoryTokensAdapter implements CMTokensAdapter { - public async createEnrollmentToken(): Promise { - return { - token: '2jnwkrhkwuehriauhweair', - expires_on: new Date().toJSON(), - }; + public async createEnrollmentToken(): Promise { + return '2jnwkrhkwuehriauhweair'; } } diff --git a/x-pack/plugins/beats_management/public/lib/adapters/tokens/rest_tokens_adapter.ts b/x-pack/plugins/beats_management/public/lib/adapters/tokens/rest_tokens_adapter.ts index de5890fdaebf5..778bcbf5d8d5c 100644 --- a/x-pack/plugins/beats_management/public/lib/adapters/tokens/rest_tokens_adapter.ts +++ b/x-pack/plugins/beats_management/public/lib/adapters/tokens/rest_tokens_adapter.ts @@ -5,13 +5,14 @@ */ import { RestAPIAdapter } from '../rest_api/adapter_types'; -import { CMTokensAdapter, TokenEnrollmentData } from './adapter_types'; +import { CMTokensAdapter } from './adapter_types'; export class RestTokensAdapter implements CMTokensAdapter { constructor(private readonly REST: RestAPIAdapter) {} - public async createEnrollmentToken(): Promise { - const tokens = await this.REST.post('/api/beats/enrollment_tokens'); + public async createEnrollmentToken(): Promise { + const tokens = (await this.REST.post<{ tokens: string[] }>('/api/beats/enrollment_tokens')) + .tokens; return tokens[0]; } } diff --git a/x-pack/plugins/beats_management/public/pages/main/beats.tsx b/x-pack/plugins/beats_management/public/pages/main/beats.tsx index 1d8a9e6717605..24baf43ba2c83 100644 --- a/x-pack/plugins/beats_management/public/pages/main/beats.tsx +++ b/x-pack/plugins/beats_management/public/pages/main/beats.tsx @@ -7,15 +7,7 @@ import { // @ts-ignore typings for EuiBadge not present in current version EuiBadge, - EuiButton, - EuiButtonEmpty, EuiFlexItem, - EuiModal, - EuiModalBody, - EuiModalFooter, - EuiModalHeader, - EuiModalHeaderTitle, - EuiOverlayMask, } from '@elastic/eui'; import React from 'react'; @@ -23,9 +15,11 @@ import { BeatTag, CMBeat, CMPopulatedBeat } from '../../../common/domain_types'; import { BeatsTagAssignment } from '../../../server/lib/adapters/beats/adapter_types'; import { BeatsTableType, Table } from '../../components/table'; import { FrontendLibs } from '../../lib/lib'; +import { BeatsActionArea } from './beats_action_area'; interface BeatsPageProps { libs: FrontendLibs; + location: any; } interface BeatsPageState { @@ -35,45 +29,7 @@ interface BeatsPageState { } export class BeatsPage extends React.PureComponent { - public static ActionArea = ({ match, history }: { match: any; history: any }) => ( -
- { - window.alert('This will later go to more general beats install instructions.'); - window.location.href = '#/home/tutorial/dockerMetrics'; - }} - > - Learn how to install beats - - { - history.push('/beats/enroll/foobar'); - }} - > - Enroll Beats - - - {match.params.enrollmentToken != null && ( - - history.push('/beats')} style={{ width: '800px' }}> - - Enroll Beats - - - - Enrollment UI here for enrollment token of: {match.params.enrollmentToken} - - - - history.push('/beats')}>Cancel - - - - )} -
- ); + public static ActionArea = BeatsActionArea; constructor(props: BeatsPageProps) { super(props); @@ -85,13 +41,18 @@ export class BeatsPage extends React.PureComponent diff --git a/x-pack/plugins/beats_management/public/pages/main/beats_action_area.tsx b/x-pack/plugins/beats_management/public/pages/main/beats_action_area.tsx new file mode 100644 index 0000000000000..6cedb5bd363b9 --- /dev/null +++ b/x-pack/plugins/beats_management/public/pages/main/beats_action_area.tsx @@ -0,0 +1,162 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { + // @ts-ignore typings for EuiBasicTable not present in current version + EuiBasicTable, + EuiButton, + EuiButtonEmpty, + EuiLoadingSpinner, + EuiModal, + EuiModalBody, + EuiModalHeader, + EuiModalHeaderTitle, + EuiOverlayMask +} from '@elastic/eui'; +import React from 'react'; +import { CMBeat } from '../../../common/domain_types'; +import { FrontendLibs } from '../../lib/lib'; + +export class BeatsActionArea extends React.Component { + private pinging = false + constructor(props: any) { + super(props) + + this.state = { + enrolledBeat: null + } + } + public pingForBeatWithToken = async(libs: FrontendLibs,token: string): Promise => { + try { + const beats = await libs.beats.getBeatWithToken(token); + if(!beats) { throw new Error('no beats') } + return beats; + } catch(err) { + if(this.pinging) { + const timeout = (ms:number) => new Promise(res => setTimeout(res, ms)) + await timeout(5000) + return await this.pingForBeatWithToken(libs, token); + } + } + } + public async componentDidMount() { + if(this.props.match.params.enrollmentToken) { + this.waitForToken(this.props.match.params.enrollmentToken) + } + } + public waitForToken = async (token: string) => { + this.pinging = true; + const enrolledBeat = await this.pingForBeatWithToken(this.props.libs, token) as CMBeat; + this.setState({ + enrolledBeat + }) + this.pinging = false + } + public render() { + const { + match, + history, + libs, + } = this.props + return ( +
+ { + window.alert('This will later go to more general beats install instructions.'); + window.location.href = '#/home/tutorial/dockerMetrics'; + }} + > + Learn how to install beats + + { + const token = await libs.tokens.createEnrollmentToken(); + history.push(`/beats/enroll/${token}`); + this.waitForToken(token); + }} + > + Enroll Beats + + + {match.params.enrollmentToken != null && ( + + { + this.pinging = false; + this.setState({ + enrolledBeat: null + }, () => history.push('/beats')) + }} style={{ width: '640px' }}> + + Enroll a new Beat + + {!this.state.enrolledBeat && ( + + To enroll a Beat with Centeral Management, run this command on the host that has Beats + installed. +
+
+
+
+
+ $ beats enroll {window.location.protocol}//{window.location.host} {match.params.enrollmentToken} +
+
+
+
+ +
+
+ Waiting for enroll command to be run... + +
+ )} + {this.state.enrolledBeat && ( + + A Beat was enrolled with the following data: +
+
+
+ +
+
+ { + this.setState({ + enrolledBeat: null + }) + const token = await libs.tokens.createEnrollmentToken(); + history.push(`/beats/enroll/${token}`); + this.waitForToken(token); + }} + > + Enroll Another Beat + +
+ )} + +
+
+ )} +
+)}} \ No newline at end of file diff --git a/x-pack/plugins/beats_management/public/pages/main/index.tsx b/x-pack/plugins/beats_management/public/pages/main/index.tsx index f18e5e05b5747..49c4f757079cd 100644 --- a/x-pack/plugins/beats_management/public/pages/main/index.tsx +++ b/x-pack/plugins/beats_management/public/pages/main/index.tsx @@ -38,18 +38,6 @@ export class MainPages extends React.PureComponent { - if (this.state.enrollBeat) { - return this.setState({ - enrollBeat: null, - }); - } - - // TODO: create a real enromment token - return this.setState({ - enrollBeat: { enrollmentToken: '5g3i4ug5uy34g' }, - }); - }; public onSelectedTabChanged = (id: string) => { this.props.history.push(id); @@ -89,7 +77,10 @@ export class MainPages extends React.PureComponent - + } + /> } > diff --git a/x-pack/plugins/beats_management/readme.md b/x-pack/plugins/beats_management/readme.md index fdd56a393e573..4fca7e635fcfc 100644 --- a/x-pack/plugins/beats_management/readme.md +++ b/x-pack/plugins/beats_management/readme.md @@ -1,7 +1,16 @@ # Documentation for Beats CM in x-pack kibana +Notes: +Falure to have auth enabled in Kibana will make for a broken UI. UI based errors not yet in place + ### Run tests ``` node scripts/jest.js plugins/beats --watch ``` + +### Run command to fake an enrolling beat (from beats_management dir) + +``` +node scripts/enroll.js +``` diff --git a/x-pack/plugins/beats_management/scripts/enroll.js b/x-pack/plugins/beats_management/scripts/enroll.js new file mode 100644 index 0000000000000..996ece4136605 --- /dev/null +++ b/x-pack/plugins/beats_management/scripts/enroll.js @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +const request = require('request'); +const Chance = require('chance'); // eslint-disable-line +const args = process.argv.slice(2); +const chance = new Chance(); + +const enroll = async token => { + const beatId = chance.word(); + + await request( + { + url: `http://localhost:5601/api/beats/agent/${beatId}`, + method: 'POST', + headers: { + 'kbn-xsrf': 'xxx', + 'kbn-beats-enrollment-token': token, + }, + body: JSON.stringify({ + type: 'filebeat', + host_name: `${chance.word()}.bar.com`, + version: '6.3.0', + }), + }, + (error, response, body) => { + console.log(error, body); + } + ); +}; + +enroll(args[0]); diff --git a/x-pack/plugins/beats_management/server/lib/adapters/beats/adapter_types.ts b/x-pack/plugins/beats_management/server/lib/adapters/beats/adapter_types.ts index 1f1f2fd13e3bc..aa12674bf25b7 100644 --- a/x-pack/plugins/beats_management/server/lib/adapters/beats/adapter_types.ts +++ b/x-pack/plugins/beats_management/server/lib/adapters/beats/adapter_types.ts @@ -10,9 +10,10 @@ import { FrameworkUser } from '../framework/adapter_types'; export interface CMBeatsAdapter { insert(beat: CMBeat): Promise; update(beat: CMBeat): Promise; - get(user: FrameworkUser, id: string): Promise; - getAll(user: FrameworkUser): Promise; - getWithIds(user: FrameworkUser, beatIds: string[]): Promise; + get(user: FrameworkUser, id: string): Promise; + getAll(user: FrameworkUser): Promise; + getWithIds(user: FrameworkUser, beatIds: string[]): Promise; + getBeatWithToken(user: FrameworkUser, enrollmentToken: string): Promise; removeTagsFromBeats( user: FrameworkUser, removals: BeatsTagAssignment[] diff --git a/x-pack/plugins/beats_management/server/lib/adapters/beats/elasticsearch_beats_adapter.ts b/x-pack/plugins/beats_management/server/lib/adapters/beats/elasticsearch_beats_adapter.ts index 1d00f95e3a4e0..dbe16067dc6f2 100644 --- a/x-pack/plugins/beats_management/server/lib/adapters/beats/elasticsearch_beats_adapter.ts +++ b/x-pack/plugins/beats_management/server/lib/adapters/beats/elasticsearch_beats_adapter.ts @@ -34,7 +34,7 @@ export class ElasticsearchBeatsAdapter implements CMBeatsAdapter { return null; } - return _get(response, '_source.beat'); + return _get(response, '_source.beat'); } public async insert(beat: CMBeat) { @@ -86,6 +86,31 @@ export class ElasticsearchBeatsAdapter implements CMBeatsAdapter { .map((b: any) => b._source.beat); } + public async getBeatWithToken( + user: FrameworkUser, + enrollmentToken: string + ): Promise { + const params = { + ignore: [404], + index: INDEX_NAMES.BEATS, + type: '_doc', + body: { + query: { + match: { 'beat.enrollment_token': enrollmentToken }, + }, + }, + }; + + const response = await this.database.search(user, params); + + const beats = _get(response, 'hits.hits', []); + + if (beats.length === 0) { + return null; + } + return _get(beats[0], '_source.beat'); + } + public async getAll(user: FrameworkUser) { const params = { index: INDEX_NAMES.BEATS, diff --git a/x-pack/plugins/beats_management/server/lib/adapters/beats/memory_beats_adapter.ts b/x-pack/plugins/beats_management/server/lib/adapters/beats/memory_beats_adapter.ts index 1de1fe71e54ea..350d88ab6cada 100644 --- a/x-pack/plugins/beats_management/server/lib/adapters/beats/memory_beats_adapter.ts +++ b/x-pack/plugins/beats_management/server/lib/adapters/beats/memory_beats_adapter.ts @@ -38,8 +38,15 @@ export class MemoryBeatsAdapter implements CMBeatsAdapter { return this.beatsDB.filter(beat => beatIds.includes(beat.id)); } + public async getBeatWithToken( + user: FrameworkUser, + enrollmentToken: string + ): Promise { + return this.beatsDB.find(beat => enrollmentToken === beat.enrollment_token) || null; + } + public async getAll(user: FrameworkUser) { - return this.beatsDB.map((beat: any) => omit(beat, ['access_token'])); + return this.beatsDB.map((beat: any) => omit(beat, ['access_token'])); } public async removeTagsFromBeats( diff --git a/x-pack/plugins/beats_management/server/lib/domains/beats.ts b/x-pack/plugins/beats_management/server/lib/domains/beats.ts index f3c98b056d5e1..2d81b0eb05950 100644 --- a/x-pack/plugins/beats_management/server/lib/domains/beats.ts +++ b/x-pack/plugins/beats_management/server/lib/domains/beats.ts @@ -39,6 +39,10 @@ export class CMBeatsDomain { return await this.adapter.get(user, beatId); } + public async getByEnrollmentToken(user: FrameworkUser, enrollmentToken: string) { + return await this.adapter.getBeatWithToken(user, enrollmentToken); + } + public async update(beatId: string, accessToken: string, beatData: Partial) { const beat = await this.adapter.get(this.framework.internalUser, beatId); @@ -83,6 +87,7 @@ export class CMBeatsDomain { await this.adapter.insert({ ...beat, + enrollment_token: enrollmentToken, verified_on: verifiedOn, access_token: accessToken, host_ip: remoteAddress, diff --git a/x-pack/plugins/beats_management/server/management_server.ts b/x-pack/plugins/beats_management/server/management_server.ts index f7316ea0887ff..2cc4676da359e 100644 --- a/x-pack/plugins/beats_management/server/management_server.ts +++ b/x-pack/plugins/beats_management/server/management_server.ts @@ -7,10 +7,13 @@ import { CMServerLibs } from './lib/lib'; import { createGetBeatConfigurationRoute } from './rest_api/beats/configuration'; import { createBeatEnrollmentRoute } from './rest_api/beats/enroll'; +import { createGetBeatRoute } from './rest_api/beats/get'; import { createListAgentsRoute } from './rest_api/beats/list'; import { createTagAssignmentsRoute } from './rest_api/beats/tag_assignment'; import { createTagRemovalsRoute } from './rest_api/beats/tag_removal'; import { createBeatUpdateRoute } from './rest_api/beats/update'; +import { createGetTagsWithIdsRoute } from './rest_api/tags/get'; +import { createListTagsRoute } from './rest_api/tags/list'; import { createSetTagRoute } from './rest_api/tags/set'; import { createTokensRoute } from './rest_api/tokens/create'; import { beatsIndexTemplate } from './utils/index_templates'; @@ -23,6 +26,9 @@ export const initManagementServer = (libs: CMServerLibs) => { }); } + libs.framework.registerRoute(createGetBeatRoute(libs)); + libs.framework.registerRoute(createGetTagsWithIdsRoute(libs)); + libs.framework.registerRoute(createListTagsRoute(libs)); libs.framework.registerRoute(createGetBeatConfigurationRoute(libs)); libs.framework.registerRoute(createTagAssignmentsRoute(libs)); libs.framework.registerRoute(createListAgentsRoute(libs)); diff --git a/x-pack/plugins/beats_management/server/rest_api/beats/get.ts b/x-pack/plugins/beats_management/server/rest_api/beats/get.ts index e7ed5ef5c2cc8..fc0437bce8329 100644 --- a/x-pack/plugins/beats_management/server/rest_api/beats/get.ts +++ b/x-pack/plugins/beats_management/server/rest_api/beats/get.ts @@ -4,32 +4,36 @@ * you may not use this file except in compliance with the Elastic License. */ -import Joi from 'joi'; import { CMBeat } from '../../../common/domain_types'; import { CMServerLibs } from '../../lib/lib'; import { wrapEsError } from '../../utils/error_wrappers'; export const createGetBeatRoute = (libs: CMServerLibs) => ({ method: 'GET', - path: '/api/beats/agent/{beatId}', - config: { - validate: { - headers: Joi.object({ - 'kbn-beats-access-token': Joi.string().required(), - }).options({ allowUnknown: true }), - }, - }, + path: '/api/beats/agent/{beatId}/{token?}', + handler: async (request: any, reply: any) => { const beatId = request.params.beatId; - let beat: CMBeat; - try { - beat = await libs.beats.getById(request.user, beatId); - if (beat === null) { - return reply({ message: 'Beat not found' }).code(404); + let beat: CMBeat | null; + if (beatId === 'unknown') { + try { + beat = await libs.beats.getByEnrollmentToken(request.user, request.params.token); + if (beat === null) { + return reply().code(200); + } + } catch (err) { + return reply(wrapEsError(err)); + } + } else { + try { + beat = await libs.beats.getById(request.user, beatId); + if (beat === null) { + return reply({ message: 'Beat not found' }).code(404); + } + } catch (err) { + return reply(wrapEsError(err)); } - } catch (err) { - return reply(wrapEsError(err)); } delete beat.access_token; diff --git a/x-pack/plugins/beats_management/server/utils/index_templates/beats_template.json b/x-pack/plugins/beats_management/server/utils/index_templates/beats_template.json index 0d00abbc5d759..c77d8f9719680 100644 --- a/x-pack/plugins/beats_management/server/utils/index_templates/beats_template.json +++ b/x-pack/plugins/beats_management/server/utils/index_templates/beats_template.json @@ -30,6 +30,9 @@ "id": { "type": "keyword" }, + "color": { + "type": "keyword" + }, "configuration_blocks": { "type": "nested", "properties": { @@ -48,6 +51,9 @@ "id": { "type": "keyword" }, + "enrollment_token": { + "type": "keyword" + }, "access_token": { "type": "keyword" }, @@ -75,7 +81,7 @@ "tags": { "type": "keyword" }, - "central_configuration_yml": { + "orig_configuration_yml": { "type": "text" }, "metadata": { diff --git a/x-pack/test/api_integration/apis/beats/enroll_beat.js b/x-pack/test/api_integration/apis/beats/enroll_beat.js index 0234359b4f9ff..61c9ec79eb74b 100644 --- a/x-pack/test/api_integration/apis/beats/enroll_beat.js +++ b/x-pack/test/api_integration/apis/beats/enroll_beat.js @@ -52,7 +52,7 @@ export default function ({ getService }) { }); }); - it('should enroll beat in an unverified state', async () => { + it('should enroll beat in a verified state', async () => { await supertest .post(`/api/beats/agent/${beatId}`) .set('kbn-xsrf', 'xxx') From 264a56621174654491b4ade7c55c620ebf441cb4 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Mon, 6 Aug 2018 23:50:52 -0400 Subject: [PATCH 28/94] [Beats Management] Add Tags List (#21274) * Add BeatsTable and control bar components. * Clean yarn.lock. * Move raw numbers/strings to constants. Remove obsolete state/props. * Update/add tests. * Change prop name from "items" to "beats". * Add TagsTable component and associated search/action bar. * Rename some variables. * Add constant after forgetting to save file. * Fix design mistake in table component. * Disable delete button when no tags selected. * Export tags table from index.ts. * Move search bar filter definitions to table render. * Update table to support assignment options. * Update action control position. * Refactor split render function into custom components. * Add assignment options to Tags List. * Remove obsolete code. * Move tooltips for tag icons to top position. --- .../common/constants/table.ts | 1 + .../beats_management/common/domain_types.ts | 1 + .../public/components/table/controls.tsx | 3 +- .../public/components/table/index.ts | 2 +- .../public/components/table/table.tsx | 12 +- .../components/table/table_type_configs.tsx | 43 +++++- .../adapters/beats/memory_beats_adapter.ts | 12 +- .../public/pages/main/beats.tsx | 3 +- .../public/pages/main/tags.tsx | 132 +++++++++++++++++- 9 files changed, 196 insertions(+), 13 deletions(-) diff --git a/x-pack/plugins/beats_management/common/constants/table.ts b/x-pack/plugins/beats_management/common/constants/table.ts index 801a60082d1b8..e5c2f6a029d67 100644 --- a/x-pack/plugins/beats_management/common/constants/table.ts +++ b/x-pack/plugins/beats_management/common/constants/table.ts @@ -7,4 +7,5 @@ export const TABLE_CONFIG = { INITIAL_ROW_SIZE: 5, PAGE_SIZE_OPTIONS: [3, 5, 10, 20], + TRUNCATE_TAG_LENGTH: 33, }; diff --git a/x-pack/plugins/beats_management/common/domain_types.ts b/x-pack/plugins/beats_management/common/domain_types.ts index b943cc2cedc2b..a1750a25b4350 100644 --- a/x-pack/plugins/beats_management/common/domain_types.ts +++ b/x-pack/plugins/beats_management/common/domain_types.ts @@ -36,4 +36,5 @@ export interface BeatTag { id: string; configuration_blocks: ConfigurationBlock[]; color?: string; + last_updated: Date; } diff --git a/x-pack/plugins/beats_management/public/components/table/controls.tsx b/x-pack/plugins/beats_management/public/components/table/controls.tsx index 5182c10c71722..730c73a37d5e1 100644 --- a/x-pack/plugins/beats_management/public/components/table/controls.tsx +++ b/x-pack/plugins/beats_management/public/components/table/controls.tsx @@ -30,6 +30,7 @@ export function ControlBar(props: ControlBarProps) { selectionCount, showAssignmentOptions, } = props; + const filters = controlDefinitions.filters.length === 0 ? null : controlDefinitions.filters; return selectionCount !== 0 && showAssignmentOptions ? ( actionHandler('search', query)} /> ); diff --git a/x-pack/plugins/beats_management/public/components/table/index.ts b/x-pack/plugins/beats_management/public/components/table/index.ts index 0789cad5c3022..459002dd49b82 100644 --- a/x-pack/plugins/beats_management/public/components/table/index.ts +++ b/x-pack/plugins/beats_management/public/components/table/index.ts @@ -6,4 +6,4 @@ export { Table } from './table'; export { ControlBar } from './controls'; -export { BeatsTableType } from './table_type_configs'; +export { BeatsTableType, TagsTableType } from './table_type_configs'; diff --git a/x-pack/plugins/beats_management/public/components/table/table.tsx b/x-pack/plugins/beats_management/public/components/table/table.tsx index c66c290b2f5f9..dd84facd75704 100644 --- a/x-pack/plugins/beats_management/public/components/table/table.tsx +++ b/x-pack/plugins/beats_management/public/components/table/table.tsx @@ -20,6 +20,7 @@ interface BeatsTableProps { assignmentOptions: any[] | null; assignmentTitle: string | null; items: any[]; + showAssignmentOptions: boolean; type: TableType; actionHandler(action: string, payload?: any): void; } @@ -42,7 +43,14 @@ export class Table extends React.Component { } public render() { - const { actionHandler, assignmentOptions, assignmentTitle, items, type } = this.props; + const { + actionHandler, + assignmentOptions, + assignmentTitle, + items, + showAssignmentOptions, + type, + } = this.props; const pagination = { initialPageSize: TABLE_CONFIG.INITIAL_ROW_SIZE, @@ -64,7 +72,7 @@ export class Table extends React.Component { assignmentTitle={assignmentTitle} controlDefinitions={type.controlDefinitions(items)} selectionCount={this.state.selection.length} - showAssignmentOptions={true} + showAssignmentOptions={showAssignmentOptions} /> ( + + {tag.id.length > TABLE_CONFIG.TRUNCATE_TAG_LENGTH + ? `${tag.id.substring(0, TABLE_CONFIG.TRUNCATE_TAG_LENGTH)}...` + : tag.id} + + ), + sortable: true, + width: '45%', + }, + { + align: 'right', + field: 'configuration_blocks', + name: 'Configurations', + render: (configurationBlocks: ConfigurationBlock[]) => ( +
{configurationBlocks.length}
+ ), + sortable: false, + }, + { + align: 'right', + field: 'last_updated', + name: 'Last update', + render: (lastUpdate: Date) =>
{moment(lastUpdate).fromNow()}
, + sortable: true, + }, + ], + controlDefinitions: (data: any) => ({ + actions: [], + filters: [], + }), +}; diff --git a/x-pack/plugins/beats_management/public/lib/adapters/beats/memory_beats_adapter.ts b/x-pack/plugins/beats_management/public/lib/adapters/beats/memory_beats_adapter.ts index 565ed0932214f..af02c4edf26c3 100644 --- a/x-pack/plugins/beats_management/public/lib/adapters/beats/memory_beats_adapter.ts +++ b/x-pack/plugins/beats_management/public/lib/adapters/beats/memory_beats_adapter.ts @@ -35,11 +35,13 @@ export class MemoryBeatsAdapter implements CMBeatsAdapter { const beatIds = removals.map(r => r.beatId); const response = this.beatsDB.filter(beat => beatIds.includes(beat.id)).map(beat => { - const tagData = removals.find(r => r.beatId === beat.id); - if (tagData) { - if (beat.tags) { - beat.tags = beat.tags.filter(tag => tag !== tagData.tag); - } + const removalsForBeat = removals.filter(r => r.beatId === beat.id); + if (removalsForBeat.length) { + removalsForBeat.forEach((assignment: BeatsTagAssignment) => { + if (beat.tags) { + beat.tags = beat.tags.filter(tag => tag !== assignment.tag); + } + }); } return beat; }); diff --git a/x-pack/plugins/beats_management/public/pages/main/beats.tsx b/x-pack/plugins/beats_management/public/pages/main/beats.tsx index 24baf43ba2c83..5653a74e83130 100644 --- a/x-pack/plugins/beats_management/public/pages/main/beats.tsx +++ b/x-pack/plugins/beats_management/public/pages/main/beats.tsx @@ -24,8 +24,8 @@ interface BeatsPageProps { interface BeatsPageState { beats: CMBeat[]; - tags: any[] | null; tableRef: any; + tags: any[] | null; } export class BeatsPage extends React.PureComponent { @@ -54,6 +54,7 @@ export class BeatsPage extends React.PureComponent ); diff --git a/x-pack/plugins/beats_management/public/pages/main/tags.tsx b/x-pack/plugins/beats_management/public/pages/main/tags.tsx index 66dab3c1b3550..d7ed848fd10c3 100644 --- a/x-pack/plugins/beats_management/public/pages/main/tags.tsx +++ b/x-pack/plugins/beats_management/public/pages/main/tags.tsx @@ -4,10 +4,138 @@ * you may not use this file except in compliance with the Elastic License. */ +// @ts-ignore EuiToolTip has no typings in current version +import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiIcon, EuiToolTip } from '@elastic/eui'; import React from 'react'; +import { BeatTag, CMBeat } from '../../../common/domain_types'; +import { BeatsTagAssignment } from '../../../server/lib/adapters/beats/adapter_types'; +import { Table, TagsTableType } from '../../components/table'; +import { FrontendLibs } from '../../lib/lib'; + +interface TagsPageProps { + libs: FrontendLibs; +} + +interface TagsPageState { + beats: any; + tableRef: any; + tags: BeatTag[]; +} + +export class TagsPage extends React.PureComponent { + constructor(props: TagsPageProps) { + super(props); + + this.state = { + beats: [], + tableRef: React.createRef(), + tags: [], + }; + + this.loadTags(); + } -export class TagsPage extends React.PureComponent { public render() { - return
tags table and stuff
; + return ( +
+ ); } + + private handleTagsAction = (action: string, payload: any) => { + switch (action) { + case 'loadAssignmentOptions': + this.loadBeats(); + break; + } + + this.loadTags(); + }; + + private async loadTags() { + const tags = await this.props.libs.tags.getAll(); + this.setState({ + tags, + }); + } + + private async loadBeats() { + const beats = await this.props.libs.beats.getAll(); + const selectedTags = this.getSelectedTags(); + const renderedBeats = beats.map((beat: CMBeat) => { + const tagsToRemove: BeatTag[] = []; + const tagsToAdd: BeatTag[] = []; + const tags = beat.tags || []; + selectedTags.forEach((tag: BeatTag) => { + tags.some((tagId: string) => tagId === tag.id) + ? tagsToRemove.push(tag) + : tagsToAdd.push(tag); + }); + + const tagIcons = tags.map((tagId: string) => { + const associatedTag = this.state.tags.find(tag => tag.id === tagId); + return ( + + + + ); + }); + + return ( + + + {tagIcons.map(icon => ( + + {icon} + + ))} + + { + this.assignTagsToBeats(beat, tagsToAdd); + this.removeTagsFromBeats(beat, tagsToRemove); + this.loadBeats(); + }} + > + {beat.id} + + + + + ); + }); + + this.setState({ + beats: renderedBeats, + }); + } + + private createBeatTagAssignments = (beat: CMBeat, tags: BeatTag[]): BeatsTagAssignment[] => + tags.map(({ id }) => ({ tag: id, beatId: beat.id })); + + private removeTagsFromBeats = async (beat: CMBeat, tags: BeatTag[]) => { + const assignments = this.createBeatTagAssignments(beat, tags); + await this.props.libs.beats.removeTagsFromBeats(assignments); + }; + + private assignTagsToBeats = async (beat: CMBeat, tags: BeatTag[]) => { + const assignments = this.createBeatTagAssignments(beat, tags); + await this.props.libs.beats.assignTagsToBeats(assignments); + }; + + private getSelectedTags = () => { + return this.state.tableRef.current.state.selection; + }; } From bb7d0e770d6b1217acba63dc60aa5ebbee6ee6e7 Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Tue, 7 Aug 2018 21:46:07 -0400 Subject: [PATCH 29/94] Beats/update (#21702) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [ML] Fixing issue with historical job audit messages (#21718) * Add proper aria-label for close inspector (#21719) * [Beats Management] Initial scaffolding for plugin (#18977) * Initial scaffolding for Beats plugin * Removing bits not (yet) necessary in initial scaffolding * [Beats Management] Install Beats index template on plugin init (#19072) * Install Beats index template on plugin init * Adding missing files * [Beats Management] APIs: Create enrollment tokens (#19018) * WIP checkin * Register API routes * Fixing typo in index name * Adding TODOs * Removing commented out license checking code that isn't yet implemented * Remove unnecessary async/await * Don't return until indices have been refreshed * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Adding TODO * Fixing variable name * Using a single index * Adding expiration date field * Adding test for expiration date field * Ignore non-existent index * Fixing logic in test * Creating constant for default enrollment tokens TTL value * Updating test * Fixing name of test file (#19100) * [Beats Management] APIs: Enroll beat (#19056) * WIP checkin * Add API integration test * Converting to Jest test * Create API for enrolling a beat * Handle invalid or expired enrollment tokens * Use create instead of index to prevent same beat from being enrolled twice * Adding unit test for duplicate beat enrollment * Do not persist enrollment token with beat once token has been checked and used * Fix datatype of host_ip field * Make Kibana API guess host IP instead of requiring it in payload * Fixing error introduced in rebase conflict resolution * [Beats Management] APIs: List beats (#19086) * WIP checkin * Add API integration test * Converting to Jest test * WIP checkin * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Updating mapping * [Beats Management] APIs: Verify beats (#19103) * WIP checkin * WIP checkin * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Creating POST /api/beats/agents/verify API * Refactoring: extracting out helper functions * Fleshing out remaining tests * Expanding TODO note so I won't forget :) * Fixing file name * Updating mapping * Moving TODO comment to right file * Rename determine* helper functions to find* * Fixing assertions (#19194) * [Beats Management] APIs: Update beat (#19148) * WIP checkin * WIP checkin * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Creating POST /api/beats/agents/verify API * Refactoring: extracting out helper functions * Expanding TODO note so I won't forget :) * Fixing file name * Updating mapping * Add API tests * Update template to allow version field for beat * Implement PUT /api/beats/agent/{beat ID} API * Make enroll beat code consistent with update beat code * Fixing minor typo in TODO comment * Allow version in request payload * Make sure beat is not updated in ES in error scenarios * Adding version as required field in Enroll Beat API payload * Using destructuring * Fixing rename that was accidentally reversed in conflict fixing * [Beats Management] APIs: take auth tokens via headers (#19210) * WIP checkin * WIP checkin * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Creating POST /api/beats/agents/verify API * Refactoring: extracting out helper functions * Expanding TODO note so I won't forget :) * Fixing file name * Updating mapping * Fixing minor typo in TODO comment * Make "Enroll Beat" API take enrollment token via header instead of request body * Make "Update Beat" API take access token via header instead of request body * [Beats Management] APIs: Create configuration block (#19270) * WIP checkin * WIP checkin * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Creating POST /api/beats/agents/verify API * Refactoring: extracting out helper functions * Expanding TODO note so I won't forget :) * Fixing file name * Updating mapping * Fixing minor typo in TODO comment * Implementing POST /api/beats/configuration_blocks API * Removing unnecessary escaping * Fleshing out types + adding validation for them * Making output singular (was outputs) * Removing metricbeat.inputs * Revert implementation of `POST /api/beats/configuration_blocks` API (#19340) This API allowed the user to operate at a level of abstraction that is unnecessarily and dangerously too low. A better API would be at one level higher, where users can create, update, and delete tags (where a tag can contain multiple configuration blocks). * [Beats Management] APIs: Create or update tag (#19342) * Updating mappings * Implementing PUT /api/beats/tag/{tag} API * [Beats Management] Prevent timing attacks when checking auth tokens (#19363) * Using crypto.timingSafeEqual() for comparing auth tokens * Prevent subtler timing attack in token comparison function * Introduce random delay after we try to find token in ES to mitigate timing attack * Remove random delay * [Beats Management] APIs: Assign tag(s) to beat(s) (#19431) * Using crypto.timingSafeEqual() for comparing auth tokens * Introduce random delay after we try to find token in ES to mitigate timing attack * Rename "determine" to "find" * Remove random delay * Starting to implement POST /api/beats/beats_tags API * Changing API * Updating tests for changes to API * Updating ES archive * Renaming * Use destructuring * Moving start of script to own line to increase readability * Using destructuring * [Beats Management] APIs: Remove tag(s) from beat(s) (#19440) * Using crypto.timingSafeEqual() for comparing auth tokens * Introduce random delay after we try to find token in ES to mitigate timing attack * Remove random delay * Starting to implement POST /api/beats/beats_tags API * Changing API * Updating tests for changes to API * Renaming * Use destructuring * Using crypto.timingSafeEqual() for comparing auth tokens * Introduce random delay after we try to find token in ES to mitigate timing attack * Implementing `POST /api/beats/agents_tags/removals` API * Updating ES archive * Use destructuring * Moving start of script to own line to increase readability * Nothing to remove if there are no existing tags! * Updating tests to match changes in bulk update painless script * Use destructuring * [Beats Management] Move to Ingest UI arch and initial TS effort (#20039) * [Beats Management] Initial scaffolding for plugin (#18977) * Initial scaffolding for Beats plugin * Removing bits not (yet) necessary in initial scaffolding * [Beats Management] Install Beats index template on plugin init (#19072) * Install Beats index template on plugin init * Adding missing files * [Beats Management] APIs: Create enrollment tokens (#19018) * WIP checkin * Register API routes * Fixing typo in index name * Adding TODOs * Removing commented out license checking code that isn't yet implemented * Remove unnecessary async/await * Don't return until indices have been refreshed * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Adding TODO * Fixing variable name * Using a single index * Adding expiration date field * Adding test for expiration date field * Ignore non-existent index * Fixing logic in test * Creating constant for default enrollment tokens TTL value * Updating test * Fixing name of test file (#19100) * [Beats Management] APIs: Enroll beat (#19056) * WIP checkin * Add API integration test * Converting to Jest test * Create API for enrolling a beat * Handle invalid or expired enrollment tokens * Use create instead of index to prevent same beat from being enrolled twice * Adding unit test for duplicate beat enrollment * Do not persist enrollment token with beat once token has been checked and used * Fix datatype of host_ip field * Make Kibana API guess host IP instead of requiring it in payload * Fixing error introduced in rebase conflict resolution * [Beats Management] APIs: List beats (#19086) * WIP checkin * Add API integration test * Converting to Jest test * WIP checkin * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Updating mapping * [Beats Management] APIs: Verify beats (#19103) * WIP checkin * WIP checkin * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Creating POST /api/beats/agents/verify API * Refactoring: extracting out helper functions * Fleshing out remaining tests * Expanding TODO note so I won't forget :) * Fixing file name * Updating mapping * Moving TODO comment to right file * Rename determine* helper functions to find* * Fixing assertions (#19194) * [Beats Management] APIs: Update beat (#19148) * WIP checkin * WIP checkin * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Creating POST /api/beats/agents/verify API * Refactoring: extracting out helper functions * Expanding TODO note so I won't forget :) * Fixing file name * Updating mapping * Add API tests * Update template to allow version field for beat * Implement PUT /api/beats/agent/{beat ID} API * Make enroll beat code consistent with update beat code * Fixing minor typo in TODO comment * Allow version in request payload * Make sure beat is not updated in ES in error scenarios * Adding version as required field in Enroll Beat API payload * Using destructuring * Fixing rename that was accidentally reversed in conflict fixing * [Beats Management] APIs: take auth tokens via headers (#19210) * WIP checkin * WIP checkin * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Creating POST /api/beats/agents/verify API * Refactoring: extracting out helper functions * Expanding TODO note so I won't forget :) * Fixing file name * Updating mapping * Fixing minor typo in TODO comment * Make "Enroll Beat" API take enrollment token via header instead of request body * Make "Update Beat" API take access token via header instead of request body * [Beats Management] APIs: Create configuration block (#19270) * WIP checkin * WIP checkin * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Creating POST /api/beats/agents/verify API * Refactoring: extracting out helper functions * Expanding TODO note so I won't forget :) * Fixing file name * Updating mapping * Fixing minor typo in TODO comment * Implementing POST /api/beats/configuration_blocks API * Removing unnecessary escaping * Fleshing out types + adding validation for them * Making output singular (was outputs) * Removing metricbeat.inputs * Revert implementation of `POST /api/beats/configuration_blocks` API (#19340) This API allowed the user to operate at a level of abstraction that is unnecessarily and dangerously too low. A better API would be at one level higher, where users can create, update, and delete tags (where a tag can contain multiple configuration blocks). * [Beats Management] APIs: Create or update tag (#19342) * Updating mappings * Implementing PUT /api/beats/tag/{tag} API * [Beats Management] Prevent timing attacks when checking auth tokens (#19363) * Using crypto.timingSafeEqual() for comparing auth tokens * Prevent subtler timing attack in token comparison function * Introduce random delay after we try to find token in ES to mitigate timing attack * Remove random delay * [Beats Management] APIs: Assign tag(s) to beat(s) (#19431) * Using crypto.timingSafeEqual() for comparing auth tokens * Introduce random delay after we try to find token in ES to mitigate timing attack * Rename "determine" to "find" * Remove random delay * Starting to implement POST /api/beats/beats_tags API * Changing API * Updating tests for changes to API * Updating ES archive * Renaming * Use destructuring * Moving start of script to own line to increase readability * Using destructuring * [Beats Management] APIs: Remove tag(s) from beat(s) (#19440) * Using crypto.timingSafeEqual() for comparing auth tokens * Introduce random delay after we try to find token in ES to mitigate timing attack * Remove random delay * Starting to implement POST /api/beats/beats_tags API * Changing API * Updating tests for changes to API * Renaming * Use destructuring * Using crypto.timingSafeEqual() for comparing auth tokens * Introduce random delay after we try to find token in ES to mitigate timing attack * Implementing `POST /api/beats/agents_tags/removals` API * Updating ES archive * Use destructuring * Moving start of script to own line to increase readability * Nothing to remove if there are no existing tags! * Updating tests to match changes in bulk update painless script * Use destructuring * Ported over base types and arch structure * move management of installIndexTemplate into the framework adapter * ts-lint fix * tslint fixes * more ts tweaks * fix paths * added several working endpoints * add more routes and bug fixes * fix linting * fix type remove CRUFT * remove more cruft * remove more CRUFT * added comments, change plurality * add tsconfig file * add extends path * fixed typo * serveral PR review fixes * fixed lodash type version * “fix” types by applying a lot of any * [Beats Management] Move tokens to use JWT, add more complete test suite (#20317) * inital effort to move to JWT and added jest based tests on libs * assign beats tests all passing * token tests now pass * add more tests * all tests now green * fix broken test, this is beats CM not logstash 😊 * added readme * move enrollment token back to a hash * remove un-needed comment * alias lodash get to avoid confusion * isolated hash creation * [Beats Management] add more tests, update types, break out ES into it's own adapter (#20566) * inital effort to move to JWT and added jest based tests on libs * assign beats tests all passing * token tests now pass * add more tests * all tests now green * move enrollment token back to a hash * remove un-needed comment * alias lodash get to avoid confusion * isolated hash creation * Add initial efforts for backend framework adapter testing * move ES code to a DatabaseAdapter from BackendAdapter and add a TON of types for ES * re-typed * renamed types to match pattern * aditional renames * adapter tests should always just use adapterSetup(); * database now uses InternalRequest * corrected spelling of framework * fix typings * remove CRUFT * RequestOrInternal * Dont pass around request objects everywhere, just pass the user. Also, removed hapi types as they were not compatible * fix tests, add test, removed extra comment * fix auth * updated lock file * [Beats Management] add get beat endpoint (#20603) * [Beats Management] Move tokens to use JWT, add more complete test suite (#20317) * inital effort to move to JWT and added jest based tests on libs * assign beats tests all passing * token tests now pass * add more tests * all tests now green * fix broken test, this is beats CM not logstash 😊 * added readme * move enrollment token back to a hash * remove un-needed comment * alias lodash get to avoid confusion * isolated hash creation * inital effort to move to JWT and added jest based tests on libs * assign beats tests all passing * token tests now pass * add more tests * all tests now green * move enrollment token back to a hash * remove un-needed comment * alias lodash get to avoid confusion * isolated hash creation * Add initial efforts for backend framework adapter testing * move ES code to a DatabaseAdapter from BackendAdapter and add a TON of types for ES * re-typed * renamed types to match pattern * aditional renames * adapter tests should always just use adapterSetup(); * database now uses InternalRequest * corrected spelling of framework * fix typings * remove CRUFT * RequestOrInternal * Dont pass around request objects everywhere, just pass the user. Also, removed hapi types as they were not compatible * fix tests, add test, removed extra comment * Moved critical path code from route, to more easeley tested domain * fix auth * remove beat verification, added get beat endpoint to return configs * fix type * update createGetBeatConfigurationRoute URL * rename method * update to match PR #20566 * updated lock file * fix bad merge * update TSLinting * fix bad rebase * [Beats Management] [WIP] Create public resources for management plugin (#20864) * Init plugin public resources. * rename beats to beats_management * rendering react now * Beats/initial ui (#20994) * initial layout and main nav * modal UI and pattern for UI established * fix path * wire up in-memroy adapters * tweak adapters * add getAll method to tags adapter (#21287) * Beats/real adapters (#21481) * add initial real adapters, and nulled data where we need endpoints * UI adapters and needed endpoints added (though not tested) * prep for route tests and some cleanup * move files * [Beats Management] Add BeatsTable/Bulk Action Search Component (#21182) * Add BeatsTable and control bar components. * Clean yarn.lock. * Move raw numbers/strings to constants. Remove obsolete state/props. * Update/add tests. * Change prop name from "items" to "beats". * Rename some variables. * Move search bar filter definitions to table render. * Update table to support assignment options. * Update action control position. * Refactor split render function into custom components. * Beats/basic use cases (#21660) * tweak adapter responses / types. re-add enroll ui * routes enabled, enroll now pings the server * full enrollment path now working * improved pinging for beat enrollment * fix location of history call * reload beats list on beat enrollment completion * add update on client side, expand update on server to allow for partial data, and user auth * remove double beat lookup * fix tests * only return active beats * disenroll now working * fig getAll query * re-enrolling a beat will now work * fix types * fix types * update deps * update kibana API for version --- .../beats_management/common/domain_types.ts | 1 + .../public/components/table/controls.tsx | 1 + .../public/components/table/table.tsx | 1 + .../components/table/table_type_configs.tsx | 1 + .../lib/adapters/beats/adapter_types.ts | 1 + .../adapters/beats/memory_beats_adapter.ts | 17 ++++ .../lib/adapters/beats/rest_beats_adapter.ts | 5 ++ .../framework/kibana_framework_adapter.ts | 1 - .../rest_api/axios_rest_api_adapter.ts | 8 +- .../public/lib/compose/kibana.ts | 3 +- .../public/pages/main/beats.tsx | 16 ++-- .../public/pages/main/tags.tsx | 1 + .../lib/adapters/beats/adapter_types.ts | 4 +- .../beats/elasticsearch_beats_adapter.ts | 49 +++++------ .../adapters/beats/memory_beats_adapter.ts | 4 +- .../server/lib/compose/kibana.ts | 3 +- .../server/lib/compose/testing.ts | 1 + .../__tests__/beats/assign_tags.test.ts | 12 +++ .../domains/__tests__/beats/enroll.test.ts | 1 + .../__tests__/beats/remove_tags.test.ts | 14 ++- .../server/lib/domains/beats.ts | 42 +++++---- .../server/lib/domains/tags.ts | 1 + .../beats_management/server/lib/lib.ts | 5 +- .../server/rest_api/beats/list.ts | 3 +- .../server/rest_api/beats/update.ts | 23 +++-- .../utils/index_templates/beats_template.json | 3 + x-pack/yarn.lock | 87 ++++++++++++++++++- yarn.lock | 79 ++++++++++++++++- 28 files changed, 313 insertions(+), 74 deletions(-) diff --git a/x-pack/plugins/beats_management/common/domain_types.ts b/x-pack/plugins/beats_management/common/domain_types.ts index a1750a25b4350..42878c0b6bd34 100644 --- a/x-pack/plugins/beats_management/common/domain_types.ts +++ b/x-pack/plugins/beats_management/common/domain_types.ts @@ -13,6 +13,7 @@ export interface ConfigurationBlock { export interface CMBeat { id: string; enrollment_token: string; + active: boolean; access_token: string; verified_on?: string; type: string; diff --git a/x-pack/plugins/beats_management/public/components/table/controls.tsx b/x-pack/plugins/beats_management/public/components/table/controls.tsx index 730c73a37d5e1..f4d85d57d4cf8 100644 --- a/x-pack/plugins/beats_management/public/components/table/controls.tsx +++ b/x-pack/plugins/beats_management/public/components/table/controls.tsx @@ -30,6 +30,7 @@ export function ControlBar(props: ControlBarProps) { selectionCount, showAssignmentOptions, } = props; + const filters = controlDefinitions.filters.length === 0 ? null : controlDefinitions.filters; return selectionCount !== 0 && showAssignmentOptions ? ( { } public render() { + const { actionHandler, assignmentOptions, diff --git a/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx b/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx index 6f20e68e16b1b..643c7ce4b810f 100644 --- a/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx +++ b/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx @@ -9,6 +9,7 @@ import { EuiBadge, EuiFlexGroup, EuiIcon, EuiLink } from '@elastic/eui'; import { flatten, uniq } from 'lodash'; import moment from 'moment'; import React from 'react'; + import { TABLE_CONFIG } from '../../../common/constants'; import { BeatTag, CMPopulatedBeat, ConfigurationBlock } from '../../../common/domain_types'; diff --git a/x-pack/plugins/beats_management/public/lib/adapters/beats/adapter_types.ts b/x-pack/plugins/beats_management/public/lib/adapters/beats/adapter_types.ts index 38129f27ab38f..c47ea7a4c6350 100644 --- a/x-pack/plugins/beats_management/public/lib/adapters/beats/adapter_types.ts +++ b/x-pack/plugins/beats_management/public/lib/adapters/beats/adapter_types.ts @@ -8,6 +8,7 @@ import { CMBeat } from '../../../../common/domain_types'; export interface CMBeatsAdapter { get(id: string): Promise; + update(id: string, beatData: Partial): Promise; getAll(): Promise; removeTagsFromBeats(removals: BeatsTagAssignment[]): Promise; assignTagsToBeats(assignments: BeatsTagAssignment[]): Promise; diff --git a/x-pack/plugins/beats_management/public/lib/adapters/beats/memory_beats_adapter.ts b/x-pack/plugins/beats_management/public/lib/adapters/beats/memory_beats_adapter.ts index af02c4edf26c3..745a11ac65464 100644 --- a/x-pack/plugins/beats_management/public/lib/adapters/beats/memory_beats_adapter.ts +++ b/x-pack/plugins/beats_management/public/lib/adapters/beats/memory_beats_adapter.ts @@ -25,6 +25,17 @@ export class MemoryBeatsAdapter implements CMBeatsAdapter { return this.beatsDB.find(beat => beat.id === id) || null; } + public async update(id: string, beatData: Partial): Promise { + const index = this.beatsDB.findIndex(beat => beat.id === id); + + if (index === -1) { + return false; + } + + this.beatsDB[index] = { ...this.beatsDB[index], ...beatData }; + return true; + } + public async getAll() { return this.beatsDB.map((beat: any) => omit(beat, ['access_token'])); } @@ -35,6 +46,12 @@ export class MemoryBeatsAdapter implements CMBeatsAdapter { const beatIds = removals.map(r => r.beatId); const response = this.beatsDB.filter(beat => beatIds.includes(beat.id)).map(beat => { + const tagData = removals.find(r => r.beatId === beat.id); + if (tagData) { + if (beat.tags) { + beat.tags = beat.tags.filter(tag => tag !== tagData.tag); + } + } const removalsForBeat = removals.filter(r => r.beatId === beat.id); if (removalsForBeat.length) { removalsForBeat.forEach((assignment: BeatsTagAssignment) => { diff --git a/x-pack/plugins/beats_management/public/lib/adapters/beats/rest_beats_adapter.ts b/x-pack/plugins/beats_management/public/lib/adapters/beats/rest_beats_adapter.ts index aaf9d545dffee..30a052a182945 100644 --- a/x-pack/plugins/beats_management/public/lib/adapters/beats/rest_beats_adapter.ts +++ b/x-pack/plugins/beats_management/public/lib/adapters/beats/rest_beats_adapter.ts @@ -28,6 +28,11 @@ export class RestBeatsAdapter implements CMBeatsAdapter { return (await this.REST.get<{ beats: CMBeat[] }>('/api/beats/agents')).beats; } + public async update(id: string, beatData: Partial): Promise { + await this.REST.put<{ success: true }>(`/api/beats/agent/${id}`, beatData); + return true; + } + public async removeTagsFromBeats(removals: BeatsTagAssignment[]): Promise { return (await this.REST.post<{ removals: BeatsRemovalReturn[] }>( `/api/beats/agents_tags/removals`, diff --git a/x-pack/plugins/beats_management/public/lib/adapters/framework/kibana_framework_adapter.ts b/x-pack/plugins/beats_management/public/lib/adapters/framework/kibana_framework_adapter.ts index 1a61a5581ce4e..497f47a48d57d 100644 --- a/x-pack/plugins/beats_management/public/lib/adapters/framework/kibana_framework_adapter.ts +++ b/x-pack/plugins/beats_management/public/lib/adapters/framework/kibana_framework_adapter.ts @@ -17,7 +17,6 @@ import { export class KibanaFrameworkAdapter implements FrameworkAdapter { public appState: object; - public kbnVersion?: string; private management: any; private adapterService: KibanaAdapterServiceProvider; diff --git a/x-pack/plugins/beats_management/public/lib/adapters/rest_api/axios_rest_api_adapter.ts b/x-pack/plugins/beats_management/public/lib/adapters/rest_api/axios_rest_api_adapter.ts index b05f2b41e30b3..56bd9b63df686 100644 --- a/x-pack/plugins/beats_management/public/lib/adapters/rest_api/axios_rest_api_adapter.ts +++ b/x-pack/plugins/beats_management/public/lib/adapters/rest_api/axios_rest_api_adapter.ts @@ -9,11 +9,7 @@ import { RestAPIAdapter } from './adapter_types'; let globalAPI: AxiosInstance; export class AxiosRestAPIAdapter implements RestAPIAdapter { - constructor( - private readonly kbnVersion: string, - private readonly xsrfToken: string, - private readonly basePath: string - ) {} + constructor(private readonly xsrfToken: string, private readonly basePath: string) {} public async get(url: string): Promise { return await this.REST.get(url).then(resp => resp.data); @@ -48,7 +44,7 @@ export class AxiosRestAPIAdapter implements RestAPIAdapter { Accept: 'application/json', credentials: 'same-origin', 'Content-Type': 'application/json', - 'kbn-version': this.kbnVersion, + 'kbn-version': this.xsrfToken, 'kbn-xsrf': this.xsrfToken, }, }); diff --git a/x-pack/plugins/beats_management/public/lib/compose/kibana.ts b/x-pack/plugins/beats_management/public/lib/compose/kibana.ts index 9f8f183170fd1..ef395a54ba73b 100644 --- a/x-pack/plugins/beats_management/public/lib/compose/kibana.ts +++ b/x-pack/plugins/beats_management/public/lib/compose/kibana.ts @@ -22,8 +22,7 @@ import { RestTokensAdapter } from '../adapters/tokens/rest_tokens_adapter'; import { FrontendDomainLibs, FrontendLibs } from '../lib'; export function compose(): FrontendLibs { - const kbnVersion = (window as any).__KBN__.version; - const api = new AxiosRestAPIAdapter(kbnVersion, chrome.getXsrfToken(), chrome.getBasePath()); + const api = new AxiosRestAPIAdapter(chrome.getXsrfToken(), chrome.getBasePath()); const tags = new RestTagsAdapter(api); const tokens = new RestTokensAdapter(api); diff --git a/x-pack/plugins/beats_management/public/pages/main/beats.tsx b/x-pack/plugins/beats_management/public/pages/main/beats.tsx index 5653a74e83130..7854e3336f070 100644 --- a/x-pack/plugins/beats_management/public/pages/main/beats.tsx +++ b/x-pack/plugins/beats_management/public/pages/main/beats.tsx @@ -79,10 +79,16 @@ export class BeatsPage extends React.PureComponent { - // const selected = this.getSelectedBeats(); - // await this.props.libs.beats.delete(selected); + const selected = this.getSelectedBeats(); + for (const beat of selected) { + await this.props.libs.beats.update(beat.id, { active: false }); + } + // because the compile code above has a very minor race condition, we wait, + // the max race condition time is really 10ms but doing 100 to be safe + setTimeout(async () => { + await this.loadBeats(); + }, 100); }; private async loadBeats() { @@ -110,7 +116,7 @@ export class BeatsPage extends React.PureComponent this.removeTagsFromBeats(selectedBeats, tag) @@ -143,7 +149,7 @@ export class BeatsPage extends React.PureComponent { + private getSelectedBeats = (): CMPopulatedBeat[] => { return this.state.tableRef.current.state.selection; }; } diff --git a/x-pack/plugins/beats_management/public/pages/main/tags.tsx b/x-pack/plugins/beats_management/public/pages/main/tags.tsx index d7ed848fd10c3..9c8c0ac2347b1 100644 --- a/x-pack/plugins/beats_management/public/pages/main/tags.tsx +++ b/x-pack/plugins/beats_management/public/pages/main/tags.tsx @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ + // @ts-ignore EuiToolTip has no typings in current version import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiIcon, EuiToolTip } from '@elastic/eui'; import React from 'react'; diff --git a/x-pack/plugins/beats_management/server/lib/adapters/beats/adapter_types.ts b/x-pack/plugins/beats_management/server/lib/adapters/beats/adapter_types.ts index aa12674bf25b7..9fc2578ccf8da 100644 --- a/x-pack/plugins/beats_management/server/lib/adapters/beats/adapter_types.ts +++ b/x-pack/plugins/beats_management/server/lib/adapters/beats/adapter_types.ts @@ -8,8 +8,8 @@ import { CMBeat } from '../../../../common/domain_types'; import { FrameworkUser } from '../framework/adapter_types'; export interface CMBeatsAdapter { - insert(beat: CMBeat): Promise; - update(beat: CMBeat): Promise; + insert(user: FrameworkUser, beat: CMBeat): Promise; + update(user: FrameworkUser, beat: CMBeat): Promise; get(user: FrameworkUser, id: string): Promise; getAll(user: FrameworkUser): Promise; getWithIds(user: FrameworkUser, beatIds: string[]): Promise; diff --git a/x-pack/plugins/beats_management/server/lib/adapters/beats/elasticsearch_beats_adapter.ts b/x-pack/plugins/beats_management/server/lib/adapters/beats/elasticsearch_beats_adapter.ts index dbe16067dc6f2..22bba3661a752 100644 --- a/x-pack/plugins/beats_management/server/lib/adapters/beats/elasticsearch_beats_adapter.ts +++ b/x-pack/plugins/beats_management/server/lib/adapters/beats/elasticsearch_beats_adapter.ts @@ -8,17 +8,15 @@ import { flatten, get as _get, omit } from 'lodash'; import { INDEX_NAMES } from '../../../../common/constants'; import { CMBeat } from '../../../../common/domain_types'; import { DatabaseAdapter } from '../database/adapter_types'; -import { BackendFrameworkAdapter } from '../framework/adapter_types'; + import { FrameworkUser } from '../framework/adapter_types'; import { BeatsTagAssignment, CMBeatsAdapter } from './adapter_types'; export class ElasticsearchBeatsAdapter implements CMBeatsAdapter { private database: DatabaseAdapter; - private framework: BackendFrameworkAdapter; - constructor(database: DatabaseAdapter, framework: BackendFrameworkAdapter) { + constructor(database: DatabaseAdapter) { this.database = database; - this.framework = framework; } public async get(user: FrameworkUser, id: string) { @@ -37,13 +35,13 @@ export class ElasticsearchBeatsAdapter implements CMBeatsAdapter { return _get(response, '_source.beat'); } - public async insert(beat: CMBeat) { + public async insert(user: FrameworkUser, beat: CMBeat) { const body = { beat, type: 'beat', }; - await this.database.create(this.framework.internalUser, { + await this.database.index(user, { body, id: `beat:${beat.id}`, index: INDEX_NAMES.BEATS, @@ -52,7 +50,7 @@ export class ElasticsearchBeatsAdapter implements CMBeatsAdapter { }); } - public async update(beat: CMBeat) { + public async update(user: FrameworkUser, beat: CMBeat) { const body = { beat, type: 'beat', @@ -65,7 +63,7 @@ export class ElasticsearchBeatsAdapter implements CMBeatsAdapter { refresh: 'wait_for', type: '_doc', }; - await this.database.index(this.framework.internalUser, params); + await this.database.index(user, params); } public async getWithIds(user: FrameworkUser, beatIds: string[]) { @@ -115,11 +113,12 @@ export class ElasticsearchBeatsAdapter implements CMBeatsAdapter { const params = { index: INDEX_NAMES.BEATS, q: 'type:beat', + size: 10000, type: '_doc', }; const response = await this.database.search(user, params); - const beats = _get(response, 'hits.hits', []); + return beats.map((beat: any) => omit(beat._source.beat, ['access_token'])); } @@ -129,16 +128,15 @@ export class ElasticsearchBeatsAdapter implements CMBeatsAdapter { ): Promise { const body = flatten( removals.map(({ beatId, tag }) => { - const script = - '' + - 'def beat = ctx._source.beat; ' + - 'if (beat.tags != null) { ' + - ' beat.tags.removeAll([params.tag]); ' + - '}'; + const script = ` + def beat = ctx._source.beat; + if (beat.tags != null) { + beat.tags.removeAll([params.tag]); + }`; return [ { update: { _id: `beat:${beatId}` } }, - { script: { source: script, params: { tag } } }, + { script: { source: script.replace(' ', ''), params: { tag } } }, ]; }) ); @@ -162,19 +160,18 @@ export class ElasticsearchBeatsAdapter implements CMBeatsAdapter { ): Promise { const body = flatten( assignments.map(({ beatId, tag }) => { - const script = - '' + - 'def beat = ctx._source.beat; ' + - 'if (beat.tags == null) { ' + - ' beat.tags = []; ' + - '} ' + - 'if (!beat.tags.contains(params.tag)) { ' + - ' beat.tags.add(params.tag); ' + - '}'; + const script = ` + def beat = ctx._source.beat; + if (beat.tags == null) { + beat.tags = []; + } + if (!beat.tags.contains(params.tag)) { + beat.tags.add(params.tag); + }`; return [ { update: { _id: `beat:${beatId}` } }, - { script: { source: script, params: { tag } } }, + { script: { source: script.replace(' ', ''), params: { tag } } }, ]; }) ); diff --git a/x-pack/plugins/beats_management/server/lib/adapters/beats/memory_beats_adapter.ts b/x-pack/plugins/beats_management/server/lib/adapters/beats/memory_beats_adapter.ts index 350d88ab6cada..3ab38a0716455 100644 --- a/x-pack/plugins/beats_management/server/lib/adapters/beats/memory_beats_adapter.ts +++ b/x-pack/plugins/beats_management/server/lib/adapters/beats/memory_beats_adapter.ts @@ -21,11 +21,11 @@ export class MemoryBeatsAdapter implements CMBeatsAdapter { return this.beatsDB.find(beat => beat.id === id) || null; } - public async insert(beat: CMBeat) { + public async insert(user: FrameworkUser, beat: CMBeat) { this.beatsDB.push(beat); } - public async update(beat: CMBeat) { + public async update(user: FrameworkUser, beat: CMBeat) { const beatIndex = this.beatsDB.findIndex(b => b.id === beat.id); this.beatsDB[beatIndex] = { diff --git a/x-pack/plugins/beats_management/server/lib/compose/kibana.ts b/x-pack/plugins/beats_management/server/lib/compose/kibana.ts index 685171669a887..bc00278251610 100644 --- a/x-pack/plugins/beats_management/server/lib/compose/kibana.ts +++ b/x-pack/plugins/beats_management/server/lib/compose/kibana.ts @@ -25,9 +25,10 @@ export function compose(server: any): CMServerLibs { const tokens = new CMTokensDomain(new ElasticsearchTokensAdapter(database, framework), { framework, }); - const beats = new CMBeatsDomain(new ElasticsearchBeatsAdapter(database, framework), { + const beats = new CMBeatsDomain(new ElasticsearchBeatsAdapter(database), { tags, tokens, + framework, }); const domainLibs: CMDomainLibs = { diff --git a/x-pack/plugins/beats_management/server/lib/compose/testing.ts b/x-pack/plugins/beats_management/server/lib/compose/testing.ts index 25b6776b6908b..7928891443708 100644 --- a/x-pack/plugins/beats_management/server/lib/compose/testing.ts +++ b/x-pack/plugins/beats_management/server/lib/compose/testing.ts @@ -36,6 +36,7 @@ export function compose({ const beats = new CMBeatsDomain(new MemoryBeatsAdapter(beatsDB), { tags, tokens, + framework, }); const domainLibs: CMDomainLibs = { diff --git a/x-pack/plugins/beats_management/server/lib/domains/__tests__/beats/assign_tags.test.ts b/x-pack/plugins/beats_management/server/lib/domains/__tests__/beats/assign_tags.test.ts index 20f4a7d36e4f0..48b1f63335ec7 100644 --- a/x-pack/plugins/beats_management/server/lib/domains/__tests__/beats/assign_tags.test.ts +++ b/x-pack/plugins/beats_management/server/lib/domains/__tests__/beats/assign_tags.test.ts @@ -39,6 +39,8 @@ describe('Beats Domain Lib', () => { beatsDB = [ { access_token: '9a6c99ae0fd84b068819701169cd8a4b', + active: true, + enrollment_token: '23423423423', host_ip: '1.2.3.4', host_name: 'foo.bar.com', id: 'qux', @@ -46,6 +48,8 @@ describe('Beats Domain Lib', () => { }, { access_token: '188255eb560a4448b72656c5e99cae6f', + active: true, + enrollment_token: 'reertrte', host_ip: '22.33.11.44', host_name: 'baz.bar.com', id: 'baz', @@ -53,6 +57,8 @@ describe('Beats Domain Lib', () => { }, { access_token: '93c4a4dd08564c189a7ec4e4f046b975', + active: true, + enrollment_token: '23s423423423', host_ip: '1.2.3.4', host_name: 'foo.bar.com', id: 'foo', @@ -62,6 +68,8 @@ describe('Beats Domain Lib', () => { }, { access_token: '3c4a4dd08564c189a7ec4e4f046b9759', + enrollment_token: 'gdfsgdf', + active: true, host_ip: '11.22.33.44', host_name: 'foo.com', id: 'bar', @@ -72,14 +80,17 @@ describe('Beats Domain Lib', () => { { configuration_blocks: [], id: 'production', + last_updated: new Date(), }, { configuration_blocks: [], id: 'development', + last_updated: new Date(), }, { configuration_blocks: [], id: 'qa', + last_updated: new Date(), }, ]; const framework = new TestingBackendFrameworkAdapter(settings); @@ -93,6 +104,7 @@ describe('Beats Domain Lib', () => { beatsLib = new CMBeatsDomain(new MemoryBeatsAdapter(beatsDB), { tags: tagsLib, tokens: tokensLib, + framework, }); }); diff --git a/x-pack/plugins/beats_management/server/lib/domains/__tests__/beats/enroll.test.ts b/x-pack/plugins/beats_management/server/lib/domains/__tests__/beats/enroll.test.ts index c5bc0935dc6cc..5f8a0b683a5b5 100644 --- a/x-pack/plugins/beats_management/server/lib/domains/__tests__/beats/enroll.test.ts +++ b/x-pack/plugins/beats_management/server/lib/domains/__tests__/beats/enroll.test.ts @@ -81,6 +81,7 @@ describe('Beats Domain Lib', () => { beatsLib = new CMBeatsDomain(new MemoryBeatsAdapter(beatsDB), { tags: tagsLib, tokens: tokensLib, + framework, }); }); diff --git a/x-pack/plugins/beats_management/server/lib/domains/__tests__/beats/remove_tags.test.ts b/x-pack/plugins/beats_management/server/lib/domains/__tests__/beats/remove_tags.test.ts index f75334e917c2b..a35da72889111 100644 --- a/x-pack/plugins/beats_management/server/lib/domains/__tests__/beats/remove_tags.test.ts +++ b/x-pack/plugins/beats_management/server/lib/domains/__tests__/beats/remove_tags.test.ts @@ -5,9 +5,9 @@ */ import { BeatTag, CMBeat } from '../../../../../common/domain_types'; +import { FrameworkInternalUser } from '../../../adapters/framework/adapter_types'; import { compose } from '../../../compose/testing'; import { CMServerLibs } from '../../../lib'; -import { FrameworkInternalUser } from './../../../adapters/framework/adapter_types'; const internalUser: FrameworkInternalUser = { kind: 'internal' }; @@ -21,6 +21,8 @@ describe('Beats Domain Lib', () => { beatsDB = [ { access_token: '9a6c99ae0fd84b068819701169cd8a4b', + active: true, + enrollment_token: '123kuil;4', host_ip: '1.2.3.4', host_name: 'foo.bar.com', id: 'qux', @@ -28,6 +30,8 @@ describe('Beats Domain Lib', () => { }, { access_token: '188255eb560a4448b72656c5e99cae6f', + active: true, + enrollment_token: '12fghjyu34', host_ip: '22.33.11.44', host_name: 'baz.bar.com', id: 'baz', @@ -35,6 +39,8 @@ describe('Beats Domain Lib', () => { }, { access_token: '93c4a4dd08564c189a7ec4e4f046b975', + active: true, + enrollment_token: '12nfhgj34', host_ip: '1.2.3.4', host_name: 'foo.bar.com', id: 'foo', @@ -44,6 +50,9 @@ describe('Beats Domain Lib', () => { }, { access_token: '3c4a4dd08564c189a7ec4e4f046b9759', + active: true, + + enrollment_token: '123sfd4', host_ip: '11.22.33.44', host_name: 'foo.com', id: 'bar', @@ -54,14 +63,17 @@ describe('Beats Domain Lib', () => { { configuration_blocks: [], id: 'production', + last_updated: new Date(), }, { configuration_blocks: [], id: 'development', + last_updated: new Date(), }, { configuration_blocks: [], id: 'qa', + last_updated: new Date(), }, ]; diff --git a/x-pack/plugins/beats_management/server/lib/domains/beats.ts b/x-pack/plugins/beats_management/server/lib/domains/beats.ts index 2d81b0eb05950..199077d8570c3 100644 --- a/x-pack/plugins/beats_management/server/lib/domains/beats.ts +++ b/x-pack/plugins/beats_management/server/lib/domains/beats.ts @@ -14,7 +14,7 @@ import { FrameworkUser } from '../adapters/framework/adapter_types'; import { CMAssignmentReturn } from '../adapters/beats/adapter_types'; import { BeatsRemovalReturn } from '../adapters/beats/adapter_types'; -import { BeatEnrollmentStatus, CMDomainLibs, CMServerLibs } from '../lib'; +import { BeatEnrollmentStatus, CMDomainLibs, CMServerLibs, UserOrToken } from '../lib'; export class CMBeatsDomain { private tags: CMDomainLibs['tags']; @@ -35,32 +35,41 @@ export class CMBeatsDomain { this.framework = libs.framework; } - public async getById(user: FrameworkUser, beatId: string) { - return await this.adapter.get(user, beatId); + public async getById(user: FrameworkUser, beatId: string): Promise { + const beat = await this.adapter.get(user, beatId); + return beat && beat.active ? beat : null; + } + + public async getAll(user: FrameworkUser) { + return (await this.adapter.getAll(user)).filter((beat: CMBeat) => beat.active === true); } public async getByEnrollmentToken(user: FrameworkUser, enrollmentToken: string) { - return await this.adapter.getBeatWithToken(user, enrollmentToken); + const beat = await this.adapter.getBeatWithToken(user, enrollmentToken); + return beat && beat.active ? beat : null; } - public async update(beatId: string, accessToken: string, beatData: Partial) { + public async update(userOrToken: UserOrToken, beatId: string, beatData: Partial) { const beat = await this.adapter.get(this.framework.internalUser, beatId); - const { verified: isAccessTokenValid } = this.tokens.verifyToken( - beat ? beat.access_token : '', - accessToken - ); - // TODO make return type enum if (beat === null) { return 'beat-not-found'; } - if (!isAccessTokenValid) { - return 'invalid-access-token'; + if (typeof userOrToken === 'string') { + const { verified: isAccessTokenValid } = this.tokens.verifyToken( + beat ? beat.access_token : '', + userOrToken + ); + if (!isAccessTokenValid) { + return 'invalid-access-token'; + } } - await this.adapter.update({ + const user = typeof userOrToken === 'string' ? this.framework.internalUser : userOrToken; + + await this.adapter.update(user, { ...beat, ...beatData, }); @@ -85,8 +94,9 @@ export class CMBeatsDomain { const accessToken = this.tokens.generateAccessToken(); const verifiedOn = moment().toJSON(); - await this.adapter.insert({ + await this.adapter.insert(this.framework.internalUser, { ...beat, + active: true, enrollment_token: enrollmentToken, verified_on: verifiedOn, access_token: accessToken, @@ -141,10 +151,6 @@ export class CMBeatsDomain { return response; } - public async getAllBeats(user: FrameworkUser) { - return await this.adapter.getAll(user); - } - public async assignTagsToBeats( user: FrameworkUser, assignments: BeatsTagAssignment[] diff --git a/x-pack/plugins/beats_management/server/lib/domains/tags.ts b/x-pack/plugins/beats_management/server/lib/domains/tags.ts index 39bc2c147ea03..5f5e6747cc847 100644 --- a/x-pack/plugins/beats_management/server/lib/domains/tags.ts +++ b/x-pack/plugins/beats_management/server/lib/domains/tags.ts @@ -35,6 +35,7 @@ export class CMTagsDomain { const tag = { configuration_blocks: configs, id: tagId, + last_updated: new Date(), }; return { isValid: true, diff --git a/x-pack/plugins/beats_management/server/lib/lib.ts b/x-pack/plugins/beats_management/server/lib/lib.ts index 824c5722a4bd8..b8d51374741fe 100644 --- a/x-pack/plugins/beats_management/server/lib/lib.ts +++ b/x-pack/plugins/beats_management/server/lib/lib.ts @@ -5,11 +5,14 @@ */ import { DatabaseAdapter } from './adapters/database/adapter_types'; -import { BackendFrameworkAdapter } from './adapters/framework/adapter_types'; +import { BackendFrameworkAdapter, FrameworkUser } from './adapters/framework/adapter_types'; + import { CMBeatsDomain } from './domains/beats'; import { CMTagsDomain } from './domains/tags'; import { CMTokensDomain } from './domains/tokens'; +export type UserOrToken = FrameworkUser | string; + export interface CMDomainLibs { beats: CMBeatsDomain; tags: CMTagsDomain; diff --git a/x-pack/plugins/beats_management/server/rest_api/beats/list.ts b/x-pack/plugins/beats_management/server/rest_api/beats/list.ts index 72105fc2f5440..876f717dd8f4a 100644 --- a/x-pack/plugins/beats_management/server/rest_api/beats/list.ts +++ b/x-pack/plugins/beats_management/server/rest_api/beats/list.ts @@ -14,7 +14,8 @@ export const createListAgentsRoute = (libs: CMServerLibs) => ({ path: '/api/beats/agents', handler: async (request: FrameworkRequest, reply: any) => { try { - const beats = await libs.beats.getAllBeats(request.user); + const beats = await libs.beats.getAll(request.user); + reply({ beats }); } catch (err) { // TODO move this to kibana route thing in adapter diff --git a/x-pack/plugins/beats_management/server/rest_api/beats/update.ts b/x-pack/plugins/beats_management/server/rest_api/beats/update.ts index 9c8e7daf475f8..26a22fe1d11f9 100644 --- a/x-pack/plugins/beats_management/server/rest_api/beats/update.ts +++ b/x-pack/plugins/beats_management/server/rest_api/beats/update.ts @@ -15,7 +15,9 @@ export const createBeatUpdateRoute = (libs: CMServerLibs) => ({ method: 'PUT', path: '/api/beats/agent/{beatId}', config: { - auth: false, + auth: { + mode: 'optional', + }, validate: { headers: Joi.object({ 'kbn-beats-access-token': Joi.string(), @@ -26,34 +28,43 @@ export const createBeatUpdateRoute = (libs: CMServerLibs) => ({ beatId: Joi.string(), }), payload: Joi.object({ + active: Joi.bool(), ephemeral_id: Joi.string(), host_name: Joi.string(), local_configuration_yml: Joi.string(), metadata: Joi.object(), type: Joi.string(), version: Joi.string(), - }).required(), + }), }, }, handler: async (request: FrameworkRequest, reply: any) => { const { beatId } = request.params; const accessToken = request.headers['kbn-beats-access-token']; const remoteAddress = request.info.remoteAddress; + const userOrToken = accessToken || request.user; + + if (request.user.kind === 'unauthenticated' && request.payload.active !== undefined) { + return reply({ message: 'access-token is not a valid auth type to change beat status' }).code( + 401 + ); + } try { - const status = await libs.beats.update(beatId, accessToken, { + const status = await libs.beats.update(userOrToken, beatId, { ...request.payload, host_ip: remoteAddress, }); switch (status) { case 'beat-not-found': - return reply({ message: 'Beat not found' }).code(404); + return reply({ message: 'Beat not found', success: false }).code(404); case 'invalid-access-token': - return reply({ message: 'Invalid access token' }).code(401); + return reply({ message: 'Invalid access token', success: false }).code(401); } - reply().code(204); + reply({ success: true }).code(204); + } catch (err) { return reply(wrapEsError(err)); } diff --git a/x-pack/plugins/beats_management/server/utils/index_templates/beats_template.json b/x-pack/plugins/beats_management/server/utils/index_templates/beats_template.json index c77d8f9719680..0442c31fd7c2d 100644 --- a/x-pack/plugins/beats_management/server/utils/index_templates/beats_template.json +++ b/x-pack/plugins/beats_management/server/utils/index_templates/beats_template.json @@ -51,6 +51,9 @@ "id": { "type": "keyword" }, + "active": { + "type": "boolean" + }, "enrollment_token": { "type": "keyword" }, diff --git a/x-pack/yarn.lock b/x-pack/yarn.lock index deb9b57e3acc0..598b142d3ca93 100644 --- a/x-pack/yarn.lock +++ b/x-pack/yarn.lock @@ -128,10 +128,18 @@ version "4.3.10" resolved "https://registry.yarnpkg.com/@types/boom/-/boom-4.3.10.tgz#39dad8c0614c26b91ef016a57d7eee4ffe4f8a25" +"@types/chance@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@types/chance/-/chance-1.0.1.tgz#c10703020369602c40dd9428cc6e1437027116df" + "@types/delay@^2.0.1": version "2.0.1" resolved "https://registry.yarnpkg.com/@types/delay/-/delay-2.0.1.tgz#61bcf318a74b61e79d1658fbf054f984c90ef901" +"@types/elasticsearch@^5.0.24": + version "5.0.25" + resolved "https://registry.yarnpkg.com/@types/elasticsearch/-/elasticsearch-5.0.25.tgz#717255a52acd9fa3ba165072d43a242283b1c898" + "@types/events@*": version "1.2.0" resolved "https://registry.yarnpkg.com/@types/events/-/events-1.2.0.tgz#81a6731ce4df43619e5c8c945383b3e62a89ea86" @@ -166,6 +174,12 @@ version "10.6.2" resolved "https://registry.yarnpkg.com/@types/joi/-/joi-10.6.2.tgz#0e7d632fe918c337784e87b16c7cc0098876179a" +"@types/jsonwebtoken@^7.2.7": + version "7.2.8" + resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-7.2.8.tgz#8d199dab4ddb5bba3234f8311b804d2027af2b3a" + dependencies: + "@types/node" "*" + "@types/lodash@^3.10.0": version "3.10.2" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-3.10.2.tgz#c1fbda1562ef5603c8192fe1fe65b017849d5873" @@ -227,6 +241,10 @@ version "0.10.2" resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.10.2.tgz#bd1740c4ad51966609b058803ee6874577848b37" +"@types/sinon@^5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-5.0.1.tgz#a15b36ec42f1f53166617491feabd1734cb03e21" + "@types/url-join@^0.8.2": version "0.8.2" resolved "https://registry.yarnpkg.com/@types/url-join/-/url-join-0.8.2.tgz#1181ecbe1d97b7034e0ea1e35e62e86cc26b422d" @@ -1227,6 +1245,10 @@ buffer-crc32@~0.2.3: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" +buffer-equal-constant-time@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + buffer-equal@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-0.0.1.tgz#91bc74b11ea405bc916bc6aa908faafa5b4aac4b" @@ -2127,6 +2149,12 @@ ecc-jsbn@~0.1.1: dependencies: jsbn "~0.1.0" +ecdsa-sig-formatter@1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz#1c595000f04a8897dfb85000892a0f4c33af86c3" + dependencies: + safe-buffer "^5.0.1" + elasticsearch@^14.1.0: version "14.2.2" resolved "https://registry.yarnpkg.com/elasticsearch/-/elasticsearch-14.2.2.tgz#6bbb63b19b17fa97211b22eeacb0f91197f4d6b6" @@ -4453,6 +4481,20 @@ jsonpointer@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" +jsonwebtoken@^8.3.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.3.0.tgz#056c90eee9a65ed6e6c72ddb0a1d325109aaf643" + dependencies: + jws "^3.1.5" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.1.1" + jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -4470,6 +4512,21 @@ just-extend@^1.1.27: version "1.1.27" resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-1.1.27.tgz#ec6e79410ff914e472652abfa0e603c03d60e905" +jwa@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.1.6.tgz#87240e76c9808dbde18783cf2264ef4929ee50e6" + dependencies: + buffer-equal-constant-time "1.0.1" + ecdsa-sig-formatter "1.0.10" + safe-buffer "^5.0.1" + +jws@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.1.5.tgz#80d12d05b293d1e841e7cb8b4e69e561adcf834f" + dependencies: + jwa "^1.1.5" + safe-buffer "^5.0.1" + keymirror@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/keymirror/-/keymirror-0.1.1.tgz#918889ea13f8d0a42e7c557250eee713adc95c35" @@ -4721,6 +4778,10 @@ lodash.get@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + lodash.isarguments@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" @@ -4729,6 +4790,10 @@ lodash.isarray@^3.0.0: version "3.0.4" resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + lodash.isempty@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.isempty/-/lodash.isempty-4.4.0.tgz#6f86cbedd8be4ec987be9aaf33c9684db1b31e7e" @@ -4737,10 +4802,26 @@ lodash.isequal@^4.1.1, lodash.isequal@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + lodash.isobject@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/lodash.isobject/-/lodash.isobject-3.0.2.tgz#3c8fb8d5b5bf4bf90ae06e14f2a530a4ed935e1d" +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + lodash.istypedarray@^3.0.0: version "3.0.6" resolved "https://registry.yarnpkg.com/lodash.istypedarray/-/lodash.istypedarray-3.0.6.tgz#c9a477498607501d8e8494d283b87c39281cef62" @@ -4761,6 +4842,10 @@ lodash.mergewith@^4.6.0: version "4.6.1" resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz#639057e726c3afbdb3e7d42741caa8d6e4335927" +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + lodash.orderby@4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.orderby/-/lodash.orderby-4.6.0.tgz#e697f04ce5d78522f54d9338b32b81a3393e4eb3" @@ -5178,7 +5263,7 @@ ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" -ms@^2.0.0: +ms@^2.0.0, ms@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" diff --git a/yarn.lock b/yarn.lock index 35a8800d3483b..6f1c1a150582f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -309,6 +309,10 @@ version "2.0.1" resolved "https://registry.yarnpkg.com/@types/delay/-/delay-2.0.1.tgz#61bcf318a74b61e79d1658fbf054f984c90ef901" +"@types/elasticsearch@^5.0.24": + version "5.0.25" + resolved "https://registry.yarnpkg.com/@types/elasticsearch/-/elasticsearch-5.0.25.tgz#717255a52acd9fa3ba165072d43a242283b1c898" + "@types/enzyme@^3.1.12": version "3.1.12" resolved "https://registry.yarnpkg.com/@types/enzyme/-/enzyme-3.1.12.tgz#293bb07c1ef5932d37add3879e72e0f5bc614f3c" @@ -422,6 +426,12 @@ version "1.0.32" resolved "https://registry.yarnpkg.com/@types/json-stable-stringify/-/json-stable-stringify-1.0.32.tgz#121f6917c4389db3923640b2e68de5fa64dda88e" +"@types/jsonwebtoken@^7.2.7": + version "7.2.8" + resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-7.2.8.tgz#8d199dab4ddb5bba3234f8311b804d2027af2b3a" + dependencies: + "@types/node" "*" + "@types/listr@^0.13.0": version "0.13.0" resolved "https://registry.yarnpkg.com/@types/listr/-/listr-0.13.0.tgz#6250bc4a04123cafa24fc73d1b880653a6ae6721" @@ -2280,6 +2290,10 @@ buffer-crc32@~0.2.3: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" +buffer-equal-constant-time@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + buffer-equal@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-0.0.1.tgz#91bc74b11ea405bc916bc6aa908faafa5b4aac4b" @@ -4190,6 +4204,12 @@ ecc-jsbn@~0.1.1: dependencies: jsbn "~0.1.0" +ecdsa-sig-formatter@1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz#1c595000f04a8897dfb85000892a0f4c33af86c3" + dependencies: + safe-buffer "^5.0.1" + editions@^1.1.1, editions@^1.3.4: version "1.3.4" resolved "https://registry.yarnpkg.com/editions/-/editions-1.3.4.tgz#3662cb592347c3168eb8e498a0ff73271d67f50b" @@ -7807,6 +7827,20 @@ jsonpointer@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" +jsonwebtoken@^8.3.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.3.0.tgz#056c90eee9a65ed6e6c72ddb0a1d325109aaf643" + dependencies: + jws "^3.1.5" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.1.1" + jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -7855,6 +7889,21 @@ just-extend@^1.1.27: version "1.1.27" resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-1.1.27.tgz#ec6e79410ff914e472652abfa0e603c03d60e905" +jwa@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.1.6.tgz#87240e76c9808dbde18783cf2264ef4929ee50e6" + dependencies: + buffer-equal-constant-time "1.0.1" + ecdsa-sig-formatter "1.0.10" + safe-buffer "^5.0.1" + +jws@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.1.5.tgz#80d12d05b293d1e841e7cb8b4e69e561adcf834f" + dependencies: + jwa "^1.1.5" + safe-buffer "^5.0.1" + karma-chrome-launcher@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/karma-chrome-launcher/-/karma-chrome-launcher-2.1.1.tgz#216879c68ac04d8d5140e99619ba04b59afd46cf" @@ -8391,6 +8440,10 @@ lodash.get@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + lodash.isarguments@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" @@ -8399,6 +8452,10 @@ lodash.isarray@^3.0.0: version "3.0.4" resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + lodash.isempty@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.isempty/-/lodash.isempty-4.4.0.tgz#6f86cbedd8be4ec987be9aaf33c9684db1b31e7e" @@ -8407,10 +8464,26 @@ lodash.isequal@^4.0.0, lodash.isequal@^4.1.1, lodash.isequal@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + lodash.isobject@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/lodash.isobject/-/lodash.isobject-3.0.2.tgz#3c8fb8d5b5bf4bf90ae06e14f2a530a4ed935e1d" +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + lodash.istypedarray@^3.0.0: version "3.0.6" resolved "https://registry.yarnpkg.com/lodash.istypedarray/-/lodash.istypedarray-3.0.6.tgz#c9a477498607501d8e8494d283b87c39281cef62" @@ -8447,6 +8520,10 @@ lodash.mergewith@^4.6.0: version "4.6.1" resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz#639057e726c3afbdb3e7d42741caa8d6e4335927" +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + lodash.orderby@4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.orderby/-/lodash.orderby-4.6.0.tgz#e697f04ce5d78522f54d9338b32b81a3393e4eb3" @@ -9039,7 +9116,7 @@ ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" -ms@^2.0.0: +ms@^2.0.0, ms@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" From 13bf2bcd89a1738e99202e33d8dded862ccd3aaf Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Wed, 8 Aug 2018 11:08:53 -0400 Subject: [PATCH 30/94] [Beats CM] Manage Tags (#21776) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [ML] Fixing issue with historical job audit messages (#21718) * Add proper aria-label for close inspector (#21719) * [Beats Management] Initial scaffolding for plugin (#18977) * Initial scaffolding for Beats plugin * Removing bits not (yet) necessary in initial scaffolding * [Beats Management] Install Beats index template on plugin init (#19072) * Install Beats index template on plugin init * Adding missing files * [Beats Management] APIs: Create enrollment tokens (#19018) * WIP checkin * Register API routes * Fixing typo in index name * Adding TODOs * Removing commented out license checking code that isn't yet implemented * Remove unnecessary async/await * Don't return until indices have been refreshed * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Adding TODO * Fixing variable name * Using a single index * Adding expiration date field * Adding test for expiration date field * Ignore non-existent index * Fixing logic in test * Creating constant for default enrollment tokens TTL value * Updating test * Fixing name of test file (#19100) * [Beats Management] APIs: Enroll beat (#19056) * WIP checkin * Add API integration test * Converting to Jest test * Create API for enrolling a beat * Handle invalid or expired enrollment tokens * Use create instead of index to prevent same beat from being enrolled twice * Adding unit test for duplicate beat enrollment * Do not persist enrollment token with beat once token has been checked and used * Fix datatype of host_ip field * Make Kibana API guess host IP instead of requiring it in payload * Fixing error introduced in rebase conflict resolution * [Beats Management] APIs: List beats (#19086) * WIP checkin * Add API integration test * Converting to Jest test * WIP checkin * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Updating mapping * [Beats Management] APIs: Verify beats (#19103) * WIP checkin * WIP checkin * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Creating POST /api/beats/agents/verify API * Refactoring: extracting out helper functions * Fleshing out remaining tests * Expanding TODO note so I won't forget :) * Fixing file name * Updating mapping * Moving TODO comment to right file * Rename determine* helper functions to find* * Fixing assertions (#19194) * [Beats Management] APIs: Update beat (#19148) * WIP checkin * WIP checkin * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Creating POST /api/beats/agents/verify API * Refactoring: extracting out helper functions * Expanding TODO note so I won't forget :) * Fixing file name * Updating mapping * Add API tests * Update template to allow version field for beat * Implement PUT /api/beats/agent/{beat ID} API * Make enroll beat code consistent with update beat code * Fixing minor typo in TODO comment * Allow version in request payload * Make sure beat is not updated in ES in error scenarios * Adding version as required field in Enroll Beat API payload * Using destructuring * Fixing rename that was accidentally reversed in conflict fixing * [Beats Management] APIs: take auth tokens via headers (#19210) * WIP checkin * WIP checkin * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Creating POST /api/beats/agents/verify API * Refactoring: extracting out helper functions * Expanding TODO note so I won't forget :) * Fixing file name * Updating mapping * Fixing minor typo in TODO comment * Make "Enroll Beat" API take enrollment token via header instead of request body * Make "Update Beat" API take access token via header instead of request body * [Beats Management] APIs: Create configuration block (#19270) * WIP checkin * WIP checkin * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Creating POST /api/beats/agents/verify API * Refactoring: extracting out helper functions * Expanding TODO note so I won't forget :) * Fixing file name * Updating mapping * Fixing minor typo in TODO comment * Implementing POST /api/beats/configuration_blocks API * Removing unnecessary escaping * Fleshing out types + adding validation for them * Making output singular (was outputs) * Removing metricbeat.inputs * Revert implementation of `POST /api/beats/configuration_blocks` API (#19340) This API allowed the user to operate at a level of abstraction that is unnecessarily and dangerously too low. A better API would be at one level higher, where users can create, update, and delete tags (where a tag can contain multiple configuration blocks). * [Beats Management] APIs: Create or update tag (#19342) * Updating mappings * Implementing PUT /api/beats/tag/{tag} API * [Beats Management] Prevent timing attacks when checking auth tokens (#19363) * Using crypto.timingSafeEqual() for comparing auth tokens * Prevent subtler timing attack in token comparison function * Introduce random delay after we try to find token in ES to mitigate timing attack * Remove random delay * [Beats Management] APIs: Assign tag(s) to beat(s) (#19431) * Using crypto.timingSafeEqual() for comparing auth tokens * Introduce random delay after we try to find token in ES to mitigate timing attack * Rename "determine" to "find" * Remove random delay * Starting to implement POST /api/beats/beats_tags API * Changing API * Updating tests for changes to API * Updating ES archive * Renaming * Use destructuring * Moving start of script to own line to increase readability * Using destructuring * [Beats Management] APIs: Remove tag(s) from beat(s) (#19440) * Using crypto.timingSafeEqual() for comparing auth tokens * Introduce random delay after we try to find token in ES to mitigate timing attack * Remove random delay * Starting to implement POST /api/beats/beats_tags API * Changing API * Updating tests for changes to API * Renaming * Use destructuring * Using crypto.timingSafeEqual() for comparing auth tokens * Introduce random delay after we try to find token in ES to mitigate timing attack * Implementing `POST /api/beats/agents_tags/removals` API * Updating ES archive * Use destructuring * Moving start of script to own line to increase readability * Nothing to remove if there are no existing tags! * Updating tests to match changes in bulk update painless script * Use destructuring * [Beats Management] Move to Ingest UI arch and initial TS effort (#20039) * [Beats Management] Initial scaffolding for plugin (#18977) * Initial scaffolding for Beats plugin * Removing bits not (yet) necessary in initial scaffolding * [Beats Management] Install Beats index template on plugin init (#19072) * Install Beats index template on plugin init * Adding missing files * [Beats Management] APIs: Create enrollment tokens (#19018) * WIP checkin * Register API routes * Fixing typo in index name * Adding TODOs * Removing commented out license checking code that isn't yet implemented * Remove unnecessary async/await * Don't return until indices have been refreshed * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Adding TODO * Fixing variable name * Using a single index * Adding expiration date field * Adding test for expiration date field * Ignore non-existent index * Fixing logic in test * Creating constant for default enrollment tokens TTL value * Updating test * Fixing name of test file (#19100) * [Beats Management] APIs: Enroll beat (#19056) * WIP checkin * Add API integration test * Converting to Jest test * Create API for enrolling a beat * Handle invalid or expired enrollment tokens * Use create instead of index to prevent same beat from being enrolled twice * Adding unit test for duplicate beat enrollment * Do not persist enrollment token with beat once token has been checked and used * Fix datatype of host_ip field * Make Kibana API guess host IP instead of requiring it in payload * Fixing error introduced in rebase conflict resolution * [Beats Management] APIs: List beats (#19086) * WIP checkin * Add API integration test * Converting to Jest test * WIP checkin * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Updating mapping * [Beats Management] APIs: Verify beats (#19103) * WIP checkin * WIP checkin * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Creating POST /api/beats/agents/verify API * Refactoring: extracting out helper functions * Fleshing out remaining tests * Expanding TODO note so I won't forget :) * Fixing file name * Updating mapping * Moving TODO comment to right file * Rename determine* helper functions to find* * Fixing assertions (#19194) * [Beats Management] APIs: Update beat (#19148) * WIP checkin * WIP checkin * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Creating POST /api/beats/agents/verify API * Refactoring: extracting out helper functions * Expanding TODO note so I won't forget :) * Fixing file name * Updating mapping * Add API tests * Update template to allow version field for beat * Implement PUT /api/beats/agent/{beat ID} API * Make enroll beat code consistent with update beat code * Fixing minor typo in TODO comment * Allow version in request payload * Make sure beat is not updated in ES in error scenarios * Adding version as required field in Enroll Beat API payload * Using destructuring * Fixing rename that was accidentally reversed in conflict fixing * [Beats Management] APIs: take auth tokens via headers (#19210) * WIP checkin * WIP checkin * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Creating POST /api/beats/agents/verify API * Refactoring: extracting out helper functions * Expanding TODO note so I won't forget :) * Fixing file name * Updating mapping * Fixing minor typo in TODO comment * Make "Enroll Beat" API take enrollment token via header instead of request body * Make "Update Beat" API take access token via header instead of request body * [Beats Management] APIs: Create configuration block (#19270) * WIP checkin * WIP checkin * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Creating POST /api/beats/agents/verify API * Refactoring: extracting out helper functions * Expanding TODO note so I won't forget :) * Fixing file name * Updating mapping * Fixing minor typo in TODO comment * Implementing POST /api/beats/configuration_blocks API * Removing unnecessary escaping * Fleshing out types + adding validation for them * Making output singular (was outputs) * Removing metricbeat.inputs * Revert implementation of `POST /api/beats/configuration_blocks` API (#19340) This API allowed the user to operate at a level of abstraction that is unnecessarily and dangerously too low. A better API would be at one level higher, where users can create, update, and delete tags (where a tag can contain multiple configuration blocks). * [Beats Management] APIs: Create or update tag (#19342) * Updating mappings * Implementing PUT /api/beats/tag/{tag} API * [Beats Management] Prevent timing attacks when checking auth tokens (#19363) * Using crypto.timingSafeEqual() for comparing auth tokens * Prevent subtler timing attack in token comparison function * Introduce random delay after we try to find token in ES to mitigate timing attack * Remove random delay * [Beats Management] APIs: Assign tag(s) to beat(s) (#19431) * Using crypto.timingSafeEqual() for comparing auth tokens * Introduce random delay after we try to find token in ES to mitigate timing attack * Rename "determine" to "find" * Remove random delay * Starting to implement POST /api/beats/beats_tags API * Changing API * Updating tests for changes to API * Updating ES archive * Renaming * Use destructuring * Moving start of script to own line to increase readability * Using destructuring * [Beats Management] APIs: Remove tag(s) from beat(s) (#19440) * Using crypto.timingSafeEqual() for comparing auth tokens * Introduce random delay after we try to find token in ES to mitigate timing attack * Remove random delay * Starting to implement POST /api/beats/beats_tags API * Changing API * Updating tests for changes to API * Renaming * Use destructuring * Using crypto.timingSafeEqual() for comparing auth tokens * Introduce random delay after we try to find token in ES to mitigate timing attack * Implementing `POST /api/beats/agents_tags/removals` API * Updating ES archive * Use destructuring * Moving start of script to own line to increase readability * Nothing to remove if there are no existing tags! * Updating tests to match changes in bulk update painless script * Use destructuring * Ported over base types and arch structure * move management of installIndexTemplate into the framework adapter * ts-lint fix * tslint fixes * more ts tweaks * fix paths * added several working endpoints * add more routes and bug fixes * fix linting * fix type remove CRUFT * remove more cruft * remove more CRUFT * added comments, change plurality * add tsconfig file * add extends path * fixed typo * serveral PR review fixes * fixed lodash type version * “fix” types by applying a lot of any * [Beats Management] Move tokens to use JWT, add more complete test suite (#20317) * inital effort to move to JWT and added jest based tests on libs * assign beats tests all passing * token tests now pass * add more tests * all tests now green * fix broken test, this is beats CM not logstash 😊 * added readme * move enrollment token back to a hash * remove un-needed comment * alias lodash get to avoid confusion * isolated hash creation * [Beats Management] add more tests, update types, break out ES into it's own adapter (#20566) * inital effort to move to JWT and added jest based tests on libs * assign beats tests all passing * token tests now pass * add more tests * all tests now green * move enrollment token back to a hash * remove un-needed comment * alias lodash get to avoid confusion * isolated hash creation * Add initial efforts for backend framework adapter testing * move ES code to a DatabaseAdapter from BackendAdapter and add a TON of types for ES * re-typed * renamed types to match pattern * aditional renames * adapter tests should always just use adapterSetup(); * database now uses InternalRequest * corrected spelling of framework * fix typings * remove CRUFT * RequestOrInternal * Dont pass around request objects everywhere, just pass the user. Also, removed hapi types as they were not compatible * fix tests, add test, removed extra comment * fix auth * updated lock file * [Beats Management] add get beat endpoint (#20603) * [Beats Management] Move tokens to use JWT, add more complete test suite (#20317) * inital effort to move to JWT and added jest based tests on libs * assign beats tests all passing * token tests now pass * add more tests * all tests now green * fix broken test, this is beats CM not logstash 😊 * added readme * move enrollment token back to a hash * remove un-needed comment * alias lodash get to avoid confusion * isolated hash creation * inital effort to move to JWT and added jest based tests on libs * assign beats tests all passing * token tests now pass * add more tests * all tests now green * move enrollment token back to a hash * remove un-needed comment * alias lodash get to avoid confusion * isolated hash creation * Add initial efforts for backend framework adapter testing * move ES code to a DatabaseAdapter from BackendAdapter and add a TON of types for ES * re-typed * renamed types to match pattern * aditional renames * adapter tests should always just use adapterSetup(); * database now uses InternalRequest * corrected spelling of framework * fix typings * remove CRUFT * RequestOrInternal * Dont pass around request objects everywhere, just pass the user. Also, removed hapi types as they were not compatible * fix tests, add test, removed extra comment * Moved critical path code from route, to more easeley tested domain * fix auth * remove beat verification, added get beat endpoint to return configs * fix type * update createGetBeatConfigurationRoute URL * rename method * update to match PR #20566 * updated lock file * fix bad merge * update TSLinting * fix bad rebase * [Beats Management] [WIP] Create public resources for management plugin (#20864) * Init plugin public resources. * rename beats to beats_management * rendering react now * Beats/initial ui (#20994) * initial layout and main nav * modal UI and pattern for UI established * fix path * wire up in-memroy adapters * tweak adapters * add getAll method to tags adapter (#21287) * Beats/real adapters (#21481) * add initial real adapters, and nulled data where we need endpoints * UI adapters and needed endpoints added (though not tested) * prep for route tests and some cleanup * move files * [Beats Management] Add BeatsTable/Bulk Action Search Component (#21182) * Add BeatsTable and control bar components. * Clean yarn.lock. * Move raw numbers/strings to constants. Remove obsolete state/props. * Update/add tests. * Change prop name from "items" to "beats". * Rename some variables. * Move search bar filter definitions to table render. * Update table to support assignment options. * Update action control position. * Refactor split render function into custom components. * Beats/basic use cases (#21660) * tweak adapter responses / types. re-add enroll ui * routes enabled, enroll now pings the server * full enrollment path now working * improved pinging for beat enrollment * fix location of history call * reload beats list on beat enrollment completion * add update on client side, expand update on server to allow for partial data, and user auth * remove double beat lookup * fix tests * only return active beats * disenroll now working * fig getAll query * re-enrolling a beat will now work * fix types * Add create tags view. * fix types * update deps * update kibana API for version * Added component/config interface for editing/creating tags. Added separate pages for create/edit tags. * Fixup. --- .../public/components/tag/index.ts | 8 + .../public/components/tag/tag_configs.ts | 17 ++ .../public/components/tag/tag_edit.tsx | 232 ++++++++++++++++++ .../public/pages/main/create_tag.tsx | 40 +++ .../public/pages/main/edit_tag.tsx | 40 +++ .../public/pages/main/index.tsx | 22 ++ 6 files changed, 359 insertions(+) create mode 100644 x-pack/plugins/beats_management/public/components/tag/index.ts create mode 100644 x-pack/plugins/beats_management/public/components/tag/tag_configs.ts create mode 100644 x-pack/plugins/beats_management/public/components/tag/tag_edit.tsx create mode 100644 x-pack/plugins/beats_management/public/pages/main/create_tag.tsx create mode 100644 x-pack/plugins/beats_management/public/pages/main/edit_tag.tsx diff --git a/x-pack/plugins/beats_management/public/components/tag/index.ts b/x-pack/plugins/beats_management/public/components/tag/index.ts new file mode 100644 index 0000000000000..8447142e16a73 --- /dev/null +++ b/x-pack/plugins/beats_management/public/components/tag/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { TagCreateConfig, TagEditConfig, TagViewConfig } from './tag_configs'; +export { TagEdit } from './tag_edit'; diff --git a/x-pack/plugins/beats_management/public/components/tag/tag_configs.ts b/x-pack/plugins/beats_management/public/components/tag/tag_configs.ts new file mode 100644 index 0000000000000..08ad711e798de --- /dev/null +++ b/x-pack/plugins/beats_management/public/components/tag/tag_configs.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface TagViewConfig { + showAttachedBeats: boolean; +} + +export const TagCreateConfig: TagViewConfig = { + showAttachedBeats: false, +}; + +export const TagEditConfig: TagViewConfig = { + showAttachedBeats: true, +}; diff --git a/x-pack/plugins/beats_management/public/components/tag/tag_edit.tsx b/x-pack/plugins/beats_management/public/components/tag/tag_edit.tsx new file mode 100644 index 0000000000000..a85f46218685d --- /dev/null +++ b/x-pack/plugins/beats_management/public/components/tag/tag_edit.tsx @@ -0,0 +1,232 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + // @ts-ignore + EuiBadge, + EuiButton, + EuiButtonEmpty, + // @ts-ignore + EuiCodeEditor, + // @ts-ignore + EuiColorPicker, + EuiFieldText, + EuiFlexGroup, + EuiFlexItem, + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiFlyoutHeader, + // @ts-ignore + EuiForm, + EuiFormRow, + EuiPanel, + // @ts-ignore + EuiSearchBar, + EuiSpacer, + // @ts-ignore + EuiTabbedContent, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import 'brace/mode/yaml'; +import 'brace/theme/github'; +import React from 'react'; +import { ConfigurationBlock } from '../../../common/domain_types'; +import { Table } from '../table'; +import { BeatsTableType } from '../table'; +import { TagViewConfig } from '../tag'; + +interface TagEditProps { + items: any[]; + config: TagViewConfig; +} + +interface TagEditState { + color: string | null; + configurationBlocks: ConfigurationBlock[]; + showFlyout: boolean; + tableRef: any; + tagName: string | null; +} + +export class TagEdit extends React.PureComponent { + constructor(props: TagEditProps) { + super(props); + + this.state = { + color: '#DD0A73', + configurationBlocks: [], + showFlyout: false, + tableRef: React.createRef(), + tagName: null, + }; + } + + public render() { + const { + config: { showAttachedBeats }, + items, + } = this.props; + const { color, configurationBlocks, tagName } = this.state; + return ( +
+ +

Add a new tag

+
+ + + + + +

Define this tag

+
+ +

+ Tags will apply a set configuration to a group of beats. +
+ The tag type defines the options available. +

+
+
+ {tagName ? tagName : 'Tag name'} +
+
+ + + + + + + + + + +
+
+ + + + + +

Configurations

+
+ +

+ You can have multiple configurations applied to an individual tag. These + configurations can repeat or mix types as necessary. For example, you may utilize + three metricbeat configurations alongside one input and filebeat configuration. +

+
+
+ +
+ Add a new configuration +
+
+
+
+ + {showAttachedBeats && ( + + +

Attached Beats

+
+
{ + /* TODO: handle assignment/delete actions */ + }} + assignmentOptions={[]} + assignmentTitle={null} + items={items} + ref={this.state.tableRef} + showAssignmentOptions={false} + type={BeatsTableType} + /> + + )} + + + + + Save + + + + Cancel + + + {this.state.showFlyout && ( + this.setState({ showFlyout: false })}> + + +

Add Configuration

+
+
+ + + { + // TODO: handle search changes + }} + /> + + + { + // TODO: update field value + }} + placeholder="Description (optional)" + /> + + Add configuration options here, + }, + { + id: 'yaml_editor', + name: 'YAML Editor', + content: , + }, + ]} + /> + + + + + this.setState({ showFlyout: false })} + > + Close + + + + Save + + + +
+ )} + + ); + } + + private openConfigFlyout = () => { + this.setState({ + showFlyout: true, + }); + }; + private updateBadgeColor = (e: any) => this.setState({ color: e }); + private updateBadgeName = (e: any) => this.setState({ tagName: e.target.value }); +} diff --git a/x-pack/plugins/beats_management/public/pages/main/create_tag.tsx b/x-pack/plugins/beats_management/public/pages/main/create_tag.tsx new file mode 100644 index 0000000000000..27ba69e435eef --- /dev/null +++ b/x-pack/plugins/beats_management/public/pages/main/create_tag.tsx @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import 'brace/mode/yaml'; +import 'brace/theme/github'; +import React from 'react'; +import { ConfigurationBlock } from '../../../common/domain_types'; +import { TagCreateConfig, TagEdit } from '../../components/tag'; +import { FrontendLibs } from '../../lib/lib'; + +interface CreateTagPageProps { + libs: FrontendLibs; +} + +interface CreateTagPageState { + color: string | null; + configurationBlocks: ConfigurationBlock[]; + showFlyout: boolean; + tagName: string | null; +} + +export class CreateTagPage extends React.PureComponent { + constructor(props: CreateTagPageProps) { + super(props); + + this.state = { + color: '#DD0A73', + configurationBlocks: [], + showFlyout: false, + tagName: null, + }; + } + + public render() { + return ; + } +} diff --git a/x-pack/plugins/beats_management/public/pages/main/edit_tag.tsx b/x-pack/plugins/beats_management/public/pages/main/edit_tag.tsx new file mode 100644 index 0000000000000..dab4d82e36844 --- /dev/null +++ b/x-pack/plugins/beats_management/public/pages/main/edit_tag.tsx @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import 'brace/mode/yaml'; +import 'brace/theme/github'; +import React from 'react'; +import { ConfigurationBlock } from '../../../common/domain_types'; +import { TagEdit, TagEditConfig } from '../../components/tag'; +import { FrontendLibs } from '../../lib/lib'; + +interface EditTagPageProps { + libs: FrontendLibs; +} + +interface EditTagPageState { + color: string | null; + configurationBlocks: ConfigurationBlock[]; + showFlyout: boolean; + tagName: string | null; +} + +export class EditTagPage extends React.PureComponent { + constructor(props: EditTagPageProps) { + super(props); + + this.state = { + color: '#DD0A73', + configurationBlocks: [], + showFlyout: false, + tagName: null, + }; + } + + public render() { + return ; + } +} diff --git a/x-pack/plugins/beats_management/public/pages/main/index.tsx b/x-pack/plugins/beats_management/public/pages/main/index.tsx index 49c4f757079cd..7d201bde445be 100644 --- a/x-pack/plugins/beats_management/public/pages/main/index.tsx +++ b/x-pack/plugins/beats_management/public/pages/main/index.tsx @@ -16,6 +16,8 @@ import { PrimaryLayout } from '../../components/layouts/primary'; import { FrontendLibs } from '../../lib/lib'; import { ActivityPage } from './activity'; import { BeatsPage } from './beats'; +import { CreateTagPage } from './create_tag'; +import { EditTagPage } from './edit_tag'; import { TagsPage } from './tags'; interface MainPagesProps { @@ -60,6 +62,16 @@ export class MainPages extends React.PureComponent ( @@ -106,6 +118,16 @@ export class MainPages extends React.PureComponent } /> + } + /> + } + /> ); From cd8f2748ba42fe68bbd52e6b3cd11c6143ad1fd1 Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Wed, 15 Aug 2018 12:43:23 -0400 Subject: [PATCH 31/94] Beats/beat tags workflow (#21923) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Beats Management] Move to Ingest UI arch and initial TS effort (#20039) * [Beats Management] Initial scaffolding for plugin (#18977) * Initial scaffolding for Beats plugin * Removing bits not (yet) necessary in initial scaffolding * [Beats Management] Install Beats index template on plugin init (#19072) * Install Beats index template on plugin init * Adding missing files * [Beats Management] APIs: Create enrollment tokens (#19018) * WIP checkin * Register API routes * Fixing typo in index name * Adding TODOs * Removing commented out license checking code that isn't yet implemented * Remove unnecessary async/await * Don't return until indices have been refreshed * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Adding TODO * Fixing variable name * Using a single index * Adding expiration date field * Adding test for expiration date field * Ignore non-existent index * Fixing logic in test * Creating constant for default enrollment tokens TTL value * Updating test * Fixing name of test file (#19100) * [Beats Management] APIs: Enroll beat (#19056) * WIP checkin * Add API integration test * Converting to Jest test * Create API for enrolling a beat * Handle invalid or expired enrollment tokens * Use create instead of index to prevent same beat from being enrolled twice * Adding unit test for duplicate beat enrollment * Do not persist enrollment token with beat once token has been checked and used * Fix datatype of host_ip field * Make Kibana API guess host IP instead of requiring it in payload * Fixing error introduced in rebase conflict resolution * [Beats Management] APIs: List beats (#19086) * WIP checkin * Add API integration test * Converting to Jest test * WIP checkin * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Updating mapping * [Beats Management] APIs: Verify beats (#19103) * WIP checkin * WIP checkin * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Creating POST /api/beats/agents/verify API * Refactoring: extracting out helper functions * Fleshing out remaining tests * Expanding TODO note so I won't forget :) * Fixing file name * Updating mapping * Moving TODO comment to right file * Rename determine* helper functions to find* * Fixing assertions (#19194) * [Beats Management] APIs: Update beat (#19148) * WIP checkin * WIP checkin * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Creating POST /api/beats/agents/verify API * Refactoring: extracting out helper functions * Expanding TODO note so I won't forget :) * Fixing file name * Updating mapping * Add API tests * Update template to allow version field for beat * Implement PUT /api/beats/agent/{beat ID} API * Make enroll beat code consistent with update beat code * Fixing minor typo in TODO comment * Allow version in request payload * Make sure beat is not updated in ES in error scenarios * Adding version as required field in Enroll Beat API payload * Using destructuring * Fixing rename that was accidentally reversed in conflict fixing * [Beats Management] APIs: take auth tokens via headers (#19210) * WIP checkin * WIP checkin * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Creating POST /api/beats/agents/verify API * Refactoring: extracting out helper functions * Expanding TODO note so I won't forget :) * Fixing file name * Updating mapping * Fixing minor typo in TODO comment * Make "Enroll Beat" API take enrollment token via header instead of request body * Make "Update Beat" API take access token via header instead of request body * [Beats Management] APIs: Create configuration block (#19270) * WIP checkin * WIP checkin * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Creating POST /api/beats/agents/verify API * Refactoring: extracting out helper functions * Expanding TODO note so I won't forget :) * Fixing file name * Updating mapping * Fixing minor typo in TODO comment * Implementing POST /api/beats/configuration_blocks API * Removing unnecessary escaping * Fleshing out types + adding validation for them * Making output singular (was outputs) * Removing metricbeat.inputs * Revert implementation of `POST /api/beats/configuration_blocks` API (#19340) This API allowed the user to operate at a level of abstraction that is unnecessarily and dangerously too low. A better API would be at one level higher, where users can create, update, and delete tags (where a tag can contain multiple configuration blocks). * [Beats Management] APIs: Create or update tag (#19342) * Updating mappings * Implementing PUT /api/beats/tag/{tag} API * [Beats Management] Prevent timing attacks when checking auth tokens (#19363) * Using crypto.timingSafeEqual() for comparing auth tokens * Prevent subtler timing attack in token comparison function * Introduce random delay after we try to find token in ES to mitigate timing attack * Remove random delay * [Beats Management] APIs: Assign tag(s) to beat(s) (#19431) * Using crypto.timingSafeEqual() for comparing auth tokens * Introduce random delay after we try to find token in ES to mitigate timing attack * Rename "determine" to "find" * Remove random delay * Starting to implement POST /api/beats/beats_tags API * Changing API * Updating tests for changes to API * Updating ES archive * Renaming * Use destructuring * Moving start of script to own line to increase readability * Using destructuring * [Beats Management] APIs: Remove tag(s) from beat(s) (#19440) * Using crypto.timingSafeEqual() for comparing auth tokens * Introduce random delay after we try to find token in ES to mitigate timing attack * Remove random delay * Starting to implement POST /api/beats/beats_tags API * Changing API * Updating tests for changes to API * Renaming * Use destructuring * Using crypto.timingSafeEqual() for comparing auth tokens * Introduce random delay after we try to find token in ES to mitigate timing attack * Implementing `POST /api/beats/agents_tags/removals` API * Updating ES archive * Use destructuring * Moving start of script to own line to increase readability * Nothing to remove if there are no existing tags! * Updating tests to match changes in bulk update painless script * Use destructuring * Ported over base types and arch structure * move management of installIndexTemplate into the framework adapter * ts-lint fix * tslint fixes * more ts tweaks * fix paths * added several working endpoints * add more routes and bug fixes * fix linting * fix type remove CRUFT * remove more cruft * remove more CRUFT * added comments, change plurality * add tsconfig file * add extends path * fixed typo * serveral PR review fixes * fixed lodash type version * “fix” types by applying a lot of any * add details page, re-configure routes * move tag crud to new route stuff * update tag create/edit component api * tags creation now working * bunch of stuff I should have split up better… * fixed perf bug, selected items that are removed are no longer phantom selected * fix rendering of assignments * remove assign to beats, the UX was too poor --- x-pack/package.json | 2 + .../public/components/connected_link.tsx | 38 +++ .../public/components/layouts/primary.tsx | 66 +++-- .../public/components/table/action_button.tsx | 1 + .../components/table/assignment_options.tsx | 78 +++--- .../public/components/table/controls.tsx | 4 + .../public/components/table/table.tsx | 13 +- .../components/table/table_type_configs.tsx | 11 +- .../public/components/tag/index.ts | 1 - .../public/components/tag/tag_configs.ts | 17 -- .../public/components/tag/tag_edit.tsx | 63 ++--- .../public/lib/adapters/tags/adapter_types.ts | 1 + .../lib/adapters/tags/memory_tags_adapter.ts | 5 + .../lib/adapters/tags/rest_tags_adapter.ts | 17 +- .../public/lib/compose/kibana.ts | 6 +- .../public/lib/domains/beats.ts | 65 +++++ .../beats_management/public/lib/lib.ts | 4 +- .../public/pages/beat/index.tsx | 18 ++ .../public/pages/main/beats.tsx | 73 ++--- .../public/pages/main/beats_action_area.tsx | 6 +- .../public/pages/main/create_tag.tsx | 40 --- .../public/pages/main/index.tsx | 52 +--- .../public/pages/main/tags.tsx | 53 +++- .../public/pages/tag/create.tsx | 79 ++++++ .../pages/{main/edit_tag.tsx => tag/edit.tsx} | 28 +- .../public/pages/tag/index.tsx | 58 ++++ .../beats_management/public/router.tsx | 18 +- .../lib/adapters/beats/adapter_types.ts | 1 + .../beats/elasticsearch_beats_adapter.ts | 24 +- .../adapters/beats/memory_beats_adapter.ts | 6 +- .../server/lib/adapters/tags/adapter_types.ts | 1 + .../tags/elasticsearch_tags_adapter.ts | 66 ++++- .../lib/adapters/tags/memory_tags_adapter.ts | 4 + .../server/lib/domains/beats.ts | 5 + .../server/lib/domains/tags.ts | 21 +- .../server/management_server.ts | 2 + .../server/rest_api/beats/tag_removal.ts | 10 +- .../server/rest_api/tags/delete.ts | 26 ++ .../server/rest_api/tags/list.ts | 2 +- .../server/rest_api/tags/set.ts | 13 +- .../beats/elasticsearch_beats_adapter.ts | 218 +++++++++++++++ .../kibana/kibana_framework_adapter.ts | 82 ++++++ .../tags/elasticsearch_tags_adapter.ts | 57 ++++ .../tokens/elasticsearch_tokens_adapter.ts | 83 ++++++ .../server/utils/compose/kibana.ts | 45 +++ .../server/utils/domains/beats.ts | 259 ++++++++++++++++++ .../server/utils/domains/tags.ts | 90 ++++++ .../server/utils/domains/tokens.ts | 80 ++++++ .../utils/index_templates/beats_template.json | 3 + .../beats_management/server/utils/lib.ts | 212 ++++++++++++++ .../grokdebugger/common/constants/index.js | 9 - .../call_with_request_factory.js | 18 -- .../common/constants/configuration_blocks.ts | 15 + .../plugins/logstash/server/kibana.index.ts | 14 + .../logstash/server/management_server.ts | 30 ++ .../logstash/server/rest_api/beats/enroll.ts | 63 +++++ .../logstash/server/rest_api/beats/list.ts | 23 ++ .../server/rest_api/beats/tag_assignment.ts | 48 ++++ .../server/rest_api/beats/tag_removal.ts | 48 ++++ .../logstash/server/rest_api/beats/update.ts | 62 +++++ .../logstash/server/rest_api/beats/verify.ts | 73 +++++ .../logstash/server/rest_api/tags/set.ts | 57 ++++ .../logstash/server/rest_api/tokens/create.ts | 42 +++ .../plugins/logstash/server/utils/README.md | 1 + .../server/utils/find_non_existent_items.ts | 14 + .../utils/index_templates/index.ts} | 5 +- .../logstash/server/utils/polyfills.ts | 17 ++ .../logstash/server/utils/wrap_request.ts | 24 ++ x-pack/plugins/logstash/tsconfig.json | 3 + x-pack/plugins/logstash/wallaby.js | 27 ++ .../driver/screenshot_stitcher/index.test.ts | 2 +- .../apis/beats/remove_tags_from_beats.js | 98 +++---- x-pack/yarn.lock | 7 + 73 files changed, 2433 insertions(+), 394 deletions(-) create mode 100644 x-pack/plugins/beats_management/public/components/connected_link.tsx delete mode 100644 x-pack/plugins/beats_management/public/components/tag/tag_configs.ts create mode 100644 x-pack/plugins/beats_management/public/lib/domains/beats.ts create mode 100644 x-pack/plugins/beats_management/public/pages/beat/index.tsx delete mode 100644 x-pack/plugins/beats_management/public/pages/main/create_tag.tsx create mode 100644 x-pack/plugins/beats_management/public/pages/tag/create.tsx rename x-pack/plugins/beats_management/public/pages/{main/edit_tag.tsx => tag/edit.tsx} (58%) create mode 100644 x-pack/plugins/beats_management/public/pages/tag/index.tsx create mode 100644 x-pack/plugins/beats_management/server/rest_api/tags/delete.ts create mode 100644 x-pack/plugins/beats_management/server/utils/adapters/beats/elasticsearch_beats_adapter.ts create mode 100644 x-pack/plugins/beats_management/server/utils/adapters/famework/kibana/kibana_framework_adapter.ts create mode 100644 x-pack/plugins/beats_management/server/utils/adapters/tags/elasticsearch_tags_adapter.ts create mode 100644 x-pack/plugins/beats_management/server/utils/adapters/tokens/elasticsearch_tokens_adapter.ts create mode 100644 x-pack/plugins/beats_management/server/utils/compose/kibana.ts create mode 100644 x-pack/plugins/beats_management/server/utils/domains/beats.ts create mode 100644 x-pack/plugins/beats_management/server/utils/domains/tags.ts create mode 100644 x-pack/plugins/beats_management/server/utils/domains/tokens.ts create mode 100644 x-pack/plugins/beats_management/server/utils/lib.ts delete mode 100644 x-pack/plugins/grokdebugger/common/constants/index.js delete mode 100644 x-pack/plugins/index_management/server/lib/call_with_request_factory/call_with_request_factory.js create mode 100644 x-pack/plugins/logstash/common/constants/configuration_blocks.ts create mode 100644 x-pack/plugins/logstash/server/kibana.index.ts create mode 100644 x-pack/plugins/logstash/server/management_server.ts create mode 100644 x-pack/plugins/logstash/server/rest_api/beats/enroll.ts create mode 100644 x-pack/plugins/logstash/server/rest_api/beats/list.ts create mode 100644 x-pack/plugins/logstash/server/rest_api/beats/tag_assignment.ts create mode 100644 x-pack/plugins/logstash/server/rest_api/beats/tag_removal.ts create mode 100644 x-pack/plugins/logstash/server/rest_api/beats/update.ts create mode 100644 x-pack/plugins/logstash/server/rest_api/beats/verify.ts create mode 100644 x-pack/plugins/logstash/server/rest_api/tags/set.ts create mode 100644 x-pack/plugins/logstash/server/rest_api/tokens/create.ts create mode 100644 x-pack/plugins/logstash/server/utils/README.md create mode 100644 x-pack/plugins/logstash/server/utils/find_non_existent_items.ts rename x-pack/plugins/logstash/{common/constants/index_names.js => server/utils/index_templates/index.ts} (73%) create mode 100644 x-pack/plugins/logstash/server/utils/polyfills.ts create mode 100644 x-pack/plugins/logstash/server/utils/wrap_request.ts create mode 100644 x-pack/plugins/logstash/tsconfig.json create mode 100644 x-pack/plugins/logstash/wallaby.js diff --git a/x-pack/package.json b/x-pack/package.json index 01d5e7cdfedb3..d550731832737 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -30,10 +30,12 @@ "@types/boom": "^4.3.8", "@types/chance": "^1.0.1", "@types/history": "^4.6.2", + "@types/hapi": "15.0.1", "@types/jest": "^22.2.3", "@types/joi": "^10.4.0", "@types/lodash": "^3.10.0", "@types/pngjs": "^3.3.0", + "@types/react-router": "^4.0.30", "@types/react-router-dom": "^4.2.7", "@types/sinon": "^5.0.1", "abab": "^1.0.4", diff --git a/x-pack/plugins/beats_management/public/components/connected_link.tsx b/x-pack/plugins/beats_management/public/components/connected_link.tsx new file mode 100644 index 0000000000000..c4b26b0ad93af --- /dev/null +++ b/x-pack/plugins/beats_management/public/components/connected_link.tsx @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; + +import { EuiLink } from '@elastic/eui'; +import { Link, withRouter } from 'react-router-dom'; + +export function ConnectedLinkComponent({ + location, + path, + disabled, + ...props +}: { + location: any; + path: string; + disabled: boolean; + [key: string]: any; +}) { + if (disabled) { + return ; + } + + // Shorthand for pathname + const pathname = path || _.get(props.to, 'pathname') || location.pathname; + + return ( + + ); +} + +export const ConnectedLink = withRouter(ConnectedLinkComponent); diff --git a/x-pack/plugins/beats_management/public/components/layouts/primary.tsx b/x-pack/plugins/beats_management/public/components/layouts/primary.tsx index cd9e8076dd092..71a329c89f955 100644 --- a/x-pack/plugins/beats_management/public/components/layouts/primary.tsx +++ b/x-pack/plugins/beats_management/public/components/layouts/primary.tsx @@ -5,13 +5,24 @@ */ import React from 'react'; +import { withRouter } from 'react-router-dom'; import styled from 'styled-components'; -import { EuiPage, EuiPageBody, EuiPageContent, EuiPageContentBody, EuiTitle } from '@elastic/eui'; +import { + EuiModal, + EuiOverlayMask, + EuiPage, + EuiPageBody, + EuiPageContent, + EuiPageContentBody, + EuiTitle, +} from '@elastic/eui'; interface PrimaryLayoutProps { title: string; - actionSection: React.ReactNode; + actionSection?: React.ReactNode; + modalRender?: () => React.ReactNode; + modalClosePath?: string; } const HeaderContainer = styled.div` @@ -22,22 +33,35 @@ const HeaderContainer = styled.div` margin-bottom: 16px; `; -export const PrimaryLayout: React.SFC = ({ - actionSection, - title, - children, -}) => ( - - - - - -

{title}

-
- {actionSection} -
- {children} -
-
-
-); +export const PrimaryLayout: React.SFC = withRouter( + ({ actionSection, title, modalRender, modalClosePath, children, history }) => { + const modalContent = modalRender && modalRender(); + return ( + + + + + +

{title}

+
+ {actionSection} +
+ {children} +
+
+ {modalContent && ( + + { + history.push(modalClosePath); + }} + style={{ width: '640px' }} + > + {modalContent} + + + )} +
+ ); + } +) as any; diff --git a/x-pack/plugins/beats_management/public/components/table/action_button.tsx b/x-pack/plugins/beats_management/public/components/table/action_button.tsx index e91e620ed8d8c..1f9d2141cba6a 100644 --- a/x-pack/plugins/beats_management/public/components/table/action_button.tsx +++ b/x-pack/plugins/beats_management/public/components/table/action_button.tsx @@ -18,6 +18,7 @@ interface ActionButtonProps { export function ActionButton(props: ActionButtonProps) { const { actions, actionHandler, hidePopover, isPopoverVisible, showPopover } = props; + if (actions.length === 0) { return null; } else if (actions.length === 1) { diff --git a/x-pack/plugins/beats_management/public/components/table/assignment_options.tsx b/x-pack/plugins/beats_management/public/components/table/assignment_options.tsx index 60a6bf2b46952..89affe7ac2a72 100644 --- a/x-pack/plugins/beats_management/public/components/table/assignment_options.tsx +++ b/x-pack/plugins/beats_management/public/components/table/assignment_options.tsx @@ -12,6 +12,7 @@ import { ControlDefinitions } from './table_type_configs'; interface AssignmentOptionsProps { assignmentOptions: any[] | null; assignmentTitle: string | null; + renderAssignmentOptions?: (item: any) => any; controlDefinitions: ControlDefinitions; selectionCount: number; actionHandler(action: string, payload?: any): void; @@ -39,6 +40,7 @@ export class AssignmentOptions extends React.Component< const { actionHandler, assignmentOptions, + renderAssignmentOptions, assignmentTitle, controlDefinitions: { actions }, selectionCount, @@ -60,43 +62,45 @@ export class AssignmentOptions extends React.Component< }} /> - - { - this.setState({ - isAssignmentPopoverVisible: true, - }); - actionHandler('loadAssignmentOptions'); - }} - > - {assignmentTitle} - - } - closePopover={() => { - this.setState({ isAssignmentPopoverVisible: false }); - }} - id="assignmentList" - isOpen={isAssignmentPopoverVisible} - panelPaddingSize="s" - withTitle - > - {assignmentOptions ? ( - // @ts-ignore direction prop not available on current typing - - {assignmentOptions} - - ) : ( -
- Loading -
- )} -
-
+ {assignmentTitle && ( + + { + this.setState({ + isAssignmentPopoverVisible: true, + }); + actionHandler('loadAssignmentOptions'); + }} + > + {assignmentTitle} + + } + closePopover={() => { + this.setState({ isAssignmentPopoverVisible: false }); + }} + id="assignmentList" + isOpen={isAssignmentPopoverVisible} + panelPaddingSize="s" + withTitle + > + {assignmentOptions && renderAssignmentOptions ? ( + // @ts-ignore direction prop not available on current typing + + {assignmentOptions.map(options => renderAssignmentOptions(options))} + + ) : ( +
+ Loading +
+ )} +
+
+ )} ); } diff --git a/x-pack/plugins/beats_management/public/components/table/controls.tsx b/x-pack/plugins/beats_management/public/components/table/controls.tsx index f4d85d57d4cf8..07de3cddadfdd 100644 --- a/x-pack/plugins/beats_management/public/components/table/controls.tsx +++ b/x-pack/plugins/beats_management/public/components/table/controls.tsx @@ -15,6 +15,8 @@ import { ControlDefinitions } from './table_type_configs'; interface ControlBarProps { assignmentOptions: any[] | null; assignmentTitle: string | null; + renderAssignmentOptions?: (item: any) => any; + showAssignmentOptions: boolean; controlDefinitions: ControlDefinitions; selectionCount: number; @@ -25,6 +27,7 @@ export function ControlBar(props: ControlBarProps) { const { actionHandler, assignmentOptions, + renderAssignmentOptions, assignmentTitle, controlDefinitions, selectionCount, @@ -36,6 +39,7 @@ export function ControlBar(props: ControlBarProps) { any; items: any[]; showAssignmentOptions: boolean; type: TableType; @@ -26,7 +26,7 @@ interface BeatsTableProps { } interface BeatsTableState { - selection: CMPopulatedBeat[]; + selection: any[]; } const TableContainer = styled.div` @@ -42,11 +42,15 @@ export class Table extends React.Component { }; } - public render() { + public resetSelection = () => { + this.setSelection([]); + }; + public render() { const { actionHandler, assignmentOptions, + renderAssignmentOptions, assignmentTitle, items, showAssignmentOptions, @@ -70,6 +74,7 @@ export class Table extends React.Component { actionHandler(action, payload)} assignmentOptions={assignmentOptions} + renderAssignmentOptions={renderAssignmentOptions} assignmentTitle={assignmentTitle} controlDefinitions={type.controlDefinitions(items)} selectionCount={this.state.selection.length} @@ -89,7 +94,7 @@ export class Table extends React.Component { ); } - private setSelection = (selection: any) => { + private setSelection = (selection: any[]) => { this.setState({ selection, }); diff --git a/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx b/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx index 643c7ce4b810f..e9111c994a337 100644 --- a/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx +++ b/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx @@ -12,6 +12,7 @@ import React from 'react'; import { TABLE_CONFIG } from '../../../common/constants'; import { BeatTag, CMPopulatedBeat, ConfigurationBlock } from '../../../common/domain_types'; +import { ConnectedLink } from '../connected_link'; export interface ColumnDefinition { align?: string; @@ -55,7 +56,7 @@ export const BeatsTableType: TableType = { { field: 'id', name: 'Beat name', - render: (id: string) => {id}, + render: (id: string) => {id}, sortable: true, }, { @@ -154,7 +155,13 @@ export const TagsTableType: TableType = { }, ], controlDefinitions: (data: any) => ({ - actions: [], + actions: [ + { + name: 'Remove Selected', + action: 'delete', + danger: true, + }, + ], filters: [], }), }; diff --git a/x-pack/plugins/beats_management/public/components/tag/index.ts b/x-pack/plugins/beats_management/public/components/tag/index.ts index 8447142e16a73..bfea59263fc44 100644 --- a/x-pack/plugins/beats_management/public/components/tag/index.ts +++ b/x-pack/plugins/beats_management/public/components/tag/index.ts @@ -4,5 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { TagCreateConfig, TagEditConfig, TagViewConfig } from './tag_configs'; export { TagEdit } from './tag_edit'; diff --git a/x-pack/plugins/beats_management/public/components/tag/tag_configs.ts b/x-pack/plugins/beats_management/public/components/tag/tag_configs.ts deleted file mode 100644 index 08ad711e798de..0000000000000 --- a/x-pack/plugins/beats_management/public/components/tag/tag_configs.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export interface TagViewConfig { - showAttachedBeats: boolean; -} - -export const TagCreateConfig: TagViewConfig = { - showAttachedBeats: false, -}; - -export const TagEditConfig: TagViewConfig = { - showAttachedBeats: true, -}; diff --git a/x-pack/plugins/beats_management/public/components/tag/tag_edit.tsx b/x-pack/plugins/beats_management/public/components/tag/tag_edit.tsx index a85f46218685d..2167d42b093ab 100644 --- a/x-pack/plugins/beats_management/public/components/tag/tag_edit.tsx +++ b/x-pack/plugins/beats_management/public/components/tag/tag_edit.tsx @@ -35,22 +35,19 @@ import { import 'brace/mode/yaml'; import 'brace/theme/github'; import React from 'react'; -import { ConfigurationBlock } from '../../../common/domain_types'; +import { BeatTag, CMBeat } from '../../../common/domain_types'; import { Table } from '../table'; import { BeatsTableType } from '../table'; -import { TagViewConfig } from '../tag'; interface TagEditProps { - items: any[]; - config: TagViewConfig; + tag: Partial; + onTagChange: (field: keyof BeatTag, value: string) => any; + attachedBeats: CMBeat[] | null; } interface TagEditState { - color: string | null; - configurationBlocks: ConfigurationBlock[]; showFlyout: boolean; tableRef: any; - tagName: string | null; } export class TagEdit extends React.PureComponent { @@ -58,26 +55,15 @@ export class TagEdit extends React.PureComponent { super(props); this.state = { - color: '#DD0A73', - configurationBlocks: [], showFlyout: false, tableRef: React.createRef(), - tagName: null, }; } public render() { - const { - config: { showAttachedBeats }, - items, - } = this.props; - const { color, configurationBlocks, tagName } = this.state; + const { tag, attachedBeats } = this.props; return (
- -

Add a new tag

-
- @@ -92,20 +78,23 @@ export class TagEdit extends React.PureComponent {

- {tagName ? tagName : 'Tag name'} + + {tag.id ? tag.id : 'Tag name'} +
- + @@ -113,7 +102,11 @@ export class TagEdit extends React.PureComponent {
- +

Configurations

@@ -134,7 +127,7 @@ export class TagEdit extends React.PureComponent {
- {showAttachedBeats && ( + {attachedBeats && (

Attached Beats

@@ -145,24 +138,14 @@ export class TagEdit extends React.PureComponent { }} assignmentOptions={[]} assignmentTitle={null} - items={items} + items={attachedBeats} ref={this.state.tableRef} showAssignmentOptions={false} type={BeatsTableType} />
)} - - - - - Save - - - - Cancel - - + {this.state.showFlyout && ( this.setState({ showFlyout: false })}> @@ -227,6 +210,6 @@ export class TagEdit extends React.PureComponent { showFlyout: true, }); }; - private updateBadgeColor = (e: any) => this.setState({ color: e }); - private updateBadgeName = (e: any) => this.setState({ tagName: e.target.value }); + private updateTag = (key: keyof BeatTag) => (e: any) => + this.props.onTagChange(key, e.target ? e.target.value : e); } diff --git a/x-pack/plugins/beats_management/public/lib/adapters/tags/adapter_types.ts b/x-pack/plugins/beats_management/public/lib/adapters/tags/adapter_types.ts index 57bdf2592baa3..395c01f259dc3 100644 --- a/x-pack/plugins/beats_management/public/lib/adapters/tags/adapter_types.ts +++ b/x-pack/plugins/beats_management/public/lib/adapters/tags/adapter_types.ts @@ -7,6 +7,7 @@ import { BeatTag } from '../../../../common/domain_types'; export interface CMTagsAdapter { getTagsWithIds(tagIds: string[]): Promise; + delete(tagIds: string[]): Promise; getAll(): Promise; upsertTag(tag: BeatTag): Promise; } diff --git a/x-pack/plugins/beats_management/public/lib/adapters/tags/memory_tags_adapter.ts b/x-pack/plugins/beats_management/public/lib/adapters/tags/memory_tags_adapter.ts index 6b7ddd1c98e8c..86daefb47c653 100644 --- a/x-pack/plugins/beats_management/public/lib/adapters/tags/memory_tags_adapter.ts +++ b/x-pack/plugins/beats_management/public/lib/adapters/tags/memory_tags_adapter.ts @@ -18,6 +18,11 @@ export class MemoryTagsAdapter implements CMTagsAdapter { return this.tagsDB.filter(tag => tagIds.includes(tag.id)); } + public async delete(tagIds: string[]) { + this.tagsDB = this.tagsDB.filter(tag => !tagIds.includes(tag.id)); + return true; + } + public async getAll() { return this.tagsDB; } diff --git a/x-pack/plugins/beats_management/public/lib/adapters/tags/rest_tags_adapter.ts b/x-pack/plugins/beats_management/public/lib/adapters/tags/rest_tags_adapter.ts index fc4a5a157ed71..e49d4a9109984 100644 --- a/x-pack/plugins/beats_management/public/lib/adapters/tags/rest_tags_adapter.ts +++ b/x-pack/plugins/beats_management/public/lib/adapters/tags/rest_tags_adapter.ts @@ -12,16 +12,25 @@ export class RestTagsAdapter implements CMTagsAdapter { constructor(private readonly REST: RestAPIAdapter) {} public async getTagsWithIds(tagIds: string[]): Promise { - return (await this.REST.get<{ tags: BeatTag[] }>(`/api/beats/tags/${tagIds.join(',')}`)).tags; + const tags = await this.REST.get(`/api/beats/tags/${tagIds.join(',')}`); + return tags; } public async getAll(): Promise { - return (await this.REST.get<{ tags: BeatTag[] }>(`/api/beats/tags`)).tags; + return await this.REST.get(`/api/beats/tags`); + } + + public async delete(tagIds: string[]): Promise { + return (await this.REST.delete<{ success: boolean }>(`/api/beats/tags/${tagIds.join(',')}`)) + .success; } public async upsertTag(tag: BeatTag): Promise { - return (await this.REST.put<{ tag: BeatTag }>(`/api/beats/tag/{tag}`, { + const response = await this.REST.put<{ success: boolean }>(`/api/beats/tag/${tag.id}`, { + color: tag.color, configuration_blocks: tag.configuration_blocks, - })).tag; + }); + + return response.success ? tag : null; } } diff --git a/x-pack/plugins/beats_management/public/lib/compose/kibana.ts b/x-pack/plugins/beats_management/public/lib/compose/kibana.ts index ef395a54ba73b..3c445ea8aaee5 100644 --- a/x-pack/plugins/beats_management/public/lib/compose/kibana.ts +++ b/x-pack/plugins/beats_management/public/lib/compose/kibana.ts @@ -13,20 +13,22 @@ import { management } from 'ui/management'; import { uiModules } from 'ui/modules'; // @ts-ignore: path dynamic for kibana import routes from 'ui/routes'; -// @ts-ignore: path dynamic for kibana import { RestBeatsAdapter } from '../adapters/beats/rest_beats_adapter'; import { KibanaFrameworkAdapter } from '../adapters/framework/kibana_framework_adapter'; import { AxiosRestAPIAdapter } from '../adapters/rest_api/axios_rest_api_adapter'; import { RestTagsAdapter } from '../adapters/tags/rest_tags_adapter'; import { RestTokensAdapter } from '../adapters/tokens/rest_tokens_adapter'; import { FrontendDomainLibs, FrontendLibs } from '../lib'; +import { BeatsLib } from './../domains/beats'; export function compose(): FrontendLibs { const api = new AxiosRestAPIAdapter(chrome.getXsrfToken(), chrome.getBasePath()); const tags = new RestTagsAdapter(api); const tokens = new RestTokensAdapter(api); - const beats = new RestBeatsAdapter(api); + const beats = new BeatsLib(new RestBeatsAdapter(api), { + tags, + }); const domainLibs: FrontendDomainLibs = { tags, diff --git a/x-pack/plugins/beats_management/public/lib/domains/beats.ts b/x-pack/plugins/beats_management/public/lib/domains/beats.ts new file mode 100644 index 0000000000000..d507cc764de83 --- /dev/null +++ b/x-pack/plugins/beats_management/public/lib/domains/beats.ts @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { flatten } from 'lodash'; +import { CMBeat, CMPopulatedBeat } from '../../../common/domain_types'; +import { + BeatsRemovalReturn, + BeatsTagAssignment, + CMAssignmentReturn, + CMBeatsAdapter, +} from '../adapters/beats/adapter_types'; +import { CMTagsAdapter } from './../adapters/tags/adapter_types'; + +export class BeatsLib { + constructor( + private readonly adapter: CMBeatsAdapter, + private readonly libs: { tags: CMTagsAdapter } + ) {} + + public async get(id: string): Promise { + const beat = await this.adapter.get(id); + return beat ? (await this.mergeInTags([beat]))[0] : null; + } + + public async getBeatWithToken(enrollmentToken: string): Promise { + const beat = await this.adapter.getBeatWithToken(enrollmentToken); + return beat; + } + + public async getAll(): Promise { + const beats = await this.adapter.getAll(); + return await this.mergeInTags(beats); + } + + public async update(id: string, beatData: Partial): Promise { + return await this.adapter.update(id, beatData); + } + + public async removeTagsFromBeats(removals: BeatsTagAssignment[]): Promise { + return await this.adapter.removeTagsFromBeats(removals); + } + + public async assignTagsToBeats(assignments: BeatsTagAssignment[]): Promise { + return await this.adapter.assignTagsToBeats(assignments); + } + + private async mergeInTags(beats: CMBeat[]): Promise { + const tagIds = flatten(beats.map(b => b.tags || [])); + const tags = await this.libs.tags.getTagsWithIds(tagIds); + + // TODO the filter should not be needed, if the data gets into a bad state, we should error + // and inform the user they need to delte the tag, or else we should auto delete it + const mergedBeats: CMPopulatedBeat[] = beats.map( + b => + ({ + ...b, + full_tags: (b.tags || []).map(tagId => tags.find(t => t.id === tagId)).filter(t => t), + } as CMPopulatedBeat) + ); + return mergedBeats; + } +} diff --git a/x-pack/plugins/beats_management/public/lib/lib.ts b/x-pack/plugins/beats_management/public/lib/lib.ts index 03dc74122abcb..c9b3c8f1b4092 100644 --- a/x-pack/plugins/beats_management/public/lib/lib.ts +++ b/x-pack/plugins/beats_management/public/lib/lib.ts @@ -7,12 +7,12 @@ import { IModule, IScope } from 'angular'; import { AxiosRequestConfig } from 'axios'; import React from 'react'; -import { CMBeatsAdapter } from './adapters/beats/adapter_types'; import { CMTagsAdapter } from './adapters/tags/adapter_types'; import { CMTokensAdapter } from './adapters/tokens/adapter_types'; +import { BeatsLib } from './domains/beats'; export interface FrontendDomainLibs { - beats: CMBeatsAdapter; + beats: BeatsLib; tags: CMTagsAdapter; tokens: CMTokensAdapter; } diff --git a/x-pack/plugins/beats_management/public/pages/beat/index.tsx b/x-pack/plugins/beats_management/public/pages/beat/index.tsx new file mode 100644 index 0000000000000..ace411b47b13b --- /dev/null +++ b/x-pack/plugins/beats_management/public/pages/beat/index.tsx @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { PrimaryLayout } from '../../components/layouts/primary'; + +export class BeatDetailsPage extends React.PureComponent { + public render() { + return ( + +
beat details view
+
+ ); + } +} diff --git a/x-pack/plugins/beats_management/public/pages/main/beats.tsx b/x-pack/plugins/beats_management/public/pages/main/beats.tsx index 7854e3336f070..e8524299a0afe 100644 --- a/x-pack/plugins/beats_management/public/pages/main/beats.tsx +++ b/x-pack/plugins/beats_management/public/pages/main/beats.tsx @@ -11,7 +11,7 @@ import { } from '@elastic/eui'; import React from 'react'; -import { BeatTag, CMBeat, CMPopulatedBeat } from '../../../common/domain_types'; +import { BeatTag, CMPopulatedBeat } from '../../../common/domain_types'; import { BeatsTagAssignment } from '../../../server/lib/adapters/beats/adapter_types'; import { BeatsTableType, Table } from '../../components/table'; import { FrontendLibs } from '../../lib/lib'; @@ -23,19 +23,19 @@ interface BeatsPageProps { } interface BeatsPageState { - beats: CMBeat[]; - tableRef: any; + beats: CMPopulatedBeat[]; tags: any[] | null; } export class BeatsPage extends React.PureComponent { public static ActionArea = BeatsActionArea; + private tableRef = React.createRef
(); + constructor(props: BeatsPageProps) { super(props); this.state = { beats: [], - tableRef: React.createRef(), tags: null, }; @@ -51,9 +51,32 @@ export class BeatsPage extends React.PureComponent { + const selectedBeats = this.getSelectedBeats(); + const hasMatches = selectedBeats.some((beat: any) => + (beat.tags || []).some((t: string) => t === tag.id) + ); + + return ( + + this.removeTagsFromBeats(selectedBeats, tag) + : () => this.assignTagsToBeats(selectedBeats, tag) + } + onClickAriaLabel={tag.id} + > + {tag.id} + + + ); + }} assignmentTitle="Set tags" items={this.state.beats || []} - ref={this.state.tableRef} + ref={this.tableRef} showAssignmentOptions={true} type={BeatsTableType} /> @@ -88,6 +111,9 @@ export class BeatsPage extends React.PureComponent { await this.loadBeats(); + if (this.tableRef && this.tableRef.current) { + this.tableRef.current.resetSelection(); + } }, 100); }; @@ -98,39 +124,16 @@ export class BeatsPage extends React.PureComponent { // await this.props.libs.beats.searach(query); }; private loadTags = async () => { const tags = await this.props.libs.tags.getAll(); - const selectedBeats = this.getSelectedBeats(); - - const renderedTags = tags.map((tag: BeatTag) => { - const hasMatches = selectedBeats.some((beat: any) => - beat.full_tags.some((t: any) => t.id === tag.id) - ); - return ( - - this.removeTagsFromBeats(selectedBeats, tag) - : () => this.assignTagsToBeats(selectedBeats, tag) - } - onClickAriaLabel={tag.id} - > - {tag.id} - - - ); - }); this.setState({ - tags: renderedTags, + tags, }); }; @@ -146,10 +149,16 @@ export class BeatsPage extends React.PureComponent { await this.props.libs.beats.assignTagsToBeats(this.createBeatTagAssignments(beats, tag)); - this.loadBeats(); + await this.loadBeats(); + await this.loadTags(); }; private getSelectedBeats = (): CMPopulatedBeat[] => { - return this.state.tableRef.current.state.selection; + if (this.tableRef && this.tableRef.current) { + return this.tableRef.current.state.selection.map( + (beat: CMPopulatedBeat) => this.state.beats.find(b => b.id === beat.id) || beat + ); + } + return []; }; } diff --git a/x-pack/plugins/beats_management/public/pages/main/beats_action_area.tsx b/x-pack/plugins/beats_management/public/pages/main/beats_action_area.tsx index 6cedb5bd363b9..d7973a92c7e50 100644 --- a/x-pack/plugins/beats_management/public/pages/main/beats_action_area.tsx +++ b/x-pack/plugins/beats_management/public/pages/main/beats_action_area.tsx @@ -75,7 +75,7 @@ export class BeatsActionArea extends React.Component { color="primary" onClick={async () => { const token = await libs.tokens.createEnrollmentToken(); - history.push(`/beats/enroll/${token}`); + history.push(`/overview/beats/enroll/${token}`); this.waitForToken(token); }} > @@ -88,7 +88,7 @@ export class BeatsActionArea extends React.Component { this.pinging = false; this.setState({ enrolledBeat: null - }, () => history.push('/beats')) + }, () => history.push('/overview/beats')) }} style={{ width: '640px' }}> Enroll a new Beat @@ -146,7 +146,7 @@ export class BeatsActionArea extends React.Component { enrolledBeat: null }) const token = await libs.tokens.createEnrollmentToken(); - history.push(`/beats/enroll/${token}`); + history.push(`/overview/beats/enroll/${token}`); this.waitForToken(token); }} > diff --git a/x-pack/plugins/beats_management/public/pages/main/create_tag.tsx b/x-pack/plugins/beats_management/public/pages/main/create_tag.tsx deleted file mode 100644 index 27ba69e435eef..0000000000000 --- a/x-pack/plugins/beats_management/public/pages/main/create_tag.tsx +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import 'brace/mode/yaml'; -import 'brace/theme/github'; -import React from 'react'; -import { ConfigurationBlock } from '../../../common/domain_types'; -import { TagCreateConfig, TagEdit } from '../../components/tag'; -import { FrontendLibs } from '../../lib/lib'; - -interface CreateTagPageProps { - libs: FrontendLibs; -} - -interface CreateTagPageState { - color: string | null; - configurationBlocks: ConfigurationBlock[]; - showFlyout: boolean; - tagName: string | null; -} - -export class CreateTagPage extends React.PureComponent { - constructor(props: CreateTagPageProps) { - super(props); - - this.state = { - color: '#DD0A73', - configurationBlocks: [], - showFlyout: false, - tagName: null, - }; - } - - public render() { - return ; - } -} diff --git a/x-pack/plugins/beats_management/public/pages/main/index.tsx b/x-pack/plugins/beats_management/public/pages/main/index.tsx index 7d201bde445be..dfac6ac994e2f 100644 --- a/x-pack/plugins/beats_management/public/pages/main/index.tsx +++ b/x-pack/plugins/beats_management/public/pages/main/index.tsx @@ -11,13 +11,11 @@ import { EuiTabs, } from '@elastic/eui'; import React from 'react'; -import { Redirect, Route, Switch } from 'react-router-dom'; +import { Route, Switch } from 'react-router-dom'; import { PrimaryLayout } from '../../components/layouts/primary'; import { FrontendLibs } from '../../lib/lib'; import { ActivityPage } from './activity'; import { BeatsPage } from './beats'; -import { CreateTagPage } from './create_tag'; -import { EditTagPage } from './edit_tag'; import { TagsPage } from './tags'; interface MainPagesProps { @@ -26,7 +24,6 @@ interface MainPagesProps { } interface MainPagesState { - selectedTabId: string; enrollBeat?: { enrollmentToken: string; } | null; @@ -35,10 +32,6 @@ interface MainPagesState { export class MainPages extends React.PureComponent { constructor(props: any) { super(props); - - this.state = { - selectedTabId: '/', - }; } public onSelectedTabChanged = (id: string) => { @@ -48,30 +41,20 @@ export class MainPages extends React.PureComponent ( @@ -90,9 +73,13 @@ export class MainPages extends React.PureComponent } /> + } + /> } > @@ -100,34 +87,19 @@ export class MainPages extends React.PureComponent } - /> - } /> } /> } /> - } - /> - } - /> ); diff --git a/x-pack/plugins/beats_management/public/pages/main/tags.tsx b/x-pack/plugins/beats_management/public/pages/main/tags.tsx index 9c8c0ac2347b1..73b39a215a46d 100644 --- a/x-pack/plugins/beats_management/public/pages/main/tags.tsx +++ b/x-pack/plugins/beats_management/public/pages/main/tags.tsx @@ -4,9 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ - -// @ts-ignore EuiToolTip has no typings in current version -import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiIcon, EuiToolTip } from '@elastic/eui'; +import { + EuiButton, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + // @ts-ignore EuiToolTip has no typings in current version + EuiToolTip, +} from '@elastic/eui'; import React from 'react'; import { BeatTag, CMBeat } from '../../../common/domain_types'; import { BeatsTagAssignment } from '../../../server/lib/adapters/beats/adapter_types'; @@ -19,17 +25,27 @@ interface TagsPageProps { interface TagsPageState { beats: any; - tableRef: any; tags: BeatTag[]; } export class TagsPage extends React.PureComponent { + public static ActionArea = ({ history }: any) => ( + { + history.push(`/tag/create`); + }} + > + Create Tag + + ); + public tableRef = React.createRef
(); constructor(props: TagsPageProps) { super(props); this.state = { beats: [], - tableRef: React.createRef(), tags: [], }; @@ -41,20 +57,34 @@ export class TagsPage extends React.PureComponent
); } - private handleTagsAction = (action: string, payload: any) => { + private handleTagsAction = async (action: string) => { switch (action) { case 'loadAssignmentOptions': this.loadBeats(); break; + case 'delete': + const tags = this.getSelectedTags().map(tag => tag.id); + const success = await this.props.libs.tags.delete(tags); + if (!success) { + alert( + 'Some of these tags might be assigned to beats. Please ensure tags being removed are not activly assigned' + ); + } else { + this.loadTags(); + if (this.tableRef && this.tableRef.current) { + this.tableRef.current.resetSelection(); + } + } + break; } this.loadTags(); @@ -136,7 +166,10 @@ export class TagsPage extends React.PureComponent await this.props.libs.beats.assignTagsToBeats(assignments); }; - private getSelectedTags = () => { - return this.state.tableRef.current.state.selection; + private getSelectedTags = (): BeatTag[] => { + if (this.tableRef && this.tableRef.current) { + return this.tableRef.current.state.selection; + } + return []; }; } diff --git a/x-pack/plugins/beats_management/public/pages/tag/create.tsx b/x-pack/plugins/beats_management/public/pages/tag/create.tsx new file mode 100644 index 0000000000000..5a319a04aad7f --- /dev/null +++ b/x-pack/plugins/beats_management/public/pages/tag/create.tsx @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiButton, EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; +import 'brace/mode/yaml'; +import 'brace/theme/github'; +import React from 'react'; +import { BeatTag } from '../../../common/domain_types'; +import { TagEdit } from '../../components/tag'; +import { FrontendLibs } from '../../lib/lib'; + +interface CreateTagPageProps { + libs: FrontendLibs; + history: any; +} + +interface CreateTagPageState { + showFlyout: boolean; + tag: BeatTag; +} + +export class CreateTagPage extends React.PureComponent { + constructor(props: CreateTagPageProps) { + super(props); + + this.state = { + showFlyout: false, + tag: { + id: '', + color: '#DD0A73', + configuration_blocks: [], + last_updated: new Date(), + }, + }; + } + + public render() { + return ( +
+ + this.setState(oldState => ({ + tag: { ...oldState.tag, [field]: value }, + })) + } + attachedBeats={null} + /> + + + + + Save + + + + this.props.history.push('/overview/tags')}> + Cancel + + + +
+ ); + } + + private saveTag = async () => { + await this.props.libs.tags.upsertTag(this.state.tag as BeatTag); + this.props.history.push('/overview/tags'); + }; +} diff --git a/x-pack/plugins/beats_management/public/pages/main/edit_tag.tsx b/x-pack/plugins/beats_management/public/pages/tag/edit.tsx similarity index 58% rename from x-pack/plugins/beats_management/public/pages/main/edit_tag.tsx rename to x-pack/plugins/beats_management/public/pages/tag/edit.tsx index dab4d82e36844..386cf2cc18eb0 100644 --- a/x-pack/plugins/beats_management/public/pages/main/edit_tag.tsx +++ b/x-pack/plugins/beats_management/public/pages/tag/edit.tsx @@ -7,8 +7,8 @@ import 'brace/mode/yaml'; import 'brace/theme/github'; import React from 'react'; -import { ConfigurationBlock } from '../../../common/domain_types'; -import { TagEdit, TagEditConfig } from '../../components/tag'; +import { BeatTag } from '../../../common/domain_types'; +import { TagEdit } from '../../components/tag'; import { FrontendLibs } from '../../lib/lib'; interface EditTagPageProps { @@ -16,10 +16,8 @@ interface EditTagPageProps { } interface EditTagPageState { - color: string | null; - configurationBlocks: ConfigurationBlock[]; showFlyout: boolean; - tagName: string | null; + tag: Partial; } export class EditTagPage extends React.PureComponent { @@ -27,14 +25,26 @@ export class EditTagPage extends React.PureComponent; + return ( + + this.setState(oldState => ({ + tag: { ...oldState.tag, [field]: value }, + })) + } + attachedBeats={[]} + /> + ); } } diff --git a/x-pack/plugins/beats_management/public/pages/tag/index.tsx b/x-pack/plugins/beats_management/public/pages/tag/index.tsx new file mode 100644 index 0000000000000..1a1054cd1ab28 --- /dev/null +++ b/x-pack/plugins/beats_management/public/pages/tag/index.tsx @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import 'brace/mode/yaml'; +import 'brace/theme/github'; +import React from 'react'; +import { Route, Switch } from 'react-router'; +import { ConfigurationBlock } from '../../../common/domain_types'; +import { PrimaryLayout } from '../../components/layouts/primary'; +import { FrontendLibs } from '../../lib/lib'; +import { CreateTagPage } from './create'; +import { EditTagPage } from './edit'; +interface EditTagPageProps { + libs: FrontendLibs; + history: any; +} + +interface EditTagPageState { + color: string | null; + configurationBlocks: ConfigurationBlock[]; + showFlyout: boolean; + tagName: string | null; +} + +export class TagPage extends React.PureComponent { + constructor(props: EditTagPageProps) { + super(props); + + this.state = { + color: '#DD0A73', + configurationBlocks: [], + showFlyout: false, + tagName: null, + }; + } + + public render() { + return ( + + + } + /> + } + /> + + + ); + } +} diff --git a/x-pack/plugins/beats_management/public/router.tsx b/x-pack/plugins/beats_management/public/router.tsx index 7fb283c33da86..6642384468803 100644 --- a/x-pack/plugins/beats_management/public/router.tsx +++ b/x-pack/plugins/beats_management/public/router.tsx @@ -5,14 +5,28 @@ */ import React from 'react'; -import { HashRouter, Route } from 'react-router-dom'; +import { HashRouter, Redirect, Route, Switch } from 'react-router-dom'; +import { BeatDetailsPage } from './pages/beat'; import { MainPages } from './pages/main'; +import { TagPage } from './pages/tag'; export const PageRouter: React.SFC<{ libs: any }> = ({ libs }) => { return ( - } /> + + } + /> + } /> + } + /> + } /> + ); }; diff --git a/x-pack/plugins/beats_management/server/lib/adapters/beats/adapter_types.ts b/x-pack/plugins/beats_management/server/lib/adapters/beats/adapter_types.ts index 9fc2578ccf8da..c8d96a77df9f7 100644 --- a/x-pack/plugins/beats_management/server/lib/adapters/beats/adapter_types.ts +++ b/x-pack/plugins/beats_management/server/lib/adapters/beats/adapter_types.ts @@ -13,6 +13,7 @@ export interface CMBeatsAdapter { get(user: FrameworkUser, id: string): Promise; getAll(user: FrameworkUser): Promise; getWithIds(user: FrameworkUser, beatIds: string[]): Promise; + getAllWithTags(user: FrameworkUser, tagIds: string[]): Promise; getBeatWithToken(user: FrameworkUser, enrollmentToken: string): Promise; removeTagsFromBeats( user: FrameworkUser, diff --git a/x-pack/plugins/beats_management/server/lib/adapters/beats/elasticsearch_beats_adapter.ts b/x-pack/plugins/beats_management/server/lib/adapters/beats/elasticsearch_beats_adapter.ts index 22bba3661a752..c7f728c9c00d8 100644 --- a/x-pack/plugins/beats_management/server/lib/adapters/beats/elasticsearch_beats_adapter.ts +++ b/x-pack/plugins/beats_management/server/lib/adapters/beats/elasticsearch_beats_adapter.ts @@ -84,6 +84,28 @@ export class ElasticsearchBeatsAdapter implements CMBeatsAdapter { .map((b: any) => b._source.beat); } + public async getAllWithTags(user: FrameworkUser, tagIds: string[]): Promise { + const params = { + ignore: [404], + index: INDEX_NAMES.BEATS, + type: '_doc', + body: { + query: { + terms: { 'beat.tags': tagIds }, + }, + }, + }; + + const response = await this.database.search(user, params); + + const beats = _get(response, 'hits.hits', []); + + if (beats.length === 0) { + return []; + } + return beats.map((beat: any) => omit(beat._source.beat, ['access_token'])); + } + public async getBeatWithToken( user: FrameworkUser, enrollmentToken: string @@ -106,7 +128,7 @@ export class ElasticsearchBeatsAdapter implements CMBeatsAdapter { if (beats.length === 0) { return null; } - return _get(beats[0], '_source.beat'); + return omit(_get(beats[0], '_source.beat'), ['access_token']); } public async getAll(user: FrameworkUser) { diff --git a/x-pack/plugins/beats_management/server/lib/adapters/beats/memory_beats_adapter.ts b/x-pack/plugins/beats_management/server/lib/adapters/beats/memory_beats_adapter.ts index 3ab38a0716455..f5bcf91e07d52 100644 --- a/x-pack/plugins/beats_management/server/lib/adapters/beats/memory_beats_adapter.ts +++ b/x-pack/plugins/beats_management/server/lib/adapters/beats/memory_beats_adapter.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { omit } from 'lodash'; +import { intersection, omit } from 'lodash'; import { CMBeat } from '../../../../common/domain_types'; import { FrameworkUser } from '../framework/adapter_types'; @@ -38,6 +38,10 @@ export class MemoryBeatsAdapter implements CMBeatsAdapter { return this.beatsDB.filter(beat => beatIds.includes(beat.id)); } + public async getAllWithTags(user: FrameworkUser, tagIds: string[]): Promise { + return this.beatsDB.filter(beat => intersection(tagIds, beat.tags || []).length !== 0); + } + public async getBeatWithToken( user: FrameworkUser, enrollmentToken: string diff --git a/x-pack/plugins/beats_management/server/lib/adapters/tags/adapter_types.ts b/x-pack/plugins/beats_management/server/lib/adapters/tags/adapter_types.ts index 77ae4ff8ad095..a5e01541ce386 100644 --- a/x-pack/plugins/beats_management/server/lib/adapters/tags/adapter_types.ts +++ b/x-pack/plugins/beats_management/server/lib/adapters/tags/adapter_types.ts @@ -8,6 +8,7 @@ import { FrameworkUser } from '../framework/adapter_types'; export interface CMTagsAdapter { getAll(user: FrameworkUser): Promise; + delete(user: FrameworkUser, tagIds: string[]): Promise; getTagsWithIds(user: FrameworkUser, tagIds: string[]): Promise; upsertTag(user: FrameworkUser, tag: BeatTag): Promise<{}>; } diff --git a/x-pack/plugins/beats_management/server/lib/adapters/tags/elasticsearch_tags_adapter.ts b/x-pack/plugins/beats_management/server/lib/adapters/tags/elasticsearch_tags_adapter.ts index 645ed37f5ff8f..c53cd2836d9ba 100644 --- a/x-pack/plugins/beats_management/server/lib/adapters/tags/elasticsearch_tags_adapter.ts +++ b/x-pack/plugins/beats_management/server/lib/adapters/tags/elasticsearch_tags_adapter.ts @@ -4,11 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { get } from 'lodash'; +import { flatten, get } from 'lodash'; import { INDEX_NAMES } from '../../../../common/constants'; import { FrameworkUser } from '../framework/adapter_types'; -import { BeatTag } from '../../../../common/domain_types'; +import { BeatTag, CMBeat } from '../../../../common/domain_types'; import { DatabaseAdapter } from '../database/adapter_types'; import { CMTagsAdapter } from './adapter_types'; @@ -21,13 +21,71 @@ export class ElasticsearchTagsAdapter implements CMTagsAdapter { public async getAll(user: FrameworkUser) { const params = { + _source: true, index: INDEX_NAMES.BEATS, q: 'type:tag', type: '_doc', }; const response = await this.database.search(user, params); + const tags = get(response, 'hits.hits', []); - return get(response, 'hits.hits', []); + return tags.map((tag: any) => tag._source.tag); + } + + public async delete(user: FrameworkUser, tagIds: string[]) { + const ids = tagIds.map(tag => tag); + + const params = { + ignore: [404], + index: INDEX_NAMES.BEATS, + type: '_doc', + body: { + query: { + terms: { 'beat.tags': tagIds }, + }, + }, + }; + + const beatsResponse = await this.database.search(user, params); + + const beats = get(beatsResponse, 'hits.hits', []).map( + (beat: any) => beat._source.beat + ); + + const inactiveBeats = beats.filter(beat => beat.active === false); + const activeBeats = beats.filter(beat => beat.active === true); + if (activeBeats.length !== 0) { + return false; + } + const beatIds = inactiveBeats.map((beat: CMBeat) => beat.id); + + const bulkBeatsUpdates = flatten( + beatIds.map(beatId => { + const script = ` + def beat = ctx._source.beat; + if (beat.tags != null) { + beat.tags.removeAll([params.tag]); + }`; + + return flatten( + ids.map(tagId => [ + { update: { _id: `beat:${beatId}` } }, + { script: { source: script.replace(' ', ''), params: { tagId } } }, + ]) + ); + }) + ); + + const bulkTagsDelete = ids.map(tagId => ({ delete: { _id: `tag:${tagId}` } })); + + await this.database.bulk(user, { + body: flatten([...bulkBeatsUpdates, ...bulkTagsDelete]), + index: INDEX_NAMES.BEATS, + refresh: 'wait_for', + type: '_doc', + }); + + return true; } public async getTagsWithIds(user: FrameworkUser, tagIds: string[]) { @@ -35,7 +93,7 @@ export class ElasticsearchTagsAdapter implements CMTagsAdapter { // TODO abstract to kibana adapter as the more generic getDocs const params = { - _sourceInclude: ['tag.configuration_blocks'], + _source: true, body: { ids, }, diff --git a/x-pack/plugins/beats_management/server/lib/adapters/tags/memory_tags_adapter.ts b/x-pack/plugins/beats_management/server/lib/adapters/tags/memory_tags_adapter.ts index 7623c8aaa21d8..4d2ed434b8760 100644 --- a/x-pack/plugins/beats_management/server/lib/adapters/tags/memory_tags_adapter.ts +++ b/x-pack/plugins/beats_management/server/lib/adapters/tags/memory_tags_adapter.ts @@ -18,7 +18,11 @@ export class MemoryTagsAdapter implements CMTagsAdapter { public async getAll(user: FrameworkUser) { return this.tagsDB; } + public async delete(user: FrameworkUser, tagIds: string[]) { + this.tagsDB = this.tagsDB.filter(tag => !tagIds.includes(tag.id)); + return true; + } public async getTagsWithIds(user: FrameworkUser, tagIds: string[]) { return this.tagsDB.filter(tag => tagIds.includes(tag.id)); } diff --git a/x-pack/plugins/beats_management/server/lib/domains/beats.ts b/x-pack/plugins/beats_management/server/lib/domains/beats.ts index 199077d8570c3..bd04b60aa6468 100644 --- a/x-pack/plugins/beats_management/server/lib/domains/beats.ts +++ b/x-pack/plugins/beats_management/server/lib/domains/beats.ts @@ -49,6 +49,11 @@ export class CMBeatsDomain { return beat && beat.active ? beat : null; } + public async getAllWithTags(user: FrameworkUser, tagIds: string[], justActive = true) { + const beats = await this.adapter.getAllWithTags(user, tagIds); + return justActive ? beats.filter((beat: CMBeat) => beat.active === true) : beats; + } + public async update(userOrToken: UserOrToken, beatId: string, beatData: Partial) { const beat = await this.adapter.get(this.framework.internalUser, beatId); diff --git a/x-pack/plugins/beats_management/server/lib/domains/tags.ts b/x-pack/plugins/beats_management/server/lib/domains/tags.ts index 5f5e6747cc847..192b16c0b9ed9 100644 --- a/x-pack/plugins/beats_management/server/lib/domains/tags.ts +++ b/x-pack/plugins/beats_management/server/lib/domains/tags.ts @@ -13,10 +13,7 @@ import { entries } from '../../utils/polyfills'; import { CMTagsAdapter } from '../adapters/tags/adapter_types'; export class CMTagsDomain { - private adapter: CMTagsAdapter; - constructor(adapter: CMTagsAdapter) { - this.adapter = adapter; - } + constructor(private readonly adapter: CMTagsAdapter) {} public async getAll(user: FrameworkUser) { return await this.adapter.getAll(user); @@ -26,14 +23,24 @@ export class CMTagsDomain { return await this.adapter.getTagsWithIds(user, tagIds); } - public async saveTag(user: FrameworkUser, tagId: string, configs: ConfigurationBlock[]) { - const { isValid, message } = await this.validateConfigurationBlocks(configs); + public async delete(user: FrameworkUser, tagIds: string[]) { + return await this.adapter.delete(user, tagIds); + } + + public async saveTag( + user: FrameworkUser, + tagId: string, + config: { color: string; configuration_blocks: ConfigurationBlock[] } + ) { + const { isValid, message } = await this.validateConfigurationBlocks( + config.configuration_blocks + ); if (!isValid) { return { isValid, result: message }; } const tag = { - configuration_blocks: configs, + ...config, id: tagId, last_updated: new Date(), }; diff --git a/x-pack/plugins/beats_management/server/management_server.ts b/x-pack/plugins/beats_management/server/management_server.ts index 2cc4676da359e..bc14bacdb9e7b 100644 --- a/x-pack/plugins/beats_management/server/management_server.ts +++ b/x-pack/plugins/beats_management/server/management_server.ts @@ -12,6 +12,7 @@ import { createListAgentsRoute } from './rest_api/beats/list'; import { createTagAssignmentsRoute } from './rest_api/beats/tag_assignment'; import { createTagRemovalsRoute } from './rest_api/beats/tag_removal'; import { createBeatUpdateRoute } from './rest_api/beats/update'; +import { createDeleteTagsWithIdsRoute } from './rest_api/tags/delete'; import { createGetTagsWithIdsRoute } from './rest_api/tags/get'; import { createListTagsRoute } from './rest_api/tags/list'; import { createSetTagRoute } from './rest_api/tags/set'; @@ -29,6 +30,7 @@ export const initManagementServer = (libs: CMServerLibs) => { libs.framework.registerRoute(createGetBeatRoute(libs)); libs.framework.registerRoute(createGetTagsWithIdsRoute(libs)); libs.framework.registerRoute(createListTagsRoute(libs)); + libs.framework.registerRoute(createDeleteTagsWithIdsRoute(libs)); libs.framework.registerRoute(createGetBeatConfigurationRoute(libs)); libs.framework.registerRoute(createTagAssignmentsRoute(libs)); libs.framework.registerRoute(createListAgentsRoute(libs)); diff --git a/x-pack/plugins/beats_management/server/rest_api/beats/tag_removal.ts b/x-pack/plugins/beats_management/server/rest_api/beats/tag_removal.ts index eaec8f2872172..0cdd9f2f28c2b 100644 --- a/x-pack/plugins/beats_management/server/rest_api/beats/tag_removal.ts +++ b/x-pack/plugins/beats_management/server/rest_api/beats/tag_removal.ts @@ -19,7 +19,7 @@ export const createTagRemovalsRoute = (libs: CMServerLibs) => ({ payload: Joi.object({ removals: Joi.array().items( Joi.object({ - beat_id: Joi.string().required(), + beatId: Joi.string().required(), tag: Joi.string().required(), }) ), @@ -29,14 +29,8 @@ export const createTagRemovalsRoute = (libs: CMServerLibs) => ({ handler: async (request: FrameworkRequest, reply: any) => { const { removals } = request.payload; - // TODO abstract or change API to keep beatId consistent - const tweakedRemovals = removals.map((removal: any) => ({ - beatId: removal.beat_id, - tag: removal.tag, - })); - try { - const response = await libs.beats.removeTagsFromBeats(request.user, tweakedRemovals); + const response = await libs.beats.removeTagsFromBeats(request.user, removals); reply(response); } catch (err) { // TODO move this to kibana route thing in adapter diff --git a/x-pack/plugins/beats_management/server/rest_api/tags/delete.ts b/x-pack/plugins/beats_management/server/rest_api/tags/delete.ts new file mode 100644 index 0000000000000..451d800122a04 --- /dev/null +++ b/x-pack/plugins/beats_management/server/rest_api/tags/delete.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CMServerLibs } from '../../lib/lib'; +import { wrapEsError } from '../../utils/error_wrappers'; + +export const createDeleteTagsWithIdsRoute = (libs: CMServerLibs) => ({ + method: 'DELETE', + path: '/api/beats/tags/{tagIds}', + handler: async (request: any, reply: any) => { + const tagIdString: string = request.params.tagIds; + const tagIds = tagIdString.split(',').filter((id: string) => id.length > 0); + + let success: boolean; + try { + success = await libs.tags.delete(request.user, tagIds); + } catch (err) { + return reply(wrapEsError(err)); + } + + reply({ success }); + }, +}); diff --git a/x-pack/plugins/beats_management/server/rest_api/tags/list.ts b/x-pack/plugins/beats_management/server/rest_api/tags/list.ts index 41874a77eef0f..0569e29bb60db 100644 --- a/x-pack/plugins/beats_management/server/rest_api/tags/list.ts +++ b/x-pack/plugins/beats_management/server/rest_api/tags/list.ts @@ -10,7 +10,7 @@ import { wrapEsError } from '../../utils/error_wrappers'; export const createListTagsRoute = (libs: CMServerLibs) => ({ method: 'GET', - path: '/api/beats/tags/', + path: '/api/beats/tags', handler: async (request: any, reply: any) => { let tags: BeatTag[]; try { diff --git a/x-pack/plugins/beats_management/server/rest_api/tags/set.ts b/x-pack/plugins/beats_management/server/rest_api/tags/set.ts index f50721e764ef3..25837100e2a60 100644 --- a/x-pack/plugins/beats_management/server/rest_api/tags/set.ts +++ b/x-pack/plugins/beats_management/server/rest_api/tags/set.ts @@ -22,6 +22,7 @@ export const createSetTagRoute = (libs: CMServerLibs) => ({ tag: Joi.string(), }), payload: Joi.object({ + color: Joi.string(), configuration_blocks: Joi.array().items( Joi.object({ block_yml: Joi.string().required(), @@ -34,18 +35,14 @@ export const createSetTagRoute = (libs: CMServerLibs) => ({ }, }, handler: async (request: FrameworkRequest, reply: any) => { - const configurationBlocks = get(request, 'payload.configuration_blocks', []); + const config = get(request, 'payload', { configuration_blocks: [], color: '#DD0A73' }); try { - const { isValid, result } = await libs.tags.saveTag( - request.user, - request.params.tag, - configurationBlocks - ); + const { isValid, result } = await libs.tags.saveTag(request.user, request.params.tag, config); if (!isValid) { - return reply({ result }).code(400); + return reply({ result, success: false }).code(400); } - reply().code(result === 'created' ? 201 : 200); + reply({ success: true }).code(result === 'created' ? 201 : 200); } catch (err) { // TODO move this to kibana route thing in adapter return reply(wrapEsError(err)); diff --git a/x-pack/plugins/beats_management/server/utils/adapters/beats/elasticsearch_beats_adapter.ts b/x-pack/plugins/beats_management/server/utils/adapters/beats/elasticsearch_beats_adapter.ts new file mode 100644 index 0000000000000..283f65c1258ae --- /dev/null +++ b/x-pack/plugins/beats_management/server/utils/adapters/beats/elasticsearch_beats_adapter.ts @@ -0,0 +1,218 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { flatten, get, omit } from 'lodash'; +import moment from 'moment'; +import { INDEX_NAMES } from '../../../../common/constants'; +import { + BackendFrameworkAdapter, + CMBeat, + CMBeatsAdapter, + CMTagAssignment, + FrameworkRequest, +} from '../../lib'; + +export class ElasticsearchBeatsAdapter implements CMBeatsAdapter { + private framework: BackendFrameworkAdapter; + + constructor(framework: BackendFrameworkAdapter) { + this.framework = framework; + } + + public async get(id: string) { + const params = { + id: `beat:${id}`, + ignore: [404], + index: INDEX_NAMES.BEATS, + type: '_doc', + }; + + const response = await this.framework.callWithInternalUser('get', params); + if (!response.found) { + return null; + } + + return get(response, '_source.beat'); + } + + public async insert(beat: CMBeat) { + const body = { + beat, + type: 'beat', + }; + + const params = { + body, + id: `beat:${beat.id}`, + index: INDEX_NAMES.BEATS, + refresh: 'wait_for', + type: '_doc', + }; + await this.framework.callWithInternalUser('create', params); + } + + public async update(beat: CMBeat) { + const body = { + beat, + type: 'beat', + }; + + const params = { + body, + id: `beat:${beat.id}`, + index: INDEX_NAMES.BEATS, + refresh: 'wait_for', + type: '_doc', + }; + return await this.framework.callWithInternalUser('index', params); + } + + public async getWithIds(req: FrameworkRequest, beatIds: string[]) { + const ids = beatIds.map(beatId => `beat:${beatId}`); + + const params = { + _source: false, + body: { + ids, + }, + index: INDEX_NAMES.BEATS, + type: '_doc', + }; + const response = await this.framework.callWithRequest(req, 'mget', params); + return get(response, 'docs', []); + } + + // TODO merge with getBeatsWithIds + public async getVerifiedWithIds(req: FrameworkRequest, beatIds: string[]) { + const ids = beatIds.map(beatId => `beat:${beatId}`); + + const params = { + _sourceInclude: ['beat.id', 'beat.verified_on'], + body: { + ids, + }, + index: INDEX_NAMES.BEATS, + type: '_doc', + }; + const response = await this.framework.callWithRequest(req, 'mget', params); + return get(response, 'docs', []); + } + + public async verifyBeats(req: FrameworkRequest, beatIds: string[]) { + if (!Array.isArray(beatIds) || beatIds.length === 0) { + return []; + } + + const verifiedOn = moment().toJSON(); + const body = flatten( + beatIds.map(beatId => [ + { update: { _id: `beat:${beatId}` } }, + { doc: { beat: { verified_on: verifiedOn } } }, + ]) + ); + + const params = { + body, + index: INDEX_NAMES.BEATS, + refresh: 'wait_for', + type: '_doc', + }; + + const response = await this.framework.callWithRequest(req, 'bulk', params); + return get(response, 'items', []); + } + + public async getAll(req: FrameworkRequest) { + const params = { + index: INDEX_NAMES.BEATS, + q: 'type:beat', + type: '_doc', + }; + const response = await this.framework.callWithRequest( + req, + 'search', + params + ); + + const beats = get(response, 'hits.hits', []); + return beats.map((beat: any) => omit(beat._source.beat, ['access_token'])); + } + + public async removeTagsFromBeats( + req: FrameworkRequest, + removals: CMTagAssignment[] + ): Promise { + const body = flatten( + removals.map(({ beatId, tag }) => { + const script = + '' + + 'def beat = ctx._source.beat; ' + + 'if (beat.tags != null) { ' + + ' beat.tags.removeAll([params.tag]); ' + + '}'; + + return [ + { update: { _id: `beat:${beatId}` } }, + { script: { source: script, params: { tag } } }, + ]; + }) + ); + + const params = { + body, + index: INDEX_NAMES.BEATS, + refresh: 'wait_for', + type: '_doc', + }; + + const response = await this.framework.callWithRequest(req, 'bulk', params); + return get(response, 'items', []).map( + (item: any, resultIdx: number) => ({ + idxInRequest: removals[resultIdx].idxInRequest, + result: item.update.result, + status: item.update.status, + }) + ); + } + + public async assignTagsToBeats( + req: FrameworkRequest, + assignments: CMTagAssignment[] + ): Promise { + const body = flatten( + assignments.map(({ beatId, tag }) => { + const script = + '' + + 'def beat = ctx._source.beat; ' + + 'if (beat.tags == null) { ' + + ' beat.tags = []; ' + + '} ' + + 'if (!beat.tags.contains(params.tag)) { ' + + ' beat.tags.add(params.tag); ' + + '}'; + + return [ + { update: { _id: `beat:${beatId}` } }, + { script: { source: script, params: { tag } } }, + ]; + }) + ); + + const params = { + body, + index: INDEX_NAMES.BEATS, + refresh: 'wait_for', + type: '_doc', + }; + + const response = await this.framework.callWithRequest(req, 'bulk', params); + return get(response, 'items', []).map((item: any, resultIdx: any) => ({ + idxInRequest: assignments[resultIdx].idxInRequest, + result: item.update.result, + status: item.update.status, + })); + } +} diff --git a/x-pack/plugins/beats_management/server/utils/adapters/famework/kibana/kibana_framework_adapter.ts b/x-pack/plugins/beats_management/server/utils/adapters/famework/kibana/kibana_framework_adapter.ts new file mode 100644 index 0000000000000..6fc2fc4853b03 --- /dev/null +++ b/x-pack/plugins/beats_management/server/utils/adapters/famework/kibana/kibana_framework_adapter.ts @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + BackendFrameworkAdapter, + FrameworkRequest, + FrameworkRouteOptions, + WrappableRequest, +} from '../../../lib'; + +import { IStrictReply, Request, Server } from 'hapi'; +import { + internalFrameworkRequest, + wrapRequest, +} from '../../../../utils/wrap_request'; + +export class KibanaBackendFrameworkAdapter implements BackendFrameworkAdapter { + public version: string; + + private server: Server; + + constructor(hapiServer: Server) { + this.server = hapiServer; + this.version = hapiServer.plugins.kibana.status.plugin.version; + } + + public getSetting(settingPath: string) { + // TODO type check this properly + // @ts-ignore + return this.server.config().get(settingPath); + } + + public exposeStaticDir(urlPath: string, dir: string): void { + this.server.route({ + handler: { + directory: { + path: dir, + }, + }, + method: 'GET', + path: urlPath, + }); + } + + public registerRoute( + route: FrameworkRouteOptions + ) { + const wrappedHandler = (request: any, reply: IStrictReply) => + route.handler(wrapRequest(request), reply); + + this.server.route({ + config: route.config, + handler: wrappedHandler, + method: route.method, + path: route.path, + }); + } + + public installIndexTemplate(name: string, template: {}) { + return this.callWithInternalUser('indices.putTemplate', { + body: template, + name, + }); + } + + public async callWithInternalUser(esMethod: string, options: {}) { + const { elasticsearch } = this.server.plugins; + const { callWithInternalUser } = elasticsearch.getCluster('admin'); + return await callWithInternalUser(esMethod, options); + } + + public async callWithRequest(req: FrameworkRequest, ...rest: any[]) { + const internalRequest = req[internalFrameworkRequest]; + const { elasticsearch } = internalRequest.server.plugins; + const { callWithRequest } = elasticsearch.getCluster('data'); + const fields = await callWithRequest(internalRequest, ...rest); + return fields; + } +} diff --git a/x-pack/plugins/beats_management/server/utils/adapters/tags/elasticsearch_tags_adapter.ts b/x-pack/plugins/beats_management/server/utils/adapters/tags/elasticsearch_tags_adapter.ts new file mode 100644 index 0000000000000..2293ba77677fd --- /dev/null +++ b/x-pack/plugins/beats_management/server/utils/adapters/tags/elasticsearch_tags_adapter.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { get } from 'lodash'; +import { INDEX_NAMES } from '../../../../common/constants'; +import { + BackendFrameworkAdapter, + BeatTag, + CMTagsAdapter, + FrameworkRequest, +} from '../../lib'; + +export class ElasticsearchTagsAdapter implements CMTagsAdapter { + private framework: BackendFrameworkAdapter; + + constructor(framework: BackendFrameworkAdapter) { + this.framework = framework; + } + + public async getTagsWithIds(req: FrameworkRequest, tagIds: string[]) { + const ids = tagIds.map(tag => `tag:${tag}`); + + // TODO abstract to kibana adapter as the more generic getDocs + const params = { + _source: false, + body: { + ids, + }, + index: INDEX_NAMES.BEATS, + type: '_doc', + }; + const response = await this.framework.callWithRequest(req, 'mget', params); + return get(response, 'docs', []); + } + + public async upsertTag(req: FrameworkRequest, tag: BeatTag) { + const body = { + tag, + type: 'tag', + }; + + const params = { + body, + id: `tag:${tag.id}`, + index: INDEX_NAMES.BEATS, + refresh: 'wait_for', + type: '_doc', + }; + const response = await this.framework.callWithRequest(req, 'index', params); + + // TODO this is not something that works for TS... change this return type + return get(response, 'result'); + } +} diff --git a/x-pack/plugins/beats_management/server/utils/adapters/tokens/elasticsearch_tokens_adapter.ts b/x-pack/plugins/beats_management/server/utils/adapters/tokens/elasticsearch_tokens_adapter.ts new file mode 100644 index 0000000000000..c8969c7ab08d0 --- /dev/null +++ b/x-pack/plugins/beats_management/server/utils/adapters/tokens/elasticsearch_tokens_adapter.ts @@ -0,0 +1,83 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { flatten, get } from 'lodash'; +import { INDEX_NAMES } from '../../../../common/constants'; +import { + BackendFrameworkAdapter, + CMTokensAdapter, + EnrollmentToken, + FrameworkRequest, +} from '../../lib'; + +export class ElasticsearchTokensAdapter implements CMTokensAdapter { + private framework: BackendFrameworkAdapter; + + constructor(framework: BackendFrameworkAdapter) { + this.framework = framework; + } + + public async deleteEnrollmentToken(enrollmentToken: string) { + const params = { + id: `enrollment_token:${enrollmentToken}`, + index: INDEX_NAMES.BEATS, + type: '_doc', + }; + + return this.framework.callWithInternalUser('delete', params); + } + + public async getEnrollmentToken( + tokenString: string + ): Promise { + const params = { + id: `enrollment_token:${tokenString}`, + ignore: [404], + index: INDEX_NAMES.BEATS, + type: '_doc', + }; + + const response = await this.framework.callWithInternalUser('get', params); + const tokenDetails = get( + response, + '_source.enrollment_token', + { + expires_on: '0', + token: null, + } + ); + + // Elasticsearch might return fast if the token is not found. OR it might return fast + // if the token *is* found. Either way, an attacker could using a timing attack to figure + // out whether a token is valid or not. So we introduce a random delay in returning from + // this function to obscure the actual time it took for Elasticsearch to find the token. + const randomDelayInMs = 25 + Math.round(Math.random() * 200); // between 25 and 225 ms + return new Promise(resolve => + setTimeout(() => resolve(tokenDetails), randomDelayInMs) + ); + } + + public async upsertTokens(req: FrameworkRequest, tokens: EnrollmentToken[]) { + const body = flatten( + tokens.map(token => [ + { index: { _id: `enrollment_token:${token.token}` } }, + { + enrollment_token: token, + type: 'enrollment_token', + }, + ]) + ); + + const params = { + body, + index: INDEX_NAMES.BEATS, + refresh: 'wait_for', + type: '_doc', + }; + + await this.framework.callWithRequest(req, 'bulk', params); + } +} diff --git a/x-pack/plugins/beats_management/server/utils/compose/kibana.ts b/x-pack/plugins/beats_management/server/utils/compose/kibana.ts new file mode 100644 index 0000000000000..ff478646aea89 --- /dev/null +++ b/x-pack/plugins/beats_management/server/utils/compose/kibana.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ElasticsearchBeatsAdapter } from '../adapters/beats/elasticsearch_beats_adapter'; +import { ElasticsearchTagsAdapter } from '../adapters/tags/elasticsearch_tags_adapter'; +import { ElasticsearchTokensAdapter } from '../adapters/tokens/elasticsearch_tokens_adapter'; + +import { KibanaBackendFrameworkAdapter } from '../adapters/famework/kibana/kibana_framework_adapter'; + +import { CMBeatsDomain } from '../domains/beats'; +import { CMTagsDomain } from '../domains/tags'; +import { CMTokensDomain } from '../domains/tokens'; + +import { CMDomainLibs, CMServerLibs } from '../lib'; + +import { Server } from 'hapi'; + +export function compose(server: Server): CMServerLibs { + const framework = new KibanaBackendFrameworkAdapter(server); + + const tags = new CMTagsDomain(new ElasticsearchTagsAdapter(framework)); + const tokens = new CMTokensDomain(new ElasticsearchTokensAdapter(framework), { + framework, + }); + const beats = new CMBeatsDomain(new ElasticsearchBeatsAdapter(framework), { + tags, + tokens, + }); + + const domainLibs: CMDomainLibs = { + beats, + tags, + tokens, + }; + + const libs: CMServerLibs = { + framework, + ...domainLibs, + }; + + return libs; +} diff --git a/x-pack/plugins/beats_management/server/utils/domains/beats.ts b/x-pack/plugins/beats_management/server/utils/domains/beats.ts new file mode 100644 index 0000000000000..c0d9ec704e2b1 --- /dev/null +++ b/x-pack/plugins/beats_management/server/utils/domains/beats.ts @@ -0,0 +1,259 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { uniq } from 'lodash'; +import uuid from 'uuid'; +import { findNonExistentItems } from '../../utils/find_non_existent_items'; + +import { + CMAssignmentReturn, + CMBeat, + CMBeatsAdapter, + CMDomainLibs, + CMRemovalReturn, + CMTagAssignment, + FrameworkRequest, +} from '../lib'; + +export class CMBeatsDomain { + private adapter: CMBeatsAdapter; + private tags: CMDomainLibs['tags']; + private tokens: CMDomainLibs['tokens']; + + constructor( + adapter: CMBeatsAdapter, + libs: { tags: CMDomainLibs['tags']; tokens: CMDomainLibs['tokens'] } + ) { + this.adapter = adapter; + this.tags = libs.tags; + this.tokens = libs.tokens; + } + + public async update( + beatId: string, + accessToken: string, + beatData: Partial + ) { + const beat = await this.adapter.get(beatId); + + // TODO make return type enum + if (beat === null) { + return 'beat-not-found'; + } + + const isAccessTokenValid = this.tokens.areTokensEqual( + beat.access_token, + accessToken + ); + if (!isAccessTokenValid) { + return 'invalid-access-token'; + } + const isBeatVerified = beat.hasOwnProperty('verified_on'); + if (!isBeatVerified) { + return 'beat-not-verified'; + } + + await this.adapter.update({ + ...beat, + ...beatData, + }); + } + + // TODO more strongly type this + public async enrollBeat( + beatId: string, + remoteAddress: string, + beat: Partial + ) { + // TODO move this to the token lib + const accessToken = uuid.v4().replace(/-/g, ''); + await this.adapter.insert({ + ...beat, + access_token: accessToken, + host_ip: remoteAddress, + id: beatId, + } as CMBeat); + return { accessToken }; + } + + public async removeTagsFromBeats( + req: FrameworkRequest, + removals: CMTagAssignment[] + ): Promise { + const beatIds = uniq(removals.map(removal => removal.beatId)); + const tagIds = uniq(removals.map(removal => removal.tag)); + + const response = { + removals: removals.map(() => ({ status: null })), + }; + + const beats = await this.adapter.getWithIds(req, beatIds); + const tags = await this.tags.getTagsWithIds(req, tagIds); + + // Handle assignments containing non-existing beat IDs or tags + const nonExistentBeatIds = findNonExistentItems(beats, beatIds); + const nonExistentTags = await findNonExistentItems(tags, tagIds); + + addNonExistentItemToResponse( + response, + removals, + nonExistentBeatIds, + nonExistentTags, + 'removals' + ); + + // TODO abstract this + const validRemovals = removals + .map((removal, idxInRequest) => ({ + beatId: removal.beatId, + idxInRequest, // so we can add the result of this removal to the correct place in the response + tag: removal.tag, + })) + .filter((removal, idx) => response.removals[idx].status === null); + + if (validRemovals.length > 0) { + const removalResults = await this.adapter.removeTagsFromBeats( + req, + validRemovals + ); + return addToResultsToResponse('removals', response, removalResults); + } + return response; + } + + public async getAllBeats(req: FrameworkRequest) { + return await this.adapter.getAll(req); + } + + // TODO cleanup return value, should return a status enum + public async verifyBeats(req: FrameworkRequest, beatIds: string[]) { + const beatsFromEs = await this.adapter.getVerifiedWithIds(req, beatIds); + + const nonExistentBeatIds = beatsFromEs.reduce( + (nonExistentIds: any, beatFromEs: any, idx: any) => { + if (!beatFromEs.found) { + nonExistentIds.push(beatIds[idx]); + } + return nonExistentIds; + }, + [] + ); + + const alreadyVerifiedBeatIds = beatsFromEs + .filter((beat: any) => beat.found) + .filter((beat: any) => beat._source.beat.hasOwnProperty('verified_on')) + .map((beat: any) => beat._source.beat.id); + + const toBeVerifiedBeatIds = beatsFromEs + .filter((beat: any) => beat.found) + .filter((beat: any) => !beat._source.beat.hasOwnProperty('verified_on')) + .map((beat: any) => beat._source.beat.id); + + const verifications = await this.adapter.verifyBeats( + req, + toBeVerifiedBeatIds + ); + return { + alreadyVerifiedBeatIds, + nonExistentBeatIds, + toBeVerifiedBeatIds, + verifications, + }; + } + + public async assignTagsToBeats( + req: FrameworkRequest, + assignments: CMTagAssignment[] + ): Promise { + const beatIds = uniq(assignments.map(assignment => assignment.beatId)); + const tagIds = uniq(assignments.map(assignment => assignment.tag)); + + const response = { + assignments: assignments.map(() => ({ status: null })), + }; + const beats = await this.adapter.getWithIds(req, beatIds); + const tags = await this.tags.getTagsWithIds(req, tagIds); + + // Handle assignments containing non-existing beat IDs or tags + const nonExistentBeatIds = findNonExistentItems(beats, beatIds); + const nonExistentTags = findNonExistentItems(tags, tagIds); + + // TODO break out back into route / function response + // TODO causes function to error if a beat or tag does not exist + addNonExistentItemToResponse( + response, + assignments, + nonExistentBeatIds, + nonExistentTags, + 'assignments' + ); + + // TODO abstract this + const validAssignments = assignments + .map((assignment, idxInRequest) => ({ + beatId: assignment.beatId, + idxInRequest, // so we can add the result of this assignment to the correct place in the response + tag: assignment.tag, + })) + .filter((assignment, idx) => response.assignments[idx].status === null); + + if (validAssignments.length > 0) { + const assignmentResults = await this.adapter.assignTagsToBeats( + req, + validAssignments + ); + + // TODO This should prob not mutate + return addToResultsToResponse('assignments', response, assignmentResults); + } + return response; + } +} + +// TODO abstract to the route, also the key arg is a temp fix +function addNonExistentItemToResponse( + response: any, + assignments: any, + nonExistentBeatIds: any, + nonExistentTags: any, + key: string +) { + assignments.forEach(({ beatId, tag }: CMTagAssignment, idx: any) => { + const isBeatNonExistent = nonExistentBeatIds.includes(beatId); + const isTagNonExistent = nonExistentTags.includes(tag); + + if (isBeatNonExistent && isTagNonExistent) { + response[key][idx].status = 404; + response[key][idx].result = `Beat ${beatId} and tag ${tag} not found`; + } else if (isBeatNonExistent) { + response[key][idx].status = 404; + response[key][idx].result = `Beat ${beatId} not found`; + } else if (isTagNonExistent) { + response[key][idx].status = 404; + response[key][idx].result = `Tag ${tag} not found`; + } + }); +} + +// TODO dont mutate response +function addToResultsToResponse( + key: string, + response: any, + assignmentResults: any +) { + assignmentResults.forEach((assignmentResult: any) => { + const { idxInRequest, status, result } = assignmentResult; + response[key][idxInRequest].status = status; + response[key][idxInRequest].result = result; + }); + return response; +} diff --git a/x-pack/plugins/beats_management/server/utils/domains/tags.ts b/x-pack/plugins/beats_management/server/utils/domains/tags.ts new file mode 100644 index 0000000000000..43bb8dfed15a1 --- /dev/null +++ b/x-pack/plugins/beats_management/server/utils/domains/tags.ts @@ -0,0 +1,90 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { intersection, uniq, values } from 'lodash'; +import { UNIQUENESS_ENFORCING_TYPES } from '../../../common/constants'; +import { CMTagsAdapter, ConfigurationBlock, FrameworkRequest } from '../lib'; +import { entries } from './../../utils/polyfills'; + +export class CMTagsDomain { + private adapter: CMTagsAdapter; + constructor(adapter: CMTagsAdapter) { + this.adapter = adapter; + } + + public async getTagsWithIds(req: FrameworkRequest, tagIds: string[]) { + return await this.adapter.getTagsWithIds(req, tagIds); + } + + public async saveTag( + req: FrameworkRequest, + tagId: string, + configs: ConfigurationBlock[] + ) { + const { isValid, message } = await this.validateConfigurationBlocks( + configs + ); + if (!isValid) { + return { isValid, result: message }; + } + + const tag = { + configuration_blocks: configs, + id: tagId, + }; + return { + isValid: true, + result: await this.adapter.upsertTag(req, tag), + }; + } + + private validateConfigurationBlocks(configurationBlocks: any) { + const types = uniq(configurationBlocks.map((block: any) => block.type)); + + // If none of the types in the given configuration blocks are uniqueness-enforcing, + // we don't need to perform any further validation checks. + const uniquenessEnforcingTypes = intersection( + types, + UNIQUENESS_ENFORCING_TYPES + ); + if (uniquenessEnforcingTypes.length === 0) { + return { isValid: true }; + } + + // Count the number of uniqueness-enforcing types in the given configuration blocks + const typeCountMap = configurationBlocks.reduce((map: any, block: any) => { + const { type } = block; + if (!uniquenessEnforcingTypes.includes(type)) { + return map; + } + + const count = map[type] || 0; + return { + ...map, + [type]: count + 1, + }; + }, {}); + + // If there is no more than one of any uniqueness-enforcing types in the given + // configuration blocks, we don't need to perform any further validation checks. + if (values(typeCountMap).filter(count => count > 1).length === 0) { + return { isValid: true }; + } + + const message = entries(typeCountMap) + .filter(([, count]) => count > 1) + .map( + ([type, count]) => + `Expected only one configuration block of type '${type}' but found ${count}` + ) + .join(' '); + + return { + isValid: false, + message, + }; + } +} diff --git a/x-pack/plugins/beats_management/server/utils/domains/tokens.ts b/x-pack/plugins/beats_management/server/utils/domains/tokens.ts new file mode 100644 index 0000000000000..6e55d78ecdcc8 --- /dev/null +++ b/x-pack/plugins/beats_management/server/utils/domains/tokens.ts @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { timingSafeEqual } from 'crypto'; +import moment from 'moment'; +import uuid from 'uuid'; +import { CMTokensAdapter, FrameworkRequest } from '../lib'; +import { BackendFrameworkAdapter } from '../lib'; + +const RANDOM_TOKEN_1 = 'b48c4bda384a40cb91c6eb9b8849e77f'; +const RANDOM_TOKEN_2 = '80a3819e3cd64f4399f1d4886be7a08b'; + +export class CMTokensDomain { + private adapter: CMTokensAdapter; + private framework: BackendFrameworkAdapter; + + constructor( + adapter: CMTokensAdapter, + libs: { framework: BackendFrameworkAdapter } + ) { + this.adapter = adapter; + this.framework = libs.framework; + } + + public async getEnrollmentToken(enrollmentToken: string) { + return await this.adapter.getEnrollmentToken(enrollmentToken); + } + + public async deleteEnrollmentToken(enrollmentToken: string) { + return await this.adapter.deleteEnrollmentToken(enrollmentToken); + } + + public areTokensEqual(token1: string, token2: string) { + if ( + typeof token1 !== 'string' || + typeof token2 !== 'string' || + token1.length !== token2.length + ) { + // This prevents a more subtle timing attack where we know already the tokens aren't going to + // match but still we don't return fast. Instead we compare two pre-generated random tokens using + // the same comparison algorithm that we would use to compare two equal-length tokens. + return timingSafeEqual( + Buffer.from(RANDOM_TOKEN_1, 'utf8'), + Buffer.from(RANDOM_TOKEN_2, 'utf8') + ); + } + + return timingSafeEqual( + Buffer.from(token1, 'utf8'), + Buffer.from(token2, 'utf8') + ); + } + + public async createEnrollmentTokens( + req: FrameworkRequest, + numTokens: number = 1 + ): Promise { + const tokens = []; + const enrollmentTokensTtlInSeconds = this.framework.getSetting( + 'xpack.beats.enrollmentTokensTtlInSeconds' + ); + const enrollmentTokenExpiration = moment() + .add(enrollmentTokensTtlInSeconds, 'seconds') + .toJSON(); + + while (tokens.length < numTokens) { + tokens.push({ + expires_on: enrollmentTokenExpiration, + token: uuid.v4().replace(/-/g, ''), + }); + } + + await this.adapter.upsertTokens(req, tokens); + + return tokens.map(token => token.token); + } +} diff --git a/x-pack/plugins/beats_management/server/utils/index_templates/beats_template.json b/x-pack/plugins/beats_management/server/utils/index_templates/beats_template.json index 0442c31fd7c2d..1bbe65b70e67d 100644 --- a/x-pack/plugins/beats_management/server/utils/index_templates/beats_template.json +++ b/x-pack/plugins/beats_management/server/utils/index_templates/beats_template.json @@ -33,6 +33,9 @@ "color": { "type": "keyword" }, + "last_updated": { + "type": "date" + }, "configuration_blocks": { "type": "nested", "properties": { diff --git a/x-pack/plugins/beats_management/server/utils/lib.ts b/x-pack/plugins/beats_management/server/utils/lib.ts new file mode 100644 index 0000000000000..37d0a989e4cf5 --- /dev/null +++ b/x-pack/plugins/beats_management/server/utils/lib.ts @@ -0,0 +1,212 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IRouteAdditionalConfigurationOptions, IStrictReply } from 'hapi'; +import { internalFrameworkRequest } from '../utils/wrap_request'; +import { CMBeatsDomain } from './domains/beats'; +import { CMTagsDomain } from './domains/tags'; +import { CMTokensDomain } from './domains/tokens'; + +import { ConfigurationBlockTypes } from '../../common/constants'; + +export interface CMDomainLibs { + beats: CMBeatsDomain; + tags: CMTagsDomain; + tokens: CMTokensDomain; +} + +export interface CMServerLibs extends CMDomainLibs { + framework: BackendFrameworkAdapter; +} + +interface CMReturnedTagAssignment { + status: number | null; + result?: string; +} + +export interface CMAssignmentReturn { + assignments: CMReturnedTagAssignment[]; +} + +export interface CMRemovalReturn { + removals: CMReturnedTagAssignment[]; +} + +export interface ConfigurationBlock { + type: ConfigurationBlockTypes; + block_yml: string; +} + +export interface CMBeat { + id: string; + access_token: string; + verified_on: string; + type: string; + version: string; + host_ip: string; + host_name: string; + ephemeral_id: string; + local_configuration_yml: string; + tags: string; + central_configuration_yml: string; + metadata: {}; +} + +export interface BeatTag { + id: string; + configuration_blocks: ConfigurationBlock[]; +} + +export interface EnrollmentToken { + token: string | null; + expires_on: string; +} + +export interface CMTokensAdapter { + deleteEnrollmentToken(enrollmentToken: string): Promise; + getEnrollmentToken(enrollmentToken: string): Promise; + upsertTokens(req: FrameworkRequest, tokens: EnrollmentToken[]): Promise; +} + +// FIXME: fix getTagsWithIds return type +export interface CMTagsAdapter { + getTagsWithIds(req: FrameworkRequest, tagIds: string[]): any; + upsertTag(req: FrameworkRequest, tag: BeatTag): Promise<{}>; +} + +// FIXME: fix getBeatsWithIds return type +export interface CMBeatsAdapter { + insert(beat: CMBeat): Promise; + update(beat: CMBeat): Promise; + get(id: string): any; + getAll(req: FrameworkRequest): any; + getWithIds(req: FrameworkRequest, beatIds: string[]): any; + getVerifiedWithIds(req: FrameworkRequest, beatIds: string[]): any; + verifyBeats(req: FrameworkRequest, beatIds: string[]): any; + removeTagsFromBeats( + req: FrameworkRequest, + removals: CMTagAssignment[] + ): Promise; + assignTagsToBeats( + req: FrameworkRequest, + assignments: CMTagAssignment[] + ): Promise; +} + +export interface CMTagAssignment { + beatId: string; + tag: string; + idxInRequest?: number; +} + +/** + * The following are generic types, sharable between projects + */ + +export interface BackendFrameworkAdapter { + version: string; + getSetting(settingPath: string): string | number; + exposeStaticDir(urlPath: string, dir: string): void; + installIndexTemplate(name: string, template: {}): void; + registerRoute( + route: FrameworkRouteOptions + ): void; + callWithInternalUser(esMethod: string, options: {}): Promise; + callWithRequest( + req: FrameworkRequest, + method: 'search', + options?: object + ): Promise>; + callWithRequest( + req: FrameworkRequest, + method: 'fieldCaps', + options?: object + ): Promise; + callWithRequest( + req: FrameworkRequest, + method: string, + options?: object + ): Promise; +} + +interface DatabaseFieldCapsResponse extends DatabaseResponse { + fields: FieldsResponse; +} + +export interface FieldsResponse { + [name: string]: FieldDef; +} + +export interface FieldDetails { + searchable: boolean; + aggregatable: boolean; + type: string; +} + +export interface FieldDef { + [type: string]: FieldDetails; +} + +export interface FrameworkRequest< + InternalRequest extends WrappableRequest = WrappableRequest +> { + [internalFrameworkRequest]: InternalRequest; + headers: InternalRequest['headers']; + info: InternalRequest['info']; + payload: InternalRequest['payload']; + params: InternalRequest['params']; + query: InternalRequest['query']; +} + +export interface FrameworkRouteOptions< + RouteRequest extends WrappableRequest, + RouteResponse +> { + path: string; + method: string | string[]; + vhost?: string; + handler: FrameworkRouteHandler; + config?: Pick< + IRouteAdditionalConfigurationOptions, + Exclude + >; +} + +export type FrameworkRouteHandler< + RouteRequest extends WrappableRequest, + RouteResponse +> = ( + request: FrameworkRequest, + reply: IStrictReply +) => void; + +export interface WrappableRequest< + Payload = any, + Params = any, + Query = any, + Headers = any, + Info = any +> { + headers: Headers; + info: Info; + payload: Payload; + params: Params; + query: Query; +} + +interface DatabaseResponse { + took: number; + timeout: boolean; +} + +interface DatabaseSearchResponse + extends DatabaseResponse { + aggregations?: Aggregations; + hits: { + total: number; + hits: Hit[]; + }; +} diff --git a/x-pack/plugins/grokdebugger/common/constants/index.js b/x-pack/plugins/grokdebugger/common/constants/index.js deleted file mode 100644 index 12e440d7ed858..0000000000000 --- a/x-pack/plugins/grokdebugger/common/constants/index.js +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { ROUTES } from './routes'; -export { PLUGIN } from './plugin'; -export { EDITOR } from './editor'; diff --git a/x-pack/plugins/index_management/server/lib/call_with_request_factory/call_with_request_factory.js b/x-pack/plugins/index_management/server/lib/call_with_request_factory/call_with_request_factory.js deleted file mode 100644 index b9a77a1a0362b..0000000000000 --- a/x-pack/plugins/index_management/server/lib/call_with_request_factory/call_with_request_factory.js +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { once } from 'lodash'; - -const callWithRequest = once((server) => { - const cluster = server.plugins.elasticsearch.getCluster('data'); - return cluster.callWithRequest; -}); - -export const callWithRequestFactory = (server, request) => { - return (...args) => { - return callWithRequest(server)(request, ...args); - }; -}; diff --git a/x-pack/plugins/logstash/common/constants/configuration_blocks.ts b/x-pack/plugins/logstash/common/constants/configuration_blocks.ts new file mode 100644 index 0000000000000..e89e53e25b89d --- /dev/null +++ b/x-pack/plugins/logstash/common/constants/configuration_blocks.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export enum ConfigurationBlockTypes { + FilebeatInputs = 'filebeat.inputs', + FilebeatModules = 'filebeat.modules', + MetricbeatModules = 'metricbeat.modules', + Output = 'output', + Processors = 'processors', +} + +export const UNIQUENESS_ENFORCING_TYPES = [ConfigurationBlockTypes.Output]; diff --git a/x-pack/plugins/logstash/server/kibana.index.ts b/x-pack/plugins/logstash/server/kibana.index.ts new file mode 100644 index 0000000000000..c9bc9b8bf02f4 --- /dev/null +++ b/x-pack/plugins/logstash/server/kibana.index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Server } from 'hapi'; +import { compose } from './lib/compose/kibana'; +import { initManagementServer } from './management_server'; + +export const initServerWithKibana = (hapiServer: Server) => { + const libs = compose(hapiServer); + initManagementServer(libs); +}; diff --git a/x-pack/plugins/logstash/server/management_server.ts b/x-pack/plugins/logstash/server/management_server.ts new file mode 100644 index 0000000000000..ed0917eda8ced --- /dev/null +++ b/x-pack/plugins/logstash/server/management_server.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CMServerLibs } from './lib/lib'; +import { createBeatEnrollmentRoute } from './rest_api/beats/enroll'; +import { createListAgentsRoute } from './rest_api/beats/list'; +import { createTagAssignmentsRoute } from './rest_api/beats/tag_assignment'; +import { createTagRemovalsRoute } from './rest_api/beats/tag_removal'; +import { createBeatUpdateRoute } from './rest_api/beats/update'; +import { createBeatVerificationRoute } from './rest_api/beats/verify'; +import { createSetTagRoute } from './rest_api/tags/set'; +import { createTokensRoute } from './rest_api/tokens/create'; + +import { beatsIndexTemplate } from './utils/index_templates'; + +export const initManagementServer = (libs: CMServerLibs) => { + libs.framework.installIndexTemplate('beats-template', beatsIndexTemplate); + + libs.framework.registerRoute(createTagAssignmentsRoute(libs)); + libs.framework.registerRoute(createListAgentsRoute(libs)); + libs.framework.registerRoute(createTagRemovalsRoute(libs)); + libs.framework.registerRoute(createBeatEnrollmentRoute(libs)); + libs.framework.registerRoute(createSetTagRoute(libs)); + libs.framework.registerRoute(createTokensRoute(libs)); + libs.framework.registerRoute(createBeatVerificationRoute(libs)); + libs.framework.registerRoute(createBeatUpdateRoute(libs)); +}; diff --git a/x-pack/plugins/logstash/server/rest_api/beats/enroll.ts b/x-pack/plugins/logstash/server/rest_api/beats/enroll.ts new file mode 100644 index 0000000000000..fe154592564ae --- /dev/null +++ b/x-pack/plugins/logstash/server/rest_api/beats/enroll.ts @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Joi from 'joi'; +import { omit } from 'lodash'; +import moment from 'moment'; +import { CMServerLibs } from '../../lib/lib'; +import { wrapEsError } from '../../utils/error_wrappers'; + +// TODO: add license check pre-hook +// TODO: write to Kibana audit log file +export const createBeatEnrollmentRoute = (libs: CMServerLibs) => ({ + config: { + auth: false, + validate: { + headers: Joi.object({ + 'kbn-beats-enrollment-token': Joi.string().required(), + }).options({ + allowUnknown: true, + }), + payload: Joi.object({ + host_name: Joi.string().required(), + type: Joi.string().required(), + version: Joi.string().required(), + }).required(), + }, + }, + handler: async (request: any, reply: any) => { + const { beatId } = request.params; + const enrollmentToken = request.headers['kbn-beats-enrollment-token']; + + try { + const { + token, + expires_on: expiresOn, + } = await libs.tokens.getEnrollmentToken(enrollmentToken); + + if (!token) { + return reply({ message: 'Invalid enrollment token' }).code(400); + } + if (moment(expiresOn).isBefore(moment())) { + return reply({ message: 'Expired enrollment token' }).code(400); + } + const { accessToken } = await libs.beats.enrollBeat( + beatId, + request.info.remoteAddress, + omit(request.payload, 'enrollment_token') + ); + + await libs.tokens.deleteEnrollmentToken(enrollmentToken); + + reply({ access_token: accessToken }).code(201); + } catch (err) { + // TODO move this to kibana route thing in adapter + return reply(wrapEsError(err)); + } + }, + method: 'POST', + path: '/api/beats/agent/{beatId}', +}); diff --git a/x-pack/plugins/logstash/server/rest_api/beats/list.ts b/x-pack/plugins/logstash/server/rest_api/beats/list.ts new file mode 100644 index 0000000000000..8263d1c0ff63f --- /dev/null +++ b/x-pack/plugins/logstash/server/rest_api/beats/list.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CMServerLibs } from '../../lib/lib'; +import { wrapEsError } from '../../utils/error_wrappers'; + +// TODO: add license check pre-hook +export const createListAgentsRoute = (libs: CMServerLibs) => ({ + handler: async (request: any, reply: any) => { + try { + const beats = await libs.beats.getAllBeats(request); + reply({ beats }); + } catch (err) { + // TODO move this to kibana route thing in adapter + return reply(wrapEsError(err)); + } + }, + method: 'GET', + path: '/api/beats/agents', +}); diff --git a/x-pack/plugins/logstash/server/rest_api/beats/tag_assignment.ts b/x-pack/plugins/logstash/server/rest_api/beats/tag_assignment.ts new file mode 100644 index 0000000000000..d06c016ce6d12 --- /dev/null +++ b/x-pack/plugins/logstash/server/rest_api/beats/tag_assignment.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Joi from 'joi'; +import { CMServerLibs } from '../../lib/lib'; +import { wrapEsError } from '../../utils/error_wrappers'; + +// TODO: add license check pre-hook +// TODO: write to Kibana audit log file +export const createTagAssignmentsRoute = (libs: CMServerLibs) => ({ + config: { + validate: { + payload: Joi.object({ + assignments: Joi.array().items( + Joi.object({ + beat_id: Joi.string().required(), + tag: Joi.string().required(), + }) + ), + }).required(), + }, + }, + handler: async (request: any, reply: any) => { + const { assignments } = request.payload; + + // TODO abstract or change API to keep beatId consistent + const tweakedAssignments = assignments.map((assignment: any) => ({ + beatId: assignment.beat_id, + tag: assignment.tag, + })); + + try { + const response = await libs.beats.assignTagsToBeats( + request, + tweakedAssignments + ); + reply(response); + } catch (err) { + // TODO move this to kibana route thing in adapter + return reply(wrapEsError(err)); + } + }, + method: 'POST', + path: '/api/beats/agents_tags/assignments', +}); diff --git a/x-pack/plugins/logstash/server/rest_api/beats/tag_removal.ts b/x-pack/plugins/logstash/server/rest_api/beats/tag_removal.ts new file mode 100644 index 0000000000000..4da33dbd50cfc --- /dev/null +++ b/x-pack/plugins/logstash/server/rest_api/beats/tag_removal.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Joi from 'joi'; +import { CMServerLibs } from '../../lib/lib'; +import { wrapEsError } from '../../utils/error_wrappers'; + +// TODO: add license check pre-hook +// TODO: write to Kibana audit log file +export const createTagRemovalsRoute = (libs: CMServerLibs) => ({ + config: { + validate: { + payload: Joi.object({ + removals: Joi.array().items( + Joi.object({ + beat_id: Joi.string().required(), + tag: Joi.string().required(), + }) + ), + }).required(), + }, + }, + handler: async (request: any, reply: any) => { + const { removals } = request.payload; + + // TODO abstract or change API to keep beatId consistent + const tweakedRemovals = removals.map((removal: any) => ({ + beatId: removal.beat_id, + tag: removal.tag, + })); + + try { + const response = await libs.beats.removeTagsFromBeats( + request, + tweakedRemovals + ); + reply(response); + } catch (err) { + // TODO move this to kibana route thing in adapter + return reply(wrapEsError(err)); + } + }, + method: 'POST', + path: '/api/beats/agents_tags/removals', +}); diff --git a/x-pack/plugins/logstash/server/rest_api/beats/update.ts b/x-pack/plugins/logstash/server/rest_api/beats/update.ts new file mode 100644 index 0000000000000..41d403399d45f --- /dev/null +++ b/x-pack/plugins/logstash/server/rest_api/beats/update.ts @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Joi from 'joi'; +import { CMServerLibs } from '../../lib/lib'; +import { wrapEsError } from '../../utils/error_wrappers'; + +// TODO: add license check pre-hook +// TODO: write to Kibana audit log file (include who did the verification as well) +export const createBeatUpdateRoute = (libs: CMServerLibs) => ({ + config: { + auth: false, + validate: { + headers: Joi.object({ + 'kbn-beats-access-token': Joi.string().required(), + }).options({ + allowUnknown: true, + }), + params: Joi.object({ + beatId: Joi.string(), + }), + payload: Joi.object({ + ephemeral_id: Joi.string(), + host_name: Joi.string(), + local_configuration_yml: Joi.string(), + metadata: Joi.object(), + type: Joi.string(), + version: Joi.string(), + }).required(), + }, + }, + handler: async (request: any, reply: any) => { + const { beatId } = request.params; + const accessToken = request.headers['kbn-beats-access-token']; + const remoteAddress = request.info.remoteAddress; + + try { + const status = await libs.beats.update(beatId, accessToken, { + ...request.payload, + host_ip: remoteAddress, + }); + + switch (status) { + case 'beat-not-found': + return reply({ message: 'Beat not found' }).code(404); + case 'invalid-access-token': + return reply({ message: 'Invalid access token' }).code(401); + case 'beat-not-verified': + return reply({ message: 'Beat has not been verified' }).code(400); + } + + reply().code(204); + } catch (err) { + return reply(wrapEsError(err)); + } + }, + method: 'PUT', + path: '/api/beats/agent/{beatId}', +}); diff --git a/x-pack/plugins/logstash/server/rest_api/beats/verify.ts b/x-pack/plugins/logstash/server/rest_api/beats/verify.ts new file mode 100644 index 0000000000000..866fa77d0c337 --- /dev/null +++ b/x-pack/plugins/logstash/server/rest_api/beats/verify.ts @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Joi from 'joi'; +import { CMServerLibs } from '../../lib/lib'; +import { wrapEsError } from '../../utils/error_wrappers'; + +// TODO: add license check pre-hook +// TODO: write to Kibana audit log file +export const createBeatVerificationRoute = (libs: CMServerLibs) => ({ + config: { + auth: false, + validate: { + payload: Joi.object({ + beats: Joi.array() + .items({ + id: Joi.string().required(), + }) + .min(1), + }).required(), + }, + }, + handler: async (request: any, reply: any) => { + const beats = [...request.payload.beats]; + const beatIds = beats.map(beat => beat.id); + + try { + const { + verifications, + alreadyVerifiedBeatIds, + toBeVerifiedBeatIds, + nonExistentBeatIds, + } = await libs.beats.verifyBeats(request, beatIds); + + const verifiedBeatIds = verifications.reduce( + (verifiedBeatList: any, verification: any, idx: any) => { + if (verification.update.status === 200) { + verifiedBeatList.push(toBeVerifiedBeatIds[idx]); + } + return verifiedBeatList; + }, + [] + ); + + // TODO calculation of status should be done in-lib, w/switch statement here + beats.forEach(beat => { + if (nonExistentBeatIds.includes(beat.id)) { + beat.status = 404; + beat.result = 'not found'; + } else if (alreadyVerifiedBeatIds.includes(beat.id)) { + beat.status = 200; + beat.result = 'already verified'; + } else if (verifiedBeatIds.includes(beat.id)) { + beat.status = 200; + beat.result = 'verified'; + } else { + beat.status = 400; + beat.result = 'not verified'; + } + }); + + const response = { beats }; + reply(response); + } catch (err) { + return reply(wrapEsError(err)); + } + }, + method: 'POST', + path: '/api/beats/agents/verify', +}); diff --git a/x-pack/plugins/logstash/server/rest_api/tags/set.ts b/x-pack/plugins/logstash/server/rest_api/tags/set.ts new file mode 100644 index 0000000000000..3f7e579bd91ae --- /dev/null +++ b/x-pack/plugins/logstash/server/rest_api/tags/set.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Joi from 'joi'; +import { get, values } from 'lodash'; +import { ConfigurationBlockTypes } from '../../../common/constants'; +import { CMServerLibs } from '../../lib/lib'; +import { wrapEsError } from '../../utils/error_wrappers'; + +// TODO: add license check pre-hook +// TODO: write to Kibana audit log file +export const createSetTagRoute = (libs: CMServerLibs) => ({ + config: { + validate: { + params: Joi.object({ + tag: Joi.string(), + }), + payload: Joi.object({ + configuration_blocks: Joi.array().items( + Joi.object({ + block_yml: Joi.string().required(), + type: Joi.string() + .only(values(ConfigurationBlockTypes)) + .required(), + }) + ), + }).allow(null), + }, + }, + handler: async (request: any, reply: any) => { + const configurationBlocks = get( + request, + 'payload.configuration_blocks', + [] + ); + try { + const { isValid, result } = await libs.tags.saveTag( + request, + request.params.tag, + configurationBlocks + ); + if (!isValid) { + return reply({ result }).code(400); + } + + reply().code(result === 'created' ? 201 : 200); + } catch (err) { + // TODO move this to kibana route thing in adapter + return reply(wrapEsError(err)); + } + }, + method: 'PUT', + path: '/api/beats/tag/{tag}', +}); diff --git a/x-pack/plugins/logstash/server/rest_api/tokens/create.ts b/x-pack/plugins/logstash/server/rest_api/tokens/create.ts new file mode 100644 index 0000000000000..b4f3e2c1a6246 --- /dev/null +++ b/x-pack/plugins/logstash/server/rest_api/tokens/create.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Joi from 'joi'; +import { get } from 'lodash'; +import { CMServerLibs } from '../../lib/lib'; +import { wrapEsError } from '../../utils/error_wrappers'; + +// TODO: add license check pre-hook +// TODO: write to Kibana audit log file +const DEFAULT_NUM_TOKENS = 1; +export const createTokensRoute = (libs: CMServerLibs) => ({ + config: { + validate: { + payload: Joi.object({ + num_tokens: Joi.number() + .optional() + .default(DEFAULT_NUM_TOKENS) + .min(1), + }).allow(null), + }, + }, + handler: async (request: any, reply: any) => { + const numTokens = get(request, 'payload.num_tokens', DEFAULT_NUM_TOKENS); + + try { + const tokens = await libs.tokens.createEnrollmentTokens( + request, + numTokens + ); + reply({ tokens }); + } catch (err) { + // TODO move this to kibana route thing in adapter + return reply(wrapEsError(err)); + } + }, + method: 'POST', + path: '/api/beats/enrollment_tokens', +}); diff --git a/x-pack/plugins/logstash/server/utils/README.md b/x-pack/plugins/logstash/server/utils/README.md new file mode 100644 index 0000000000000..8a6a27aa29867 --- /dev/null +++ b/x-pack/plugins/logstash/server/utils/README.md @@ -0,0 +1 @@ +Utils should be data processing functions and other tools.... all in all utils is basicly everything that is not an adaptor, or presenter and yet too much to put in a lib. \ No newline at end of file diff --git a/x-pack/plugins/logstash/server/utils/find_non_existent_items.ts b/x-pack/plugins/logstash/server/utils/find_non_existent_items.ts new file mode 100644 index 0000000000000..53e4066acc879 --- /dev/null +++ b/x-pack/plugins/logstash/server/utils/find_non_existent_items.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export function findNonExistentItems(items: any, requestedItems: any) { + return items.reduce((nonExistentItems: any, item: any, idx: any) => { + if (!item.found) { + nonExistentItems.push(requestedItems[idx]); + } + return nonExistentItems; + }, []); +} diff --git a/x-pack/plugins/logstash/common/constants/index_names.js b/x-pack/plugins/logstash/server/utils/index_templates/index.ts similarity index 73% rename from x-pack/plugins/logstash/common/constants/index_names.js rename to x-pack/plugins/logstash/server/utils/index_templates/index.ts index 0f6946f407c58..eeaef7a68d49f 100644 --- a/x-pack/plugins/logstash/common/constants/index_names.js +++ b/x-pack/plugins/logstash/server/utils/index_templates/index.ts @@ -4,6 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ -export const INDEX_NAMES = { - PIPELINES: '.logstash', -}; +import beatsIndexTemplate from './beats_template.json'; +export { beatsIndexTemplate }; diff --git a/x-pack/plugins/logstash/server/utils/polyfills.ts b/x-pack/plugins/logstash/server/utils/polyfills.ts new file mode 100644 index 0000000000000..5291e2c72be7d --- /dev/null +++ b/x-pack/plugins/logstash/server/utils/polyfills.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const entries = (obj: any) => { + const ownProps = Object.keys(obj); + let i = ownProps.length; + const resArray = new Array(i); // preallocate the Array + + while (i--) { + resArray[i] = [ownProps[i], obj[ownProps[i]]]; + } + + return resArray; +}; diff --git a/x-pack/plugins/logstash/server/utils/wrap_request.ts b/x-pack/plugins/logstash/server/utils/wrap_request.ts new file mode 100644 index 0000000000000..a29f9055f3688 --- /dev/null +++ b/x-pack/plugins/logstash/server/utils/wrap_request.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FrameworkRequest, WrappableRequest } from '../lib/lib'; + +export const internalFrameworkRequest = Symbol('internalFrameworkRequest'); + +export function wrapRequest( + req: InternalRequest +): FrameworkRequest { + const { params, payload, query, headers, info } = req; + + return { + [internalFrameworkRequest]: req, + headers, + info, + params, + payload, + query, + }; +} diff --git a/x-pack/plugins/logstash/tsconfig.json b/x-pack/plugins/logstash/tsconfig.json new file mode 100644 index 0000000000000..4082f16a5d91c --- /dev/null +++ b/x-pack/plugins/logstash/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tsconfig.json" +} diff --git a/x-pack/plugins/logstash/wallaby.js b/x-pack/plugins/logstash/wallaby.js new file mode 100644 index 0000000000000..c20488d35cfb6 --- /dev/null +++ b/x-pack/plugins/logstash/wallaby.js @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +module.exports = function (wallaby) { + return { + debug: true, + files: [ + '../../tsconfig.json', + //'plugins/beats/public/**/*.+(js|jsx|ts|tsx|json|snap|css|less|sass|scss|jpg|jpeg|gif|png|svg)', + 'server/**/*.+(js|jsx|ts|tsx|json|snap|css|less|sass|scss|jpg|jpeg|gif|png|svg)', + 'common/**/*.+(js|jsx|ts|tsx|json|snap|css|less|sass|scss|jpg|jpeg|gif|png|svg)', + ], + + tests: ['**/*.test.ts'], + env: { + type: 'node', + runner: 'node', + }, + testFramework: 'jest', + compilers: { + '**/*.ts?(x)': wallaby.compilers.typeScript({ module: 'commonjs' }), + }, + }; +}; diff --git a/x-pack/plugins/reporting/server/browsers/chromium/driver/screenshot_stitcher/index.test.ts b/x-pack/plugins/reporting/server/browsers/chromium/driver/screenshot_stitcher/index.test.ts index b519a0f6363a5..2fb34a026a502 100644 --- a/x-pack/plugins/reporting/server/browsers/chromium/driver/screenshot_stitcher/index.test.ts +++ b/x-pack/plugins/reporting/server/browsers/chromium/driver/screenshot_stitcher/index.test.ts @@ -8,7 +8,7 @@ import { promisify } from 'bluebird'; import fs from 'fs'; import path from 'path'; -import { screenshotStitcher } from './index'; +import { screenshotStitcher } from '.'; const loggerMock = { debug: () => { diff --git a/x-pack/test/api_integration/apis/beats/remove_tags_from_beats.js b/x-pack/test/api_integration/apis/beats/remove_tags_from_beats.js index 19583f9279732..95030a63ac749 100644 --- a/x-pack/test/api_integration/apis/beats/remove_tags_from_beats.js +++ b/x-pack/test/api_integration/apis/beats/remove_tags_from_beats.js @@ -5,10 +5,7 @@ */ import expect from 'expect.js'; -import { - ES_INDEX_NAME, - ES_TYPE_NAME -} from './constants'; +import { ES_INDEX_NAME, ES_TYPE_NAME } from './constants'; export default function ({ getService }) { const supertest = getService('supertest'); @@ -24,25 +21,19 @@ export default function ({ getService }) { it('should remove a single tag from a single beat', async () => { const { body: apiResponse } = await supertest - .post( - '/api/beats/agents_tags/removals' - ) + .post('/api/beats/agents_tags/removals') .set('kbn-xsrf', 'xxx') .send({ - removals: [ - { beat_id: 'foo', tag: 'production' } - ] + removals: [{ beatId: 'foo', tag: 'production' }], }) .expect(200); - expect(apiResponse.removals).to.eql([ - { status: 200, result: 'updated' } - ]); + expect(apiResponse.removals).to.eql([{ status: 200, result: 'updated' }]); const esResponse = await es.get({ index: ES_INDEX_NAME, type: ES_TYPE_NAME, - id: `beat:foo` + id: `beat:foo`, }); const beat = esResponse._source.beat; @@ -51,21 +42,16 @@ export default function ({ getService }) { it('should remove a single tag from a multiple beats', async () => { const { body: apiResponse } = await supertest - .post( - '/api/beats/agents_tags/removals' - ) + .post('/api/beats/agents_tags/removals') .set('kbn-xsrf', 'xxx') .send({ - removals: [ - { beat_id: 'foo', tag: 'development' }, - { beat_id: 'bar', tag: 'development' } - ] + removals: [{ beatId: 'foo', tag: 'development' }, { beatId: 'bar', tag: 'development' }], }) .expect(200); expect(apiResponse.removals).to.eql([ { status: 200, result: 'updated' }, - { status: 200, result: 'updated' } + { status: 200, result: 'updated' }, ]); let esResponse; @@ -75,17 +61,17 @@ export default function ({ getService }) { esResponse = await es.get({ index: ES_INDEX_NAME, type: ES_TYPE_NAME, - id: `beat:foo` + id: `beat:foo`, }); beat = esResponse._source.beat; - expect(beat.tags).to.eql(['production', 'qa' ]); // as beat 'foo' already had 'production' and 'qa' tags attached to it + expect(beat.tags).to.eql(['production', 'qa']); // as beat 'foo' already had 'production' and 'qa' tags attached to it // Beat bar esResponse = await es.get({ index: ES_INDEX_NAME, type: ES_TYPE_NAME, - id: `beat:bar` + id: `beat:bar`, }); beat = esResponse._source.beat; @@ -94,27 +80,22 @@ export default function ({ getService }) { it('should remove multiple tags from a single beat', async () => { const { body: apiResponse } = await supertest - .post( - '/api/beats/agents_tags/removals' - ) + .post('/api/beats/agents_tags/removals') .set('kbn-xsrf', 'xxx') .send({ - removals: [ - { beat_id: 'foo', tag: 'development' }, - { beat_id: 'foo', tag: 'production' } - ] + removals: [{ beatId: 'foo', tag: 'development' }, { beatId: 'foo', tag: 'production' }], }) .expect(200); expect(apiResponse.removals).to.eql([ { status: 200, result: 'updated' }, - { status: 200, result: 'updated' } + { status: 200, result: 'updated' }, ]); const esResponse = await es.get({ index: ES_INDEX_NAME, type: ES_TYPE_NAME, - id: `beat:foo` + id: `beat:foo`, }); const beat = esResponse._source.beat; @@ -123,21 +104,16 @@ export default function ({ getService }) { it('should remove multiple tags from a multiple beats', async () => { const { body: apiResponse } = await supertest - .post( - '/api/beats/agents_tags/removals' - ) + .post('/api/beats/agents_tags/removals') .set('kbn-xsrf', 'xxx') .send({ - removals: [ - { beat_id: 'foo', tag: 'production' }, - { beat_id: 'bar', tag: 'development' } - ] + removals: [{ beatId: 'foo', tag: 'production' }, { beatId: 'bar', tag: 'development' }], }) .expect(200); expect(apiResponse.removals).to.eql([ { status: 200, result: 'updated' }, - { status: 200, result: 'updated' } + { status: 200, result: 'updated' }, ]); let esResponse; @@ -147,7 +123,7 @@ export default function ({ getService }) { esResponse = await es.get({ index: ES_INDEX_NAME, type: ES_TYPE_NAME, - id: `beat:foo` + id: `beat:foo`, }); beat = esResponse._source.beat; @@ -157,7 +133,7 @@ export default function ({ getService }) { esResponse = await es.get({ index: ES_INDEX_NAME, type: ES_TYPE_NAME, - id: `beat:bar` + id: `beat:bar`, }); beat = esResponse._source.beat; @@ -168,19 +144,15 @@ export default function ({ getService }) { const nonExistentBeatId = chance.word(); const { body: apiResponse } = await supertest - .post( - '/api/beats/agents_tags/removals' - ) + .post('/api/beats/agents_tags/removals') .set('kbn-xsrf', 'xxx') .send({ - removals: [ - { beat_id: nonExistentBeatId, tag: 'production' } - ] + removals: [{ beat_id: nonExistentBeatId, tag: 'production' }], }) .expect(200); expect(apiResponse.removals).to.eql([ - { status: 404, result: `Beat ${nonExistentBeatId} not found` } + { status: 404, result: `Beat ${nonExistentBeatId} not found` }, ]); }); @@ -188,25 +160,21 @@ export default function ({ getService }) { const nonExistentTag = chance.word(); const { body: apiResponse } = await supertest - .post( - '/api/beats/agents_tags/removals' - ) + .post('/api/beats/agents_tags/removals') .set('kbn-xsrf', 'xxx') .send({ - removals: [ - { beat_id: 'bar', tag: nonExistentTag } - ] + removals: [{ beatId: 'bar', tag: nonExistentTag }], }) .expect(200); expect(apiResponse.removals).to.eql([ - { status: 404, result: `Tag ${nonExistentTag} not found` } + { status: 404, result: `Tag ${nonExistentTag} not found` }, ]); const esResponse = await es.get({ index: ES_INDEX_NAME, type: ES_TYPE_NAME, - id: `beat:bar` + id: `beat:bar`, }); const beat = esResponse._source.beat; @@ -218,25 +186,21 @@ export default function ({ getService }) { const nonExistentTag = chance.word(); const { body: apiResponse } = await supertest - .post( - '/api/beats/agents_tags/removals' - ) + .post('/api/beats/agents_tags/removals') .set('kbn-xsrf', 'xxx') .send({ - removals: [ - { beat_id: nonExistentBeatId, tag: nonExistentTag } - ] + removals: [{ beatId: nonExistentBeatId, tag: nonExistentTag }], }) .expect(200); expect(apiResponse.removals).to.eql([ - { status: 404, result: `Beat ${nonExistentBeatId} and tag ${nonExistentTag} not found` } + { status: 404, result: `Beat ${nonExistentBeatId} and tag ${nonExistentTag} not found` }, ]); const esResponse = await es.get({ index: ES_INDEX_NAME, type: ES_TYPE_NAME, - id: `beat:bar` + id: `beat:bar`, }); const beat = esResponse._source.beat; diff --git a/x-pack/yarn.lock b/x-pack/yarn.lock index 598b142d3ca93..0dd2bc432b2fc 100644 --- a/x-pack/yarn.lock +++ b/x-pack/yarn.lock @@ -231,6 +231,13 @@ "@types/history" "*" "@types/react" "*" +"@types/react-router@^4.0.30": + version "4.0.30" + resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-4.0.30.tgz#64bcd886befd1f932779b74d17954adbefb7a3a7" + dependencies: + "@types/history" "*" + "@types/react" "*" + "@types/react@*": version "16.4.6" resolved "https://registry.yarnpkg.com/@types/react/-/react-16.4.6.tgz#5024957c6bcef4f02823accf5974faba2e54fada" From b0f0848c8f04114849f09da260486024819a1949 Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Tue, 28 Aug 2018 08:49:45 -0400 Subject: [PATCH 32/94] Beats/config view (#22177) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Beats Management] Move to Ingest UI arch and initial TS effort (#20039) * [Beats Management] Initial scaffolding for plugin (#18977) * Initial scaffolding for Beats plugin * Removing bits not (yet) necessary in initial scaffolding * [Beats Management] Install Beats index template on plugin init (#19072) * Install Beats index template on plugin init * Adding missing files * [Beats Management] APIs: Create enrollment tokens (#19018) * WIP checkin * Register API routes * Fixing typo in index name * Adding TODOs * Removing commented out license checking code that isn't yet implemented * Remove unnecessary async/await * Don't return until indices have been refreshed * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Adding TODO * Fixing variable name * Using a single index * Adding expiration date field * Adding test for expiration date field * Ignore non-existent index * Fixing logic in test * Creating constant for default enrollment tokens TTL value * Updating test * Fixing name of test file (#19100) * [Beats Management] APIs: Enroll beat (#19056) * WIP checkin * Add API integration test * Converting to Jest test * Create API for enrolling a beat * Handle invalid or expired enrollment tokens * Use create instead of index to prevent same beat from being enrolled twice * Adding unit test for duplicate beat enrollment * Do not persist enrollment token with beat once token has been checked and used * Fix datatype of host_ip field * Make Kibana API guess host IP instead of requiring it in payload * Fixing error introduced in rebase conflict resolution * [Beats Management] APIs: List beats (#19086) * WIP checkin * Add API integration test * Converting to Jest test * WIP checkin * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Updating mapping * [Beats Management] APIs: Verify beats (#19103) * WIP checkin * WIP checkin * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Creating POST /api/beats/agents/verify API * Refactoring: extracting out helper functions * Fleshing out remaining tests * Expanding TODO note so I won't forget :) * Fixing file name * Updating mapping * Moving TODO comment to right file * Rename determine* helper functions to find* * Fixing assertions (#19194) * [Beats Management] APIs: Update beat (#19148) * WIP checkin * WIP checkin * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Creating POST /api/beats/agents/verify API * Refactoring: extracting out helper functions * Expanding TODO note so I won't forget :) * Fixing file name * Updating mapping * Add API tests * Update template to allow version field for beat * Implement PUT /api/beats/agent/{beat ID} API * Make enroll beat code consistent with update beat code * Fixing minor typo in TODO comment * Allow version in request payload * Make sure beat is not updated in ES in error scenarios * Adding version as required field in Enroll Beat API payload * Using destructuring * Fixing rename that was accidentally reversed in conflict fixing * [Beats Management] APIs: take auth tokens via headers (#19210) * WIP checkin * WIP checkin * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Creating POST /api/beats/agents/verify API * Refactoring: extracting out helper functions * Expanding TODO note so I won't forget :) * Fixing file name * Updating mapping * Fixing minor typo in TODO comment * Make "Enroll Beat" API take enrollment token via header instead of request body * Make "Update Beat" API take access token via header instead of request body * [Beats Management] APIs: Create configuration block (#19270) * WIP checkin * WIP checkin * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Creating POST /api/beats/agents/verify API * Refactoring: extracting out helper functions * Expanding TODO note so I won't forget :) * Fixing file name * Updating mapping * Fixing minor typo in TODO comment * Implementing POST /api/beats/configuration_blocks API * Removing unnecessary escaping * Fleshing out types + adding validation for them * Making output singular (was outputs) * Removing metricbeat.inputs * Revert implementation of `POST /api/beats/configuration_blocks` API (#19340) This API allowed the user to operate at a level of abstraction that is unnecessarily and dangerously too low. A better API would be at one level higher, where users can create, update, and delete tags (where a tag can contain multiple configuration blocks). * [Beats Management] APIs: Create or update tag (#19342) * Updating mappings * Implementing PUT /api/beats/tag/{tag} API * [Beats Management] Prevent timing attacks when checking auth tokens (#19363) * Using crypto.timingSafeEqual() for comparing auth tokens * Prevent subtler timing attack in token comparison function * Introduce random delay after we try to find token in ES to mitigate timing attack * Remove random delay * [Beats Management] APIs: Assign tag(s) to beat(s) (#19431) * Using crypto.timingSafeEqual() for comparing auth tokens * Introduce random delay after we try to find token in ES to mitigate timing attack * Rename "determine" to "find" * Remove random delay * Starting to implement POST /api/beats/beats_tags API * Changing API * Updating tests for changes to API * Updating ES archive * Renaming * Use destructuring * Moving start of script to own line to increase readability * Using destructuring * [Beats Management] APIs: Remove tag(s) from beat(s) (#19440) * Using crypto.timingSafeEqual() for comparing auth tokens * Introduce random delay after we try to find token in ES to mitigate timing attack * Remove random delay * Starting to implement POST /api/beats/beats_tags API * Changing API * Updating tests for changes to API * Renaming * Use destructuring * Using crypto.timingSafeEqual() for comparing auth tokens * Introduce random delay after we try to find token in ES to mitigate timing attack * Implementing `POST /api/beats/agents_tags/removals` API * Updating ES archive * Use destructuring * Moving start of script to own line to increase readability * Nothing to remove if there are no existing tags! * Updating tests to match changes in bulk update painless script * Use destructuring * Ported over base types and arch structure * move management of installIndexTemplate into the framework adapter * ts-lint fix * tslint fixes * more ts tweaks * fix paths * added several working endpoints * add more routes and bug fixes * fix linting * fix type remove CRUFT * remove more cruft * remove more CRUFT * added comments, change plurality * add tsconfig file * add extends path * fixed typo * serveral PR review fixes * fixed lodash type version * “fix” types by applying a lot of any * add details page, re-configure routes * move tag crud to new route stuff * update tag create/edit component api * tags creation now working * bunch of stuff I should have split up better… * fixed perf bug, selected items that are removed are no longer phantom selected * fix rendering of assignments * remove assign to beats, the UX was too poor * [Beats Management] Move to Ingest UI arch and initial TS effort (#20039) * [Beats Management] Initial scaffolding for plugin (#18977) * Initial scaffolding for Beats plugin * Removing bits not (yet) necessary in initial scaffolding * [Beats Management] Install Beats index template on plugin init (#19072) * Install Beats index template on plugin init * Adding missing files * [Beats Management] APIs: Create enrollment tokens (#19018) * WIP checkin * Register API routes * Fixing typo in index name * Adding TODOs * Removing commented out license checking code that isn't yet implemented * Remove unnecessary async/await * Don't return until indices have been refreshed * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Adding TODO * Fixing variable name * Using a single index * Adding expiration date field * Adding test for expiration date field * Ignore non-existent index * Fixing logic in test * Creating constant for default enrollment tokens TTL value * Updating test * Fixing name of test file (#19100) * [Beats Management] APIs: Enroll beat (#19056) * WIP checkin * Add API integration test * Converting to Jest test * Create API for enrolling a beat * Handle invalid or expired enrollment tokens * Use create instead of index to prevent same beat from being enrolled twice * Adding unit test for duplicate beat enrollment * Do not persist enrollment token with beat once token has been checked and used * Fix datatype of host_ip field * Make Kibana API guess host IP instead of requiring it in payload * Fixing error introduced in rebase conflict resolution * [Beats Management] APIs: List beats (#19086) * WIP checkin * Add API integration test * Converting to Jest test * WIP checkin * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Updating mapping * [Beats Management] APIs: Verify beats (#19103) * WIP checkin * WIP checkin * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Creating POST /api/beats/agents/verify API * Refactoring: extracting out helper functions * Fleshing out remaining tests * Expanding TODO note so I won't forget :) * Fixing file name * Updating mapping * Moving TODO comment to right file * Rename determine* helper functions to find* * Fixing assertions (#19194) * [Beats Management] APIs: Update beat (#19148) * WIP checkin * WIP checkin * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Creating POST /api/beats/agents/verify API * Refactoring: extracting out helper functions * Expanding TODO note so I won't forget :) * Fixing file name * Updating mapping * Add API tests * Update template to allow version field for beat * Implement PUT /api/beats/agent/{beat ID} API * Make enroll beat code consistent with update beat code * Fixing minor typo in TODO comment * Allow version in request payload * Make sure beat is not updated in ES in error scenarios * Adding version as required field in Enroll Beat API payload * Using destructuring * Fixing rename that was accidentally reversed in conflict fixing * [Beats Management] APIs: take auth tokens via headers (#19210) * WIP checkin * WIP checkin * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Creating POST /api/beats/agents/verify API * Refactoring: extracting out helper functions * Expanding TODO note so I won't forget :) * Fixing file name * Updating mapping * Fixing minor typo in TODO comment * Make "Enroll Beat" API take enrollment token via header instead of request body * Make "Update Beat" API take access token via header instead of request body * [Beats Management] APIs: Create configuration block (#19270) * WIP checkin * WIP checkin * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Creating POST /api/beats/agents/verify API * Refactoring: extracting out helper functions * Expanding TODO note so I won't forget :) * Fixing file name * Updating mapping * Fixing minor typo in TODO comment * Implementing POST /api/beats/configuration_blocks API * Removing unnecessary escaping * Fleshing out types + adding validation for them * Making output singular (was outputs) * Removing metricbeat.inputs * Revert implementation of `POST /api/beats/configuration_blocks` API (#19340) This API allowed the user to operate at a level of abstraction that is unnecessarily and dangerously too low. A better API would be at one level higher, where users can create, update, and delete tags (where a tag can contain multiple configuration blocks). * [Beats Management] APIs: Create or update tag (#19342) * Updating mappings * Implementing PUT /api/beats/tag/{tag} API * [Beats Management] Prevent timing attacks when checking auth tokens (#19363) * Using crypto.timingSafeEqual() for comparing auth tokens * Prevent subtler timing attack in token comparison function * Introduce random delay after we try to find token in ES to mitigate timing attack * Remove random delay * [Beats Management] APIs: Assign tag(s) to beat(s) (#19431) * Using crypto.timingSafeEqual() for comparing auth tokens * Introduce random delay after we try to find token in ES to mitigate timing attack * Rename "determine" to "find" * Remove random delay * Starting to implement POST /api/beats/beats_tags API * Changing API * Updating tests for changes to API * Updating ES archive * Renaming * Use destructuring * Moving start of script to own line to increase readability * Using destructuring * [Beats Management] APIs: Remove tag(s) from beat(s) (#19440) * Using crypto.timingSafeEqual() for comparing auth tokens * Introduce random delay after we try to find token in ES to mitigate timing attack * Remove random delay * Starting to implement POST /api/beats/beats_tags API * Changing API * Updating tests for changes to API * Renaming * Use destructuring * Using crypto.timingSafeEqual() for comparing auth tokens * Introduce random delay after we try to find token in ES to mitigate timing attack * Implementing `POST /api/beats/agents_tags/removals` API * Updating ES archive * Use destructuring * Moving start of script to own line to increase readability * Nothing to remove if there are no existing tags! * Updating tests to match changes in bulk update painless script * Use destructuring * Ported over base types and arch structure * move management of installIndexTemplate into the framework adapter * ts-lint fix * tslint fixes * more ts tweaks * fix paths * added several working endpoints * add more routes and bug fixes * fix linting * fix type remove CRUFT * remove more cruft * remove more CRUFT * added comments, change plurality * add tsconfig file * add extends path * fixed typo * serveral PR review fixes * fixed lodash type version * “fix” types by applying a lot of any * Beats/update (#21702) * [ML] Fixing issue with historical job audit messages (#21718) * Add proper aria-label for close inspector (#21719) * [Beats Management] Initial scaffolding for plugin (#18977) * Initial scaffolding for Beats plugin * Removing bits not (yet) necessary in initial scaffolding * [Beats Management] Install Beats index template on plugin init (#19072) * Install Beats index template on plugin init * Adding missing files * [Beats Management] APIs: Create enrollment tokens (#19018) * WIP checkin * Register API routes * Fixing typo in index name * Adding TODOs * Removing commented out license checking code that isn't yet implemented * Remove unnecessary async/await * Don't return until indices have been refreshed * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Adding TODO * Fixing variable name * Using a single index * Adding expiration date field * Adding test for expiration date field * Ignore non-existent index * Fixing logic in test * Creating constant for default enrollment tokens TTL value * Updating test * Fixing name of test file (#19100) * [Beats Management] APIs: Enroll beat (#19056) * WIP checkin * Add API integration test * Converting to Jest test * Create API for enrolling a beat * Handle invalid or expired enrollment tokens * Use create instead of index to prevent same beat from being enrolled twice * Adding unit test for duplicate beat enrollment * Do not persist enrollment token with beat once token has been checked and used * Fix datatype of host_ip field * Make Kibana API guess host IP instead of requiring it in payload * Fixing error introduced in rebase conflict resolution * [Beats Management] APIs: List beats (#19086) * WIP checkin * Add API integration test * Converting to Jest test * WIP checkin * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Updating mapping * [Beats Management] APIs: Verify beats (#19103) * WIP checkin * WIP checkin * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Creating POST /api/beats/agents/verify API * Refactoring: extracting out helper functions * Fleshing out remaining tests * Expanding TODO note so I won't forget :) * Fixing file name * Updating mapping * Moving TODO comment to right file * Rename determine* helper functions to find* * Fixing assertions (#19194) * [Beats Management] APIs: Update beat (#19148) * WIP checkin * WIP checkin * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Creating POST /api/beats/agents/verify API * Refactoring: extracting out helper functions * Expanding TODO note so I won't forget :) * Fixing file name * Updating mapping * Add API tests * Update template to allow version field for beat * Implement PUT /api/beats/agent/{beat ID} API * Make enroll beat code consistent with update beat code * Fixing minor typo in TODO comment * Allow version in request payload * Make sure beat is not updated in ES in error scenarios * Adding version as required field in Enroll Beat API payload * Using destructuring * Fixing rename that was accidentally reversed in conflict fixing * [Beats Management] APIs: take auth tokens via headers (#19210) * WIP checkin * WIP checkin * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Creating POST /api/beats/agents/verify API * Refactoring: extracting out helper functions * Expanding TODO note so I won't forget :) * Fixing file name * Updating mapping * Fixing minor typo in TODO comment * Make "Enroll Beat" API take enrollment token via header instead of request body * Make "Update Beat" API take access token via header instead of request body * [Beats Management] APIs: Create configuration block (#19270) * WIP checkin * WIP checkin * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Creating POST /api/beats/agents/verify API * Refactoring: extracting out helper functions * Expanding TODO note so I won't forget :) * Fixing file name * Updating mapping * Fixing minor typo in TODO comment * Implementing POST /api/beats/configuration_blocks API * Removing unnecessary escaping * Fleshing out types + adding validation for them * Making output singular (was outputs) * Removing metricbeat.inputs * Revert implementation of `POST /api/beats/configuration_blocks` API (#19340) This API allowed the user to operate at a level of abstraction that is unnecessarily and dangerously too low. A better API would be at one level higher, where users can create, update, and delete tags (where a tag can contain multiple configuration blocks). * [Beats Management] APIs: Create or update tag (#19342) * Updating mappings * Implementing PUT /api/beats/tag/{tag} API * [Beats Management] Prevent timing attacks when checking auth tokens (#19363) * Using crypto.timingSafeEqual() for comparing auth tokens * Prevent subtler timing attack in token comparison function * Introduce random delay after we try to find token in ES to mitigate timing attack * Remove random delay * [Beats Management] APIs: Assign tag(s) to beat(s) (#19431) * Using crypto.timingSafeEqual() for comparing auth tokens * Introduce random delay after we try to find token in ES to mitigate timing attack * Rename "determine" to "find" * Remove random delay * Starting to implement POST /api/beats/beats_tags API * Changing API * Updating tests for changes to API * Updating ES archive * Renaming * Use destructuring * Moving start of script to own line to increase readability * Using destructuring * [Beats Management] APIs: Remove tag(s) from beat(s) (#19440) * Using crypto.timingSafeEqual() for comparing auth tokens * Introduce random delay after we try to find token in ES to mitigate timing attack * Remove random delay * Starting to implement POST /api/beats/beats_tags API * Changing API * Updating tests for changes to API * Renaming * Use destructuring * Using crypto.timingSafeEqual() for comparing auth tokens * Introduce random delay after we try to find token in ES to mitigate timing attack * Implementing `POST /api/beats/agents_tags/removals` API * Updating ES archive * Use destructuring * Moving start of script to own line to increase readability * Nothing to remove if there are no existing tags! * Updating tests to match changes in bulk update painless script * Use destructuring * [Beats Management] Move to Ingest UI arch and initial TS effort (#20039) * [Beats Management] Initial scaffolding for plugin (#18977) * Initial scaffolding for Beats plugin * Removing bits not (yet) necessary in initial scaffolding * [Beats Management] Install Beats index template on plugin init (#19072) * Install Beats index template on plugin init * Adding missing files * [Beats Management] APIs: Create enrollment tokens (#19018) * WIP checkin * Register API routes * Fixing typo in index name * Adding TODOs * Removing commented out license checking code that isn't yet implemented * Remove unnecessary async/await * Don't return until indices have been refreshed * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Adding TODO * Fixing variable name * Using a single index * Adding expiration date field * Adding test for expiration date field * Ignore non-existent index * Fixing logic in test * Creating constant for default enrollment tokens TTL value * Updating test * Fixing name of test file (#19100) * [Beats Management] APIs: Enroll beat (#19056) * WIP checkin * Add API integration test * Converting to Jest test * Create API for enrolling a beat * Handle invalid or expired enrollment tokens * Use create instead of index to prevent same beat from being enrolled twice * Adding unit test for duplicate beat enrollment * Do not persist enrollment token with beat once token has been checked and used * Fix datatype of host_ip field * Make Kibana API guess host IP instead of requiring it in payload * Fixing error introduced in rebase conflict resolution * [Beats Management] APIs: List beats (#19086) * WIP checkin * Add API integration test * Converting to Jest test * WIP checkin * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Updating mapping * [Beats Management] APIs: Verify beats (#19103) * WIP checkin * WIP checkin * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Creating POST /api/beats/agents/verify API * Refactoring: extracting out helper functions * Fleshing out remaining tests * Expanding TODO note so I won't forget :) * Fixing file name * Updating mapping * Moving TODO comment to right file * Rename determine* helper functions to find* * Fixing assertions (#19194) * [Beats Management] APIs: Update beat (#19148) * WIP checkin * WIP checkin * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Creating POST /api/beats/agents/verify API * Refactoring: extracting out helper functions * Expanding TODO note so I won't forget :) * Fixing file name * Updating mapping * Add API tests * Update template to allow version field for beat * Implement PUT /api/beats/agent/{beat ID} API * Make enroll beat code consistent with update beat code * Fixing minor typo in TODO comment * Allow version in request payload * Make sure beat is not updated in ES in error scenarios * Adding version as required field in Enroll Beat API payload * Using destructuring * Fixing rename that was accidentally reversed in conflict fixing * [Beats Management] APIs: take auth tokens via headers (#19210) * WIP checkin * WIP checkin * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Creating POST /api/beats/agents/verify API * Refactoring: extracting out helper functions * Expanding TODO note so I won't forget :) * Fixing file name * Updating mapping * Fixing minor typo in TODO comment * Make "Enroll Beat" API take enrollment token via header instead of request body * Make "Update Beat" API take access token via header instead of request body * [Beats Management] APIs: Create configuration block (#19270) * WIP checkin * WIP checkin * Add API integration test * Converting to Jest test * Fixing API for default case + adding test for it * Fixing copy pasta typos * Fixing variable name * Using a single index * Implementing GET /api/beats/agents API * Creating POST /api/beats/agents/verify API * Refactoring: extracting out helper functions * Expanding TODO note so I won't forget :) * Fixing file name * Updating mapping * Fixing minor typo in TODO comment * Implementing POST /api/beats/configuration_blocks API * Removing unnecessary escaping * Fleshing out types + adding validation for them * Making output singular (was outputs) * Removing metricbeat.inputs * Revert implementation of `POST /api/beats/configuration_blocks` API (#19340) This API allowed the user to operate at a level of abstraction that is unnecessarily and dangerously too low. A better API would be at one level higher, where users can create, update, and delete tags (where a tag can contain multiple configuration blocks). * [Beats Management] APIs: Create or update tag (#19342) * Updating mappings * Implementing PUT /api/beats/tag/{tag} API * [Beats Management] Prevent timing attacks when checking auth tokens (#19363) * Using crypto.timingSafeEqual() for comparing auth tokens * Prevent subtler timing attack in token comparison function * Introduce random delay after we try to find token in ES to mitigate timing attack * Remove random delay * [Beats Management] APIs: Assign tag(s) to beat(s) (#19431) * Using crypto.timingSafeEqual() for comparing auth tokens * Introduce random delay after we try to find token in ES to mitigate timing attack * Rename "determine" to "find" * Remove random delay * Starting to implement POST /api/beats/beats_tags API * Changing API * Updating tests for changes to API * Updating ES archive * Renaming * Use destructuring * Moving start of script to own line to increase readability * Using destructuring * [Beats Management] APIs: Remove tag(s) from beat(s) (#19440) * Using crypto.timingSafeEqual() for comparing auth tokens * Introduce random delay after we try to find token in ES to mitigate timing attack * Remove random delay * Starting to implement POST /api/beats/beats_tags API * Changing API * Updating tests for changes to API * Renaming * Use destructuring * Using crypto.timingSafeEqual() for comparing auth tokens * Introduce random delay after we try to find token in ES to mitigate timing attack * Implementing `POST /api/beats/agents_tags/removals` API * Updating ES archive * Use destructuring * Moving start of script to own line to increase readability * Nothing to remove if there are no existing tags! * Updating tests to match changes in bulk update painless script * Use destructuring * Ported over base types and arch structure * move management of installIndexTemplate into the framework adapter * ts-lint fix * tslint fixes * more ts tweaks * fix paths * added several working endpoints * add more routes and bug fixes * fix linting * fix type remove CRUFT * remove more cruft * remove more CRUFT * added comments, change plurality * add tsconfig file * add extends path * fixed typo * serveral PR review fixes * fixed lodash type version * “fix” types by applying a lot of any * [Beats Management] Move tokens to use JWT, add more complete test suite (#20317) * inital effort to move to JWT and added jest based tests on libs * assign beats tests all passing * token tests now pass * add more tests * all tests now green * fix broken test, this is beats CM not logstash 😊 * added readme * move enrollment token back to a hash * remove un-needed comment * alias lodash get to avoid confusion * isolated hash creation * [Beats Management] add more tests, update types, break out ES into it's own adapter (#20566) * inital effort to move to JWT and added jest based tests on libs * assign beats tests all passing * token tests now pass * add more tests * all tests now green * move enrollment token back to a hash * remove un-needed comment * alias lodash get to avoid confusion * isolated hash creation * Add initial efforts for backend framework adapter testing * move ES code to a DatabaseAdapter from BackendAdapter and add a TON of types for ES * re-typed * renamed types to match pattern * aditional renames * adapter tests should always just use adapterSetup(); * database now uses InternalRequest * corrected spelling of framework * fix typings * remove CRUFT * RequestOrInternal * Dont pass around request objects everywhere, just pass the user. Also, removed hapi types as they were not compatible * fix tests, add test, removed extra comment * fix auth * updated lock file * [Beats Management] add get beat endpoint (#20603) * [Beats Management] Move tokens to use JWT, add more complete test suite (#20317) * inital effort to move to JWT and added jest based tests on libs * assign beats tests all passing * token tests now pass * add more tests * all tests now green * fix broken test, this is beats CM not logstash 😊 * added readme * move enrollment token back to a hash * remove un-needed comment * alias lodash get to avoid confusion * isolated hash creation * inital effort to move to JWT and added jest based tests on libs * assign beats tests all passing * token tests now pass * add more tests * all tests now green * move enrollment token back to a hash * remove un-needed comment * alias lodash get to avoid confusion * isolated hash creation * Add initial efforts for backend framework adapter testing * move ES code to a DatabaseAdapter from BackendAdapter and add a TON of types for ES * re-typed * renamed types to match pattern * aditional renames * adapter tests should always just use adapterSetup(); * database now uses InternalRequest * corrected spelling of framework * fix typings * remove CRUFT * RequestOrInternal * Dont pass around request objects everywhere, just pass the user. Also, removed hapi types as they were not compatible * fix tests, add test, removed extra comment * Moved critical path code from route, to more easeley tested domain * fix auth * remove beat verification, added get beat endpoint to return configs * fix type * update createGetBeatConfigurationRoute URL * rename method * update to match PR #20566 * updated lock file * fix bad merge * update TSLinting * fix bad rebase * [Beats Management] [WIP] Create public resources for management plugin (#20864) * Init plugin public resources. * rename beats to beats_management * rendering react now * Beats/initial ui (#20994) * initial layout and main nav * modal UI and pattern for UI established * fix path * wire up in-memroy adapters * tweak adapters * add getAll method to tags adapter (#21287) * Beats/real adapters (#21481) * add initial real adapters, and nulled data where we need endpoints * UI adapters and needed endpoints added (though not tested) * prep for route tests and some cleanup * move files * [Beats Management] Add BeatsTable/Bulk Action Search Component (#21182) * Add BeatsTable and control bar components. * Clean yarn.lock. * Move raw numbers/strings to constants. Remove obsolete state/props. * Update/add tests. * Change prop name from "items" to "beats". * Rename some variables. * Move search bar filter definitions to table render. * Update table to support assignment options. * Update action control position. * Refactor split render function into custom components. * Beats/basic use cases (#21660) * tweak adapter responses / types. re-add enroll ui * routes enabled, enroll now pings the server * full enrollment path now working * improved pinging for beat enrollment * fix location of history call * reload beats list on beat enrollment completion * add update on client side, expand update on server to allow for partial data, and user auth * remove double beat lookup * fix tests * only return active beats * disenroll now working * fig getAll query * re-enrolling a beat will now work * fix types * fix types * update deps * update kibana API for version * progress on config forms * config view inital input types working * ts fixes * fix more ts * code now errors on invalid yaml * remove un-needed include * fix bad rebase * saving config blocks as yaml to db is now working * propperly formatted YAML * loading tags back on edit screen in-progress * fix types * vis name validation for tag * update EUI style * tweak design * fixed tag assignments (still has a ui glitch) * fix form validation on select * fix deps * update deps * attached beats now works in the edit tag screen, edit now disables changing the tag id * better un-parsing of yaml, some elements now rendering to edit config blocks * delete config block now works * fix ability to edit config * fix deps * fix another rebase issue * tweaks and fixes * fix several bugs --- package.json | 2 +- x-pack/index.js | 2 +- x-pack/package.json | 8 +- .../beats_management/common/domain_types.ts | 4 +- x-pack/plugins/beats_management/index.ts | 1 - .../public/components/config_list.tsx | 62 + .../public/components/inputs/code_editor.tsx | 115 + .../public/components/inputs/input.tsx | 111 + .../public/components/inputs/multi_input.tsx | 113 + .../public/components/inputs/select.tsx | 117 + .../public/components/layouts/primary.tsx | 21 +- .../public/components/table/table.tsx | 1 + .../components/table/table_type_configs.tsx | 12 +- .../tag/config_view/config_form.tsx | 186 + .../components/tag/config_view/index.tsx | 144 + .../public/components/tag/tag_edit.tsx | 267 +- .../beats_management/public/config_schemas.ts | 200 ++ .../lib/adapters/beats/adapter_types.ts | 1 + .../adapters/beats/memory_beats_adapter.ts | 4 + .../lib/adapters/beats/rest_beats_adapter.ts | 6 +- .../public/lib/compose/kibana.ts | 6 +- .../public/lib/compose/memory.ts | 8 +- .../public/lib/domains/beats.ts | 9 +- .../public/lib/domains/tags.ts | 79 + .../beats_management/public/lib/lib.ts | 75 +- .../public/pages/main/beats.tsx | 41 +- .../public/pages/main/tags.tsx | 20 +- .../public/pages/tag/create.tsx | 79 - .../public/pages/tag/edit.tsx | 50 - .../public/pages/tag/index.tsx | 109 +- .../beats_management/public/router.tsx | 5 +- .../server/lib/domains/beats.ts | 11 +- .../server/rest_api/beats/list.ts | 24 +- .../server/rest_api/beats/update.ts | 1 - .../server/rest_api/tags/set.ts | 1 + .../beats/elasticsearch_beats_adapter.ts | 218 -- .../kibana/kibana_framework_adapter.ts | 82 - .../tags/elasticsearch_tags_adapter.ts | 57 - .../tokens/elasticsearch_tokens_adapter.ts | 83 - .../server/utils/compose/kibana.ts | 45 - .../server/utils/domains/beats.ts | 259 -- .../server/utils/domains/tags.ts | 90 - .../server/utils/domains/tokens.ts | 80 - .../utils/index_templates/beats_template.json | 3 + .../beats_management/server/utils/lib.ts | 212 -- x-pack/plugins/beats_management/tsconfig.json | 6 +- .../beats_management/types/formsy.d.ts | 47 + .../grokdebugger/common/constants/index.js | 9 + .../call_with_request_factory.js | 2 +- .../call_with_request_factory.js | 18 + x-pack/plugins/logstash/README.md | 0 .../common/constants/configuration_blocks.ts | 15 - .../logstash/common/constants/editor.js | 0 .../common/constants/es_scroll_settings.js | 0 .../logstash/common/constants/index.js | 0 .../constants/index_names.js} | 5 +- .../logstash/common/constants/monitoring.js | 0 .../logstash/common/constants/pagination.js | 0 .../logstash/common/constants/pipeline.js | 0 .../logstash/common/constants/plugin.js | 0 .../logstash/common/constants/routes.js | 0 .../logstash/common/constants/tooltips.js | 0 .../logstash/common/constants/type_names.js | 0 .../common/lib/__tests__/get_moment.js | 0 .../plugins/logstash/common/lib/get_moment.js | 0 x-pack/plugins/logstash/common/lib/index.js | 0 x-pack/plugins/logstash/index.js | 0 .../public/components/tooltip/index.js | 0 .../public/components/tooltip/tooltip.html | 0 .../public/components/tooltip/tooltip.js | 0 .../public/components/tooltip/tooltip.less | 0 .../lib/get_search_value/get_search_value.js | 0 .../public/lib/get_search_value/index.js | 0 .../public/lib/register_home_feature/index.js | 0 .../register_home_feature.js | 0 .../lib/update_management_sections/index.js | 0 .../update_logstash_sections.js | 0 .../logstash/public/models/cluster/cluster.js | 0 .../logstash/public/models/cluster/index.js | 0 .../logstash/public/models/pipeline/index.js | 0 .../public/models/pipeline/pipeline.js | 0 .../public/models/pipeline_list_item/index.js | 0 .../pipeline_list_item/pipeline_list_item.js | 0 .../components/pipeline_edit/index.js | 0 .../pipeline_edit/pipeline_edit.html | 0 .../components/pipeline_edit/pipeline_edit.js | 0 .../pipeline_edit/pipeline_edit.less | 0 .../components/upgrade_failure/index.js | 0 .../upgrade_failure/upgrade_failure.html | 0 .../upgrade_failure/upgrade_failure.js | 0 .../public/sections/pipeline_edit/index.js | 0 .../pipeline_edit/pipeline_edit_route.html | 0 .../pipeline_edit/pipeline_edit_route.js | 0 .../components/pipeline_list/index.js | 0 .../pipeline_list/pipeline_list.html | 0 .../components/pipeline_list/pipeline_list.js | 0 .../components/pipeline_table/index.js | 0 .../pipeline_table/pipeline_table.html | 0 .../pipeline_table/pipeline_table.js | 0 .../public/sections/pipeline_list/index.js | 0 .../pipeline_list/pipeline_list_route.html | 0 .../pipeline_list/pipeline_list_route.js | 0 .../register_management_section.js | 0 .../cluster/cluster_service.factory.js | 0 .../services/cluster/cluster_service.js | 0 .../logstash/public/services/cluster/index.js | 0 .../logstash/public/services/license/index.js | 0 .../license/license_service.factory.js | 0 .../license/logstash_license_service.js | 0 .../public/services/monitoring/index.js | 0 .../monitoring/monitoring_service.factory.js | 0 .../services/monitoring/monitoring_service.js | 0 .../public/services/pipeline/index.js | 0 .../pipeline/pipeline_service.factory.js | 0 .../services/pipeline/pipeline_service.js | 0 .../public/services/pipelines/index.js | 0 .../pipelines/pipelines_service.factory.js | 0 .../services/pipelines/pipelines_service.js | 0 .../public/services/security/index.js | 0 .../security/logstash_security_service.js | 0 .../security/security_service.factory.js | 0 .../logstash/public/services/upgrade/index.js | 0 .../upgrade/upgrade_service.factory.js | 0 .../services/upgrade/upgrade_service.js | 0 .../plugins/logstash/server/kibana.index.ts | 14 - .../call_with_request_factory.js | 0 .../lib/call_with_request_factory/index.js | 0 .../check_license/__tests__/check_license.js | 0 .../server/lib/check_license/check_license.js | 0 .../server/lib/check_license/index.js | 0 .../__tests__/wrap_custom_error.js | 0 .../error_wrappers/__tests__/wrap_es_error.js | 41 + .../__tests__/wrap_unknown_error.js | 0 .../server/lib/error_wrappers/index.js | 0 .../lib/error_wrappers/wrap_custom_error.js | 0 .../lib/error_wrappers/wrap_es_error.js | 0 .../lib/error_wrappers/wrap_unknown_error.js | 0 .../__tests__/fetch_all_from_scroll.js | 0 .../fetch_all_from_scroll.js | 0 .../server/lib/fetch_all_from_scroll/index.js | 0 .../__tests__/license_pre_routing_factory.js | 0 .../lib/license_pre_routing_factory/index.js | 0 .../license_pre_routing_factory.js | 0 .../lib/register_license_checker/index.js | 0 .../register_license_checker.js | 0 .../logstash/server/management_server.ts | 30 - .../models/cluster/__tests__/cluster.js | 0 .../logstash/server/models/cluster/cluster.js | 0 .../logstash/server/models/cluster/index.js | 0 .../models/pipeline/__tests__/pipeline.js | 0 .../logstash/server/models/pipeline/index.js | 0 .../server/models/pipeline/pipeline.js | 0 .../__tests__/pipeline_list_item.js | 0 .../server/models/pipeline_list_item/index.js | 0 .../pipeline_list_item/pipeline_list_item.js | 0 .../logstash/server/rest_api/beats/enroll.ts | 63 - .../logstash/server/rest_api/beats/list.ts | 23 - .../server/rest_api/beats/tag_assignment.ts | 48 - .../server/rest_api/beats/tag_removal.ts | 48 - .../logstash/server/rest_api/beats/update.ts | 62 - .../logstash/server/rest_api/beats/verify.ts | 73 - .../logstash/server/rest_api/tags/set.ts | 57 - .../logstash/server/rest_api/tokens/create.ts | 42 - .../server/routes/api/cluster/index.js | 0 .../api/cluster/register_cluster_routes.js | 0 .../routes/api/cluster/register_load_route.js | 0 .../server/routes/api/pipeline/index.js | 0 .../api/pipeline/register_delete_route.js | 0 .../api/pipeline/register_load_route.js | 0 .../api/pipeline/register_pipeline_routes.js | 0 .../api/pipeline/register_save_route.js | 0 .../server/routes/api/pipelines/index.js | 0 .../api/pipelines/register_delete_route.js | 0 .../api/pipelines/register_list_route.js | 0 .../pipelines/register_pipelines_routes.js | 0 .../server/routes/api/upgrade/index.js | 0 .../api/upgrade/register_execute_route.js | 0 .../api/upgrade/register_upgrade_routes.js | 0 .../plugins/logstash/server/utils/README.md | 1 - .../server/utils/find_non_existent_items.ts | 14 - .../logstash/server/utils/polyfills.ts | 17 - .../logstash/server/utils/wrap_request.ts | 24 - x-pack/plugins/logstash/tsconfig.json | 3 - x-pack/plugins/logstash/wallaby.js | 27 - .../simple/components/constants/states.js | 6 +- x-pack/plugins/monitoring/public/index.css | 541 +++ .../watcher/common/constants/index_names.js | 2 +- x-pack/yarn.lock | 2511 ++++++------- yarn.lock | 3154 ++++++++--------- 189 files changed, 4835 insertions(+), 5092 deletions(-) create mode 100644 x-pack/plugins/beats_management/public/components/config_list.tsx create mode 100644 x-pack/plugins/beats_management/public/components/inputs/code_editor.tsx create mode 100644 x-pack/plugins/beats_management/public/components/inputs/input.tsx create mode 100644 x-pack/plugins/beats_management/public/components/inputs/multi_input.tsx create mode 100644 x-pack/plugins/beats_management/public/components/inputs/select.tsx create mode 100644 x-pack/plugins/beats_management/public/components/tag/config_view/config_form.tsx create mode 100644 x-pack/plugins/beats_management/public/components/tag/config_view/index.tsx create mode 100644 x-pack/plugins/beats_management/public/config_schemas.ts create mode 100644 x-pack/plugins/beats_management/public/lib/domains/tags.ts delete mode 100644 x-pack/plugins/beats_management/public/pages/tag/create.tsx delete mode 100644 x-pack/plugins/beats_management/public/pages/tag/edit.tsx delete mode 100644 x-pack/plugins/beats_management/server/utils/adapters/beats/elasticsearch_beats_adapter.ts delete mode 100644 x-pack/plugins/beats_management/server/utils/adapters/famework/kibana/kibana_framework_adapter.ts delete mode 100644 x-pack/plugins/beats_management/server/utils/adapters/tags/elasticsearch_tags_adapter.ts delete mode 100644 x-pack/plugins/beats_management/server/utils/adapters/tokens/elasticsearch_tokens_adapter.ts delete mode 100644 x-pack/plugins/beats_management/server/utils/compose/kibana.ts delete mode 100644 x-pack/plugins/beats_management/server/utils/domains/beats.ts delete mode 100644 x-pack/plugins/beats_management/server/utils/domains/tags.ts delete mode 100644 x-pack/plugins/beats_management/server/utils/domains/tokens.ts delete mode 100644 x-pack/plugins/beats_management/server/utils/lib.ts create mode 100644 x-pack/plugins/beats_management/types/formsy.d.ts create mode 100644 x-pack/plugins/grokdebugger/common/constants/index.js create mode 100644 x-pack/plugins/index_management/server/lib/call_with_request_factory/call_with_request_factory.js mode change 100644 => 100755 x-pack/plugins/logstash/README.md delete mode 100644 x-pack/plugins/logstash/common/constants/configuration_blocks.ts mode change 100644 => 100755 x-pack/plugins/logstash/common/constants/editor.js mode change 100644 => 100755 x-pack/plugins/logstash/common/constants/es_scroll_settings.js mode change 100644 => 100755 x-pack/plugins/logstash/common/constants/index.js rename x-pack/plugins/logstash/{server/utils/index_templates/index.ts => common/constants/index_names.js} (73%) mode change 100644 => 100755 mode change 100644 => 100755 x-pack/plugins/logstash/common/constants/monitoring.js mode change 100644 => 100755 x-pack/plugins/logstash/common/constants/pagination.js mode change 100644 => 100755 x-pack/plugins/logstash/common/constants/pipeline.js mode change 100644 => 100755 x-pack/plugins/logstash/common/constants/plugin.js mode change 100644 => 100755 x-pack/plugins/logstash/common/constants/routes.js mode change 100644 => 100755 x-pack/plugins/logstash/common/constants/tooltips.js mode change 100644 => 100755 x-pack/plugins/logstash/common/constants/type_names.js mode change 100644 => 100755 x-pack/plugins/logstash/common/lib/__tests__/get_moment.js mode change 100644 => 100755 x-pack/plugins/logstash/common/lib/get_moment.js mode change 100644 => 100755 x-pack/plugins/logstash/common/lib/index.js mode change 100644 => 100755 x-pack/plugins/logstash/index.js mode change 100644 => 100755 x-pack/plugins/logstash/public/components/tooltip/index.js mode change 100644 => 100755 x-pack/plugins/logstash/public/components/tooltip/tooltip.html mode change 100644 => 100755 x-pack/plugins/logstash/public/components/tooltip/tooltip.js mode change 100644 => 100755 x-pack/plugins/logstash/public/components/tooltip/tooltip.less mode change 100644 => 100755 x-pack/plugins/logstash/public/lib/get_search_value/get_search_value.js mode change 100644 => 100755 x-pack/plugins/logstash/public/lib/get_search_value/index.js mode change 100644 => 100755 x-pack/plugins/logstash/public/lib/register_home_feature/index.js mode change 100644 => 100755 x-pack/plugins/logstash/public/lib/register_home_feature/register_home_feature.js mode change 100644 => 100755 x-pack/plugins/logstash/public/lib/update_management_sections/index.js mode change 100644 => 100755 x-pack/plugins/logstash/public/lib/update_management_sections/update_logstash_sections.js mode change 100644 => 100755 x-pack/plugins/logstash/public/models/cluster/cluster.js mode change 100644 => 100755 x-pack/plugins/logstash/public/models/cluster/index.js mode change 100644 => 100755 x-pack/plugins/logstash/public/models/pipeline/index.js mode change 100644 => 100755 x-pack/plugins/logstash/public/models/pipeline/pipeline.js mode change 100644 => 100755 x-pack/plugins/logstash/public/models/pipeline_list_item/index.js mode change 100644 => 100755 x-pack/plugins/logstash/public/models/pipeline_list_item/pipeline_list_item.js mode change 100644 => 100755 x-pack/plugins/logstash/public/sections/pipeline_edit/components/pipeline_edit/index.js mode change 100644 => 100755 x-pack/plugins/logstash/public/sections/pipeline_edit/components/pipeline_edit/pipeline_edit.html mode change 100644 => 100755 x-pack/plugins/logstash/public/sections/pipeline_edit/components/pipeline_edit/pipeline_edit.js mode change 100644 => 100755 x-pack/plugins/logstash/public/sections/pipeline_edit/components/pipeline_edit/pipeline_edit.less mode change 100644 => 100755 x-pack/plugins/logstash/public/sections/pipeline_edit/components/upgrade_failure/index.js mode change 100644 => 100755 x-pack/plugins/logstash/public/sections/pipeline_edit/components/upgrade_failure/upgrade_failure.html mode change 100644 => 100755 x-pack/plugins/logstash/public/sections/pipeline_edit/components/upgrade_failure/upgrade_failure.js mode change 100644 => 100755 x-pack/plugins/logstash/public/sections/pipeline_edit/index.js mode change 100644 => 100755 x-pack/plugins/logstash/public/sections/pipeline_edit/pipeline_edit_route.html mode change 100644 => 100755 x-pack/plugins/logstash/public/sections/pipeline_edit/pipeline_edit_route.js mode change 100644 => 100755 x-pack/plugins/logstash/public/sections/pipeline_list/components/pipeline_list/index.js mode change 100644 => 100755 x-pack/plugins/logstash/public/sections/pipeline_list/components/pipeline_list/pipeline_list.html mode change 100644 => 100755 x-pack/plugins/logstash/public/sections/pipeline_list/components/pipeline_list/pipeline_list.js mode change 100644 => 100755 x-pack/plugins/logstash/public/sections/pipeline_list/components/pipeline_table/index.js mode change 100644 => 100755 x-pack/plugins/logstash/public/sections/pipeline_list/components/pipeline_table/pipeline_table.html mode change 100644 => 100755 x-pack/plugins/logstash/public/sections/pipeline_list/components/pipeline_table/pipeline_table.js mode change 100644 => 100755 x-pack/plugins/logstash/public/sections/pipeline_list/index.js mode change 100644 => 100755 x-pack/plugins/logstash/public/sections/pipeline_list/pipeline_list_route.html mode change 100644 => 100755 x-pack/plugins/logstash/public/sections/pipeline_list/pipeline_list_route.js mode change 100644 => 100755 x-pack/plugins/logstash/public/sections/pipeline_list/register_management_section.js mode change 100644 => 100755 x-pack/plugins/logstash/public/services/cluster/cluster_service.factory.js mode change 100644 => 100755 x-pack/plugins/logstash/public/services/cluster/cluster_service.js mode change 100644 => 100755 x-pack/plugins/logstash/public/services/cluster/index.js mode change 100644 => 100755 x-pack/plugins/logstash/public/services/license/index.js mode change 100644 => 100755 x-pack/plugins/logstash/public/services/license/license_service.factory.js mode change 100644 => 100755 x-pack/plugins/logstash/public/services/license/logstash_license_service.js mode change 100644 => 100755 x-pack/plugins/logstash/public/services/monitoring/index.js mode change 100644 => 100755 x-pack/plugins/logstash/public/services/monitoring/monitoring_service.factory.js mode change 100644 => 100755 x-pack/plugins/logstash/public/services/monitoring/monitoring_service.js mode change 100644 => 100755 x-pack/plugins/logstash/public/services/pipeline/index.js mode change 100644 => 100755 x-pack/plugins/logstash/public/services/pipeline/pipeline_service.factory.js mode change 100644 => 100755 x-pack/plugins/logstash/public/services/pipeline/pipeline_service.js mode change 100644 => 100755 x-pack/plugins/logstash/public/services/pipelines/index.js mode change 100644 => 100755 x-pack/plugins/logstash/public/services/pipelines/pipelines_service.factory.js mode change 100644 => 100755 x-pack/plugins/logstash/public/services/pipelines/pipelines_service.js mode change 100644 => 100755 x-pack/plugins/logstash/public/services/security/index.js mode change 100644 => 100755 x-pack/plugins/logstash/public/services/security/logstash_security_service.js mode change 100644 => 100755 x-pack/plugins/logstash/public/services/security/security_service.factory.js mode change 100644 => 100755 x-pack/plugins/logstash/public/services/upgrade/index.js mode change 100644 => 100755 x-pack/plugins/logstash/public/services/upgrade/upgrade_service.factory.js mode change 100644 => 100755 x-pack/plugins/logstash/public/services/upgrade/upgrade_service.js delete mode 100644 x-pack/plugins/logstash/server/kibana.index.ts mode change 100644 => 100755 x-pack/plugins/logstash/server/lib/call_with_request_factory/call_with_request_factory.js mode change 100644 => 100755 x-pack/plugins/logstash/server/lib/call_with_request_factory/index.js mode change 100644 => 100755 x-pack/plugins/logstash/server/lib/check_license/__tests__/check_license.js mode change 100644 => 100755 x-pack/plugins/logstash/server/lib/check_license/check_license.js mode change 100644 => 100755 x-pack/plugins/logstash/server/lib/check_license/index.js mode change 100644 => 100755 x-pack/plugins/logstash/server/lib/error_wrappers/__tests__/wrap_custom_error.js create mode 100755 x-pack/plugins/logstash/server/lib/error_wrappers/__tests__/wrap_es_error.js mode change 100644 => 100755 x-pack/plugins/logstash/server/lib/error_wrappers/__tests__/wrap_unknown_error.js mode change 100644 => 100755 x-pack/plugins/logstash/server/lib/error_wrappers/index.js mode change 100644 => 100755 x-pack/plugins/logstash/server/lib/error_wrappers/wrap_custom_error.js mode change 100644 => 100755 x-pack/plugins/logstash/server/lib/error_wrappers/wrap_es_error.js mode change 100644 => 100755 x-pack/plugins/logstash/server/lib/error_wrappers/wrap_unknown_error.js mode change 100644 => 100755 x-pack/plugins/logstash/server/lib/fetch_all_from_scroll/__tests__/fetch_all_from_scroll.js mode change 100644 => 100755 x-pack/plugins/logstash/server/lib/fetch_all_from_scroll/fetch_all_from_scroll.js mode change 100644 => 100755 x-pack/plugins/logstash/server/lib/fetch_all_from_scroll/index.js mode change 100644 => 100755 x-pack/plugins/logstash/server/lib/license_pre_routing_factory/__tests__/license_pre_routing_factory.js mode change 100644 => 100755 x-pack/plugins/logstash/server/lib/license_pre_routing_factory/index.js mode change 100644 => 100755 x-pack/plugins/logstash/server/lib/license_pre_routing_factory/license_pre_routing_factory.js mode change 100644 => 100755 x-pack/plugins/logstash/server/lib/register_license_checker/index.js mode change 100644 => 100755 x-pack/plugins/logstash/server/lib/register_license_checker/register_license_checker.js delete mode 100644 x-pack/plugins/logstash/server/management_server.ts mode change 100644 => 100755 x-pack/plugins/logstash/server/models/cluster/__tests__/cluster.js mode change 100644 => 100755 x-pack/plugins/logstash/server/models/cluster/cluster.js mode change 100644 => 100755 x-pack/plugins/logstash/server/models/cluster/index.js mode change 100644 => 100755 x-pack/plugins/logstash/server/models/pipeline/__tests__/pipeline.js mode change 100644 => 100755 x-pack/plugins/logstash/server/models/pipeline/index.js mode change 100644 => 100755 x-pack/plugins/logstash/server/models/pipeline/pipeline.js mode change 100644 => 100755 x-pack/plugins/logstash/server/models/pipeline_list_item/__tests__/pipeline_list_item.js mode change 100644 => 100755 x-pack/plugins/logstash/server/models/pipeline_list_item/index.js mode change 100644 => 100755 x-pack/plugins/logstash/server/models/pipeline_list_item/pipeline_list_item.js delete mode 100644 x-pack/plugins/logstash/server/rest_api/beats/enroll.ts delete mode 100644 x-pack/plugins/logstash/server/rest_api/beats/list.ts delete mode 100644 x-pack/plugins/logstash/server/rest_api/beats/tag_assignment.ts delete mode 100644 x-pack/plugins/logstash/server/rest_api/beats/tag_removal.ts delete mode 100644 x-pack/plugins/logstash/server/rest_api/beats/update.ts delete mode 100644 x-pack/plugins/logstash/server/rest_api/beats/verify.ts delete mode 100644 x-pack/plugins/logstash/server/rest_api/tags/set.ts delete mode 100644 x-pack/plugins/logstash/server/rest_api/tokens/create.ts mode change 100644 => 100755 x-pack/plugins/logstash/server/routes/api/cluster/index.js mode change 100644 => 100755 x-pack/plugins/logstash/server/routes/api/cluster/register_cluster_routes.js mode change 100644 => 100755 x-pack/plugins/logstash/server/routes/api/cluster/register_load_route.js mode change 100644 => 100755 x-pack/plugins/logstash/server/routes/api/pipeline/index.js mode change 100644 => 100755 x-pack/plugins/logstash/server/routes/api/pipeline/register_delete_route.js mode change 100644 => 100755 x-pack/plugins/logstash/server/routes/api/pipeline/register_load_route.js mode change 100644 => 100755 x-pack/plugins/logstash/server/routes/api/pipeline/register_pipeline_routes.js mode change 100644 => 100755 x-pack/plugins/logstash/server/routes/api/pipeline/register_save_route.js mode change 100644 => 100755 x-pack/plugins/logstash/server/routes/api/pipelines/index.js mode change 100644 => 100755 x-pack/plugins/logstash/server/routes/api/pipelines/register_delete_route.js mode change 100644 => 100755 x-pack/plugins/logstash/server/routes/api/pipelines/register_list_route.js mode change 100644 => 100755 x-pack/plugins/logstash/server/routes/api/pipelines/register_pipelines_routes.js mode change 100644 => 100755 x-pack/plugins/logstash/server/routes/api/upgrade/index.js mode change 100644 => 100755 x-pack/plugins/logstash/server/routes/api/upgrade/register_execute_route.js mode change 100644 => 100755 x-pack/plugins/logstash/server/routes/api/upgrade/register_upgrade_routes.js delete mode 100644 x-pack/plugins/logstash/server/utils/README.md delete mode 100644 x-pack/plugins/logstash/server/utils/find_non_existent_items.ts delete mode 100644 x-pack/plugins/logstash/server/utils/polyfills.ts delete mode 100644 x-pack/plugins/logstash/server/utils/wrap_request.ts delete mode 100644 x-pack/plugins/logstash/tsconfig.json delete mode 100644 x-pack/plugins/logstash/wallaby.js create mode 100644 x-pack/plugins/monitoring/public/index.css diff --git a/package.json b/package.json index 8041ee396c2d7..4da2f4985606d 100644 --- a/package.json +++ b/package.json @@ -233,7 +233,7 @@ "@types/minimatch": "^2.0.29", "@types/node": "^8.10.20", "@types/prop-types": "^15.5.3", - "@types/react": "^16.3.14", + "@types/react": "^16.3.0", "@types/react-dom": "^16.0.5", "@types/react-redux": "^5.0.6", "@types/redux": "^3.6.31", diff --git a/x-pack/index.js b/x-pack/index.js index 2a91738c67a32..af575aac5be78 100644 --- a/x-pack/index.js +++ b/x-pack/index.js @@ -46,6 +46,6 @@ module.exports = function (kibana) { indexManagement(kibana), consoleExtensions(kibana), notifications(kibana), - kueryAutocomplete(kibana) + kueryAutocomplete(kibana), ]; }; diff --git a/x-pack/package.json b/x-pack/package.json index d550731832737..00a915d8bca9c 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -28,16 +28,13 @@ "@kbn/plugin-helpers": "link:../packages/kbn-plugin-helpers", "@kbn/test": "link:../packages/kbn-test", "@types/boom": "^4.3.8", - "@types/chance": "^1.0.1", - "@types/history": "^4.6.2", "@types/hapi": "15.0.1", "@types/jest": "^22.2.3", "@types/joi": "^10.4.0", + "@types/jsonwebtoken": "^7.2.8", "@types/lodash": "^3.10.0", "@types/pngjs": "^3.3.0", - "@types/react-router": "^4.0.30", "@types/react-router-dom": "^4.2.7", - "@types/sinon": "^5.0.1", "abab": "^1.0.4", "ansicolors": "0.3.2", "aws-sdk": "2.2.33", @@ -97,8 +94,6 @@ "@kbn/ui-framework": "link:../packages/kbn-ui-framework", "@samverschueren/stream-to-observable": "^0.3.0", "@slack/client": "^4.2.2", - "@types/elasticsearch": "^5.0.24", - "@types/jsonwebtoken": "^7.2.7", "@types/uuid": "^3.4.3", "angular-paging": "2.2.1", "angular-resource": "1.4.9", @@ -120,6 +115,7 @@ "elasticsearch": "^14.1.0", "extract-zip": "1.5.0", "font-awesome": "4.4.0", + "formsy-react": "^1.1.4", "get-port": "2.1.0", "getos": "^3.1.0", "glob": "6.0.4", diff --git a/x-pack/plugins/beats_management/common/domain_types.ts b/x-pack/plugins/beats_management/common/domain_types.ts index 42878c0b6bd34..e825cfe654bfb 100644 --- a/x-pack/plugins/beats_management/common/domain_types.ts +++ b/x-pack/plugins/beats_management/common/domain_types.ts @@ -3,10 +3,12 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { ClientSideBeatTag } from '../public/lib/lib'; import { ConfigurationBlockTypes } from './constants'; export interface ConfigurationBlock { type: ConfigurationBlockTypes; + description: string; block_yml: string; } @@ -30,7 +32,7 @@ export interface CMBeat { } export interface CMPopulatedBeat extends CMBeat { - full_tags: BeatTag[]; + full_tags: ClientSideBeatTag[]; } export interface BeatTag { diff --git a/x-pack/plugins/beats_management/index.ts b/x-pack/plugins/beats_management/index.ts index e6677d6bbcd57..951022bff9052 100644 --- a/x-pack/plugins/beats_management/index.ts +++ b/x-pack/plugins/beats_management/index.ts @@ -30,7 +30,6 @@ export function beats(kibana: any) { }, config: () => config, configPrefix, - init(server: any) { initServerWithKibana(server); }, diff --git a/x-pack/plugins/beats_management/public/components/config_list.tsx b/x-pack/plugins/beats_management/public/components/config_list.tsx new file mode 100644 index 0000000000000..36ea07cfc86f5 --- /dev/null +++ b/x-pack/plugins/beats_management/public/components/config_list.tsx @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +// @ts-ignore +import { EuiBasicTable, EuiLink } from '@elastic/eui'; +import React from 'react'; +import { supportedConfigs } from '../config_schemas'; +import { ClientSideConfigurationBlock } from '../lib/lib'; + +interface ComponentProps { + configs: ClientSideConfigurationBlock[]; + onConfigClick: (action: 'edit' | 'delete', config: ClientSideConfigurationBlock) => any; +} + +export const ConfigList: React.SFC = props => ( + { + const type = supportedConfigs.find((sc: any) => sc.value === config.type); + + return ( + props.onConfigClick('edit', config)}> + {type ? type.text : config.type} + + ); + }, + }, + { + field: 'block_obj.module', + name: 'Module', + truncateText: false, + render: (value: string) => { + return value || 'N/A'; + }, + }, + { + field: 'description', + name: 'Description', + }, + { + name: 'Actions', + actions: [ + { + name: 'Remove', + description: 'Remove this config from tag', + type: 'icon', + icon: 'trash', + onClick: (item: ClientSideConfigurationBlock) => props.onConfigClick('delete', item), + }, + ], + }, + ]} + /> +); diff --git a/x-pack/plugins/beats_management/public/components/inputs/code_editor.tsx b/x-pack/plugins/beats_management/public/components/inputs/code_editor.tsx new file mode 100644 index 0000000000000..b370de2429c74 --- /dev/null +++ b/x-pack/plugins/beats_management/public/components/inputs/code_editor.tsx @@ -0,0 +1,115 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +// @ts-ignore +import { CommonProps, EuiCodeEditor, EuiCodeEditorProps, EuiFormRow } from '@elastic/eui'; +// @ts-ignore +import { FormsyInputProps, withFormsy } from 'formsy-react'; +import React, { Component, InputHTMLAttributes } from 'react'; + +interface ComponentProps extends FormsyInputProps, CommonProps, EuiCodeEditorProps { + instantValidation: boolean; + label: string; + isReadOnly: boolean; + mode: 'javascript' | 'yaml'; + errorText: string; + fullWidth: boolean; + helpText: React.ReactElement; + compressed: boolean; + onChange(value: string): void; + onBlur(): void; +} + +interface ComponentState { + allowError: boolean; +} + +class CodeEditor extends Component< + InputHTMLAttributes & ComponentProps, + ComponentState +> { + public static defaultProps = { + passRequiredToField: true, + }; + + public state = { allowError: false }; + + public componentDidMount() { + const { defaultValue, setValue } = this.props; + if (defaultValue) { + setValue(defaultValue); + } + } + + public componentWillReceiveProps(nextProps: ComponentProps) { + if (nextProps.isFormSubmitted()) { + this.showError(); + } + } + + public handleChange = (value: string) => { + this.props.setValue(value); + if (this.props.onChange) { + this.props.onChange(value); + } + if (this.props.instantValidation) { + this.showError(); + } + }; + + public handleBlur = () => { + this.showError(); + if (this.props.onBlur) { + this.props.onBlur(); + } + }; + + public showError = () => this.setState({ allowError: true }); + + public render() { + const { + id, + label, + isReadOnly, + isValid, + getValue, + isPristine, + getErrorMessage, + mode, + fullWidth, + className, + helpText, + } = this.props; + + const { allowError } = this.state; + const error = !isPristine() && !isValid() && allowError; + + return ( + + + + ); + } +} + +export const FormsyEuiCodeEditor = withFormsy(CodeEditor); diff --git a/x-pack/plugins/beats_management/public/components/inputs/input.tsx b/x-pack/plugins/beats_management/public/components/inputs/input.tsx new file mode 100644 index 0000000000000..cfcb1de832098 --- /dev/null +++ b/x-pack/plugins/beats_management/public/components/inputs/input.tsx @@ -0,0 +1,111 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { CommonProps, EuiFieldText, EuiFieldTextProps, EuiFormRow } from '@elastic/eui'; +import { FormsyInputProps, withFormsy } from 'formsy-react'; +import React, { Component, InputHTMLAttributes } from 'react'; + +interface ComponentProps extends FormsyInputProps, CommonProps, EuiFieldTextProps { + instantValidation?: boolean; + label: string; + errorText: string; + fullWidth: boolean; + helpText: React.ReactElement; + compressed: boolean; + onChange?(e: React.ChangeEvent, value: any): void; + onBlur?(e: React.ChangeEvent, value: any): void; +} + +interface ComponentState { + allowError: boolean; +} + +class FieldText extends Component< + InputHTMLAttributes & ComponentProps, + ComponentState +> { + public static defaultProps = { + passRequiredToField: true, + }; + + public state = { allowError: false }; + + public componentDidMount() { + const { defaultValue, setValue } = this.props; + if (defaultValue) { + setValue(defaultValue); + } + } + + public componentWillReceiveProps(nextProps: ComponentProps) { + if (nextProps.isFormSubmitted()) { + this.showError(); + } + } + + public handleChange = (e: React.ChangeEvent) => { + const { value } = e.currentTarget; + this.props.setValue(value); + if (this.props.onChange) { + this.props.onChange(e, e.currentTarget.value); + } + if (this.props.instantValidation) { + this.showError(); + } + }; + + public handleBlur = (e: React.ChangeEvent) => { + this.showError(); + if (this.props.onBlur) { + this.props.onBlur(e, e.currentTarget.value); + } + }; + + public showError = () => this.setState({ allowError: true }); + + public render() { + const { + id, + required, + label, + getValue, + isValid, + isPristine, + getErrorMessage, + fullWidth, + className, + disabled, + helpText, + } = this.props; + + const { allowError } = this.state; + const error = !isPristine() && !isValid() && allowError; + + return ( + + + + ); + } +} + +export const FormsyEuiFieldText = withFormsy(FieldText); diff --git a/x-pack/plugins/beats_management/public/components/inputs/multi_input.tsx b/x-pack/plugins/beats_management/public/components/inputs/multi_input.tsx new file mode 100644 index 0000000000000..2608c5524e783 --- /dev/null +++ b/x-pack/plugins/beats_management/public/components/inputs/multi_input.tsx @@ -0,0 +1,113 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { CommonProps, EuiFormRow, EuiTextArea, EuiTextAreaProps } from '@elastic/eui'; +// @ts-ignore +import { FormsyInputProps, withFormsy } from 'formsy-react'; +import React, { Component, InputHTMLAttributes } from 'react'; + +interface ComponentProps extends FormsyInputProps, CommonProps, EuiTextAreaProps { + instantValidation: boolean; + label: string; + errorText: string; + fullWidth: boolean; + helpText: React.ReactElement; + compressed: boolean; + onChange(e: React.ChangeEvent, value: any): void; + onBlur(e: React.ChangeEvent, value: any): void; +} + +interface ComponentState { + allowError: boolean; +} + +class MultiFieldText extends Component< + InputHTMLAttributes & ComponentProps, + ComponentState +> { + public static defaultProps = { + passRequiredToField: true, + }; + + public state = { allowError: false }; + + public componentDidMount() { + const { defaultValue, setValue } = this.props; + + if (defaultValue) { + setValue(defaultValue); + } + } + + public componentWillReceiveProps(nextProps: ComponentProps) { + if (nextProps.isFormSubmitted()) { + this.showError(); + } + } + + public handleChange = (e: React.ChangeEvent) => { + const { value } = e.currentTarget; + this.props.setValue(value); + if (this.props.onChange) { + this.props.onChange(e, e.currentTarget.value); + } + if (this.props.instantValidation) { + this.showError(); + } + }; + + public handleBlur = (e: React.ChangeEvent) => { + this.showError(); + if (this.props.onBlur) { + this.props.onBlur(e, e.currentTarget.value); + } + }; + + public showError = () => this.setState({ allowError: true }); + + public render() { + const { + id, + required, + label, + getValue, + isValid, + isPristine, + getErrorMessage, + fullWidth, + className, + disabled, + helpText, + } = this.props; + + const { allowError } = this.state; + const error = !isPristine() && !isValid() && allowError; + + return ( + + + + ); + } +} + +export const FormsyEuiMultiFieldText = withFormsy(MultiFieldText); diff --git a/x-pack/plugins/beats_management/public/components/inputs/select.tsx b/x-pack/plugins/beats_management/public/components/inputs/select.tsx new file mode 100644 index 0000000000000..858637463bfd9 --- /dev/null +++ b/x-pack/plugins/beats_management/public/components/inputs/select.tsx @@ -0,0 +1,117 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +// @ts-ignore +import { CommonProps, EuiFormRow, EuiSelect } from '@elastic/eui'; +// @ts-ignore +import { FormsyInputProps, withFormsy } from 'formsy-react'; +import React, { Component, InputHTMLAttributes } from 'react'; + +interface ComponentProps extends FormsyInputProps, CommonProps { + instantValidation: boolean; + options: Array<{ value: string; text: string }>; + label: string; + errorText: string; + fullWidth: boolean; + helpText: React.ReactElement; + compressed: boolean; + onChange(e: React.ChangeEvent, value: any): void; + onBlur(e: React.ChangeEvent, value: any): void; +} + +interface ComponentState { + allowError: boolean; +} + +class FieldSelect extends Component< + InputHTMLAttributes & ComponentProps, + ComponentState +> { + public static defaultProps = { + passRequiredToField: true, + }; + + public state = { allowError: false }; + + public componentDidMount() { + const { defaultValue, setValue } = this.props; + if (defaultValue) { + setValue(defaultValue); + } + } + + public componentWillReceiveProps(nextProps: ComponentProps) { + if (nextProps.isFormSubmitted()) { + this.showError(); + } + } + + public handleChange = (e: React.ChangeEvent) => { + const { value } = e.currentTarget; + + this.props.setValue(value); + if (this.props.onChange) { + this.props.onChange(e, e.currentTarget.value); + } + if (this.props.instantValidation) { + this.showError(); + } + }; + + public handleBlur = (e: React.ChangeEvent) => { + this.showError(); + if (this.props.onBlur) { + this.props.onBlur(e, e.currentTarget.value); + } + }; + + public showError = () => this.setState({ allowError: true }); + + public render() { + const { + id, + required, + label, + options, + getValue, + isValid, + isPristine, + getErrorMessage, + fullWidth, + className, + disabled, + helpText, + } = this.props; + + const { allowError } = this.state; + const error = !isPristine() && !isValid() && allowError; + + return ( + + + + ); + } +} + +export const FormsyEuiSelect = withFormsy(FieldSelect); diff --git a/x-pack/plugins/beats_management/public/components/layouts/primary.tsx b/x-pack/plugins/beats_management/public/components/layouts/primary.tsx index 71a329c89f955..516ad648eac8b 100644 --- a/x-pack/plugins/beats_management/public/components/layouts/primary.tsx +++ b/x-pack/plugins/beats_management/public/components/layouts/primary.tsx @@ -6,7 +6,6 @@ import React from 'react'; import { withRouter } from 'react-router-dom'; -import styled from 'styled-components'; import { EuiModal, @@ -15,6 +14,8 @@ import { EuiPageBody, EuiPageContent, EuiPageContentBody, + EuiPageHeader, + EuiPageHeaderSection, EuiTitle, } from '@elastic/eui'; @@ -25,27 +26,21 @@ interface PrimaryLayoutProps { modalClosePath?: string; } -const HeaderContainer = styled.div` - display: flex; - justify-content: space-between; - align-items: flex-start; - padding: 24px 24px 0; - margin-bottom: 16px; -`; - export const PrimaryLayout: React.SFC = withRouter( ({ actionSection, title, modalRender, modalClosePath, children, history }) => { const modalContent = modalRender && modalRender(); return ( - - + +

{title}

- {actionSection} -
+ + {actionSection} + + {children}
diff --git a/x-pack/plugins/beats_management/public/components/table/table.tsx b/x-pack/plugins/beats_management/public/components/table/table.tsx index dc3e39ee0748c..7bfcd2c97de32 100644 --- a/x-pack/plugins/beats_management/public/components/table/table.tsx +++ b/x-pack/plugins/beats_management/public/components/table/table.tsx @@ -47,6 +47,7 @@ export class Table extends React.Component { }; public render() { + const { actionHandler, assignmentOptions, diff --git a/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx b/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx index e9111c994a337..7ddd11a11e807 100644 --- a/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx +++ b/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx @@ -128,11 +128,13 @@ export const TagsTableType: TableType = { field: 'id', name: 'Tag name', render: (id: string, tag: BeatTag) => ( - - {tag.id.length > TABLE_CONFIG.TRUNCATE_TAG_LENGTH - ? `${tag.id.substring(0, TABLE_CONFIG.TRUNCATE_TAG_LENGTH)}...` - : tag.id} - + + + {tag.id.length > TABLE_CONFIG.TRUNCATE_TAG_LENGTH + ? `${tag.id.substring(0, TABLE_CONFIG.TRUNCATE_TAG_LENGTH)}...` + : tag.id} + + ), sortable: true, width: '45%', diff --git a/x-pack/plugins/beats_management/public/components/tag/config_view/config_form.tsx b/x-pack/plugins/beats_management/public/components/tag/config_view/config_form.tsx new file mode 100644 index 0000000000000..f4cec9230c059 --- /dev/null +++ b/x-pack/plugins/beats_management/public/components/tag/config_view/config_form.tsx @@ -0,0 +1,186 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +// @ts-ignore +import Formsy, { addValidationRule, FieldValue, FormData } from 'formsy-react'; +import yaml from 'js-yaml'; +import { get } from 'lodash'; +import React from 'react'; +import { ClientSideConfigurationBlock, YamlConfigSchema } from '../../../lib/lib'; +import { FormsyEuiCodeEditor } from '../../inputs/code_editor'; +import { FormsyEuiFieldText } from '../../inputs/input'; +import { FormsyEuiMultiFieldText } from '../../inputs/multi_input'; +import { FormsyEuiSelect } from '../../inputs/select'; + +addValidationRule('isHost', (values: FormData, value: FieldValue) => { + return value && value.length > 0; +}); + +addValidationRule('isString', (values: FormData, value: FieldValue) => { + return value && value.length > 0; +}); + +addValidationRule('isPeriod', (values: FormData, value: FieldValue) => { + // TODO add more validation + return true; +}); + +addValidationRule('isPath', (values: FormData, value: FieldValue) => { + // TODO add more validation + return value && value.length > 0; +}); + +addValidationRule('isPaths', (values: FormData, value: FieldValue) => { + // TODO add more validation + + return true; +}); + +addValidationRule('isYaml', (values: FormData, value: FieldValue) => { + try { + const stuff = yaml.safeLoad(value || ''); + if (typeof stuff === 'string') { + return false; + } + return true; + } catch (e) { + return false; + } + return true; +}); + +interface ComponentProps { + values: ClientSideConfigurationBlock; + schema: YamlConfigSchema[]; + id: string; + canSubmit(canIt: boolean): any; + onSubmit(modal: any): any; +} + +export class ConfigForm extends React.Component { + private form = React.createRef(); + constructor(props: ComponentProps) { + super(props); + + this.state = { + canSubmit: false, + }; + } + + public enableButton = () => { + this.setState({ + canSubmit: true, + }); + this.props.canSubmit(true); + }; + public disableButton = () => { + this.setState({ + canSubmit: false, + }); + this.props.canSubmit(false); + }; + public submit = () => { + if (this.form.current) { + this.form.current.click(); + } + }; + public onValidSubmit = (model: ModelType) => { + const newModel: any = {}; + + Object.keys(model).forEach(field => { + const fieldSchema = this.props.schema.find(s => s.id === field); + if (fieldSchema && fieldSchema.parseValidResult) { + newModel[field] = fieldSchema.parseValidResult(model[field]); + } else { + newModel[field] = model[field]; + } + }); + this.props.onSubmit(newModel); + }; + public render() { + return ( +
+
+ + {this.props.schema.map(schema => { + switch (schema.ui.type) { + case 'input': + return ( + + ); + case 'multi-input': + return ( + + ); + case 'select': + return ( + + ); + case 'code': + return ( + + ); + } + })} +
+ ); + } +} diff --git a/x-pack/plugins/beats_management/public/components/tag/config_view/index.tsx b/x-pack/plugins/beats_management/public/components/tag/config_view/index.tsx new file mode 100644 index 0000000000000..c6a26bb021a7b --- /dev/null +++ b/x-pack/plugins/beats_management/public/components/tag/config_view/index.tsx @@ -0,0 +1,144 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + EuiButton, + EuiButtonEmpty, + // @ts-ignore + EuiCodeEditor, + EuiFieldText, + EuiFlexGroup, + EuiFlexItem, + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiFlyoutHeader, + EuiFormRow, + // @ts-ignore + EuiHorizontalRule, + // @ts-ignore + EuiSearchBar, + // @ts-ignore + EuiSelect, + // @ts-ignore + EuiTabbedContent, + EuiTitle, +} from '@elastic/eui'; +import React from 'react'; +import { supportedConfigs } from '../../../config_schemas'; +import { ClientSideConfigurationBlock } from '../../../lib/lib'; +import { ConfigForm } from './config_form'; + +interface ComponentProps { + configBlock?: ClientSideConfigurationBlock; + onClose(): any; + onSave(config: ClientSideConfigurationBlock): any; +} + +export class ConfigView extends React.Component { + private form = React.createRef(); + private editMode: boolean; + constructor(props: any) { + super(props); + this.editMode = props.configBlock !== undefined; + + this.state = { + valid: false, + configBlock: props.configBlock || { + type: supportedConfigs[0].value, + }, + }; + } + public onValueChange = (field: string) => (e: any) => { + const value = e.currentTarget ? e.currentTarget.value : e; + this.setState((state: any) => ({ + configBlock: { + ...state.configBlock, + [field]: value, + }, + })); + }; + public render() { + return ( + + + +

{this.editMode ? 'Edit Configuration' : 'Add Configuration'}

+
+
+ + + + + + + +

+ Config for  + { + (supportedConfigs.find(config => this.state.configBlock.type === config.value) as any) + .text + } +

+ + + { + this.props.onSave({ + ...this.state.configBlock, + block_obj: data, + }); + this.props.onClose(); + }} + canSubmit={canIt => this.setState({ valid: canIt })} + ref={this.form} + values={this.state.configBlock} + id={ + (supportedConfigs.find(config => this.state.configBlock.type === config.value) as any) + .value + } + schema={ + (supportedConfigs.find(config => this.state.configBlock.type === config.value) as any) + .config + } + /> +
+ + + + + Close + + + + { + if (this.form.current) { + this.form.current.submit(); + } + }} + > + Save + + + + +
+ ); + } +} diff --git a/x-pack/plugins/beats_management/public/components/tag/tag_edit.tsx b/x-pack/plugins/beats_management/public/components/tag/tag_edit.tsx index 2167d42b093ab..bc3b5315eba1a 100644 --- a/x-pack/plugins/beats_management/public/components/tag/tag_edit.tsx +++ b/x-pack/plugins/beats_management/public/components/tag/tag_edit.tsx @@ -8,46 +8,41 @@ import { // @ts-ignore EuiBadge, EuiButton, - EuiButtonEmpty, - // @ts-ignore - EuiCodeEditor, // @ts-ignore EuiColorPicker, EuiFieldText, EuiFlexGroup, EuiFlexItem, - EuiFlyout, - EuiFlyoutBody, - EuiFlyoutFooter, - EuiFlyoutHeader, // @ts-ignore EuiForm, EuiFormRow, - EuiPanel, - // @ts-ignore - EuiSearchBar, + EuiHorizontalRule, EuiSpacer, - // @ts-ignore - EuiTabbedContent, EuiText, EuiTitle, } from '@elastic/eui'; import 'brace/mode/yaml'; import 'brace/theme/github'; +import { isEqual } from 'lodash'; import React from 'react'; -import { BeatTag, CMBeat } from '../../../common/domain_types'; +import { CMBeat } from '../../../common/domain_types'; +import { ClientSideBeatTag, ClientSideConfigurationBlock } from '../../lib/lib'; +import { ConfigList } from '../config_list'; import { Table } from '../table'; import { BeatsTableType } from '../table'; +import { ConfigView } from './config_view'; interface TagEditProps { - tag: Partial; - onTagChange: (field: keyof BeatTag, value: string) => any; + mode: 'edit' | 'create'; + tag: Pick>; + onTagChange: (field: keyof ClientSideBeatTag, value: string) => any; attachedBeats: CMBeat[] | null; } interface TagEditState { showFlyout: boolean; tableRef: any; + selectedConfigIndex?: number; } export class TagEdit extends React.PureComponent { @@ -64,77 +59,108 @@ export class TagEdit extends React.PureComponent { const { tag, attachedBeats } = this.props; return (
- - - - -

Define this tag

-
- -

- Tags will apply a set configuration to a group of beats. -
- The tag type defines the options available. -

-
-
- - {tag.id ? tag.id : 'Tag name'} - -
-
- - - - - + + + +

Define this tag

+
+ +

+ Tags will apply a set configuration to a group of beats. +
+ The tag type defines the options available. +

+
+
+ + {tag.id ? tag.id : 'Tag name'} + +
+
+ + + + + + {this.props.mode === 'create' && ( - - -
-
+ )} + + + - - - - -

Configurations

-
- -

- You can have multiple configurations applied to an individual tag. These - configurations can repeat or mix types as necessary. For example, you may utilize - three metricbeat configurations alongside one input and filebeat configuration. -

-
-
- -
- Add a new configuration -
-
-
-
+ + + + + +

Configurations

+
+ +

+ You can have multiple configurations applied to an individual tag. These + configurations can repeat or mix types as necessary. For example, you may utilize + three metricbeat configurations alongside one input and filebeat configuration. +

+
+
+ +
+ { + const selectedIndex = tag.configurations.findIndex(c => { + return isEqual(config, c); + }); + if (action === 'delete') { + const configs = [...tag.configurations]; + configs.splice(selectedIndex, 1); + this.updateTag('configurations', configs); + } else { + this.setState({ + showFlyout: true, + selectedConfigIndex: selectedIndex, + }); + } + }} + /> +
+ { + this.setState({ showFlyout: true }); + }} + > + Add a new configuration + +
+
+
{attachedBeats && ( - +
+ +

Attached Beats

{ - /* TODO: handle assignment/delete actions */ + /* TODO: this prop should be optional */ }} assignmentOptions={[]} assignmentTitle={null} @@ -143,73 +169,44 @@ export class TagEdit extends React.PureComponent { showAssignmentOptions={false} type={BeatsTableType} /> - + )} {this.state.showFlyout && ( - this.setState({ showFlyout: false })}> - - -

Add Configuration

-
-
- - - { - // TODO: handle search changes - }} - /> - - - { - // TODO: update field value - }} - placeholder="Description (optional)" - /> - - Add configuration options here, - }, - { - id: 'yaml_editor', - name: 'YAML Editor', - content: , - }, - ]} - /> - - - - - this.setState({ showFlyout: false })} - > - Close - - - - Save - - - -
+ this.setState({ showFlyout: false, selectedConfigIndex: undefined })} + onSave={(config: any) => { + this.setState({ showFlyout: false, selectedConfigIndex: undefined }); + if (this.state.selectedConfigIndex !== undefined) { + const configs = [...tag.configurations]; + configs[this.state.selectedConfigIndex] = config; + this.updateTag('configurations', configs); + } else { + this.updateTag('configurations', [...tag.configurations, config]); + } + }} + /> )} ); } - private openConfigFlyout = () => { - this.setState({ - showFlyout: true, - }); + private getNameError = (name: string) => { + if (name && name !== '' && name.search(/^[a-zA-Z0-9-]+$/) === -1) { + return 'Tag name must consist of letters, numbers, and dashes only'; + } else { + return false; + } }; - private updateTag = (key: keyof BeatTag) => (e: any) => - this.props.onTagChange(key, e.target ? e.target.value : e); + + // TODO this should disable save button on bad validations + private updateTag = (key: keyof ClientSideBeatTag, value?: any) => + value !== undefined + ? this.props.onTagChange(key, value) + : (e: any) => this.props.onTagChange(key, e.target ? e.target.value : e); } diff --git a/x-pack/plugins/beats_management/public/config_schemas.ts b/x-pack/plugins/beats_management/public/config_schemas.ts new file mode 100644 index 0000000000000..1b7da87ef8d58 --- /dev/null +++ b/x-pack/plugins/beats_management/public/config_schemas.ts @@ -0,0 +1,200 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { YamlConfigSchema } from './lib/lib'; + +const filebeatInputConfig: YamlConfigSchema[] = [ + { + id: 'paths', + ui: { + label: 'Paths', + type: 'multi-input', + }, + validations: 'isPaths', + error: 'One file path per line', + required: true, + parseValidResult: v => v.split('\n'), + }, + { + id: 'other', + ui: { + label: 'Other Config', + type: 'code', + }, + validations: 'isYaml', + error: 'Config entered must be in valid YAML format', + }, +]; + +const filebeatModuleConfig: YamlConfigSchema[] = [ + { + id: 'module', + ui: { + label: 'Module', + type: 'select', + }, + options: [ + { + value: 'system', + text: 'system', + }, + { + value: 'apache2', + text: 'apache2', + }, + { + value: 'nginx', + text: 'nginx', + }, + { + value: 'mongodb', + text: 'mongodb', + }, + { + value: 'elasticsearch', + text: 'elasticsearch', + }, + ], + error: 'Please select a module', + required: true, + }, + { + id: 'other', + ui: { + label: 'Other Config', + type: 'code', + }, + validations: 'isYaml', + error: 'Config entered must be in valid YAML format', + }, +]; + +const metricbeatModuleConfig: YamlConfigSchema[] = [ + { + id: 'module', + ui: { + label: 'Module', + type: 'select', + }, + options: [ + { + value: 'system', + text: 'system', + }, + { + value: 'apache2', + text: 'apache2', + }, + { + value: 'nginx', + text: 'nginx', + }, + { + value: 'mongodb', + text: 'mongodb', + }, + ], + error: 'Please select a module', + required: true, + }, + { + id: 'hosts', + ui: { + label: 'Hosts', + type: 'multi-input', + }, + validations: 'isHost', + error: 'One file host per line', + required: true, + parseValidResult: v => v.split('\n'), + }, + { + id: 'period', + ui: { + label: 'Period', + type: 'multi-input', + }, + defaultValue: '10s', + validations: 'isPeriod', + error: 'Invalid Period, must be formatted as `10s` for 10 seconds', + required: true, + parseValidResult: v => v.split('\n'), + }, + { + id: 'other', + ui: { + label: 'Other Config', + type: 'code', + }, + validations: 'isYaml', + error: 'Config entered must be in valid YAML format', + }, +]; + +// const outputConfig: YamlConfigSchema[] = [ +// { +// id: 'output', +// ui: { +// label: 'Output Type', +// type: 'select', +// }, +// options: [ +// { +// value: 'elasticsearch', +// text: 'Elasticsearch', +// }, +// { +// value: 'logstash', +// text: 'Logstash', +// }, +// { +// value: 'kafka', +// text: 'Kafka', +// }, +// { +// value: 'console', +// text: 'Console', +// }, +// ], +// error: 'Please select an output type', +// required: true, +// }, +// { +// id: 'hosts', +// ui: { +// label: 'Hosts', +// type: 'multi-input', +// }, +// validations: 'isHost', +// error: 'One file host per line', +// parseValidResult: v => v.split('\n'), +// }, +// { +// id: 'username', +// ui: { +// label: 'Username', +// type: 'input', +// }, +// validations: 'isString', +// error: 'Unprocessable username', +// }, +// { +// id: 'password', +// ui: { +// label: 'Password', +// type: 'input', +// }, +// validations: 'isString', +// error: 'Unprocessable password', +// }, +// ]; + +export const supportedConfigs = [ + { text: 'Filebeat Input', value: 'filebeat.inputs', config: filebeatInputConfig }, + { text: 'Filebeat Module', value: 'filebeat.modules', config: filebeatModuleConfig }, + { text: 'Metricbeat Input', value: 'metricbeat.modules', config: metricbeatModuleConfig }, + // { text: 'Output', value: 'output', config: outputConfig }, +]; diff --git a/x-pack/plugins/beats_management/public/lib/adapters/beats/adapter_types.ts b/x-pack/plugins/beats_management/public/lib/adapters/beats/adapter_types.ts index c47ea7a4c6350..aaa41871cd068 100644 --- a/x-pack/plugins/beats_management/public/lib/adapters/beats/adapter_types.ts +++ b/x-pack/plugins/beats_management/public/lib/adapters/beats/adapter_types.ts @@ -9,6 +9,7 @@ import { CMBeat } from '../../../../common/domain_types'; export interface CMBeatsAdapter { get(id: string): Promise; update(id: string, beatData: Partial): Promise; + getBeatsWithTag(tagId: string): Promise; getAll(): Promise; removeTagsFromBeats(removals: BeatsTagAssignment[]): Promise; assignTagsToBeats(assignments: BeatsTagAssignment[]): Promise; diff --git a/x-pack/plugins/beats_management/public/lib/adapters/beats/memory_beats_adapter.ts b/x-pack/plugins/beats_management/public/lib/adapters/beats/memory_beats_adapter.ts index 745a11ac65464..e66e7e45b5c4f 100644 --- a/x-pack/plugins/beats_management/public/lib/adapters/beats/memory_beats_adapter.ts +++ b/x-pack/plugins/beats_management/public/lib/adapters/beats/memory_beats_adapter.ts @@ -39,6 +39,10 @@ export class MemoryBeatsAdapter implements CMBeatsAdapter { public async getAll() { return this.beatsDB.map((beat: any) => omit(beat, ['access_token'])); } + public async getBeatsWithTag(tagId: string): Promise { + return this.beatsDB.map((beat: any) => omit(beat, ['access_token'])); + } + public async getBeatWithToken(enrollmentToken: string): Promise { return this.beatsDB.map((beat: any) => omit(beat, ['access_token']))[0]; } diff --git a/x-pack/plugins/beats_management/public/lib/adapters/beats/rest_beats_adapter.ts b/x-pack/plugins/beats_management/public/lib/adapters/beats/rest_beats_adapter.ts index 30a052a182945..e0e8051e14a94 100644 --- a/x-pack/plugins/beats_management/public/lib/adapters/beats/rest_beats_adapter.ts +++ b/x-pack/plugins/beats_management/public/lib/adapters/beats/rest_beats_adapter.ts @@ -25,7 +25,11 @@ export class RestBeatsAdapter implements CMBeatsAdapter { } public async getAll(): Promise { - return (await this.REST.get<{ beats: CMBeat[] }>('/api/beats/agents')).beats; + return (await this.REST.get<{ beats: CMBeat[] }>('/api/beats/agents/all')).beats; + } + + public async getBeatsWithTag(tagId: string): Promise { + return (await this.REST.get<{ beats: CMBeat[] }>(`/api/beats/agents/tag/${tagId}`)).beats; } public async update(id: string, beatData: Partial): Promise { diff --git a/x-pack/plugins/beats_management/public/lib/compose/kibana.ts b/x-pack/plugins/beats_management/public/lib/compose/kibana.ts index 3c445ea8aaee5..0d313c3184356 100644 --- a/x-pack/plugins/beats_management/public/lib/compose/kibana.ts +++ b/x-pack/plugins/beats_management/public/lib/compose/kibana.ts @@ -13,18 +13,20 @@ import { management } from 'ui/management'; import { uiModules } from 'ui/modules'; // @ts-ignore: path dynamic for kibana import routes from 'ui/routes'; +import { supportedConfigs } from '../../config_schemas'; import { RestBeatsAdapter } from '../adapters/beats/rest_beats_adapter'; import { KibanaFrameworkAdapter } from '../adapters/framework/kibana_framework_adapter'; import { AxiosRestAPIAdapter } from '../adapters/rest_api/axios_rest_api_adapter'; import { RestTagsAdapter } from '../adapters/tags/rest_tags_adapter'; import { RestTokensAdapter } from '../adapters/tokens/rest_tokens_adapter'; +import { BeatsLib } from '../domains/beats'; +import { TagsLib } from '../domains/tags'; import { FrontendDomainLibs, FrontendLibs } from '../lib'; -import { BeatsLib } from './../domains/beats'; export function compose(): FrontendLibs { const api = new AxiosRestAPIAdapter(chrome.getXsrfToken(), chrome.getBasePath()); - const tags = new RestTagsAdapter(api); + const tags = new TagsLib(new RestTagsAdapter(api), supportedConfigs); const tokens = new RestTokensAdapter(api); const beats = new BeatsLib(new RestBeatsAdapter(api), { tags, diff --git a/x-pack/plugins/beats_management/public/lib/compose/memory.ts b/x-pack/plugins/beats_management/public/lib/compose/memory.ts index ef65d77229ec9..e5f515014652c 100644 --- a/x-pack/plugins/beats_management/public/lib/compose/memory.ts +++ b/x-pack/plugins/beats_management/public/lib/compose/memory.ts @@ -17,12 +17,16 @@ import { KibanaFrameworkAdapter } from '../adapters/framework/kibana_framework_a import { MemoryTagsAdapter } from '../adapters/tags/memory_tags_adapter'; import { MemoryTokensAdapter } from '../adapters/tokens/memory_tokens_adapter'; +import { BeatsLib } from '../domains/beats'; import { FrontendDomainLibs, FrontendLibs } from '../lib'; +import { supportedConfigs } from '../../config_schemas'; +import { TagsLib } from '../domains/tags'; + export function compose(): FrontendLibs { - const tags = new MemoryTagsAdapter([]); + const tags = new TagsLib(new MemoryTagsAdapter([]), supportedConfigs); const tokens = new MemoryTokensAdapter(); - const beats = new MemoryBeatsAdapter([]); + const beats = new BeatsLib(new MemoryBeatsAdapter([]), { tags }); const domainLibs: FrontendDomainLibs = { tags, diff --git a/x-pack/plugins/beats_management/public/lib/domains/beats.ts b/x-pack/plugins/beats_management/public/lib/domains/beats.ts index d507cc764de83..a0aeaabbcfe67 100644 --- a/x-pack/plugins/beats_management/public/lib/domains/beats.ts +++ b/x-pack/plugins/beats_management/public/lib/domains/beats.ts @@ -12,12 +12,12 @@ import { CMAssignmentReturn, CMBeatsAdapter, } from '../adapters/beats/adapter_types'; -import { CMTagsAdapter } from './../adapters/tags/adapter_types'; +import { FrontendDomainLibs } from '../lib'; export class BeatsLib { constructor( private readonly adapter: CMBeatsAdapter, - private readonly libs: { tags: CMTagsAdapter } + private readonly libs: { tags: FrontendDomainLibs['tags'] } ) {} public async get(id: string): Promise { @@ -30,6 +30,11 @@ export class BeatsLib { return beat; } + public async getBeatsWithTag(tagId: string): Promise { + const beats = await this.adapter.getBeatsWithTag(tagId); + return await this.mergeInTags(beats); + } + public async getAll(): Promise { const beats = await this.adapter.getAll(); return await this.mergeInTags(beats); diff --git a/x-pack/plugins/beats_management/public/lib/domains/tags.ts b/x-pack/plugins/beats_management/public/lib/domains/tags.ts new file mode 100644 index 0000000000000..3f09cc23ae926 --- /dev/null +++ b/x-pack/plugins/beats_management/public/lib/domains/tags.ts @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import yaml from 'js-yaml'; +import { omit } from 'lodash'; +import { BeatTag, ConfigurationBlock } from '../../../common/domain_types'; +import { CMTagsAdapter } from '../adapters/tags/adapter_types'; +import { ClientSideBeatTag, ClientSideConfigurationBlock } from '../lib'; + +export class TagsLib { + constructor(private readonly adapter: CMTagsAdapter, private readonly tagConfigs: any) {} + + public async getTagsWithIds(tagIds: string[]): Promise { + return this.tagYamlToConfigs(await this.adapter.getTagsWithIds(tagIds)); + } + public async delete(tagIds: string[]): Promise { + return await this.adapter.delete(tagIds); + } + public async getAll(): Promise { + return this.tagYamlToConfigs(await this.adapter.getAll()); + } + public async upsertTag(tag: ClientSideBeatTag): Promise { + tag.id = tag.id.replace(' ', '-'); + return await this.adapter.upsertTag(this.tagConfigsToYaml([tag])[0]); + } + + private tagYamlToConfigs(tags: BeatTag[]): ClientSideBeatTag[] { + return tags.map(tag => { + const transformedTag: ClientSideBeatTag = tag as any; + // configuration_blocks yaml, JS cant read YAML so we parse it into JS, + // because beats flattens all fields, and we need more structure. + // we take tagConfigs, grab the config that applies here, render what we can into + // an object, and the rest we assume to be the yaml string that goes + // into the yaml editor... + // NOTE: The perk of this, is that as we support more features via controls + // vs yaml editing, it should "just work", and things that were in YAML + // will now be in the UI forms... + transformedTag.configurations = tag.configuration_blocks.map(block => { + const { type, description, block_yml } = block; + const rawConfig = yaml.safeLoad(block_yml); + const thisConfig = this.tagConfigs.find((conf: any) => conf.value === type).config; + const knownConfigIds = thisConfig.map((config: any) => config.id); + + return { + type, + description, + block_obj: knownConfigIds.reduce((blockObj: any, id: string) => { + blockObj[id] = + id === 'other' ? yaml.dump(omit(rawConfig, knownConfigIds)) : rawConfig[id]; + + return blockObj; + }, {}), + } as ClientSideConfigurationBlock; + }); + return transformedTag; + }); + } + + private tagConfigsToYaml(tags: ClientSideBeatTag[]): BeatTag[] { + return tags.map(tag => { + const transformedTag: BeatTag = tag as any; + // configurations is the JS representation of the config yaml, + // so here we take that JS and convert it into a YAML string. + // we do so while also flattening "other" into the flat yaml beats expect + transformedTag.configuration_blocks = tag.configurations.map(block => { + const { type, description, block_obj } = block; + const { other, ...configs } = block_obj; + return { + type, + description, + block_yml: yaml.safeDump({ ...configs, ...yaml.safeLoad(other) }), + } as ConfigurationBlock; + }); + return transformedTag; + }); + } +} diff --git a/x-pack/plugins/beats_management/public/lib/lib.ts b/x-pack/plugins/beats_management/public/lib/lib.ts index c9b3c8f1b4092..67659914efc01 100644 --- a/x-pack/plugins/beats_management/public/lib/lib.ts +++ b/x-pack/plugins/beats_management/public/lib/lib.ts @@ -7,13 +7,14 @@ import { IModule, IScope } from 'angular'; import { AxiosRequestConfig } from 'axios'; import React from 'react'; -import { CMTagsAdapter } from './adapters/tags/adapter_types'; +import { ConfigurationBlock } from '../../common/domain_types'; import { CMTokensAdapter } from './adapters/tokens/adapter_types'; import { BeatsLib } from './domains/beats'; +import { TagsLib } from './domains/tags'; export interface FrontendDomainLibs { beats: BeatsLib; - tags: CMTagsAdapter; + tags: TagsLib; tokens: CMTokensAdapter; } @@ -21,6 +22,76 @@ export interface FrontendLibs extends FrontendDomainLibs { framework: FrameworkAdapter; } +export enum FilebeatModuleName { + system = 'system', + apache2 = 'apache2', + nginx = 'nginx', + mongodb = 'mongodb', + elasticsearch = 'elasticsearch', +} + +export enum MetricbeatModuleName { + system = 'system', + apache2 = 'apache2', + nginx = 'nginx', + mongodb = 'mongodb', + elasticsearch = 'elasticsearch', +} + +export enum OutputType { + elasticsearch = 'elasticsearch', + logstash = 'logstash', + kafka = 'kafka', + console = 'console', +} + +export type ClientConfigContent = + | FilebeatInputsConfig + | FilebeatModuleConfig + | MetricbeatModuleConfig; + +export interface YamlConfigSchema { + id: string; + ui: { + label: string; + type: 'input' | 'multi-input' | 'select' | 'code'; + helpText?: string; + }; + options?: Array<{ value: string; text: string }>; + validations?: 'isHost' | 'isString' | 'isPeriod' | 'isPath' | 'isPaths' | 'isYaml'; + error: string; + defaultValue?: string; + required?: boolean; + parseValidResult?: (value: any) => any; +} + +export interface ClientSideBeatTag { + id: string; + configurations: ClientSideConfigurationBlock[]; + color?: string; + last_updated: Date; +} + +export interface ClientSideConfigurationBlock + extends Pick> { + block_obj: ClientConfigContent; +} + +export interface FilebeatInputsConfig { + paths: string[]; + other: string; +} +export interface FilebeatModuleConfig { + module: FilebeatModuleName; + other: string; +} +export interface MetricbeatModuleConfig { + module: MetricbeatModuleName; + hosts: string[]; + period: string; + other: string; +} + export interface FrameworkAdapter { // Insstance vars appState?: object; diff --git a/x-pack/plugins/beats_management/public/pages/main/beats.tsx b/x-pack/plugins/beats_management/public/pages/main/beats.tsx index e8524299a0afe..d3980400ced34 100644 --- a/x-pack/plugins/beats_management/public/pages/main/beats.tsx +++ b/x-pack/plugins/beats_management/public/pages/main/beats.tsx @@ -11,10 +11,10 @@ import { } from '@elastic/eui'; import React from 'react'; -import { BeatTag, CMPopulatedBeat } from '../../../common/domain_types'; +import { CMBeat, CMPopulatedBeat } from '../../../common/domain_types'; import { BeatsTagAssignment } from '../../../server/lib/adapters/beats/adapter_types'; import { BeatsTableType, Table } from '../../components/table'; -import { FrontendLibs } from '../../lib/lib'; +import { ClientSideBeatTag, FrontendLibs } from '../../lib/lib'; import { BeatsActionArea } from './beats_action_area'; interface BeatsPageProps { @@ -23,19 +23,19 @@ interface BeatsPageProps { } interface BeatsPageState { - beats: CMPopulatedBeat[]; + beats: CMBeat[]; + tableRef: any; tags: any[] | null; } export class BeatsPage extends React.PureComponent { public static ActionArea = BeatsActionArea; - private tableRef = React.createRef
(); - constructor(props: BeatsPageProps) { super(props); this.state = { beats: [], + tableRef: React.createRef(), tags: null, }; @@ -51,7 +51,11 @@ export class BeatsPage extends React.PureComponent { + assignmentTitle="Set tags" + items={this.state.beats || []} + ref={this.state.tableRef} + showAssignmentOptions={true} + renderAssignmentOptions={(tag: ClientSideBeatTag) => { const selectedBeats = this.getSelectedBeats(); const hasMatches = selectedBeats.some((beat: any) => (beat.tags || []).some((t: string) => t === tag.id) @@ -74,10 +78,6 @@ export class BeatsPage extends React.PureComponent ); }} - assignmentTitle="Set tags" - items={this.state.beats || []} - ref={this.tableRef} - showAssignmentOptions={true} type={BeatsTableType} /> ); @@ -111,9 +111,6 @@ export class BeatsPage extends React.PureComponent { await this.loadBeats(); - if (this.tableRef && this.tableRef.current) { - this.tableRef.current.resetSelection(); - } }, 100); }; @@ -124,7 +121,7 @@ export class BeatsPage extends React.PureComponent { // await this.props.libs.beats.searach(query); }; @@ -139,26 +136,22 @@ export class BeatsPage extends React.PureComponent beats.map(({ id }) => ({ beatId: id, tag: tag.id })); - private removeTagsFromBeats = async (beats: CMPopulatedBeat[], tag: BeatTag) => { + private removeTagsFromBeats = async (beats: CMPopulatedBeat[], tag: ClientSideBeatTag) => { await this.props.libs.beats.removeTagsFromBeats(this.createBeatTagAssignments(beats, tag)); - this.loadBeats(); + await this.loadBeats(); + await this.loadTags(); }; - private assignTagsToBeats = async (beats: CMPopulatedBeat[], tag: BeatTag) => { + private assignTagsToBeats = async (beats: CMPopulatedBeat[], tag: ClientSideBeatTag) => { await this.props.libs.beats.assignTagsToBeats(this.createBeatTagAssignments(beats, tag)); await this.loadBeats(); await this.loadTags(); }; private getSelectedBeats = (): CMPopulatedBeat[] => { - if (this.tableRef && this.tableRef.current) { - return this.tableRef.current.state.selection.map( - (beat: CMPopulatedBeat) => this.state.beats.find(b => b.id === beat.id) || beat - ); - } - return []; + return this.state.tableRef.current.state.selection; }; } diff --git a/x-pack/plugins/beats_management/public/pages/main/tags.tsx b/x-pack/plugins/beats_management/public/pages/main/tags.tsx index 73b39a215a46d..b8a5c36a1ed0b 100644 --- a/x-pack/plugins/beats_management/public/pages/main/tags.tsx +++ b/x-pack/plugins/beats_management/public/pages/main/tags.tsx @@ -17,7 +17,7 @@ import React from 'react'; import { BeatTag, CMBeat } from '../../../common/domain_types'; import { BeatsTagAssignment } from '../../../server/lib/adapters/beats/adapter_types'; import { Table, TagsTableType } from '../../components/table'; -import { FrontendLibs } from '../../lib/lib'; +import { ClientSideBeatTag, FrontendLibs } from '../../lib/lib'; interface TagsPageProps { libs: FrontendLibs; @@ -25,7 +25,7 @@ interface TagsPageProps { interface TagsPageState { beats: any; - tags: BeatTag[]; + tags: ClientSideBeatTag[]; } export class TagsPage extends React.PureComponent { @@ -37,10 +37,11 @@ export class TagsPage extends React.PureComponent history.push(`/tag/create`); }} > - Create Tag + Add Tag ); - public tableRef = React.createRef
(); + private tableRef = React.createRef
(); + constructor(props: TagsPageProps) { super(props); @@ -57,7 +58,7 @@ export class TagsPage extends React.PureComponent
); } - private handleTagsAction = async (action: string) => { + private handleTagsAction = async (action: string, payload: any) => { switch (action) { case 'loadAssignmentOptions': this.loadBeats(); @@ -166,10 +167,7 @@ export class TagsPage extends React.PureComponent await this.props.libs.beats.assignTagsToBeats(assignments); }; - private getSelectedTags = (): BeatTag[] => { - if (this.tableRef && this.tableRef.current) { - return this.tableRef.current.state.selection; - } - return []; + private getSelectedTags = () => { + return this.tableRef.current ? this.tableRef.current.state.selection : []; }; } diff --git a/x-pack/plugins/beats_management/public/pages/tag/create.tsx b/x-pack/plugins/beats_management/public/pages/tag/create.tsx deleted file mode 100644 index 5a319a04aad7f..0000000000000 --- a/x-pack/plugins/beats_management/public/pages/tag/create.tsx +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { EuiButton, EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; -import 'brace/mode/yaml'; -import 'brace/theme/github'; -import React from 'react'; -import { BeatTag } from '../../../common/domain_types'; -import { TagEdit } from '../../components/tag'; -import { FrontendLibs } from '../../lib/lib'; - -interface CreateTagPageProps { - libs: FrontendLibs; - history: any; -} - -interface CreateTagPageState { - showFlyout: boolean; - tag: BeatTag; -} - -export class CreateTagPage extends React.PureComponent { - constructor(props: CreateTagPageProps) { - super(props); - - this.state = { - showFlyout: false, - tag: { - id: '', - color: '#DD0A73', - configuration_blocks: [], - last_updated: new Date(), - }, - }; - } - - public render() { - return ( -
- - this.setState(oldState => ({ - tag: { ...oldState.tag, [field]: value }, - })) - } - attachedBeats={null} - /> - - - - - Save - - - - this.props.history.push('/overview/tags')}> - Cancel - - - -
- ); - } - - private saveTag = async () => { - await this.props.libs.tags.upsertTag(this.state.tag as BeatTag); - this.props.history.push('/overview/tags'); - }; -} diff --git a/x-pack/plugins/beats_management/public/pages/tag/edit.tsx b/x-pack/plugins/beats_management/public/pages/tag/edit.tsx deleted file mode 100644 index 386cf2cc18eb0..0000000000000 --- a/x-pack/plugins/beats_management/public/pages/tag/edit.tsx +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import 'brace/mode/yaml'; -import 'brace/theme/github'; -import React from 'react'; -import { BeatTag } from '../../../common/domain_types'; -import { TagEdit } from '../../components/tag'; -import { FrontendLibs } from '../../lib/lib'; - -interface EditTagPageProps { - libs: FrontendLibs; -} - -interface EditTagPageState { - showFlyout: boolean; - tag: Partial; -} - -export class EditTagPage extends React.PureComponent { - constructor(props: EditTagPageProps) { - super(props); - - this.state = { - showFlyout: false, - tag: { - id: '', - color: '#DD0A73', - configuration_blocks: [], - }, - }; - } - - public render() { - return ( - - this.setState(oldState => ({ - tag: { ...oldState.tag, [field]: value }, - })) - } - attachedBeats={[]} - /> - ); - } -} diff --git a/x-pack/plugins/beats_management/public/pages/tag/index.tsx b/x-pack/plugins/beats_management/public/pages/tag/index.tsx index 1a1054cd1ab28..af9840c63fefc 100644 --- a/x-pack/plugins/beats_management/public/pages/tag/index.tsx +++ b/x-pack/plugins/beats_management/public/pages/tag/index.tsx @@ -4,55 +4,108 @@ * you may not use this file except in compliance with the Elastic License. */ +import { EuiButton, EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import 'brace/mode/yaml'; + import 'brace/theme/github'; import React from 'react'; -import { Route, Switch } from 'react-router'; -import { ConfigurationBlock } from '../../../common/domain_types'; +import { CMPopulatedBeat } from '../../../common/domain_types'; import { PrimaryLayout } from '../../components/layouts/primary'; -import { FrontendLibs } from '../../lib/lib'; -import { CreateTagPage } from './create'; -import { EditTagPage } from './edit'; -interface EditTagPageProps { +import { TagEdit } from '../../components/tag'; +import { ClientSideBeatTag, FrontendLibs } from '../../lib/lib'; + +interface TagPageProps { libs: FrontendLibs; history: any; + match: any; } -interface EditTagPageState { - color: string | null; - configurationBlocks: ConfigurationBlock[]; +interface TagPageState { showFlyout: boolean; - tagName: string | null; + attachedBeats: CMPopulatedBeat[] | null; + tag: ClientSideBeatTag; } -export class TagPage extends React.PureComponent { - constructor(props: EditTagPageProps) { +export class TagPage extends React.PureComponent { + private mode: 'edit' | 'create' = 'create'; + constructor(props: TagPageProps) { super(props); - this.state = { - color: '#DD0A73', - configurationBlocks: [], showFlyout: false, - tagName: null, + attachedBeats: null, + tag: { + id: props.match.params.action === 'create' ? '' : props.match.params.tagid, + color: '#DD0A73', + configurations: [], + last_updated: new Date(), + }, }; + + if (props.match.params.action !== 'create') { + this.mode = 'edit'; + this.loadTag(); + this.loadAttachedBeats(); + } } public render() { return ( - - - } + +
+ + this.setState(oldState => ({ + tag: { ...oldState.tag, [field]: value }, + })) + } + attachedBeats={this.state.attachedBeats} /> - } - /> - + + + + + Save + + + + this.props.history.push('/overview/tags')}> + Cancel + + + +
); } + private loadTag = async () => { + const tags = await this.props.libs.tags.getTagsWithIds([this.props.match.params.tagid]); + if (tags.length === 0) { + // TODO do something to error + } + this.setState({ + tag: tags[0], + }); + }; + + private loadAttachedBeats = async () => { + const beats = await this.props.libs.beats.getBeatsWithTag(this.props.match.params.tagid); + + this.setState({ + attachedBeats: beats, + }); + }; + private saveTag = async () => { + await this.props.libs.tags.upsertTag(this.state.tag as ClientSideBeatTag); + this.props.history.push('/overview/tags'); + }; } diff --git a/x-pack/plugins/beats_management/public/router.tsx b/x-pack/plugins/beats_management/public/router.tsx index 6642384468803..03faf8f2d07a4 100644 --- a/x-pack/plugins/beats_management/public/router.tsx +++ b/x-pack/plugins/beats_management/public/router.tsx @@ -25,7 +25,10 @@ export const PageRouter: React.SFC<{ libs: any }> = ({ libs }) => { path="/beat/:beatId" render={(props: any) => } /> - } /> + } + />
); diff --git a/x-pack/plugins/beats_management/server/lib/domains/beats.ts b/x-pack/plugins/beats_management/server/lib/domains/beats.ts index bd04b60aa6468..04c43440822bd 100644 --- a/x-pack/plugins/beats_management/server/lib/domains/beats.ts +++ b/x-pack/plugins/beats_management/server/lib/domains/beats.ts @@ -44,16 +44,17 @@ export class CMBeatsDomain { return (await this.adapter.getAll(user)).filter((beat: CMBeat) => beat.active === true); } + public async getAllWithTag(user: FrameworkUser, tagId: string) { + return (await this.adapter.getAllWithTags(user, [tagId])).filter( + (beat: CMBeat) => beat.active === true + ); + } + public async getByEnrollmentToken(user: FrameworkUser, enrollmentToken: string) { const beat = await this.adapter.getBeatWithToken(user, enrollmentToken); return beat && beat.active ? beat : null; } - public async getAllWithTags(user: FrameworkUser, tagIds: string[], justActive = true) { - const beats = await this.adapter.getAllWithTags(user, tagIds); - return justActive ? beats.filter((beat: CMBeat) => beat.active === true) : beats; - } - public async update(userOrToken: UserOrToken, beatId: string, beatData: Partial) { const beat = await this.adapter.get(this.framework.internalUser, beatId); diff --git a/x-pack/plugins/beats_management/server/rest_api/beats/list.ts b/x-pack/plugins/beats_management/server/rest_api/beats/list.ts index 876f717dd8f4a..3bfb249b39b6f 100644 --- a/x-pack/plugins/beats_management/server/rest_api/beats/list.ts +++ b/x-pack/plugins/beats_management/server/rest_api/beats/list.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { CMBeat } from '../../../common/domain_types'; import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types'; import { CMServerLibs } from '../../lib/lib'; import { wrapEsError } from '../../utils/error_wrappers'; @@ -11,10 +12,29 @@ import { wrapEsError } from '../../utils/error_wrappers'; // TODO: add license check pre-hook export const createListAgentsRoute = (libs: CMServerLibs) => ({ method: 'GET', - path: '/api/beats/agents', + path: '/api/beats/agents/{listByAndValue*}', handler: async (request: FrameworkRequest, reply: any) => { + const listByAndValueParts = request.params.listByAndValue.split('/'); + let listBy: 'tag' | null = null; + let listByValue: string | null = null; + + if (listByAndValueParts.length === 2) { + listBy = listByAndValueParts[0]; + listByValue = listByAndValueParts[1]; + } + try { - const beats = await libs.beats.getAll(request.user); + let beats: CMBeat[]; + switch (listBy) { + case 'tag': + beats = await libs.beats.getAllWithTag(request.user, listByValue || ''); + break; + + default: + beats = await libs.beats.getAll(request.user); + + break; + } reply({ beats }); } catch (err) { diff --git a/x-pack/plugins/beats_management/server/rest_api/beats/update.ts b/x-pack/plugins/beats_management/server/rest_api/beats/update.ts index 26a22fe1d11f9..bf94f83ec22d7 100644 --- a/x-pack/plugins/beats_management/server/rest_api/beats/update.ts +++ b/x-pack/plugins/beats_management/server/rest_api/beats/update.ts @@ -64,7 +64,6 @@ export const createBeatUpdateRoute = (libs: CMServerLibs) => ({ } reply({ success: true }).code(204); - } catch (err) { return reply(wrapEsError(err)); } diff --git a/x-pack/plugins/beats_management/server/rest_api/tags/set.ts b/x-pack/plugins/beats_management/server/rest_api/tags/set.ts index 25837100e2a60..4ec082ba99de6 100644 --- a/x-pack/plugins/beats_management/server/rest_api/tags/set.ts +++ b/x-pack/plugins/beats_management/server/rest_api/tags/set.ts @@ -26,6 +26,7 @@ export const createSetTagRoute = (libs: CMServerLibs) => ({ configuration_blocks: Joi.array().items( Joi.object({ block_yml: Joi.string().required(), + description: Joi.string(), type: Joi.string() .only(values(ConfigurationBlockTypes)) .required(), diff --git a/x-pack/plugins/beats_management/server/utils/adapters/beats/elasticsearch_beats_adapter.ts b/x-pack/plugins/beats_management/server/utils/adapters/beats/elasticsearch_beats_adapter.ts deleted file mode 100644 index 283f65c1258ae..0000000000000 --- a/x-pack/plugins/beats_management/server/utils/adapters/beats/elasticsearch_beats_adapter.ts +++ /dev/null @@ -1,218 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { flatten, get, omit } from 'lodash'; -import moment from 'moment'; -import { INDEX_NAMES } from '../../../../common/constants'; -import { - BackendFrameworkAdapter, - CMBeat, - CMBeatsAdapter, - CMTagAssignment, - FrameworkRequest, -} from '../../lib'; - -export class ElasticsearchBeatsAdapter implements CMBeatsAdapter { - private framework: BackendFrameworkAdapter; - - constructor(framework: BackendFrameworkAdapter) { - this.framework = framework; - } - - public async get(id: string) { - const params = { - id: `beat:${id}`, - ignore: [404], - index: INDEX_NAMES.BEATS, - type: '_doc', - }; - - const response = await this.framework.callWithInternalUser('get', params); - if (!response.found) { - return null; - } - - return get(response, '_source.beat'); - } - - public async insert(beat: CMBeat) { - const body = { - beat, - type: 'beat', - }; - - const params = { - body, - id: `beat:${beat.id}`, - index: INDEX_NAMES.BEATS, - refresh: 'wait_for', - type: '_doc', - }; - await this.framework.callWithInternalUser('create', params); - } - - public async update(beat: CMBeat) { - const body = { - beat, - type: 'beat', - }; - - const params = { - body, - id: `beat:${beat.id}`, - index: INDEX_NAMES.BEATS, - refresh: 'wait_for', - type: '_doc', - }; - return await this.framework.callWithInternalUser('index', params); - } - - public async getWithIds(req: FrameworkRequest, beatIds: string[]) { - const ids = beatIds.map(beatId => `beat:${beatId}`); - - const params = { - _source: false, - body: { - ids, - }, - index: INDEX_NAMES.BEATS, - type: '_doc', - }; - const response = await this.framework.callWithRequest(req, 'mget', params); - return get(response, 'docs', []); - } - - // TODO merge with getBeatsWithIds - public async getVerifiedWithIds(req: FrameworkRequest, beatIds: string[]) { - const ids = beatIds.map(beatId => `beat:${beatId}`); - - const params = { - _sourceInclude: ['beat.id', 'beat.verified_on'], - body: { - ids, - }, - index: INDEX_NAMES.BEATS, - type: '_doc', - }; - const response = await this.framework.callWithRequest(req, 'mget', params); - return get(response, 'docs', []); - } - - public async verifyBeats(req: FrameworkRequest, beatIds: string[]) { - if (!Array.isArray(beatIds) || beatIds.length === 0) { - return []; - } - - const verifiedOn = moment().toJSON(); - const body = flatten( - beatIds.map(beatId => [ - { update: { _id: `beat:${beatId}` } }, - { doc: { beat: { verified_on: verifiedOn } } }, - ]) - ); - - const params = { - body, - index: INDEX_NAMES.BEATS, - refresh: 'wait_for', - type: '_doc', - }; - - const response = await this.framework.callWithRequest(req, 'bulk', params); - return get(response, 'items', []); - } - - public async getAll(req: FrameworkRequest) { - const params = { - index: INDEX_NAMES.BEATS, - q: 'type:beat', - type: '_doc', - }; - const response = await this.framework.callWithRequest( - req, - 'search', - params - ); - - const beats = get(response, 'hits.hits', []); - return beats.map((beat: any) => omit(beat._source.beat, ['access_token'])); - } - - public async removeTagsFromBeats( - req: FrameworkRequest, - removals: CMTagAssignment[] - ): Promise { - const body = flatten( - removals.map(({ beatId, tag }) => { - const script = - '' + - 'def beat = ctx._source.beat; ' + - 'if (beat.tags != null) { ' + - ' beat.tags.removeAll([params.tag]); ' + - '}'; - - return [ - { update: { _id: `beat:${beatId}` } }, - { script: { source: script, params: { tag } } }, - ]; - }) - ); - - const params = { - body, - index: INDEX_NAMES.BEATS, - refresh: 'wait_for', - type: '_doc', - }; - - const response = await this.framework.callWithRequest(req, 'bulk', params); - return get(response, 'items', []).map( - (item: any, resultIdx: number) => ({ - idxInRequest: removals[resultIdx].idxInRequest, - result: item.update.result, - status: item.update.status, - }) - ); - } - - public async assignTagsToBeats( - req: FrameworkRequest, - assignments: CMTagAssignment[] - ): Promise { - const body = flatten( - assignments.map(({ beatId, tag }) => { - const script = - '' + - 'def beat = ctx._source.beat; ' + - 'if (beat.tags == null) { ' + - ' beat.tags = []; ' + - '} ' + - 'if (!beat.tags.contains(params.tag)) { ' + - ' beat.tags.add(params.tag); ' + - '}'; - - return [ - { update: { _id: `beat:${beatId}` } }, - { script: { source: script, params: { tag } } }, - ]; - }) - ); - - const params = { - body, - index: INDEX_NAMES.BEATS, - refresh: 'wait_for', - type: '_doc', - }; - - const response = await this.framework.callWithRequest(req, 'bulk', params); - return get(response, 'items', []).map((item: any, resultIdx: any) => ({ - idxInRequest: assignments[resultIdx].idxInRequest, - result: item.update.result, - status: item.update.status, - })); - } -} diff --git a/x-pack/plugins/beats_management/server/utils/adapters/famework/kibana/kibana_framework_adapter.ts b/x-pack/plugins/beats_management/server/utils/adapters/famework/kibana/kibana_framework_adapter.ts deleted file mode 100644 index 6fc2fc4853b03..0000000000000 --- a/x-pack/plugins/beats_management/server/utils/adapters/famework/kibana/kibana_framework_adapter.ts +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { - BackendFrameworkAdapter, - FrameworkRequest, - FrameworkRouteOptions, - WrappableRequest, -} from '../../../lib'; - -import { IStrictReply, Request, Server } from 'hapi'; -import { - internalFrameworkRequest, - wrapRequest, -} from '../../../../utils/wrap_request'; - -export class KibanaBackendFrameworkAdapter implements BackendFrameworkAdapter { - public version: string; - - private server: Server; - - constructor(hapiServer: Server) { - this.server = hapiServer; - this.version = hapiServer.plugins.kibana.status.plugin.version; - } - - public getSetting(settingPath: string) { - // TODO type check this properly - // @ts-ignore - return this.server.config().get(settingPath); - } - - public exposeStaticDir(urlPath: string, dir: string): void { - this.server.route({ - handler: { - directory: { - path: dir, - }, - }, - method: 'GET', - path: urlPath, - }); - } - - public registerRoute( - route: FrameworkRouteOptions - ) { - const wrappedHandler = (request: any, reply: IStrictReply) => - route.handler(wrapRequest(request), reply); - - this.server.route({ - config: route.config, - handler: wrappedHandler, - method: route.method, - path: route.path, - }); - } - - public installIndexTemplate(name: string, template: {}) { - return this.callWithInternalUser('indices.putTemplate', { - body: template, - name, - }); - } - - public async callWithInternalUser(esMethod: string, options: {}) { - const { elasticsearch } = this.server.plugins; - const { callWithInternalUser } = elasticsearch.getCluster('admin'); - return await callWithInternalUser(esMethod, options); - } - - public async callWithRequest(req: FrameworkRequest, ...rest: any[]) { - const internalRequest = req[internalFrameworkRequest]; - const { elasticsearch } = internalRequest.server.plugins; - const { callWithRequest } = elasticsearch.getCluster('data'); - const fields = await callWithRequest(internalRequest, ...rest); - return fields; - } -} diff --git a/x-pack/plugins/beats_management/server/utils/adapters/tags/elasticsearch_tags_adapter.ts b/x-pack/plugins/beats_management/server/utils/adapters/tags/elasticsearch_tags_adapter.ts deleted file mode 100644 index 2293ba77677fd..0000000000000 --- a/x-pack/plugins/beats_management/server/utils/adapters/tags/elasticsearch_tags_adapter.ts +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { get } from 'lodash'; -import { INDEX_NAMES } from '../../../../common/constants'; -import { - BackendFrameworkAdapter, - BeatTag, - CMTagsAdapter, - FrameworkRequest, -} from '../../lib'; - -export class ElasticsearchTagsAdapter implements CMTagsAdapter { - private framework: BackendFrameworkAdapter; - - constructor(framework: BackendFrameworkAdapter) { - this.framework = framework; - } - - public async getTagsWithIds(req: FrameworkRequest, tagIds: string[]) { - const ids = tagIds.map(tag => `tag:${tag}`); - - // TODO abstract to kibana adapter as the more generic getDocs - const params = { - _source: false, - body: { - ids, - }, - index: INDEX_NAMES.BEATS, - type: '_doc', - }; - const response = await this.framework.callWithRequest(req, 'mget', params); - return get(response, 'docs', []); - } - - public async upsertTag(req: FrameworkRequest, tag: BeatTag) { - const body = { - tag, - type: 'tag', - }; - - const params = { - body, - id: `tag:${tag.id}`, - index: INDEX_NAMES.BEATS, - refresh: 'wait_for', - type: '_doc', - }; - const response = await this.framework.callWithRequest(req, 'index', params); - - // TODO this is not something that works for TS... change this return type - return get(response, 'result'); - } -} diff --git a/x-pack/plugins/beats_management/server/utils/adapters/tokens/elasticsearch_tokens_adapter.ts b/x-pack/plugins/beats_management/server/utils/adapters/tokens/elasticsearch_tokens_adapter.ts deleted file mode 100644 index c8969c7ab08d0..0000000000000 --- a/x-pack/plugins/beats_management/server/utils/adapters/tokens/elasticsearch_tokens_adapter.ts +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { flatten, get } from 'lodash'; -import { INDEX_NAMES } from '../../../../common/constants'; -import { - BackendFrameworkAdapter, - CMTokensAdapter, - EnrollmentToken, - FrameworkRequest, -} from '../../lib'; - -export class ElasticsearchTokensAdapter implements CMTokensAdapter { - private framework: BackendFrameworkAdapter; - - constructor(framework: BackendFrameworkAdapter) { - this.framework = framework; - } - - public async deleteEnrollmentToken(enrollmentToken: string) { - const params = { - id: `enrollment_token:${enrollmentToken}`, - index: INDEX_NAMES.BEATS, - type: '_doc', - }; - - return this.framework.callWithInternalUser('delete', params); - } - - public async getEnrollmentToken( - tokenString: string - ): Promise { - const params = { - id: `enrollment_token:${tokenString}`, - ignore: [404], - index: INDEX_NAMES.BEATS, - type: '_doc', - }; - - const response = await this.framework.callWithInternalUser('get', params); - const tokenDetails = get( - response, - '_source.enrollment_token', - { - expires_on: '0', - token: null, - } - ); - - // Elasticsearch might return fast if the token is not found. OR it might return fast - // if the token *is* found. Either way, an attacker could using a timing attack to figure - // out whether a token is valid or not. So we introduce a random delay in returning from - // this function to obscure the actual time it took for Elasticsearch to find the token. - const randomDelayInMs = 25 + Math.round(Math.random() * 200); // between 25 and 225 ms - return new Promise(resolve => - setTimeout(() => resolve(tokenDetails), randomDelayInMs) - ); - } - - public async upsertTokens(req: FrameworkRequest, tokens: EnrollmentToken[]) { - const body = flatten( - tokens.map(token => [ - { index: { _id: `enrollment_token:${token.token}` } }, - { - enrollment_token: token, - type: 'enrollment_token', - }, - ]) - ); - - const params = { - body, - index: INDEX_NAMES.BEATS, - refresh: 'wait_for', - type: '_doc', - }; - - await this.framework.callWithRequest(req, 'bulk', params); - } -} diff --git a/x-pack/plugins/beats_management/server/utils/compose/kibana.ts b/x-pack/plugins/beats_management/server/utils/compose/kibana.ts deleted file mode 100644 index ff478646aea89..0000000000000 --- a/x-pack/plugins/beats_management/server/utils/compose/kibana.ts +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { ElasticsearchBeatsAdapter } from '../adapters/beats/elasticsearch_beats_adapter'; -import { ElasticsearchTagsAdapter } from '../adapters/tags/elasticsearch_tags_adapter'; -import { ElasticsearchTokensAdapter } from '../adapters/tokens/elasticsearch_tokens_adapter'; - -import { KibanaBackendFrameworkAdapter } from '../adapters/famework/kibana/kibana_framework_adapter'; - -import { CMBeatsDomain } from '../domains/beats'; -import { CMTagsDomain } from '../domains/tags'; -import { CMTokensDomain } from '../domains/tokens'; - -import { CMDomainLibs, CMServerLibs } from '../lib'; - -import { Server } from 'hapi'; - -export function compose(server: Server): CMServerLibs { - const framework = new KibanaBackendFrameworkAdapter(server); - - const tags = new CMTagsDomain(new ElasticsearchTagsAdapter(framework)); - const tokens = new CMTokensDomain(new ElasticsearchTokensAdapter(framework), { - framework, - }); - const beats = new CMBeatsDomain(new ElasticsearchBeatsAdapter(framework), { - tags, - tokens, - }); - - const domainLibs: CMDomainLibs = { - beats, - tags, - tokens, - }; - - const libs: CMServerLibs = { - framework, - ...domainLibs, - }; - - return libs; -} diff --git a/x-pack/plugins/beats_management/server/utils/domains/beats.ts b/x-pack/plugins/beats_management/server/utils/domains/beats.ts deleted file mode 100644 index c0d9ec704e2b1..0000000000000 --- a/x-pack/plugins/beats_management/server/utils/domains/beats.ts +++ /dev/null @@ -1,259 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { uniq } from 'lodash'; -import uuid from 'uuid'; -import { findNonExistentItems } from '../../utils/find_non_existent_items'; - -import { - CMAssignmentReturn, - CMBeat, - CMBeatsAdapter, - CMDomainLibs, - CMRemovalReturn, - CMTagAssignment, - FrameworkRequest, -} from '../lib'; - -export class CMBeatsDomain { - private adapter: CMBeatsAdapter; - private tags: CMDomainLibs['tags']; - private tokens: CMDomainLibs['tokens']; - - constructor( - adapter: CMBeatsAdapter, - libs: { tags: CMDomainLibs['tags']; tokens: CMDomainLibs['tokens'] } - ) { - this.adapter = adapter; - this.tags = libs.tags; - this.tokens = libs.tokens; - } - - public async update( - beatId: string, - accessToken: string, - beatData: Partial - ) { - const beat = await this.adapter.get(beatId); - - // TODO make return type enum - if (beat === null) { - return 'beat-not-found'; - } - - const isAccessTokenValid = this.tokens.areTokensEqual( - beat.access_token, - accessToken - ); - if (!isAccessTokenValid) { - return 'invalid-access-token'; - } - const isBeatVerified = beat.hasOwnProperty('verified_on'); - if (!isBeatVerified) { - return 'beat-not-verified'; - } - - await this.adapter.update({ - ...beat, - ...beatData, - }); - } - - // TODO more strongly type this - public async enrollBeat( - beatId: string, - remoteAddress: string, - beat: Partial - ) { - // TODO move this to the token lib - const accessToken = uuid.v4().replace(/-/g, ''); - await this.adapter.insert({ - ...beat, - access_token: accessToken, - host_ip: remoteAddress, - id: beatId, - } as CMBeat); - return { accessToken }; - } - - public async removeTagsFromBeats( - req: FrameworkRequest, - removals: CMTagAssignment[] - ): Promise { - const beatIds = uniq(removals.map(removal => removal.beatId)); - const tagIds = uniq(removals.map(removal => removal.tag)); - - const response = { - removals: removals.map(() => ({ status: null })), - }; - - const beats = await this.adapter.getWithIds(req, beatIds); - const tags = await this.tags.getTagsWithIds(req, tagIds); - - // Handle assignments containing non-existing beat IDs or tags - const nonExistentBeatIds = findNonExistentItems(beats, beatIds); - const nonExistentTags = await findNonExistentItems(tags, tagIds); - - addNonExistentItemToResponse( - response, - removals, - nonExistentBeatIds, - nonExistentTags, - 'removals' - ); - - // TODO abstract this - const validRemovals = removals - .map((removal, idxInRequest) => ({ - beatId: removal.beatId, - idxInRequest, // so we can add the result of this removal to the correct place in the response - tag: removal.tag, - })) - .filter((removal, idx) => response.removals[idx].status === null); - - if (validRemovals.length > 0) { - const removalResults = await this.adapter.removeTagsFromBeats( - req, - validRemovals - ); - return addToResultsToResponse('removals', response, removalResults); - } - return response; - } - - public async getAllBeats(req: FrameworkRequest) { - return await this.adapter.getAll(req); - } - - // TODO cleanup return value, should return a status enum - public async verifyBeats(req: FrameworkRequest, beatIds: string[]) { - const beatsFromEs = await this.adapter.getVerifiedWithIds(req, beatIds); - - const nonExistentBeatIds = beatsFromEs.reduce( - (nonExistentIds: any, beatFromEs: any, idx: any) => { - if (!beatFromEs.found) { - nonExistentIds.push(beatIds[idx]); - } - return nonExistentIds; - }, - [] - ); - - const alreadyVerifiedBeatIds = beatsFromEs - .filter((beat: any) => beat.found) - .filter((beat: any) => beat._source.beat.hasOwnProperty('verified_on')) - .map((beat: any) => beat._source.beat.id); - - const toBeVerifiedBeatIds = beatsFromEs - .filter((beat: any) => beat.found) - .filter((beat: any) => !beat._source.beat.hasOwnProperty('verified_on')) - .map((beat: any) => beat._source.beat.id); - - const verifications = await this.adapter.verifyBeats( - req, - toBeVerifiedBeatIds - ); - return { - alreadyVerifiedBeatIds, - nonExistentBeatIds, - toBeVerifiedBeatIds, - verifications, - }; - } - - public async assignTagsToBeats( - req: FrameworkRequest, - assignments: CMTagAssignment[] - ): Promise { - const beatIds = uniq(assignments.map(assignment => assignment.beatId)); - const tagIds = uniq(assignments.map(assignment => assignment.tag)); - - const response = { - assignments: assignments.map(() => ({ status: null })), - }; - const beats = await this.adapter.getWithIds(req, beatIds); - const tags = await this.tags.getTagsWithIds(req, tagIds); - - // Handle assignments containing non-existing beat IDs or tags - const nonExistentBeatIds = findNonExistentItems(beats, beatIds); - const nonExistentTags = findNonExistentItems(tags, tagIds); - - // TODO break out back into route / function response - // TODO causes function to error if a beat or tag does not exist - addNonExistentItemToResponse( - response, - assignments, - nonExistentBeatIds, - nonExistentTags, - 'assignments' - ); - - // TODO abstract this - const validAssignments = assignments - .map((assignment, idxInRequest) => ({ - beatId: assignment.beatId, - idxInRequest, // so we can add the result of this assignment to the correct place in the response - tag: assignment.tag, - })) - .filter((assignment, idx) => response.assignments[idx].status === null); - - if (validAssignments.length > 0) { - const assignmentResults = await this.adapter.assignTagsToBeats( - req, - validAssignments - ); - - // TODO This should prob not mutate - return addToResultsToResponse('assignments', response, assignmentResults); - } - return response; - } -} - -// TODO abstract to the route, also the key arg is a temp fix -function addNonExistentItemToResponse( - response: any, - assignments: any, - nonExistentBeatIds: any, - nonExistentTags: any, - key: string -) { - assignments.forEach(({ beatId, tag }: CMTagAssignment, idx: any) => { - const isBeatNonExistent = nonExistentBeatIds.includes(beatId); - const isTagNonExistent = nonExistentTags.includes(tag); - - if (isBeatNonExistent && isTagNonExistent) { - response[key][idx].status = 404; - response[key][idx].result = `Beat ${beatId} and tag ${tag} not found`; - } else if (isBeatNonExistent) { - response[key][idx].status = 404; - response[key][idx].result = `Beat ${beatId} not found`; - } else if (isTagNonExistent) { - response[key][idx].status = 404; - response[key][idx].result = `Tag ${tag} not found`; - } - }); -} - -// TODO dont mutate response -function addToResultsToResponse( - key: string, - response: any, - assignmentResults: any -) { - assignmentResults.forEach((assignmentResult: any) => { - const { idxInRequest, status, result } = assignmentResult; - response[key][idxInRequest].status = status; - response[key][idxInRequest].result = result; - }); - return response; -} diff --git a/x-pack/plugins/beats_management/server/utils/domains/tags.ts b/x-pack/plugins/beats_management/server/utils/domains/tags.ts deleted file mode 100644 index 43bb8dfed15a1..0000000000000 --- a/x-pack/plugins/beats_management/server/utils/domains/tags.ts +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { intersection, uniq, values } from 'lodash'; -import { UNIQUENESS_ENFORCING_TYPES } from '../../../common/constants'; -import { CMTagsAdapter, ConfigurationBlock, FrameworkRequest } from '../lib'; -import { entries } from './../../utils/polyfills'; - -export class CMTagsDomain { - private adapter: CMTagsAdapter; - constructor(adapter: CMTagsAdapter) { - this.adapter = adapter; - } - - public async getTagsWithIds(req: FrameworkRequest, tagIds: string[]) { - return await this.adapter.getTagsWithIds(req, tagIds); - } - - public async saveTag( - req: FrameworkRequest, - tagId: string, - configs: ConfigurationBlock[] - ) { - const { isValid, message } = await this.validateConfigurationBlocks( - configs - ); - if (!isValid) { - return { isValid, result: message }; - } - - const tag = { - configuration_blocks: configs, - id: tagId, - }; - return { - isValid: true, - result: await this.adapter.upsertTag(req, tag), - }; - } - - private validateConfigurationBlocks(configurationBlocks: any) { - const types = uniq(configurationBlocks.map((block: any) => block.type)); - - // If none of the types in the given configuration blocks are uniqueness-enforcing, - // we don't need to perform any further validation checks. - const uniquenessEnforcingTypes = intersection( - types, - UNIQUENESS_ENFORCING_TYPES - ); - if (uniquenessEnforcingTypes.length === 0) { - return { isValid: true }; - } - - // Count the number of uniqueness-enforcing types in the given configuration blocks - const typeCountMap = configurationBlocks.reduce((map: any, block: any) => { - const { type } = block; - if (!uniquenessEnforcingTypes.includes(type)) { - return map; - } - - const count = map[type] || 0; - return { - ...map, - [type]: count + 1, - }; - }, {}); - - // If there is no more than one of any uniqueness-enforcing types in the given - // configuration blocks, we don't need to perform any further validation checks. - if (values(typeCountMap).filter(count => count > 1).length === 0) { - return { isValid: true }; - } - - const message = entries(typeCountMap) - .filter(([, count]) => count > 1) - .map( - ([type, count]) => - `Expected only one configuration block of type '${type}' but found ${count}` - ) - .join(' '); - - return { - isValid: false, - message, - }; - } -} diff --git a/x-pack/plugins/beats_management/server/utils/domains/tokens.ts b/x-pack/plugins/beats_management/server/utils/domains/tokens.ts deleted file mode 100644 index 6e55d78ecdcc8..0000000000000 --- a/x-pack/plugins/beats_management/server/utils/domains/tokens.ts +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { timingSafeEqual } from 'crypto'; -import moment from 'moment'; -import uuid from 'uuid'; -import { CMTokensAdapter, FrameworkRequest } from '../lib'; -import { BackendFrameworkAdapter } from '../lib'; - -const RANDOM_TOKEN_1 = 'b48c4bda384a40cb91c6eb9b8849e77f'; -const RANDOM_TOKEN_2 = '80a3819e3cd64f4399f1d4886be7a08b'; - -export class CMTokensDomain { - private adapter: CMTokensAdapter; - private framework: BackendFrameworkAdapter; - - constructor( - adapter: CMTokensAdapter, - libs: { framework: BackendFrameworkAdapter } - ) { - this.adapter = adapter; - this.framework = libs.framework; - } - - public async getEnrollmentToken(enrollmentToken: string) { - return await this.adapter.getEnrollmentToken(enrollmentToken); - } - - public async deleteEnrollmentToken(enrollmentToken: string) { - return await this.adapter.deleteEnrollmentToken(enrollmentToken); - } - - public areTokensEqual(token1: string, token2: string) { - if ( - typeof token1 !== 'string' || - typeof token2 !== 'string' || - token1.length !== token2.length - ) { - // This prevents a more subtle timing attack where we know already the tokens aren't going to - // match but still we don't return fast. Instead we compare two pre-generated random tokens using - // the same comparison algorithm that we would use to compare two equal-length tokens. - return timingSafeEqual( - Buffer.from(RANDOM_TOKEN_1, 'utf8'), - Buffer.from(RANDOM_TOKEN_2, 'utf8') - ); - } - - return timingSafeEqual( - Buffer.from(token1, 'utf8'), - Buffer.from(token2, 'utf8') - ); - } - - public async createEnrollmentTokens( - req: FrameworkRequest, - numTokens: number = 1 - ): Promise { - const tokens = []; - const enrollmentTokensTtlInSeconds = this.framework.getSetting( - 'xpack.beats.enrollmentTokensTtlInSeconds' - ); - const enrollmentTokenExpiration = moment() - .add(enrollmentTokensTtlInSeconds, 'seconds') - .toJSON(); - - while (tokens.length < numTokens) { - tokens.push({ - expires_on: enrollmentTokenExpiration, - token: uuid.v4().replace(/-/g, ''), - }); - } - - await this.adapter.upsertTokens(req, tokens); - - return tokens.map(token => token.token); - } -} diff --git a/x-pack/plugins/beats_management/server/utils/index_templates/beats_template.json b/x-pack/plugins/beats_management/server/utils/index_templates/beats_template.json index 1bbe65b70e67d..54dc9fce1e469 100644 --- a/x-pack/plugins/beats_management/server/utils/index_templates/beats_template.json +++ b/x-pack/plugins/beats_management/server/utils/index_templates/beats_template.json @@ -42,6 +42,9 @@ "type": { "type": "keyword" }, + "description": { + "type": "text" + }, "block_yml": { "type": "text" } diff --git a/x-pack/plugins/beats_management/server/utils/lib.ts b/x-pack/plugins/beats_management/server/utils/lib.ts deleted file mode 100644 index 37d0a989e4cf5..0000000000000 --- a/x-pack/plugins/beats_management/server/utils/lib.ts +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { IRouteAdditionalConfigurationOptions, IStrictReply } from 'hapi'; -import { internalFrameworkRequest } from '../utils/wrap_request'; -import { CMBeatsDomain } from './domains/beats'; -import { CMTagsDomain } from './domains/tags'; -import { CMTokensDomain } from './domains/tokens'; - -import { ConfigurationBlockTypes } from '../../common/constants'; - -export interface CMDomainLibs { - beats: CMBeatsDomain; - tags: CMTagsDomain; - tokens: CMTokensDomain; -} - -export interface CMServerLibs extends CMDomainLibs { - framework: BackendFrameworkAdapter; -} - -interface CMReturnedTagAssignment { - status: number | null; - result?: string; -} - -export interface CMAssignmentReturn { - assignments: CMReturnedTagAssignment[]; -} - -export interface CMRemovalReturn { - removals: CMReturnedTagAssignment[]; -} - -export interface ConfigurationBlock { - type: ConfigurationBlockTypes; - block_yml: string; -} - -export interface CMBeat { - id: string; - access_token: string; - verified_on: string; - type: string; - version: string; - host_ip: string; - host_name: string; - ephemeral_id: string; - local_configuration_yml: string; - tags: string; - central_configuration_yml: string; - metadata: {}; -} - -export interface BeatTag { - id: string; - configuration_blocks: ConfigurationBlock[]; -} - -export interface EnrollmentToken { - token: string | null; - expires_on: string; -} - -export interface CMTokensAdapter { - deleteEnrollmentToken(enrollmentToken: string): Promise; - getEnrollmentToken(enrollmentToken: string): Promise; - upsertTokens(req: FrameworkRequest, tokens: EnrollmentToken[]): Promise; -} - -// FIXME: fix getTagsWithIds return type -export interface CMTagsAdapter { - getTagsWithIds(req: FrameworkRequest, tagIds: string[]): any; - upsertTag(req: FrameworkRequest, tag: BeatTag): Promise<{}>; -} - -// FIXME: fix getBeatsWithIds return type -export interface CMBeatsAdapter { - insert(beat: CMBeat): Promise; - update(beat: CMBeat): Promise; - get(id: string): any; - getAll(req: FrameworkRequest): any; - getWithIds(req: FrameworkRequest, beatIds: string[]): any; - getVerifiedWithIds(req: FrameworkRequest, beatIds: string[]): any; - verifyBeats(req: FrameworkRequest, beatIds: string[]): any; - removeTagsFromBeats( - req: FrameworkRequest, - removals: CMTagAssignment[] - ): Promise; - assignTagsToBeats( - req: FrameworkRequest, - assignments: CMTagAssignment[] - ): Promise; -} - -export interface CMTagAssignment { - beatId: string; - tag: string; - idxInRequest?: number; -} - -/** - * The following are generic types, sharable between projects - */ - -export interface BackendFrameworkAdapter { - version: string; - getSetting(settingPath: string): string | number; - exposeStaticDir(urlPath: string, dir: string): void; - installIndexTemplate(name: string, template: {}): void; - registerRoute( - route: FrameworkRouteOptions - ): void; - callWithInternalUser(esMethod: string, options: {}): Promise; - callWithRequest( - req: FrameworkRequest, - method: 'search', - options?: object - ): Promise>; - callWithRequest( - req: FrameworkRequest, - method: 'fieldCaps', - options?: object - ): Promise; - callWithRequest( - req: FrameworkRequest, - method: string, - options?: object - ): Promise; -} - -interface DatabaseFieldCapsResponse extends DatabaseResponse { - fields: FieldsResponse; -} - -export interface FieldsResponse { - [name: string]: FieldDef; -} - -export interface FieldDetails { - searchable: boolean; - aggregatable: boolean; - type: string; -} - -export interface FieldDef { - [type: string]: FieldDetails; -} - -export interface FrameworkRequest< - InternalRequest extends WrappableRequest = WrappableRequest -> { - [internalFrameworkRequest]: InternalRequest; - headers: InternalRequest['headers']; - info: InternalRequest['info']; - payload: InternalRequest['payload']; - params: InternalRequest['params']; - query: InternalRequest['query']; -} - -export interface FrameworkRouteOptions< - RouteRequest extends WrappableRequest, - RouteResponse -> { - path: string; - method: string | string[]; - vhost?: string; - handler: FrameworkRouteHandler; - config?: Pick< - IRouteAdditionalConfigurationOptions, - Exclude - >; -} - -export type FrameworkRouteHandler< - RouteRequest extends WrappableRequest, - RouteResponse -> = ( - request: FrameworkRequest, - reply: IStrictReply -) => void; - -export interface WrappableRequest< - Payload = any, - Params = any, - Query = any, - Headers = any, - Info = any -> { - headers: Headers; - info: Info; - payload: Payload; - params: Params; - query: Query; -} - -interface DatabaseResponse { - took: number; - timeout: boolean; -} - -interface DatabaseSearchResponse - extends DatabaseResponse { - aggregations?: Aggregations; - hits: { - total: number; - hits: Hit[]; - }; -} diff --git a/x-pack/plugins/beats_management/tsconfig.json b/x-pack/plugins/beats_management/tsconfig.json index 4082f16a5d91c..67fefc7286ca4 100644 --- a/x-pack/plugins/beats_management/tsconfig.json +++ b/x-pack/plugins/beats_management/tsconfig.json @@ -1,3 +1,7 @@ { - "extends": "../../tsconfig.json" + "extends": "../../tsconfig.json", + "exclude": ["**/node_modules/**"], + "paths": { + "react": ["../../../node_modules/@types/react"] + } } diff --git a/x-pack/plugins/beats_management/types/formsy.d.ts b/x-pack/plugins/beats_management/types/formsy.d.ts new file mode 100644 index 0000000000000..f153e80d13e53 --- /dev/null +++ b/x-pack/plugins/beats_management/types/formsy.d.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +declare module 'formsy-react' { + import React, { SFC } from 'react'; + let Formsy: SFC; + export interface FormsyInputProps { + getErrorMessage(): any; + getValue(): any; + hasValue(): boolean; + isFormDisabled(): boolean; + isFormSubmitted(): boolean; + isPristine(): boolean; + isRequired(): boolean; + isValid(): boolean; + isValidValue(): boolean; + resetValue(): void; + setValidations(validations: any, required: boolean): void; + setValue(value: any): void; + showError(): boolean; + showRequired(): boolean; + } + + export default Formsy; + export type FormData = { [fieldName in keyof FormShape]: string }; + export type FieldValue = string | null | undefined; + + type ValidationMethod = ( + values: FormData, + value: string | null | undefined + ) => void; + + export function addValidationRule( + validationName: string, + validationMethod: ValidationMethod + ): void; + export function withFormsy(component: any): any; + + // function withFormsy( + // component: + // | React.Component + // | SFC + // ): React.Component; +} diff --git a/x-pack/plugins/grokdebugger/common/constants/index.js b/x-pack/plugins/grokdebugger/common/constants/index.js new file mode 100644 index 0000000000000..c37101b73078a --- /dev/null +++ b/x-pack/plugins/grokdebugger/common/constants/index.js @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { ROUTES } from './routes'; +export { PLUGIN } from './plugin'; +export { EDITOR } from './editor'; \ No newline at end of file diff --git a/x-pack/plugins/grokdebugger/server/lib/call_with_request_factory/call_with_request_factory.js b/x-pack/plugins/grokdebugger/server/lib/call_with_request_factory/call_with_request_factory.js index b9a77a1a0362b..7359a831994f9 100644 --- a/x-pack/plugins/grokdebugger/server/lib/call_with_request_factory/call_with_request_factory.js +++ b/x-pack/plugins/grokdebugger/server/lib/call_with_request_factory/call_with_request_factory.js @@ -6,7 +6,7 @@ import { once } from 'lodash'; -const callWithRequest = once((server) => { +const callWithRequest = once(server => { const cluster = server.plugins.elasticsearch.getCluster('data'); return cluster.callWithRequest; }); diff --git a/x-pack/plugins/index_management/server/lib/call_with_request_factory/call_with_request_factory.js b/x-pack/plugins/index_management/server/lib/call_with_request_factory/call_with_request_factory.js new file mode 100644 index 0000000000000..7359a831994f9 --- /dev/null +++ b/x-pack/plugins/index_management/server/lib/call_with_request_factory/call_with_request_factory.js @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { once } from 'lodash'; + +const callWithRequest = once(server => { + const cluster = server.plugins.elasticsearch.getCluster('data'); + return cluster.callWithRequest; +}); + +export const callWithRequestFactory = (server, request) => { + return (...args) => { + return callWithRequest(server)(request, ...args); + }; +}; diff --git a/x-pack/plugins/logstash/README.md b/x-pack/plugins/logstash/README.md old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/common/constants/configuration_blocks.ts b/x-pack/plugins/logstash/common/constants/configuration_blocks.ts deleted file mode 100644 index e89e53e25b89d..0000000000000 --- a/x-pack/plugins/logstash/common/constants/configuration_blocks.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export enum ConfigurationBlockTypes { - FilebeatInputs = 'filebeat.inputs', - FilebeatModules = 'filebeat.modules', - MetricbeatModules = 'metricbeat.modules', - Output = 'output', - Processors = 'processors', -} - -export const UNIQUENESS_ENFORCING_TYPES = [ConfigurationBlockTypes.Output]; diff --git a/x-pack/plugins/logstash/common/constants/editor.js b/x-pack/plugins/logstash/common/constants/editor.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/common/constants/es_scroll_settings.js b/x-pack/plugins/logstash/common/constants/es_scroll_settings.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/common/constants/index.js b/x-pack/plugins/logstash/common/constants/index.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/server/utils/index_templates/index.ts b/x-pack/plugins/logstash/common/constants/index_names.js old mode 100644 new mode 100755 similarity index 73% rename from x-pack/plugins/logstash/server/utils/index_templates/index.ts rename to x-pack/plugins/logstash/common/constants/index_names.js index eeaef7a68d49f..0f6946f407c58 --- a/x-pack/plugins/logstash/server/utils/index_templates/index.ts +++ b/x-pack/plugins/logstash/common/constants/index_names.js @@ -4,5 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import beatsIndexTemplate from './beats_template.json'; -export { beatsIndexTemplate }; +export const INDEX_NAMES = { + PIPELINES: '.logstash', +}; diff --git a/x-pack/plugins/logstash/common/constants/monitoring.js b/x-pack/plugins/logstash/common/constants/monitoring.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/common/constants/pagination.js b/x-pack/plugins/logstash/common/constants/pagination.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/common/constants/pipeline.js b/x-pack/plugins/logstash/common/constants/pipeline.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/common/constants/plugin.js b/x-pack/plugins/logstash/common/constants/plugin.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/common/constants/routes.js b/x-pack/plugins/logstash/common/constants/routes.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/common/constants/tooltips.js b/x-pack/plugins/logstash/common/constants/tooltips.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/common/constants/type_names.js b/x-pack/plugins/logstash/common/constants/type_names.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/common/lib/__tests__/get_moment.js b/x-pack/plugins/logstash/common/lib/__tests__/get_moment.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/common/lib/get_moment.js b/x-pack/plugins/logstash/common/lib/get_moment.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/common/lib/index.js b/x-pack/plugins/logstash/common/lib/index.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/index.js b/x-pack/plugins/logstash/index.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/components/tooltip/index.js b/x-pack/plugins/logstash/public/components/tooltip/index.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/components/tooltip/tooltip.html b/x-pack/plugins/logstash/public/components/tooltip/tooltip.html old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/components/tooltip/tooltip.js b/x-pack/plugins/logstash/public/components/tooltip/tooltip.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/components/tooltip/tooltip.less b/x-pack/plugins/logstash/public/components/tooltip/tooltip.less old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/lib/get_search_value/get_search_value.js b/x-pack/plugins/logstash/public/lib/get_search_value/get_search_value.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/lib/get_search_value/index.js b/x-pack/plugins/logstash/public/lib/get_search_value/index.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/lib/register_home_feature/index.js b/x-pack/plugins/logstash/public/lib/register_home_feature/index.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/lib/register_home_feature/register_home_feature.js b/x-pack/plugins/logstash/public/lib/register_home_feature/register_home_feature.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/lib/update_management_sections/index.js b/x-pack/plugins/logstash/public/lib/update_management_sections/index.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/lib/update_management_sections/update_logstash_sections.js b/x-pack/plugins/logstash/public/lib/update_management_sections/update_logstash_sections.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/models/cluster/cluster.js b/x-pack/plugins/logstash/public/models/cluster/cluster.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/models/cluster/index.js b/x-pack/plugins/logstash/public/models/cluster/index.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/models/pipeline/index.js b/x-pack/plugins/logstash/public/models/pipeline/index.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/models/pipeline/pipeline.js b/x-pack/plugins/logstash/public/models/pipeline/pipeline.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/models/pipeline_list_item/index.js b/x-pack/plugins/logstash/public/models/pipeline_list_item/index.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/models/pipeline_list_item/pipeline_list_item.js b/x-pack/plugins/logstash/public/models/pipeline_list_item/pipeline_list_item.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/sections/pipeline_edit/components/pipeline_edit/index.js b/x-pack/plugins/logstash/public/sections/pipeline_edit/components/pipeline_edit/index.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/sections/pipeline_edit/components/pipeline_edit/pipeline_edit.html b/x-pack/plugins/logstash/public/sections/pipeline_edit/components/pipeline_edit/pipeline_edit.html old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/sections/pipeline_edit/components/pipeline_edit/pipeline_edit.js b/x-pack/plugins/logstash/public/sections/pipeline_edit/components/pipeline_edit/pipeline_edit.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/sections/pipeline_edit/components/pipeline_edit/pipeline_edit.less b/x-pack/plugins/logstash/public/sections/pipeline_edit/components/pipeline_edit/pipeline_edit.less old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/sections/pipeline_edit/components/upgrade_failure/index.js b/x-pack/plugins/logstash/public/sections/pipeline_edit/components/upgrade_failure/index.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/sections/pipeline_edit/components/upgrade_failure/upgrade_failure.html b/x-pack/plugins/logstash/public/sections/pipeline_edit/components/upgrade_failure/upgrade_failure.html old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/sections/pipeline_edit/components/upgrade_failure/upgrade_failure.js b/x-pack/plugins/logstash/public/sections/pipeline_edit/components/upgrade_failure/upgrade_failure.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/sections/pipeline_edit/index.js b/x-pack/plugins/logstash/public/sections/pipeline_edit/index.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/sections/pipeline_edit/pipeline_edit_route.html b/x-pack/plugins/logstash/public/sections/pipeline_edit/pipeline_edit_route.html old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/sections/pipeline_edit/pipeline_edit_route.js b/x-pack/plugins/logstash/public/sections/pipeline_edit/pipeline_edit_route.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/sections/pipeline_list/components/pipeline_list/index.js b/x-pack/plugins/logstash/public/sections/pipeline_list/components/pipeline_list/index.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/sections/pipeline_list/components/pipeline_list/pipeline_list.html b/x-pack/plugins/logstash/public/sections/pipeline_list/components/pipeline_list/pipeline_list.html old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/sections/pipeline_list/components/pipeline_list/pipeline_list.js b/x-pack/plugins/logstash/public/sections/pipeline_list/components/pipeline_list/pipeline_list.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/sections/pipeline_list/components/pipeline_table/index.js b/x-pack/plugins/logstash/public/sections/pipeline_list/components/pipeline_table/index.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/sections/pipeline_list/components/pipeline_table/pipeline_table.html b/x-pack/plugins/logstash/public/sections/pipeline_list/components/pipeline_table/pipeline_table.html old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/sections/pipeline_list/components/pipeline_table/pipeline_table.js b/x-pack/plugins/logstash/public/sections/pipeline_list/components/pipeline_table/pipeline_table.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/sections/pipeline_list/index.js b/x-pack/plugins/logstash/public/sections/pipeline_list/index.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/sections/pipeline_list/pipeline_list_route.html b/x-pack/plugins/logstash/public/sections/pipeline_list/pipeline_list_route.html old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/sections/pipeline_list/pipeline_list_route.js b/x-pack/plugins/logstash/public/sections/pipeline_list/pipeline_list_route.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/sections/pipeline_list/register_management_section.js b/x-pack/plugins/logstash/public/sections/pipeline_list/register_management_section.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/services/cluster/cluster_service.factory.js b/x-pack/plugins/logstash/public/services/cluster/cluster_service.factory.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/services/cluster/cluster_service.js b/x-pack/plugins/logstash/public/services/cluster/cluster_service.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/services/cluster/index.js b/x-pack/plugins/logstash/public/services/cluster/index.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/services/license/index.js b/x-pack/plugins/logstash/public/services/license/index.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/services/license/license_service.factory.js b/x-pack/plugins/logstash/public/services/license/license_service.factory.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/services/license/logstash_license_service.js b/x-pack/plugins/logstash/public/services/license/logstash_license_service.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/services/monitoring/index.js b/x-pack/plugins/logstash/public/services/monitoring/index.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/services/monitoring/monitoring_service.factory.js b/x-pack/plugins/logstash/public/services/monitoring/monitoring_service.factory.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/services/monitoring/monitoring_service.js b/x-pack/plugins/logstash/public/services/monitoring/monitoring_service.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/services/pipeline/index.js b/x-pack/plugins/logstash/public/services/pipeline/index.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/services/pipeline/pipeline_service.factory.js b/x-pack/plugins/logstash/public/services/pipeline/pipeline_service.factory.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/services/pipeline/pipeline_service.js b/x-pack/plugins/logstash/public/services/pipeline/pipeline_service.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/services/pipelines/index.js b/x-pack/plugins/logstash/public/services/pipelines/index.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/services/pipelines/pipelines_service.factory.js b/x-pack/plugins/logstash/public/services/pipelines/pipelines_service.factory.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/services/pipelines/pipelines_service.js b/x-pack/plugins/logstash/public/services/pipelines/pipelines_service.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/services/security/index.js b/x-pack/plugins/logstash/public/services/security/index.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/services/security/logstash_security_service.js b/x-pack/plugins/logstash/public/services/security/logstash_security_service.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/services/security/security_service.factory.js b/x-pack/plugins/logstash/public/services/security/security_service.factory.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/services/upgrade/index.js b/x-pack/plugins/logstash/public/services/upgrade/index.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/services/upgrade/upgrade_service.factory.js b/x-pack/plugins/logstash/public/services/upgrade/upgrade_service.factory.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/public/services/upgrade/upgrade_service.js b/x-pack/plugins/logstash/public/services/upgrade/upgrade_service.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/server/kibana.index.ts b/x-pack/plugins/logstash/server/kibana.index.ts deleted file mode 100644 index c9bc9b8bf02f4..0000000000000 --- a/x-pack/plugins/logstash/server/kibana.index.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { Server } from 'hapi'; -import { compose } from './lib/compose/kibana'; -import { initManagementServer } from './management_server'; - -export const initServerWithKibana = (hapiServer: Server) => { - const libs = compose(hapiServer); - initManagementServer(libs); -}; diff --git a/x-pack/plugins/logstash/server/lib/call_with_request_factory/call_with_request_factory.js b/x-pack/plugins/logstash/server/lib/call_with_request_factory/call_with_request_factory.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/server/lib/call_with_request_factory/index.js b/x-pack/plugins/logstash/server/lib/call_with_request_factory/index.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/server/lib/check_license/__tests__/check_license.js b/x-pack/plugins/logstash/server/lib/check_license/__tests__/check_license.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/server/lib/check_license/check_license.js b/x-pack/plugins/logstash/server/lib/check_license/check_license.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/server/lib/check_license/index.js b/x-pack/plugins/logstash/server/lib/check_license/index.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/server/lib/error_wrappers/__tests__/wrap_custom_error.js b/x-pack/plugins/logstash/server/lib/error_wrappers/__tests__/wrap_custom_error.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/server/lib/error_wrappers/__tests__/wrap_es_error.js b/x-pack/plugins/logstash/server/lib/error_wrappers/__tests__/wrap_es_error.js new file mode 100755 index 0000000000000..f1b956bdcc3bb --- /dev/null +++ b/x-pack/plugins/logstash/server/lib/error_wrappers/__tests__/wrap_es_error.js @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from 'expect.js'; +import { wrapEsError } from '../wrap_es_error'; + +describe('wrap_es_error', () => { + describe('#wrapEsError', () => { + + let originalError; + beforeEach(() => { + originalError = new Error('I am an error'); + originalError.statusCode = 404; + }); + + it('should return a Boom object', () => { + const wrappedError = wrapEsError(originalError); + + expect(wrappedError.isBoom).to.be(true); + }); + + it('should return the correct Boom object', () => { + const wrappedError = wrapEsError(originalError); + + expect(wrappedError.output.statusCode).to.be(originalError.statusCode); + expect(wrappedError.output.payload.message).to.be(originalError.message); + }); + + it('should return invalid permissions message for 403 errors', () => { + const securityError = new Error('I am an error'); + securityError.statusCode = 403; + const wrappedError = wrapEsError(securityError); + + expect(wrappedError.isBoom).to.be(true); + expect(wrappedError.message).to.be('Insufficient user permissions for managing Logstash pipelines'); + }); + }); +}); diff --git a/x-pack/plugins/logstash/server/lib/error_wrappers/__tests__/wrap_unknown_error.js b/x-pack/plugins/logstash/server/lib/error_wrappers/__tests__/wrap_unknown_error.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/server/lib/error_wrappers/index.js b/x-pack/plugins/logstash/server/lib/error_wrappers/index.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/server/lib/error_wrappers/wrap_custom_error.js b/x-pack/plugins/logstash/server/lib/error_wrappers/wrap_custom_error.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/server/lib/error_wrappers/wrap_es_error.js b/x-pack/plugins/logstash/server/lib/error_wrappers/wrap_es_error.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/server/lib/error_wrappers/wrap_unknown_error.js b/x-pack/plugins/logstash/server/lib/error_wrappers/wrap_unknown_error.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/server/lib/fetch_all_from_scroll/__tests__/fetch_all_from_scroll.js b/x-pack/plugins/logstash/server/lib/fetch_all_from_scroll/__tests__/fetch_all_from_scroll.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/server/lib/fetch_all_from_scroll/fetch_all_from_scroll.js b/x-pack/plugins/logstash/server/lib/fetch_all_from_scroll/fetch_all_from_scroll.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/server/lib/fetch_all_from_scroll/index.js b/x-pack/plugins/logstash/server/lib/fetch_all_from_scroll/index.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/server/lib/license_pre_routing_factory/__tests__/license_pre_routing_factory.js b/x-pack/plugins/logstash/server/lib/license_pre_routing_factory/__tests__/license_pre_routing_factory.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/server/lib/license_pre_routing_factory/index.js b/x-pack/plugins/logstash/server/lib/license_pre_routing_factory/index.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/server/lib/license_pre_routing_factory/license_pre_routing_factory.js b/x-pack/plugins/logstash/server/lib/license_pre_routing_factory/license_pre_routing_factory.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/server/lib/register_license_checker/index.js b/x-pack/plugins/logstash/server/lib/register_license_checker/index.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/server/lib/register_license_checker/register_license_checker.js b/x-pack/plugins/logstash/server/lib/register_license_checker/register_license_checker.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/server/management_server.ts b/x-pack/plugins/logstash/server/management_server.ts deleted file mode 100644 index ed0917eda8ced..0000000000000 --- a/x-pack/plugins/logstash/server/management_server.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { CMServerLibs } from './lib/lib'; -import { createBeatEnrollmentRoute } from './rest_api/beats/enroll'; -import { createListAgentsRoute } from './rest_api/beats/list'; -import { createTagAssignmentsRoute } from './rest_api/beats/tag_assignment'; -import { createTagRemovalsRoute } from './rest_api/beats/tag_removal'; -import { createBeatUpdateRoute } from './rest_api/beats/update'; -import { createBeatVerificationRoute } from './rest_api/beats/verify'; -import { createSetTagRoute } from './rest_api/tags/set'; -import { createTokensRoute } from './rest_api/tokens/create'; - -import { beatsIndexTemplate } from './utils/index_templates'; - -export const initManagementServer = (libs: CMServerLibs) => { - libs.framework.installIndexTemplate('beats-template', beatsIndexTemplate); - - libs.framework.registerRoute(createTagAssignmentsRoute(libs)); - libs.framework.registerRoute(createListAgentsRoute(libs)); - libs.framework.registerRoute(createTagRemovalsRoute(libs)); - libs.framework.registerRoute(createBeatEnrollmentRoute(libs)); - libs.framework.registerRoute(createSetTagRoute(libs)); - libs.framework.registerRoute(createTokensRoute(libs)); - libs.framework.registerRoute(createBeatVerificationRoute(libs)); - libs.framework.registerRoute(createBeatUpdateRoute(libs)); -}; diff --git a/x-pack/plugins/logstash/server/models/cluster/__tests__/cluster.js b/x-pack/plugins/logstash/server/models/cluster/__tests__/cluster.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/server/models/cluster/cluster.js b/x-pack/plugins/logstash/server/models/cluster/cluster.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/server/models/cluster/index.js b/x-pack/plugins/logstash/server/models/cluster/index.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/server/models/pipeline/__tests__/pipeline.js b/x-pack/plugins/logstash/server/models/pipeline/__tests__/pipeline.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/server/models/pipeline/index.js b/x-pack/plugins/logstash/server/models/pipeline/index.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/server/models/pipeline/pipeline.js b/x-pack/plugins/logstash/server/models/pipeline/pipeline.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/server/models/pipeline_list_item/__tests__/pipeline_list_item.js b/x-pack/plugins/logstash/server/models/pipeline_list_item/__tests__/pipeline_list_item.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/server/models/pipeline_list_item/index.js b/x-pack/plugins/logstash/server/models/pipeline_list_item/index.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/server/models/pipeline_list_item/pipeline_list_item.js b/x-pack/plugins/logstash/server/models/pipeline_list_item/pipeline_list_item.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/server/rest_api/beats/enroll.ts b/x-pack/plugins/logstash/server/rest_api/beats/enroll.ts deleted file mode 100644 index fe154592564ae..0000000000000 --- a/x-pack/plugins/logstash/server/rest_api/beats/enroll.ts +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import Joi from 'joi'; -import { omit } from 'lodash'; -import moment from 'moment'; -import { CMServerLibs } from '../../lib/lib'; -import { wrapEsError } from '../../utils/error_wrappers'; - -// TODO: add license check pre-hook -// TODO: write to Kibana audit log file -export const createBeatEnrollmentRoute = (libs: CMServerLibs) => ({ - config: { - auth: false, - validate: { - headers: Joi.object({ - 'kbn-beats-enrollment-token': Joi.string().required(), - }).options({ - allowUnknown: true, - }), - payload: Joi.object({ - host_name: Joi.string().required(), - type: Joi.string().required(), - version: Joi.string().required(), - }).required(), - }, - }, - handler: async (request: any, reply: any) => { - const { beatId } = request.params; - const enrollmentToken = request.headers['kbn-beats-enrollment-token']; - - try { - const { - token, - expires_on: expiresOn, - } = await libs.tokens.getEnrollmentToken(enrollmentToken); - - if (!token) { - return reply({ message: 'Invalid enrollment token' }).code(400); - } - if (moment(expiresOn).isBefore(moment())) { - return reply({ message: 'Expired enrollment token' }).code(400); - } - const { accessToken } = await libs.beats.enrollBeat( - beatId, - request.info.remoteAddress, - omit(request.payload, 'enrollment_token') - ); - - await libs.tokens.deleteEnrollmentToken(enrollmentToken); - - reply({ access_token: accessToken }).code(201); - } catch (err) { - // TODO move this to kibana route thing in adapter - return reply(wrapEsError(err)); - } - }, - method: 'POST', - path: '/api/beats/agent/{beatId}', -}); diff --git a/x-pack/plugins/logstash/server/rest_api/beats/list.ts b/x-pack/plugins/logstash/server/rest_api/beats/list.ts deleted file mode 100644 index 8263d1c0ff63f..0000000000000 --- a/x-pack/plugins/logstash/server/rest_api/beats/list.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { CMServerLibs } from '../../lib/lib'; -import { wrapEsError } from '../../utils/error_wrappers'; - -// TODO: add license check pre-hook -export const createListAgentsRoute = (libs: CMServerLibs) => ({ - handler: async (request: any, reply: any) => { - try { - const beats = await libs.beats.getAllBeats(request); - reply({ beats }); - } catch (err) { - // TODO move this to kibana route thing in adapter - return reply(wrapEsError(err)); - } - }, - method: 'GET', - path: '/api/beats/agents', -}); diff --git a/x-pack/plugins/logstash/server/rest_api/beats/tag_assignment.ts b/x-pack/plugins/logstash/server/rest_api/beats/tag_assignment.ts deleted file mode 100644 index d06c016ce6d12..0000000000000 --- a/x-pack/plugins/logstash/server/rest_api/beats/tag_assignment.ts +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import Joi from 'joi'; -import { CMServerLibs } from '../../lib/lib'; -import { wrapEsError } from '../../utils/error_wrappers'; - -// TODO: add license check pre-hook -// TODO: write to Kibana audit log file -export const createTagAssignmentsRoute = (libs: CMServerLibs) => ({ - config: { - validate: { - payload: Joi.object({ - assignments: Joi.array().items( - Joi.object({ - beat_id: Joi.string().required(), - tag: Joi.string().required(), - }) - ), - }).required(), - }, - }, - handler: async (request: any, reply: any) => { - const { assignments } = request.payload; - - // TODO abstract or change API to keep beatId consistent - const tweakedAssignments = assignments.map((assignment: any) => ({ - beatId: assignment.beat_id, - tag: assignment.tag, - })); - - try { - const response = await libs.beats.assignTagsToBeats( - request, - tweakedAssignments - ); - reply(response); - } catch (err) { - // TODO move this to kibana route thing in adapter - return reply(wrapEsError(err)); - } - }, - method: 'POST', - path: '/api/beats/agents_tags/assignments', -}); diff --git a/x-pack/plugins/logstash/server/rest_api/beats/tag_removal.ts b/x-pack/plugins/logstash/server/rest_api/beats/tag_removal.ts deleted file mode 100644 index 4da33dbd50cfc..0000000000000 --- a/x-pack/plugins/logstash/server/rest_api/beats/tag_removal.ts +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import Joi from 'joi'; -import { CMServerLibs } from '../../lib/lib'; -import { wrapEsError } from '../../utils/error_wrappers'; - -// TODO: add license check pre-hook -// TODO: write to Kibana audit log file -export const createTagRemovalsRoute = (libs: CMServerLibs) => ({ - config: { - validate: { - payload: Joi.object({ - removals: Joi.array().items( - Joi.object({ - beat_id: Joi.string().required(), - tag: Joi.string().required(), - }) - ), - }).required(), - }, - }, - handler: async (request: any, reply: any) => { - const { removals } = request.payload; - - // TODO abstract or change API to keep beatId consistent - const tweakedRemovals = removals.map((removal: any) => ({ - beatId: removal.beat_id, - tag: removal.tag, - })); - - try { - const response = await libs.beats.removeTagsFromBeats( - request, - tweakedRemovals - ); - reply(response); - } catch (err) { - // TODO move this to kibana route thing in adapter - return reply(wrapEsError(err)); - } - }, - method: 'POST', - path: '/api/beats/agents_tags/removals', -}); diff --git a/x-pack/plugins/logstash/server/rest_api/beats/update.ts b/x-pack/plugins/logstash/server/rest_api/beats/update.ts deleted file mode 100644 index 41d403399d45f..0000000000000 --- a/x-pack/plugins/logstash/server/rest_api/beats/update.ts +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import Joi from 'joi'; -import { CMServerLibs } from '../../lib/lib'; -import { wrapEsError } from '../../utils/error_wrappers'; - -// TODO: add license check pre-hook -// TODO: write to Kibana audit log file (include who did the verification as well) -export const createBeatUpdateRoute = (libs: CMServerLibs) => ({ - config: { - auth: false, - validate: { - headers: Joi.object({ - 'kbn-beats-access-token': Joi.string().required(), - }).options({ - allowUnknown: true, - }), - params: Joi.object({ - beatId: Joi.string(), - }), - payload: Joi.object({ - ephemeral_id: Joi.string(), - host_name: Joi.string(), - local_configuration_yml: Joi.string(), - metadata: Joi.object(), - type: Joi.string(), - version: Joi.string(), - }).required(), - }, - }, - handler: async (request: any, reply: any) => { - const { beatId } = request.params; - const accessToken = request.headers['kbn-beats-access-token']; - const remoteAddress = request.info.remoteAddress; - - try { - const status = await libs.beats.update(beatId, accessToken, { - ...request.payload, - host_ip: remoteAddress, - }); - - switch (status) { - case 'beat-not-found': - return reply({ message: 'Beat not found' }).code(404); - case 'invalid-access-token': - return reply({ message: 'Invalid access token' }).code(401); - case 'beat-not-verified': - return reply({ message: 'Beat has not been verified' }).code(400); - } - - reply().code(204); - } catch (err) { - return reply(wrapEsError(err)); - } - }, - method: 'PUT', - path: '/api/beats/agent/{beatId}', -}); diff --git a/x-pack/plugins/logstash/server/rest_api/beats/verify.ts b/x-pack/plugins/logstash/server/rest_api/beats/verify.ts deleted file mode 100644 index 866fa77d0c337..0000000000000 --- a/x-pack/plugins/logstash/server/rest_api/beats/verify.ts +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import Joi from 'joi'; -import { CMServerLibs } from '../../lib/lib'; -import { wrapEsError } from '../../utils/error_wrappers'; - -// TODO: add license check pre-hook -// TODO: write to Kibana audit log file -export const createBeatVerificationRoute = (libs: CMServerLibs) => ({ - config: { - auth: false, - validate: { - payload: Joi.object({ - beats: Joi.array() - .items({ - id: Joi.string().required(), - }) - .min(1), - }).required(), - }, - }, - handler: async (request: any, reply: any) => { - const beats = [...request.payload.beats]; - const beatIds = beats.map(beat => beat.id); - - try { - const { - verifications, - alreadyVerifiedBeatIds, - toBeVerifiedBeatIds, - nonExistentBeatIds, - } = await libs.beats.verifyBeats(request, beatIds); - - const verifiedBeatIds = verifications.reduce( - (verifiedBeatList: any, verification: any, idx: any) => { - if (verification.update.status === 200) { - verifiedBeatList.push(toBeVerifiedBeatIds[idx]); - } - return verifiedBeatList; - }, - [] - ); - - // TODO calculation of status should be done in-lib, w/switch statement here - beats.forEach(beat => { - if (nonExistentBeatIds.includes(beat.id)) { - beat.status = 404; - beat.result = 'not found'; - } else if (alreadyVerifiedBeatIds.includes(beat.id)) { - beat.status = 200; - beat.result = 'already verified'; - } else if (verifiedBeatIds.includes(beat.id)) { - beat.status = 200; - beat.result = 'verified'; - } else { - beat.status = 400; - beat.result = 'not verified'; - } - }); - - const response = { beats }; - reply(response); - } catch (err) { - return reply(wrapEsError(err)); - } - }, - method: 'POST', - path: '/api/beats/agents/verify', -}); diff --git a/x-pack/plugins/logstash/server/rest_api/tags/set.ts b/x-pack/plugins/logstash/server/rest_api/tags/set.ts deleted file mode 100644 index 3f7e579bd91ae..0000000000000 --- a/x-pack/plugins/logstash/server/rest_api/tags/set.ts +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import Joi from 'joi'; -import { get, values } from 'lodash'; -import { ConfigurationBlockTypes } from '../../../common/constants'; -import { CMServerLibs } from '../../lib/lib'; -import { wrapEsError } from '../../utils/error_wrappers'; - -// TODO: add license check pre-hook -// TODO: write to Kibana audit log file -export const createSetTagRoute = (libs: CMServerLibs) => ({ - config: { - validate: { - params: Joi.object({ - tag: Joi.string(), - }), - payload: Joi.object({ - configuration_blocks: Joi.array().items( - Joi.object({ - block_yml: Joi.string().required(), - type: Joi.string() - .only(values(ConfigurationBlockTypes)) - .required(), - }) - ), - }).allow(null), - }, - }, - handler: async (request: any, reply: any) => { - const configurationBlocks = get( - request, - 'payload.configuration_blocks', - [] - ); - try { - const { isValid, result } = await libs.tags.saveTag( - request, - request.params.tag, - configurationBlocks - ); - if (!isValid) { - return reply({ result }).code(400); - } - - reply().code(result === 'created' ? 201 : 200); - } catch (err) { - // TODO move this to kibana route thing in adapter - return reply(wrapEsError(err)); - } - }, - method: 'PUT', - path: '/api/beats/tag/{tag}', -}); diff --git a/x-pack/plugins/logstash/server/rest_api/tokens/create.ts b/x-pack/plugins/logstash/server/rest_api/tokens/create.ts deleted file mode 100644 index b4f3e2c1a6246..0000000000000 --- a/x-pack/plugins/logstash/server/rest_api/tokens/create.ts +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import Joi from 'joi'; -import { get } from 'lodash'; -import { CMServerLibs } from '../../lib/lib'; -import { wrapEsError } from '../../utils/error_wrappers'; - -// TODO: add license check pre-hook -// TODO: write to Kibana audit log file -const DEFAULT_NUM_TOKENS = 1; -export const createTokensRoute = (libs: CMServerLibs) => ({ - config: { - validate: { - payload: Joi.object({ - num_tokens: Joi.number() - .optional() - .default(DEFAULT_NUM_TOKENS) - .min(1), - }).allow(null), - }, - }, - handler: async (request: any, reply: any) => { - const numTokens = get(request, 'payload.num_tokens', DEFAULT_NUM_TOKENS); - - try { - const tokens = await libs.tokens.createEnrollmentTokens( - request, - numTokens - ); - reply({ tokens }); - } catch (err) { - // TODO move this to kibana route thing in adapter - return reply(wrapEsError(err)); - } - }, - method: 'POST', - path: '/api/beats/enrollment_tokens', -}); diff --git a/x-pack/plugins/logstash/server/routes/api/cluster/index.js b/x-pack/plugins/logstash/server/routes/api/cluster/index.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/server/routes/api/cluster/register_cluster_routes.js b/x-pack/plugins/logstash/server/routes/api/cluster/register_cluster_routes.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/server/routes/api/cluster/register_load_route.js b/x-pack/plugins/logstash/server/routes/api/cluster/register_load_route.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/server/routes/api/pipeline/index.js b/x-pack/plugins/logstash/server/routes/api/pipeline/index.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/server/routes/api/pipeline/register_delete_route.js b/x-pack/plugins/logstash/server/routes/api/pipeline/register_delete_route.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/server/routes/api/pipeline/register_load_route.js b/x-pack/plugins/logstash/server/routes/api/pipeline/register_load_route.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/server/routes/api/pipeline/register_pipeline_routes.js b/x-pack/plugins/logstash/server/routes/api/pipeline/register_pipeline_routes.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/server/routes/api/pipeline/register_save_route.js b/x-pack/plugins/logstash/server/routes/api/pipeline/register_save_route.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/server/routes/api/pipelines/index.js b/x-pack/plugins/logstash/server/routes/api/pipelines/index.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/server/routes/api/pipelines/register_delete_route.js b/x-pack/plugins/logstash/server/routes/api/pipelines/register_delete_route.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/server/routes/api/pipelines/register_list_route.js b/x-pack/plugins/logstash/server/routes/api/pipelines/register_list_route.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/server/routes/api/pipelines/register_pipelines_routes.js b/x-pack/plugins/logstash/server/routes/api/pipelines/register_pipelines_routes.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/server/routes/api/upgrade/index.js b/x-pack/plugins/logstash/server/routes/api/upgrade/index.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/server/routes/api/upgrade/register_execute_route.js b/x-pack/plugins/logstash/server/routes/api/upgrade/register_execute_route.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/server/routes/api/upgrade/register_upgrade_routes.js b/x-pack/plugins/logstash/server/routes/api/upgrade/register_upgrade_routes.js old mode 100644 new mode 100755 diff --git a/x-pack/plugins/logstash/server/utils/README.md b/x-pack/plugins/logstash/server/utils/README.md deleted file mode 100644 index 8a6a27aa29867..0000000000000 --- a/x-pack/plugins/logstash/server/utils/README.md +++ /dev/null @@ -1 +0,0 @@ -Utils should be data processing functions and other tools.... all in all utils is basicly everything that is not an adaptor, or presenter and yet too much to put in a lib. \ No newline at end of file diff --git a/x-pack/plugins/logstash/server/utils/find_non_existent_items.ts b/x-pack/plugins/logstash/server/utils/find_non_existent_items.ts deleted file mode 100644 index 53e4066acc879..0000000000000 --- a/x-pack/plugins/logstash/server/utils/find_non_existent_items.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export function findNonExistentItems(items: any, requestedItems: any) { - return items.reduce((nonExistentItems: any, item: any, idx: any) => { - if (!item.found) { - nonExistentItems.push(requestedItems[idx]); - } - return nonExistentItems; - }, []); -} diff --git a/x-pack/plugins/logstash/server/utils/polyfills.ts b/x-pack/plugins/logstash/server/utils/polyfills.ts deleted file mode 100644 index 5291e2c72be7d..0000000000000 --- a/x-pack/plugins/logstash/server/utils/polyfills.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export const entries = (obj: any) => { - const ownProps = Object.keys(obj); - let i = ownProps.length; - const resArray = new Array(i); // preallocate the Array - - while (i--) { - resArray[i] = [ownProps[i], obj[ownProps[i]]]; - } - - return resArray; -}; diff --git a/x-pack/plugins/logstash/server/utils/wrap_request.ts b/x-pack/plugins/logstash/server/utils/wrap_request.ts deleted file mode 100644 index a29f9055f3688..0000000000000 --- a/x-pack/plugins/logstash/server/utils/wrap_request.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { FrameworkRequest, WrappableRequest } from '../lib/lib'; - -export const internalFrameworkRequest = Symbol('internalFrameworkRequest'); - -export function wrapRequest( - req: InternalRequest -): FrameworkRequest { - const { params, payload, query, headers, info } = req; - - return { - [internalFrameworkRequest]: req, - headers, - info, - params, - payload, - query, - }; -} diff --git a/x-pack/plugins/logstash/tsconfig.json b/x-pack/plugins/logstash/tsconfig.json deleted file mode 100644 index 4082f16a5d91c..0000000000000 --- a/x-pack/plugins/logstash/tsconfig.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "../../tsconfig.json" -} diff --git a/x-pack/plugins/logstash/wallaby.js b/x-pack/plugins/logstash/wallaby.js deleted file mode 100644 index c20488d35cfb6..0000000000000 --- a/x-pack/plugins/logstash/wallaby.js +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -module.exports = function (wallaby) { - return { - debug: true, - files: [ - '../../tsconfig.json', - //'plugins/beats/public/**/*.+(js|jsx|ts|tsx|json|snap|css|less|sass|scss|jpg|jpeg|gif|png|svg)', - 'server/**/*.+(js|jsx|ts|tsx|json|snap|css|less|sass|scss|jpg|jpeg|gif|png|svg)', - 'common/**/*.+(js|jsx|ts|tsx|json|snap|css|less|sass|scss|jpg|jpeg|gif|png|svg)', - ], - - tests: ['**/*.test.ts'], - env: { - type: 'node', - runner: 'node', - }, - testFramework: 'jest', - compilers: { - '**/*.ts?(x)': wallaby.compilers.typeScript({ module: 'commonjs' }), - }, - }; -}; diff --git a/x-pack/plugins/ml/public/jobs/new_job/simple/components/constants/states.js b/x-pack/plugins/ml/public/jobs/new_job/simple/components/constants/states.js index b90428c5ab841..795cfd07fc807 100644 --- a/x-pack/plugins/ml/public/jobs/new_job/simple/components/constants/states.js +++ b/x-pack/plugins/ml/public/jobs/new_job/simple/components/constants/states.js @@ -4,18 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ - - export const CHART_STATE = { NOT_STARTED: 0, LOADING: 1, LOADED: 2, - NO_RESULTS: 3 + NO_RESULTS: 3, }; export const JOB_STATE = { NOT_STARTED: 0, RUNNING: 1, FINISHED: 2, - STOPPING: 3 + STOPPING: 3, }; diff --git a/x-pack/plugins/monitoring/public/index.css b/x-pack/plugins/monitoring/public/index.css new file mode 100644 index 0000000000000..026bf66180cce --- /dev/null +++ b/x-pack/plugins/monitoring/public/index.css @@ -0,0 +1,541 @@ +@-webkit-keyframes euiAnimFadeIn { + 0% { + opacity: 0; } + 100% { + opacity: 1; } } + +@keyframes euiAnimFadeIn { + 0% { + opacity: 0; } + 100% { + opacity: 1; } } + +@-webkit-keyframes euiGrow { + 0% { + opacity: 0; } + 1% { + opacity: 0; + -webkit-transform: scale(0); + transform: scale(0); } + 100% { + opacity: 1; + -webkit-transform: scale(1); + transform: scale(1); } } + +@keyframes euiGrow { + 0% { + opacity: 0; } + 1% { + opacity: 0; + -webkit-transform: scale(0); + transform: scale(0); } + 100% { + opacity: 1; + -webkit-transform: scale(1); + transform: scale(1); } } + +/** + * Text truncation + * + * Prevent text from wrapping onto multiple lines, and truncate with an + * ellipsis. + * + * 1. Ensure that the node has a maximum width after which truncation can + * occur. + * 2. Fix for IE 8/9 if `word-wrap: break-word` is in effect on ancestor + * nodes. + */ +/** + * Set scroll bar appearance on Chrome. + */ +/** + * Specifically target IE11, but not Edge. + */ +@-webkit-keyframes focusRingAnimate { + 0% { + -webkit-box-shadow: 0 0 0 6px rgba(0, 121, 165, 0); + box-shadow: 0 0 0 6px rgba(0, 121, 165, 0); } + 100% { + -webkit-box-shadow: 0 0 0 2px rgba(0, 121, 165, 0.3); + box-shadow: 0 0 0 2px rgba(0, 121, 165, 0.3); } } +@keyframes focusRingAnimate { + 0% { + -webkit-box-shadow: 0 0 0 6px rgba(0, 121, 165, 0); + box-shadow: 0 0 0 6px rgba(0, 121, 165, 0); } + 100% { + -webkit-box-shadow: 0 0 0 2px rgba(0, 121, 165, 0.3); + box-shadow: 0 0 0 2px rgba(0, 121, 165, 0.3); } } + +@-webkit-keyframes focusRingAnimateLarge { + 0% { + -webkit-box-shadow: 0 0 0 10px rgba(0, 121, 165, 0); + box-shadow: 0 0 0 10px rgba(0, 121, 165, 0); } + 100% { + -webkit-box-shadow: 0 0 0 4px rgba(0, 121, 165, 0.3); + box-shadow: 0 0 0 4px rgba(0, 121, 165, 0.3); } } + +@keyframes focusRingAnimateLarge { + 0% { + -webkit-box-shadow: 0 0 0 10px rgba(0, 121, 165, 0); + box-shadow: 0 0 0 10px rgba(0, 121, 165, 0); } + 100% { + -webkit-box-shadow: 0 0 0 4px rgba(0, 121, 165, 0.3); + box-shadow: 0 0 0 4px rgba(0, 121, 165, 0.3); } } + +.tab-no-data, .tab-overview, .tab-license { + background: #F5F5F5; } + +.pui-tooltip-inner { + font-size: 12.0px; } + +.monitoring-tooltip__trigger, +.monitoring-tooltip__trigger:hover { + color: #2D2D2D; } + +.betaIcon { + color: #666; } + +.xpack-breadcrumbs { + min-height: 37px; + padding: 8px 10px; + margin: 0; } + +.monRhythmChart { + position: relative; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + -webkit-box-flex: 1; + -webkit-flex: 1 0 auto; + -ms-flex: 1 0 auto; + flex: 1 0 auto; } + +.monRhythmChart__title { + color: #2D2D2D; + margin: 0 0 8px; } + +.monRhythmChart__content { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-flex: 1; + -webkit-flex: 1 0 auto; + -ms-flex: 1 0 auto; + flex: 1 0 auto; } + +.monRhythmChart__visualization { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + -webkit-box-flex: 1; + -webkit-flex: 1 0 auto; + -ms-flex: 1 0 auto; + flex: 1 0 auto; + position: relative; } + .monRhythmChart__visualization > div { + min-width: 1px; + width: 100%; + height: 100%; } + .monRhythmChart__visualization div { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + -webkit-touch-callout: none; + -webkit-tap-highlight-color: transparent; } + +.monRhythmChart__legendItem { + font-size: 12.0px; + cursor: pointer; + color: #2D2D2D; } + .monRhythmChart__legendItem-isDisabled { + opacity: 0.5; } + +.monRhythmChart__legendHorizontal { + margin-top: 4px; } + +.monRhythmChart__legendLabel { + overflow: hidden; + white-space: nowrap; } + +.monRhythmChart__legendValue { + overflow: hidden; + white-space: nowrap; + margin-left: 4px; } + +.noData__content { + max-width: 600px; + text-align: center; + position: relative; } + +.monSparkline { + height: 2em; } + +.monSparklineTooltip { + font-weight: normal; + background: rgba(63, 63, 63, 0.7); + font-size: 12.0px; + padding: 4px; + border-radius: 4px; + pointer-events: none; } + +.monSparklineTooltip__xValue { + color: rgba(255, 255, 255, 0.7); } + +.monSparklineTooltip__yValue { + color: #FFF; } + +.monSparklineTooltip__caret { + font-size: 18.0px; + color: rgba(63, 63, 63, 0.7); + display: none; } + +.monSparklineTooltip__container { + position: fixed; + z-index: 2000; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -webkit-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; + -webkit-box-pack: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; + -webkit-box-align: center; + -webkit-align-items: center; + -ms-flex-align: center; + align-items: center; } + +.monSummaryStatus { + background-color: #F5F5F5; + border-bottom: 1px solid #D9D9D9; + padding: 16px; } + +/* + * A table that stretches the full window width and columns size appropriately to content. + * The .monitoringTable class is on the KuiControlledTable instance. + * The table within it requires the shrinkToContent flag as well as width set to 100% + */ +.monTable .kuiTable { + width: 100%; } + +.monTableCell__clusterCellExpired, +.monTableCell__offline { + color: #2D2D2D; } + +.monTableCell__clusterCellLiscense { + font-size: 16px; } + +.monTableCell__clusterCellExpiration { + color: #666; + font-size: 14px; + font-size: 1rem; + line-height: 1.5; } + +.monTableCell__name, +.monTableCell__status, +.monTableCell__version { + font-size: 16.0px; + font-size: 1.14286rem; + line-height: 1.5; } + +.monTableCell__transportAddress { + color: #666; + font-size: 14px; + font-size: 1rem; + line-height: 1.5; } + +.monTableCell__number { + font-size: 24.0px; + font-size: 1.71429rem; + line-height: 1.25; + font-weight: 600; } + +.monTableCell__splitNumber { + font-size: 16.0px; + font-size: 1.14286rem; + line-height: 1.5; } + +.monTableCell__metricCellMetric { + display: inline-block; + font-size: 24.0px; + font-size: 1.71429rem; + line-height: 1.25; + font-weight: 600; } + +.monTableCell__metricCellSlopeArrow { + display: inline-block; + margin-left: 4px; + font-size: 24.0px; + font-size: 1.71429rem; + line-height: 1.25; + font-weight: 600; } + +.monTableCell__metricCellMixMax { + display: inline-block; + text-align: right; + margin-left: 4px; + color: #666; + font-size: 14px; + font-size: 1rem; + line-height: 1.5; } + +monitoring-main[page="pipeline"] { + background: #F5F5F5; + min-height: 100vh; } + +.monPipelineViewer { + max-width: 1000px; } + +.monPipelineViewer__statement { + padding-left: 12px; } + +.monPipelineViewer__plugin { + margin-left: 4px; } + +.monPipelineViewer__spaceContainer { + background-color: #FFF; + -webkit-align-self: stretch; + -ms-flex-item-align: stretch; + align-self: stretch; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + border-bottom: solid 2px #FFF; } + +.monPipelineViewer__spacer { + width: 12px; + -webkit-align-self: stretch; + -ms-flex-item-align: stretch; + align-self: stretch; + margin-left: 12px; + border-left: 1px #D9D9D9 dashed; } + .monPipelineViewer__spacer:last-child { + width: 0px; } + .monPipelineViewer__spacer:first-child { + margin-left: 23px; } + +.monPipelineViewer__metric { + text-align: right; } + .monPipelineViewer__metric--cpuTime { + width: 40px; } + .monPipelineViewer__metric--events, .monPipelineViewer__metric--eventsEmitted { + width: 160px; } + .monPipelineViewer__metric--eventMillis { + width: 80px; } + +.monPipelineViewer__queueMessage { + margin-left: 24px; + color: #666; } + +.monPipelineViewer__list .monPipelineViewer__listItem { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + min-height: 32px; + -webkit-box-align: center; + -webkit-align-items: center; + -ms-flex-align: center; + align-items: center; + padding-right: 12px; } + .monPipelineViewer__list .monPipelineViewer__listItem:nth-child(2n+1) { + background: whitesmoke; } + +.monPipelineViewer__conditional { + font-weight: bold; } + +img.lspvDetailDrawerIcon { + display: inline; + margin: 0 4px 0 0; + width: auto; + vertical-align: middle; } + +.lspvDetailDrawerSparklineContainer { + width: 7vw; } + +@media only screen and (min-width: 768px) and (max-width: 991px) { + .monPipelineViewer .monPipelineViewer__spacer { + border: none; } + .monPipelineViewer .monPipelineViewer__metricFlexItem { + margin-bottom: 4px !important; } + .monPipelineViewer .monPipelineViewer__metric { + text-align: left; + padding-left: 32px; } } + +.monChart__container { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + -webkit-box-flex: 1; + -webkit-flex: 1 0 auto; + -ms-flex: 1 0 auto; + flex: 1 0 auto; + height: 200px; + margin-bottom: 16px; } + +.monChart__tooltipTrigger { + float: right; + position: relative; + top: 4px; + right: 16px; } + +.monChart__tooltipLabel, +.monChart__tooltipValue { + text-align: left; + font-size: 12.0px; + padding: 4px; + word-wrap: break-word; + white-space: normal; } + +.monChart__tooltipLabel { + font-weight: 700; } + +monitoring-shard-allocation { + display: block; + border-top: 8px solid #F5F5F5; } + +.monClusterTitle { + font-size: 18.0px; + margin: 0; } + +.monCluster cluster-view { + display: block; } + +.monCluster .parent { + padding-top: 14px; + border-left: 3px solid #017F75 !important; } + .monCluster .parent.red { + border-left: 3px solid #A30000 !important; } + .monCluster .parent.yellow { + border-left: 3px solid #E5830E !important; } + +.monCluster td.unassigned { + vertical-align: middle; + width: 150px; } + +.monCluster .children { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -webkit-flex-flow: row wrap; + -ms-flex-flow: row wrap; + flex-flow: row wrap; } + +.monCluster .child { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-self: center; + -ms-flex-item-align: center; + align-self: center; + background-color: #666; + margin: 5px; } + .monCluster .child.index { + border-left: 4px solid #017F75; } + .monCluster .child.index.red { + border-left: 4px solid #A30000; } + .monCluster .child.index.yellow { + border-left: 4px solid #E5830E; } + .monCluster .child .title { + padding: 5px 7px; + float: left; + text-align: center; + font-size: 12px; + font: 10px sans-serif; + color: #FFF; } + .monCluster .child .title a { + color: #FFF; + text-decoration: none; } + .monCluster .child .title i { + margin-left: 5px; } + .monCluster .child.unassigned .title { + color: #999; + display: none; } + +.monCluster th { + text-align: left; } + +.monCluster td:first-child { + width: 200px; } + +.monCluster .shard { + -webkit-align-self: center; + -ms-flex-item-align: center; + align-self: center; + padding: 5px 7px; + background-color: #0079a5; + font: 10px sans-serif; + border-left: 1px solid #FFF; + position: relative; + color: #FFF; } + .monCluster .shard .shard-tooltip { + padding: 5px; + bottom: 25px; + left: 0; + background-color: #D9D9D9; + position: absolute; + color: #666; + border: 1px solid #D9D9D9; + white-space: nowrap; } + .monCluster .shard.replica { + background-color: #268db3; } + .monCluster .shard.unassigned { + background-color: #999 !important; + color: #000; } + .monCluster .shard.emergency { + background-color: #A30000 !important; + color: #000; } + .monCluster .shard.relocating { + background-color: #490092; } + .monCluster .shard.initializing { + background-color: #6426a2; } + +.monCluster .legend { + font-size: 12px; + background-color: #FFF; + color: #3F3F3F; + padding: 5px; } + .monCluster .legend .title { + margin-left: 5px; + font-weight: bold; } + .monCluster .legend span.shard { + float: none; + display: inline-block; + margin: 0 5px 0 10px; + padding: 0 4px; } +/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL25vZGVfbW9kdWxlcy9AZWxhc3RpYy9ldWkvc3JjL2dsb2JhbF9zdHlsaW5nL3ZhcmlhYmxlcy9fYW5pbWF0aW9ucy5zY3NzIiwiLi4vLi4vLi4vLi4vbm9kZV9tb2R1bGVzL0BlbGFzdGljL2V1aS9zcmMvZ2xvYmFsX3N0eWxpbmcvbWl4aW5zL190eXBvZ3JhcGh5LnNjc3MiLCIuLi8uLi8uLi8uLi9ub2RlX21vZHVsZXMvQGVsYXN0aWMvZXVpL3NyYy9nbG9iYWxfc3R5bGluZy9taXhpbnMvX2hlbHBlcnMuc2NzcyIsIi4uLy4uLy4uLy4uL25vZGVfbW9kdWxlcy9AZWxhc3RpYy9ldWkvc3JjL2dsb2JhbF9zdHlsaW5nL21peGlucy9fc3RhdGVzLnNjc3MiLCIuLi8uLi8uLi8uLi9ub2RlX21vZHVsZXMvQGVsYXN0aWMvZXVpL3NyYy9nbG9iYWxfc3R5bGluZy92YXJpYWJsZXMvX2NvbG9ycy5zY3NzIiwiX2hhY2tzLnNjc3MiLCIuLi8uLi8uLi8uLi9ub2RlX21vZHVsZXMvQGVsYXN0aWMvZXVpL3NyYy90aGVtZXMvazYvazZfZ2xvYmFscy5zY3NzIiwiLi4vLi4vLi4vLi4vbm9kZV9tb2R1bGVzL0BlbGFzdGljL2V1aS9zcmMvdGhlbWVzL2s2L2s2X2NvbG9yc19saWdodC5zY3NzIiwiY29tcG9uZW50cy9jaGFydC9fY2hhcnQuc2NzcyIsIi4uLy4uLy4uLy4uL25vZGVfbW9kdWxlcy9AZWxhc3RpYy9ldWkvc3JjL2dsb2JhbF9zdHlsaW5nL3ZhcmlhYmxlcy9fc2l6ZS5zY3NzIiwiY29tcG9uZW50cy9ub19kYXRhL19ub19kYXRhLnNjc3MiLCJjb21wb25lbnRzL3NwYXJrbGluZS9fc3BhcmtsaW5lLnNjc3MiLCIuLi8uLi8uLi8uLi9ub2RlX21vZHVsZXMvQGVsYXN0aWMvZXVpL3NyYy9nbG9iYWxfc3R5bGluZy92YXJpYWJsZXMvX2JvcmRlcnMuc2NzcyIsIi4uLy4uLy4uLy4uL25vZGVfbW9kdWxlcy9AZWxhc3RpYy9ldWkvc3JjL2dsb2JhbF9zdHlsaW5nL3ZhcmlhYmxlcy9fel9pbmRleC5zY3NzIiwiY29tcG9uZW50cy9zdW1tYXJ5X3N0YXR1cy9fc3VtbWFyeV9zdGF0dXMuc2NzcyIsImNvbXBvbmVudHMvdGFibGUvX3RhYmxlLnNjc3MiLCIuLi8uLi8uLi8uLi9ub2RlX21vZHVsZXMvQGVsYXN0aWMvZXVpL3NyYy9nbG9iYWxfc3R5bGluZy92YXJpYWJsZXMvX3R5cG9ncmFwaHkuc2NzcyIsImNvbXBvbmVudHMvbG9nc3Rhc2gvcGlwZWxpbmVfdmlld2VyL3ZpZXdzL19waXBlbGluZV92aWV3ZXIuc2NzcyIsIi4uLy4uLy4uLy4uL25vZGVfbW9kdWxlcy9AZWxhc3RpYy9ldWkvc3JjL2dsb2JhbF9zdHlsaW5nL2Z1bmN0aW9ucy9fY29sb3JzLnNjc3MiLCIuLi8uLi8uLi8uLi9ub2RlX21vZHVsZXMvQGVsYXN0aWMvZXVpL3NyYy9nbG9iYWxfc3R5bGluZy9taXhpbnMvX3Jlc3BvbnNpdmUuc2NzcyIsImRpcmVjdGl2ZXMvY2hhcnQvX2NoYXJ0LnNjc3MiLCJkaXJlY3RpdmVzL2VsYXN0aWNzZWFyY2gvc2hhcmRfYWxsb2NhdGlvbi9fc2hhcmRfYWxsb2NhdGlvbi5zY3NzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQVdBO0VBQ0U7SUFDRSxXQUFVLEVBQUE7RUFFWjtJQUNFLFdBQVUsRUFBQSxFQUFBOztBQUxkO0VBQ0U7SUFDRSxXQUFVLEVBQUE7RUFFWjtJQUNFLFdBQVUsRUFBQSxFQUFBOztBQUlkO0VBQ0U7SUFDRSxXQUFVLEVBQUE7RUFFWjtJQUNFLFdBQVU7SUFDViw0QkFBbUI7WUFBbkIsb0JBQW1CLEVBQUE7RUFFckI7SUFDRSxXQUFVO0lBQ1YsNEJBQW1CO1lBQW5CLG9CQUFtQixFQUFBLEVBQUE7O0FBVnZCO0VBQ0U7SUFDRSxXQUFVLEVBQUE7RUFFWjtJQUNFLFdBQVU7SUFDViw0QkFBbUI7WUFBbkIsb0JBQW1CLEVBQUE7RUFFckI7SUFDRSxXQUFVO0lBQ1YsNEJBQW1CO1lBQW5CLG9CQUFtQixFQUFBLEVBQUE7O0FDeUR2Qjs7Ozs7Ozs7OztHQVVHO0FDbEVIOztHQUVHO0FBb0JIOztHQUVHO0FDdkNIO0VBQ0U7SUFDRSxtRENoQnFCO1lEZ0JyQiwyQ0NoQnFCLEVBQUE7RURrQnZCO0lBQ0UscURDbkJxQjtZRG1CckIsNkNDbkJxQixFQUFBLEVBQUE7QURjekI7RUFDRTtJQUNFLG1EQ2hCcUI7WURnQnJCLDJDQ2hCcUIsRUFBQTtFRGtCdkI7SUFDRSxxRENuQnFCO1lEbUJyQiw2Q0NuQnFCLEVBQUEsRUFBQTs7QUR1QnpCO0VBQ0U7SUFDRSxvREN6QnFCO1lEeUJyQiw0Q0N6QnFCLEVBQUE7RUQyQnZCO0lBQ0UscURDNUJxQjtZRDRCckIsNkNDNUJxQixFQUFBLEVBQUE7O0FEdUJ6QjtFQUNFO0lBQ0Usb0RDekJxQjtZRHlCckIsNENDekJxQixFQUFBO0VEMkJ2QjtJQUNFLHFEQzVCcUI7WUQ0QnJCLDZDQzVCcUIsRUFBQSxFQUFBOztBQ0R6QjtFQUNFLG9CRGE2QixFQ1o5Qjs7QUFHRDtFQUNFLGtCQ0FzRCxFREN2RDs7QUFFRDs7RUFFRSxlRVpvQixFRmFyQjs7QUFHRDtFQUNFLFlEQ3NCLEVDQXZCOztBQUdEO0VBQ0UsaUJBQWdCO0VBQ2hCLGtCQUFpQjtFQUNqQixVQUFTLEVBQ1Y7O0FHbkJEO0VBQ0UsbUJBQWtCO0VBQ2xCLHFCQUFhO0VBQWIsc0JBQWE7RUFBYixxQkFBYTtFQUFiLGNBQWE7RUFDYiw2QkFBc0I7RUFBdEIsOEJBQXNCO0VBQXRCLCtCQUFzQjtNQUF0QiwyQkFBc0I7VUFBdEIsdUJBQXNCO0VBQ3RCLG9CQUFjO0VBQWQsdUJBQWM7TUFBZCxtQkFBYztVQUFkLGVBQWMsRUFDZjs7QUFFRDtFQUNFLGVEZG9CO0VDZXBCLGdCQ1p5QixFRGExQjs7QUFFRDtFQUNFLG1CQUFrQjtFQUNsQixPQUFNO0VBQ04sU0FBUTtFQUNSLFVBQVM7RUFDVCxRQUFPO0VBQ1AscUJBQWE7RUFBYixzQkFBYTtFQUFiLHFCQUFhO0VBQWIsY0FBYTtFQUNiLG9CQUFjO0VBQWQsdUJBQWM7TUFBZCxtQkFBYztVQUFkLGVBQWMsRUFDZjs7QUFFRDtFQUNFLHFCQUFhO0VBQWIsc0JBQWE7RUFBYixxQkFBYTtFQUFiLGNBQWE7RUFDYiw2QkFBc0I7RUFBdEIsOEJBQXNCO0VBQXRCLCtCQUFzQjtNQUF0QiwyQkFBc0I7VUFBdEIsdUJBQXNCO0VBQ3RCLG9CQUFjO0VBQWQsdUJBQWM7TUFBZCxtQkFBYztVQUFkLGVBQWM7RUFDZCxtQkFBa0IsRUFhbkI7RUFqQkQ7SUFRSSxlQUFjO0lBQ2QsWUFBVztJQUNYLGFBQVksRUFDYjtFQVhIO0lBM0JFLDBCQUFpQjtPQUFqQix1QkFBaUI7UUFBakIsc0JBQWlCO1lBQWpCLGtCQUFpQjtJQUNqQiw0QkFBMkI7SUFDM0IseUNBQXdDLEVBeUN2Qzs7QUFHSDtFQUNFLGtCRnpDc0Q7RUUwQ3RELGdCQUFlO0VBQ2YsZURsRG9CLEVDdURyQjtFQUhDO0lBQ0UsYUFBWSxFQUNiOztBQUdIO0VBQ0UsZ0JDeEQwQixFRHlEM0I7O0FBRUQ7RUFDRSxpQkFBZ0I7RUFDaEIsb0JBQW1CLEVBQ3BCOztBQUNEO0VBQ0UsaUJBQWdCO0VBQ2hCLG9CQUFtQjtFQUNuQixpQkNsRTBCLEVEbUUzQjs7QUVyRUQ7RUFDRSxpQkFBZ0I7RUFDaEIsbUJBQWtCO0VBQ2xCLG1CQUFrQixFQUNuQjs7QUNKRDtFQUNFLFlBQVcsRUFDWjs7QUFHRDtFQUNFLG9CQUFtQjtFQUNuQixrQ0FBcUQ7RUFDckQsa0JMRHNEO0VLRXRELGFGUDBCO0VFUTFCLG1CQ0ptQjtFREtuQixxQkFBb0IsRUFDckI7O0FBRUQ7RUFDRSxnQ0FBeUMsRUFDMUM7O0FBRUQ7RUFDRSxZUGJrQixFT2NuQjs7QUFFRDtFQUNFLGtCTGJzRDtFS2N0RCw2QkFBZ0Q7RUFDaEQsY0FBYSxFQUNkOztBQUVEO0VBQ0UsZ0JBQWU7RUFDZixjRTFCd0I7RUYyQnhCLHFCQUFhO0VBQWIsc0JBQWE7RUFBYixxQkFBYTtFQUFiLGNBQWE7RUFDYiwrQkFBbUI7RUFBbkIsOEJBQW1CO0VBQW5CLDRCQUFtQjtNQUFuQix3QkFBbUI7VUFBbkIsb0JBQW1CO0VBQ25CLHlCQUF1QjtFQUF2QixnQ0FBdUI7TUFBdkIsc0JBQXVCO1VBQXZCLHdCQUF1QjtFQUN2QiwwQkFBbUI7RUFBbkIsNEJBQW1CO01BQW5CLHVCQUFtQjtVQUFuQixvQkFBbUIsRUFDcEI7O0FHbkNEO0VBQ0UsMEJWYzZCO0VVYjdCLGlDVmMwQjtFVWIxQixjTEhnQixFS0lqQjs7QUNKRDs7OztHQUlHO0FBQ0g7RUFDRSxZQUFXLEVBQ1o7O0FBRUQ7O0VBRUUsZVJYb0IsRVFZckI7O0FBRUQ7RUFDRSxnQkFBZSxFQUNoQjs7QUFDRDtFQUNFLFlYQXNCO0VZSnRCLGdCVk5zRDtFVU90RCxnQkFQcUM7RWZpRHJDLGlCZVhxQixFRDFCdEI7O0FBRUQ7OztFQ1JFLGtCVkxzRDtFVU10RCxzQkFQcUM7RWYyRHJDLGlCZXJCcUIsRURwQnRCOztBQUVEO0VBQ0UsWVhYc0I7RVlKdEIsZ0JWTnNEO0VVT3RELGdCQVBxQztFZmlEckMsaUJlWHFCLEVEZnRCOztBQUVEO0VDbkJFLGtCVkhzRDtFVUl0RCxzQkFQcUM7RWZxRXJDLGtCQUFpQjtFQUNqQixpQksvRHNCLEVTb0J2Qjs7QUFFRDtFQ3ZCRSxrQlZMc0Q7RVVNdEQsc0JBUHFDO0VmMkRyQyxpQmVyQnFCLEVEUHRCOztBQUVEO0VBQ0Usc0JBQXFCO0VDNUJyQixrQlZIc0Q7RVVJdEQsc0JBUHFDO0VmcUVyQyxrQkFBaUI7RUFDakIsaUJLL0RzQixFUzZCdkI7O0FBRUQ7RUFDRSxzQkFBcUI7RUFDckIsaUJOOUMwQjtFT1kxQixrQlZIc0Q7RVVJdEQsc0JBUHFDO0VmcUVyQyxrQkFBaUI7RUFDakIsaUJLL0RzQixFU21DdkI7O0FBRUQ7RUFDRSxzQkFBcUI7RUFDckIsa0JBQWlCO0VBQ2pCLGlCTnJEMEI7RU1zRDFCLFlYdENzQjtFWUp0QixnQlZOc0Q7RVVPdEQsZ0JBUHFDO0VmaURyQyxpQmVYcUIsRURZdEI7O0FFekREO0VBQ0Usb0JiYTZCO0VhWjdCLGtCQUFpQixFQUNsQjs7QUFFRDtFQUNFLGtCQUFpQixFQUNsQjs7QUFFRDtFQUNFLG1CUlAwQixFUVEzQjs7QUFFRDtFQUNFLGlCUmIwQixFUWMzQjs7QUFFRDtFQUNFLHVCYkx1QjtFYU12Qiw0QkFBbUI7TUFBbkIsNkJBQW1CO1VBQW5CLG9CQUFtQjtFQUNuQixxQkFBYTtFQUFiLHNCQUFhO0VBQWIscUJBQWE7RUFBYixjQUFhO0VBRWIsOEJiVHVCLEVhVXhCOztBQUVEO0VBQ0UsWVJ2QjBCO0VRd0IxQiw0QkFBbUI7TUFBbkIsNkJBQW1CO1VBQW5CLG9CQUFtQjtFQUNuQixrQlJ6QjBCO0VRMEIxQixnQ0FBdUMsRUFXeEM7RUFmRDtJQVFJLFdBQVUsRUFDWDtFQVRIO0lBYUksa0JBQTRCLEVBQzdCOztBQUdIO0VBQ0Usa0JBQWlCLEVBYWxCO0VBWEM7SUFDRSxZUnhDd0IsRVF5Q3pCO0VBRUQ7SUFDRSxhQUFzQixFQUN2QjtFQUVEO0lBQ0UsWUFBc0IsRUFDdkI7O0FBR0g7RUFDRSxrQlJ2RDBCO0VRd0QxQixZYjNDc0IsRWE0Q3ZCOztBQUVEO0VBRUkscUJBQWE7RUFBYixzQkFBYTtFQUFiLHFCQUFhO0VBQWIsY0FBYTtFQUNiLGlCUjdEc0I7RVE4RHRCLDBCQUFtQjtFQUFuQiw0QkFBbUI7TUFBbkIsdUJBQW1CO1VBQW5CLG9CQUFtQjtFQUNuQixvQlJqRXdCLEVRc0V6QjtFQVZIO0lBUU0sdUJDdEUrQixFRHVFaEM7O0FBSUw7RUFDRSxrQkFBaUIsRUFDbEI7O0FBRUQ7RUFDRSxnQkFBZTtFQUNmLGtCQUF3QjtFQUN4QixZQUFXO0VBQ1gsdUJBQXNCLEVBQ3ZCOztBQUdEO0VBQ0UsV0FBVSxFQUNYOztBRXBEUztFRnVEUjtJQUVJLGFBQVksRUFDYjtFQUhIO0lBTUksOEJBQW9DLEVBQ3JDO0VBUEg7SUFVSSxpQkFBZ0I7SUFDaEIsbUJSbkdvQixFUW9HckIsRUFBQTs7QUcxR0w7RUFDRSxxQkFBYTtFQUFiLHNCQUFhO0VBQWIscUJBQWE7RUFBYixjQUFhO0VBQ2IsNkJBQXNCO0VBQXRCLDhCQUFzQjtFQUF0QiwrQkFBc0I7TUFBdEIsMkJBQXNCO1VBQXRCLHVCQUFzQjtFQUN0QixvQkFBYztFQUFkLHVCQUFjO01BQWQsbUJBQWM7VUFBZCxlQUFjO0VBQ2QsY0FBYTtFQUNiLG9CWExnQixFV01qQjs7QUFFRDtFQUNFLGFBQVk7RUFDWixtQkFBa0I7RUFDbEIsU1hUMEI7RVdVMUIsWVhaZ0IsRVdhakI7O0FBRUQ7O0VBRUUsaUJBQWdCO0VBQ2hCLGtCZFhzRDtFY1l0RCxhWGpCMEI7RVdrQjFCLHNCQUFxQjtFQUNyQixvQkFBbUIsRUFDcEI7O0FBRUQ7RUFDRSxpQko2QnlCLEVJNUIxQjs7QUN6QkQ7RUFDRSxlQUFjO0VBQ2QsOEJqQlk2QixFaUJYOUI7O0FBRUQ7RUFDRSxrQmZHc0Q7RWVGdEQsVUFBUyxFQUNWOztBQUdEO0VBRUksZUFBYyxFQUNmOztBQUhIO0VBS0ksa0JBQWlCO0VBQ2pCLDBDQUFrRCxFQU9uRDtFQWJIO0lBUU0sMENBQWlELEVBQ2xEO0VBVEw7SUFXTSwwQ0FBa0QsRUFDbkQ7O0FBWkw7RUFlSSx1QkFBc0I7RUFDdEIsYUFBWSxFQUNiOztBQWpCSDtFQW1CSSxxQkFBYTtFQUFiLHNCQUFhO0VBQWIscUJBQWE7RUFBYixjQUFhO0VBQ2IsK0JBQW1CO0VBQW5CLDhCQUFtQjtFQUFuQiw0QkFBbUI7TUFBbkIsd0JBQW1CO1VBQW5CLG9CQUFtQixFQUNwQjs7QUFyQkg7RUF1QkkscUJBQWE7RUFBYixzQkFBYTtFQUFiLHFCQUFhO0VBQWIsY0FBYTtFQUNiLDJCQUFrQjtNQUFsQiw0QkFBa0I7VUFBbEIsbUJBQWtCO0VBVWxCLHVCakI1Qm9CO0VpQjZCcEIsWUFBVyxFQXNCWjtFQXpESDtJQTBCTSwrQmpCbkNxQixFaUIwQ3RCO0lBakNMO01BNEJRLCtCakI5QmdCLEVpQitCakI7SUE3QlA7TUErQlEsK0JqQmhDaUIsRWlCaUNsQjtFQWhDUDtJQXFDTSxpQkFBZ0I7SUFDaEIsWUFBVztJQUNYLG1CQUFrQjtJQUNsQixnQkFBZTtJQUNmLHNCQUFxQjtJQUNyQixZakJoRGMsRWlCd0RmO0lBbERMO01BNENRLFlqQmxEWTtNaUJtRFosc0JBQXFCLEVBQ3RCO0lBOUNQO01BZ0RRLGlCQUFnQixFQUNqQjtFQWpEUDtJQXFEUSxZQUFXO0lBQ1gsY0FBYSxFQUNkOztBQXZEUDtFQTRESSxpQkFBZ0IsRUFDakI7O0FBN0RIO0VBZ0VJLGFBQVksRUFDYjs7QUFqRUg7RUFvRUksMkJBQWtCO01BQWxCLDRCQUFrQjtVQUFsQixtQkFBa0I7RUFDbEIsaUJBQWdCO0VBQ2hCLDBCakJoRnFCO0VpQmlGckIsc0JBQXFCO0VBQ3JCLDRCakJ0RXFCO0VpQnVFckIsbUJBQWtCO0VBQ2xCLFlqQmhGZ0IsRWlCa0hqQjtFQTVHSDtJQTZFTSxhQUFZO0lBQ1osYUFBWTtJQUNaLFFBQU87SUFDUCwwQmpCNUVzQjtJaUI2RXRCLG1CQUFrQjtJQUNsQixZakI1RWtCO0lpQjZFbEIsMEJqQi9Fc0I7SWlCZ0Z0QixvQkFBbUIsRUFDcEI7RUFyRkw7SUF3Rk0sMEJIbEcrQixFR21HaEM7RUF6Rkw7SUE0Rk0sa0NBQWlEO0lBQ2pELFlqQnJGa0IsRWlCc0ZuQjtFQTlGTDtJQWlHTSxxQ0FBNEM7SUFDNUMsWWpCMUZrQixFaUIyRm5CO0VBbkdMO0lBc0dNLDBCakIvRWdCLEVpQmdGakI7RUF2R0w7SUEwR00sMEJIcEgrQixFR3FIaEM7O0FBM0dMO0VBK0dJLGdCQUFlO0VBQ2YsdUJqQjlHcUI7RWlCbUhyQixlakI5RzBCO0VpQitHMUIsYUFBWSxFQU9iO0VBN0hIO0lBa0hNLGlCQUFnQjtJQUNoQixrQkFBaUIsRUFDbEI7RUFwSEw7SUF3SE0sWUFBVztJQUNYLHNCQUFxQjtJQUNyQixxQkFBb0I7SUFDcEIsZUFBYyxFQUNmIiwiZmlsZSI6InRvLmNzcyJ9 */ \ No newline at end of file diff --git a/x-pack/plugins/watcher/common/constants/index_names.js b/x-pack/plugins/watcher/common/constants/index_names.js index 120de88bfc02e..663d932fbe133 100644 --- a/x-pack/plugins/watcher/common/constants/index_names.js +++ b/x-pack/plugins/watcher/common/constants/index_names.js @@ -6,5 +6,5 @@ export const INDEX_NAMES = { WATCHES: '.watches', - WATCHER_HISTORY: '.watcher-history-*' + WATCHER_HISTORY: '.watcher-history-*', }; diff --git a/x-pack/yarn.lock b/x-pack/yarn.lock index 0dd2bc432b2fc..23616139ac1ea 100644 --- a/x-pack/yarn.lock +++ b/x-pack/yarn.lock @@ -3,8 +3,14 @@ "@babel/code-frame@^7.0.0-beta.35": - version "7.0.0-beta.37" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0-beta.37.tgz#2da1dd3b1b57bfdea777ddc378df7cd12fe40171" + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0-rc.1.tgz#5c2154415d6c09959a71845ef519d11157e95d10" + dependencies: + "@babel/highlight" "7.0.0-rc.1" + +"@babel/highlight@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.0.0-rc.1.tgz#e0ca4731fa4786f7b9500421d6ff5e5a7753e81e" dependencies: chalk "^2.0.0" esutils "^2.0.2" @@ -92,22 +98,20 @@ samsam "1.3.0" "@slack/client@^4.2.2": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@slack/client/-/client-4.2.2.tgz#f997f39780bbff9c2128816e8377230a5f6bd0d5" + version "4.4.0" + resolved "https://registry.yarnpkg.com/@slack/client/-/client-4.4.0.tgz#5600777fd630375683aeaa3445ddfaec8b5408fb" dependencies: - "@types/delay" "^2.0.1" "@types/form-data" "^2.2.1" "@types/got" "^7.1.7" "@types/is-stream" "^1.1.0" "@types/loglevel" "^1.5.3" - "@types/node" "^9.4.7" + "@types/node" ">=6.0.0" "@types/p-cancelable" "^0.3.0" "@types/p-queue" "^2.3.1" "@types/p-retry" "^1.0.1" "@types/retry" "^0.10.2" "@types/url-join" "^0.8.2" - "@types/ws" "^4.0.1" - delay "^2.0.0" + "@types/ws" "^5.1.1" eventemitter3 "^3.0.0" finity "^0.5.4" form-data "^2.3.1" @@ -119,27 +123,15 @@ object.values "^1.0.4" p-cancelable "^0.3.0" p-queue "^2.3.0" - p-retry "^1.0.0" - retry "^0.10.1" + p-retry "^2.0.0" + retry "^0.12.0" url-join "^4.0.0" - ws "^4.1.0" + ws "^5.2.0" "@types/boom@^4.3.8": version "4.3.10" resolved "https://registry.yarnpkg.com/@types/boom/-/boom-4.3.10.tgz#39dad8c0614c26b91ef016a57d7eee4ffe4f8a25" -"@types/chance@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@types/chance/-/chance-1.0.1.tgz#c10703020369602c40dd9428cc6e1437027116df" - -"@types/delay@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@types/delay/-/delay-2.0.1.tgz#61bcf318a74b61e79d1658fbf054f984c90ef901" - -"@types/elasticsearch@^5.0.24": - version "5.0.25" - resolved "https://registry.yarnpkg.com/@types/elasticsearch/-/elasticsearch-5.0.25.tgz#717255a52acd9fa3ba165072d43a242283b1c898" - "@types/events@*": version "1.2.0" resolved "https://registry.yarnpkg.com/@types/events/-/events-1.2.0.tgz#81a6731ce4df43619e5c8c945383b3e62a89ea86" @@ -156,9 +148,15 @@ dependencies: "@types/node" "*" -"@types/history@*", "@types/history@^4.6.2": - version "4.6.2" - resolved "https://registry.yarnpkg.com/@types/history/-/history-4.6.2.tgz#12cfaba693ba20f114ed5765467ff25fdf67ddb0" +"@types/hapi@15.0.1": + version "15.0.1" + resolved "https://registry.yarnpkg.com/@types/hapi/-/hapi-15.0.1.tgz#919e1d3a9160a080c9fdefaccc892239772e1258" + dependencies: + "@types/node" "*" + +"@types/history@*": + version "4.7.0" + resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.0.tgz#2fac51050c68f7d6f96c5aafc631132522f4aa3f" "@types/is-stream@^1.1.0": version "1.1.0" @@ -171,10 +169,10 @@ resolved "https://registry.yarnpkg.com/@types/jest/-/jest-22.2.3.tgz#0157c0316dc3722c43a7b71de3fdf3acbccef10d" "@types/joi@^10.4.0": - version "10.6.2" - resolved "https://registry.yarnpkg.com/@types/joi/-/joi-10.6.2.tgz#0e7d632fe918c337784e87b16c7cc0098876179a" + version "10.6.4" + resolved "https://registry.yarnpkg.com/@types/joi/-/joi-10.6.4.tgz#0989d69e792a7db13e951852e6949df6787f113f" -"@types/jsonwebtoken@^7.2.7": +"@types/jsonwebtoken@^7.2.8": version "7.2.8" resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-7.2.8.tgz#8d199dab4ddb5bba3234f8311b804d2027af2b3a" dependencies: @@ -188,13 +186,9 @@ version "1.5.3" resolved "https://registry.yarnpkg.com/@types/loglevel/-/loglevel-1.5.3.tgz#adfce55383edc5998a2170ad581b3e23d6adb5b8" -"@types/node@*": - version "9.3.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-9.3.0.tgz#3a129cda7c4e5df2409702626892cb4b96546dd5" - -"@types/node@^9.4.7": - version "9.6.18" - resolved "https://registry.yarnpkg.com/@types/node/-/node-9.6.18.tgz#092e13ef64c47e986802c9c45a61c1454813b31d" +"@types/node@*", "@types/node@>=6.0.0": + version "10.7.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.7.1.tgz#b704d7c259aa40ee052eec678758a68d07132a2e" "@types/p-cancelable@^0.3.0": version "0.3.0" @@ -216,22 +210,21 @@ dependencies: "@types/node" "*" -"@types/react-router-dom@^4.2.7": - version "4.2.7" - resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-4.2.7.tgz#9d36bfe175f916dd8d7b6b0237feed6cce376b4c" +"@types/prop-types@*": + version "15.5.5" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.5.5.tgz#17038dd322c2325f5da650a94d5f9974943625e3" dependencies: - "@types/history" "*" "@types/react" "*" - "@types/react-router" "*" -"@types/react-router@*": - version "4.0.29" - resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-4.0.29.tgz#1a906dd99abf21297a5b7cf003d1fd36e7a92069" +"@types/react-router-dom@^4.2.7": + version "4.3.0" + resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-4.3.0.tgz#c91796d02deb3a5b24bc1c5db4a255df0d18b8b5" dependencies: "@types/history" "*" "@types/react" "*" + "@types/react-router" "*" -"@types/react-router@^4.0.30": +"@types/react-router@*": version "4.0.30" resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-4.0.30.tgz#64bcd886befd1f932779b74d17954adbefb7a3a7" dependencies: @@ -239,19 +232,16 @@ "@types/react" "*" "@types/react@*": - version "16.4.6" - resolved "https://registry.yarnpkg.com/@types/react/-/react-16.4.6.tgz#5024957c6bcef4f02823accf5974faba2e54fada" + version "16.4.11" + resolved "https://registry.yarnpkg.com/@types/react/-/react-16.4.11.tgz#330f3d864300f71150dc2d125e48644c098f8770" dependencies: + "@types/prop-types" "*" csstype "^2.2.0" "@types/retry@*", "@types/retry@^0.10.2": version "0.10.2" resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.10.2.tgz#bd1740c4ad51966609b058803ee6874577848b37" -"@types/sinon@^5.0.1": - version "5.0.1" - resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-5.0.1.tgz#a15b36ec42f1f53166617491feabd1734cb03e21" - "@types/url-join@^0.8.2": version "0.8.2" resolved "https://registry.yarnpkg.com/@types/url-join/-/url-join-0.8.2.tgz#1181ecbe1d97b7034e0ea1e35e62e86cc26b422d" @@ -262,17 +252,21 @@ dependencies: "@types/node" "*" -"@types/ws@^4.0.1": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-4.0.2.tgz#b29037627dd7ba31ec49a4f1584840422efb856f" +"@types/ws@^5.1.1": + version "5.1.2" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-5.1.2.tgz#f02d3b1cd46db7686734f3ce83bdf46c49decd64" dependencies: "@types/events" "*" "@types/node" "*" -abab@^1.0.3, abab@^1.0.4: +abab@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.4.tgz#5faad9c2c07f60dd76770f71cf025b62a63cfd4e" +abab@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.0.tgz#aba0ab4c5eee2d4c79d3487d85450fb2376ebb0f" + abbrev@1: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" @@ -284,30 +278,23 @@ accept@2.x.x: boom "5.x.x" hoek "4.x.x" -acorn-globals@^4.0.0: +acorn-globals@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-4.1.0.tgz#ab716025dbe17c54d3ef81d32ece2b2d99fe2538" dependencies: acorn "^5.0.0" -acorn@^5.0.0, acorn@^5.1.2: - version "5.3.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.3.0.tgz#7446d39459c54fb49a80e6ee6478149b940ec822" +acorn@^5.0.0, acorn@^5.5.3: + version "5.7.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.1.tgz#f095829297706a7c9776958c0afc8930a9b9d9d8" agentkeepalive@^3.4.1: - version "3.4.1" - resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-3.4.1.tgz#aa95aebc3a749bca5ed53e3880a09f5235b48f0c" + version "3.5.1" + resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-3.5.1.tgz#4eba75cf2ad258fc09efd506cdb8d8c2971d35a4" dependencies: humanize-ms "^1.2.1" -ajv@^4.9.1: - version "4.11.8" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536" - dependencies: - co "^4.6.0" - json-stable-stringify "^1.0.1" - -ajv@^5.1.0: +ajv@^5.1.0, ajv@^5.3.0: version "5.5.2" resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965" dependencies: @@ -362,8 +349,8 @@ ansi-escapes@^1.1.0: resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e" ansi-escapes@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.0.0.tgz#ec3e8b4e9f8064fc02c3ac9b65f1c275bda8ef92" + version "3.1.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.1.0.tgz#f73207bb81207d75fd6c83f125af26eea378ca30" ansi-gray@^0.1.1: version "0.1.1" @@ -389,13 +376,7 @@ ansi-styles@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" -ansi-styles@^3.1.0, ansi-styles@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.0.tgz#c159b8d5be0f9e5a6f346dab94f16ce022161b88" - dependencies: - color-convert "^1.9.0" - -ansi-styles@^3.2.1: +ansi-styles@^3.2.0, ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" dependencies: @@ -413,12 +394,12 @@ any-observable@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/any-observable/-/any-observable-0.3.0.tgz#af933475e5806a67d0d7df090dd5e8bef65d119b" -anymatch@^1.3.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.2.tgz#553dcb8f91e3c889845dfdba34c77721b90b9d7a" +anymatch@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" dependencies: - micromatch "^2.1.5" - normalize-path "^2.0.0" + micromatch "^3.1.4" + normalize-path "^2.1.1" append-buffer@^1.0.2: version "1.0.2" @@ -426,11 +407,11 @@ append-buffer@^1.0.2: dependencies: buffer-equal "^1.0.0" -append-transform@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-0.4.0.tgz#d76ebf8ca94d276e247a36bad44a4b74ab611991" +append-transform@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-1.0.0.tgz#046a52ae582a228bd72f58acfbe2967c678759ab" dependencies: - default-require-extensions "^1.0.0" + default-require-extensions "^2.0.0" aproba@^1.0.3: version "1.2.0" @@ -441,15 +422,15 @@ archy@^1.0.0: resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" are-we-there-yet@~1.1.2: - version "1.1.4" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz#bb5dca382bb94f05e15194373d16fd3ba1ca110d" + version "1.1.5" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" dependencies: delegates "^1.0.0" readable-stream "^2.0.6" argparse@^1.0.7: - version "1.0.9" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86" + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" dependencies: sprintf-js "~1.0.2" @@ -544,17 +525,15 @@ asap@~2.0.3: resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" asn1@~0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86" + version "0.2.4" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" + dependencies: + safer-buffer "~2.1.0" assert-plus@1.0.0, assert-plus@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" -assert-plus@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234" - assertion-error@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.0.0.tgz#c7f85438fdd466bc7ca16ab90c81513797a5d23b" @@ -598,22 +577,18 @@ async@^1.4.0: resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" async@^2.1.4: - version "2.6.0" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.0.tgz#61a29abb6fcc026fea77e56d1c6ec53a795951f4" + version "2.6.1" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610" dependencies: - lodash "^4.14.0" + lodash "^4.17.10" asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" -atob@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/atob/-/atob-2.0.3.tgz#19c7a760473774468f20b2d2d03372ad7d4cbf5d" - -atob@~1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/atob/-/atob-1.1.3.tgz#95f13629b12c3a51a5d215abdce2aa9f32f80773" +atob@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" autolinker@~0.15.0: version "0.15.3" @@ -627,17 +602,13 @@ aws-sdk@2.2.33: xml2js "0.2.8" xmlbuilder "0.4.2" -aws-sign2@~0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f" - aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" -aws4@^1.2.1, aws4@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" +aws4@^1.6.0, aws4@^1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" axios@^0.18.0: version "0.18.0" @@ -659,8 +630,8 @@ babel-code-frame@^6.26.0: js-tokens "^3.0.2" babel-core@^6.0.0, babel-core@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.0.tgz#af32f78b31a6fcef119c87b0fd8d9753f03a0bb8" + version "6.26.3" + resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.3.tgz#b2e2f09e342d0f0c88e2f02e067794125e75c207" dependencies: babel-code-frame "^6.26.0" babel-generator "^6.26.0" @@ -672,19 +643,19 @@ babel-core@^6.0.0, babel-core@^6.26.0: babel-traverse "^6.26.0" babel-types "^6.26.0" babylon "^6.18.0" - convert-source-map "^1.5.0" - debug "^2.6.8" + convert-source-map "^1.5.1" + debug "^2.6.9" json5 "^0.5.1" lodash "^4.17.4" minimatch "^3.0.4" path-is-absolute "^1.0.1" - private "^0.1.7" + private "^0.1.8" slash "^1.0.0" - source-map "^0.5.6" + source-map "^0.5.7" babel-generator@^6.18.0, babel-generator@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.0.tgz#ac1ae20070b79f6e3ca1d3269613053774f20dc5" + version "6.26.1" + resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.1.tgz#1844408d3b8f0d35a404ea7ac180f087a601bd90" dependencies: babel-messages "^6.23.0" babel-runtime "^6.26.0" @@ -692,7 +663,7 @@ babel-generator@^6.18.0, babel-generator@^6.26.0: detect-indent "^4.0.0" jsesc "^1.3.0" lodash "^4.17.4" - source-map "^0.5.6" + source-map "^0.5.7" trim-right "^1.0.1" babel-helper-call-delegate@^6.24.1: @@ -770,12 +741,12 @@ babel-helpers@^6.24.1: babel-runtime "^6.22.0" babel-template "^6.24.1" -babel-jest@^22.4.3: - version "22.4.3" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-22.4.3.tgz#4b7a0b6041691bbd422ab49b3b73654a49a6627a" +babel-jest@^22.4.3, babel-jest@^22.4.4: + version "22.4.4" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-22.4.4.tgz#977259240420e227444ebe49e226a61e49ea659d" dependencies: babel-plugin-istanbul "^4.1.5" - babel-preset-jest "^22.4.3" + babel-preset-jest "^22.4.4" babel-messages@^6.23.0: version "6.23.0" @@ -790,16 +761,17 @@ babel-plugin-check-es2015-constants@^6.22.0: babel-runtime "^6.22.0" babel-plugin-istanbul@^4.1.5: - version "4.1.5" - resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.5.tgz#6760cdd977f411d3e175bb064f2bc327d99b2b6e" + version "4.1.6" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz#36c59b2192efce81c5b378321b74175add1c9a45" dependencies: + babel-plugin-syntax-object-rest-spread "^6.13.0" find-up "^2.1.0" - istanbul-lib-instrument "^1.7.5" - test-exclude "^4.1.1" + istanbul-lib-instrument "^1.10.1" + test-exclude "^4.2.1" -babel-plugin-jest-hoist@^22.4.3: - version "22.4.3" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-22.4.3.tgz#7d8bcccadc2667f96a0dcc6afe1891875ee6c14a" +babel-plugin-jest-hoist@^22.4.4: + version "22.4.4" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-22.4.4.tgz#b9851906eab34c7bf6f8c895a2b08bea1a844c0b" babel-plugin-syntax-object-rest-spread@^6.13.0: version "6.13.0" @@ -890,8 +862,8 @@ babel-plugin-transform-es2015-modules-amd@^6.24.1: babel-template "^6.24.1" babel-plugin-transform-es2015-modules-commonjs@^6.24.1: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.0.tgz#0d8394029b7dc6abe1a97ef181e00758dd2e5d8a" + version "6.26.2" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz#58a793863a9e7ca870bdc5a881117ffac27db6f3" dependencies: babel-plugin-transform-strict-mode "^6.24.1" babel-runtime "^6.26.0" @@ -1015,11 +987,11 @@ babel-preset-es2015@^6.24.1: babel-plugin-transform-es2015-unicode-regex "^6.24.1" babel-plugin-transform-regenerator "^6.24.1" -babel-preset-jest@^22.4.3: - version "22.4.3" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-22.4.3.tgz#e92eef9813b7026ab4ca675799f37419b5a44156" +babel-preset-jest@^22.4.4: + version "22.4.4" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-22.4.4.tgz#ec9fbd8bcd7dfd24b8b5320e0e688013235b7c39" dependencies: - babel-plugin-jest-hoist "^22.4.3" + babel-plugin-jest-hoist "^22.4.4" babel-plugin-syntax-object-rest-spread "^6.13.0" babel-register@^6.26.0: @@ -1087,8 +1059,8 @@ base64-js@0.0.8: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-0.0.8.tgz#1101e9544f4a76b1bc3b26d452ca96d7a35e7978" base64-js@^1.0.2, base64-js@^1.1.2: - version "1.2.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.1.tgz#a91947da1f4a516ea38e5b4ec0ec3773675e0886" + version "1.3.0" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3" base@^0.11.1: version "0.11.2" @@ -1103,8 +1075,8 @@ base@^0.11.1: pascalcase "^0.1.1" bcrypt-pbkdf@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d" + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" dependencies: tweetnacl "^0.14.3" @@ -1113,10 +1085,11 @@ beeper@^1.0.0: resolved "https://registry.yarnpkg.com/beeper/-/beeper-1.1.1.tgz#e6d5ea8c5dad001304a70b22638447f69cb2f809" bl@^1.0.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.1.tgz#cac328f7bee45730d404b692203fcb590e172d5e" + version "1.2.2" + resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.2.tgz#a160911717103c07410cef63ef51b397c025af9c" dependencies: - readable-stream "^2.0.5" + readable-stream "^2.3.5" + safe-buffer "^5.1.1" block-stream@*: version "0.0.9" @@ -1136,12 +1109,6 @@ boolbase@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" -boom@2.x.x: - version "2.10.1" - resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f" - dependencies: - hoek "2.x.x" - boom@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/boom/-/boom-3.1.1.tgz#b6424f01ed8d492b2b12ae86047c24e8b6a7c937" @@ -1167,20 +1134,16 @@ boom@5.x.x: hoek "4.x.x" brace-expansion@^1.0.0, brace-expansion@^1.1.7: - version "1.1.8" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" dependencies: balanced-match "^1.0.0" concat-map "0.0.1" -brace@0.11.1: +brace@0.11.1, brace@^0.11.0: version "0.11.1" resolved "https://registry.yarnpkg.com/brace/-/brace-0.11.1.tgz#4896fcc9d544eef45f4bb7660db320d3b379fe58" -brace@^0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/brace/-/brace-0.11.0.tgz#155cd80607687dc8cb908f0df94e62a033c1d563" - braces@^1.8.2: version "1.8.5" resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7" @@ -1189,13 +1152,12 @@ braces@^1.8.2: preserve "^0.2.0" repeat-element "^1.1.2" -braces@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.0.tgz#a46941cb5fb492156b3d6a656e06c35364e3e66e" +braces@^2.3.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" dependencies: arr-flatten "^1.1.0" array-unique "^0.3.2" - define-property "^1.0.0" extend-shallow "^2.0.1" fill-range "^4.0.0" isobject "^3.0.1" @@ -1206,12 +1168,12 @@ braces@^2.3.0: to-regex "^3.0.1" brfs@^1.3.0, brfs@^1.4.0: - version "1.4.3" - resolved "https://registry.yarnpkg.com/brfs/-/brfs-1.4.3.tgz#db675d6f5e923e6df087fca5859c9090aaed3216" + version "1.6.1" + resolved "https://registry.yarnpkg.com/brfs/-/brfs-1.6.1.tgz#b78ce2336d818e25eea04a0947cba6d4fb8849c3" dependencies: quote-stream "^1.0.1" resolve "^1.1.5" - static-module "^1.1.0" + static-module "^2.2.0" through2 "^2.0.0" brotli@^1.2.0: @@ -1225,8 +1187,8 @@ browser-process-hrtime@^0.1.2: resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-0.1.2.tgz#425d68a58d3447f02a04aa894187fce8af8b7b8e" browser-resolve@^1.11.2, browser-resolve@^1.8.1: - version "1.11.2" - resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.2.tgz#8ff09b0a2c421718a1051c260b32e48f442938ce" + version "1.11.3" + resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.3.tgz#9b7cbb3d0f510e4cb86bdbd796124d28b5890af6" dependencies: resolve "1.1.7" @@ -1248,6 +1210,17 @@ bser@^2.0.0: dependencies: node-int64 "^0.4.0" +buffer-alloc-unsafe@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0" + +buffer-alloc@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec" + dependencies: + buffer-alloc-unsafe "^1.1.0" + buffer-fill "^1.0.0" + buffer-crc32@~0.2.3: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" @@ -1264,6 +1237,14 @@ buffer-equal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-1.0.0.tgz#59616b498304d556abd466966b22eeda3eca5fbe" +buffer-fill@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c" + +buffer-from@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" + buffer@^3.0.1: version "3.6.0" resolved "https://registry.yarnpkg.com/buffer/-/buffer-3.6.0.tgz#a72c936f77b96bf52f5f7e7b467180628551defb" @@ -1273,8 +1254,8 @@ buffer@^3.0.1: isarray "^1.0.0" buffer@^5.0.3: - version "5.0.8" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.0.8.tgz#84daa52e7cf2fa8ce4195bc5cf0f7809e0930b24" + version "5.2.0" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.2.0.tgz#53cf98241100099e9eeae20ee6d51d21b16e541e" dependencies: base64-js "^1.0.2" ieee754 "^1.1.4" @@ -1343,9 +1324,11 @@ camelcase@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" -caseless@~0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.11.0.tgz#715b96ea9841593cc33067923f5ec60ebda4f7d7" +capture-exit@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-1.2.0.tgz#1c5fcc489fd0ab00d4f1ac7ae1072e3173fbab6f" + dependencies: + rsvp "^3.3.3" caseless@~0.12.0: version "0.12.0" @@ -1389,15 +1372,7 @@ chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" -chalk@^2.0.0, chalk@^2.0.1: - version "2.3.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.0.tgz#b5ea48efc9c1793dccc9b4767c93914d3f2d52ba" - dependencies: - ansi-styles "^3.1.0" - escape-string-regexp "^1.0.5" - supports-color "^4.0.0" - -chalk@^2.4.1: +chalk@^2.0.0, chalk@^2.0.1, chalk@^2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e" dependencies: @@ -1437,25 +1412,24 @@ chrome-remote-interface@0.24.2: commander "2.1.x" ws "2.0.x" -ci-info@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.1.2.tgz#03561259db48d0474c8bdc90f5b47b068b6bbfb4" +ci-info@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.4.0.tgz#4841d53cad49f11b827b648ebde27a6e189b412f" class-utils@^0.3.5: - version "0.3.5" - resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.5.tgz#17e793103750f9627b2176ea34cfd1b565903c80" + version "0.3.6" + resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" dependencies: arr-union "^3.1.0" define-property "^0.2.5" isobject "^3.0.0" - lazy-cache "^2.0.2" static-extend "^0.1.1" -classnames@2.2.5, classnames@^2.1.2, classnames@^2.2.4: +classnames@2.2.5: version "2.2.5" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.5.tgz#fb3801d453467649ef3603c7d61a02bd129bde6d" -classnames@^2.2.3, classnames@^2.2.5: +classnames@^2.1.2, classnames@^2.2.3, classnames@^2.2.4, classnames@^2.2.5: version "2.2.6" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce" @@ -1494,8 +1468,8 @@ cliui@^3.2.0: wrap-ansi "^2.0.0" cliui@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.0.0.tgz#743d4650e05f36d1ed2575b59638d87322bfbbcc" + version "4.1.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.1.0.tgz#348422dbe82d800b3022eef4f6ac10bf2e4d1b49" dependencies: string-width "^2.1.1" strip-ansi "^4.0.0" @@ -1524,12 +1498,12 @@ clone@^0.2.0: resolved "https://registry.yarnpkg.com/clone/-/clone-0.2.0.tgz#c6126a90ad4f72dbf5acdb243cc37724fe93fc1f" clone@^1.0.0, clone@^1.0.1, clone@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.3.tgz#298d7e2231660f40c003c2ed3140decf3f53085f" + version "1.0.4" + resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" clone@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.1.tgz#d217d1e961118e3ac9a4b8bba3285553bf647cdb" + version "2.1.2" + resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" cloneable-readable@^1.0.0: version "1.1.2" @@ -1555,14 +1529,14 @@ collection-visit@^1.0.0: object-visit "^1.0.0" color-convert@^1.9.0: - version "1.9.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.1.tgz#c1261107aeb2f294ebffec9ed9ecad529a6097ed" + version "1.9.2" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.2.tgz#49881b8fba67df12a96bdf3f56c0aab9e7913147" dependencies: - color-name "^1.1.1" + color-name "1.1.1" -color-name@^1.1.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" +color-name@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.1.tgz#4b1415304cf50028ea81643643bd82ea05803689" color-support@^1.1.3: version "1.1.3" @@ -1572,9 +1546,9 @@ colors@0.5.x: version "0.5.1" resolved "https://registry.yarnpkg.com/colors/-/colors-0.5.1.tgz#7d0023eaeb154e8ee9fce75dcb923d0ed1667774" -combined-stream@^1.0.5, combined-stream@~1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009" +combined-stream@1.0.6, combined-stream@~1.0.5, combined-stream@~1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.6.tgz#723e7df6e801ac5613113a7e445a9b69cb632818" dependencies: delayed-stream "~1.0.0" @@ -1586,7 +1560,7 @@ commander@2.1.x: version "2.1.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.1.0.tgz#d121bbae860d9992a3d517ba96f56588e47c6781" -commander@2.12.2, commander@^2.9.0: +commander@2.12.2: version "2.12.2" resolved "https://registry.yarnpkg.com/commander/-/commander-2.12.2.tgz#0f5946c427ed9ec0d91a46bb9def53e54650e555" @@ -1600,6 +1574,14 @@ commander@2.9.0: dependencies: graceful-readlink ">= 1.0.0" +commander@^2.9.0: + version "2.17.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf" + +compare-versions@^3.1.0: + version "3.3.1" + resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.3.1.tgz#1ede3172b713c15f7c7beb98cb74d2d82576dad3" + component-emitter@^1.2.0, component-emitter@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" @@ -1625,9 +1607,10 @@ concat-stream@1.5.1: typedarray "~0.0.5" concat-stream@^1.4.7, concat-stream@~1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7" + version "1.6.2" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" dependencies: + buffer-from "^1.0.0" inherits "^2.0.3" readable-stream "^2.2.2" typedarray "^0.0.6" @@ -1636,23 +1619,19 @@ console-control-strings@^1.0.0, console-control-strings@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" -content-type-parser@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/content-type-parser/-/content-type-parser-1.0.2.tgz#caabe80623e63638b2502fd4c7f12ff4ce2352e7" - content@3.x.x: - version "3.0.6" - resolved "https://registry.yarnpkg.com/content/-/content-3.0.6.tgz#9c2e301e9ae515ed65a4b877d78aa5659bb1b809" + version "3.0.7" + resolved "https://registry.yarnpkg.com/content/-/content-3.0.7.tgz#0cbb88e82702d35ccf59800b8add609bb5c1dfc2" dependencies: boom "5.x.x" -convert-source-map@^1.4.0, convert-source-map@^1.5.0: +convert-source-map@^1.4.0, convert-source-map@^1.5.0, convert-source-map@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.1.tgz#b8278097b9bc229365de5c62cf5fcaed8b5599e5" cookiejar@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.1.tgz#41ad57b1b555951ec171412a81942b1e8200d34a" + version "2.1.2" + resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.2.tgz#dd8a235530752f988f9a0844f3fc589e3111125c" copy-descriptor@^0.1.0: version "0.1.1" @@ -1662,11 +1641,7 @@ core-js@^1.0.0: version "1.2.7" resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" -core-js@^2.4.0, core-js@^2.5.0: - version "2.5.3" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.3.tgz#8acc38345824f16d8365b7c9b4259168e8ed603e" - -core-js@^2.5.1: +core-js@^2.4.0, core-js@^2.5.0, core-js@^2.5.1: version "2.5.7" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.7.tgz#f972608ff0cead68b841a16a932d0b183791814e" @@ -1674,14 +1649,6 @@ core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" -create-react-class@^15.5.2: - version "15.6.2" - resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.6.2.tgz#cf1ed15f12aad7f14ef5f2dfe05e6c42f91ef02a" - dependencies: - fbjs "^0.8.9" - loose-envify "^1.3.1" - object-assign "^4.1.1" - cross-spawn@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-3.0.1.tgz#1256037ecb9f0c5f79e3d6ef135e30770184b982" @@ -1707,12 +1674,6 @@ cross-spawn@^6.0.0: shebang-command "^1.2.0" which "^1.2.9" -cryptiles@2.x.x: - version "2.0.5" - resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" - dependencies: - boom "2.x.x" - cryptiles@3.x.x: version "3.1.2" resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-3.1.2.tgz#a89fbb220f5ce25ec56e8c4aa8a4fd7b5b0d29fe" @@ -1733,8 +1694,8 @@ css-select@~1.2.0: nth-check "~1.0.1" css-to-react-native@^2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-2.0.4.tgz#cf4cc407558b3474d4ba8be1a2cd3b6ce713101b" + version "2.2.1" + resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-2.2.1.tgz#7f3f4c95de65501b8720c87bf0caf1f39073b88e" dependencies: css-color-keywords "^1.0.0" fbjs "^0.8.5" @@ -1745,27 +1706,27 @@ css-what@2.1: resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.0.tgz#9467d032c38cfaefb9f2d79501253062f87fa1bd" css@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/css/-/css-2.2.1.tgz#73a4c81de85db664d4ee674f7d47085e3b2d55dc" + version "2.2.3" + resolved "https://registry.yarnpkg.com/css/-/css-2.2.3.tgz#f861f4ba61e79bedc962aa548e5780fd95cbc6be" dependencies: inherits "^2.0.1" source-map "^0.1.38" - source-map-resolve "^0.3.0" + source-map-resolve "^0.5.1" urix "^0.1.0" cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0": - version "0.3.2" - resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.2.tgz#b8036170c79f07a90ff2f16e22284027a243848b" + version "0.3.4" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.4.tgz#8cd52e8a3acfd68d3aed38ee0a640177d2f9d797" -"cssstyle@>= 0.2.37 < 0.3.0": - version "0.2.37" - resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-0.2.37.tgz#541097234cb2513c83ceed3acddc27ff27987d54" +cssstyle@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-1.1.1.tgz#18b038a9c44d65f7a8e428a653b9f6fe42faf5fb" dependencies: cssom "0.3.x" csstype@^2.2.0: - version "2.5.5" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.5.5.tgz#4125484a3d42189a863943f23b9e4b80fedfa106" + version "2.5.6" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.5.6.tgz#2ae1db2319642d8b80a668d2d025c6196071e788" currently-unhandled@^0.4.1: version "0.4.1" @@ -1782,32 +1743,32 @@ d3-collection@1, d3-collection@^1.0.3: resolved "https://registry.yarnpkg.com/d3-collection/-/d3-collection-1.0.4.tgz#342dfd12837c90974f33f1cc0a785aea570dcdc2" d3-color@1, d3-color@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-1.0.3.tgz#bc7643fca8e53a8347e2fbdaffa236796b58509b" + version "1.2.0" + resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-1.2.0.tgz#d1ea19db5859c86854586276ec892cf93148459a" d3-contour@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/d3-contour/-/d3-contour-1.1.2.tgz#21f5456fcf57645922d69a27a58e782c91f842b3" + version "1.3.0" + resolved "https://registry.yarnpkg.com/d3-contour/-/d3-contour-1.3.0.tgz#cfb99098c48c46edd77e15ce123162f9e333e846" dependencies: d3-array "^1.1.1" d3-format@1, d3-format@^1.2.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.2.1.tgz#4e19ecdb081a341dafaf5f555ee956bcfdbf167f" + version "1.3.0" + resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.3.0.tgz#a3ac44269a2011cdb87c7b5693040c18cddfff11" d3-geo@^1.6.4: - version "1.9.1" - resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-1.9.1.tgz#157e3b0f917379d0f73bebfff3be537f49fa7356" + version "1.10.0" + resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-1.10.0.tgz#2972d18014f1e38fc1f8bb6d545377bdfb00c9ab" dependencies: d3-array "1" d3-hierarchy@^1.1.4: - version "1.1.5" - resolved "https://registry.yarnpkg.com/d3-hierarchy/-/d3-hierarchy-1.1.5.tgz#a1c845c42f84a206bcf1c01c01098ea4ddaa7a26" + version "1.1.6" + resolved "https://registry.yarnpkg.com/d3-hierarchy/-/d3-hierarchy-1.1.6.tgz#842c1372090f870b7ea013ebae5c0c8d9f56229c" d3-interpolate@1, d3-interpolate@^1.1.4: - version "1.1.6" - resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-1.1.6.tgz#2cf395ae2381804df08aa1bf766b7f97b5f68fb6" + version "1.2.0" + resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-1.2.0.tgz#40d81bd8e959ff021c5ea7545bc79b8d22331c41" dependencies: d3-color "1" @@ -1877,6 +1838,14 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" +data-urls@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-1.0.1.tgz#d416ac3896918f29ca84d81085bc3705834da579" + dependencies: + abab "^2.0.0" + whatwg-mimetype "^2.1.0" + whatwg-url "^7.0.0" + dateformat@^1.0.11: version "1.0.12" resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-1.0.12.tgz#9f124b67594c937ff706932e4a642cca8dbbfee9" @@ -1904,7 +1873,7 @@ debug@2.6.0: dependencies: ms "0.7.2" -debug@^2.2.0, debug@^2.3.3, debug@^2.6.8: +debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" dependencies: @@ -1944,19 +1913,19 @@ deep-equal@^1.0.0, deep-equal@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" -deep-extend@~0.4.0: - version "0.4.2" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f" +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" deep-is@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" -default-require-extensions@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-1.0.0.tgz#f37ea15d3e13ffd9b437d33e1a75b5fb97874cb8" +default-require-extensions@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-2.0.0.tgz#f5f8fbb18a7d6d50b21f641f649ebb522cfe24f7" dependencies: - strip-bom "^2.0.0" + strip-bom "^3.0.0" defaults@^1.0.0: version "1.0.3" @@ -1965,11 +1934,10 @@ defaults@^1.0.0: clone "^1.0.2" define-properties@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.2.tgz#83a73f2fea569898fb737193c8f873caf6d45c94" + version "1.1.3" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" dependencies: - foreach "^2.0.5" - object-keys "^1.0.8" + object-keys "^1.0.12" define-property@^0.2.5: version "0.2.5" @@ -1983,6 +1951,13 @@ define-property@^1.0.0: dependencies: is-descriptor "^1.0.0" +define-property@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" + dependencies: + is-descriptor "^1.0.2" + isobject "^3.0.1" + del@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/del/-/del-2.2.2.tgz#c12c981d067846c84bcaf862cff930d907ffd1a8" @@ -2006,12 +1981,6 @@ del@^3.0.0: pify "^3.0.0" rimraf "^2.2.8" -delay@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/delay/-/delay-2.0.0.tgz#9112eadc03e4ec7e00297337896f273bbd91fae5" - dependencies: - p-defer "^1.0.0" - delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -2060,14 +2029,10 @@ diff@3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.2.0.tgz#c9ce393a4b7cbd0b058a725c93df299027868ff9" -diff@^3.1.0: +diff@^3.2.0, diff@^3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" -diff@^3.2.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-3.4.0.tgz#b1d85507daf3964828de54b37d0d73ba67dda56c" - discontinuous-range@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/discontinuous-range/-/discontinuous-range-1.0.0.tgz#e38331f0844bba49b9a9cb71c771585aab1bc65a" @@ -2095,13 +2060,15 @@ domelementtype@~1.1.1: version "1.1.3" resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b" -domexception@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/domexception/-/domexception-1.0.0.tgz#81fe5df81b3f057052cde3a9fa9bf536a85b9ab0" +domexception@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/domexception/-/domexception-1.0.1.tgz#937442644ca6a31261ef36e3ec677fe805582c90" + dependencies: + webidl-conversions "^4.0.2" domhandler@^2.3.0: - version "2.4.1" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.1.tgz#892e47000a99be55bbf3774ffea0561d8879c259" + version "2.4.2" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803" dependencies: domelementtype "1" @@ -2113,8 +2080,8 @@ domutils@1.5.1: domelementtype "1" domutils@^1.5.1: - version "1.6.2" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.6.2.tgz#1958cc0b4c9426e9ed367fb1c8e854891b0fa3ff" + version "1.7.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" dependencies: dom-serializer "0" domelementtype "1" @@ -2127,19 +2094,25 @@ dragselect@1.7.17: version "1.7.17" resolved "https://registry.yarnpkg.com/dragselect/-/dragselect-1.7.17.tgz#ab98661d8599286c0ada66ce5f5923b06b4f09fd" -duplexer2@0.0.2, duplexer2@~0.0.2: +duplexer2@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.0.2.tgz#c614dcf67e2fb14995a91711e5a617e8a60a31db" dependencies: readable-stream "~1.1.9" +duplexer2@~0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" + dependencies: + readable-stream "^2.0.2" + duplexer3@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" -duplexify@^3.5.3: - version "3.5.4" - resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.5.4.tgz#4bb46c1796eabebeec4ca9a2e66b808cb7a3d8b4" +duplexify@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.6.0.tgz#592903f5d80b38d037220541264d69a198fb3410" dependencies: end-of-stream "^1.0.0" inherits "^2.0.1" @@ -2151,10 +2124,11 @@ easing-js@^1.1.2: resolved "https://registry.yarnpkg.com/easing-js/-/easing-js-1.1.2.tgz#42077952bc3cd6e06aa6d336a9bf3c4eeced2594" ecc-jsbn@~0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505" + version "0.1.2" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" dependencies: jsbn "~0.1.0" + safer-buffer "^2.1.0" ecdsa-sig-formatter@1.0.10: version "1.0.10" @@ -2180,8 +2154,8 @@ encoding@^0.1.11: iconv-lite "~0.4.13" end-of-stream@^1.0.0, end-of-stream@^1.1.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.0.tgz#7a90d833efda6cfa6eac0f4949dbb0fad3a63206" + version "1.4.1" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43" dependencies: once "^1.4.0" @@ -2196,24 +2170,25 @@ entities@^1.1.1, entities@~1.1.1: resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" enzyme-adapter-react-16@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.1.1.tgz#a8f4278b47e082fbca14f5bfb1ee50ee650717b4" + version "1.2.0" + resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.2.0.tgz#c6e80f334e0a817873262d7d01ee9e4747e3c97e" dependencies: - enzyme-adapter-utils "^1.3.0" - lodash "^4.17.4" - object.assign "^4.0.4" + enzyme-adapter-utils "^1.5.0" + function.prototype.name "^1.1.0" + object.assign "^4.1.0" object.values "^1.0.4" - prop-types "^15.6.0" + prop-types "^15.6.2" + react-is "^16.4.2" react-reconciler "^0.7.0" react-test-renderer "^16.0.0-0" -enzyme-adapter-utils@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/enzyme-adapter-utils/-/enzyme-adapter-utils-1.3.0.tgz#d6c85756826c257a8544d362cc7a67e97ea698c7" +enzyme-adapter-utils@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/enzyme-adapter-utils/-/enzyme-adapter-utils-1.5.0.tgz#a020ab3ae79bb1c85e1d51f48f35e995e0eed810" dependencies: - lodash "^4.17.4" - object.assign "^4.0.4" - prop-types "^15.6.0" + function.prototype.name "^1.1.0" + object.assign "^4.1.0" + prop-types "^15.6.2" enzyme-to-json@3.3.1: version "3.3.1" @@ -2238,14 +2213,14 @@ enzyme@3.2.0: rst-selector-parser "^2.2.3" error-ex@^1.2.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.1.tgz#f855a86ce61adc4e8621c3cda21e7a7612c3a8dc" + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" dependencies: is-arrayish "^0.2.1" es-abstract@^1.5.1, es-abstract@^1.6.1: - version "1.10.0" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.10.0.tgz#1ecb36c197842a00d8ee4c2dfd8646bb97d60864" + version "1.12.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.12.0.tgz#9dbbdd27c6856f0001421ca18782d786bf8a6165" dependencies: es-to-primitive "^1.1.1" function-bind "^1.1.1" @@ -2273,25 +2248,16 @@ escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1 version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" -escodegen@^1.9.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.9.0.tgz#9811a2f265dc1cd3894420ee3717064b632b8852" +escodegen@^1.8.1, escodegen@^1.9.1: + version "1.11.0" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.11.0.tgz#b27a9389481d5bfd5bec76f7bb1eb3f8f4556589" dependencies: esprima "^3.1.3" estraverse "^4.2.0" esutils "^2.0.2" optionator "^0.8.1" optionalDependencies: - source-map "~0.5.6" - -escodegen@~0.0.24: - version "0.0.28" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-0.0.28.tgz#0e4ff1715f328775d6cab51ac44a406cd7abffd3" - dependencies: - esprima "~1.0.2" - estraverse "~1.3.0" - optionalDependencies: - source-map ">= 0.1.2" + source-map "~0.6.1" escodegen@~1.2.0: version "1.2.0" @@ -2303,40 +2269,33 @@ escodegen@~1.2.0: optionalDependencies: source-map "~0.1.30" -escodegen@~1.3.2: - version "1.3.3" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.3.3.tgz#f024016f5a88e046fd12005055e939802e6c5f23" +escodegen@~1.9.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.9.1.tgz#dbae17ef96c8e4bedb1356f4504fa4cc2f7cb7e2" dependencies: - esprima "~1.1.1" - estraverse "~1.5.0" - esutils "~1.0.0" + esprima "^3.1.3" + estraverse "^4.2.0" + esutils "^2.0.2" + optionator "^0.8.1" optionalDependencies: - source-map "~0.1.33" + source-map "~0.6.1" esprima@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" esprima@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804" + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" -esprima@~1.0.2, esprima@~1.0.4: +esprima@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/esprima/-/esprima-1.0.4.tgz#9f557e08fc3b4d26ece9dd34f8fbf476b62585ad" -esprima@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-1.1.1.tgz#5b6f1547f4d102e670e140c509be6771d6aeb549" - estraverse@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" -estraverse@~1.3.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-1.3.2.tgz#37c2b893ef13d723f276d878d60d8535152a6c42" - estraverse@~1.5.0: version "1.5.1" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-1.5.1.tgz#867a3e8e58a9f84618afb6c2ddbcd916b7cbaf71" @@ -2354,10 +2313,10 @@ eventemitter3@^3.0.0: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.0.tgz#090b4d6cdbd645ed10bf750d4b5407942d7ba163" exec-sh@^0.2.0: - version "0.2.1" - resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.2.1.tgz#163b98a6e89e6b65b47c2a28d215bc1f63989c38" + version "0.2.2" + resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.2.2.tgz#2a5e7ffcbd7d0ba2755bdecb16e5a427dfbdec36" dependencies: - merge "^1.1.3" + merge "^1.2.0" execa@^0.10.0: version "0.10.0" @@ -2425,7 +2384,7 @@ expect.js@0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/expect.js/-/expect.js-0.3.1.tgz#b0a59a0d2eff5437544ebf0ceaa6015841d09b5b" -expect@^22.4.3: +expect@^22.4.0: version "22.4.3" resolved "https://registry.yarnpkg.com/expect/-/expect-22.4.3.tgz#d5a29d0a0e1fb2153557caef2674d4547e914674" dependencies: @@ -2448,16 +2407,16 @@ extend-shallow@^2.0.1: dependencies: is-extendable "^0.1.0" -extend-shallow@^3.0.0: +extend-shallow@^3.0.0, extend-shallow@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" dependencies: assign-symbols "^1.0.0" is-extendable "^1.0.1" -extend@^3.0.0, extend@~3.0.0, extend@~3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" +extend@^3.0.0, extend@~3.0.1, extend@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" external-editor@^1.1.0: version "1.1.1" @@ -2473,9 +2432,9 @@ extglob@^0.3.1: dependencies: is-extglob "^1.0.0" -extglob@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.3.tgz#55e019d0c95bf873949c737b7e5172dba84ebb29" +extglob@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" dependencies: array-unique "^0.3.2" define-property "^1.0.0" @@ -2521,8 +2480,8 @@ fancy-log@^1.1.0, fancy-log@^1.3.2: time-stamp "^1.0.0" fast-deep-equal@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff" + version "1.1.0" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614" fast-json-stable-stringify@^2.0.0: version "2.0.0" @@ -2532,6 +2491,12 @@ fast-levenshtein@~2.0.4: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" +fault@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/fault/-/fault-1.0.2.tgz#c3d0fec202f172a3a4d414042ad2bb5e2a3ffbaa" + dependencies: + format "^0.2.2" + fb-watchman@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.0.tgz#54e9abf7dfa2f26cd9b1636c588c1afc05de5d58" @@ -2539,8 +2504,8 @@ fb-watchman@^2.0.0: bser "^2.0.0" fbjs@^0.8.16, fbjs@^0.8.4, fbjs@^0.8.5, fbjs@^0.8.9: - version "0.8.16" - resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.16.tgz#5e67432f550dc41b572bf55847b8aca64e5337db" + version "0.8.17" + resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd" dependencies: core-js "^1.0.0" isomorphic-fetch "^2.1.1" @@ -2548,7 +2513,7 @@ fbjs@^0.8.16, fbjs@^0.8.4, fbjs@^0.8.5, fbjs@^0.8.9: object-assign "^4.1.0" promise "^7.1.1" setimmediate "^1.0.5" - ua-parser-js "^0.7.9" + ua-parser-js "^0.7.18" fd-slicer@~1.0.1: version "1.0.1" @@ -2596,12 +2561,12 @@ fill-keys@^1.0.2: merge-descriptors "~1.0.0" fill-range@^2.1.0: - version "2.2.3" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.3.tgz#50b77dfd7e469bc7492470963699fe7a8485a723" + version "2.2.4" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.4.tgz#eb1e773abb056dcd8df2bfdf6af59b8b3a936565" dependencies: is-number "^2.1.0" isobject "^2.0.0" - randomatic "^1.1.3" + randomatic "^3.0.0" repeat-element "^1.1.2" repeat-string "^1.5.2" @@ -2669,27 +2634,27 @@ flagged-respawn@^1.0.0: resolved "https://registry.yarnpkg.com/flagged-respawn/-/flagged-respawn-1.0.0.tgz#4e79ae9b2eb38bf86b3bb56bf3e0a56aa5fcabd7" flush-write-stream@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.0.2.tgz#c81b90d8746766f1a609a46809946c45dd8ae417" + version "1.0.3" + resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.0.3.tgz#c5d586ef38af6097650b49bc41b55fabb19f35bd" dependencies: inherits "^2.0.1" readable-stream "^2.0.4" focus-trap-react@^3.0.4, focus-trap-react@^3.1.1: - version "3.1.2" - resolved "https://registry.yarnpkg.com/focus-trap-react/-/focus-trap-react-3.1.2.tgz#4dd021ccd028bbd3321147d132cdf7585d6d1394" + version "3.1.4" + resolved "https://registry.yarnpkg.com/focus-trap-react/-/focus-trap-react-3.1.4.tgz#e95f4aece5c493be4d3653dfccd5036d11ad24d5" dependencies: focus-trap "^2.0.1" focus-trap@^2.0.1: - version "2.4.2" - resolved "https://registry.yarnpkg.com/focus-trap/-/focus-trap-2.4.2.tgz#44ea1c55a9c22c2b6529dcebbde6390eb2ee4c88" + version "2.4.6" + resolved "https://registry.yarnpkg.com/focus-trap/-/focus-trap-2.4.6.tgz#332b475b317cec6a4a129f5307ce7ebc0da90b40" dependencies: tabbable "^1.0.3" follow-redirects@^1.3.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.4.1.tgz#d8120f4518190f55aac65bb6fc7b85fcd666d6aa" + version "1.5.6" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.6.tgz#44eb4fe1981dff25e2bd86b7d4033abcdb81e965" dependencies: debug "^3.1.0" @@ -2737,25 +2702,32 @@ forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" -form-data@^2.3.1, form-data@~2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.1.tgz#6fb94fbd71885306d73d15cc497fe4cc4ecd44bf" - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.5" - mime-types "^2.1.12" +form-data-to-object@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/form-data-to-object/-/form-data-to-object-0.2.0.tgz#f7a8e68ddd910a1100a65e25ac6a484143ff8168" -form-data@~2.1.1: - version "2.1.4" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.4.tgz#33c183acf193276ecaa98143a69e94bfee1750d1" +form-data@^2.3.1, form-data@~2.3.1, form-data@~2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.2.tgz#4970498be604c20c005d4f5c23aecd21d6b49099" dependencies: asynckit "^0.4.0" - combined-stream "^1.0.5" + combined-stream "1.0.6" mime-types "^2.1.12" -formidable@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.1.1.tgz#96b8886f7c3c3508b932d6bd70c4d3a88f35f1a9" +format@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b" + +formidable@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.2.1.tgz#70fb7ca0290ee6ff961090415f4b3df3d2082659" + +formsy-react@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/formsy-react/-/formsy-react-1.1.4.tgz#687525cbcaa6f6a6000e0c8029076fb5ac8dac3e" + dependencies: + form-data-to-object "^0.2.0" + prop-types "^15.5.10" fragment-cache@^0.2.1: version "0.2.1" @@ -2774,6 +2746,16 @@ from@^0.1.3: version "0.1.7" resolved "https://registry.yarnpkg.com/from/-/from-0.1.7.tgz#83c60afc58b9c56997007ed1a768b3ab303a44fe" +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + +fs-minipass@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d" + dependencies: + minipass "^2.2.1" + fs-mkdirp-stream@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz#0b7815fc3201c6a69e14db98ce098c16935259eb" @@ -2785,22 +2767,14 @@ fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" -fsevents@^1.1.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.1.3.tgz#11f82318f5fe7bb2cd22965a108e9306208216d8" - dependencies: - nan "^2.3.0" - node-pre-gyp "^0.6.39" - -fstream-ignore@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/fstream-ignore/-/fstream-ignore-1.0.5.tgz#9c31dae34767018fe1d249b24dada67d092da105" +fsevents@^1.2.3: + version "1.2.4" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.4.tgz#f41dcb1af2582af3692da36fc55cbd8e1041c426" dependencies: - fstream "^1.0.0" - inherits "2" - minimatch "^3.0.0" + nan "^2.9.2" + node-pre-gyp "^0.10.0" -fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2: +fstream@^1.0.0, fstream@^1.0.2: version "1.0.11" resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171" dependencies: @@ -2809,11 +2783,11 @@ fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2: mkdirp ">=0.5 0" rimraf "2" -function-bind@^1.0.2, function-bind@^1.1.0, function-bind@^1.1.1: +function-bind@^1.1.0, function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" -function.prototype.name@^1.0.3: +function.prototype.name@^1.0.3, function.prototype.name@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.0.tgz#8bd763cc0af860a859cc5d49384d74b932cd2327" dependencies: @@ -2846,19 +2820,9 @@ gaze@^1.0.0: dependencies: globule "^1.0.0" -generate-function@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.0.0.tgz#6858fe7c0969b7d4e9093337647ac79f60dfbe74" - -generate-object-property@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/generate-object-property/-/generate-object-property-1.2.0.tgz#9c0e1c40308ce804f4783618b937fa88f99d50d0" - dependencies: - is-property "^1.0.0" - get-caller-file@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5" + version "1.0.3" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" get-port@2.1.0: version "2.1.0" @@ -2879,8 +2843,8 @@ get-value@^2.0.3, get-value@^2.0.6: resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" getopts@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/getopts/-/getopts-2.0.6.tgz#4788d533a977527e79efd57b5e742ffa0dd33105" + version "2.2.0" + resolved "https://registry.yarnpkg.com/getopts/-/getopts-2.2.0.tgz#22cfc88e666b00a15342c87188f022b68a07f2c3" getos@^3.1.0: version "3.1.0" @@ -3088,8 +3052,8 @@ globule@~0.1.0: minimatch "~0.2.11" glogg@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/glogg/-/glogg-1.0.0.tgz#7fe0f199f57ac906cf512feead8f90ee4a284fc5" + version "1.0.1" + resolved "https://registry.yarnpkg.com/glogg/-/glogg-1.0.1.tgz#dcf758e44789cc3f3d32c1f3562a3676e6a34810" dependencies: sparkles "^1.0.0" @@ -3100,8 +3064,8 @@ good-listener@^1.2.2: delegate "^3.1.2" got@^8.0.3: - version "8.3.1" - resolved "https://registry.yarnpkg.com/got/-/got-8.3.1.tgz#093324403d4d955f5a16a7a8d39955d055ae10ed" + version "8.3.2" + resolved "https://registry.yarnpkg.com/got/-/got-8.3.2.tgz#1d23f64390e97f776cac52e5b936e5f514d2e937" dependencies: "@sindresorhus/is" "^0.7.0" cacheable-request "^2.1.1" @@ -3228,8 +3192,8 @@ gulp-zip@3.1.0: yazl "^2.1.0" gulp-zip@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/gulp-zip/-/gulp-zip-4.1.0.tgz#dab178bd99afa190923f1eb78abaf0db47817704" + version "4.2.0" + resolved "https://registry.yarnpkg.com/gulp-zip/-/gulp-zip-4.2.0.tgz#e25e738c41ad0795ad853d1d8aeb1744d2a4ca82" dependencies: get-stream "^3.0.0" plugin-error "^0.1.2" @@ -3303,30 +3267,10 @@ hapi@14.2.0: subtext "4.x.x" topo "2.x.x" -har-schema@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e" - har-schema@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" -har-validator@~2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-2.0.6.tgz#cdcbc08188265ad119b6a5a7c8ab70eecfb5d27d" - dependencies: - chalk "^1.1.1" - commander "^2.9.0" - is-my-json-valid "^2.12.4" - pinkie-promise "^2.0.0" - -har-validator@~4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a" - dependencies: - ajv "^4.9.1" - har-schema "^1.0.5" - har-validator@~5.0.3: version "5.0.3" resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.0.3.tgz#ba402c266194f15956ef15e0fcf242993f6a7dfd" @@ -3334,6 +3278,13 @@ har-validator@~5.0.3: ajv "^5.1.0" har-schema "^2.0.0" +har-validator@~5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.0.tgz#44657f5688a22cfd4b72486e81b3a3fb11742c29" + dependencies: + ajv "^5.3.0" + har-schema "^2.0.0" + has-ansi@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" @@ -3344,10 +3295,6 @@ has-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" -has-flag@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" - has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" @@ -3403,36 +3350,18 @@ has-values@^1.0.0: is-number "^3.0.0" kind-of "^4.0.0" -has@^1.0.0, has@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.1.tgz#8461733f538b0837c9361e39a9ab9e9704dc2f28" +has@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" dependencies: - function-bind "^1.0.2" + function-bind "^1.1.1" -hasharray@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/hasharray/-/hasharray-1.1.0.tgz#fe87cf9977baa9d9159b8465a8e2edf58fd0d681" +hasharray@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/hasharray/-/hasharray-1.1.1.tgz#0bfadd91c0ee76919c05eeef5a3dc1034b4ea4f1" dependencies: jclass "^1.0.1" -hawk@3.1.3, hawk@~3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4" - dependencies: - boom "2.x.x" - cryptiles "2.x.x" - hoek "2.x.x" - sntp "1.x.x" - -hawk@~6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/hawk/-/hawk-6.0.2.tgz#af4d914eb065f9b5ce4d9d11c1cb2126eecc3038" - dependencies: - boom "4.x.x" - cryptiles "3.x.x" - hoek "4.x.x" - sntp "2.x.x" - heavy@4.x.x: version "4.0.4" resolved "https://registry.yarnpkg.com/heavy/-/heavy-4.0.4.tgz#36c91336c00ccfe852caa4d153086335cd2f00e9" @@ -3463,18 +3392,10 @@ hoek@3.x.x: version "3.0.4" resolved "https://registry.yarnpkg.com/hoek/-/hoek-3.0.4.tgz#268adff66bb6695c69b4789a88b1e0847c3f3123" -hoek@4.2.1: +hoek@4.2.1, hoek@4.x.x: version "4.2.1" resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.1.tgz#9634502aa12c445dd5a7c5734b572bb8738aacbb" -hoek@4.x.x: - version "4.2.0" - resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.0.tgz#72d9d0754f7fe25ca2d01ad8f8f9a9449a89526d" - -hoist-non-react-statics@^2.3.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.3.1.tgz#343db84c6018c650778898240135a1420ee22ce0" - hoist-non-react-statics@^2.5.0: version "2.5.5" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47" @@ -3493,10 +3414,10 @@ homedir-polyfill@^1.0.1: parse-passwd "^1.0.0" hosted-git-info@^2.1.4: - version "2.5.0" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.5.0.tgz#6d60e34b3abbc8313062c3b798ef8d901a07af3c" + version "2.7.1" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.7.1.tgz#97f236977bd6e125408930ff6de3eec6281ec047" -html-encoding-sniffer@^1.0.1: +html-encoding-sniffer@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz#e70d84b94da53aa375e11fe3a351be6642ca46f8" dependencies: @@ -3523,14 +3444,6 @@ http-cache-semantics@3.8.1: version "3.8.1" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2" -http-signature@~1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" - dependencies: - assert-plus "^0.2.0" - jsprim "^1.2.2" - sshpk "^1.7.0" - http-signature@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" @@ -3553,17 +3466,25 @@ icalendar@0.7.1: version "0.7.1" resolved "https://registry.yarnpkg.com/icalendar/-/icalendar-0.7.1.tgz#d0d3486795f8f1c5cf4f8cafac081b4b4e7a32ae" -iconv-lite@0.4.19, iconv-lite@^0.4.19, iconv-lite@~0.4.13: - version "0.4.19" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" +iconv-lite@0.4.23, iconv-lite@^0.4.19, iconv-lite@^0.4.4, iconv-lite@~0.4.13: + version "0.4.23" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63" + dependencies: + safer-buffer ">= 2.1.2 < 3" ieee754@^1.1.4: - version "1.1.8" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4" + version "1.1.12" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b" + +ignore-walk@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.1.tgz#a83e62e7d272ac0e3b551aaa82831a19b69f82f8" + dependencies: + minimatch "^3.0.4" immutability-helper@^2.0.0: - version "2.6.4" - resolved "https://registry.yarnpkg.com/immutability-helper/-/immutability-helper-2.6.4.tgz#a931aef97257fcb6d2b5456de652ab6e3bba8408" + version "2.7.1" + resolved "https://registry.yarnpkg.com/immutability-helper/-/immutability-helper-2.7.1.tgz#5636dbb593e3deb5e572766d42249ea06bae7640" dependencies: invariant "^2.2.0" @@ -3657,13 +3578,7 @@ into-stream@^3.1.0: from2 "^2.1.1" p-is-promise "^1.1.0" -invariant@^2.0.0, invariant@^2.2.0, invariant@^2.2.1, invariant@^2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360" - dependencies: - loose-envify "^1.0.0" - -invariant@^2.1.1: +invariant@^2.0.0, invariant@^2.1.1, invariant@^2.2.0, invariant@^2.2.1, invariant@^2.2.2, invariant@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" dependencies: @@ -3719,14 +3634,14 @@ is-builtin-module@^1.0.0: builtin-modules "^1.0.0" is-callable@^1.1.1, is-callable@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.3.tgz#86eb75392805ddc33af71c92a0eedf74ee7604b2" + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75" is-ci@^1.0.10: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.1.0.tgz#247e4162e7860cebbdaf30b774d6b0ac7dcfe7a5" + version "1.2.0" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.2.0.tgz#3f4a08d6303a09882cef3f0fb97439c5f5ce2d53" dependencies: - ci-info "^1.0.0" + ci-info "^1.3.0" is-data-descriptor@^0.1.4: version "0.1.4" @@ -3752,7 +3667,7 @@ is-descriptor@^0.1.0: is-data-descriptor "^0.1.4" kind-of "^5.0.0" -is-descriptor@^1.0.0: +is-descriptor@^1.0.0, is-descriptor@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" dependencies: @@ -3820,20 +3735,6 @@ is-glob@^3.1.0: dependencies: is-extglob "^2.1.0" -is-my-ip-valid@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz#7b351b8e8edd4d3995d4d066680e664d94696824" - -is-my-json-valid@^2.12.4: - version "2.17.2" - resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.17.2.tgz#6b2103a288e94ef3de5cf15d29dd85fc4b78d65c" - dependencies: - generate-function "^2.0.0" - generate-object-property "^1.1.0" - is-my-ip-valid "^1.0.0" - jsonpointer "^4.0.0" - xtend "^4.0.0" - is-negated-glob@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-negated-glob/-/is-negated-glob-1.0.0.tgz#6910bca5da8c95e784b5751b976cf5a10fee36d2" @@ -3850,23 +3751,21 @@ is-number@^3.0.0: dependencies: kind-of "^3.0.2" +is-number@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff" + is-object@^1.0.1, is-object@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.1.tgz#8952688c5ec2ffd6b03ecc85e769e02903083470" -is-odd@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-odd/-/is-odd-1.0.0.tgz#3b8a932eb028b3775c39bb09e91767accdb69088" - dependencies: - is-number "^3.0.0" - is-path-cwd@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d" is-path-in-cwd@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz#6477582b8214d602346094567003be8a9eac04dc" + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz#5ac48b345ef675339bd6c7a48a912110b241cf52" dependencies: is-path-inside "^1.0.0" @@ -3898,10 +3797,6 @@ is-promise@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" -is-property@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" - is-regex@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491" @@ -3948,9 +3843,9 @@ is-valid-glob@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-valid-glob/-/is-valid-glob-1.0.0.tgz#29bf3eff701be2d4d315dbacc39bc39fe8f601aa" -is-windows@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.1.tgz#310db70f742d259a16a369202b51af84233310d9" +is-windows@^1.0.1, is-windows@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" isarray@0.0.1: version "0.0.1" @@ -3994,65 +3889,76 @@ isstream@~0.1.2: resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" istanbul-api@^1.1.14: - version "1.2.1" - resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-1.2.1.tgz#0c60a0515eb11c7d65c6b50bba2c6e999acd8620" + version "1.3.1" + resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-1.3.1.tgz#4c3b05d18c0016d1022e079b98dc82c40f488954" dependencies: async "^2.1.4" + compare-versions "^3.1.0" fileset "^2.0.2" - istanbul-lib-coverage "^1.1.1" - istanbul-lib-hook "^1.1.0" - istanbul-lib-instrument "^1.9.1" - istanbul-lib-report "^1.1.2" - istanbul-lib-source-maps "^1.2.2" - istanbul-reports "^1.1.3" + istanbul-lib-coverage "^1.2.0" + istanbul-lib-hook "^1.2.0" + istanbul-lib-instrument "^1.10.1" + istanbul-lib-report "^1.1.4" + istanbul-lib-source-maps "^1.2.4" + istanbul-reports "^1.3.0" js-yaml "^3.7.0" mkdirp "^0.5.1" once "^1.4.0" -istanbul-lib-coverage@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.1.1.tgz#73bfb998885299415c93d38a3e9adf784a77a9da" +istanbul-lib-coverage@^1.1.1, istanbul-lib-coverage@^1.1.2, istanbul-lib-coverage@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.0.tgz#f7d8f2e42b97e37fe796114cb0f9d68b5e3a4341" -istanbul-lib-hook@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.1.0.tgz#8538d970372cb3716d53e55523dd54b557a8d89b" +istanbul-lib-hook@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.2.1.tgz#f614ec45287b2a8fc4f07f5660af787575601805" dependencies: - append-transform "^0.4.0" + append-transform "^1.0.0" -istanbul-lib-instrument@^1.7.5, istanbul-lib-instrument@^1.8.0, istanbul-lib-instrument@^1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.9.1.tgz#250b30b3531e5d3251299fdd64b0b2c9db6b558e" +istanbul-lib-instrument@^1.10.1, istanbul-lib-instrument@^1.8.0: + version "1.10.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.1.tgz#724b4b6caceba8692d3f1f9d0727e279c401af7b" dependencies: babel-generator "^6.18.0" babel-template "^6.16.0" babel-traverse "^6.18.0" babel-types "^6.18.0" babylon "^6.18.0" - istanbul-lib-coverage "^1.1.1" + istanbul-lib-coverage "^1.2.0" semver "^5.3.0" -istanbul-lib-report@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-1.1.2.tgz#922be27c13b9511b979bd1587359f69798c1d425" +istanbul-lib-report@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-1.1.4.tgz#e886cdf505c4ebbd8e099e4396a90d0a28e2acb5" dependencies: - istanbul-lib-coverage "^1.1.1" + istanbul-lib-coverage "^1.2.0" mkdirp "^0.5.1" path-parse "^1.0.5" supports-color "^3.1.2" -istanbul-lib-source-maps@^1.2.1, istanbul-lib-source-maps@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.2.tgz#750578602435f28a0c04ee6d7d9e0f2960e62c1c" +istanbul-lib-source-maps@^1.2.1: + version "1.2.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.3.tgz#20fb54b14e14b3fb6edb6aca3571fd2143db44e6" dependencies: debug "^3.1.0" - istanbul-lib-coverage "^1.1.1" + istanbul-lib-coverage "^1.1.2" mkdirp "^0.5.1" rimraf "^2.6.1" source-map "^0.5.3" -istanbul-reports@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.1.3.tgz#3b9e1e8defb6d18b1d425da8e8b32c5a163f2d10" +istanbul-lib-source-maps@^1.2.4: + version "1.2.5" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.5.tgz#ffe6be4e7ab86d3603e4290d54990b14506fc9b1" + dependencies: + debug "^3.1.0" + istanbul-lib-coverage "^1.2.0" + mkdirp "^0.5.1" + rimraf "^2.6.1" + source-map "^0.5.3" + +istanbul-reports@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.3.0.tgz#2f322e81e1d9520767597dca3c20a0cce89a3554" dependencies: handlebars "^4.0.3" @@ -4078,15 +3984,15 @@ jclass@^1.0.1: version "1.2.1" resolved "https://registry.yarnpkg.com/jclass/-/jclass-1.2.1.tgz#eaafeec0dd6a5bf8b3ea43c04e010c637638768b" -jest-changed-files@^22.4.3: +jest-changed-files@^22.2.0: version "22.4.3" resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-22.4.3.tgz#8882181e022c38bd46a2e4d18d44d19d90a90fb2" dependencies: throat "^4.0.0" -jest-cli@^22.4.3: - version "22.4.3" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-22.4.3.tgz#bf16c4a5fb7edc3fa5b9bb7819e34139e88a72c7" +jest-cli@^22.4.3, jest-cli@^22.4.4: + version "22.4.4" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-22.4.4.tgz#68cd2a2aae983adb1e6638248ca21082fd6d9e90" dependencies: ansi-escapes "^3.0.0" chalk "^2.0.1" @@ -4099,20 +4005,20 @@ jest-cli@^22.4.3: istanbul-lib-coverage "^1.1.1" istanbul-lib-instrument "^1.8.0" istanbul-lib-source-maps "^1.2.1" - jest-changed-files "^22.4.3" - jest-config "^22.4.3" - jest-environment-jsdom "^22.4.3" - jest-get-type "^22.4.3" - jest-haste-map "^22.4.3" - jest-message-util "^22.4.3" - jest-regex-util "^22.4.3" - jest-resolve-dependencies "^22.4.3" - jest-runner "^22.4.3" - jest-runtime "^22.4.3" - jest-snapshot "^22.4.3" - jest-util "^22.4.3" - jest-validate "^22.4.3" - jest-worker "^22.4.3" + jest-changed-files "^22.2.0" + jest-config "^22.4.4" + jest-environment-jsdom "^22.4.1" + jest-get-type "^22.1.0" + jest-haste-map "^22.4.2" + jest-message-util "^22.4.0" + jest-regex-util "^22.1.0" + jest-resolve-dependencies "^22.1.0" + jest-runner "^22.4.4" + jest-runtime "^22.4.4" + jest-snapshot "^22.4.0" + jest-util "^22.4.1" + jest-validate "^22.4.4" + jest-worker "^22.2.2" micromatch "^2.3.11" node-notifier "^5.2.1" realpath-native "^1.0.0" @@ -4123,23 +4029,23 @@ jest-cli@^22.4.3: which "^1.2.12" yargs "^10.0.3" -jest-config@^22.4.3: - version "22.4.3" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-22.4.3.tgz#0e9d57db267839ea31309119b41dc2fa31b76403" +jest-config@^22.4.4: + version "22.4.4" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-22.4.4.tgz#72a521188720597169cd8b4ff86934ef5752d86a" dependencies: chalk "^2.0.1" glob "^7.1.1" - jest-environment-jsdom "^22.4.3" - jest-environment-node "^22.4.3" - jest-get-type "^22.4.3" - jest-jasmine2 "^22.4.3" - jest-regex-util "^22.4.3" - jest-resolve "^22.4.3" - jest-util "^22.4.3" - jest-validate "^22.4.3" - pretty-format "^22.4.3" - -jest-diff@^22.4.3: + jest-environment-jsdom "^22.4.1" + jest-environment-node "^22.4.1" + jest-get-type "^22.1.0" + jest-jasmine2 "^22.4.4" + jest-regex-util "^22.1.0" + jest-resolve "^22.4.2" + jest-util "^22.4.1" + jest-validate "^22.4.4" + pretty-format "^22.4.0" + +jest-diff@^22.4.0, jest-diff@^22.4.3: version "22.4.3" resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-22.4.3.tgz#e18cc3feff0aeef159d02310f2686d4065378030" dependencies: @@ -4148,13 +4054,13 @@ jest-diff@^22.4.3: jest-get-type "^22.4.3" pretty-format "^22.4.3" -jest-docblock@^22.4.3: +jest-docblock@^22.4.0, jest-docblock@^22.4.3: version "22.4.3" resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-22.4.3.tgz#50886f132b42b280c903c592373bb6e93bb68b19" dependencies: detect-newline "^2.1.0" -jest-environment-jsdom@^22.4.3: +jest-environment-jsdom@^22.4.1: version "22.4.3" resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-22.4.3.tgz#d67daa4155e33516aecdd35afd82d4abf0fa8a1e" dependencies: @@ -4162,18 +4068,18 @@ jest-environment-jsdom@^22.4.3: jest-util "^22.4.3" jsdom "^11.5.1" -jest-environment-node@^22.4.3: +jest-environment-node@^22.4.1: version "22.4.3" resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-22.4.3.tgz#54c4eaa374c83dd52a9da8759be14ebe1d0b9129" dependencies: jest-mock "^22.4.3" jest-util "^22.4.3" -jest-get-type@^22.4.3: +jest-get-type@^22.1.0, jest-get-type@^22.4.3: version "22.4.3" resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-22.4.3.tgz#e3a8504d8479342dd4420236b322869f18900ce4" -jest-haste-map@^22.4.3: +jest-haste-map@^22.4.2: version "22.4.3" resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-22.4.3.tgz#25842fa2ba350200767ac27f658d58b9d5c2e20b" dependencies: @@ -4185,29 +4091,29 @@ jest-haste-map@^22.4.3: micromatch "^2.3.11" sane "^2.0.0" -jest-jasmine2@^22.4.3: - version "22.4.3" - resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-22.4.3.tgz#4daf64cd14c793da9db34a7c7b8dcfe52a745965" +jest-jasmine2@^22.4.4: + version "22.4.4" + resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-22.4.4.tgz#c55f92c961a141f693f869f5f081a79a10d24e23" dependencies: chalk "^2.0.1" co "^4.6.0" - expect "^22.4.3" + expect "^22.4.0" graceful-fs "^4.1.11" is-generator-fn "^1.0.0" - jest-diff "^22.4.3" - jest-matcher-utils "^22.4.3" - jest-message-util "^22.4.3" - jest-snapshot "^22.4.3" - jest-util "^22.4.3" + jest-diff "^22.4.0" + jest-matcher-utils "^22.4.0" + jest-message-util "^22.4.0" + jest-snapshot "^22.4.0" + jest-util "^22.4.1" source-map-support "^0.5.0" -jest-leak-detector@^22.4.3: +jest-leak-detector@^22.4.0: version "22.4.3" resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-22.4.3.tgz#2b7b263103afae8c52b6b91241a2de40117e5b35" dependencies: pretty-format "^22.4.3" -jest-matcher-utils@^22.4.3: +jest-matcher-utils@^22.4.0, jest-matcher-utils@^22.4.3: version "22.4.3" resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-22.4.3.tgz#4632fe428ebc73ebc194d3c7b65d37b161f710ff" dependencies: @@ -4215,7 +4121,7 @@ jest-matcher-utils@^22.4.3: jest-get-type "^22.4.3" pretty-format "^22.4.3" -jest-message-util@^22.4.3: +jest-message-util@^22.4.0, jest-message-util@^22.4.3: version "22.4.3" resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-22.4.3.tgz#cf3d38aafe4befddbfc455e57d65d5239e399eb7" dependencies: @@ -4229,56 +4135,56 @@ jest-mock@^22.4.3: version "22.4.3" resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-22.4.3.tgz#f63ba2f07a1511772cdc7979733397df770aabc7" -jest-regex-util@^22.4.3: +jest-regex-util@^22.1.0, jest-regex-util@^22.4.3: version "22.4.3" resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-22.4.3.tgz#a826eb191cdf22502198c5401a1fc04de9cef5af" -jest-resolve-dependencies@^22.4.3: +jest-resolve-dependencies@^22.1.0: version "22.4.3" resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-22.4.3.tgz#e2256a5a846732dc3969cb72f3c9ad7725a8195e" dependencies: jest-regex-util "^22.4.3" -jest-resolve@^22.4.3: +jest-resolve@^22.4.2: version "22.4.3" resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-22.4.3.tgz#0ce9d438c8438229aa9b916968ec6b05c1abb4ea" dependencies: browser-resolve "^1.11.2" chalk "^2.0.1" -jest-runner@^22.4.3: - version "22.4.3" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-22.4.3.tgz#298ddd6a22b992c64401b4667702b325e50610c3" +jest-runner@^22.4.4: + version "22.4.4" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-22.4.4.tgz#dfca7b7553e0fa617e7b1291aeb7ce83e540a907" dependencies: exit "^0.1.2" - jest-config "^22.4.3" - jest-docblock "^22.4.3" - jest-haste-map "^22.4.3" - jest-jasmine2 "^22.4.3" - jest-leak-detector "^22.4.3" - jest-message-util "^22.4.3" - jest-runtime "^22.4.3" - jest-util "^22.4.3" - jest-worker "^22.4.3" + jest-config "^22.4.4" + jest-docblock "^22.4.0" + jest-haste-map "^22.4.2" + jest-jasmine2 "^22.4.4" + jest-leak-detector "^22.4.0" + jest-message-util "^22.4.0" + jest-runtime "^22.4.4" + jest-util "^22.4.1" + jest-worker "^22.2.2" throat "^4.0.0" -jest-runtime@^22.4.3: - version "22.4.3" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-22.4.3.tgz#b69926c34b851b920f666c93e86ba2912087e3d0" +jest-runtime@^22.4.4: + version "22.4.4" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-22.4.4.tgz#9ba7792fc75582a5be0f79af6f8fe8adea314048" dependencies: babel-core "^6.0.0" - babel-jest "^22.4.3" + babel-jest "^22.4.4" babel-plugin-istanbul "^4.1.5" chalk "^2.0.1" convert-source-map "^1.4.0" exit "^0.1.2" graceful-fs "^4.1.11" - jest-config "^22.4.3" - jest-haste-map "^22.4.3" - jest-regex-util "^22.4.3" - jest-resolve "^22.4.3" - jest-util "^22.4.3" - jest-validate "^22.4.3" + jest-config "^22.4.4" + jest-haste-map "^22.4.2" + jest-regex-util "^22.1.0" + jest-resolve "^22.4.2" + jest-util "^22.4.1" + jest-validate "^22.4.4" json-stable-stringify "^1.0.1" micromatch "^2.3.11" realpath-native "^1.0.0" @@ -4291,7 +4197,7 @@ jest-serializer@^22.4.3: version "22.4.3" resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-22.4.3.tgz#a679b81a7f111e4766235f4f0c46d230ee0f7436" -jest-snapshot@^22.4.3: +jest-snapshot@^22.4.0: version "22.4.3" resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-22.4.3.tgz#b5c9b42846ffb9faccb76b841315ba67887362d2" dependencies: @@ -4308,7 +4214,7 @@ jest-styled-components@^5.0.1: dependencies: css "^2.2.1" -jest-util@^22.4.3: +jest-util@^22.4.1, jest-util@^22.4.3: version "22.4.3" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-22.4.3.tgz#c70fec8eec487c37b10b0809dc064a7ecf6aafac" dependencies: @@ -4320,28 +4226,28 @@ jest-util@^22.4.3: mkdirp "^0.5.1" source-map "^0.6.0" -jest-validate@^22.4.3: - version "22.4.3" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-22.4.3.tgz#0780954a5a7daaeec8d3c10834b9280865976b30" +jest-validate@^22.4.4: + version "22.4.4" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-22.4.4.tgz#1dd0b616ef46c995de61810d85f57119dbbcec4d" dependencies: chalk "^2.0.1" - jest-config "^22.4.3" - jest-get-type "^22.4.3" + jest-config "^22.4.4" + jest-get-type "^22.1.0" leven "^2.1.0" - pretty-format "^22.4.3" + pretty-format "^22.4.0" -jest-worker@^22.4.3: +jest-worker@^22.2.2, jest-worker@^22.4.3: version "22.4.3" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-22.4.3.tgz#5c421417cba1c0abf64bf56bd5fb7968d79dd40b" dependencies: merge-stream "^1.0.1" jest@^22.4.3: - version "22.4.3" - resolved "https://registry.yarnpkg.com/jest/-/jest-22.4.3.tgz#2261f4b117dc46d9a4a1a673d2150958dee92f16" + version "22.4.4" + resolved "https://registry.yarnpkg.com/jest/-/jest-22.4.4.tgz#ffb36c9654b339a13e10b3d4b338eb3e9d49f6eb" dependencies: import-local "^1.0.0" - jest-cli "^22.4.3" + jest-cli "^22.4.4" joi@10.x.x: version "10.6.0" @@ -4385,8 +4291,8 @@ jquery@^3.3.1: resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.3.1.tgz#958ce29e81c9790f31be7792df5d4d95fc57fbca" js-base64@^2.1.8: - version "2.4.5" - resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.4.5.tgz#e293cd3c7c82f070d700fc7a1ca0a2e69f101f92" + version "2.4.8" + resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.4.8.tgz#57a9b130888f956834aa40c5b165ba59c758f033" js-tokens@^3.0.0, js-tokens@^3.0.2: version "3.0.2" @@ -4397,8 +4303,8 @@ js-tokens@^3.0.0, js-tokens@^3.0.2: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" js-yaml@^3.7.0: - version "3.10.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.10.0.tgz#2e78441646bd4682e963f22b6e92823c309c62dc" + version "3.12.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.0.tgz#eaed656ec8344f10f527c6bfa1b6e2244de167d1" dependencies: argparse "^1.0.7" esprima "^4.0.0" @@ -4408,33 +4314,35 @@ jsbn@~0.1.0: resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" jsdom@^11.5.1: - version "11.5.1" - resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-11.5.1.tgz#5df753b8d0bca20142ce21f4f6c039f99a992929" + version "11.12.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-11.12.0.tgz#1a80d40ddd378a1de59656e9e6dc5a3ba8657bc8" dependencies: - abab "^1.0.3" - acorn "^5.1.2" - acorn-globals "^4.0.0" + abab "^2.0.0" + acorn "^5.5.3" + acorn-globals "^4.1.0" array-equal "^1.0.0" - browser-process-hrtime "^0.1.2" - content-type-parser "^1.0.1" cssom ">= 0.3.2 < 0.4.0" - cssstyle ">= 0.2.37 < 0.3.0" - domexception "^1.0.0" - escodegen "^1.9.0" - html-encoding-sniffer "^1.0.1" - left-pad "^1.2.0" - nwmatcher "^1.4.3" - parse5 "^3.0.2" - pn "^1.0.0" - request "^2.83.0" - request-promise-native "^1.0.3" - sax "^1.2.1" - symbol-tree "^3.2.1" - tough-cookie "^2.3.3" + cssstyle "^1.0.0" + data-urls "^1.0.0" + domexception "^1.0.1" + escodegen "^1.9.1" + html-encoding-sniffer "^1.0.2" + left-pad "^1.3.0" + nwsapi "^2.0.7" + parse5 "4.0.0" + pn "^1.1.0" + request "^2.87.0" + request-promise-native "^1.0.5" + sax "^1.2.4" + symbol-tree "^3.2.2" + tough-cookie "^2.3.4" + w3c-hr-time "^1.0.1" webidl-conversions "^4.0.2" - whatwg-encoding "^1.0.1" - whatwg-url "^6.3.0" - xml-name-validator "^2.0.1" + whatwg-encoding "^1.0.3" + whatwg-mimetype "^2.1.0" + whatwg-url "^6.4.1" + ws "^5.2.0" + xml-name-validator "^3.0.0" jsesc@^1.3.0: version "1.3.0" @@ -4484,10 +4392,6 @@ jsonify@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" -jsonpointer@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" - jsonwebtoken@^8.3.0: version "8.3.0" resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.3.0.tgz#056c90eee9a65ed6e6c72ddb0a1d325109aaf643" @@ -4554,7 +4458,7 @@ kind-of@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-1.1.0.tgz#140a3d2d41a36d2efcfa9377b62c24f8495a5c44" -kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.1.0, kind-of@^3.2.0: +kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" dependencies: @@ -4566,7 +4470,7 @@ kind-of@^4.0.0: dependencies: is-buffer "^1.1.5" -kind-of@^5.0.0, kind-of@^5.0.2: +kind-of@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" @@ -4578,12 +4482,6 @@ lazy-cache@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" -lazy-cache@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-2.0.2.tgz#b9190a4f913354694840859f8a8f7084d8822264" - dependencies: - set-getter "^0.1.0" - lazystream@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.0.tgz#f6995fe0f820392f61396be89462407bb77168e4" @@ -4602,9 +4500,9 @@ lead@^1.0.0: dependencies: flush-write-stream "^1.0.2" -left-pad@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.2.0.tgz#d30a73c6b8201d8f7d8e7956ba9616087a68e0ee" +left-pad@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.3.0.tgz#5b8a3a7765dfe001261dde915589e782f8c94d1e" leven@^2.1.0: version "2.1.0" @@ -4655,11 +4553,7 @@ locate-path@^2.0.0: p-locate "^2.0.0" path-exists "^3.0.0" -lodash-es@^4.17.4: - version "4.17.4" - resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.4.tgz#dcc1d7552e150a0640073ba9cb31d70f032950e7" - -lodash-es@^4.17.5: +lodash-es@^4.17.4, lodash-es@^4.17.5: version "4.17.10" resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.10.tgz#62cd7104cdf5dd87f235a837f0ede0e8e5117e05" @@ -4928,18 +4822,10 @@ lodash@3.10.1, lodash@^3.10.1: version "3.10.1" resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" -lodash@^4.0.0, lodash@^4.17.5, lodash@~4.17.10: +lodash@^4.0.0, lodash@^4.0.1, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.3.0, lodash@~4.17.10: version "4.17.10" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7" -lodash@^4.0.1, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.4: - version "4.17.4" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" - -lodash@^4.3.0: - version "4.17.5" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511" - lodash@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/lodash/-/lodash-1.0.2.tgz#8f57560c83b59fc270bd3d561b690043430e2551" @@ -4948,21 +4834,15 @@ loglevel@^1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.1.tgz#e0fc95133b6ef276cdc8887cdaf24aa6f156f8fa" -lolex@^2.2.0, lolex@^2.3.2: - version "2.6.0" - resolved "https://registry.yarnpkg.com/lolex/-/lolex-2.6.0.tgz#cf9166f3c9dece3cdeb5d6b01fce50f14a1203e3" +lolex@^2.3.2, lolex@^2.4.2: + version "2.7.1" + resolved "https://registry.yarnpkg.com/lolex/-/lolex-2.7.1.tgz#e40a8c4d1f14b536aa03e42a537c7adbaf0c20be" longest@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" -loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848" - dependencies: - js-tokens "^3.0.0" - -loose-envify@^1.3.0: +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.0, loose-envify@^1.3.1: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" dependencies: @@ -4984,9 +4864,10 @@ lowercase-keys@^1.0.0: resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" lowlight@~1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/lowlight/-/lowlight-1.9.1.tgz#ed7c3dffc36f8c1f263735c0fe0c907847c11250" + version "1.9.2" + resolved "https://registry.yarnpkg.com/lowlight/-/lowlight-1.9.2.tgz#0b9127e3cec2c3021b7795dd81005c709a42fdd1" dependencies: + fault "^1.0.2" highlight.js "~9.12.0" lru-cache@2: @@ -4994,17 +4875,23 @@ lru-cache@2: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952" lru-cache@^4.0.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.1.tgz#622e32e82488b49279114a4f9ecf45e7cd6bba55" + version "4.1.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.3.tgz#a1175cf3496dfc8436c156c334b4955992bce69c" dependencies: pseudomap "^1.0.2" yallist "^2.1.2" +magic-string@^0.22.4: + version "0.22.5" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.22.5.tgz#8e9cf5afddf44385c1da5bc2a6a0dbd10b03657e" + dependencies: + vlq "^0.2.2" + make-iterator@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/make-iterator/-/make-iterator-1.0.0.tgz#57bef5dc85d23923ba23767324d8e8f8f3d9694b" + version "1.0.1" + resolved "https://registry.yarnpkg.com/make-iterator/-/make-iterator-1.0.1.tgz#29b33f312aa8f547c4a5e490f56afcec99133ad6" dependencies: - kind-of "^3.1.0" + kind-of "^6.0.2" makeerror@1.0.x: version "1.0.11" @@ -5027,8 +4914,12 @@ map-visit@^1.0.0: object-visit "^1.0.0" material-colors@^1.2.1: - version "1.2.5" - resolved "https://registry.yarnpkg.com/material-colors/-/material-colors-1.2.5.tgz#5292593e6754cb1bcc2b98030e4e0d6a3afc9ea1" + version "1.2.6" + resolved "https://registry.yarnpkg.com/material-colors/-/material-colors-1.2.6.tgz#6d1958871126992ceecc72f4bcc4d8f010865f46" + +math-random@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.1.tgz#8b3aac588b8a66e4975e3cdea67f7bb329601fac" mem@^1.1.0: version "1.1.0" @@ -5055,13 +4946,19 @@ merge-descriptors@~1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" +merge-source-map@1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/merge-source-map/-/merge-source-map-1.0.4.tgz#a5de46538dae84d4114cc5ea02b4772a6346701f" + dependencies: + source-map "^0.5.6" + merge-stream@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-1.0.1.tgz#4041202d508a342ba00174008df0c251b8c135e1" dependencies: readable-stream "^2.0.1" -merge@^1.1.3: +merge@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.0.tgz#7531e39d4949c281a66b8c5a6e0265e8b05894da" @@ -5069,7 +4966,7 @@ methods@^1.1.1, methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" -micromatch@^2.1.5, micromatch@^2.3.11: +micromatch@^2.3.11: version "2.3.11" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565" dependencies: @@ -5087,49 +4984,49 @@ micromatch@^2.1.5, micromatch@^2.3.11: parse-glob "^3.0.4" regex-cache "^0.4.2" -micromatch@^3.0.4: - version "3.1.5" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.5.tgz#d05e168c206472dfbca985bfef4f57797b4cd4ba" +micromatch@^3.0.4, micromatch@^3.1.4, micromatch@^3.1.8: + version "3.1.10" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" dependencies: arr-diff "^4.0.0" array-unique "^0.3.2" - braces "^2.3.0" - define-property "^1.0.0" - extend-shallow "^2.0.1" - extglob "^2.0.2" + braces "^2.3.1" + define-property "^2.0.2" + extend-shallow "^3.0.2" + extglob "^2.0.4" fragment-cache "^0.2.1" - kind-of "^6.0.0" - nanomatch "^1.2.5" + kind-of "^6.0.2" + nanomatch "^1.2.9" object.pick "^1.3.0" regex-not "^1.0.0" snapdragon "^0.8.1" - to-regex "^3.0.1" + to-regex "^3.0.2" mime-db@1.x.x: - version "1.32.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.32.0.tgz#485b3848b01a3cda5f968b4882c0771e58e09414" + version "1.36.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.36.0.tgz#5020478db3c7fe93aad7bbcc4dcf869c43363397" -mime-db@~1.30.0: - version "1.30.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01" +mime-db@~1.35.0: + version "1.35.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.35.0.tgz#0569d657466491283709663ad379a99b90d9ab47" -mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.7: - version "2.1.17" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.17.tgz#09d7a393f03e995a79f8af857b70a9e0ab16557a" +mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.19: + version "2.1.19" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.19.tgz#71e464537a7ef81c15f2db9d97e913fc0ff606f0" dependencies: - mime-db "~1.30.0" + mime-db "~1.35.0" mime@^1.4.1: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" mimic-fn@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18" + version "1.2.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" mimic-response@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.0.tgz#df3d3652a73fded6b9b0b24146e6fd052353458e" + version "1.0.1" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" mimos@3.x.x: version "3.0.3" @@ -5151,7 +5048,7 @@ minimatch@0.3: lru-cache "2" sigmund "~1.0.0" -"minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4, minimatch@~3.0.2: +"minimatch@2 || 3", minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4, minimatch@~3.0.2: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" dependencies: @@ -5182,9 +5079,22 @@ minimist@~0.0.1: version "0.0.10" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" +minipass@^2.2.1, minipass@^2.3.3: + version "2.3.4" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.4.tgz#4768d7605ed6194d6d576169b9e12ef71e9d9957" + dependencies: + safe-buffer "^5.1.2" + yallist "^3.0.0" + +minizlib@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.1.0.tgz#11e13658ce46bc3a70a267aac58359d1e0c29ceb" + dependencies: + minipass "^2.2.1" + mixin-deep@^1.2.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.0.tgz#47a8732ba97799457c8c1eca28f95132d7e8150a" + version "1.3.1" + resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.1.tgz#a49e7268dce1a0d9698e45326c5626df3543d0fe" dependencies: for-in "^1.0.2" is-extendable "^1.0.1" @@ -5237,8 +5147,8 @@ mocha@^2.0.1: to-iso-string "0.0.2" mock-fs@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/mock-fs/-/mock-fs-4.5.0.tgz#75245b966f7e3defe197b03454af9c5b355594b7" + version "4.6.0" + resolved "https://registry.yarnpkg.com/mock-fs/-/mock-fs-4.6.0.tgz#d944ef4c3e03ceb4e8332b4b31b8ac254051c8ae" module-not-found-error@^1.0.0: version "1.0.1" @@ -5249,14 +5159,18 @@ moment-duration-format@^1.3.0: resolved "https://registry.yarnpkg.com/moment-duration-format/-/moment-duration-format-1.3.0.tgz#541771b5f87a049cc65540475d3ad966737d6908" moment-timezone@^0.5.14: - version "0.5.14" - resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.14.tgz#4eb38ff9538b80108ba467a458f3ed4268ccfcb1" + version "0.5.21" + resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.21.tgz#3cba247d84492174dbf71de2a9848fa13207b845" dependencies: moment ">= 2.9.0" moment@2.x.x, "moment@>= 2.9.0", moment@^2.13.0, moment@^2.20.1: - version "2.20.1" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.20.1.tgz#d6eb1a46cbcc14a2b2f9434112c1ff8907f313fd" + version "2.22.2" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66" + +moo@^0.4.3: + version "0.4.3" + resolved "https://registry.yarnpkg.com/moo/-/moo-0.4.3.tgz#3f847a26f31cf625a956a87f2b10fbc013bfd10e" ms@0.7.1: version "0.7.1" @@ -5289,8 +5203,8 @@ multipipe@^0.1.2: duplexer2 "0.0.2" mustache@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/mustache/-/mustache-2.3.0.tgz#4028f7778b17708a489930a6e52ac3bca0da41d0" + version "2.3.2" + resolved "https://registry.yarnpkg.com/mustache/-/mustache-2.3.2.tgz#a6d4d9c3f91d13359ab889a812954f9230a3d0c5" mutation-observer@^1.0.3: version "1.0.3" @@ -5300,45 +5214,51 @@ mute-stream@0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.6.tgz#48962b19e169fd1dfc240b3f1e7317627bbc47db" -nan@^2.10.0: +nan@^2.10.0, nan@^2.9.2: version "2.10.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f" -nan@^2.3.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.8.0.tgz#ed715f3fe9de02b57a5e6252d90a96675e1f085a" - -nanomatch@^1.2.5: - version "1.2.7" - resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.7.tgz#53cd4aa109ff68b7f869591fdc9d10daeeea3e79" +nanomatch@^1.2.9: + version "1.2.13" + resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" dependencies: arr-diff "^4.0.0" array-unique "^0.3.2" - define-property "^1.0.0" - extend-shallow "^2.0.1" + define-property "^2.0.2" + extend-shallow "^3.0.2" fragment-cache "^0.2.1" - is-odd "^1.0.0" - kind-of "^5.0.2" + is-windows "^1.0.2" + kind-of "^6.0.2" object.pick "^1.3.0" regex-not "^1.0.0" snapdragon "^0.8.1" to-regex "^3.0.1" natives@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/natives/-/natives-1.1.1.tgz#011acce1f7cbd87f7ba6b3093d6cd9392be1c574" + version "1.1.4" + resolved "https://registry.yarnpkg.com/natives/-/natives-1.1.4.tgz#2f0f224fc9a7dd53407c7667c84cf8dbe773de58" natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" nearley@^2.7.10: - version "2.11.0" - resolved "https://registry.yarnpkg.com/nearley/-/nearley-2.11.0.tgz#5e626c79a6cd2f6ab9e7e5d5805e7668967757ae" + version "2.15.1" + resolved "https://registry.yarnpkg.com/nearley/-/nearley-2.15.1.tgz#965e4e6ec9ed6b80fc81453e161efbcebb36d247" dependencies: + moo "^0.4.3" nomnom "~1.6.2" railroad-diagrams "^1.0.0" - randexp "^0.4.2" + randexp "0.4.6" + semver "^5.4.1" + +needle@^2.2.1: + version "2.2.2" + resolved "https://registry.yarnpkg.com/needle/-/needle-2.2.2.tgz#1120ca4c41f2fcc6976fd28a8968afe239929418" + dependencies: + debug "^2.1.2" + iconv-lite "^0.4.4" + sax "^1.2.4" ngreact@^0.5.1: version "0.5.1" @@ -5355,9 +5275,9 @@ nigel@2.x.x: hoek "4.x.x" vise "2.x.x" -nise@^1.2.0: - version "1.3.3" - resolved "https://registry.yarnpkg.com/nise/-/nise-1.3.3.tgz#c17a850066a8a1dfeb37f921da02441afc4a82ba" +nise@^1.3.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/nise/-/nise-1.4.3.tgz#d1996e8d15256ceff1a0a1596e0c72bff370e37c" dependencies: "@sinonjs/formatio" "^2.0.0" just-extend "^1.1.27" @@ -5373,12 +5293,12 @@ node-fetch@^1.0.1, node-fetch@^1.3.3: is-stream "^1.0.1" node-fetch@^2.0.0, node-fetch@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.1.2.tgz#ab884e8e7e57e38a944753cec706f788d1768bb5" + version "2.2.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.2.0.tgz#4ee79bde909262f9775f731e3656d0db55ced5b5" -node-gyp@^3.3.1: - version "3.7.0" - resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.7.0.tgz#789478e8f6c45e277aa014f3e28f958f286f9203" +node-gyp@^3.8.0: + version "3.8.0" + resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.8.0.tgz#540304261c330e80d0d5edce253a68cb3964218c" dependencies: fstream "^1.0.0" glob "^7.0.3" @@ -5387,7 +5307,7 @@ node-gyp@^3.3.1: nopt "2 || 3" npmlog "0 || 1 || 2 || 3 || 4" osenv "0" - request ">=2.9.0 <2.82.0" + request "^2.87.0" rimraf "2" semver "~5.3.0" tar "^2.0.0" @@ -5406,25 +5326,24 @@ node-notifier@^5.2.1: shellwords "^0.1.1" which "^1.3.0" -node-pre-gyp@^0.6.39: - version "0.6.39" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.39.tgz#c00e96860b23c0e1420ac7befc5044e1d78d8649" +node-pre-gyp@^0.10.0: + version "0.10.3" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.10.3.tgz#3070040716afdc778747b61b6887bf78880b80fc" dependencies: detect-libc "^1.0.2" - hawk "3.1.3" mkdirp "^0.5.1" + needle "^2.2.1" nopt "^4.0.1" + npm-packlist "^1.1.6" npmlog "^4.0.2" - rc "^1.1.7" - request "2.81.0" + rc "^1.2.7" rimraf "^2.6.1" semver "^5.3.0" - tar "^2.2.1" - tar-pack "^3.4.0" + tar "^4" node-sass@^4.9.0: - version "4.9.0" - resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.9.0.tgz#d1b8aa855d98ed684d6848db929a20771cc2ae52" + version "4.9.3" + resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.9.3.tgz#f407cf3d66f78308bb1e346b24fa428703196224" dependencies: async-foreach "^0.1.3" chalk "^1.1.1" @@ -5439,16 +5358,16 @@ node-sass@^4.9.0: meow "^3.7.0" mkdirp "^0.5.1" nan "^2.10.0" - node-gyp "^3.3.1" + node-gyp "^3.8.0" npmlog "^4.0.0" - request "~2.79.0" + request "2.87.0" sass-graph "^2.2.4" stdout-stream "^1.4.0" "true-case-path" "^1.0.2" nodemailer@^4.6.4: - version "4.6.4" - resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-4.6.4.tgz#f0d72d0c6a6ec5f4369fa8f4bf5127a31baa2014" + version "4.6.8" + resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-4.6.8.tgz#f82fb407828bf2e76d92acc34b823d83e774f89c" nomnom@~1.6.2: version "1.6.2" @@ -5479,7 +5398,7 @@ normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: semver "2 || 3 || 4 || 5" validate-npm-package-license "^3.0.1" -normalize-path@^2.0.0, normalize-path@^2.0.1, normalize-path@^2.1.1: +normalize-path@^2.0.1, normalize-path@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" dependencies: @@ -5499,6 +5418,17 @@ now-and-later@^2.0.0: dependencies: once "^1.3.2" +npm-bundled@^1.0.1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.5.tgz#3c1732b7ba936b3a10325aef616467c0ccbcc979" + +npm-packlist@^1.1.6: + version "1.1.11" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.1.11.tgz#84e8c683cbe7867d34b1d357d893ce29e28a02de" + dependencies: + ignore-walk "^3.0.1" + npm-bundled "^1.0.1" + npm-run-path@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" @@ -5528,14 +5458,18 @@ numeral@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/numeral/-/numeral-2.0.6.tgz#4ad080936d443c2561aed9f2197efffe25f4e506" -nwmatcher@^1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/nwmatcher/-/nwmatcher-1.4.3.tgz#64348e3b3d80f035b40ac11563d278f8b72db89c" +nwsapi@^2.0.7: + version "2.0.8" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.0.8.tgz#e3603579b7e162b3dbedae4fb24e46f771d8fa24" -oauth-sign@~0.8.1, oauth-sign@~0.8.2: +oauth-sign@~0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" +oauth-sign@~0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" + object-assign@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2" @@ -5552,21 +5486,17 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" -object-inspect@~0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-0.4.0.tgz#f5157c116c1455b243b06ee97703392c5ad89fec" +object-inspect@~1.4.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.4.1.tgz#37ffb10e71adaf3748d05f713b4c9452f402cbc4" object-is@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.0.1.tgz#0aa60ec9989a0b3ed795cf4d06f62cf1ad6539b6" -object-keys@^1.0.11, object-keys@^1.0.6, object-keys@^1.0.8: - version "1.0.11" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d" - -object-keys@~0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-0.4.0.tgz#28a6aae7428dd2c3a92f3d95f21335dd204e0336" +object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.0.6: + version "1.0.12" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.12.tgz#09c53855377575310cca62f55bb334abff7b3ed2" object-visit@^1.0.0: version "1.0.1" @@ -5574,7 +5504,7 @@ object-visit@^1.0.0: dependencies: isobject "^3.0.0" -object.assign@^4.0.4: +object.assign@^4.0.4, object.assign@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" dependencies: @@ -5637,7 +5567,7 @@ object.values@^1.0.4: function-bind "^1.1.0" has "^1.0.1" -once@^1.3.0, once@^1.3.1, once@^1.3.2, once@^1.3.3, once@^1.4.0: +once@^1.3.0, once@^1.3.1, once@^1.3.2, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" dependencies: @@ -5721,20 +5651,13 @@ os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.1, os-tmpdir@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" -osenv@0: +osenv@0, osenv@^0.1.4: version "0.1.5" resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" dependencies: os-homedir "^1.0.0" os-tmpdir "^1.0.0" -osenv@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.4.tgz#42fe6d5953df06c8064be6f176c3d05aaaa34644" - dependencies: - os-homedir "^1.0.0" - os-tmpdir "^1.0.0" - p-cancelable@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.3.0.tgz#b9e123800bcebb7ac13a479be195b507b98d30fa" @@ -5743,10 +5666,6 @@ p-cancelable@^0.4.0: version "0.4.1" resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.4.1.tgz#35f363d67d52081c8d9585e37bcceb7e0bbcb2a0" -p-defer@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" - p-finally@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" @@ -5756,8 +5675,8 @@ p-is-promise@^1.1.0: resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-1.1.0.tgz#9c9456989e9f6588017b0434d56097675c3da05e" p-limit@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.2.0.tgz#0e92b6bedcb59f022c13d0f1949dc82d15909f1c" + version "1.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" dependencies: p-try "^1.0.0" @@ -5775,11 +5694,11 @@ p-queue@^2.3.0: version "2.4.2" resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-2.4.2.tgz#03609826682b743be9a22dba25051bd46724fc34" -p-retry@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-1.0.0.tgz#3927332a4b7d70269b535515117fc547da1a6968" +p-retry@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-2.0.0.tgz#b97f1f4d6d81a3c065b2b40107b811e995c1bfba" dependencies: - retry "^0.10.0" + retry "^0.12.0" p-timeout@^2.0.1: version "2.0.1" @@ -5822,7 +5741,11 @@ parse-passwd@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" -parse5@^3.0.1, parse5@^3.0.2: +parse5@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-4.0.0.tgz#6d78656e3da8d78b4ec0b906f7c08ef1dfe3f608" + +parse5@^3.0.1: version "3.0.3" resolved "https://registry.yarnpkg.com/parse5/-/parse5-3.0.3.tgz#042f792ffdd36851551cf4e9e066b3874ab45b5c" dependencies: @@ -5859,8 +5782,8 @@ path-key@^2.0.0, path-key@^2.0.1: resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" path-parse@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" + version "1.0.6" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" path-root-regex@^0.1.0: version "0.1.2" @@ -6016,7 +5939,7 @@ pluralize@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-3.1.0.tgz#84213d0a12356069daa84060c559242633161368" -pn@^1.0.0: +pn@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb" @@ -6024,17 +5947,21 @@ png-js@>=0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/png-js/-/png-js-0.1.1.tgz#1cc7c212303acabe74263ec3ac78009580242d93" -pngjs@3.3.1, pngjs@^3.0.0: +pngjs@3.3.1: version "3.3.1" resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.3.1.tgz#8e14e6679ee7424b544334c3b2d21cea6d8c209a" -polished@^1.9.2: - version "1.9.2" - resolved "https://registry.yarnpkg.com/polished/-/polished-1.9.2.tgz#d705cac66f3a3ed1bd38aad863e2c1e269baf6b6" +pngjs@^3.0.0: + version "3.3.3" + resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.3.3.tgz#85173703bde3edac8998757b96e5821d0966a21b" + +polished@^1.9.2: + version "1.9.3" + resolved "https://registry.yarnpkg.com/polished/-/polished-1.9.3.tgz#d61b8a0c4624efe31e2583ff24a358932b6b75e1" popper.js@^1.14.1: - version "1.14.3" - resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.14.3.tgz#1438f98d046acf7b4d78cd502bf418ac64d4f095" + version "1.14.4" + resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.14.4.tgz#8eec1d8ff02a5a3a152dd43414a15c7b79fd69b6" posix-character-classes@^0.1.0: version "0.1.1" @@ -6056,7 +5983,7 @@ preserve@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" -pretty-format@^22.4.3: +pretty-format@^22.4.0, pretty-format@^22.4.3: version "22.4.3" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-22.4.3.tgz#f873d780839a9c02e9664c8a082e9ee79eaac16f" dependencies: @@ -6071,7 +5998,7 @@ prismjs@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-0.0.1.tgz#0fd50f4baf26e5cd33523b65bac2f0bc90f5503f" -private@^0.1.6, private@^0.1.7: +private@^0.1.6, private@^0.1.8: version "0.1.8" resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" @@ -6099,19 +6026,10 @@ prop-types@15.5.8: dependencies: fbjs "^0.8.9" -prop-types@^15.5.0, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.8, prop-types@^15.6.0: - version "15.6.0" - resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.0.tgz#ceaf083022fc46b4a35f69e13ef75aed0d639856" - dependencies: - fbjs "^0.8.16" - loose-envify "^1.3.1" - object-assign "^4.1.1" - -prop-types@^15.6.1: - version "15.6.1" - resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.1.tgz#36644453564255ddda391191fb3a125cbdf654ca" +prop-types@^15.5.0, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2: + version "15.6.2" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102" dependencies: - fbjs "^0.8.16" loose-envify "^1.3.1" object-assign "^4.1.1" @@ -6127,6 +6045,10 @@ pseudomap@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" +psl@^1.1.24: + version "1.1.29" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.29.tgz#60f580d360170bb722a797cc704411e6da850c67" + pui-cursor@^3.0.4: version "3.0.5" resolved "https://registry.yarnpkg.com/pui-cursor/-/pui-cursor-3.0.5.tgz#e80805f27edfc4e7b8c54d2755180cd087729bb5" @@ -6166,10 +6088,10 @@ pump@^2.0.0: once "^1.3.1" pumpify@^1.3.5: - version "1.4.0" - resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.4.0.tgz#80b7c5df7e24153d03f0e7ac8a05a5d068bd07fb" + version "1.5.1" + resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce" dependencies: - duplexify "^3.5.3" + duplexify "^3.6.0" inherits "^2.0.3" pump "^2.0.0" @@ -6178,20 +6100,12 @@ punycode@^1.4.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" punycode@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.0.tgz#5f863edc89b96db09074bad7947bf09056ca4e7d" - -qs@^6.5.1, qs@~6.5.1: - version "6.5.1" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" - -qs@~6.3.0: - version "6.3.2" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.2.tgz#e75bd5f6e268122a2a0e0bda630b2550c166502c" + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" -qs@~6.4.0: - version "6.4.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" +qs@^6.5.1, qs@~6.5.1, qs@~6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" query-string@^5.0.1: version "5.1.1" @@ -6201,7 +6115,7 @@ query-string@^5.0.1: object-assign "^4.1.0" strict-uri-encode "^1.0.0" -quote-stream@^1.0.1: +quote-stream@^1.0.1, quote-stream@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/quote-stream/-/quote-stream-1.0.2.tgz#84963f8c9c26b942e153feeb53aae74652b7e0b2" dependencies: @@ -6209,13 +6123,6 @@ quote-stream@^1.0.1: minimist "^1.1.3" through2 "^2.0.0" -quote-stream@~0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/quote-stream/-/quote-stream-0.0.0.tgz#cde29e94c409b16e19dc7098b89b6658f9721d3b" - dependencies: - minimist "0.0.8" - through2 "~0.4.1" - raf@^3.0.0, raf@^3.1.0, raf@^3.3.0, raf@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.0.tgz#a28876881b4bc2ca9117d4138163ddb80f781575" @@ -6226,30 +6133,31 @@ railroad-diagrams@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz#eb7e6267548ddedfb899c1b90e57374559cddb7e" -randexp@^0.4.2: +randexp@0.4.6: version "0.4.6" resolved "https://registry.yarnpkg.com/randexp/-/randexp-0.4.6.tgz#e986ad5e5e31dae13ddd6f7b3019aa7c87f60ca3" dependencies: discontinuous-range "1.0.0" ret "~0.1.10" -randomatic@^1.1.3: - version "1.1.7" - resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.7.tgz#c7abe9cc8b87c0baa876b19fde83fd464797e38c" +randomatic@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-3.1.0.tgz#36f2ca708e9e567f5ed2ec01949026d50aa10116" dependencies: - is-number "^3.0.0" - kind-of "^4.0.0" + is-number "^4.0.0" + kind-of "^6.0.0" + math-random "^1.0.1" -rc@^1.1.7: - version "1.2.3" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.3.tgz#51575a900f8dd68381c710b4712c2154c3e2035b" +rc@^1.2.7: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" dependencies: - deep-extend "~0.4.0" + deep-extend "^0.6.0" ini "~1.3.0" minimist "^1.2.0" strip-json-comments "~2.0.1" -react-ace@^5.5.0: +react-ace@^5.5.0, react-ace@^5.9.0: version "5.10.0" resolved "https://registry.yarnpkg.com/react-ace/-/react-ace-5.10.0.tgz#e328b37ac52759f700be5afdb86ada2f5ec84c5e" dependencies: @@ -6258,15 +6166,6 @@ react-ace@^5.5.0: lodash.isequal "^4.1.1" prop-types "^15.5.8" -react-ace@^5.9.0: - version "5.9.0" - resolved "https://registry.yarnpkg.com/react-ace/-/react-ace-5.9.0.tgz#427a1cc4869b960a6f9748aa7eb169a9269fc336" - dependencies: - brace "^0.11.0" - lodash.get "^4.4.2" - lodash.isequal "^4.1.1" - prop-types "^15.5.8" - react-addons-shallow-compare@^15.0.1: version "15.6.2" resolved "https://registry.yarnpkg.com/react-addons-shallow-compare/-/react-addons-shallow-compare-15.6.2.tgz#198a00b91fc37623db64a28fd17b596ba362702f" @@ -6275,15 +6174,15 @@ react-addons-shallow-compare@^15.0.1: object-assign "^4.1.0" react-clipboard.js@^1.1.2: - version "1.1.3" - resolved "https://registry.yarnpkg.com/react-clipboard.js/-/react-clipboard.js-1.1.3.tgz#86feeb49364553ecd15aea91c75aa142532a60e0" + version "1.1.4" + resolved "https://registry.yarnpkg.com/react-clipboard.js/-/react-clipboard.js-1.1.4.tgz#d284ba6666fc2d7ae873533e4db111bf8394b12d" dependencies: clipboard "^1.6.1" prop-types "^15.5.0" react-color@^2.13.8: - version "2.13.8" - resolved "https://registry.yarnpkg.com/react-color/-/react-color-2.13.8.tgz#bcc58f79a722b9bfc37c402e68cd18f26970aee4" + version "2.14.1" + resolved "https://registry.yarnpkg.com/react-color/-/react-color-2.14.1.tgz#db8ad4f45d81e74896fc2e1c99508927c6d084e0" dependencies: lodash "^4.0.1" material-colors "^1.2.1" @@ -6300,18 +6199,9 @@ react-datepicker@v1.5.0: react-onclickoutside "^6.7.1" react-popper "^0.9.1" -react-dom@^16.0.0: - version "16.2.0" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.2.0.tgz#69003178601c0ca19b709b33a83369fe6124c044" - dependencies: - fbjs "^0.8.16" - loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.0" - -react-dom@^16.3.0: - version "16.3.2" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.3.2.tgz#cb90f107e09536d683d84ed5d4888e9640e0e4df" +react-dom@^16.0.0, react-dom@^16.3.0: + version "16.4.2" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.4.2.tgz#4afed569689f2c561d2b8da0b819669c38a0bda4" dependencies: fbjs "^0.8.16" loose-envify "^1.1.0" @@ -6333,9 +6223,9 @@ react-intl@^2.4.0: intl-relativeformat "^2.0.0" invariant "^2.1.1" -react-is@^16.3.1: - version "16.4.1" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.4.1.tgz#d624c4650d2c65dbd52c72622bbf389435d9776e" +react-is@^16.3.1, react-is@^16.4.2: + version "16.4.2" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.4.2.tgz#84891b56c2b6d9efdee577cc83501dfc5ecead88" react-lifecycles-compat@^3.0.4: version "3.0.4" @@ -6348,15 +6238,6 @@ react-markdown-renderer@^1.4.0: prop-types "^15.5.10" remarkable "^1.7.1" -react-motion@^0.4.8: - version "0.4.8" - resolved "https://registry.yarnpkg.com/react-motion/-/react-motion-0.4.8.tgz#23bb2dd27c2d8e00d229e45572d105efcf40a35e" - dependencies: - create-react-class "^15.5.2" - performance-now "^0.2.0" - prop-types "^15.5.8" - raf "^3.1.0" - react-motion@^0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/react-motion/-/react-motion-0.5.2.tgz#0dd3a69e411316567927917c6626551ba0607316" @@ -6415,39 +6296,39 @@ react-router-breadcrumbs-hoc@1.1.2: resolved "https://registry.yarnpkg.com/react-router-breadcrumbs-hoc/-/react-router-breadcrumbs-hoc-1.1.2.tgz#4fafb620e7c6b876d98f7151f4c85ae5c3157dc0" react-router-dom@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-4.2.2.tgz#c8a81df3adc58bba8a76782e946cbd4eae649b8d" + version "4.3.1" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-4.3.1.tgz#4c2619fc24c4fa87c9fd18f4fb4a43fe63fbd5c6" dependencies: history "^4.7.2" - invariant "^2.2.2" + invariant "^2.2.4" loose-envify "^1.3.1" - prop-types "^15.5.4" - react-router "^4.2.0" - warning "^3.0.0" + prop-types "^15.6.1" + react-router "^4.3.1" + warning "^4.0.1" -react-router@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-4.2.0.tgz#61f7b3e3770daeb24062dae3eedef1b054155986" +react-router@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-4.3.1.tgz#aada4aef14c809cb2e686b05cee4742234506c4e" dependencies: history "^4.7.2" - hoist-non-react-statics "^2.3.0" - invariant "^2.2.2" + hoist-non-react-statics "^2.5.0" + invariant "^2.2.4" loose-envify "^1.3.1" path-to-regexp "^1.7.0" - prop-types "^15.5.4" - warning "^3.0.0" + prop-types "^15.6.1" + warning "^4.0.1" react-select@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/react-select/-/react-select-1.2.1.tgz#a2fe58a569eb14dcaa6543816260b97e538120d1" + version "1.3.0" + resolved "https://registry.yarnpkg.com/react-select/-/react-select-1.3.0.tgz#1828ad5bf7f3e42a835c7e2d8cb13b5c20714876" dependencies: classnames "^2.2.4" prop-types "^15.5.8" react-input-autosize "^2.1.2" react-sticky@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/react-sticky/-/react-sticky-6.0.1.tgz#356988bdcc6fc8cd2d89746d2302edce67d86687" + version "6.0.3" + resolved "https://registry.yarnpkg.com/react-sticky/-/react-sticky-6.0.3.tgz#7a18b643e1863da113d7f7036118d2a75d9ecde4" dependencies: prop-types "^15.5.8" raf "^3.3.0" @@ -6461,12 +6342,13 @@ react-syntax-highlighter@^5.7.0: lowlight "~1.9.1" react-test-renderer@^16.0.0-0, react-test-renderer@^16.2.0: - version "16.2.0" - resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.2.0.tgz#bddf259a6b8fcd8555f012afc8eacc238872a211" + version "16.4.2" + resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.4.2.tgz#4e03eca9359bb3210d4373f7547d1364218ef74e" dependencies: fbjs "^0.8.16" object-assign "^4.1.1" prop-types "^15.6.0" + react-is "^16.4.2" react-virtualized@^9.18.5: version "9.20.1" @@ -6502,8 +6384,8 @@ react-vis@1.10.2: react-motion "^0.5.2" react-vis@^1.8.1: - version "1.8.2" - resolved "https://registry.yarnpkg.com/react-vis/-/react-vis-1.8.2.tgz#0e0aebc427e50856a01b666569ffad0411ef050f" + version "1.10.5" + resolved "https://registry.yarnpkg.com/react-vis/-/react-vis-1.10.5.tgz#f04a78bc66f44912a586d9150db57da41ad994e0" dependencies: d3-array "^1.2.0" d3-collection "^1.0.3" @@ -6519,21 +6401,13 @@ react-vis@^1.8.1: d3-voronoi "^1.1.2" deep-equal "^1.0.1" global "^4.3.1" + hoek "4.2.1" prop-types "^15.5.8" - react-motion "^0.4.8" - -react@>=0.13.3, react@^16.2.0: - version "16.2.0" - resolved "https://registry.yarnpkg.com/react/-/react-16.2.0.tgz#a31bd2dab89bff65d42134fa187f24d054c273ba" - dependencies: - fbjs "^0.8.16" - loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.0" + react-motion "^0.5.2" -react@^16.3.0: - version "16.3.2" - resolved "https://registry.yarnpkg.com/react/-/react-16.3.2.tgz#fdc8420398533a1e58872f59091b272ce2f91ea9" +react@>=0.13.3, react@^16.2.0, react@^16.3.0: + version "16.4.2" + resolved "https://registry.yarnpkg.com/react/-/react-16.4.2.tgz#2cd90154e3a9d9dd8da2991149fdca3c260e129f" dependencies: fbjs "^0.8.16" loose-envify "^1.1.0" @@ -6561,7 +6435,7 @@ read-pkg@^1.0.0: normalize-package-data "^2.3.2" path-type "^1.0.0" -"readable-stream@>=1.0.33-1 <1.1.0-0", readable-stream@~1.0.17, readable-stream@~1.0.27-1: +"readable-stream@>=1.0.33-1 <1.1.0-0": version "1.0.34" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" dependencies: @@ -6570,28 +6444,16 @@ read-pkg@^1.0.0: isarray "0.0.1" string_decoder "~0.10.x" -readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.1.5, readable-stream@^2.2.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c" - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~1.0.6" - safe-buffer "~5.1.1" - string_decoder "~1.0.3" - util-deprecate "~1.0.1" - -readable-stream@^2.3.3, readable-stream@^2.3.5: - version "2.3.5" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.5.tgz#b4f85003a938cbb6ecbce2a124fb1012bd1a838d" +readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.0, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@~2.3.3: + version "2.3.6" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" dependencies: core-util-is "~1.0.0" inherits "~2.0.3" isarray "~1.0.0" process-nextick-args "~2.0.0" safe-buffer "~5.1.1" - string_decoder "~1.0.3" + string_decoder "~1.1.1" util-deprecate "~1.0.1" readable-stream@~1.1.9: @@ -6615,8 +6477,8 @@ readable-stream@~2.0.0: util-deprecate "~1.0.1" realpath-native@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-1.0.0.tgz#7885721a83b43bd5327609f0ddecb2482305fdf0" + version "1.0.1" + resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-1.0.1.tgz#07f40a0cce8f8261e2e8b7ebebf5c95965d7b633" dependencies: util.promisify "^1.0.0" @@ -6634,8 +6496,8 @@ redent@^1.0.0: strip-indent "^1.0.1" reduce-reducers@^0.1.0: - version "0.1.2" - resolved "https://registry.yarnpkg.com/reduce-reducers/-/reduce-reducers-0.1.2.tgz#fa1b4718bc5292a71ddd1e5d839c9bea9770f14b" + version "0.1.5" + resolved "https://registry.yarnpkg.com/reduce-reducers/-/reduce-reducers-0.1.5.tgz#ff77ca8068ff41007319b8b4b91533c7e0e54576" redux-actions@2.2.1: version "2.2.1" @@ -6662,8 +6524,8 @@ redux@4.0.0: symbol-observable "^1.2.0" regenerate@^1.2.1: - version "1.3.3" - resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.3.tgz#0c336d3980553d755c39b586ae3b20aa49c82b7f" + version "1.4.0" + resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11" regenerator-runtime@^0.11.0: version "0.11.1" @@ -6683,11 +6545,12 @@ regex-cache@^0.4.2: dependencies: is-equal-shallow "^0.1.3" -regex-not@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.0.tgz#42f83e39771622df826b02af176525d6a5f157f9" +regex-not@^1.0.0, regex-not@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" dependencies: - extend-shallow "^2.0.1" + extend-shallow "^3.0.2" + safe-regex "^1.1.0" regexpu-core@^2.0.0: version "2.0.0" @@ -6734,8 +6597,8 @@ remove-trailing-separator@^1.0.1: resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" repeat-element@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.2.tgz#ef089a178d1483baae4d93eb98b4f9e4e11d990a" + version "1.1.3" + resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" repeat-string@^1.5.2, repeat-string@^1.6.1: version "1.6.1" @@ -6761,7 +6624,7 @@ request-promise-core@1.1.1: dependencies: lodash "^4.13.1" -request-promise-native@^1.0.3: +request-promise-native@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.5.tgz#5281770f68e0c9719e5163fd3fab482215f4fda5" dependencies: @@ -6769,36 +6632,9 @@ request-promise-native@^1.0.3: stealthy-require "^1.1.0" tough-cookie ">=2.3.3" -request@2.81.0, "request@>=2.9.0 <2.82.0": - version "2.81.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" - dependencies: - aws-sign2 "~0.6.0" - aws4 "^1.2.1" - caseless "~0.12.0" - combined-stream "~1.0.5" - extend "~3.0.0" - forever-agent "~0.6.1" - form-data "~2.1.1" - har-validator "~4.2.1" - hawk "~3.1.3" - http-signature "~1.1.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.7" - oauth-sign "~0.8.1" - performance-now "^0.2.0" - qs "~6.4.0" - safe-buffer "^5.0.1" - stringstream "~0.0.4" - tough-cookie "~2.3.0" - tunnel-agent "^0.6.0" - uuid "^3.0.0" - -request@^2.83.0: - version "2.83.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.83.0.tgz#ca0b65da02ed62935887808e6f510381034e3356" +request@2.87.0: + version "2.87.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.87.0.tgz#32f00235cd08d482b4d0d68db93a829c0ed5756e" dependencies: aws-sign2 "~0.7.0" aws4 "^1.6.0" @@ -6808,7 +6644,6 @@ request@^2.83.0: forever-agent "~0.6.1" form-data "~2.3.1" har-validator "~5.0.3" - hawk "~6.0.2" http-signature "~1.2.0" is-typedarray "~1.0.0" isstream "~0.1.2" @@ -6818,62 +6653,34 @@ request@^2.83.0: performance-now "^2.1.0" qs "~6.5.1" safe-buffer "^5.1.1" - stringstream "~0.0.5" tough-cookie "~2.3.3" tunnel-agent "^0.6.0" uuid "^3.1.0" -request@^2.85.0: - version "2.85.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.85.0.tgz#5a03615a47c61420b3eb99b7dba204f83603e1fa" +request@^2.85.0, request@^2.87.0: + version "2.88.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" dependencies: aws-sign2 "~0.7.0" - aws4 "^1.6.0" + aws4 "^1.8.0" caseless "~0.12.0" - combined-stream "~1.0.5" - extend "~3.0.1" + combined-stream "~1.0.6" + extend "~3.0.2" forever-agent "~0.6.1" - form-data "~2.3.1" - har-validator "~5.0.3" - hawk "~6.0.2" + form-data "~2.3.2" + har-validator "~5.1.0" http-signature "~1.2.0" is-typedarray "~1.0.0" isstream "~0.1.2" json-stringify-safe "~5.0.1" - mime-types "~2.1.17" - oauth-sign "~0.8.2" + mime-types "~2.1.19" + oauth-sign "~0.9.0" performance-now "^2.1.0" - qs "~6.5.1" - safe-buffer "^5.1.1" - stringstream "~0.0.5" - tough-cookie "~2.3.3" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.4.3" tunnel-agent "^0.6.0" - uuid "^3.1.0" - -request@~2.79.0: - version "2.79.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de" - dependencies: - aws-sign2 "~0.6.0" - aws4 "^1.2.1" - caseless "~0.11.0" - combined-stream "~1.0.5" - extend "~3.0.0" - forever-agent "~0.6.1" - form-data "~2.1.1" - har-validator "~2.0.6" - hawk "~3.1.3" - http-signature "~1.1.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.7" - oauth-sign "~0.8.1" - qs "~6.3.0" - stringstream "~0.0.4" - tough-cookie "~2.3.0" - tunnel-agent "~0.4.1" - uuid "^3.0.0" + uuid "^3.3.2" require-directory@^2.1.1: version "2.1.1" @@ -6922,7 +6729,7 @@ resolve-pathname@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-2.2.0.tgz#7e9ae21ed815fd63ab189adeee64dc831eefa879" -resolve-url@^0.2.1, resolve-url@~0.2.1: +resolve-url@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" @@ -6931,8 +6738,8 @@ resolve@1.1.7, resolve@~1.1.7: resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" resolve@^1.1.5, resolve@^1.1.6, resolve@^1.1.7: - version "1.5.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.5.0.tgz#1f09acce796c9a762579f31b2c1cc4c3cddf9f36" + version "1.8.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.8.1.tgz#82f1ec19a423ac1fbd080b0bab06ba36e84a7a26" dependencies: path-parse "^1.0.5" @@ -6959,9 +6766,9 @@ ret@~0.1.10: version "0.1.15" resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" -retry@^0.10.0, retry@^0.10.1: - version "0.10.1" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.10.1.tgz#e76388d217992c252750241d3d3956fed98d8ff4" +retry@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" right-align@^0.1.1: version "0.1.3" @@ -6969,7 +6776,7 @@ right-align@^0.1.1: dependencies: align-text "^0.1.1" -rimraf@2, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2: +rimraf@2, rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" dependencies: @@ -6990,6 +6797,10 @@ rst-selector-parser@^2.2.3: lodash.flattendeep "^4.4.0" nearley "^2.7.10" +rsvp@^3.3.3: + version "3.6.2" + resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-3.6.2.tgz#2e96491599a96cde1b515d5674a8f7a91452926a" + rsync@0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/rsync/-/rsync-0.4.0.tgz#0274f3163007a94396d88b1cdeb4bbde92cb9dc1" @@ -7013,32 +6824,43 @@ rx@^4.1.0: resolved "https://registry.yarnpkg.com/rx/-/rx-4.1.0.tgz#a5f13ff79ef3b740fe30aa803fb09f98805d4782" rxjs@^6.2.1: - version "6.2.1" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.2.1.tgz#246cebec189a6cbc143a3ef9f62d6f4c91813ca1" + version "6.2.2" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.2.2.tgz#eb75fa3c186ff5289907d06483a77884586e1cf9" dependencies: tslib "^1.9.0" -safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" +safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + +safe-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" + dependencies: + ret "~0.1.10" + +"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" samsam@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.3.0.tgz#8d1d9350e25622da30de3e44ba692b5221ab7c50" sane@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/sane/-/sane-2.2.0.tgz#d6d2e2fcab00e3d283c93b912b7c3a20846f1d56" + version "2.5.2" + resolved "https://registry.yarnpkg.com/sane/-/sane-2.5.2.tgz#b4dc1861c21b427e929507a3e751e2a2cb8ab3fa" dependencies: - anymatch "^1.3.0" + anymatch "^2.0.0" + capture-exit "^1.2.0" exec-sh "^0.2.0" fb-watchman "^2.0.0" - minimatch "^3.0.2" + micromatch "^3.1.4" minimist "^1.1.1" walker "~1.0.5" watch "~0.18.0" optionalDependencies: - fsevents "^1.1.1" + fsevents "^1.2.3" sass-graph@^2.2.4: version "2.2.4" @@ -7057,13 +6879,13 @@ sax@0.5.x: version "0.5.8" resolved "https://registry.yarnpkg.com/sax/-/sax-0.5.8.tgz#d472db228eb331c2506b0e8c15524adb939d12c1" -sax@>=0.6.0, sax@^1.2.1: +sax@>=0.6.0, sax@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" scroll-into-view@^1.3.0: - version "1.9.1" - resolved "https://registry.yarnpkg.com/scroll-into-view/-/scroll-into-view-1.9.1.tgz#90c3b338422f9fddaebad90e6954790940dc9c1e" + version "1.9.3" + resolved "https://registry.yarnpkg.com/scroll-into-view/-/scroll-into-view-1.9.3.tgz#e5977ded33de31024508f3444c959dfa3711f2f8" scss-tokenizer@^0.2.3: version "0.2.3" @@ -7076,9 +6898,9 @@ select@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d" -"semver@2 || 3 || 4 || 5", semver@^5.3.0: - version "5.4.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" +"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5.0: + version "5.5.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.1.tgz#7dfdd8814bdb7cabc7be0fb1d734cfb66c940477" semver@5.1.0: version "5.1.0" @@ -7088,10 +6910,6 @@ semver@^4.1.0: version "4.3.6" resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.6.tgz#300bc6e0e86374f7ba61068b5b1ecd57fc6532da" -semver@^5.4.1, semver@^5.5.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" - semver@~5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" @@ -7108,12 +6926,6 @@ set-blocking@^2.0.0, set-blocking@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" -set-getter@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/set-getter/-/set-getter-0.1.0.tgz#d769c182c9d5a51f409145f2fba82e5e86e80376" - dependencies: - to-object-path "^0.3.0" - set-value@^0.4.3: version "0.4.3" resolved "https://registry.yarnpkg.com/set-value/-/set-value-0.4.3.tgz#7db08f9d3d22dc7f78e53af3c3bf4666ecdfccf1" @@ -7174,22 +6986,22 @@ simple-git@1.37.0: resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-1.37.0.tgz#a5d522dd4e97c6091f657766c28a323738233f0f" simple-git@^1.91.0: - version "1.92.0" - resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-1.92.0.tgz#6061468eb7d19f0141078fc742e62457e910f547" + version "1.96.0" + resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-1.96.0.tgz#d0279b771ed89a2d1e7d9e15f9abd7f26e319d7c" dependencies: debug "^3.1.0" sinon@^5.0.7: - version "5.0.7" - resolved "https://registry.yarnpkg.com/sinon/-/sinon-5.0.7.tgz#3bded6a73613ccc9e512e20246ced69a27c27dab" + version "5.1.1" + resolved "https://registry.yarnpkg.com/sinon/-/sinon-5.1.1.tgz#19c59810ffb733ea6e76a28b94a71fc4c2f523b8" dependencies: "@sinonjs/formatio" "^2.0.0" - diff "^3.1.0" + diff "^3.5.0" lodash.get "^4.4.2" - lolex "^2.2.0" - nise "^1.2.0" - supports-color "^5.1.0" - type-detect "^4.0.5" + lolex "^2.4.2" + nise "^1.3.3" + supports-color "^5.4.0" + type-detect "^4.0.8" slash@^1.0.0: version "1.0.0" @@ -7210,8 +7022,8 @@ snapdragon-util@^3.0.1: kind-of "^3.2.0" snapdragon@^0.8.1: - version "0.8.1" - resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.1.tgz#e12b5487faded3e3dea0ac91e9400bf75b401370" + version "0.8.2" + resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" dependencies: base "^0.11.1" debug "^2.2.0" @@ -7220,19 +7032,7 @@ snapdragon@^0.8.1: map-cache "^0.2.2" source-map "^0.5.6" source-map-resolve "^0.5.0" - use "^2.0.0" - -sntp@1.x.x: - version "1.0.9" - resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198" - dependencies: - hoek "2.x.x" - -sntp@2.x.x: - version "2.1.0" - resolved "https://registry.yarnpkg.com/sntp/-/sntp-2.1.0.tgz#2c6cec14fedc2222739caf9b5c3d85d1cc5a2cc8" - dependencies: - hoek "4.x.x" + use "^3.1.0" sort-keys@^2.0.0: version "2.0.0" @@ -7240,20 +7040,11 @@ sort-keys@^2.0.0: dependencies: is-plain-obj "^1.0.0" -source-map-resolve@^0.3.0: - version "0.3.1" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.3.1.tgz#610f6122a445b8dd51535a2a71b783dfc1248761" - dependencies: - atob "~1.1.0" - resolve-url "~0.2.1" - source-map-url "~0.3.0" - urix "~0.1.0" - -source-map-resolve@^0.5.0: - version "0.5.1" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.1.tgz#7ad0f593f2281598e854df80f19aae4b92d7a11a" +source-map-resolve@^0.5.0, source-map-resolve@^0.5.1: + version "0.5.2" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259" dependencies: - atob "^2.0.0" + atob "^2.1.1" decode-uri-component "^0.2.0" resolve-url "^0.2.1" source-map-url "^0.4.0" @@ -7266,24 +7057,17 @@ source-map-support@^0.4.15: source-map "^0.5.6" source-map-support@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.0.tgz#2018a7ad2bdf8faf2691e5fddab26bed5a2bacab" + version "0.5.9" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.9.tgz#41bc953b2534267ea2d605bccfa7bfa3111ced5f" dependencies: + buffer-from "^1.0.0" source-map "^0.6.0" source-map-url@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" -source-map-url@~0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.3.0.tgz#7ecaf13b57bcd09da8a40c5d269db33799d4aaf9" - -"source-map@>= 0.1.2", source-map@^0.6.0: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - -source-map@^0.1.38, source-map@~0.1.30, source-map@~0.1.33: +source-map@^0.1.38, source-map@~0.1.30: version "0.1.43" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.1.43.tgz#c24bc146ca517c1471f5dacbe2571b2b7f9e3346" dependencies: @@ -7295,13 +7079,17 @@ source-map@^0.4.2, source-map@^0.4.4: dependencies: amdefine ">=0.0.4" -source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.1, source-map@~0.5.6: +source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.1: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" +source-map@^0.6.0, source-map@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + sparkles@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/sparkles/-/sparkles-1.0.0.tgz#1acbbfb592436d10bbe8f785b7cc6f82815012c3" + version "1.0.1" + resolved "https://registry.yarnpkg.com/sparkles/-/sparkles-1.0.1.tgz#008db65edce6c50eec0c5e228e1945061dd0437c" spawn-sync@^1.0.15: version "1.0.15" @@ -7310,19 +7098,27 @@ spawn-sync@^1.0.15: concat-stream "^1.4.7" os-shim "^0.1.2" -spdx-correct@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40" +spdx-correct@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.0.0.tgz#05a5b4d7153a195bc92c3c425b69f3b2a9524c82" dependencies: - spdx-license-ids "^1.0.2" + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" -spdx-expression-parse@~1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz#9bdf2f20e1f40ed447fbe273266191fced51626c" +spdx-exceptions@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz#2c7ae61056c714a5b9b9b2b2af7d311ef5c78fe9" -spdx-license-ids@^1.0.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz#c9df7a3424594ade6bd11900d596696dc06bac57" +spdx-expression-parse@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0" + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz#7a7cd28470cc6d3a1cfe6d66886f6bc430d3ac87" split-string@^3.0.1, split-string@^3.0.2: version "3.1.0" @@ -7335,13 +7131,14 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" sshpk@^1.7.0: - version "1.13.1" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.13.1.tgz#512df6da6287144316dc4c18fe1cf1d940739be3" + version "1.14.2" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.14.2.tgz#c6fc61648a3d9c4e764fd3fcdf4ea105e492ba98" dependencies: asn1 "~0.2.3" assert-plus "^1.0.0" dashdash "^1.12.0" getpass "^0.1.1" + safer-buffer "^2.0.2" optionalDependencies: bcrypt-pbkdf "^1.0.0" ecc-jsbn "~0.1.1" @@ -7363,11 +7160,11 @@ statehood@4.x.x: items "2.x.x" joi "9.x.x" -static-eval@~0.2.0: - version "0.2.4" - resolved "https://registry.yarnpkg.com/static-eval/-/static-eval-0.2.4.tgz#b7d34d838937b969f9641ca07d48f8ede263ea7b" +static-eval@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/static-eval/-/static-eval-2.0.0.tgz#0e821f8926847def7b4b50cda5d55c04a9b13864" dependencies: - escodegen "~0.0.24" + escodegen "^1.8.1" static-extend@^0.1.1: version "0.1.2" @@ -7376,21 +7173,24 @@ static-extend@^0.1.1: define-property "^0.2.5" object-copy "^0.1.0" -static-module@^1.1.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/static-module/-/static-module-1.5.0.tgz#27da9883c41a8cd09236f842f0c1ebc6edf63d86" +static-module@^2.2.0: + version "2.2.5" + resolved "https://registry.yarnpkg.com/static-module/-/static-module-2.2.5.tgz#bd40abceae33da6b7afb84a0e4329ff8852bfbbf" dependencies: concat-stream "~1.6.0" - duplexer2 "~0.0.2" - escodegen "~1.3.2" + convert-source-map "^1.5.1" + duplexer2 "~0.1.4" + escodegen "~1.9.0" falafel "^2.1.0" - has "^1.0.0" - object-inspect "~0.4.0" - quote-stream "~0.0.0" - readable-stream "~1.0.27-1" + has "^1.0.1" + magic-string "^0.22.4" + merge-source-map "1.0.4" + object-inspect "~1.4.0" + quote-stream "~1.0.2" + readable-stream "~2.3.3" shallow-copy "~0.0.1" - static-eval "~0.2.0" - through2 "~0.4.1" + static-eval "^2.0.0" + through2 "~2.0.3" stdout-stream@^1.4.0: version "1.4.0" @@ -7403,8 +7203,8 @@ stealthy-require@^1.1.0: resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" stream-consume@~0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/stream-consume/-/stream-consume-0.1.0.tgz#a41ead1a6d6081ceb79f65b061901b6d8f3d1d0f" + version "0.1.1" + resolved "https://registry.yarnpkg.com/stream-consume/-/stream-consume-0.1.1.tgz#d3bdb598c2bd0ae82b8cac7ac50b1107a7996c48" stream-shift@^1.0.0: version "1.0.0" @@ -7429,7 +7229,7 @@ string-width@^1.0.1, string-width@^1.0.2: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -string-width@^2.0.0, string-width@^2.1.1: +"string-width@^1.0.2 || 2", string-width@^2.0.0, string-width@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" dependencies: @@ -7440,16 +7240,12 @@ string_decoder@~0.10.x: version "0.10.31" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" -string_decoder@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab" +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" dependencies: safe-buffer "~5.1.0" -stringstream@~0.0.4, stringstream@~0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878" - strip-ansi@^3.0.0, strip-ansi@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" @@ -7462,7 +7258,7 @@ strip-ansi@^4.0.0: dependencies: ansi-regex "^3.0.0" -strip-bom@3.0.0: +strip-bom@3.0.0, strip-bom@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" @@ -7513,8 +7309,8 @@ stylis-rule-sheet@^0.0.10: resolved "https://registry.yarnpkg.com/stylis-rule-sheet/-/stylis-rule-sheet-0.0.10.tgz#44e64a2b076643f4b52e5ff71efc04d8c3c4a430" stylis@^3.5.0: - version "3.5.1" - resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.5.1.tgz#fd341d59f57f9aeb412bc14c9d8a8670b438e03b" + version "3.5.3" + resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.5.3.tgz#99fdc46afba6af4deff570825994181a5e6ce546" subtext@4.x.x: version "4.4.1" @@ -7527,19 +7323,19 @@ subtext@4.x.x: wreck "12.x.x" superagent@^3.0.0: - version "3.8.2" - resolved "https://registry.yarnpkg.com/superagent/-/superagent-3.8.2.tgz#e4a11b9d047f7d3efeb3bbe536d9ec0021d16403" + version "3.8.3" + resolved "https://registry.yarnpkg.com/superagent/-/superagent-3.8.3.tgz#460ea0dbdb7d5b11bc4f78deba565f86a178e128" dependencies: component-emitter "^1.2.0" cookiejar "^2.1.0" debug "^3.1.0" extend "^3.0.0" form-data "^2.3.1" - formidable "^1.1.1" + formidable "^1.2.0" methods "^1.1.1" mime "^1.4.1" qs "^6.5.1" - readable-stream "^2.0.5" + readable-stream "^2.3.5" supertest-as-promised@4.0.2: version "4.0.2" @@ -7575,21 +7371,9 @@ supports-color@^3.1.2, supports-color@^3.2.3: dependencies: has-flag "^1.0.0" -supports-color@^4.0.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.5.0.tgz#be7a0de484dec5c5cddf8b3d59125044912f635b" - dependencies: - has-flag "^2.0.0" - -supports-color@^5.1.0: - version "5.4.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54" - dependencies: - has-flag "^3.0.0" - -supports-color@^5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.3.0.tgz#5b24ac15db80fa927cf5227a4a33fd3c4c7676c0" +supports-color@^5.3.0, supports-color@^5.4.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" dependencies: has-flag "^3.0.0" @@ -7597,7 +7381,7 @@ symbol-observable@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" -symbol-tree@^3.2.1: +symbol-tree@^3.2.2: version "3.2.2" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6" @@ -7609,11 +7393,7 @@ tabbable@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-1.1.0.tgz#2c9a9c9f09db5bb0659f587d532548dd6ef2067b" -tabbable@^1.0.3: - version "1.1.2" - resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-1.1.2.tgz#b171680aea6e0a3e9281ff23532e2e5de11c0d94" - -tabbable@^1.1.0: +tabbable@^1.0.3, tabbable@^1.1.0: version "1.1.3" resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-1.1.3.tgz#0e4ee376f3631e42d7977a074dbd2b3827843081" @@ -7625,47 +7405,28 @@ tar-fs@1.13.0: pump "^1.0.0" tar-stream "^1.1.2" -tar-fs@^1.16.0: - version "1.16.0" - resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-1.16.0.tgz#e877a25acbcc51d8c790da1c57c9cf439817b896" +tar-fs@^1.16.0, tar-fs@^1.16.2: + version "1.16.3" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-1.16.3.tgz#966a628841da2c4010406a82167cbd5e0c72d509" dependencies: chownr "^1.0.1" mkdirp "^0.5.1" pump "^1.0.0" tar-stream "^1.1.2" -tar-fs@^1.16.2: - version "1.16.2" - resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-1.16.2.tgz#17e5239747e399f7e77344f5f53365f04af53577" - dependencies: - chownr "^1.0.1" - mkdirp "^0.5.1" - pump "^1.0.0" - tar-stream "^1.1.2" - -tar-pack@^3.4.0: - version "3.4.1" - resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.4.1.tgz#e1dbc03a9b9d3ba07e896ad027317eb679a10a1f" - dependencies: - debug "^2.2.0" - fstream "^1.0.10" - fstream-ignore "^1.0.5" - once "^1.3.3" - readable-stream "^2.1.4" - rimraf "^2.5.1" - tar "^2.2.1" - uid-number "^0.0.6" - tar-stream@^1.1.2: - version "1.5.5" - resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.5.5.tgz#5cad84779f45c83b1f2508d96b09d88c7218af55" + version "1.6.1" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.1.tgz#f84ef1696269d6223ca48f6e1eeede3f7e81f395" dependencies: bl "^1.0.0" + buffer-alloc "^1.1.0" end-of-stream "^1.0.0" - readable-stream "^2.0.0" + fs-constants "^1.0.0" + readable-stream "^2.3.0" + to-buffer "^1.1.0" xtend "^4.0.0" -tar@^2.0.0, tar@^2.2.1: +tar@^2.0.0: version "2.2.1" resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1" dependencies: @@ -7673,6 +7434,18 @@ tar@^2.0.0, tar@^2.2.1: fstream "^1.0.2" inherits "2" +tar@^4: + version "4.4.6" + resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.6.tgz#63110f09c00b4e60ac8bcfe1bf3c8660235fbc9b" + dependencies: + chownr "^1.0.1" + fs-minipass "^1.2.5" + minipass "^2.3.3" + minizlib "^1.1.0" + mkdirp "^0.5.0" + safe-buffer "^5.1.2" + yallist "^3.0.2" + temp@^0.8.3: version "0.8.3" resolved "https://registry.yarnpkg.com/temp/-/temp-0.8.3.tgz#e0c6bc4d26b903124410e4fed81103014dfc1f59" @@ -7680,19 +7453,19 @@ temp@^0.8.3: os-tmpdir "^1.0.0" rimraf "~2.2.6" -test-exclude@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-4.1.1.tgz#4d84964b0966b0087ecc334a2ce002d3d9341e26" +test-exclude@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-4.2.1.tgz#dfa222f03480bca69207ca728b37d74b45f724fa" dependencies: arrify "^1.0.1" - micromatch "^2.3.11" + micromatch "^3.1.8" object-assign "^4.1.0" read-pkg-up "^1.0.1" require-main-filename "^1.0.1" tether@^1.3.7: - version "1.4.3" - resolved "https://registry.yarnpkg.com/tether/-/tether-1.4.3.tgz#fd547024c47b6e5c9b87e1880f997991a9a6ad54" + version "1.4.4" + resolved "https://registry.yarnpkg.com/tether/-/tether-1.4.4.tgz#9dc6eb2b3e601da2098fd264e7f7a8b264de1125" text-encoding@^0.6.4: version "0.6.4" @@ -7723,20 +7496,13 @@ through2@^0.6.1: readable-stream ">=1.0.33-1 <1.1.0-0" xtend ">=4.0.0 <4.1.0-0" -through2@^2.0.0, through2@^2.0.1, through2@^2.0.3, through2@~2.0.0: +through2@^2.0.0, through2@^2.0.1, through2@^2.0.3, through2@~2.0.0, through2@~2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be" dependencies: readable-stream "^2.1.5" xtend "~4.0.1" -through2@~0.4.1: - version "0.4.2" - resolved "https://registry.yarnpkg.com/through2/-/through2-0.4.2.tgz#dbf5866031151ec8352bb6c4db64a2292a840b9b" - dependencies: - readable-stream "~1.0.17" - xtend "~2.1.1" - through@^2.3.4, through@^2.3.6, through@^2.3.8, through@~2.3.4: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" @@ -7800,6 +7566,10 @@ to-absolute-glob@^2.0.0: is-absolute "^1.0.0" is-negated-glob "^1.0.0" +to-buffer@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80" + to-fast-properties@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" @@ -7821,13 +7591,14 @@ to-regex-range@^2.1.0: is-number "^3.0.0" repeat-string "^1.6.1" -to-regex@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.1.tgz#15358bee4a2c83bd76377ba1dc049d0f18837aae" +to-regex@^3.0.1, to-regex@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" dependencies: - define-property "^0.2.5" - extend-shallow "^2.0.1" - regex-not "^1.0.0" + define-property "^2.0.2" + extend-shallow "^3.0.2" + regex-not "^1.0.2" + safe-regex "^1.1.0" to-through@^2.0.0: version "2.0.0" @@ -7847,13 +7618,20 @@ topo@2.x.x: dependencies: hoek "4.x.x" -tough-cookie@>=2.3.3, tough-cookie@^2.3.3, tough-cookie@~2.3.0, tough-cookie@~2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.3.tgz#0b618a5565b6dea90bf3425d04d55edc475a7561" +tough-cookie@>=2.3.3, tough-cookie@^2.3.4, tough-cookie@~2.4.3: + version "2.4.3" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" + dependencies: + psl "^1.1.24" + punycode "^1.4.1" + +tough-cookie@~2.3.3: + version "2.3.4" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.4.tgz#ec60cee38ac675063ffc97a5c18970578ee83655" dependencies: punycode "^1.4.1" -tr46@^1.0.0: +tr46@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" dependencies: @@ -7864,10 +7642,10 @@ tree-kill@^1.1.0, tree-kill@^1.2.0: resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.0.tgz#5846786237b4239014f05db156b643212d4c6f36" trie-search@^1.0.1: - version "1.0.9" - resolved "https://registry.yarnpkg.com/trie-search/-/trie-search-1.0.9.tgz#1587757478c3900b0120c5aaf53e048cfaaff24a" + version "1.2.8" + resolved "https://registry.yarnpkg.com/trie-search/-/trie-search-1.2.8.tgz#9cca9c4d0df7b6638ad56e5e6b1a90307b1a94af" dependencies: - hasharray "^1.0.0" + hasharray "^1.1.1" trim-newlines@^1.0.0: version "1.0.0" @@ -7884,8 +7662,8 @@ trim-right@^1.0.1: glob "^6.0.4" tslib@^1.9.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.0.tgz#e37a86fda8cbbaf23a057f473c9f4dc64e5fc2e8" + version "1.9.3" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" tunnel-agent@^0.6.0: version "0.6.0" @@ -7893,10 +7671,6 @@ tunnel-agent@^0.6.0: dependencies: safe-buffer "^5.0.1" -tunnel-agent@~0.4.1: - version "0.4.3" - resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb" - tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" @@ -7911,7 +7685,7 @@ type-detect@0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-0.1.1.tgz#0ba5ec2a885640e470ea4e8505971900dac58822" -type-detect@^4.0.5: +type-detect@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" @@ -7923,9 +7697,9 @@ typescript@^2.9.2: version "2.9.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.2.tgz#1cbf61d05d6b96269244eb6a3bce4bd914e0f00c" -ua-parser-js@^0.7.9: - version "0.7.17" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.17.tgz#e9ec5f9498b9ec910e7ae3ac626a805c4d09ecac" +ua-parser-js@^0.7.18: + version "0.7.18" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.18.tgz#a7bfd92f56edfb117083b69e31d2aa8882d4b1ed" uglify-js@^2.6: version "2.8.29" @@ -7944,10 +7718,6 @@ ui-select@0.19.4: version "0.19.4" resolved "https://registry.yarnpkg.com/ui-select/-/ui-select-0.19.4.tgz#f5702c90cd91eca094202188a7fdbc9483e58797" -uid-number@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" - ultron@~1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c" @@ -8016,7 +7786,7 @@ unset-value@^1.0.0: has-value "^0.3.1" isobject "^3.0.0" -urix@^0.1.0, urix@~0.1.0: +urix@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" @@ -8034,13 +7804,9 @@ url-to-options@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9" -use@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/use/-/use-2.0.2.tgz#ae28a0d72f93bf22422a18a2e379993112dec8e8" - dependencies: - define-property "^0.2.5" - isobject "^3.0.0" - lazy-cache "^2.0.2" +use@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" user-home@^1.1.1: version "1.1.1" @@ -8061,9 +7827,9 @@ uuid@3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.1.tgz#6544bba2dfda8c1cf17e629a3a305e2bb1fee6c1" -uuid@^3.0.0, uuid@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" +uuid@^3.1.0, uuid@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" v8flags@^2.0.2: version "2.1.1" @@ -8072,11 +7838,11 @@ v8flags@^2.0.2: user-home "^1.1.1" validate-npm-package-license@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz#2804babe712ad3379459acfbe24746ab2c303fbc" + version "3.0.4" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" dependencies: - spdx-correct "~1.0.0" - spdx-expression-parse "~1.0.0" + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" value-equal@^0.4.0: version "0.4.0" @@ -8111,29 +7877,7 @@ vinyl-fs@^0.3.0: through2 "^0.6.1" vinyl "^0.4.0" -vinyl-fs@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-3.0.2.tgz#1b86258844383f57581fcaac081fe09ef6d6d752" - dependencies: - fs-mkdirp-stream "^1.0.0" - glob-stream "^6.1.0" - graceful-fs "^4.0.0" - is-valid-glob "^1.0.0" - lazystream "^1.0.0" - lead "^1.0.0" - object.assign "^4.0.4" - pumpify "^1.3.5" - readable-stream "^2.3.3" - remove-bom-buffer "^3.0.0" - remove-bom-stream "^1.2.0" - resolve-options "^1.1.0" - through2 "^2.0.0" - to-through "^2.0.0" - value-or-function "^3.0.0" - vinyl "^2.0.0" - vinyl-sourcemap "^1.1.0" - -vinyl-fs@^3.0.2: +vinyl-fs@^3.0.0, vinyl-fs@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-3.0.3.tgz#c85849405f67428feabbbd5c5dbdd64f47d31bc7" dependencies: @@ -8183,8 +7927,8 @@ vinyl@^0.5.0: replace-ext "0.0.1" vinyl@^2.0.0, vinyl@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-2.1.0.tgz#021f9c2cf951d6b939943c89eb5ee5add4fd924c" + version "2.2.0" + resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-2.2.0.tgz#d85b07da96e458d25b2ffe19fece9f2caa13ed86" dependencies: clone "^2.1.1" clone-buffer "^1.0.0" @@ -8199,6 +7943,16 @@ vise@2.x.x: dependencies: hoek "4.x.x" +vlq@^0.2.2: + version "0.2.3" + resolved "https://registry.yarnpkg.com/vlq/-/vlq-0.2.3.tgz#8f3e4328cf63b1540c0d67e1b2778386f8975b26" + +w3c-hr-time@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz#82ac2bff63d950ea9e3189a58a65625fedf19045" + dependencies: + browser-process-hrtime "^0.1.2" + walker@~1.0.5: version "1.0.7" resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb" @@ -8217,6 +7971,12 @@ warning@^3.0.0: dependencies: loose-envify "^1.0.0" +warning@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.2.tgz#aa6876480872116fa3e11d434b0d0d8d91e44607" + dependencies: + loose-envify "^1.0.0" + watch@~0.18.0: version "0.18.0" resolved "https://registry.yarnpkg.com/watch/-/watch-0.18.0.tgz#28095476c6df7c90c963138990c0a5423eb4b986" @@ -8224,27 +7984,39 @@ watch@~0.18.0: exec-sh "^0.2.0" minimist "^1.2.0" -webidl-conversions@^4.0.1, webidl-conversions@^4.0.2: +webidl-conversions@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" -whatwg-encoding@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.3.tgz#57c235bc8657e914d24e1a397d3c82daee0a6ba3" +whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.4.tgz#63fb016b7435b795d9025632c086a5209dbd2621" dependencies: - iconv-lite "0.4.19" + iconv-lite "0.4.23" whatwg-fetch@>=0.10.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84" + version "2.0.4" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f" + +whatwg-mimetype@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.1.0.tgz#f0f21d76cbba72362eb609dbed2a30cd17fcc7d4" + +whatwg-url@^6.4.1: + version "6.5.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.5.0.tgz#f2df02bff176fd65070df74ad5ccbb5a199965a8" + dependencies: + lodash.sortby "^4.7.0" + tr46 "^1.0.1" + webidl-conversions "^4.0.2" -whatwg-url@^6.3.0: - version "6.4.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.4.0.tgz#08fdf2b9e872783a7a1f6216260a1d66cc722e08" +whatwg-url@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.0.0.tgz#fde926fa54a599f3adf82dff25a9f7be02dc6edd" dependencies: lodash.sortby "^4.7.0" - tr46 "^1.0.0" - webidl-conversions "^4.0.1" + tr46 "^1.0.1" + webidl-conversions "^4.0.2" which-module@^1.0.0: version "1.0.0" @@ -8254,23 +8026,17 @@ which-module@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" -which@1: +which@1, which@^1.2.12, which@^1.2.14, which@^1.2.9, which@^1.3.0: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" dependencies: isexe "^2.0.0" -which@^1.2.12, which@^1.2.14, which@^1.2.9, which@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a" - dependencies: - isexe "^2.0.0" - wide-align@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.2.tgz#571e0f1b0604636ebc0dfc21b0339bbe31341710" + version "1.1.3" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" dependencies: - string-width "^1.0.2" + string-width "^1.0.2 || 2" window-size@0.1.0: version "0.1.0" @@ -8324,12 +8090,11 @@ ws@2.0.x: dependencies: ultron "~1.1.0" -ws@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-4.1.0.tgz#a979b5d7d4da68bf54efe0408967c324869a7289" +ws@^5.2.0: + version "5.2.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-5.2.2.tgz#dffef14866b8e8dc9133582514d1befaf96e980f" dependencies: async-limiter "~1.0.0" - safe-buffer "~5.1.0" xml-crypto@^0.10.1: version "0.10.1" @@ -8338,9 +8103,9 @@ xml-crypto@^0.10.1: xmldom "=0.1.19" xpath.js ">=0.0.3" -xml-name-validator@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-2.0.1.tgz#4d8b8f1eccd3419aa362061becef515e1e559635" +xml-name-validator@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" xml2js@0.2.8: version "0.2.8" @@ -8360,8 +8125,8 @@ xmlbuilder@0.4.2: resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-0.4.2.tgz#1776d65f3fdbad470a08d8604cdeb1c4e540ff83" xmlbuilder@~9.0.1: - version "9.0.4" - resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.4.tgz#519cb4ca686d005a8420d3496f3f0caeecca580f" + version "9.0.7" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" xmldom@=0.1.19: version "0.1.19" @@ -8379,12 +8144,6 @@ xregexp@3.2.0: version "4.0.1" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" -xtend@~2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-2.1.2.tgz#6efecc2a4dad8e6962c4901b337ce7ba87b5d28b" - dependencies: - object-keys "~0.4.0" - y18n@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" @@ -8393,6 +8152,10 @@ yallist@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" +yallist@^3.0.0, yallist@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.2.tgz#8452b4bb7e83c7c188d8041c1a837c773d6d8bb9" + yargs-parser@^2.4.0: version "2.4.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-2.4.1.tgz#85568de3cf150ff49fa51825f03a8c880ddcc5c4" @@ -8431,8 +8194,8 @@ yargs@4.7.1: yargs-parser "^2.4.0" yargs@^10.0.3: - version "10.1.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-10.1.1.tgz#5fe1ea306985a099b33492001fa19a1e61efe285" + version "10.1.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-10.1.2.tgz#454d074c2b16a51a43e2fb7807e4f9de69ccb5c5" dependencies: cliui "^4.0.0" decamelize "^1.1.1" diff --git a/yarn.lock b/yarn.lock index 6f1c1a150582f..c768fd3ffee84 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11,10 +11,10 @@ js-tokens "^3.0.0" "@babel/code-frame@^7.0.0-beta.35": - version "7.0.0-beta.42" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0-beta.42.tgz#a9c83233fa7cd06b39dc77adbb908616ff4f1962" + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0-rc.1.tgz#5c2154415d6c09959a71845ef519d11157e95d10" dependencies: - "@babel/highlight" "7.0.0-beta.42" + "@babel/highlight" "7.0.0-rc.1" "@babel/helper-function-name@7.0.0-beta.31": version "7.0.0-beta.31" @@ -31,9 +31,9 @@ dependencies: "@babel/types" "7.0.0-beta.31" -"@babel/highlight@7.0.0-beta.42": - version "7.0.0-beta.42" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.0.0-beta.42.tgz#a502a1c0d6f99b2b0e81d468a1b0c0e81e3f3623" +"@babel/highlight@7.0.0-rc.1": + version "7.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.0.0-rc.1.tgz#e0ca4731fa4786f7b9500421d6ff5e5a7753e81e" dependencies: chalk "^2.0.0" esutils "^2.0.2" @@ -181,6 +181,10 @@ call-me-maybe "^1.0.1" glob-to-regexp "^0.3.0" +"@nodelib/fs.stat@^1.0.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.1.tgz#53f349bb986ab273d601175aa1b25a655ab90ee3" + "@samverschueren/stream-to-observable@^0.3.0": version "0.3.0" resolved "https://registry.yarnpkg.com/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz#ecdf48d532c58ea477acfcab80348424f8d0662f" @@ -198,22 +202,20 @@ samsam "1.3.0" "@slack/client@^4.2.2": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@slack/client/-/client-4.2.2.tgz#f997f39780bbff9c2128816e8377230a5f6bd0d5" + version "4.4.0" + resolved "https://registry.yarnpkg.com/@slack/client/-/client-4.4.0.tgz#5600777fd630375683aeaa3445ddfaec8b5408fb" dependencies: - "@types/delay" "^2.0.1" "@types/form-data" "^2.2.1" "@types/got" "^7.1.7" "@types/is-stream" "^1.1.0" "@types/loglevel" "^1.5.3" - "@types/node" "^9.4.7" + "@types/node" ">=6.0.0" "@types/p-cancelable" "^0.3.0" "@types/p-queue" "^2.3.1" "@types/p-retry" "^1.0.1" "@types/retry" "^0.10.2" "@types/url-join" "^0.8.2" - "@types/ws" "^4.0.1" - delay "^2.0.0" + "@types/ws" "^5.1.1" eventemitter3 "^3.0.0" finity "^0.5.4" form-data "^2.3.1" @@ -225,14 +227,14 @@ object.values "^1.0.4" p-cancelable "^0.3.0" p-queue "^2.3.0" - p-retry "^1.0.0" - retry "^0.10.1" + p-retry "^2.0.0" + retry "^0.12.0" url-join "^4.0.0" - ws "^4.1.0" + ws "^5.2.0" "@types/angular@^1.6.45": - version "1.6.45" - resolved "https://registry.yarnpkg.com/@types/angular/-/angular-1.6.45.tgz#5b0b91a51d717f6fc816d59e1234d5292f33f7b9" + version "1.6.50" + resolved "https://registry.yarnpkg.com/@types/angular/-/angular-1.6.50.tgz#8b6599088d80f68ef0cad7d3a2062248ebe72b3d" "@types/babel-core@^6.25.5": version "6.25.5" @@ -274,16 +276,16 @@ "@types/babel-types" "*" "@types/bluebird@^3.1.1": - version "3.5.20" - resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.20.tgz#f6363172add6f4eabb8cada53ca9af2781e8d6a1" + version "3.5.23" + resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.23.tgz#e805da976b76892b2b2e50eec29e84914c730670" "@types/boom@*": version "7.2.0" resolved "https://registry.yarnpkg.com/@types/boom/-/boom-7.2.0.tgz#19c36cbb5811a7493f0f2e37f31d42b28df1abc1" "@types/catbox@*": - version "10.0.0" - resolved "https://registry.yarnpkg.com/@types/catbox/-/catbox-10.0.0.tgz#1e01e5ad83e224f110cc59f6f57c56558f7eeb61" + version "10.0.1" + resolved "https://registry.yarnpkg.com/@types/catbox/-/catbox-10.0.1.tgz#266679017749041fe9873fee1131dd2aaa04a07e" "@types/chance@^1.0.0": version "1.0.1" @@ -294,8 +296,8 @@ resolved "https://registry.yarnpkg.com/@types/cheerio/-/cheerio-0.22.8.tgz#5702f74f78b73e13f1eb1bd435c2c9de61a250d4" "@types/classnames@^2.2.3": - version "2.2.3" - resolved "https://registry.yarnpkg.com/@types/classnames/-/classnames-2.2.3.tgz#3f0ff6873da793870e20a260cada55982f38a9e5" + version "2.2.6" + resolved "https://registry.yarnpkg.com/@types/classnames/-/classnames-2.2.6.tgz#dbe8a666156d556ed018e15a4c65f08937c3f628" "@types/cookiejar@*": version "2.1.0" @@ -305,24 +307,16 @@ version "0.7.0" resolved "https://registry.yarnpkg.com/@types/dedent/-/dedent-0.7.0.tgz#155f339ca404e6dd90b9ce46a3f78fd69ca9b050" -"@types/delay@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@types/delay/-/delay-2.0.1.tgz#61bcf318a74b61e79d1658fbf054f984c90ef901" - -"@types/elasticsearch@^5.0.24": - version "5.0.25" - resolved "https://registry.yarnpkg.com/@types/elasticsearch/-/elasticsearch-5.0.25.tgz#717255a52acd9fa3ba165072d43a242283b1c898" - "@types/enzyme@^3.1.12": - version "3.1.12" - resolved "https://registry.yarnpkg.com/@types/enzyme/-/enzyme-3.1.12.tgz#293bb07c1ef5932d37add3879e72e0f5bc614f3c" + version "3.1.13" + resolved "https://registry.yarnpkg.com/@types/enzyme/-/enzyme-3.1.13.tgz#4bbc5c81fa40c9fc7efee25c4a23cb37119a33ea" dependencies: "@types/cheerio" "*" "@types/react" "*" "@types/eslint@^4.16.2": - version "4.16.2" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-4.16.2.tgz#30f4f026019eb78a6ef12f276b75cd16ea2afb27" + version "4.16.3" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-4.16.3.tgz#6ed2a9c68f73162fa57ba84582d63dcd8ee2d473" dependencies: "@types/estree" "*" "@types/json-schema" "*" @@ -403,12 +397,12 @@ resolved "https://registry.yarnpkg.com/@types/jest/-/jest-22.2.3.tgz#0157c0316dc3722c43a7b71de3fdf3acbccef10d" "@types/joi@*": - version "13.3.0" - resolved "https://registry.yarnpkg.com/@types/joi/-/joi-13.3.0.tgz#bdfa2e49d8d258ba79f23304228d0c4d5cfc848c" + version "13.4.3" + resolved "https://registry.yarnpkg.com/@types/joi/-/joi-13.4.3.tgz#4a9b19d9d16ab70f5eb4fc5e5a6c60392d72144a" "@types/joi@^10.4.4": - version "10.6.2" - resolved "https://registry.yarnpkg.com/@types/joi/-/joi-10.6.2.tgz#0e7d632fe918c337784e87b16c7cc0098876179a" + version "10.6.4" + resolved "https://registry.yarnpkg.com/@types/joi/-/joi-10.6.4.tgz#0989d69e792a7db13e951852e6949df6787f113f" "@types/jquery@3.3.1": version "3.3.1" @@ -419,19 +413,13 @@ resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.11.2.tgz#699ad86054cc20043c30d66a6fcde30bbf5d3d5e" "@types/json-schema@*": - version "6.0.1" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-6.0.1.tgz#a761975746f1c1b2579c62e3a4b5e88f986f7e2e" + version "7.0.0" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.0.tgz#5a2b780fec9f2cf851e1b5e0a29a4cac6c9686c5" "@types/json-stable-stringify@^1.0.32": version "1.0.32" resolved "https://registry.yarnpkg.com/@types/json-stable-stringify/-/json-stable-stringify-1.0.32.tgz#121f6917c4389db3923640b2e68de5fa64dda88e" -"@types/jsonwebtoken@^7.2.7": - version "7.2.8" - resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-7.2.8.tgz#8d199dab4ddb5bba3234f8311b804d2027af2b3a" - dependencies: - "@types/node" "*" - "@types/listr@^0.13.0": version "0.13.0" resolved "https://registry.yarnpkg.com/@types/listr/-/listr-0.13.0.tgz#6250bc4a04123cafa24fc73d1b880653a6ae6721" @@ -464,17 +452,13 @@ version "2.0.29" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-2.0.29.tgz#5002e14f75e2d71e564281df0431c8c1b4a2a36a" -"@types/node@*": - version "9.4.7" - resolved "https://registry.yarnpkg.com/@types/node/-/node-9.4.7.tgz#57d81cd98719df2c9de118f2d5f3b1120dcd7275" +"@types/node@*", "@types/node@>=6.0.0": + version "10.7.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.7.1.tgz#b704d7c259aa40ee052eec678758a68d07132a2e" "@types/node@^8.10.20": - version "8.10.21" - resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.21.tgz#12b3f2359b27aa05a45d886c8ba1eb8d1a77e285" - -"@types/node@^9.4.7": - version "9.6.18" - resolved "https://registry.yarnpkg.com/@types/node/-/node-9.6.18.tgz#092e13ef64c47e986802c9c45a61c1454813b31d" + version "8.10.26" + resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.26.tgz#950e3d4e6b316ba6e1ae4e84d9155aba67f88c2f" "@types/p-cancelable@^0.3.0": version "0.3.0" @@ -494,13 +478,15 @@ version "1.0.0" resolved "https://registry.yarnpkg.com/@types/podium/-/podium-1.0.0.tgz#bfaa2151be2b1d6109cc69f7faa9dac2cba3bb20" -"@types/prop-types@^15.5.3": - version "15.5.3" - resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.5.3.tgz#bef071852dca2a2dbb65fecdb7bfb30cedae2de2" +"@types/prop-types@*", "@types/prop-types@^15.5.3": + version "15.5.5" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.5.5.tgz#17038dd322c2325f5da650a94d5f9974943625e3" + dependencies: + "@types/react" "*" "@types/react-dom@^16.0.5": - version "16.0.5" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.0.5.tgz#a757457662e3819409229e8f86795ff37b371f96" + version "16.0.7" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.0.7.tgz#54d0f867a76b90597e8432030d297982f25c20ba" dependencies: "@types/node" "*" "@types/react" "*" @@ -512,10 +498,11 @@ "@types/react" "*" redux "^3.6.0" -"@types/react@*", "@types/react@^16.3.14": - version "16.3.14" - resolved "https://registry.yarnpkg.com/@types/react/-/react-16.3.14.tgz#f90ac6834de172e13ecca430dcb6814744225d36" +"@types/react@*", "@types/react@^16.3.0": + version "16.4.11" + resolved "https://registry.yarnpkg.com/@types/react/-/react-16.4.11.tgz#330f3d864300f71150dc2d125e48644c098f8770" dependencies: + "@types/prop-types" "*" csstype "^2.2.0" "@types/redux-actions@^2.2.1": @@ -545,15 +532,15 @@ resolved "https://registry.yarnpkg.com/@types/strip-ansi/-/strip-ansi-3.0.0.tgz#9b63d453a6b54aa849182207711a08be8eea48ae" "@types/superagent@*": - version "3.8.2" - resolved "https://registry.yarnpkg.com/@types/superagent/-/superagent-3.8.2.tgz#ffdda92843f8966fb4c5f482755ee641ffc53aa7" + version "3.8.3" + resolved "https://registry.yarnpkg.com/@types/superagent/-/superagent-3.8.3.tgz#51aa6040c21b66df00f46e10bb08fb51c541acfc" dependencies: "@types/cookiejar" "*" "@types/node" "*" "@types/supertest@^2.0.4": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@types/supertest/-/supertest-2.0.4.tgz#28770e13293365e240a842d7d5c5a1b3d2dee593" + version "2.0.5" + resolved "https://registry.yarnpkg.com/@types/supertest/-/supertest-2.0.5.tgz#18d082a667eaed22759be98f4923e0061ae70c62" dependencies: "@types/superagent" "*" @@ -571,17 +558,21 @@ dependencies: "@types/node" "*" -"@types/ws@^4.0.1": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-4.0.2.tgz#b29037627dd7ba31ec49a4f1584840422efb856f" +"@types/ws@^5.1.1": + version "5.1.2" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-5.1.2.tgz#f02d3b1cd46db7686734f3ce83bdf46c49decd64" dependencies: "@types/events" "*" "@types/node" "*" -abab@^1.0.0, abab@^1.0.4: +abab@^1.0.0: version "1.0.4" resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.4.tgz#5faad9c2c07f60dd76770f71cf025b62a63cfd4e" +abab@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.0.tgz#aba0ab4c5eee2d4c79d3487d85450fb2376ebb0f" + abbrev@1: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" @@ -657,9 +648,9 @@ acorn@^3.0.4, acorn@^3.1.0: version "3.3.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a" -acorn@^5.0.0, acorn@^5.3.0, acorn@^5.5.0: - version "5.5.3" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.5.3.tgz#f473dd47e0277a08e28e9bec5aeeb04751f0b8c9" +acorn@^5.0.0, acorn@^5.5.0, acorn@^5.5.3: + version "5.7.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.1.tgz#f095829297706a7c9776958c0afc8930a9b9d9d8" adm-zip@0.4.7: version "0.4.7" @@ -670,14 +661,14 @@ after@0.8.2: resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f" agent-base@4, agent-base@^4.1.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.0.tgz#9838b5c3392b962bad031e6a4c5e1024abec45ce" + version "4.2.1" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9" dependencies: es6-promisify "^5.0.0" agentkeepalive@^3.4.1: - version "3.4.1" - resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-3.4.1.tgz#aa95aebc3a749bca5ed53e3880a09f5235b48f0c" + version "3.5.1" + resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-3.5.1.tgz#4eba75cf2ad258fc09efd506cdb8d8c2971d35a4" dependencies: humanize-ms "^1.2.1" @@ -685,21 +676,10 @@ ajv-keywords@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.1.tgz#617997fc5f60576894c435f940d819e135b80762" -ajv-keywords@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.1.0.tgz#ac2b27939c543e95d2c06e7f7f5c27be4aa543be" - -ajv-keywords@^3.1.0: +ajv-keywords@^3.0.0, ajv-keywords@^3.1.0: version "3.2.0" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.2.0.tgz#e86b819c602cf8821ad637413698f1dec021847a" -ajv@^4.9.1: - version "4.11.8" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536" - dependencies: - co "^4.6.0" - json-stable-stringify "^1.0.1" - ajv@^5.0.0, ajv@^5.1.0, ajv@^5.1.5, ajv@^5.3.0: version "5.5.2" resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965" @@ -709,22 +689,14 @@ ajv@^5.0.0, ajv@^5.1.0, ajv@^5.1.5, ajv@^5.3.0: fast-json-stable-stringify "^2.0.0" json-schema-traverse "^0.3.0" -ajv@^6.0.1: - version "6.3.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.3.0.tgz#1650a41114ef00574cac10b8032d8f4c14812da7" - dependencies: - fast-deep-equal "^1.0.0" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.3.0" - -ajv@^6.1.0: - version "6.5.2" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.5.2.tgz#678495f9b82f7cca6be248dd92f59bff5e1f4360" +ajv@^6.0.1, ajv@^6.1.0: + version "6.5.3" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.5.3.tgz#71a569d189ecf4f4f321224fecb166f071dd90f9" dependencies: fast-deep-equal "^2.0.1" fast-json-stable-stringify "^2.0.0" json-schema-traverse "^0.4.1" - uri-js "^4.2.1" + uri-js "^4.2.2" align-text@^0.1.1, align-text@^0.1.3: version "0.1.4" @@ -801,10 +773,14 @@ angular-ui-ace@0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/angular-ui-ace/-/angular-ui-ace-0.2.3.tgz#3cb903428100621a367fc7f641440e97a42a26d0" -angular@1.6.9, angular@>=1.0.6: +angular@1.6.9: version "1.6.9" resolved "https://registry.yarnpkg.com/angular/-/angular-1.6.9.tgz#bc812932e18909038412d594a5990f4bb66c0619" +angular@>=1.0.6: + version "1.7.3" + resolved "https://registry.yarnpkg.com/angular/-/angular-1.7.3.tgz#0f25a661d079fc84d5ccc877a2ef9d7178575ddb" + ansi-align@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-2.0.0.tgz#c36aeccba563b89ceb556f3690f0b1d9e3547f7f" @@ -822,8 +798,8 @@ ansi-escapes@^1.0.0, ansi-escapes@^1.1.0: resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e" ansi-escapes@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.0.0.tgz#ec3e8b4e9f8064fc02c3ac9b65f1c275bda8ef92" + version "3.1.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.1.0.tgz#f73207bb81207d75fd6c83f125af26eea378ca30" ansi-regex@^0.2.0, ansi-regex@^0.2.1: version "0.2.1" @@ -879,19 +855,19 @@ append-buffer@^1.0.2: dependencies: buffer-equal "^1.0.0" -append-transform@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-0.4.0.tgz#d76ebf8ca94d276e247a36bad44a4b74ab611991" +append-transform@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-1.0.0.tgz#046a52ae582a228bd72f58acfbe2967c678759ab" dependencies: - default-require-extensions "^1.0.0" + default-require-extensions "^2.0.0" aproba@^1.0.3, aproba@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" are-we-there-yet@~1.1.2: - version "1.1.4" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz#bb5dca382bb94f05e15194373d16fd3ba1ca110d" + version "1.1.5" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" dependencies: delegates "^1.0.0" readable-stream "^2.0.6" @@ -931,6 +907,10 @@ array-differ@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-1.0.0.tgz#eff52e3758249d33be402b8bb8e564bb2b5d4031" +array-each@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/array-each/-/array-each-1.0.1.tgz#a794af0c05ab1752846ee753a1f211a05ba0c44f" + array-equal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93" @@ -974,6 +954,10 @@ array-slice@^0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-0.2.3.tgz#dd3cfb80ed7973a75117cdac69b0b99ec86186f5" +array-slice@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-1.1.0.tgz#e368ea15f89bc7069f7ffb89aec3a6c7d4ac22d4" + array-union@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" @@ -1013,17 +997,15 @@ asn1.js@^4.0.0: minimalistic-assert "^1.0.0" asn1@~0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86" + version "0.2.4" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" + dependencies: + safer-buffer "~2.1.0" assert-plus@1.0.0, assert-plus@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" -assert-plus@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234" - assert@^1.1.1: version "1.4.1" resolved "https://registry.yarnpkg.com/assert/-/assert-1.4.1.tgz#99912d591836b5a6f5b345c0f07eefc08fc65d91" @@ -1074,7 +1056,7 @@ async-limiter@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8" -async@1.x, async@^1.4.0, async@^1.4.2, async@^1.5.0, async@~1.5.2: +async@1.x, async@^1.4.0, async@^1.4.2, async@~1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" @@ -1084,11 +1066,11 @@ async@2.4.0: dependencies: lodash "^4.14.0" -async@^2.1.2, async@^2.1.4, async@^2.3.0, async@^2.4.1: - version "2.6.0" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.0.tgz#61a29abb6fcc026fea77e56d1c6ec53a795951f4" +async@^2.1.2, async@^2.1.4, async@^2.3.0, async@^2.4.1, async@^2.6.0: + version "2.6.1" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610" dependencies: - lodash "^4.14.0" + lodash "^4.17.10" async@~0.2.9: version "0.2.10" @@ -1102,13 +1084,9 @@ atoa@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/atoa/-/atoa-1.0.0.tgz#0cc0e91a480e738f923ebc103676471779b34a49" -atob@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/atob/-/atob-2.0.3.tgz#19c7a760473774468f20b2d2d03372ad7d4cbf5d" - -atob@~1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/atob/-/atob-1.1.3.tgz#95f13629b12c3a51a5d215abdce2aa9f32f80773" +atob@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" autobind-decorator@^1.3.4: version "1.4.3" @@ -1130,27 +1108,23 @@ autoprefixer@^6.3.1: postcss-value-parser "^3.2.3" autoprefixer@^9.1.0: - version "9.1.0" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.1.0.tgz#566a70d1148046b96b31efa08090f1999ffb6d8c" + version "9.1.2" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.1.2.tgz#73672614e3ee43a433b84c1c2a4b1ca392d2f6a1" dependencies: - browserslist "^4.0.1" - caniuse-lite "^1.0.30000872" + browserslist "^4.0.2" + caniuse-lite "^1.0.30000877" normalize-range "^0.1.2" num2fraction "^1.2.2" postcss "^7.0.2" postcss-value-parser "^3.2.3" -aws-sign2@~0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f" - aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" -aws4@^1.2.1, aws4@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" +aws4@^1.6.0, aws4@^1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" axios@^0.18.0: version "0.18.0" @@ -1199,31 +1173,7 @@ babel-core@6.21.0: slash "^1.0.0" source-map "^0.5.0" -babel-core@^6.0.0, babel-core@^6.18.0, babel-core@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.0.tgz#af32f78b31a6fcef119c87b0fd8d9753f03a0bb8" - dependencies: - babel-code-frame "^6.26.0" - babel-generator "^6.26.0" - babel-helpers "^6.24.1" - babel-messages "^6.23.0" - babel-register "^6.26.0" - babel-runtime "^6.26.0" - babel-template "^6.26.0" - babel-traverse "^6.26.0" - babel-types "^6.26.0" - babylon "^6.18.0" - convert-source-map "^1.5.0" - debug "^2.6.8" - json5 "^0.5.1" - lodash "^4.17.4" - minimatch "^3.0.4" - path-is-absolute "^1.0.1" - private "^0.1.7" - slash "^1.0.0" - source-map "^0.5.6" - -babel-core@^6.26.3: +babel-core@^6.0.0, babel-core@^6.18.0, babel-core@^6.26.0, babel-core@^6.26.3: version "6.26.3" resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.3.tgz#b2e2f09e342d0f0c88e2f02e067794125e75c207" dependencies: @@ -1380,12 +1330,12 @@ babel-helpers@^6.16.0, babel-helpers@^6.24.1: babel-runtime "^6.22.0" babel-template "^6.24.1" -babel-jest@^22.4.3: - version "22.4.3" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-22.4.3.tgz#4b7a0b6041691bbd422ab49b3b73654a49a6627a" +babel-jest@^22.4.3, babel-jest@^22.4.4: + version "22.4.4" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-22.4.4.tgz#977259240420e227444ebe49e226a61e49ea659d" dependencies: babel-plugin-istanbul "^4.1.5" - babel-preset-jest "^22.4.3" + babel-preset-jest "^22.4.4" babel-loader@7.1.2: version "7.1.2" @@ -1411,15 +1361,7 @@ babel-plugin-check-es2015-constants@^6.22.0: dependencies: babel-runtime "^6.22.0" -babel-plugin-istanbul@^4.1.5: - version "4.1.5" - resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.5.tgz#6760cdd977f411d3e175bb064f2bc327d99b2b6e" - dependencies: - find-up "^2.1.0" - istanbul-lib-instrument "^1.7.5" - test-exclude "^4.1.1" - -babel-plugin-istanbul@^4.1.6: +babel-plugin-istanbul@^4.1.5, babel-plugin-istanbul@^4.1.6: version "4.1.6" resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz#36c59b2192efce81c5b378321b74175add1c9a45" dependencies: @@ -1428,9 +1370,9 @@ babel-plugin-istanbul@^4.1.6: istanbul-lib-instrument "^1.10.1" test-exclude "^4.2.1" -babel-plugin-jest-hoist@^22.4.3: - version "22.4.3" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-22.4.3.tgz#7d8bcccadc2667f96a0dcc6afe1891875ee6c14a" +babel-plugin-jest-hoist@^22.4.4: + version "22.4.4" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-22.4.4.tgz#b9851906eab34c7bf6f8c895a2b08bea1a844c0b" babel-plugin-syntax-async-functions@^6.8.0: version "6.13.0" @@ -1580,16 +1522,7 @@ babel-plugin-transform-es2015-modules-amd@^6.22.0, babel-plugin-transform-es2015 babel-runtime "^6.22.0" babel-template "^6.24.1" -babel-plugin-transform-es2015-modules-commonjs@^6.23.0, babel-plugin-transform-es2015-modules-commonjs@^6.24.1: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.0.tgz#0d8394029b7dc6abe1a97ef181e00758dd2e5d8a" - dependencies: - babel-plugin-transform-strict-mode "^6.24.1" - babel-runtime "^6.26.0" - babel-template "^6.26.0" - babel-types "^6.26.0" - -babel-plugin-transform-es2015-modules-commonjs@^6.26.2: +babel-plugin-transform-es2015-modules-commonjs@^6.23.0, babel-plugin-transform-es2015-modules-commonjs@^6.24.1, babel-plugin-transform-es2015-modules-commonjs@^6.26.2: version "6.26.2" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz#58a793863a9e7ca870bdc5a881117ffac27db6f3" dependencies: @@ -1813,11 +1746,11 @@ babel-preset-flow@^6.23.0: dependencies: babel-plugin-transform-flow-strip-types "^6.22.0" -babel-preset-jest@^22.4.3: - version "22.4.3" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-22.4.3.tgz#e92eef9813b7026ab4ca675799f37419b5a44156" +babel-preset-jest@^22.4.3, babel-preset-jest@^22.4.4: + version "22.4.4" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-22.4.4.tgz#ec9fbd8bcd7dfd24b8b5320e0e688013235b7c39" dependencies: - babel-plugin-jest-hoist "^22.4.3" + babel-plugin-jest-hoist "^22.4.4" babel-plugin-syntax-object-rest-spread "^6.13.0" babel-preset-react@^6.24.1: @@ -1926,8 +1859,8 @@ backport@4.2.0: yargs "^11.0.0" bail@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/bail/-/bail-1.0.2.tgz#f7d6c1731630a9f9f0d4d35ed1f962e2074a1764" + version "1.0.3" + resolved "https://registry.yarnpkg.com/bail/-/bail-1.0.3.tgz#63cfb9ddbac829b02a3128cd53224be78e6c21a3" balanced-match@^0.4.2: version "0.4.2" @@ -1946,8 +1879,8 @@ base64-js@0.0.8: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-0.0.8.tgz#1101e9544f4a76b1bc3b26d452ca96d7a35e7978" base64-js@^1.0.2, base64-js@^1.1.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.3.tgz#fb13668233d9614cf5fb4bce95a9ba4096cdf801" + version "1.3.0" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3" base64id@1.0.0: version "1.0.0" @@ -1970,8 +1903,8 @@ batch-processor@^1.0.0: resolved "https://registry.yarnpkg.com/batch-processor/-/batch-processor-1.0.0.tgz#75c95c32b748e0850d10c2b168f6bdbe9891ace8" bcrypt-pbkdf@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d" + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" dependencies: tweetnacl "^0.14.3" @@ -1998,10 +1931,11 @@ binary-extensions@^1.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.11.0.tgz#46aa1751fb6a2f93ee5e689bb1087d4b14c6c205" bl@^1.0.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.1.tgz#cac328f7bee45730d404b692203fcb590e172d5e" + version "1.2.2" + resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.2.tgz#a160911717103c07410cef63ef51b397c025af9c" dependencies: - readable-stream "^2.0.5" + readable-stream "^2.3.5" + safe-buffer "^5.1.1" blob@0.0.4: version "0.0.4" @@ -2042,34 +1976,28 @@ bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" body-parser@^1.16.1: - version "1.18.2" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.2.tgz#87678a19d84b47d859b83199bd59bce222b10454" + version "1.18.3" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.3.tgz#5b292198ffdd553b3a0f20ded0592b956955c8b4" dependencies: bytes "3.0.0" content-type "~1.0.4" debug "2.6.9" - depd "~1.1.1" - http-errors "~1.6.2" - iconv-lite "0.4.19" + depd "~1.1.2" + http-errors "~1.6.3" + iconv-lite "0.4.23" on-finished "~2.3.0" - qs "6.5.1" - raw-body "2.3.2" - type-is "~1.6.15" + qs "6.5.2" + raw-body "2.3.3" + type-is "~1.6.16" -body-parser@~1.14.0: - version "1.14.2" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.14.2.tgz#1015cb1fe2c443858259581db53332f8d0cf50f9" +body@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/body/-/body-5.1.0.tgz#e4ba0ce410a46936323367609ecb4e6553125069" dependencies: - bytes "2.2.0" - content-type "~1.0.1" - debug "~2.2.0" - depd "~1.1.0" - http-errors "~1.3.1" - iconv-lite "0.4.13" - on-finished "~2.3.0" - qs "5.2.0" - raw-body "~2.1.5" - type-is "~1.6.10" + continuable-cache "^0.3.1" + error "^7.0.0" + raw-body "~1.1.0" + safe-json-parse "~1.0.1" boolbase@~1.0.0: version "1.0.0" @@ -2156,16 +2084,14 @@ braces@^1.8.2: repeat-element "^1.1.2" braces@^2.3.0, braces@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.1.tgz#7086c913b4e5a08dbe37ac0ee6a2500c4ba691bb" + version "2.3.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" dependencies: arr-flatten "^1.1.0" array-unique "^0.3.2" - define-property "^1.0.0" extend-shallow "^2.0.1" fill-range "^4.0.0" isobject "^3.0.1" - kind-of "^6.0.2" repeat-element "^1.1.2" snapdragon "^0.8.1" snapdragon-node "^2.0.1" @@ -2173,8 +2099,8 @@ braces@^2.3.0, braces@^2.3.1: to-regex "^3.0.1" brfs@^1.3.0, brfs@^1.4.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/brfs/-/brfs-1.5.0.tgz#a3822ed7a65723e056f89ff4b58e8abc63658f03" + version "1.6.1" + resolved "https://registry.yarnpkg.com/brfs/-/brfs-1.6.1.tgz#b78ce2336d818e25eea04a0947cba6d4fb8849c3" dependencies: quote-stream "^1.0.1" resolve "^1.1.5" @@ -2196,8 +2122,8 @@ browser-process-hrtime@^0.1.2: resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-0.1.2.tgz#425d68a58d3447f02a04aa894187fce8af8b7b8e" browser-resolve@^1.11.2, browser-resolve@^1.8.1: - version "1.11.2" - resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.2.tgz#8ff09b0a2c421718a1051c260b32e48f442938ce" + version "1.11.3" + resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.3.tgz#9b7cbb3d0f510e4cb86bdbd796124d28b5890af6" dependencies: resolve "1.1.7" @@ -2206,8 +2132,8 @@ browser-stdout@1.3.0: resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.0.tgz#f351d32969d32fa5d7a5567154263d928ae3bd1f" browserify-aes@^1.0.0, browserify-aes@^1.0.4: - version "1.1.1" - resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.1.1.tgz#38b7ab55edb806ff2dcda1a7f1620773a477c49f" + version "1.2.0" + resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" dependencies: buffer-xor "^1.0.3" cipher-base "^1.0.0" @@ -2217,20 +2143,21 @@ browserify-aes@^1.0.0, browserify-aes@^1.0.4: safe-buffer "^5.0.1" browserify-cipher@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.0.tgz#9988244874bf5ed4e28da95666dcd66ac8fc363a" + version "1.0.1" + resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0" dependencies: browserify-aes "^1.0.4" browserify-des "^1.0.0" evp_bytestokey "^1.0.0" browserify-des@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.0.tgz#daa277717470922ed2fe18594118a175439721dd" + version "1.0.2" + resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.2.tgz#3af4f1f59839403572f1c66204375f7a7f703e9c" dependencies: cipher-base "^1.0.1" des.js "^1.0.0" inherits "^2.0.1" + safe-buffer "^5.1.2" browserify-optional@^1.0.0: version "1.0.1" @@ -2272,13 +2199,13 @@ browserslist@^1.3.6, browserslist@^1.4.0, browserslist@^1.5.2, browserslist@^1.7 caniuse-db "^1.0.30000639" electron-to-chromium "^1.2.7" -browserslist@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.0.1.tgz#61c05ce2a5843c7d96166408bc23d58b5416e818" +browserslist@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.0.2.tgz#294388f5844bb3ab15ef7394ca17f49bf7a4e6f1" dependencies: - caniuse-lite "^1.0.30000865" - electron-to-chromium "^1.3.52" - node-releases "^1.0.0-alpha.10" + caniuse-lite "^1.0.30000876" + electron-to-chromium "^1.3.57" + node-releases "^1.0.0-alpha.11" bser@^2.0.0: version "2.0.0" @@ -2286,6 +2213,17 @@ bser@^2.0.0: dependencies: node-int64 "^0.4.0" +buffer-alloc-unsafe@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0" + +buffer-alloc@^1.1.0, buffer-alloc@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec" + dependencies: + buffer-alloc-unsafe "^1.1.0" + buffer-fill "^1.0.0" + buffer-crc32@~0.2.3: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" @@ -2302,9 +2240,13 @@ buffer-equal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-1.0.0.tgz#59616b498304d556abd466966b22eeda3eca5fbe" -buffer-from@^1.0.0: +buffer-fill@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.0.0.tgz#4cb8832d23612589b0406e9e2956c17f06fdf531" + resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c" + +buffer-from@^1.0.0, buffer-from@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" buffer-xor@^1.0.3: version "1.0.3" @@ -2327,8 +2269,8 @@ buffer@^4.3.0: isarray "^1.0.0" buffer@^5.0.3: - version "5.1.0" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.1.0.tgz#c913e43678c7cb7c8bd16afbcddb6c5505e8f9fe" + version "5.2.0" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.2.0.tgz#53cf98241100099e9eeae20ee6d51d21b16e541e" dependencies: base64-js "^1.0.2" ieee754 "^1.1.4" @@ -2353,13 +2295,9 @@ builtins@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/builtins/-/builtins-0.0.7.tgz#355219cd6cf18dbe7c01cc7fd2dce765cfdc549a" -bytes@2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.2.0.tgz#fd35464a403f6f9117c2de3609ecff9cae000588" - -bytes@2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.4.0.tgz#7d97196f9d5baf7f6935e25985549edd2a6c2339" +bytes@1: + version "1.0.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-1.0.0.tgz#3569ede8ba34315fab99c3e92cb04c7220de1fa8" bytes@3.0.0: version "3.0.0" @@ -2516,21 +2454,23 @@ caniuse-api@^1.5.2: lodash.uniq "^4.5.0" caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639: - version "1.0.30000815" - resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000815.tgz#0e218fa133d0d071c886aa041b435258cc746891" + version "1.0.30000878" + resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000878.tgz#0d0c6d8500c3aea21441fad059bce4b8f3f509df" -caniuse-lite@^1.0.30000865, caniuse-lite@^1.0.30000872: - version "1.0.30000874" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000874.tgz#a641b1f1c420d58d9b132920ef6ba87bbdcd2223" +caniuse-lite@^1.0.30000876, caniuse-lite@^1.0.30000877: + version "1.0.30000878" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000878.tgz#c644c39588dd42d3498e952234c372e5a40a4123" + +capture-exit@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-1.2.0.tgz#1c5fcc489fd0ab00d4f1ac7ae1072e3173fbab6f" + dependencies: + rsvp "^3.3.3" capture-stack-trace@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz#4a6fa07399c26bba47f0b2496b4d0fb408c5550d" -caseless@~0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.11.0.tgz#715b96ea9841593cc33067923f5ec60ebda4f7d7" - caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" @@ -2600,15 +2540,7 @@ chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3, chalk@~1.1.1: strip-ansi "^3.0.0" supports-color "^2.0.0" -chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.1, chalk@^2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.2.tgz#250dc96b07491bfd601e648d66ddf5f60c7a5c65" - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -chalk@^2.4.1: +chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.1, chalk@^2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e" dependencies: @@ -2631,12 +2563,12 @@ chance@1.0.10: resolved "https://registry.yarnpkg.com/chance/-/chance-1.0.10.tgz#03500b04ad94e778dd2891b09ec73a6ad87b1996" character-entities-legacy@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-1.1.1.tgz#f40779df1a101872bb510a3d295e1fccf147202f" + version "1.1.2" + resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-1.1.2.tgz#7c6defb81648498222c9855309953d05f4d63a9c" character-entities@^1.0.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-1.2.1.tgz#f76871be5ef66ddb7f8f8e3478ecc374c27d6dca" + version "1.2.2" + resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-1.2.2.tgz#58c8f371c0774ef0ba9b2aca5f00d8f100e6e363" character-parser@^2.1.1: version "2.2.0" @@ -2645,8 +2577,8 @@ character-parser@^2.1.1: is-regex "^1.0.3" character-reference-invalid@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.1.tgz#942835f750e4ec61a308e60c2ef8cc1011202efc" + version "1.1.2" + resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.2.tgz#21e421ad3d84055952dab4a43a04e73cd425d3ed" chardet@^0.4.0: version "0.4.2" @@ -2719,8 +2651,8 @@ chokidar@^1.4.1, chokidar@^1.6.0: fsevents "^1.0.0" chokidar@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.0.2.tgz#4dc65139eeb2714977735b6a35d06e97b494dfd7" + version "2.0.4" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.0.4.tgz#356ff4e2b0e8e43e322d18a372460bbcf3accd26" dependencies: anymatch "^2.0.0" async-each "^1.0.0" @@ -2729,12 +2661,13 @@ chokidar@^2.0.2: inherits "^2.0.1" is-binary-path "^1.0.0" is-glob "^4.0.0" + lodash.debounce "^4.0.8" normalize-path "^2.1.1" path-is-absolute "^1.0.0" readdirp "^2.0.0" - upath "^1.0.0" + upath "^1.0.5" optionalDependencies: - fsevents "^1.0.0" + fsevents "^1.2.2" chownr@^1.0.1: version "1.0.1" @@ -2757,9 +2690,9 @@ chromedriver@2.41.0: mkdirp "^0.5.1" request "^2.87.0" -ci-info@^1.0.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.1.3.tgz#710193264bb05c77b8c90d02f5aaf22216a667b2" +ci-info@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.4.0.tgz#4841d53cad49f11b827b648ebde27a6e189b412f" cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: version "1.0.4" @@ -2787,15 +2720,19 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" -classnames@2.2.5, classnames@2.x, classnames@^2.1.2, classnames@^2.2.3, classnames@^2.2.4, classnames@^2.2.5: +classnames@2.2.5: version "2.2.5" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.5.tgz#fb3801d453467649ef3603c7d61a02bd129bde6d" +classnames@2.x, classnames@^2.1.2, classnames@^2.2.3, classnames@^2.2.4, classnames@^2.2.5: + version "2.2.6" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce" + clean-css@^4.1.11: - version "4.1.11" - resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.1.11.tgz#2ecdf145aba38f54740f26cefd0ff3e03e125d6a" + version "4.2.1" + resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.1.tgz#2d411ef76b8569b6d0c84068dabe85b0aa5e5c17" dependencies: - source-map "0.5.x" + source-map "~0.6.0" cli-boxes@^1.0.0: version "1.0.0" @@ -2817,11 +2754,7 @@ cli-spinners@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-0.1.2.tgz#bb764d88e185fb9e1e6a2a1f19772318f605e31c" -cli-spinners@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-1.1.0.tgz#f1847b168844d917a671eb9d147e3df497c90d06" - -cli-spinners@^1.1.0: +cli-spinners@^1.0.1, cli-spinners@^1.1.0: version "1.3.1" resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-1.3.1.tgz#002c1990912d0d59580c93bd36c056de99e4259a" @@ -2865,8 +2798,8 @@ cliui@^3.2.0: wrap-ansi "^2.0.0" cliui@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.0.0.tgz#743d4650e05f36d1ed2575b59638d87322bfbbcc" + version "4.1.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.1.0.tgz#348422dbe82d800b3022eef4f6ac10bf2e4d1b49" dependencies: string-width "^2.1.1" strip-ansi "^4.0.0" @@ -2891,12 +2824,12 @@ clone-stats@^1.0.0: resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-1.0.0.tgz#b3782dff8bb5474e18b9b6bf0fdfe782f8777680" clone@^1.0.0, clone@^1.0.1, clone@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.3.tgz#298d7e2231660f40c003c2ed3140decf3f53085f" + version "1.0.4" + resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" clone@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.1.tgz#d217d1e961118e3ac9a4b8bba3285553bf647cdb" + version "2.1.2" + resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" cloneable-readable@^1.0.0: version "1.1.2" @@ -2906,10 +2839,6 @@ cloneable-readable@^1.0.0: process-nextick-args "^2.0.0" readable-stream "^2.3.5" -co@3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/co/-/co-3.1.0.tgz#4ea54ea5a08938153185e15210c68d9092bc1b78" - co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" @@ -2933,8 +2862,8 @@ coffee-script@~1.10.0: resolved "https://registry.yarnpkg.com/coffee-script/-/coffee-script-1.10.0.tgz#12938bcf9be1948fa006f92e0c4c9e81705108c0" collapse-white-space@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.3.tgz#4b906f670e5a963a87b76b0e1689643341b6023c" + version "1.0.4" + resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.4.tgz#ce05cf49e54c3277ae573036a26851ba430a0091" collection-visit@^1.0.0: version "1.0.0" @@ -2944,12 +2873,16 @@ collection-visit@^1.0.0: object-visit "^1.0.0" color-convert@^1.3.0, color-convert@^1.8.2, color-convert@^1.9.0: - version "1.9.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.1.tgz#c1261107aeb2f294ebffec9ed9ecad529a6097ed" + version "1.9.2" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.2.tgz#49881b8fba67df12a96bdf3f56c0aab9e7913147" dependencies: - color-name "^1.1.1" + color-name "1.1.1" -color-name@^1.0.0, color-name@^1.1.1: +color-name@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.1.tgz#4b1415304cf50028ea81643643bd82ea05803689" + +color-name@^1.0.0: version "1.1.3" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" @@ -2960,8 +2893,8 @@ color-string@^0.3.0: color-name "^1.0.0" color-string@^1.4.0: - version "1.5.2" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.2.tgz#26e45814bc3c9a7cbd6751648a41434514a773a9" + version "1.5.3" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.3.tgz#c9bbc5f01b58b5492f3d6857459cb6590ce204cc" dependencies: color-name "^1.0.0" simple-swizzle "^0.2.2" @@ -2994,8 +2927,8 @@ colors@0.5.x: resolved "https://registry.yarnpkg.com/colors/-/colors-0.5.1.tgz#7d0023eaeb154e8ee9fce75dcb923d0ed1667774" colors@^1.1.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.2.1.tgz#f4a3d302976aaf042356ba1ade3b1a2c62d9d794" + version "1.3.1" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.3.1.tgz#4accdb89cf2cabc7f982771925e9468784f32f3d" colors@~1.1.2: version "1.1.2" @@ -3007,15 +2940,15 @@ combine-lists@^1.0.0: dependencies: lodash "^4.5.0" -combined-stream@1.0.6, combined-stream@^1.0.5, combined-stream@~1.0.5: +combined-stream@1.0.6, combined-stream@~1.0.5, combined-stream@~1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.6.tgz#723e7df6e801ac5613113a7e445a9b69cb632818" dependencies: delayed-stream "~1.0.0" -commander@2, commander@^2.12.1, commander@^2.9.0: - version "2.15.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" +commander@2, commander@^2.12.1: + version "2.17.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf" commander@2.1.x: version "2.1.0" @@ -3041,10 +2974,14 @@ commondir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" -compare-versions@3.1.0, compare-versions@^3.1.0: +compare-versions@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.1.0.tgz#43310256a5c555aaed4193c04d8f154cf9c6efd5" +compare-versions@^3.1.0: + version "3.3.1" + resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.3.1.tgz#1ede3172b713c15f7c7beb98cb74d2d82576dad3" + component-bind@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/component-bind/-/component-bind-1.0.0.tgz#00c608ab7dcd93897c0009651b1d3a8e1e73bbd1" @@ -3081,7 +3018,7 @@ concat-stream@1.5.1: readable-stream "~2.0.0" typedarray "~0.0.5" -concat-stream@1.6.2, concat-stream@^1.5.0: +concat-stream@1.6.2, concat-stream@^1.4.7, concat-stream@^1.5.0, concat-stream@^1.6.0, concat-stream@~1.6.0: version "1.6.2" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" dependencies: @@ -3090,14 +3027,6 @@ concat-stream@1.6.2, concat-stream@^1.5.0: readable-stream "^2.2.2" typedarray "^0.0.6" -concat-stream@^1.4.7, concat-stream@^1.6.0, concat-stream@~1.6.0: - version "1.6.1" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.1.tgz#261b8f518301f1d834e36342b9fea095d2620a26" - dependencies: - inherits "^2.0.3" - readable-stream "^2.2.2" - typedarray "^0.0.6" - conf@^1.1.2: version "1.4.0" resolved "https://registry.yarnpkg.com/conf/-/conf-1.4.0.tgz#1ea66c9d7a9b601674a5bb9d2b8dc3c726625e67" @@ -3129,8 +3058,8 @@ configstore@^1.0.0: xdg-basedir "^2.0.0" configstore@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/configstore/-/configstore-3.1.1.tgz#094ee662ab83fad9917678de114faaea8fcdca90" + version "3.1.2" + resolved "https://registry.yarnpkg.com/configstore/-/configstore-3.1.2.tgz#c6f25defaeef26df12dd33414b001fe81a543f8f" dependencies: dot-prop "^4.1.0" graceful-fs "^4.1.2" @@ -3175,11 +3104,11 @@ contains-path@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" -content-type-parser@^1.0.1, content-type-parser@^1.0.2: +content-type-parser@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/content-type-parser/-/content-type-parser-1.0.2.tgz#caabe80623e63638b2502fd4c7f12ff4ce2352e7" -content-type@~1.0.1, content-type@~1.0.4: +content-type@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" @@ -3195,6 +3124,10 @@ content@4.x.x: dependencies: boom "7.x.x" +continuable-cache@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/continuable-cache/-/continuable-cache-0.3.1.tgz#bd727a7faed77e71ff3985ac93351a912733ad0f" + contra@1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/contra/-/contra-1.9.1.tgz#60e498274b3d2d332896d60f82900aefa2ecac8c" @@ -3211,8 +3144,8 @@ cookie@0.3.1: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" cookiejar@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.1.tgz#41ad57b1b555951ec171412a81942b1e8200d34a" + version "2.1.2" + resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.2.tgz#dd8a235530752f988f9a0844f3fc589e3111125c" copy-concurrently@^1.0.0: version "1.0.5" @@ -3234,8 +3167,8 @@ core-js@^1.0.0: resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" core-js@^2.2.0, core-js@^2.4.0, core-js@^2.5.0, core-js@^2.5.1: - version "2.5.3" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.3.tgz#8acc38345824f16d8365b7c9b4259168e8ed603e" + version "2.5.7" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.7.tgz#f972608ff0cead68b841a16a932d0b183791814e" core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" @@ -3270,8 +3203,8 @@ cpx@^1.5.0: subarg "^1.0.0" create-ecdh@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.0.tgz#888c723596cdf7612f6498233eebd7a35301737d" + version "4.0.3" + resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.3.tgz#c9111b6f33045c4697f144787f9254cdc77c45ff" dependencies: bn.js "^4.1.0" elliptic "^6.0.0" @@ -3283,17 +3216,18 @@ create-error-class@^3.0.0, create-error-class@^3.0.1: capture-stack-trace "^1.0.0" create-hash@^1.1.0, create-hash@^1.1.2: - version "1.1.3" - resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.1.3.tgz#606042ac8b9262750f483caddab0f5819172d8fd" + version "1.2.0" + resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" dependencies: cipher-base "^1.0.1" inherits "^2.0.1" - ripemd160 "^2.0.0" + md5.js "^1.3.4" + ripemd160 "^2.0.1" sha.js "^2.4.0" create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: - version "1.1.6" - resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.6.tgz#acb9e221a4e17bdb076e90657c42b93e3726cf06" + version "1.1.7" + resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" dependencies: cipher-base "^1.0.3" create-hash "^1.1.0" @@ -3355,12 +3289,6 @@ crossvent@1.5.4: dependencies: custom-event "1.0.0" -cryptiles@2.x.x: - version "2.0.5" - resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" - dependencies: - boom "2.x.x" - cryptiles@3.x.x: version "3.1.2" resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-3.1.2.tgz#a89fbb220f5ce25ec56e8c4aa8a4fd7b5b0d29fe" @@ -3454,8 +3382,8 @@ css-selector-tokenizer@^0.7.0: regexpu-core "^1.0.0" css-to-react-native@^2.0.3: - version "2.1.2" - resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-2.1.2.tgz#c06d628467ef961c85ec358a90f3c87469fb0095" + version "2.2.1" + resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-2.2.1.tgz#7f3f4c95de65501b8720c87bf0caf1f39073b88e" dependencies: css-color-keywords "^1.0.0" fbjs "^0.8.5" @@ -3466,12 +3394,12 @@ css-what@2.1: resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.0.tgz#9467d032c38cfaefb9f2d79501253062f87fa1bd" css@2.X: - version "2.2.1" - resolved "https://registry.yarnpkg.com/css/-/css-2.2.1.tgz#73a4c81de85db664d4ee674f7d47085e3b2d55dc" + version "2.2.3" + resolved "https://registry.yarnpkg.com/css/-/css-2.2.3.tgz#f861f4ba61e79bedc962aa548e5780fd95cbc6be" dependencies: inherits "^2.0.1" source-map "^0.1.38" - source-map-resolve "^0.3.0" + source-map-resolve "^0.5.1" urix "^0.1.0" cssesc@^0.1.0: @@ -3523,18 +3451,24 @@ csso@~2.3.1: source-map "^0.5.3" cssom@0.3.x, "cssom@>= 0.3.0 < 0.4.0", "cssom@>= 0.3.2 < 0.4.0": - version "0.3.2" - resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.2.tgz#b8036170c79f07a90ff2f16e22284027a243848b" + version "0.3.4" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.4.tgz#8cd52e8a3acfd68d3aed38ee0a640177d2f9d797" -"cssstyle@>= 0.2.36 < 0.3.0", "cssstyle@>= 0.2.37 < 0.3.0": +"cssstyle@>= 0.2.36 < 0.3.0": version "0.2.37" resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-0.2.37.tgz#541097234cb2513c83ceed3acddc27ff27987d54" dependencies: cssom "0.3.x" +cssstyle@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-1.1.1.tgz#18b038a9c44d65f7a8e428a653b9f6fe42faf5fb" + dependencies: + cssom "0.3.x" + csstype@^2.2.0: - version "2.5.2" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.5.2.tgz#4534308476ceede8fbe148b9b99f9baf1c80fa06" + version "2.5.6" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.5.6.tgz#2ae1db2319642d8b80a668d2d025c6196071e788" currently-unhandled@^0.4.1: version "0.4.1" @@ -3558,7 +3492,7 @@ cyclist@~0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640" -d3-array@1, d3-array@^1.1.1, d3-array@^1.2.0: +d3-array@1, d3-array@^1.1.1, d3-array@^1.2.0, d3-array@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-1.2.1.tgz#d1ca33de2f6ac31efadb8e050a021d7e2396d5dc" @@ -3573,12 +3507,12 @@ d3-collection@1, d3-collection@^1.0.3: resolved "https://registry.yarnpkg.com/d3-collection/-/d3-collection-1.0.4.tgz#342dfd12837c90974f33f1cc0a785aea570dcdc2" d3-color@1, d3-color@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-1.0.3.tgz#bc7643fca8e53a8347e2fbdaffa236796b58509b" + version "1.2.0" + resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-1.2.0.tgz#d1ea19db5859c86854586276ec892cf93148459a" d3-contour@1, d3-contour@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/d3-contour/-/d3-contour-1.2.0.tgz#de3ea7991bbb652155ee2a803aeafd084be03b63" + version "1.3.0" + resolved "https://registry.yarnpkg.com/d3-contour/-/d3-contour-1.3.0.tgz#cfb99098c48c46edd77e15ce123162f9e333e846" dependencies: d3-array "^1.1.1" @@ -3608,26 +3542,26 @@ d3-force@1: d3-timer "1" d3-format@1, d3-format@^1.2.0: - version "1.2.2" - resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.2.2.tgz#1a39c479c8a57fe5051b2e67a3bee27061a74e7a" + version "1.3.0" + resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.3.0.tgz#a3ac44269a2011cdb87c7b5693040c18cddfff11" -d3-geo@1, d3-geo@^1.6.4: +d3-geo@1, d3-geo@^1.10.0, d3-geo@^1.6.4: version "1.10.0" resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-1.10.0.tgz#2972d18014f1e38fc1f8bb6d545377bdfb00c9ab" dependencies: d3-array "1" d3-hierarchy@1, d3-hierarchy@^1.1.4: - version "1.1.5" - resolved "https://registry.yarnpkg.com/d3-hierarchy/-/d3-hierarchy-1.1.5.tgz#a1c845c42f84a206bcf1c01c01098ea4ddaa7a26" - -d3-interpolate@1, d3-interpolate@^1.1.4: version "1.1.6" - resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-1.1.6.tgz#2cf395ae2381804df08aa1bf766b7f97b5f68fb6" + resolved "https://registry.yarnpkg.com/d3-hierarchy/-/d3-hierarchy-1.1.6.tgz#842c1372090f870b7ea013ebae5c0c8d9f56229c" + +d3-interpolate@1, d3-interpolate@^1.1.4, d3-interpolate@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-1.2.0.tgz#40d81bd8e959ff021c5ea7545bc79b8d22331c41" dependencies: d3-color "1" -d3-path@1: +d3-path@1, d3-path@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-1.0.5.tgz#241eb1849bd9e9e8021c0d0a799f8a0e8e441764" @@ -3652,9 +3586,9 @@ d3-sankey@^0.7.1: d3-collection "1" d3-shape "^1.2.0" -d3-scale-chromatic@^1.2: - version "1.2.0" - resolved "https://registry.yarnpkg.com/d3-scale-chromatic/-/d3-scale-chromatic-1.2.0.tgz#25820d059c0eccc33e85f77561f37382a817ab58" +d3-scale-chromatic@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/d3-scale-chromatic/-/d3-scale-chromatic-1.3.0.tgz#7ee38ffcaa7ad55cfed83a6a668aac5570c653c4" dependencies: d3-color "1" d3-interpolate "1" @@ -3671,30 +3605,30 @@ d3-scale@1.0.6: d3-time "1" d3-time-format "2" -d3-scale@2: - version "2.0.0" - resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-2.0.0.tgz#fd8ac78381bc2ed741d8c71770437a5e0549a5a5" +d3-scale@^1.0.5: + version "1.0.7" + resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-1.0.7.tgz#fa90324b3ea8a776422bd0472afab0b252a0945d" dependencies: d3-array "^1.2.0" d3-collection "1" + d3-color "1" d3-format "1" d3-interpolate "1" d3-time "1" d3-time-format "2" -d3-scale@^1.0.5: - version "1.0.7" - resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-1.0.7.tgz#fa90324b3ea8a776422bd0472afab0b252a0945d" +d3-scale@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-2.1.0.tgz#8d3fd3e2a7c9080782a523c08507c5248289eef8" dependencies: d3-array "^1.2.0" d3-collection "1" - d3-color "1" d3-format "1" d3-interpolate "1" d3-time "1" d3-time-format "2" -d3-shape@1, d3-shape@^1.1.0, d3-shape@^1.2.0: +d3-shape@^1.1.0, d3-shape@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.2.0.tgz#45d01538f064bafd05ea3d6d2cb748fd8c41f777" dependencies: @@ -3706,7 +3640,7 @@ d3-time-format@2: dependencies: d3-time "1" -d3-time@1: +d3-time@1, d3-time@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-1.0.8.tgz#dbd2d6007bf416fe67a76d17947b784bffea1e84" @@ -3734,6 +3668,14 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" +data-urls@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-1.0.1.tgz#d416ac3896918f29ca84d81085bc3705834da579" + dependencies: + abab "^2.0.0" + whatwg-mimetype "^2.1.0" + whatwg-url "^7.0.0" + date-fns@^1.27.2: version "1.29.0" resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.29.0.tgz#12e609cdcb935127311d04d33334e2960a2a54e6" @@ -3779,7 +3721,7 @@ debug@2.6.0: dependencies: ms "0.7.2" -debug@2.6.9, debug@2.X, debug@^2.1.1, debug@^2.2.0, debug@^2.3.3, debug@^2.6.6, debug@^2.6.8, debug@^2.6.9: +debug@2.6.9, debug@2.X, debug@^2.1.1, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.6, debug@^2.6.8, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" dependencies: @@ -3871,19 +3813,19 @@ deep-equal@^1.0.0, deep-equal@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" -deep-extend@~0.4.0: - version "0.4.2" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f" +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" deep-is@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" -default-require-extensions@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-1.0.0.tgz#f37ea15d3e13ffd9b437d33e1a75b5fb97874cb8" +default-require-extensions@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-2.0.0.tgz#f5f8fbb18a7d6d50b21f641f649ebb522cfe24f7" dependencies: - strip-bom "^2.0.0" + strip-bom "^3.0.0" defaults@^1.0.3: version "1.0.3" @@ -3892,11 +3834,10 @@ defaults@^1.0.3: clone "^1.0.2" define-properties@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.2.tgz#83a73f2fea569898fb737193c8f873caf6d45c94" + version "1.1.3" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" dependencies: - foreach "^2.0.5" - object-keys "^1.0.8" + object-keys "^1.0.12" define-property@^0.2.5: version "0.2.5" @@ -3944,12 +3885,6 @@ del@^3.0.0: pify "^3.0.0" rimraf "^2.2.8" -delay@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/delay/-/delay-2.0.0.tgz#9112eadc03e4ec7e00297337896f273bbd91fae5" - dependencies: - p-defer "^1.0.0" - delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -3962,11 +3897,7 @@ delegates@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" -depd@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359" - -depd@~1.1.0, depd@~1.1.1: +depd@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" @@ -3977,6 +3908,10 @@ des.js@^1.0.0: inherits "^2.0.1" minimalistic-assert "^1.0.0" +detect-file@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7" + detect-indent@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208" @@ -4012,13 +3947,13 @@ diff@3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.2.0.tgz#c9ce393a4b7cbd0b058a725c93df299027868ff9" -diff@^3.1.0, diff@^3.2.0: +diff@^3.1.0, diff@^3.2.0, diff@^3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" diffie-hellman@^5.0.0: - version "5.0.2" - resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.2.tgz#b5835739270cfe26acf632099fded2a07f209e5e" + version "5.0.3" + resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" dependencies: bn.js "^4.1.0" miller-rabin "^4.0.0" @@ -4092,15 +4027,15 @@ domelementtype@~1.1.1: version "1.1.3" resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b" -domexception@^1.0.0: +domexception@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/domexception/-/domexception-1.0.1.tgz#937442644ca6a31261ef36e3ec677fe805582c90" dependencies: webidl-conversions "^4.0.2" domhandler@^2.3.0: - version "2.4.1" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.1.tgz#892e47000a99be55bbf3774ffea0561d8879c259" + version "2.4.2" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803" dependencies: domelementtype "1" @@ -4125,8 +4060,8 @@ dot-prop@^4.1.0: is-obj "^1.0.0" download-git-repo@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/download-git-repo/-/download-git-repo-1.0.2.tgz#0b93a62057e41e2f21b1a06c95e7b26362b108ff" + version "1.1.0" + resolved "https://registry.yarnpkg.com/download-git-repo/-/download-git-repo-1.1.0.tgz#7dc88a82ced064b1372a0002f8a3aebf10eb1d3c" dependencies: download "^5.0.3" git-clone "^0.1.0" @@ -4169,16 +4104,7 @@ duplexer@^0.1.1, duplexer@~0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" -duplexify@^3.2.0, duplexify@^3.5.3: - version "3.5.4" - resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.5.4.tgz#4bb46c1796eabebeec4ca9a2e66b808cb7a3d8b4" - dependencies: - end-of-stream "^1.0.0" - inherits "^2.0.1" - readable-stream "^2.0.0" - stream-shift "^1.0.0" - -duplexify@^3.4.2, duplexify@^3.6.0: +duplexify@^3.2.0, duplexify@^3.4.2, duplexify@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.6.0.tgz#592903f5d80b38d037220541264d69a198fb3410" dependencies: @@ -4199,10 +4125,11 @@ easing-js@^1.1.2: resolved "https://registry.yarnpkg.com/easing-js/-/easing-js-1.1.2.tgz#42077952bc3cd6e06aa6d336a9bf3c4eeced2594" ecc-jsbn@~0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505" + version "0.1.2" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" dependencies: jsbn "~0.1.0" + safer-buffer "^2.1.0" ecdsa-sig-formatter@1.0.10: version "1.0.10" @@ -4219,16 +4146,16 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" ejs@^2.2.4: - version "2.5.7" - resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.7.tgz#cc872c168880ae3c7189762fd5ffc00896c9518a" + version "2.6.1" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.6.1.tgz#498ec0d495655abc6f23cd61868d926464071aa0" elasticsearch-browser@^14.2.1: - version "14.2.1" - resolved "https://registry.yarnpkg.com/elasticsearch-browser/-/elasticsearch-browser-14.2.1.tgz#5d1a75339ec6caf2a56c7c912639ab9e83b29755" + version "14.2.2" + resolved "https://registry.yarnpkg.com/elasticsearch-browser/-/elasticsearch-browser-14.2.2.tgz#6d49b8263031d979503c11571740f68b5833497f" elasticsearch@^14.1.0, elasticsearch@^14.2.0, elasticsearch@^14.2.1: - version "14.2.1" - resolved "https://registry.yarnpkg.com/elasticsearch/-/elasticsearch-14.2.1.tgz#d10cb0b9562ca6614d178c30a112b93f6e8570d1" + version "14.2.2" + resolved "https://registry.yarnpkg.com/elasticsearch/-/elasticsearch-14.2.2.tgz#6bbb63b19b17fa97211b22eeacb0f91197f4d6b6" dependencies: agentkeepalive "^3.4.1" chalk "^1.0.0" @@ -4237,13 +4164,9 @@ elasticsearch@^14.1.0, elasticsearch@^14.2.0, elasticsearch@^14.2.1: lodash.isempty "^4.4.0" lodash.trimend "^4.5.1" -electron-to-chromium@^1.2.7: - version "1.3.39" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.39.tgz#d7a4696409ca0995e2750156da612c221afad84d" - -electron-to-chromium@^1.3.52: - version "1.3.56" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.56.tgz#aad1420d23e9dd8cd2fc2bc53f4928adcf85f02f" +electron-to-chromium@^1.2.7, electron-to-chromium@^1.3.57: + version "1.3.59" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.59.tgz#6377db04d8d3991d6286c72ed5c3fde6f4aaf112" elegant-spinner@^1.0.1: version "1.0.1" @@ -4256,8 +4179,8 @@ element-resize-detector@^1.1.12: batch-processor "^1.0.0" elliptic@^6.0.0: - version "6.4.0" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.0.tgz#cac9af8762c85836187003c8dfe193e5e2eae5df" + version "6.4.1" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.1.tgz#c2d0b7776911b86722c632c3c06c60f2f819939a" dependencies: bn.js "^4.4.0" brorand "^1.0.1" @@ -4360,24 +4283,25 @@ env-paths@^1.0.0: resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-1.0.0.tgz#4168133b42bb05c38a35b1ae4397c8298ab369e0" enzyme-adapter-react-16@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.1.1.tgz#a8f4278b47e082fbca14f5bfb1ee50ee650717b4" + version "1.2.0" + resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.2.0.tgz#c6e80f334e0a817873262d7d01ee9e4747e3c97e" dependencies: - enzyme-adapter-utils "^1.3.0" - lodash "^4.17.4" - object.assign "^4.0.4" + enzyme-adapter-utils "^1.5.0" + function.prototype.name "^1.1.0" + object.assign "^4.1.0" object.values "^1.0.4" - prop-types "^15.6.0" + prop-types "^15.6.2" + react-is "^16.4.2" react-reconciler "^0.7.0" react-test-renderer "^16.0.0-0" -enzyme-adapter-utils@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/enzyme-adapter-utils/-/enzyme-adapter-utils-1.3.0.tgz#d6c85756826c257a8544d362cc7a67e97ea698c7" +enzyme-adapter-utils@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/enzyme-adapter-utils/-/enzyme-adapter-utils-1.5.0.tgz#a020ab3ae79bb1c85e1d51f48f35e995e0eed810" dependencies: - lodash "^4.17.4" - object.assign "^4.0.4" - prop-types "^15.6.0" + function.prototype.name "^1.1.0" + object.assign "^4.1.0" + prop-types "^15.6.2" enzyme-to-json@3.3.1: version "3.3.1" @@ -4412,14 +4336,21 @@ errno@^0.1.1, errno@^0.1.3, errno@~0.1.7: prr "~1.0.1" error-ex@^1.2.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.1.tgz#f855a86ce61adc4e8621c3cda21e7a7612c3a8dc" + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" dependencies: is-arrayish "^0.2.1" +error@^7.0.0: + version "7.0.2" + resolved "https://registry.yarnpkg.com/error/-/error-7.0.2.tgz#a5f75fff4d9926126ddac0ea5dc38e689153cb02" + dependencies: + string-template "~0.2.1" + xtend "~4.0.0" + es-abstract@^1.5.1, es-abstract@^1.6.1, es-abstract@^1.7.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.10.0.tgz#1ecb36c197842a00d8ee4c2dfd8646bb97d60864" + version "1.12.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.12.0.tgz#9dbbdd27c6856f0001421ca18782d786bf8a6165" dependencies: es-to-primitive "^1.1.1" function-bind "^1.1.1" @@ -4436,8 +4367,8 @@ es-to-primitive@^1.1.1: is-symbol "^1.0.1" es5-ext@^0.10.14, es5-ext@^0.10.35, es5-ext@^0.10.9, es5-ext@~0.10.14: - version "0.10.41" - resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.41.tgz#bab3e982d750f0112f0cb9e6abed72c59eb33eb2" + version "0.10.46" + resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.46.tgz#efd99f67c5a7ec789baa3daa7f79870388f7f572" dependencies: es6-iterator "~2.0.3" es6-symbol "~3.1.1" @@ -4521,9 +4452,9 @@ escodegen@1.8.x: optionalDependencies: source-map "~0.2.0" -escodegen@^1.6.1, escodegen@^1.8.1, escodegen@^1.9.0, escodegen@~1.9.0: - version "1.9.1" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.9.1.tgz#dbae17ef96c8e4bedb1356f4504fa4cc2f7cb7e2" +escodegen@^1.6.1, escodegen@^1.8.1, escodegen@^1.9.1: + version "1.11.0" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.11.0.tgz#b27a9389481d5bfd5bec76f7bb1eb3f8f4556589" dependencies: esprima "^3.1.3" estraverse "^4.2.0" @@ -4542,6 +4473,17 @@ escodegen@~1.2.0: optionalDependencies: source-map "~0.1.30" +escodegen@~1.9.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.9.1.tgz#dbae17ef96c8e4bedb1356f4504fa4cc2f7cb7e2" + dependencies: + esprima "^3.1.3" + estraverse "^4.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + escope@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/escope/-/escope-3.6.0.tgz#e01975e812781a163a6dadfdd80398dc64c889c3" @@ -4552,8 +4494,8 @@ escope@^3.6.0: estraverse "^4.1.1" eslint-config-prettier@^2.9.0: - version "2.9.0" - resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-2.9.0.tgz#5ecd65174d486c22dff389fe036febf502d468a3" + version "2.10.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-2.10.0.tgz#ec07bc1d01f87d09f61d3840d112dc8a9791e30b" dependencies: get-stdin "^5.0.1" @@ -4581,8 +4523,8 @@ eslint-import-resolver-webpack@^0.8.1: semver "^5.3.0" eslint-module-utils@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.1.1.tgz#abaec824177613b8a95b299639e1b6facf473449" + version "2.2.0" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.2.0.tgz#b270362cd88b1a48ad308976ce7fa54e98411746" dependencies: debug "^2.6.8" pkg-dir "^1.0.0" @@ -4607,8 +4549,8 @@ eslint-plugin-import@2.8.0: read-pkg-up "^2.0.0" eslint-plugin-jest@^21.6.2: - version "21.15.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-21.15.0.tgz#645a3f560d3e61d99611b215adc80b4f31e6d896" + version "21.21.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-21.21.0.tgz#f0afd138c4acb5f0cd7698318fb49c7d49f3bf45" eslint-plugin-mocha@4.11.0: version "4.11.0" @@ -4625,8 +4567,8 @@ eslint-plugin-prefer-object-spread@1.2.1: resolved "https://registry.yarnpkg.com/eslint-plugin-prefer-object-spread/-/eslint-plugin-prefer-object-spread-1.2.1.tgz#27fb91853690cceb3ae6101d9c8aecc6a67a402c" eslint-plugin-prettier@^2.2.0, eslint-plugin-prettier@^2.6.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-2.6.0.tgz#33e4e228bdb06142d03c560ce04ec23f6c767dd7" + version "2.6.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-2.6.2.tgz#71998c60aedfa2141f7bfcbf9d1c459bf98b4fad" dependencies: fast-diff "^1.1.1" jest-docblock "^21.0.0" @@ -4641,8 +4583,8 @@ eslint-plugin-react@7.5.1: prop-types "^15.6.0" eslint-scope@^3.7.1, eslint-scope@~3.7.1: - version "3.7.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.1.tgz#3d63c3edfda02e06e01a452ad88caacc7cdcb6e8" + version "3.7.3" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.3.tgz#bb507200d3d17f60247636160b4826284b108535" dependencies: esrecurse "^4.1.0" estraverse "^4.1.1" @@ -4709,8 +4651,8 @@ esprima@^3.1.3: resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" esprima@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804" + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" esprima@~1.0.4: version "1.0.4" @@ -4721,8 +4663,8 @@ esprima@~2.2.0: resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.2.0.tgz#4292c1d68e4173d815fa2290dc7afc96d81fcd83" esquery@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.0.tgz#cfba8b57d7fba93f17298a8a006a04cda13d80fa" + version "1.0.1" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.1.tgz#406c51658b1f5991a5f9b62b1dc25b00e3e5c708" dependencies: estraverse "^4.0.0" @@ -4785,10 +4727,6 @@ eventemitter2@~0.4.13: version "0.4.14" resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-0.4.14.tgz#8f61b75cde012b2e9eb284d4545583b5643b61ab" -eventemitter3@1.x.x: - version "1.2.0" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-1.2.0.tgz#1c86991d816ad1e504750e73874224ecf3bec508" - eventemitter3@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.0.tgz#090b4d6cdbd645ed10bf750d4b5407942d7ba163" @@ -4805,10 +4743,10 @@ evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: safe-buffer "^5.1.1" exec-sh@^0.2.0: - version "0.2.1" - resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.2.1.tgz#163b98a6e89e6b65b47c2a28d215bc1f63989c38" + version "0.2.2" + resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.2.2.tgz#2a5e7ffcbd7d0ba2755bdecb16e5a427dfbdec36" dependencies: - merge "^1.1.3" + merge "^1.2.0" execa@^0.10.0: version "0.10.0" @@ -4897,11 +4835,17 @@ expand-range@^1.8.1: dependencies: fill-range "^2.1.0" +expand-tilde@^2.0.0, expand-tilde@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502" + dependencies: + homedir-polyfill "^1.0.1" + expect.js@0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/expect.js/-/expect.js-0.3.1.tgz#b0a59a0d2eff5437544ebf0ceaa6015841d09b5b" -expect@^22.4.3: +expect@^22.4.0: version "22.4.3" resolved "https://registry.yarnpkg.com/expect/-/expect-22.4.3.tgz#d5a29d0a0e1fb2153557caef2674d4547e914674" dependencies: @@ -4929,21 +4873,21 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2: assign-symbols "^1.0.0" is-extendable "^1.0.1" -extend@^3.0.0, extend@~3.0.0, extend@~3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" +extend@^3.0.0, extend@~3.0.1, extend@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" external-editor@^2.0.4: - version "2.1.0" - resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.1.0.tgz#3d026a21b7f95b5726387d4200ac160d372c3b48" + version "2.2.0" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.2.0.tgz#045511cfd8d133f3846673d1047c154e214ad3d5" dependencies: chardet "^0.4.0" iconv-lite "^0.4.17" tmp "^0.0.33" external-editor@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.0.0.tgz#dc35c48c6f98a30ca27a20e9687d7f3c77704bb6" + version "3.0.1" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.0.1.tgz#fc9638c4d7cde4f0bb82b12307a1a23912c492e3" dependencies: chardet "^0.5.0" iconv-lite "^0.4.22" @@ -5037,14 +4981,15 @@ fast-diff@^1.1.1: resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.1.2.tgz#4b62c42b8e03de3f848460b639079920695d0154" fast-glob@^2.0.2: - version "2.2.0" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-2.2.0.tgz#e9d032a69b86bef46fc03d935408f02fb211d9fc" + version "2.2.2" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-2.2.2.tgz#71723338ac9b4e0e2fff1d6748a2a13d5ed352bf" dependencies: "@mrmlnc/readdir-enhanced" "^2.2.1" + "@nodelib/fs.stat" "^1.0.1" glob-parent "^3.1.0" is-glob "^4.0.0" merge2 "^1.2.1" - micromatch "^3.1.8" + micromatch "^3.1.10" fast-json-stable-stringify@^2.0.0: version "2.0.0" @@ -5058,6 +5003,12 @@ fastparse@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.1.tgz#d1e2643b38a94d7583b479060e6c4affc94071f8" +fault@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/fault/-/fault-1.0.2.tgz#c3d0fec202f172a3a4d414042ad2bb5e2a3ffbaa" + dependencies: + format "^0.2.2" + faye-websocket@~0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4" @@ -5071,8 +5022,8 @@ fb-watchman@^2.0.0: bser "^2.0.0" fbjs@^0.8.16, fbjs@^0.8.4, fbjs@^0.8.5, fbjs@^0.8.9: - version "0.8.16" - resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.16.tgz#5e67432f550dc41b572bf55847b8aca64e5337db" + version "0.8.17" + resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd" dependencies: core-js "^1.0.0" isomorphic-fetch "^2.1.1" @@ -5080,7 +5031,7 @@ fbjs@^0.8.16, fbjs@^0.8.4, fbjs@^0.8.5, fbjs@^0.8.9: object-assign "^4.1.0" promise "^7.1.1" setimmediate "^1.0.5" - ua-parser-js "^0.7.9" + ua-parser-js "^0.7.18" fd-slicer@~1.0.1: version "1.0.1" @@ -5150,8 +5101,8 @@ filename-reserved-regex@^2.0.0: resolved "https://registry.yarnpkg.com/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz#abf73dfab735d045440abfea2d91f389ebbfa229" filenamify@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/filenamify/-/filenamify-2.0.0.tgz#bd162262c0b6e94bfbcdcf19a3bbb3764f785695" + version "2.1.0" + resolved "https://registry.yarnpkg.com/filenamify/-/filenamify-2.1.0.tgz#88faf495fb1b47abfd612300002a16228c677ee9" dependencies: filename-reserved-regex "^2.0.0" strip-outer "^1.0.0" @@ -5172,12 +5123,12 @@ fill-keys@^1.0.2: merge-descriptors "~1.0.0" fill-range@^2.1.0: - version "2.2.3" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.3.tgz#50b77dfd7e469bc7492470963699fe7a8485a723" + version "2.2.4" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.4.tgz#eb1e773abb056dcd8df2bfdf6af59b8b3a936565" dependencies: is-number "^2.1.0" isobject "^2.0.0" - randomatic "^1.1.3" + randomatic "^3.0.0" repeat-element "^1.1.2" repeat-string "^1.5.2" @@ -5237,16 +5188,39 @@ find-up@^3.0.0: dependencies: locate-path "^3.0.0" +findup-sync@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-2.0.0.tgz#9326b1488c22d1a6088650a86901b2d9a90a2cbc" + dependencies: + detect-file "^1.0.0" + is-glob "^3.1.0" + micromatch "^3.0.4" + resolve-dir "^1.0.1" + findup-sync@~0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-0.3.0.tgz#37930aa5d816b777c03445e1966cc6790a4c0b16" dependencies: glob "~5.0.0" +fined@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fined/-/fined-1.1.0.tgz#b37dc844b76a2f5e7081e884f7c0ae344f153476" + dependencies: + expand-tilde "^2.0.2" + is-plain-object "^2.0.3" + object.defaults "^1.1.0" + object.pick "^1.2.0" + parse-filepath "^1.0.1" + finity@^0.5.4: version "0.5.4" resolved "https://registry.yarnpkg.com/finity/-/finity-0.5.4.tgz#f2a8a9198e8286467328ec32c8bfcc19a2229c11" +flagged-respawn@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/flagged-respawn/-/flagged-respawn-1.0.0.tgz#4e79ae9b2eb38bf86b3bb56bf3e0a56aa5fcabd7" + flat-cache@^1.2.1: version "1.3.0" resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.3.0.tgz#d3030b32b38154f4e3b7e9c709f490f7ef97c481" @@ -5260,35 +5234,28 @@ flatten@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782" -flush-write-stream@^1.0.0: +flush-write-stream@^1.0.0, flush-write-stream@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.0.3.tgz#c5d586ef38af6097650b49bc41b55fabb19f35bd" dependencies: inherits "^2.0.1" readable-stream "^2.0.4" -flush-write-stream@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.0.2.tgz#c81b90d8746766f1a609a46809946c45dd8ae417" - dependencies: - inherits "^2.0.1" - readable-stream "^2.0.4" - focus-trap-react@^3.0.4, focus-trap-react@^3.1.1: - version "3.1.2" - resolved "https://registry.yarnpkg.com/focus-trap-react/-/focus-trap-react-3.1.2.tgz#4dd021ccd028bbd3321147d132cdf7585d6d1394" + version "3.1.4" + resolved "https://registry.yarnpkg.com/focus-trap-react/-/focus-trap-react-3.1.4.tgz#e95f4aece5c493be4d3653dfccd5036d11ad24d5" dependencies: focus-trap "^2.0.1" focus-trap@^2.0.1: - version "2.4.3" - resolved "https://registry.yarnpkg.com/focus-trap/-/focus-trap-2.4.3.tgz#95edc23e77829b7772cb2486d61fd6371ce112f9" + version "2.4.6" + resolved "https://registry.yarnpkg.com/focus-trap/-/focus-trap-2.4.6.tgz#332b475b317cec6a4a129f5307ce7ebc0da90b40" dependencies: tabbable "^1.0.3" -follow-redirects@^1.3.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.0.tgz#234f49cf770b7f35b40e790f636ceba0c3a0ab77" +follow-redirects@^1.0.0, follow-redirects@^1.3.0: + version "1.5.6" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.6.tgz#44eb4fe1981dff25e2bd86b7d4033abcdb81e965" dependencies: debug "^3.1.0" @@ -5313,10 +5280,10 @@ fontkit@^1.0.0: unicode-trie "^0.3.0" for-each@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.2.tgz#2c40450b9348e97f281322593ba96704b9abd4d4" + version "0.3.3" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" dependencies: - is-function "~1.0.0" + is-callable "^1.1.3" for-in@^1.0.1, for-in@^1.0.2: version "1.0.2" @@ -5328,6 +5295,12 @@ for-own@^0.1.4: dependencies: for-in "^1.0.1" +for-own@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/for-own/-/for-own-1.0.0.tgz#c63332f415cedc4b04dbfe70cf836494c53cb44b" + dependencies: + for-in "^1.0.1" + foreach@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" @@ -5336,7 +5309,11 @@ forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" -form-data@^2.3.1, form-data@~2.3.1: +form-data-to-object@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/form-data-to-object/-/form-data-to-object-0.2.0.tgz#f7a8e68ddd910a1100a65e25ac6a484143ff8168" + +form-data@^2.3.1, form-data@~2.3.1, form-data@~2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.2.tgz#4970498be604c20c005d4f5c23aecd21d6b49099" dependencies: @@ -5344,18 +5321,21 @@ form-data@^2.3.1, form-data@~2.3.1: combined-stream "1.0.6" mime-types "^2.1.12" -form-data@~2.1.1: - version "2.1.4" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.4.tgz#33c183acf193276ecaa98143a69e94bfee1750d1" - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.5" - mime-types "^2.1.12" +format@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b" -formidable@^1.1.1: +formidable@^1.2.0: version "1.2.1" resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.2.1.tgz#70fb7ca0290ee6ff961090415f4b3df3d2082659" +formsy-react@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/formsy-react/-/formsy-react-1.1.4.tgz#687525cbcaa6f6a6000e0c8029076fb5ac8dac3e" + dependencies: + form-data-to-object "^0.2.0" + prop-types "^15.5.10" + fragment-cache@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" @@ -5379,6 +5359,10 @@ fs-access@^1.0.0: dependencies: null-check "^1.0.0" +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + fs-exists-sync@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz#982d6893af918e72d08dec9e8673ff2b5a8d6add" @@ -5407,6 +5391,12 @@ fs-extra@^4.0.1: jsonfile "^4.0.0" universalify "^0.1.0" +fs-minipass@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d" + dependencies: + minipass "^2.2.1" + fs-mkdirp-stream@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz#0b7815fc3201c6a69e14db98ce098c16935259eb" @@ -5427,22 +5417,14 @@ fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" -fsevents@^1.0.0, fsevents@^1.1.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.1.3.tgz#11f82318f5fe7bb2cd22965a108e9306208216d8" - dependencies: - nan "^2.3.0" - node-pre-gyp "^0.6.39" - -fstream-ignore@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/fstream-ignore/-/fstream-ignore-1.0.5.tgz#9c31dae34767018fe1d249b24dada67d092da105" +fsevents@^1.0.0, fsevents@^1.2.2, fsevents@^1.2.3: + version "1.2.4" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.4.tgz#f41dcb1af2582af3692da36fc55cbd8e1041c426" dependencies: - fstream "^1.0.0" - inherits "2" - minimatch "^3.0.0" + nan "^2.9.2" + node-pre-gyp "^0.10.0" -fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2: +fstream@^1.0.0, fstream@^1.0.2: version "1.0.11" resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171" dependencies: @@ -5451,11 +5433,11 @@ fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2: mkdirp ">=0.5 0" rimraf "2" -function-bind@^1.0.2, function-bind@^1.1.0, function-bind@^1.1.1: +function-bind@^1.1.0, function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" -function.prototype.name@^1.0.3: +function.prototype.name@^1.0.3, function.prototype.name@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.0.tgz#8bd763cc0af860a859cc5d49384d74b932cd2327" dependencies: @@ -5480,9 +5462,9 @@ gauge@~2.7.3: strip-ansi "^3.0.1" wide-align "^1.1.0" -gaze@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/gaze/-/gaze-1.1.2.tgz#847224677adb8870d679257ed3388fdb61e40105" +gaze@^1.0.0, gaze@^1.1.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/gaze/-/gaze-1.1.3.tgz#c441733e13b927ac8c0ff0b4c3b033f28812924a" dependencies: globule "^1.0.0" @@ -5495,19 +5477,9 @@ geckodriver@1.11.0: got "5.6.0" tar "4.0.2" -generate-function@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.0.0.tgz#6858fe7c0969b7d4e9093337647ac79f60dfbe74" - -generate-object-property@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/generate-object-property/-/generate-object-property-1.2.0.tgz#9c0e1c40308ce804f4783618b937fa88f99d50d0" - dependencies: - is-property "^1.0.0" - get-caller-file@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5" + version "1.0.3" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" get-port@2.1.0: version "2.1.0" @@ -5552,13 +5524,9 @@ getopts@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/getopts/-/getopts-2.0.0.tgz#e9119f3e79d22d0685b77fbe78d5cd6e19ca1af0" -getopts@^2.0.0: - version "2.0.5" - resolved "https://registry.yarnpkg.com/getopts/-/getopts-2.0.5.tgz#e4d3948e87fd9fb50c8a0f2912f4de16301fb8ae" - -getopts@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/getopts/-/getopts-2.0.6.tgz#4788d533a977527e79efd57b5e742ffa0dd33105" +getopts@^2.0.0, getopts@^2.0.6: + version "2.2.0" + resolved "https://registry.yarnpkg.com/getopts/-/getopts-2.2.0.tgz#22cfc88e666b00a15342c87188f022b68a07f2c3" getos@^3.1.0: version "3.1.0" @@ -5695,6 +5663,24 @@ global-dirs@^0.1.0: dependencies: ini "^1.3.4" +global-modules@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea" + dependencies: + global-prefix "^1.0.1" + is-windows "^1.0.1" + resolve-dir "^1.0.0" + +global-prefix@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe" + dependencies: + expand-tilde "^2.0.2" + homedir-polyfill "^1.0.1" + ini "^1.3.4" + is-windows "^1.0.1" + which "^1.2.14" + global@^4.3.1, global@~4.3.0: version "4.3.2" resolved "https://registry.yarnpkg.com/global/-/global-4.3.2.tgz#e76989268a6c74c38908b1305b10fc0e394e9d0f" @@ -5707,8 +5693,8 @@ globals@^10.0.0: resolved "https://registry.yarnpkg.com/globals/-/globals-10.4.0.tgz#5c477388b128a9e4c5c5d01c7a2aca68c68b2da7" globals@^11.0.1: - version "11.3.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.3.0.tgz#e04fdb7b9796d8adac9c8f64c14837b2313378b0" + version "11.7.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.7.0.tgz#a583faa43055b1aca771914bf68258e2fc125673" globals@^9.18.0: version "9.18.0" @@ -5748,11 +5734,11 @@ globby@^8.0.1: slash "^1.0.0" globule@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/globule/-/globule-1.2.0.tgz#1dc49c6822dd9e8a2fa00ba2a295006e8664bd09" + version "1.2.1" + resolved "https://registry.yarnpkg.com/globule/-/globule-1.2.1.tgz#5dffb1b191f22d20797a9369b49eab4e9839696d" dependencies: glob "~7.1.1" - lodash "~4.17.4" + lodash "~4.17.10" minimatch "~3.0.2" gm@~1.21.1: @@ -5829,8 +5815,8 @@ got@^6.3.0, got@^6.7.1: url-parse-lax "^1.0.0" got@^8.0.3: - version "8.3.1" - resolved "https://registry.yarnpkg.com/got/-/got-8.3.1.tgz#093324403d4d955f5a16a7a8d39955d055ae10ed" + version "8.3.2" + resolved "https://registry.yarnpkg.com/got/-/got-8.3.2.tgz#1d23f64390e97f776cac52e5b936e5f514d2e937" dependencies: "@sindresorhus/is" "^0.7.0" cacheable-request "^2.1.1" @@ -5866,7 +5852,17 @@ growly@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" -grunt-cli@^1.2.0, grunt-cli@~1.2.0: +grunt-cli@^1.2.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/grunt-cli/-/grunt-cli-1.3.1.tgz#b9cb5b7a7200e490711e1ee7cb049c9a815471f0" + dependencies: + grunt-known-options "~1.1.0" + interpret "~1.1.0" + liftoff "~2.5.0" + nopt "~4.0.1" + v8flags "~3.0.2" + +grunt-cli@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/grunt-cli/-/grunt-cli-1.2.0.tgz#562b119ebb069ddb464ace2845501be97b35b6a8" dependencies: @@ -5876,13 +5872,13 @@ grunt-cli@^1.2.0, grunt-cli@~1.2.0: resolve "~1.1.0" grunt-contrib-watch@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/grunt-contrib-watch/-/grunt-contrib-watch-1.0.0.tgz#84a1a7a1d6abd26ed568413496c73133e990018f" + version "1.1.0" + resolved "https://registry.yarnpkg.com/grunt-contrib-watch/-/grunt-contrib-watch-1.1.0.tgz#c143ca5b824b288a024b856639a5345aedb78ed4" dependencies: - async "^1.5.0" - gaze "^1.0.0" - lodash "^3.10.1" - tiny-lr "^0.2.1" + async "^2.6.0" + gaze "^1.1.0" + lodash "^4.17.10" + tiny-lr "^1.1.1" grunt-karma@2.0.0: version "2.0.0" @@ -5891,8 +5887,8 @@ grunt-karma@2.0.0: lodash "^3.10.1" grunt-known-options@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/grunt-known-options/-/grunt-known-options-1.1.0.tgz#a4274eeb32fa765da5a7a3b1712617ce3b144149" + version "1.1.1" + resolved "https://registry.yarnpkg.com/grunt-known-options/-/grunt-known-options-1.1.1.tgz#6cc088107bd0219dc5d3e57d91923f469059804d" grunt-legacy-log-utils@~1.0.0: version "1.0.0" @@ -5902,14 +5898,13 @@ grunt-legacy-log-utils@~1.0.0: lodash "~4.3.0" grunt-legacy-log@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/grunt-legacy-log/-/grunt-legacy-log-1.0.1.tgz#c7731b2745f4732aa9950ee4d7ae63c553f68469" + version "1.0.2" + resolved "https://registry.yarnpkg.com/grunt-legacy-log/-/grunt-legacy-log-1.0.2.tgz#7d7440426ace77b206e74f993e332e2a15a3904e" dependencies: colors "~1.1.2" grunt-legacy-log-utils "~1.0.0" hooker "~0.2.3" lodash "~4.17.5" - underscore.string "~3.3.4" grunt-legacy-util@~1.0.0: version "1.0.0" @@ -6073,30 +6068,10 @@ hapi@14.2.0: subtext "4.x.x" topo "2.x.x" -har-schema@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e" - har-schema@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" -har-validator@~2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-2.0.6.tgz#cdcbc08188265ad119b6a5a7c8ab70eecfb5d27d" - dependencies: - chalk "^1.1.1" - commander "^2.9.0" - is-my-json-valid "^2.12.4" - pinkie-promise "^2.0.0" - -har-validator@~4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a" - dependencies: - ajv "^4.9.1" - har-schema "^1.0.5" - har-validator@~5.0.3: version "5.0.3" resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.0.3.tgz#ba402c266194f15956ef15e0fcf242993f6a7dfd" @@ -6104,6 +6079,13 @@ har-validator@~5.0.3: ajv "^5.1.0" har-schema "^2.0.0" +har-validator@~5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.0.tgz#44657f5688a22cfd4b72486e81b3a3fb11742c29" + dependencies: + ajv "^5.3.0" + har-schema "^2.0.0" + has-ansi@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-0.1.0.tgz#84f265aae8c0e6a88a12d7022894b7568894c62e" @@ -6190,16 +6172,10 @@ has-values@^1.0.0: kind-of "^4.0.0" has@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.1.tgz#8461733f538b0837c9361e39a9ab9e9704dc2f28" - dependencies: - function-bind "^1.0.2" - -hash-base@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-2.0.2.tgz#66ea1d856db4e8a5470cadf6fce23ae5244ef2e1" + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" dependencies: - inherits "^2.0.1" + function-bind "^1.1.1" hash-base@^3.0.0: version "3.0.4" @@ -6209,36 +6185,18 @@ hash-base@^3.0.0: safe-buffer "^5.0.1" hash.js@^1.0.0, hash.js@^1.0.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.3.tgz#340dedbe6290187151c1ea1d777a3448935df846" + version "1.1.5" + resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.5.tgz#e38ab4b85dfb1e0c40fe9265c0e9b54854c23812" dependencies: inherits "^2.0.3" - minimalistic-assert "^1.0.0" + minimalistic-assert "^1.0.1" -hasharray@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/hasharray/-/hasharray-1.1.0.tgz#fe87cf9977baa9d9159b8465a8e2edf58fd0d681" +hasharray@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/hasharray/-/hasharray-1.1.1.tgz#0bfadd91c0ee76919c05eeef5a3dc1034b4ea4f1" dependencies: jclass "^1.0.1" -hawk@3.1.3, hawk@~3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4" - dependencies: - boom "2.x.x" - cryptiles "2.x.x" - hoek "2.x.x" - sntp "1.x.x" - -hawk@~6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/hawk/-/hawk-6.0.2.tgz#af4d914eb065f9b5ce4d9d11c1cb2126eecc3038" - dependencies: - boom "4.x.x" - cryptiles "3.x.x" - hoek "4.x.x" - sntp "2.x.x" - he@^0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/he/-/he-0.5.0.tgz#2c05ffaef90b68e860f3fd2b54ef580989277ee2" @@ -6298,12 +6256,12 @@ hoek@4.2.1, hoek@4.X.X, hoek@4.x.x: resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.1.tgz#9634502aa12c445dd5a7c5734b572bb8738aacbb" hoek@5.x.x: - version "5.0.3" - resolved "https://registry.yarnpkg.com/hoek/-/hoek-5.0.3.tgz#b71d40d943d0a95da01956b547f83c4a5b4a34ac" + version "5.0.4" + resolved "https://registry.yarnpkg.com/hoek/-/hoek-5.0.4.tgz#0f7fa270a1cafeb364a4b2ddfaa33f864e4157da" -hoist-non-react-statics@^2.3.0, hoist-non-react-statics@^2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.0.tgz#d2ca2dfc19c5a91c5a6615ce8e564ef0347e2a40" +hoist-non-react-statics@^2.5.0: + version "2.5.5" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47" home-or-tmp@^2.0.0: version "2.0.0" @@ -6312,7 +6270,7 @@ home-or-tmp@^2.0.0: os-homedir "^1.0.0" os-tmpdir "^1.0.1" -homedir-polyfill@^1.0.0: +homedir-polyfill@^1.0.0, homedir-polyfill@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz#4c2bbc8a758998feebf5ed68580f76d46768b4bc" dependencies: @@ -6323,8 +6281,8 @@ hooker@~0.2.3: resolved "https://registry.yarnpkg.com/hooker/-/hooker-0.2.3.tgz#b834f723cc4a242aa65963459df6d984c5d3d959" hosted-git-info@^2.1.4: - version "2.6.0" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.6.0.tgz#23235b29ab230c576aab0d4f13fc046b0b038222" + version "2.7.1" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.7.1.tgz#97f236977bd6e125408930ff6de3eec6281ec047" html-comment-regex@^1.1.0: version "1.1.1" @@ -6357,25 +6315,18 @@ http-cache-semantics@3.8.1: version "3.8.1" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2" -http-errors@1.6.2, http-errors@~1.6.2: - version "1.6.2" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736" +http-errors@1.6.3, http-errors@~1.6.3: + version "1.6.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" dependencies: - depd "1.1.1" + depd "~1.1.2" inherits "2.0.3" - setprototypeof "1.0.3" - statuses ">= 1.3.1 < 2" - -http-errors@~1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.3.1.tgz#197e22cdebd4198585e8694ef6786197b91ed942" - dependencies: - inherits "~2.0.1" - statuses "1" + setprototypeof "1.1.0" + statuses ">= 1.4.0 < 2" http-parser-js@>=0.4.0: - version "0.4.9" - resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.4.9.tgz#ea1a04fb64adff0242e9974f297dd4c3cad271e1" + version "0.4.13" + resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.4.13.tgz#3bd6d6fde6e3172c9334c3b33b6c193d80fe1137" http-proxy-agent@^2.1.0: version "2.1.0" @@ -6385,19 +6336,12 @@ http-proxy-agent@^2.1.0: debug "3.1.0" http-proxy@^1.13.0: - version "1.16.2" - resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.16.2.tgz#06dff292952bf64dbe8471fa9df73066d4f37742" - dependencies: - eventemitter3 "1.x.x" - requires-port "1.x.x" - -http-signature@~1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" + version "1.17.0" + resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.17.0.tgz#7ad38494658f84605e2f6db4436df410f4e5be9a" dependencies: - assert-plus "^0.2.0" - jsprim "^1.2.2" - sshpk "^1.7.0" + eventemitter3 "^3.0.0" + follow-redirects "^1.0.0" + requires-port "^1.0.0" http-signature@~1.2.0: version "1.2.0" @@ -6436,15 +6380,7 @@ icalendar@0.7.1: version "0.7.1" resolved "https://registry.yarnpkg.com/icalendar/-/icalendar-0.7.1.tgz#d0d3486795f8f1c5cf4f8cafac081b4b4e7a32ae" -iconv-lite@0.4, iconv-lite@0.4.19, iconv-lite@^0.4.13, iconv-lite@^0.4.17, iconv-lite@^0.4.19, iconv-lite@~0.4.13: - version "0.4.19" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" - -iconv-lite@0.4.13: - version "0.4.13" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.13.tgz#1f88aba4ab0b1508e8312acc39345f36e992e2f2" - -iconv-lite@^0.4.22: +iconv-lite@0.4, iconv-lite@0.4.23, iconv-lite@^0.4.13, iconv-lite@^0.4.17, iconv-lite@^0.4.19, iconv-lite@^0.4.22, iconv-lite@^0.4.4, iconv-lite@~0.4.13: version "0.4.23" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63" dependencies: @@ -6461,16 +6397,22 @@ icss-utils@^2.1.0: postcss "^6.0.1" ieee754@^1.1.4: - version "1.1.10" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.10.tgz#719a6f7b026831e64bdb838b0de1bb0029bbf716" + version "1.1.12" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b" iferr@^0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" +ignore-walk@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.1.tgz#a83e62e7d272ac0e3b551aaa82831a19b69f82f8" + dependencies: + minimatch "^3.0.4" + ignore@^3.3.3, ignore@^3.3.5: - version "3.3.7" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.7.tgz#612289bfb3c220e186a58118618d5be8c1bab021" + version "3.3.10" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043" image-diff@1.6.0: version "1.6.0" @@ -6488,8 +6430,8 @@ image-size@~0.5.0: resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.5.5.tgz#09dfd4ab9d20e29eb1c3e80b8990378df9e3cb9c" immutability-helper@^2.0.0: - version "2.6.6" - resolved "https://registry.yarnpkg.com/immutability-helper/-/immutability-helper-2.6.6.tgz#9b384c240d65257133c155086e16f678ca563b05" + version "2.7.1" + resolved "https://registry.yarnpkg.com/immutability-helper/-/immutability-helper-2.7.1.tgz#5636dbb593e3deb5e572766d42249ea06bae7640" dependencies: invariant "^2.2.0" @@ -6606,8 +6548,8 @@ inquirer@^3.0.6, inquirer@^3.2.3: through "^2.3.6" inquirer@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.0.0.tgz#e8c20303ddc15bbfc2c12a6213710ccd9e1413d8" + version "6.1.0" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.1.0.tgz#8f65c7b31c498285f4ddf3b742ad8c487892040b" dependencies: ansi-escapes "^3.0.0" chalk "^2.0.0" @@ -6630,7 +6572,7 @@ insane@2.5.0: assignment "^2.0.0" he "^0.5.0" -interpret@^1.0.0: +interpret@^1.0.0, interpret@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614" @@ -6661,7 +6603,7 @@ into-stream@^3.1.0: from2 "^2.1.1" p-is-promise "^1.1.0" -invariant@^2.0.0, invariant@^2.1.1, invariant@^2.2.0, invariant@^2.2.1, invariant@^2.2.2: +invariant@^2.0.0, invariant@^2.1.1, invariant@^2.2.0, invariant@^2.2.1, invariant@^2.2.2, invariant@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" dependencies: @@ -6722,12 +6664,12 @@ is-accessor-descriptor@^1.0.0: kind-of "^6.0.0" is-alphabetical@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-1.0.1.tgz#c77079cc91d4efac775be1034bf2d243f95e6f08" + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-1.0.2.tgz#1fa6e49213cb7885b75d15862fb3f3d96c884f41" is-alphanumerical@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-1.0.1.tgz#dfb4aa4d1085e33bdb61c2dee9c80e9c6c19f53b" + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-1.0.2.tgz#1138e9ae5040158dc6ff76b820acd6b7a181fd40" dependencies: is-alphabetical "^1.0.0" is-decimal "^1.0.0" @@ -6737,8 +6679,8 @@ is-arrayish@^0.2.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" is-arrayish@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.1.tgz#c2dfc386abaa0c3e33c48db3fe87059e69065efd" + version "0.3.2" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" is-binary-path@^1.0.0: version "1.0.1" @@ -6763,14 +6705,14 @@ is-builtin-module@^1.0.0: builtin-modules "^1.0.0" is-callable@^1.1.1, is-callable@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.3.tgz#86eb75392805ddc33af71c92a0eedf74ee7604b2" + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75" is-ci@^1.0.10: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.1.0.tgz#247e4162e7860cebbdaf30b774d6b0ac7dcfe7a5" + version "1.2.0" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.2.0.tgz#3f4a08d6303a09882cef3f0fb97439c5f5ce2d53" dependencies: - ci-info "^1.0.0" + ci-info "^1.3.0" is-data-descriptor@^0.1.4: version "0.1.4" @@ -6789,8 +6731,8 @@ is-date-object@^1.0.1: resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16" is-decimal@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-1.0.1.tgz#f5fb6a94996ad9e8e3761fbfbd091f1fca8c4e82" + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-1.0.2.tgz#894662d6a8709d307f3a276ca4339c8fa5dff0ff" is-descriptor@^0.1.0: version "0.1.6" @@ -6863,7 +6805,7 @@ is-fullwidth-code-point@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" -is-function@^1.0.1, is-function@~1.0.0: +is-function@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-function/-/is-function-1.0.1.tgz#12cfb98b65b57dd3d193a3121f5f6e2f437602b5" @@ -6890,8 +6832,8 @@ is-glob@^4.0.0: is-extglob "^2.1.1" is-hexadecimal@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.1.tgz#6e084bbc92061fbb0971ec58b6ce6d404e24da69" + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.2.tgz#b6e710d7d07bb66b98cb8cece5c9b4921deeb835" is-installed-globally@^0.1.0: version "0.1.0" @@ -6900,20 +6842,6 @@ is-installed-globally@^0.1.0: global-dirs "^0.1.0" is-path-inside "^1.0.0" -is-my-ip-valid@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz#7b351b8e8edd4d3995d4d066680e664d94696824" - -is-my-json-valid@^2.12.4: - version "2.17.2" - resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.17.2.tgz#6b2103a288e94ef3de5cf15d29dd85fc4b78d65c" - dependencies: - generate-function "^2.0.0" - generate-object-property "^1.1.0" - is-my-ip-valid "^1.0.0" - jsonpointer "^4.0.0" - xtend "^4.0.0" - is-natural-number@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8" @@ -6960,19 +6888,13 @@ is-observable@^1.1.0: dependencies: symbol-observable "^1.1.0" -is-odd@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-odd/-/is-odd-2.0.0.tgz#7646624671fd7ea558ccd9a2795182f2958f1b24" - dependencies: - is-number "^4.0.0" - is-path-cwd@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d" is-path-in-cwd@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz#6477582b8214d602346094567003be8a9eac04dc" + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz#5ac48b345ef675339bd6c7a48a912110b241cf52" dependencies: is-path-inside "^1.0.0" @@ -7004,10 +6926,6 @@ is-promise@^2.0.0, is-promise@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" -is-property@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" - is-redirect@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24" @@ -7081,8 +6999,8 @@ is-valid-glob@^1.0.0: resolved "https://registry.yarnpkg.com/is-valid-glob/-/is-valid-glob-1.0.0.tgz#29bf3eff701be2d4d315dbacc39bc39fe8f601aa" is-whitespace-character@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-whitespace-character/-/is-whitespace-character-1.0.1.tgz#9ae0176f3282b65457a1992cdb084f8a5f833e3b" + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-whitespace-character/-/is-whitespace-character-1.0.2.tgz#ede53b4c6f6fb3874533751ec9280d01928d03ed" is-windows@^0.2.0: version "0.2.0" @@ -7093,8 +7011,8 @@ is-windows@^1.0.1, is-windows@^1.0.2: resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" is-word-character@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-word-character/-/is-word-character-1.0.1.tgz#5a03fa1ea91ace8a6eb0c7cd770eb86d65c8befb" + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-word-character/-/is-word-character-1.0.2.tgz#46a5dac3f2a1840898b91e576cd40d493f3ae553" isarray@0.0.1: version "0.0.1" @@ -7105,8 +7023,10 @@ isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" isbinaryfile@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-3.0.2.tgz#4a3e974ec0cba9004d3fc6cde7209ea69368a621" + version "3.0.3" + resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-3.0.3.tgz#5d6def3edebf6e8ca8cae9c30183a804b5f8be80" + dependencies: + buffer-alloc "^1.2.0" isemail@1.x.x: version "1.2.0" @@ -7117,8 +7037,8 @@ isemail@2.x.x: resolved "https://registry.yarnpkg.com/isemail/-/isemail-2.2.1.tgz#0353d3d9a62951080c262c2aa0a42b8ea8e9e2a6" isemail@3.x.x: - version "3.1.1" - resolved "https://registry.yarnpkg.com/isemail/-/isemail-3.1.1.tgz#e8450fe78ff1b48347db599122adcd0668bd92b5" + version "3.1.3" + resolved "https://registry.yarnpkg.com/isemail/-/isemail-3.1.3.tgz#64f37fc113579ea12523165c3ebe3a71a56ce571" dependencies: punycode "2.x.x" @@ -7178,12 +7098,12 @@ istanbul-lib-coverage@^1.1.1, istanbul-lib-coverage@^1.1.2, istanbul-lib-coverag resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.0.tgz#f7d8f2e42b97e37fe796114cb0f9d68b5e3a4341" istanbul-lib-hook@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.2.0.tgz#ae556fd5a41a6e8efa0b1002b1e416dfeaf9816c" + version "1.2.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.2.1.tgz#f614ec45287b2a8fc4f07f5660af787575601805" dependencies: - append-transform "^0.4.0" + append-transform "^1.0.0" -istanbul-lib-instrument@^1.10.1, istanbul-lib-instrument@^1.7.3, istanbul-lib-instrument@^1.7.5, istanbul-lib-instrument@^1.8.0: +istanbul-lib-instrument@^1.10.1, istanbul-lib-instrument@^1.7.3, istanbul-lib-instrument@^1.8.0: version "1.10.1" resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.1.tgz#724b4b6caceba8692d3f1f9d0727e279c401af7b" dependencies: @@ -7215,8 +7135,8 @@ istanbul-lib-source-maps@^1.2.1: source-map "^0.5.3" istanbul-lib-source-maps@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.4.tgz#cc7ccad61629f4efff8e2f78adb8c522c9976ec7" + version "1.2.5" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.5.tgz#ffe6be4e7ab86d3603e4290d54990b14506fc9b1" dependencies: debug "^3.1.0" istanbul-lib-coverage "^1.2.0" @@ -7268,15 +7188,15 @@ jclass@^1.0.1: version "1.2.1" resolved "https://registry.yarnpkg.com/jclass/-/jclass-1.2.1.tgz#eaafeec0dd6a5bf8b3ea43c04e010c637638768b" -jest-changed-files@^22.4.3: +jest-changed-files@^22.2.0: version "22.4.3" resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-22.4.3.tgz#8882181e022c38bd46a2e4d18d44d19d90a90fb2" dependencies: throat "^4.0.0" -jest-cli@^22.4.3: - version "22.4.3" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-22.4.3.tgz#bf16c4a5fb7edc3fa5b9bb7819e34139e88a72c7" +jest-cli@^22.4.3, jest-cli@^22.4.4: + version "22.4.4" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-22.4.4.tgz#68cd2a2aae983adb1e6638248ca21082fd6d9e90" dependencies: ansi-escapes "^3.0.0" chalk "^2.0.1" @@ -7289,20 +7209,20 @@ jest-cli@^22.4.3: istanbul-lib-coverage "^1.1.1" istanbul-lib-instrument "^1.8.0" istanbul-lib-source-maps "^1.2.1" - jest-changed-files "^22.4.3" - jest-config "^22.4.3" - jest-environment-jsdom "^22.4.3" - jest-get-type "^22.4.3" - jest-haste-map "^22.4.3" - jest-message-util "^22.4.3" - jest-regex-util "^22.4.3" - jest-resolve-dependencies "^22.4.3" - jest-runner "^22.4.3" - jest-runtime "^22.4.3" - jest-snapshot "^22.4.3" - jest-util "^22.4.3" - jest-validate "^22.4.3" - jest-worker "^22.4.3" + jest-changed-files "^22.2.0" + jest-config "^22.4.4" + jest-environment-jsdom "^22.4.1" + jest-get-type "^22.1.0" + jest-haste-map "^22.4.2" + jest-message-util "^22.4.0" + jest-regex-util "^22.1.0" + jest-resolve-dependencies "^22.1.0" + jest-runner "^22.4.4" + jest-runtime "^22.4.4" + jest-snapshot "^22.4.0" + jest-util "^22.4.1" + jest-validate "^22.4.4" + jest-worker "^22.2.2" micromatch "^2.3.11" node-notifier "^5.2.1" realpath-native "^1.0.0" @@ -7313,23 +7233,23 @@ jest-cli@^22.4.3: which "^1.2.12" yargs "^10.0.3" -jest-config@^22.4.3: - version "22.4.3" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-22.4.3.tgz#0e9d57db267839ea31309119b41dc2fa31b76403" +jest-config@^22.4.3, jest-config@^22.4.4: + version "22.4.4" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-22.4.4.tgz#72a521188720597169cd8b4ff86934ef5752d86a" dependencies: chalk "^2.0.1" glob "^7.1.1" - jest-environment-jsdom "^22.4.3" - jest-environment-node "^22.4.3" - jest-get-type "^22.4.3" - jest-jasmine2 "^22.4.3" - jest-regex-util "^22.4.3" - jest-resolve "^22.4.3" - jest-util "^22.4.3" - jest-validate "^22.4.3" - pretty-format "^22.4.3" - -jest-diff@^22.4.3: + jest-environment-jsdom "^22.4.1" + jest-environment-node "^22.4.1" + jest-get-type "^22.1.0" + jest-jasmine2 "^22.4.4" + jest-regex-util "^22.1.0" + jest-resolve "^22.4.2" + jest-util "^22.4.1" + jest-validate "^22.4.4" + pretty-format "^22.4.0" + +jest-diff@^22.4.0, jest-diff@^22.4.3: version "22.4.3" resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-22.4.3.tgz#e18cc3feff0aeef159d02310f2686d4065378030" dependencies: @@ -7342,13 +7262,13 @@ jest-docblock@^21.0.0: version "21.2.0" resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-21.2.0.tgz#51529c3b30d5fd159da60c27ceedc195faf8d414" -jest-docblock@^22.4.3: +jest-docblock@^22.4.0, jest-docblock@^22.4.3: version "22.4.3" resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-22.4.3.tgz#50886f132b42b280c903c592373bb6e93bb68b19" dependencies: detect-newline "^2.1.0" -jest-environment-jsdom@^22.4.3: +jest-environment-jsdom@^22.4.1: version "22.4.3" resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-22.4.3.tgz#d67daa4155e33516aecdd35afd82d4abf0fa8a1e" dependencies: @@ -7356,18 +7276,18 @@ jest-environment-jsdom@^22.4.3: jest-util "^22.4.3" jsdom "^11.5.1" -jest-environment-node@^22.4.3: +jest-environment-node@^22.4.1: version "22.4.3" resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-22.4.3.tgz#54c4eaa374c83dd52a9da8759be14ebe1d0b9129" dependencies: jest-mock "^22.4.3" jest-util "^22.4.3" -jest-get-type@^22.4.3: +jest-get-type@^22.1.0, jest-get-type@^22.4.3: version "22.4.3" resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-22.4.3.tgz#e3a8504d8479342dd4420236b322869f18900ce4" -jest-haste-map@^22.4.3: +jest-haste-map@^22.4.2: version "22.4.3" resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-22.4.3.tgz#25842fa2ba350200767ac27f658d58b9d5c2e20b" dependencies: @@ -7379,29 +7299,29 @@ jest-haste-map@^22.4.3: micromatch "^2.3.11" sane "^2.0.0" -jest-jasmine2@^22.4.3: - version "22.4.3" - resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-22.4.3.tgz#4daf64cd14c793da9db34a7c7b8dcfe52a745965" +jest-jasmine2@^22.4.4: + version "22.4.4" + resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-22.4.4.tgz#c55f92c961a141f693f869f5f081a79a10d24e23" dependencies: chalk "^2.0.1" co "^4.6.0" - expect "^22.4.3" + expect "^22.4.0" graceful-fs "^4.1.11" is-generator-fn "^1.0.0" - jest-diff "^22.4.3" - jest-matcher-utils "^22.4.3" - jest-message-util "^22.4.3" - jest-snapshot "^22.4.3" - jest-util "^22.4.3" + jest-diff "^22.4.0" + jest-matcher-utils "^22.4.0" + jest-message-util "^22.4.0" + jest-snapshot "^22.4.0" + jest-util "^22.4.1" source-map-support "^0.5.0" -jest-leak-detector@^22.4.3: +jest-leak-detector@^22.4.0: version "22.4.3" resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-22.4.3.tgz#2b7b263103afae8c52b6b91241a2de40117e5b35" dependencies: pretty-format "^22.4.3" -jest-matcher-utils@^22.4.3: +jest-matcher-utils@^22.4.0, jest-matcher-utils@^22.4.3: version "22.4.3" resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-22.4.3.tgz#4632fe428ebc73ebc194d3c7b65d37b161f710ff" dependencies: @@ -7409,7 +7329,7 @@ jest-matcher-utils@^22.4.3: jest-get-type "^22.4.3" pretty-format "^22.4.3" -jest-message-util@^22.4.3: +jest-message-util@^22.4.0, jest-message-util@^22.4.3: version "22.4.3" resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-22.4.3.tgz#cf3d38aafe4befddbfc455e57d65d5239e399eb7" dependencies: @@ -7427,56 +7347,56 @@ jest-raw-loader@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/jest-raw-loader/-/jest-raw-loader-1.0.1.tgz#ce9f56d54650f157c4a7d16d224ba5d613bcd626" -jest-regex-util@^22.4.3: +jest-regex-util@^22.1.0, jest-regex-util@^22.4.3: version "22.4.3" resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-22.4.3.tgz#a826eb191cdf22502198c5401a1fc04de9cef5af" -jest-resolve-dependencies@^22.4.3: +jest-resolve-dependencies@^22.1.0: version "22.4.3" resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-22.4.3.tgz#e2256a5a846732dc3969cb72f3c9ad7725a8195e" dependencies: jest-regex-util "^22.4.3" -jest-resolve@^22.4.3: +jest-resolve@^22.4.2: version "22.4.3" resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-22.4.3.tgz#0ce9d438c8438229aa9b916968ec6b05c1abb4ea" dependencies: browser-resolve "^1.11.2" chalk "^2.0.1" -jest-runner@^22.4.3: - version "22.4.3" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-22.4.3.tgz#298ddd6a22b992c64401b4667702b325e50610c3" +jest-runner@^22.4.4: + version "22.4.4" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-22.4.4.tgz#dfca7b7553e0fa617e7b1291aeb7ce83e540a907" dependencies: exit "^0.1.2" - jest-config "^22.4.3" - jest-docblock "^22.4.3" - jest-haste-map "^22.4.3" - jest-jasmine2 "^22.4.3" - jest-leak-detector "^22.4.3" - jest-message-util "^22.4.3" - jest-runtime "^22.4.3" - jest-util "^22.4.3" - jest-worker "^22.4.3" + jest-config "^22.4.4" + jest-docblock "^22.4.0" + jest-haste-map "^22.4.2" + jest-jasmine2 "^22.4.4" + jest-leak-detector "^22.4.0" + jest-message-util "^22.4.0" + jest-runtime "^22.4.4" + jest-util "^22.4.1" + jest-worker "^22.2.2" throat "^4.0.0" -jest-runtime@^22.4.3: - version "22.4.3" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-22.4.3.tgz#b69926c34b851b920f666c93e86ba2912087e3d0" +jest-runtime@^22.4.4: + version "22.4.4" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-22.4.4.tgz#9ba7792fc75582a5be0f79af6f8fe8adea314048" dependencies: babel-core "^6.0.0" - babel-jest "^22.4.3" + babel-jest "^22.4.4" babel-plugin-istanbul "^4.1.5" chalk "^2.0.1" convert-source-map "^1.4.0" exit "^0.1.2" graceful-fs "^4.1.11" - jest-config "^22.4.3" - jest-haste-map "^22.4.3" - jest-regex-util "^22.4.3" - jest-resolve "^22.4.3" - jest-util "^22.4.3" - jest-validate "^22.4.3" + jest-config "^22.4.4" + jest-haste-map "^22.4.2" + jest-regex-util "^22.1.0" + jest-resolve "^22.4.2" + jest-util "^22.4.1" + jest-validate "^22.4.4" json-stable-stringify "^1.0.1" micromatch "^2.3.11" realpath-native "^1.0.0" @@ -7489,7 +7409,7 @@ jest-serializer@^22.4.3: version "22.4.3" resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-22.4.3.tgz#a679b81a7f111e4766235f4f0c46d230ee0f7436" -jest-snapshot@^22.4.3: +jest-snapshot@^22.4.0: version "22.4.3" resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-22.4.3.tgz#b5c9b42846ffb9faccb76b841315ba67887362d2" dependencies: @@ -7500,7 +7420,7 @@ jest-snapshot@^22.4.3: natural-compare "^1.4.0" pretty-format "^22.4.3" -jest-util@^22.4.3: +jest-util@^22.4.1, jest-util@^22.4.3: version "22.4.3" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-22.4.3.tgz#c70fec8eec487c37b10b0809dc064a7ecf6aafac" dependencies: @@ -7512,28 +7432,28 @@ jest-util@^22.4.3: mkdirp "^0.5.1" source-map "^0.6.0" -jest-validate@^22.4.3: - version "22.4.3" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-22.4.3.tgz#0780954a5a7daaeec8d3c10834b9280865976b30" +jest-validate@^22.4.4: + version "22.4.4" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-22.4.4.tgz#1dd0b616ef46c995de61810d85f57119dbbcec4d" dependencies: chalk "^2.0.1" - jest-config "^22.4.3" - jest-get-type "^22.4.3" + jest-config "^22.4.4" + jest-get-type "^22.1.0" leven "^2.1.0" - pretty-format "^22.4.3" + pretty-format "^22.4.0" -jest-worker@^22.4.3: +jest-worker@^22.2.2, jest-worker@^22.4.3: version "22.4.3" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-22.4.3.tgz#5c421417cba1c0abf64bf56bd5fb7968d79dd40b" dependencies: merge-stream "^1.0.1" jest@^22.4.3: - version "22.4.3" - resolved "https://registry.yarnpkg.com/jest/-/jest-22.4.3.tgz#2261f4b117dc46d9a4a1a673d2150958dee92f16" + version "22.4.4" + resolved "https://registry.yarnpkg.com/jest/-/jest-22.4.4.tgz#ffb36c9654b339a13e10b3d4b338eb3e9d49f6eb" dependencies: import-local "^1.0.0" - jest-cli "^22.4.3" + jest-cli "^22.4.4" jimp@0.2.28: version "0.2.28" @@ -7579,8 +7499,8 @@ joi@10.x.x: topo "2.x.x" joi@13.x.x, joi@^13.4.0: - version "13.4.0" - resolved "https://registry.yarnpkg.com/joi/-/joi-13.4.0.tgz#afc359ee3d8bc5f9b9ba6cdc31b46d44af14cecc" + version "13.6.0" + resolved "https://registry.yarnpkg.com/joi/-/joi-13.6.0.tgz#877d820e3ad688a49c32421ffefc746bfbe2d0a0" dependencies: hoek "5.x.x" isemail "3.x.x" @@ -7631,13 +7551,9 @@ jquery@^3.3.1: version "3.3.1" resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.3.1.tgz#958ce29e81c9790f31be7792df5d4d95fc57fbca" -js-base64@^2.1.8: - version "2.4.5" - resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.4.5.tgz#e293cd3c7c82f070d700fc7a1ca0a2e69f101f92" - -js-base64@^2.1.9: - version "2.4.3" - resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.4.3.tgz#2e545ec2b0f2957f41356510205214e98fad6582" +js-base64@^2.1.8, js-base64@^2.1.9: + version "2.4.8" + resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.4.8.tgz#57a9b130888f956834aa40c5b165ba59c758f033" js-stringify@^1.0.1: version "1.0.2" @@ -7647,6 +7563,10 @@ js-tokens@^3.0.0, js-tokens@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" +"js-tokens@^3.0.0 || ^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + js-yaml@3.4.1: version "3.4.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.4.1.tgz#7183990c62f646369eaa04675b2d5f1e71d62b8b" @@ -7655,8 +7575,8 @@ js-yaml@3.4.1: esprima "~2.2.0" js-yaml@3.x, js-yaml@^3.4.3, js-yaml@^3.7.0, js-yaml@^3.9.1: - version "3.11.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.11.0.tgz#597c1a8bd57152f26d622ce4117851a51f5ebaef" + version "3.12.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.0.tgz#eaed656ec8344f10f527c6bfa1b6e2244de167d1" dependencies: argparse "^1.0.7" esprima "^4.0.0" @@ -7713,34 +7633,34 @@ jsdom@9.9.1: xml-name-validator ">= 2.0.1 < 3.0.0" jsdom@^11.5.1: - version "11.6.2" - resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-11.6.2.tgz#25d1ef332d48adf77fc5221fe2619967923f16bb" + version "11.12.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-11.12.0.tgz#1a80d40ddd378a1de59656e9e6dc5a3ba8657bc8" dependencies: - abab "^1.0.4" - acorn "^5.3.0" + abab "^2.0.0" + acorn "^5.5.3" acorn-globals "^4.1.0" array-equal "^1.0.0" - browser-process-hrtime "^0.1.2" - content-type-parser "^1.0.2" cssom ">= 0.3.2 < 0.4.0" - cssstyle ">= 0.2.37 < 0.3.0" - domexception "^1.0.0" - escodegen "^1.9.0" + cssstyle "^1.0.0" + data-urls "^1.0.0" + domexception "^1.0.1" + escodegen "^1.9.1" html-encoding-sniffer "^1.0.2" - left-pad "^1.2.0" - nwmatcher "^1.4.3" + left-pad "^1.3.0" + nwsapi "^2.0.7" parse5 "4.0.0" pn "^1.1.0" - request "^2.83.0" + request "^2.87.0" request-promise-native "^1.0.5" sax "^1.2.4" symbol-tree "^3.2.2" - tough-cookie "^2.3.3" + tough-cookie "^2.3.4" w3c-hr-time "^1.0.1" webidl-conversions "^4.0.2" whatwg-encoding "^1.0.3" - whatwg-url "^6.4.0" - ws "^4.0.0" + whatwg-mimetype "^2.1.0" + whatwg-url "^6.4.1" + ws "^5.2.0" xml-name-validator "^3.0.0" jsesc@^1.3.0: @@ -7760,8 +7680,8 @@ json-loader@^0.5.4: resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.7.tgz#dca14a70235ff82f0ac9a3abeb60d337a365185d" json-parse-better-errors@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.1.tgz#50183cd1b2d25275de069e9e71b467ac9eab973a" + version "1.0.2" + resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" json-schema-traverse@^0.3.0: version "0.3.1" @@ -7823,10 +7743,6 @@ jsonify@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" -jsonpointer@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" - jsonwebtoken@^8.3.0: version "8.3.0" resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.3.0.tgz#056c90eee9a65ed6e6c72ddb0a1d325109aaf643" @@ -8021,16 +7937,15 @@ kind-of@^6.0.0, kind-of@^6.0.2: resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" kopy@^8.2.0: - version "8.3.0" - resolved "https://registry.yarnpkg.com/kopy/-/kopy-8.3.0.tgz#7a476efeeed90a0d49ca57464ba2ffdbf41244ee" + version "8.3.1" + resolved "https://registry.yarnpkg.com/kopy/-/kopy-8.3.1.tgz#1f5ed132541fa01bc93af847fdf4d7addbba764d" dependencies: inquirer "^3.2.3" is-binary-path "^2.0.0" jstransformer "^1.0.0" jstransformer-ejs "^0.0.3" - majo "^0.4.1" - minimatch "^3.0.4" - multimatch "^2.1.0" + majo "^0.5.1" + micromatch "^3.1.10" path-exists "^3.0.0" latest-version@^1.0.0: @@ -8100,9 +8015,9 @@ leaflet@1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/leaflet/-/leaflet-1.0.3.tgz#1f401b98b45c8192134c6c8d69686253805007c8" -left-pad@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.2.0.tgz#d30a73c6b8201d8f7d8e7956ba9616087a68e0ee" +left-pad@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.3.0.tgz#5b8a3a7765dfe001261dde915589e782f8c94d1e" less-loader@4.0.5: version "4.0.5" @@ -8150,6 +8065,19 @@ license-checker@^16.0.0: spdx-satisfies "^0.1.3" treeify "^1.0.1" +liftoff@~2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/liftoff/-/liftoff-2.5.0.tgz#2009291bb31cea861bbf10a7c15a28caf75c31ec" + dependencies: + extend "^3.0.0" + findup-sync "^2.0.0" + fined "^1.0.1" + flagged-respawn "^1.0.0" + is-plain-object "^2.0.4" + object.map "^1.0.0" + rechoir "^0.6.2" + resolve "^1.1.7" + linebreak@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/linebreak/-/linebreak-0.3.0.tgz#0526480a62c05bd679f3e9d99830e09c6a7d0ed6" @@ -8211,19 +8139,19 @@ listr@^0.14.1: rxjs "^6.1.0" strip-ansi "^3.0.1" -livereload-js@^2.2.0: +livereload-js@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/livereload-js/-/livereload-js-2.3.0.tgz#c3ab22e8aaf5bf3505d80d098cbad67726548c9a" load-bmfont@^1.2.3: - version "1.3.0" - resolved "https://registry.yarnpkg.com/load-bmfont/-/load-bmfont-1.3.0.tgz#bb7e7c710de6bcafcb13cb3b8c81e0c0131ecbc9" + version "1.3.1" + resolved "https://registry.yarnpkg.com/load-bmfont/-/load-bmfont-1.3.1.tgz#0e7933f66543409882127b0d0fbad7a66d5a7869" dependencies: buffer-equal "0.0.1" mime "^1.3.4" parse-bmfont-ascii "^1.0.3" parse-bmfont-binary "^1.0.5" - parse-bmfont-xml "^1.1.0" + parse-bmfont-xml "^1.1.4" xhr "^2.0.1" xtend "^4.0.0" @@ -8292,8 +8220,8 @@ locate-path@^3.0.0: path-exists "^3.0.0" lodash-es@^4.17.4, lodash-es@^4.17.5, lodash-es@^4.2.1: - version "4.17.7" - resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.7.tgz#db240a3252c3dd8360201ac9feef91ac977ea856" + version "4.17.10" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.10.tgz#62cd7104cdf5dd87f235a837f0ede0e8e5117e05" lodash._baseassign@^3.0.0: version "3.2.0" @@ -8405,6 +8333,10 @@ lodash.debounce@^3.0.0: dependencies: lodash._getnative "^3.0.0" +lodash.debounce@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" + lodash.defaults@^4.0.1: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" @@ -8580,6 +8512,10 @@ lodash.throttle@^3.0.2: dependencies: lodash.debounce "^3.0.0" +lodash.throttle@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4" + lodash.trimend@^4.5.1: version "4.5.1" resolved "https://registry.yarnpkg.com/lodash.trimend/-/lodash.trimend-4.5.1.tgz#12804437286b98cad8996b79414e11300114082f" @@ -8606,14 +8542,10 @@ lodash@4.17.4: version "4.17.4" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" -lodash@^4.0.0, lodash@^4.17.10: +lodash@^4.0.0, lodash@^4.0.1, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.5.0, lodash@^4.6.1, lodash@^4.8.2, lodash@~4.17.10, lodash@~4.17.5: version "4.17.10" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7" -lodash@^4.0.1, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.5.0, lodash@^4.6.1, lodash@^4.8.2, lodash@~4.17.4, lodash@~4.17.5: - version "4.17.5" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511" - lodash@~4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.3.0.tgz#efd9c4a6ec53f3b05412429915c3e4824e4d25a4" @@ -8648,19 +8580,19 @@ loglevel@^1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.1.tgz#e0fc95133b6ef276cdc8887cdaf24aa6f156f8fa" -lolex@^2.2.0, lolex@^2.3.2: - version "2.6.0" - resolved "https://registry.yarnpkg.com/lolex/-/lolex-2.6.0.tgz#cf9166f3c9dece3cdeb5d6b01fce50f14a1203e3" +lolex@^2.3.2, lolex@^2.4.2: + version "2.7.1" + resolved "https://registry.yarnpkg.com/lolex/-/lolex-2.7.1.tgz#e40a8c4d1f14b536aa03e42a537c7adbaf0c20be" longest@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.0, loose-envify@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848" + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" dependencies: - js-tokens "^3.0.0" + js-tokens "^3.0.0 || ^4.0.0" loud-rejection@^1.0.0: version "1.6.0" @@ -8669,14 +8601,19 @@ loud-rejection@^1.0.0: currently-unhandled "^0.4.1" signal-exit "^3.0.0" -lowercase-keys@1.0.0, lowercase-keys@^1.0.0: +lowercase-keys@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306" +lowercase-keys@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" + lowlight@~1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/lowlight/-/lowlight-1.9.1.tgz#ed7c3dffc36f8c1f263735c0fe0c907847c11250" + version "1.9.2" + resolved "https://registry.yarnpkg.com/lowlight/-/lowlight-1.9.2.tgz#0b9127e3cec2c3021b7795dd81005c709a42fdd1" dependencies: + fault "^1.0.2" highlight.js "~9.12.0" lru-cache@4.0.x: @@ -8693,9 +8630,9 @@ lru-cache@4.1.1: pseudomap "^1.0.2" yallist "^2.1.2" -lru-cache@4.1.x, lru-cache@^4.0.1, lru-cache@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.2.tgz#45234b2e6e2f2b33da125624c4664929a0224c3f" +lru-cache@4.1.x, lru-cache@^4.0.1, lru-cache@^4.1.1, lru-cache@^4.1.2: + version "4.1.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.3.tgz#a1175cf3496dfc8436c156c334b4955992bce69c" dependencies: pseudomap "^1.0.2" yallist "^2.1.2" @@ -8704,34 +8641,22 @@ lru-cache@^2.6.5: version "2.7.3" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952" -lru-cache@^4.1.1: - version "4.1.3" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.3.tgz#a1175cf3496dfc8436c156c334b4955992bce69c" - dependencies: - pseudomap "^1.0.2" - yallist "^2.1.2" - -macaddress@^0.2.8: - version "0.2.9" - resolved "https://registry.yarnpkg.com/macaddress/-/macaddress-0.2.9.tgz#3579b8b9acd5b96b4553abf0f394185a86813cb3" - magic-string@^0.22.4: version "0.22.5" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.22.5.tgz#8e9cf5afddf44385c1da5bc2a6a0dbd10b03657e" dependencies: vlq "^0.2.2" -majo@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/majo/-/majo-0.4.1.tgz#5e6eeb9b63bda77e59d396b9c9ce4189ce6100bc" +majo@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/majo/-/majo-0.5.1.tgz#c1948544c24d29835beed10ef4574cff22a76dad" dependencies: fs-extra "^3.0.1" globby "^6.1.0" - ware "^1.3.0" make-dir@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.2.0.tgz#6d6a49eead4aae296c53bbf3a1a008bd6c89469b" + version "1.3.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" dependencies: pify "^3.0.0" @@ -8739,6 +8664,12 @@ make-error@^1.1.1: version "1.3.4" resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.4.tgz#19978ed575f9e9545d2ff8c13e33b5d18a67d535" +make-iterator@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/make-iterator/-/make-iterator-1.0.1.tgz#29b33f312aa8f547c4a5e490f56afcec99133ad6" + dependencies: + kind-of "^6.0.2" + makeerror@1.0.x: version "1.0.11" resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c" @@ -8746,8 +8677,8 @@ makeerror@1.0.x: tmpl "1.0.x" makelogs@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/makelogs/-/makelogs-4.1.0.tgz#9bb74ede29844648196f47b405bbd1383e5dca3a" + version "4.2.0" + resolved "https://registry.yarnpkg.com/makelogs/-/makelogs-4.2.0.tgz#aad2498f6f88ab868f6aa17192ca27454e6091ca" dependencies: async "^1.4.2" bluebird "^2.10.0" @@ -8760,7 +8691,7 @@ makelogs@^4.1.0: through2 "^2.0.0" update-notifier "^0.5.0" -map-cache@^0.2.2: +map-cache@^0.2.0, map-cache@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" @@ -8779,12 +8710,12 @@ map-visit@^1.0.0: object-visit "^1.0.0" markdown-escapes@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/markdown-escapes/-/markdown-escapes-1.0.1.tgz#1994df2d3af4811de59a6714934c2b2292734518" + version "1.0.2" + resolved "https://registry.yarnpkg.com/markdown-escapes/-/markdown-escapes-1.0.2.tgz#e639cbde7b99c841c0bacc8a07982873b46d2122" markdown-it@^8.4.1: - version "8.4.1" - resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-8.4.1.tgz#206fe59b0e4e1b78a7c73250af9b34a4ad0aaf44" + version "8.4.2" + resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-8.4.2.tgz#386f98998dc15a37722aa7722084f4020bdd9b54" dependencies: argparse "^1.0.7" entities "~1.1.1" @@ -8793,13 +8724,17 @@ markdown-it@^8.4.1: uc.micro "^1.0.5" material-colors@^1.2.1: - version "1.2.5" - resolved "https://registry.yarnpkg.com/material-colors/-/material-colors-1.2.5.tgz#5292593e6754cb1bcc2b98030e4e0d6a3afc9ea1" + version "1.2.6" + resolved "https://registry.yarnpkg.com/material-colors/-/material-colors-1.2.6.tgz#6d1958871126992ceecc72f4bcc4d8f010865f46" math-expression-evaluator@^1.2.14: version "1.2.17" resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz#de819fdbcd84dccd8fae59c6aeb79615b9d266ac" +math-random@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.1.tgz#8b3aac588b8a66e4975e3cdea67f7bb329601fac" + md5.js@^1.3.4: version "1.3.4" resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.4.tgz#e9bdbde94a20a5ac18b04340fc5764d5b09d901d" @@ -8807,6 +8742,12 @@ md5.js@^1.3.4: hash-base "^3.0.0" inherits "^2.0.1" +mdast-add-list-metadata@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mdast-add-list-metadata/-/mdast-add-list-metadata-1.0.1.tgz#95e73640ce2fc1fa2dcb7ec443d09e2bfe7db4cf" + dependencies: + unist-util-visit-parents "1.1.2" + mdurl@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" @@ -8864,10 +8805,10 @@ merge-stream@^1.0.1: readable-stream "^2.0.1" merge2@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.2.1.tgz#271d2516ff52d4af7f7b710b8bf3e16e183fef66" + version "1.2.2" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.2.2.tgz#03212e3da8d86c4d8523cebd6318193414f94e34" -merge@^1.1.3: +merge@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.0.tgz#7531e39d4949c281a66b8c5a6e0265e8b05894da" @@ -8893,9 +8834,9 @@ micromatch@^2.1.5, micromatch@^2.3.11: parse-glob "^3.0.4" regex-cache "^0.4.2" -micromatch@^3.1.4, micromatch@^3.1.8: - version "3.1.9" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.9.tgz#15dc93175ae39e52e93087847096effc73efcf89" +micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4, micromatch@^3.1.8: + version "3.1.10" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" dependencies: arr-diff "^4.0.0" array-unique "^0.3.2" @@ -8909,7 +8850,7 @@ micromatch@^3.1.4, micromatch@^3.1.8: object.pick "^1.3.0" regex-not "^1.0.0" snapdragon "^0.8.1" - to-regex "^3.0.1" + to-regex "^3.0.2" miller-rabin@^4.0.0: version "4.0.1" @@ -8918,15 +8859,19 @@ miller-rabin@^4.0.0: bn.js "^4.0.0" brorand "^1.0.1" -mime-db@1.x.x, mime-db@~1.33.0: - version "1.33.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db" +mime-db@1.x.x: + version "1.36.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.36.0.tgz#5020478db3c7fe93aad7bbcc4dcf869c43363397" -mime-types@^2.1.12, mime-types@~2.1.11, mime-types@~2.1.17, mime-types@~2.1.18, mime-types@~2.1.7: - version "2.1.18" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8" +mime-db@~1.35.0: + version "1.35.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.35.0.tgz#0569d657466491283709663ad379a99b90d9ab47" + +mime-types@^2.1.12, mime-types@~2.1.11, mime-types@~2.1.17, mime-types@~2.1.18, mime-types@~2.1.19: + version "2.1.19" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.19.tgz#71e464537a7ef81c15f2db9d97e913fc0ff606f0" dependencies: - mime-db "~1.33.0" + mime-db "~1.35.0" mime@1.3.x: version "1.3.6" @@ -8941,8 +8886,8 @@ mimic-fn@^1.0.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" mimic-response@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.0.tgz#df3d3652a73fded6b9b0b24146e6fd052353458e" + version "1.0.1" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" mimos@3.x.x: version "3.0.3" @@ -8964,9 +8909,9 @@ min-document@^2.19.0: dependencies: dom-walk "^0.1.0" -minimalistic-assert@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz#702be2dda6b37f4836bcb3f5db56641b64a1d3d3" +minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: version "1.0.1" @@ -8995,19 +8940,19 @@ minimist@~0.0.1: resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" minimost@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/minimost/-/minimost-1.0.2.tgz#9c01e1601b4cbcef590ced58cda6332cd25afe91" + version "1.1.0" + resolved "https://registry.yarnpkg.com/minimost/-/minimost-1.1.0.tgz#b0356d50fec059c965743d72ce4d202d262a9705" dependencies: minimist "^1.2.0" -minipass@^2.2.1: - version "2.2.4" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.2.4.tgz#03c824d84551ec38a8d1bb5bc350a5a30a354a40" +minipass@^2.2.1, minipass@^2.3.3: + version "2.3.4" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.4.tgz#4768d7605ed6194d6d576169b9e12ef71e9d9957" dependencies: - safe-buffer "^5.1.1" + safe-buffer "^5.1.2" yallist "^3.0.0" -minizlib@^1.0.4: +minizlib@^1.0.4, minizlib@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.1.0.tgz#11e13658ce46bc3a70a267aac58359d1e0c29ceb" dependencies: @@ -9067,13 +9012,9 @@ mocha@3.3.0: mkdirp "0.5.1" supports-color "3.1.2" -mock-fs@^4.4.2: - version "4.4.2" - resolved "https://registry.yarnpkg.com/mock-fs/-/mock-fs-4.4.2.tgz#09dec5313f97095a450be6aa2ad8ab6738d63d6b" - -mock-fs@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/mock-fs/-/mock-fs-4.5.0.tgz#75245b966f7e3defe197b03454af9c5b355594b7" +mock-fs@^4.4.2, mock-fs@^4.5.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/mock-fs/-/mock-fs-4.6.0.tgz#d944ef4c3e03ceb4e8332b4b31b8ac254051c8ae" module-not-found-error@^1.0.0: version "1.0.1" @@ -9084,14 +9025,18 @@ moment-duration-format@^1.3.0: resolved "https://registry.yarnpkg.com/moment-duration-format/-/moment-duration-format-1.3.0.tgz#541771b5f87a049cc65540475d3ad966737d6908" moment-timezone@^0.5.14: - version "0.5.14" - resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.14.tgz#4eb38ff9538b80108ba467a458f3ed4268ccfcb1" + version "0.5.21" + resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.21.tgz#3cba247d84492174dbf71de2a9848fa13207b845" dependencies: moment ">= 2.9.0" moment@2.x.x, "moment@>= 2.9.0", moment@^2.10.6, moment@^2.13.0, moment@^2.20.1: - version "2.21.0" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.21.0.tgz#2a114b51d2a6ec9e6d83cf803f838a878d8a023a" + version "2.22.2" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66" + +moo@^0.4.3: + version "0.4.3" + resolved "https://registry.yarnpkg.com/moo/-/moo-0.4.3.tgz#3f847a26f31cf625a956a87f2b10fbc013bfd10e" move-concurrently@^1.0.1: version "1.0.1" @@ -9120,7 +9065,7 @@ ms@^2.0.0, ms@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" -multimatch@^2.0.0, multimatch@^2.1.0: +multimatch@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/multimatch/-/multimatch-2.1.0.tgz#9c7906a22fb4c02919e2f5f75161b4cdbd4b2a2b" dependencies: @@ -9149,20 +9094,19 @@ mute-stream@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" -nan@^2.10.0, nan@^2.3.0: +nan@^2.10.0, nan@^2.9.2: version "2.10.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f" nanomatch@^1.2.9: - version "1.2.9" - resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.9.tgz#879f7150cb2dab7a471259066c104eee6e0fa7c2" + version "1.2.13" + resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" dependencies: arr-diff "^4.0.0" array-unique "^0.3.2" define-property "^2.0.2" extend-shallow "^3.0.2" fragment-cache "^0.2.1" - is-odd "^2.0.0" is-windows "^1.0.2" kind-of "^6.0.2" object.pick "^1.3.0" @@ -9175,21 +9119,30 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" nearley@^2.7.10: - version "2.13.0" - resolved "https://registry.yarnpkg.com/nearley/-/nearley-2.13.0.tgz#6e7b0f4e68bfc3e74c99eaef2eda39e513143439" + version "2.15.1" + resolved "https://registry.yarnpkg.com/nearley/-/nearley-2.15.1.tgz#965e4e6ec9ed6b80fc81453e161efbcebb36d247" dependencies: + moo "^0.4.3" nomnom "~1.6.2" railroad-diagrams "^1.0.0" randexp "0.4.6" semver "^5.4.1" +needle@^2.2.1: + version "2.2.2" + resolved "https://registry.yarnpkg.com/needle/-/needle-2.2.2.tgz#1120ca4c41f2fcc6976fd28a8968afe239929418" + dependencies: + debug "^2.1.2" + iconv-lite "^0.4.4" + sax "^1.2.4" + negotiator@0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" neo-async@^2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.5.0.tgz#76b1c823130cca26acfbaccc8fbaf0a2fa33b18f" + version "2.5.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.5.2.tgz#489105ce7bc54e709d736b195f82135048c50fcc" nested-error-stacks@^1.0.0: version "1.0.2" @@ -9223,9 +9176,9 @@ nigel@3.x.x: hoek "5.x.x" vise "3.x.x" -nise@^1.2.0: - version "1.3.3" - resolved "https://registry.yarnpkg.com/nise/-/nise-1.3.3.tgz#c17a850066a8a1dfeb37f921da02441afc4a82ba" +nise@^1.3.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/nise/-/nise-1.4.3.tgz#d1996e8d15256ceff1a0a1596e0c72bff370e37c" dependencies: "@sinonjs/formatio" "^2.0.0" just-extend "^1.1.27" @@ -9264,22 +9217,21 @@ node-fetch@^1.0.1, node-fetch@^1.3.3: is-stream "^1.0.1" node-fetch@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.1.1.tgz#369ca70b82f50c86496104a6c776d274f4e4a2d4" + version "2.2.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.2.0.tgz#4ee79bde909262f9775f731e3656d0db55ced5b5" -node-gyp@^3.3.1: - version "3.6.2" - resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.6.2.tgz#9bfbe54562286284838e750eac05295853fa1c60" +node-gyp@^3.8.0: + version "3.8.0" + resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.8.0.tgz#540304261c330e80d0d5edce253a68cb3964218c" dependencies: fstream "^1.0.0" glob "^7.0.3" graceful-fs "^4.1.2" - minimatch "^3.0.2" mkdirp "^0.5.0" nopt "2 || 3" npmlog "0 || 1 || 2 || 3 || 4" osenv "0" - request "2" + request "^2.87.0" rimraf "2" semver "~5.3.0" tar "^2.0.0" @@ -9326,31 +9278,30 @@ node-notifier@^5.2.1: shellwords "^0.1.1" which "^1.3.0" -node-pre-gyp@^0.6.39: - version "0.6.39" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.39.tgz#c00e96860b23c0e1420ac7befc5044e1d78d8649" +node-pre-gyp@^0.10.0: + version "0.10.3" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.10.3.tgz#3070040716afdc778747b61b6887bf78880b80fc" dependencies: detect-libc "^1.0.2" - hawk "3.1.3" mkdirp "^0.5.1" + needle "^2.2.1" nopt "^4.0.1" + npm-packlist "^1.1.6" npmlog "^4.0.2" - rc "^1.1.7" - request "2.81.0" + rc "^1.2.7" rimraf "^2.6.1" semver "^5.3.0" - tar "^2.2.1" - tar-pack "^3.4.0" + tar "^4" -node-releases@^1.0.0-alpha.10: - version "1.0.0-alpha.10" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.0.0-alpha.10.tgz#61c8d5f9b5b2e05d84eba941d05b6f5202f68a2a" +node-releases@^1.0.0-alpha.11: + version "1.0.0-alpha.11" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.0.0-alpha.11.tgz#73c810acc2e5b741a17ddfbb39dfca9ab9359d8a" dependencies: semver "^5.3.0" node-sass@^4.9.0: - version "4.9.0" - resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.9.0.tgz#d1b8aa855d98ed684d6848db929a20771cc2ae52" + version "4.9.3" + resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.9.3.tgz#f407cf3d66f78308bb1e346b24fa428703196224" dependencies: async-foreach "^0.1.3" chalk "^1.1.1" @@ -9365,9 +9316,9 @@ node-sass@^4.9.0: meow "^3.7.0" mkdirp "^0.5.1" nan "^2.10.0" - node-gyp "^3.3.1" + node-gyp "^3.8.0" npmlog "^4.0.0" - request "~2.79.0" + request "2.87.0" sass-graph "^2.2.4" stdout-stream "^1.4.0" "true-case-path" "^1.0.2" @@ -9377,8 +9328,8 @@ node-status-codes@^1.0.0: resolved "https://registry.yarnpkg.com/node-status-codes/-/node-status-codes-1.0.0.tgz#5ae5541d024645d32a58fcddc9ceecea7ae3ac2f" nodemailer@^4.6.4: - version "4.6.4" - resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-4.6.4.tgz#f0d72d0c6a6ec5f4369fa8f4bf5127a31baa2014" + version "4.6.8" + resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-4.6.8.tgz#f82fb407828bf2e76d92acc34b823d83e774f89c" nomnom@~1.6.2: version "1.6.2" @@ -9399,7 +9350,7 @@ nopt@^2.2.0: dependencies: abbrev "1" -nopt@^4.0.1: +nopt@^4.0.1, nopt@~4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" dependencies: @@ -9452,6 +9403,10 @@ now-and-later@^2.0.0: dependencies: once "^1.3.2" +npm-bundled@^1.0.1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.5.tgz#3c1732b7ba936b3a10325aef616467c0ccbcc979" + npm-conf@^1.1.0: version "1.1.3" resolved "https://registry.yarnpkg.com/npm-conf/-/npm-conf-1.1.3.tgz#256cc47bd0e218c259c4e9550bf413bc2192aff9" @@ -9459,6 +9414,13 @@ npm-conf@^1.1.0: config-chain "^1.1.11" pify "^3.0.0" +npm-packlist@^1.1.6: + version "1.1.11" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.1.11.tgz#84e8c683cbe7867d34b1d357d893ce29e28a02de" + dependencies: + ignore-walk "^3.0.1" + npm-bundled "^1.0.1" + npm-run-path@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" @@ -9496,14 +9458,22 @@ numeral@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/numeral/-/numeral-2.0.6.tgz#4ad080936d443c2561aed9f2197efffe25f4e506" -"nwmatcher@>= 1.3.9 < 2.0.0", nwmatcher@^1.4.3: +"nwmatcher@>= 1.3.9 < 2.0.0": version "1.4.4" resolved "https://registry.yarnpkg.com/nwmatcher/-/nwmatcher-1.4.4.tgz#2285631f34a95f0d0395cd900c96ed39b58f346e" -oauth-sign@~0.8.1, oauth-sign@~0.8.2: +nwsapi@^2.0.7: + version "2.0.8" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.0.8.tgz#e3603579b7e162b3dbedae4fb24e46f771d8fa24" + +oauth-sign@~0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" +oauth-sign@~0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" + object-assign@4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.0.tgz#7a3b3d0e98063d43f4c03f2e8ae6cd51a86883a0" @@ -9536,9 +9506,9 @@ object-is@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.0.1.tgz#0aa60ec9989a0b3ed795cf4d06f62cf1ad6539b6" -object-keys@^1.0.11, object-keys@^1.0.6, object-keys@^1.0.8: - version "1.0.11" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d" +object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.0.6: + version "1.0.12" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.12.tgz#09c53855377575310cca62f55bb334abff7b3ed2" object-visit@^1.0.0: version "1.0.1" @@ -9546,7 +9516,7 @@ object-visit@^1.0.0: dependencies: isobject "^3.0.0" -object.assign@^4.0.4: +object.assign@^4.0.4, object.assign@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" dependencies: @@ -9555,6 +9525,15 @@ object.assign@^4.0.4: has-symbols "^1.0.0" object-keys "^1.0.11" +object.defaults@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/object.defaults/-/object.defaults-1.1.0.tgz#3a7f868334b407dea06da16d88d5cd29e435fecf" + dependencies: + array-each "^1.0.1" + array-slice "^1.0.0" + for-own "^1.0.0" + isobject "^3.0.0" + object.entries@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.0.4.tgz#1bf9a4dd2288f5b33f3a993d257661f05d161a5f" @@ -9571,6 +9550,13 @@ object.getownpropertydescriptors@^2.0.3: define-properties "^1.1.2" es-abstract "^1.5.1" +object.map@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object.map/-/object.map-1.0.1.tgz#cf83e59dc8fcc0ad5f4250e1f78b3b81bd801d37" + dependencies: + for-own "^1.0.0" + make-iterator "^1.0.0" + object.omit@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa" @@ -9578,7 +9564,7 @@ object.omit@^2.0.0: for-own "^0.1.4" is-extendable "^0.1.1" -object.pick@^1.3.0: +object.pick@^1.2.0, object.pick@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" dependencies: @@ -9599,7 +9585,7 @@ on-finished@~2.3.0: dependencies: ee-first "1.1.1" -once@1.x, once@^1.3.0, once@^1.3.1, once@^1.3.2, once@^1.3.3, once@^1.4.0: +once@1.x, once@^1.3.0, once@^1.3.1, once@^1.3.2, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" dependencies: @@ -9713,10 +9699,6 @@ p-cancelable@^0.4.0: version "0.4.1" resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.4.1.tgz#35f363d67d52081c8d9585e37bcceb7e0bbcb2a0" -p-defer@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" - p-finally@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" @@ -9726,8 +9708,8 @@ p-is-promise@^1.1.0: resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-1.1.0.tgz#9c9456989e9f6588017b0434d56097675c3da05e" p-limit@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.2.0.tgz#0e92b6bedcb59f022c13d0f1949dc82d15909f1c" + version "1.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" dependencies: p-try "^1.0.0" @@ -9757,11 +9739,11 @@ p-queue@^2.3.0: version "2.4.2" resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-2.4.2.tgz#03609826682b743be9a22dba25051bd46724fc34" -p-retry@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-1.0.0.tgz#3927332a4b7d70269b535515117fc547da1a6968" +p-retry@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-2.0.0.tgz#b97f1f4d6d81a3c065b2b40107b811e995c1bfba" dependencies: - retry "^0.10.0" + retry "^0.12.0" p-timeout@^2.0.1: version "2.0.1" @@ -9810,8 +9792,8 @@ parallel-transform@^1.1.0: readable-stream "^2.1.5" parse-asn1@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.0.tgz#37c4f9b7ed3ab65c74817b5f2480937fbf97c712" + version "5.1.1" + resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.1.tgz#f6bf293818332bd0dab54efb16087724745e6ca8" dependencies: asn1.js "^4.0.0" browserify-aes "^1.0.0" @@ -9827,16 +9809,16 @@ parse-bmfont-binary@^1.0.5: version "1.0.6" resolved "https://registry.yarnpkg.com/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz#d038b476d3e9dd9db1e11a0b0e53a22792b69006" -parse-bmfont-xml@^1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/parse-bmfont-xml/-/parse-bmfont-xml-1.1.3.tgz#d6b66a371afd39c5007d9f0eeb262a4f2cce7b7c" +parse-bmfont-xml@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/parse-bmfont-xml/-/parse-bmfont-xml-1.1.4.tgz#015319797e3e12f9e739c4d513872cd2fa35f389" dependencies: xml-parse-from-string "^1.0.0" xml2js "^0.4.5" parse-entities@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-1.1.1.tgz#8112d88471319f27abae4d64964b122fe4e1b890" + version "1.1.2" + resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-1.1.2.tgz#9eaf719b29dc3bd62246b4332009072e01527777" dependencies: character-entities "^1.0.0" character-entities-legacy "^1.0.0" @@ -9845,6 +9827,14 @@ parse-entities@^1.1.0: is-decimal "^1.0.0" is-hexadecimal "^1.0.0" +parse-filepath@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/parse-filepath/-/parse-filepath-1.0.2.tgz#a632127f53aaf3d15876f5872f3ffac763d6c891" + dependencies: + is-absolute "^1.0.0" + map-cache "^0.2.0" + path-root "^0.1.1" + parse-git-config@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/parse-git-config/-/parse-git-config-1.1.1.tgz#d3a9984317132f57398712bba438e129590ddf8c" @@ -9912,7 +9902,7 @@ parseuri@0.0.5: dependencies: better-assert "~1.0.0" -parseurl@~1.3.0, parseurl@~1.3.2: +parseurl@~1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3" @@ -9951,8 +9941,18 @@ path-key@^2.0.0, path-key@^2.0.1: resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" path-parse@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" + version "1.0.6" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" + +path-root-regex@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/path-root-regex/-/path-root-regex-0.1.2.tgz#bfccdc8df5b12dc52c8b43ec38d18d72c04ba96d" + +path-root@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/path-root/-/path-root-0.1.1.tgz#9a4a6814cac1c0cd73360a95f32083c8ea4745b7" + dependencies: + path-root-regex "^0.1.0" path-to-regexp@^1.7.0: version "1.7.0" @@ -9987,8 +9987,8 @@ pause-stream@0.0.11: through "~2.3" pbkdf2@^3.0.3: - version "3.0.14" - resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.14.tgz#a35e13c64799b06ce15320f459c230e68e73bade" + version "3.0.16" + resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.16.tgz#7404208ec6b01b62d85bf83853a8064f8d9c2a5c" dependencies: create-hash "^1.1.2" create-hmac "^1.1.4" @@ -10152,8 +10152,8 @@ pngjs@3.3.1: resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.3.1.tgz#8e14e6679ee7424b544334c3b2d21cea6d8c209a" pngjs@^3.0.0: - version "3.3.2" - resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.3.2.tgz#097c3c2a75feb223eadddea6bc9f0050cf830bc3" + version "3.3.3" + resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.3.3.tgz#85173703bde3edac8998757b96e5821d0966a21b" podium@3.x.x: version "3.1.2" @@ -10163,12 +10163,12 @@ podium@3.x.x: joi "13.x.x" polished@^1.9.2: - version "1.9.2" - resolved "https://registry.yarnpkg.com/polished/-/polished-1.9.2.tgz#d705cac66f3a3ed1bd38aad863e2c1e269baf6b6" + version "1.9.3" + resolved "https://registry.yarnpkg.com/polished/-/polished-1.9.3.tgz#d61b8a0c4624efe31e2583ff24a358932b6b75e1" popper.js@^1.14.1: - version "1.14.3" - resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.14.3.tgz#1438f98d046acf7b4d78cd502bf418ac64d4f095" + version "1.14.4" + resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.14.4.tgz#8eec1d8ff02a5a3a152dd43414a15c7b79fd69b6" posix-character-classes@^0.1.0: version "0.1.1" @@ -10229,11 +10229,10 @@ postcss-discard-unused@^2.2.1: uniqs "^2.0.0" postcss-filter-plugins@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/postcss-filter-plugins/-/postcss-filter-plugins-2.0.2.tgz#6d85862534d735ac420e4a85806e1f5d4286d84c" + version "2.0.3" + resolved "https://registry.yarnpkg.com/postcss-filter-plugins/-/postcss-filter-plugins-2.0.3.tgz#82245fdf82337041645e477114d8e593aa18b8ec" dependencies: postcss "^5.0.4" - uniqid "^4.0.0" postcss-load-config@^1.2.0: version "1.2.0" @@ -10445,12 +10444,12 @@ postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0 supports-color "^3.2.3" postcss@^6.0.1, postcss@^6.0.2: - version "6.0.20" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.20.tgz#686107e743a12d5530cb68438c590d5b2bf72c3c" + version "6.0.23" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.23.tgz#61c82cc328ac60e677645f979054eb98bc0e3324" dependencies: - chalk "^2.3.2" + chalk "^2.4.1" source-map "^0.6.1" - supports-color "^5.3.0" + supports-color "^5.4.0" postcss@^7.0.2: version "7.0.2" @@ -10477,10 +10476,10 @@ preserve@^0.2.0: resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" prettier@^1.14.0: - version "1.14.0" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.14.0.tgz#847c235522035fd988100f1f43cf20a7d24f9372" + version "1.14.2" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.14.2.tgz#0ac1c6e1a90baa22a62925f41963c841983282f9" -pretty-format@^22.4.3: +pretty-format@^22.4.0, pretty-format@^22.4.3: version "22.4.3" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-22.4.3.tgz#f873d780839a9c02e9664c8a082e9ee79eaac16f" dependencies: @@ -10491,7 +10490,7 @@ prismjs@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-0.0.1.tgz#0fd50f4baf26e5cd33523b65bac2f0bc90f5503f" -private@^0.1.6, private@^0.1.7, private@^0.1.8: +private@^0.1.6, private@^0.1.8: version "0.1.8" resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" @@ -10535,11 +10534,10 @@ prop-types@15.5.8: dependencies: fbjs "^0.8.9" -prop-types@15.x, prop-types@^15.5.0, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1: - version "15.6.1" - resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.1.tgz#36644453564255ddda391191fb3a125cbdf654ca" +prop-types@15.x, prop-types@^15.5.0, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2: + version "15.6.2" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102" dependencies: - fbjs "^0.8.16" loose-envify "^1.3.1" object-assign "^4.1.1" @@ -10571,9 +10569,13 @@ pseudomap@^1.0.1, pseudomap@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" +psl@^1.1.24: + version "1.1.29" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.29.tgz#60f580d360170bb722a797cc704411e6da850c67" + public-encrypt@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.0.tgz#39f699f3a46560dd5ebacbca693caf7c65c18cc6" + version "4.0.2" + resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.2.tgz#46eb9107206bf73489f8b85b69d91334c6610994" dependencies: bn.js "^4.1.0" browserify-rsa "^4.0.0" @@ -10712,7 +10714,7 @@ pump@^2.0.0, pump@^2.0.1: end-of-stream "^1.1.0" once "^1.3.1" -pumpify@^1.3.3: +pumpify@^1.3.3, pumpify@^1.3.5: version "1.5.1" resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce" dependencies: @@ -10720,21 +10722,13 @@ pumpify@^1.3.3: inherits "^2.0.3" pump "^2.0.0" -pumpify@^1.3.5: - version "1.4.0" - resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.4.0.tgz#80b7c5df7e24153d03f0e7ac8a05a5d068bd07fb" - dependencies: - duplexify "^3.5.3" - inherits "^2.0.3" - pump "^2.0.0" - punycode@1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" punycode@2.x.x, punycode@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.0.tgz#5f863edc89b96db09074bad7947bf09056ca4e7d" + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" punycode@^1.2.4, punycode@^1.4.1: version "1.4.1" @@ -10748,25 +10742,9 @@ qjobs@^1.1.4: version "1.2.0" resolved "https://registry.yarnpkg.com/qjobs/-/qjobs-1.2.0.tgz#c45e9c61800bd087ef88d7e256423bdd49e5d071" -qs@5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-5.2.0.tgz#a9f31142af468cb72b25b30136ba2456834916be" - -qs@6.5.1, qs@^6.0.2, qs@^6.5.1, qs@~6.5.1: - version "6.5.1" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" - -qs@~5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-5.1.0.tgz#4d932e5c7ea411cca76a312d39a606200fd50cd9" - -qs@~6.3.0: - version "6.3.2" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.2.tgz#e75bd5f6e268122a2a0e0bda630b2550c166502c" - -qs@~6.4.0: - version "6.4.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" +qs@6.5.2, qs@^6.0.2, qs@^6.4.0, qs@^6.5.1, qs@~6.5.1, qs@~6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" query-string@^4.1.0: version "4.3.4" @@ -10824,12 +10802,13 @@ randexp@0.4.6: discontinuous-range "1.0.0" ret "~0.1.10" -randomatic@^1.1.3: - version "1.1.7" - resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.7.tgz#c7abe9cc8b87c0baa876b19fde83fd464797e38c" +randomatic@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-3.1.0.tgz#36f2ca708e9e567f5ed2ec01949026d50aa10116" dependencies: - is-number "^3.0.0" - kind-of "^4.0.0" + is-number "^4.0.0" + kind-of "^6.0.0" + math-random "^1.0.1" randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: version "2.0.6" @@ -10848,39 +10827,38 @@ range-parser@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" -raw-body@2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.2.tgz#bcd60c77d3eb93cde0050295c3f379389bc88f89" +raw-body@2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.3.tgz#1b324ece6b5706e153855bc1148c65bb7f6ea0c3" dependencies: bytes "3.0.0" - http-errors "1.6.2" - iconv-lite "0.4.19" + http-errors "1.6.3" + iconv-lite "0.4.23" unpipe "1.0.0" -raw-body@~2.1.5: - version "2.1.7" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.1.7.tgz#adfeace2e4fb3098058014d08c072dcc59758774" +raw-body@~1.1.0: + version "1.1.7" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-1.1.7.tgz#1d027c2bfa116acc6623bca8f00016572a87d425" dependencies: - bytes "2.4.0" - iconv-lite "0.4.13" - unpipe "1.0.0" + bytes "1" + string_decoder "0.10" raw-loader@0.5.1, raw-loader@~0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-0.5.1.tgz#0c3d0beaed8a01c966d9787bf778281252a979aa" -rc@^1.0.1, rc@^1.1.6, rc@^1.1.7: - version "1.2.6" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.6.tgz#eb18989c6d4f4f162c399f79ddd29f3835568092" +rc@^1.0.1, rc@^1.1.6, rc@^1.2.7: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" dependencies: - deep-extend "~0.4.0" + deep-extend "^0.6.0" ini "~1.3.0" minimist "^1.2.0" strip-json-comments "~2.0.1" react-ace@^5.5.0, react-ace@^5.9.0: - version "5.9.0" - resolved "https://registry.yarnpkg.com/react-ace/-/react-ace-5.9.0.tgz#427a1cc4869b960a6f9748aa7eb169a9269fc336" + version "5.10.0" + resolved "https://registry.yarnpkg.com/react-ace/-/react-ace-5.10.0.tgz#e328b37ac52759f700be5afdb86ada2f5ec84c5e" dependencies: brace "^0.11.0" lodash.get "^4.4.2" @@ -10902,15 +10880,15 @@ react-anything-sortable@^1.7.4: prop-types "^15.5.8" react-clipboard.js@^1.1.2: - version "1.1.3" - resolved "https://registry.yarnpkg.com/react-clipboard.js/-/react-clipboard.js-1.1.3.tgz#86feeb49364553ecd15aea91c75aa142532a60e0" + version "1.1.4" + resolved "https://registry.yarnpkg.com/react-clipboard.js/-/react-clipboard.js-1.1.4.tgz#d284ba6666fc2d7ae873533e4db111bf8394b12d" dependencies: clipboard "^1.6.1" prop-types "^15.5.0" react-color@^2.13.8: - version "2.14.0" - resolved "https://registry.yarnpkg.com/react-color/-/react-color-2.14.0.tgz#5828a11c034aa0939befbd888a066ee37d8c3cc2" + version "2.14.1" + resolved "https://registry.yarnpkg.com/react-color/-/react-color-2.14.1.tgz#db8ad4f45d81e74896fc2e1c99508927c6d084e0" dependencies: lodash "^4.0.1" material-colors "^1.2.1" @@ -10927,18 +10905,9 @@ react-datepicker@v1.5.0: react-onclickoutside "^6.7.1" react-popper "^0.9.1" -react-dom@^16.0.0: - version "16.2.0" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.2.0.tgz#69003178601c0ca19b709b33a83369fe6124c044" - dependencies: - fbjs "^0.8.16" - loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.0" - -react-dom@^16.3.0: - version "16.3.1" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.3.1.tgz#6a3c90a4fb62f915bdbcf6204422d93a7d4ca573" +react-dom@^16.0.0, react-dom@^16.3.0: + version "16.4.2" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.4.2.tgz#4afed569689f2c561d2b8da0b819669c38a0bda4" dependencies: fbjs "^0.8.16" loose-envify "^1.1.0" @@ -10984,13 +10953,13 @@ react-intl@^2.4.0: intl-relativeformat "^2.0.0" invariant "^2.1.1" -react-is@^16.3.1: - version "16.4.1" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.4.1.tgz#d624c4650d2c65dbd52c72622bbf389435d9776e" +react-is@^16.3.1, react-is@^16.4.2: + version "16.4.2" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.4.2.tgz#84891b56c2b6d9efdee577cc83501dfc5ecead88" -react-is@^16.4.0: - version "16.4.0" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.4.0.tgz#cc9fdc855ac34d2e7d9d2eb7059bbc240d35ffcf" +react-lifecycles-compat@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" react-markdown-renderer@^1.4.0: version "1.4.0" @@ -11000,9 +10969,10 @@ react-markdown-renderer@^1.4.0: remarkable "^1.7.1" react-markdown@^3.1.4: - version "3.3.0" - resolved "https://registry.yarnpkg.com/react-markdown/-/react-markdown-3.3.0.tgz#a87cdd822aa9302d6add9687961dd1a82a45d02e" + version "3.4.1" + resolved "https://registry.yarnpkg.com/react-markdown/-/react-markdown-3.4.1.tgz#9fb726a94dfcb600d625fd06a5550629801303dc" dependencies: + mdast-add-list-metadata "^1.0.1" prop-types "^15.6.1" remark-parse "^5.0.0" unified "^6.1.5" @@ -11073,7 +11043,7 @@ react-router-breadcrumbs-hoc@1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/react-router-breadcrumbs-hoc/-/react-router-breadcrumbs-hoc-1.1.2.tgz#4fafb620e7c6b876d98f7151f4c85ae5c3157dc0" -react-router-dom@4.2.2, react-router-dom@^4.2.2: +react-router-dom@4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-4.2.2.tgz#c8a81df3adc58bba8a76782e946cbd4eae649b8d" dependencies: @@ -11084,37 +11054,50 @@ react-router-dom@4.2.2, react-router-dom@^4.2.2: react-router "^4.2.0" warning "^3.0.0" -react-router@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-4.2.0.tgz#61f7b3e3770daeb24062dae3eedef1b054155986" +react-router-dom@^4.2.2: + version "4.3.1" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-4.3.1.tgz#4c2619fc24c4fa87c9fd18f4fb4a43fe63fbd5c6" dependencies: history "^4.7.2" - hoist-non-react-statics "^2.3.0" - invariant "^2.2.2" + invariant "^2.2.4" + loose-envify "^1.3.1" + prop-types "^15.6.1" + react-router "^4.3.1" + warning "^4.0.1" + +react-router@^4.2.0, react-router@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-4.3.1.tgz#aada4aef14c809cb2e686b05cee4742234506c4e" + dependencies: + history "^4.7.2" + hoist-non-react-statics "^2.5.0" + invariant "^2.2.4" loose-envify "^1.3.1" path-to-regexp "^1.7.0" - prop-types "^15.5.4" - warning "^3.0.0" + prop-types "^15.6.1" + warning "^4.0.1" react-select@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/react-select/-/react-select-1.2.1.tgz#a2fe58a569eb14dcaa6543816260b97e538120d1" + version "1.3.0" + resolved "https://registry.yarnpkg.com/react-select/-/react-select-1.3.0.tgz#1828ad5bf7f3e42a835c7e2d8cb13b5c20714876" dependencies: classnames "^2.2.4" prop-types "^15.5.8" react-input-autosize "^2.1.2" react-sizeme@^2.3.6: - version "2.3.6" - resolved "https://registry.yarnpkg.com/react-sizeme/-/react-sizeme-2.3.6.tgz#d60ea2634acc3fd827a3c7738d41eea0992fa678" + version "2.5.2" + resolved "https://registry.yarnpkg.com/react-sizeme/-/react-sizeme-2.5.2.tgz#e7041390cfb895ed15d896aa91d76e147e3b70b5" dependencies: element-resize-detector "^1.1.12" invariant "^2.2.2" - lodash "^4.17.4" + lodash.debounce "^4.0.8" + lodash.throttle "^4.1.1" + shallowequal "^1.0.2" react-sticky@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/react-sticky/-/react-sticky-6.0.1.tgz#356988bdcc6fc8cd2d89746d2302edce67d86687" + version "6.0.3" + resolved "https://registry.yarnpkg.com/react-sticky/-/react-sticky-6.0.3.tgz#7a18b643e1863da113d7f7036118d2a75d9ecde4" dependencies: prop-types "^15.5.8" raf "^3.3.0" @@ -11128,13 +11111,13 @@ react-syntax-highlighter@^5.7.0: lowlight "~1.9.1" react-test-renderer@^16.0.0-0: - version "16.4.0" - resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.4.0.tgz#0dbe0e24263e94e1830c7afb1f403707fad313a3" + version "16.4.2" + resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.4.2.tgz#4e03eca9359bb3210d4373f7547d1364218ef74e" dependencies: fbjs "^0.8.16" object-assign "^4.1.1" prop-types "^15.6.0" - react-is "^16.4.0" + react-is "^16.4.2" react-toggle@4.0.2: version "4.0.2" @@ -11143,14 +11126,15 @@ react-toggle@4.0.2: classnames "^2.2.5" react-virtualized@^9.18.5: - version "9.18.5" - resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-9.18.5.tgz#42dd390ebaa7ea809bfcaf775d39872641679b89" + version "9.20.1" + resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-9.20.1.tgz#02dc08fe9070386b8c48e2ac56bce7af0208d22d" dependencies: babel-runtime "^6.26.0" classnames "^2.2.3" dom-helpers "^2.4.0 || ^3.0.0" loose-envify "^1.3.0" prop-types "^15.6.0" + react-lifecycles-compat "^3.0.4" react-vis@1.10.2: version "1.10.2" @@ -11175,8 +11159,8 @@ react-vis@1.10.2: react-motion "^0.5.2" react-vis@^1.8.1: - version "1.9.2" - resolved "https://registry.yarnpkg.com/react-vis/-/react-vis-1.9.2.tgz#4dbd5d91ac820fd39fa7ad1c892198495194f6e3" + version "1.10.5" + resolved "https://registry.yarnpkg.com/react-vis/-/react-vis-1.10.5.tgz#f04a78bc66f44912a586d9150db57da41ad994e0" dependencies: d3-array "^1.2.0" d3-collection "^1.0.3" @@ -11192,21 +11176,13 @@ react-vis@^1.8.1: d3-voronoi "^1.1.2" deep-equal "^1.0.1" global "^4.3.1" + hoek "4.2.1" prop-types "^15.5.8" react-motion "^0.5.2" -react@>=0.13.3, react@^16.2.0: - version "16.2.0" - resolved "https://registry.yarnpkg.com/react/-/react-16.2.0.tgz#a31bd2dab89bff65d42134fa187f24d054c273ba" - dependencies: - fbjs "^0.8.16" - loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.0" - -react@^16.3.0: - version "16.3.1" - resolved "https://registry.yarnpkg.com/react/-/react-16.3.1.tgz#4a2da433d471251c69b6033ada30e2ed1202cfd8" +react@>=0.13.3, react@^16.2.0, react@^16.3.0: + version "16.4.2" + resolved "https://registry.yarnpkg.com/react/-/react-16.4.2.tgz#2cd90154e3a9d9dd8da2991149fdca3c260e129f" dependencies: fbjs "^0.8.16" loose-envify "^1.1.0" @@ -11284,7 +11260,7 @@ read-pkg@^2.0.0: normalize-package-data "^2.3.2" path-type "^2.0.0" -"readable-stream@1 || 2": +"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.0, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.3: version "2.3.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" dependencies: @@ -11296,18 +11272,6 @@ read-pkg@^2.0.0: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@~2.3.3: - version "2.3.5" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.5.tgz#b4f85003a938cbb6ecbce2a124fb1012bd1a838d" - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.0.3" - util-deprecate "~1.0.1" - readable-stream@~1.0.2: version "1.0.34" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" @@ -11355,11 +11319,17 @@ readline2@^1.0.1: mute-stream "0.0.5" realpath-native@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-1.0.0.tgz#7885721a83b43bd5327609f0ddecb2482305fdf0" + version "1.0.1" + resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-1.0.1.tgz#07f40a0cce8f8261e2e8b7ebebf5c95965d7b633" dependencies: util.promisify "^1.0.0" +rechoir@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" + dependencies: + resolve "^1.1.6" + redent@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" @@ -11389,8 +11359,8 @@ reduce-function-call@^1.0.1: balanced-match "^0.4.2" reduce-reducers@^0.1.0: - version "0.1.2" - resolved "https://registry.yarnpkg.com/reduce-reducers/-/reduce-reducers-0.1.2.tgz#fa1b4718bc5292a71ddd1e5d839c9bea9770f14b" + version "0.1.5" + resolved "https://registry.yarnpkg.com/reduce-reducers/-/reduce-reducers-0.1.5.tgz#ff77ca8068ff41007319b8b4b91533c7e0e54576" redux-actions@2.2.1: version "2.2.1" @@ -11426,8 +11396,8 @@ redux@4.0.0: symbol-observable "^1.2.0" regenerate@^1.2.1: - version "1.3.3" - resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.3.tgz#0c336d3980553d755c39b586ae3b20aa49c82b7f" + version "1.4.0" + resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11" regenerator-runtime@^0.10.0: version "0.10.5" @@ -11548,8 +11518,8 @@ remove-trailing-separator@^1.0.1: resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" repeat-element@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.2.tgz#ef089a178d1483baae4d93eb98b4f9e4e11d990a" + version "1.1.3" + resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" repeat-string@^0.2.2: version "0.2.2" @@ -11593,7 +11563,7 @@ request-promise-native@^1.0.5: stealthy-require "^1.1.0" tough-cookie ">=2.3.3" -request@2, request@^2.87.0: +request@2.87.0: version "2.87.0" resolved "https://registry.yarnpkg.com/request/-/request-2.87.0.tgz#32f00235cd08d482b4d0d68db93a829c0ed5756e" dependencies: @@ -11618,84 +11588,30 @@ request@2, request@^2.87.0: tunnel-agent "^0.6.0" uuid "^3.1.0" -request@2.81.0: - version "2.81.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" - dependencies: - aws-sign2 "~0.6.0" - aws4 "^1.2.1" - caseless "~0.12.0" - combined-stream "~1.0.5" - extend "~3.0.0" - forever-agent "~0.6.1" - form-data "~2.1.1" - har-validator "~4.2.1" - hawk "~3.1.3" - http-signature "~1.1.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.7" - oauth-sign "~0.8.1" - performance-now "^0.2.0" - qs "~6.4.0" - safe-buffer "^5.0.1" - stringstream "~0.0.4" - tough-cookie "~2.3.0" - tunnel-agent "^0.6.0" - uuid "^3.0.0" - -request@^2.55.0, request@^2.65.0, request@^2.83.0, request@^2.85.0: - version "2.85.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.85.0.tgz#5a03615a47c61420b3eb99b7dba204f83603e1fa" +request@^2.55.0, request@^2.65.0, request@^2.85.0, request@^2.87.0: + version "2.88.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" dependencies: aws-sign2 "~0.7.0" - aws4 "^1.6.0" + aws4 "^1.8.0" caseless "~0.12.0" - combined-stream "~1.0.5" - extend "~3.0.1" + combined-stream "~1.0.6" + extend "~3.0.2" forever-agent "~0.6.1" - form-data "~2.3.1" - har-validator "~5.0.3" - hawk "~6.0.2" + form-data "~2.3.2" + har-validator "~5.1.0" http-signature "~1.2.0" is-typedarray "~1.0.0" isstream "~0.1.2" json-stringify-safe "~5.0.1" - mime-types "~2.1.17" - oauth-sign "~0.8.2" + mime-types "~2.1.19" + oauth-sign "~0.9.0" performance-now "^2.1.0" - qs "~6.5.1" - safe-buffer "^5.1.1" - stringstream "~0.0.5" - tough-cookie "~2.3.3" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.4.3" tunnel-agent "^0.6.0" - uuid "^3.1.0" - -request@~2.79.0: - version "2.79.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de" - dependencies: - aws-sign2 "~0.6.0" - aws4 "^1.2.1" - caseless "~0.11.0" - combined-stream "~1.0.5" - extend "~3.0.0" - forever-agent "~0.6.1" - form-data "~2.1.1" - har-validator "~2.0.6" - hawk "~3.1.3" - http-signature "~1.1.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.7" - oauth-sign "~0.8.1" - qs "~6.3.0" - stringstream "~0.0.4" - tough-cookie "~2.3.0" - tunnel-agent "~0.4.1" - uuid "^3.0.0" + uuid "^3.3.2" require-directory@^2.1.1: version "2.1.1" @@ -11722,7 +11638,7 @@ requirefresh@^2.0.0: dependencies: editions "^1.1.1" -requires-port@1.x.x: +requires-port@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" @@ -11744,6 +11660,13 @@ resolve-cwd@^2.0.0: dependencies: resolve-from "^3.0.0" +resolve-dir@^1.0.0, resolve-dir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43" + dependencies: + expand-tilde "^2.0.0" + global-modules "^1.0.0" + resolve-from@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226" @@ -11762,7 +11685,7 @@ resolve-pathname@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-2.2.0.tgz#7e9ae21ed815fd63ab189adeee64dc831eefa879" -resolve-url@^0.2.1, resolve-url@~0.2.1: +resolve-url@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" @@ -11770,24 +11693,12 @@ resolve@1.1.7, resolve@1.1.x, resolve@~1.1.0, resolve@~1.1.7: version "1.1.7" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" -resolve@^1.1.5, resolve@^1.1.7, resolve@^1.2.0, resolve@^1.5.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.6.0.tgz#0fbd21278b27b4004481c395349e7aba60a9ff5c" - dependencies: - path-parse "^1.0.5" - -resolve@^1.1.6: +resolve@^1.1.5, resolve@^1.1.6, resolve@^1.1.7, resolve@^1.2.0, resolve@^1.3.2, resolve@^1.5.0, resolve@^1.7.1: version "1.8.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.8.1.tgz#82f1ec19a423ac1fbd080b0bab06ba36e84a7a26" dependencies: path-parse "^1.0.5" -resolve@^1.3.2, resolve@^1.7.1: - version "1.7.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.7.1.tgz#aadd656374fd298aee895bc026b8297418677fd3" - dependencies: - path-parse "^1.0.5" - responselike@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" @@ -11818,9 +11729,9 @@ ret@~0.1.10: version "0.1.15" resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" -retry@^0.10.0, retry@^0.10.1: - version "0.10.1" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.10.1.tgz#e76388d217992c252750241d3d3956fed98d8ff4" +retry@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" right-align@^0.1.1: version "0.1.3" @@ -11828,7 +11739,7 @@ right-align@^0.1.1: dependencies: align-text "^0.1.1" -rimraf@2, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.5.4, rimraf@^2.6.0, rimraf@^2.6.1, rimraf@^2.6.2: +rimraf@2, rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.0, rimraf@^2.6.1, rimraf@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" dependencies: @@ -11845,10 +11756,10 @@ rimraf@~2.2.8: resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.2.8.tgz#e439be2aaee327321952730f99a8929e4fc50582" ripemd160@^2.0.0, ripemd160@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.1.tgz#0f4584295c53a3628af7e6d79aca21ce57d1c6e7" + version "2.0.2" + resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" dependencies: - hash-base "^2.0.0" + hash-base "^3.0.0" inherits "^2.0.1" rison-node@0.3.1: @@ -11866,6 +11777,10 @@ rst-selector-parser@^2.2.3: lodash.flattendeep "^4.4.0" nearley "^2.7.10" +rsvp@^3.3.3: + version "3.6.2" + resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-3.6.2.tgz#2e96491599a96cde1b515d5674a8f7a91452926a" + run-async@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/run-async/-/run-async-0.1.0.tgz#c8ad4a5e110661e402a7d21b530e009f25f8e389" @@ -11902,21 +11817,19 @@ rx-lite@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102" -rxjs@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.1.0.tgz#833447de4e4f6427b9cec3e5eb9f56415cd28315" +rxjs@^6.1.0, rxjs@^6.2.1: + version "6.2.2" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.2.2.tgz#eb75fa3c186ff5289907d06483a77884586e1cf9" dependencies: tslib "^1.9.0" -rxjs@^6.2.1: - version "6.2.1" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.2.1.tgz#246cebec189a6cbc143a3ef9f62d6f4c91813ca1" - dependencies: - tslib "^1.9.0" +safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" -safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" +safe-json-parse@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/safe-json-parse/-/safe-json-parse-1.0.1.tgz#3e76723e38dfdda13c9b1d29a1e07ffee4b30b57" safe-regex@^1.1.0: version "1.1.0" @@ -11931,7 +11844,7 @@ safefs@^4.0.0: editions "^1.1.1" graceful-fs "^4.1.4" -"safer-buffer@>= 2.1.2 < 3": +"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" @@ -11940,10 +11853,11 @@ samsam@1.3.0: resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.3.0.tgz#8d1d9350e25622da30de3e44ba692b5221ab7c50" sane@^2.0.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/sane/-/sane-2.5.0.tgz#6359cd676f5efd9988b264d8ce3b827dd6b27bec" + version "2.5.2" + resolved "https://registry.yarnpkg.com/sane/-/sane-2.5.2.tgz#b4dc1861c21b427e929507a3e751e2a2cb8ab3fa" dependencies: anymatch "^2.0.0" + capture-exit "^1.2.0" exec-sh "^0.2.0" fb-watchman "^2.0.0" micromatch "^3.1.4" @@ -11951,11 +11865,11 @@ sane@^2.0.0: walker "~1.0.5" watch "~0.18.0" optionalDependencies: - fsevents "^1.1.1" + fsevents "^1.2.3" sao@^0.22.12: - version "0.22.15" - resolved "https://registry.yarnpkg.com/sao/-/sao-0.22.15.tgz#9e1cce275ca85b2f1cc24a0e7a502d1bf354b465" + version "0.22.17" + resolved "https://registry.yarnpkg.com/sao/-/sao-0.22.17.tgz#f678ab240cd1695260491fba9f3a864cd1fc02e9" dependencies: boxen "^1.2.2" cac "^4.3.4" @@ -11977,7 +11891,7 @@ sao@^0.22.12: tildify "^1.2.0" update-notifier "^2.2.0" user-home "^2.0.0" - yarn-install "^0.5.1" + yarn-install "^1.0.0" sass-graph@^2.2.4: version "2.2.4" @@ -12012,8 +11926,8 @@ script-loader@0.7.2: raw-loader "~0.5.1" scroll-into-view@^1.3.0: - version "1.9.1" - resolved "https://registry.yarnpkg.com/scroll-into-view/-/scroll-into-view-1.9.1.tgz#90c3b338422f9fddaebad90e6954790940dc9c1e" + version "1.9.3" + resolved "https://registry.yarnpkg.com/scroll-into-view/-/scroll-into-view-1.9.3.tgz#e5977ded33de31024508f3444c959dfa3711f2f8" scss-tokenizer@^0.2.3: version "0.2.3" @@ -12039,8 +11953,8 @@ semver-diff@^2.0.0: semver "^5.0.3" "semver@2 || 3 || 4 || 5", semver@^5.0.1, semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" + version "5.5.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.1.tgz#7dfdd8814bdb7cabc7be0fb1d734cfb66c940477" semver@5.1.0: version "5.1.0" @@ -12088,9 +12002,9 @@ setimmediate@^1.0.4, setimmediate@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" -setprototypeof@1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04" +setprototypeof@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" sha.js@^2.4.0, sha.js@^2.4.8: version "2.4.11" @@ -12103,6 +12017,10 @@ shallow-copy@~0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/shallow-copy/-/shallow-copy-0.0.1.tgz#415f42702d73d810330292cc5ee86eae1a11a170" +shallowequal@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" + shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" @@ -12149,8 +12067,8 @@ simple-git@1.37.0: resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-1.37.0.tgz#a5d522dd4e97c6091f657766c28a323738233f0f" simple-git@^1.91.0: - version "1.92.0" - resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-1.92.0.tgz#6061468eb7d19f0141078fc742e62457e910f547" + version "1.96.0" + resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-1.96.0.tgz#d0279b771ed89a2d1e7d9e15f9abd7f26e319d7c" dependencies: debug "^3.1.0" @@ -12161,16 +12079,16 @@ simple-swizzle@^0.2.2: is-arrayish "^0.3.1" sinon@^5.0.7: - version "5.0.7" - resolved "https://registry.yarnpkg.com/sinon/-/sinon-5.0.7.tgz#3bded6a73613ccc9e512e20246ced69a27c27dab" + version "5.1.1" + resolved "https://registry.yarnpkg.com/sinon/-/sinon-5.1.1.tgz#19c59810ffb733ea6e76a28b94a71fc4c2f523b8" dependencies: "@sinonjs/formatio" "^2.0.0" - diff "^3.1.0" + diff "^3.5.0" lodash.get "^4.4.2" - lolex "^2.2.0" - nise "^1.2.0" - supports-color "^5.1.0" - type-detect "^4.0.5" + lolex "^2.4.2" + nise "^1.3.3" + supports-color "^5.4.0" + type-detect "^4.0.8" slash@^1.0.0: version "1.0.0" @@ -12217,18 +12135,6 @@ snapdragon@^0.8.1: source-map-resolve "^0.5.0" use "^3.1.0" -sntp@1.x.x: - version "1.0.9" - resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198" - dependencies: - hoek "2.x.x" - -sntp@2.x.x: - version "2.1.0" - resolved "https://registry.yarnpkg.com/sntp/-/sntp-2.1.0.tgz#2c6cec14fedc2222739caf9b5c3d85d1cc5a2cc8" - dependencies: - hoek "4.x.x" - socket.io-adapter@0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-0.5.0.tgz#cb6d4bb8bec81e1078b99677f9ced0046066bb8b" @@ -12289,20 +12195,11 @@ source-list-map@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.0.tgz#aaa47403f7b245a92fbc97ea08f250d6087ed085" -source-map-resolve@^0.3.0: - version "0.3.1" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.3.1.tgz#610f6122a445b8dd51535a2a71b783dfc1248761" - dependencies: - atob "~1.1.0" - resolve-url "~0.2.1" - source-map-url "~0.3.0" - urix "~0.1.0" - -source-map-resolve@^0.5.0: - version "0.5.1" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.1.tgz#7ad0f593f2281598e854df80f19aae4b92d7a11a" +source-map-resolve@^0.5.0, source-map-resolve@^0.5.1: + version "0.5.2" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259" dependencies: - atob "^2.0.0" + atob "^2.1.1" decode-uri-component "^0.2.0" resolve-url "^0.2.1" source-map-url "^0.4.0" @@ -12314,15 +12211,9 @@ source-map-support@^0.4.15, source-map-support@^0.4.2: dependencies: source-map "^0.5.6" -source-map-support@^0.5.0: - version "0.5.4" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.4.tgz#54456efa89caa9270af7cd624cc2f123e51fbae8" - dependencies: - source-map "^0.6.0" - -source-map-support@^0.5.5, source-map-support@^0.5.6: - version "0.5.6" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.6.tgz#4435cee46b1aab62b8e8610ce60f788091c51c13" +source-map-support@^0.5.0, source-map-support@^0.5.5, source-map-support@^0.5.6: + version "0.5.9" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.9.tgz#41bc953b2534267ea2d605bccfa7bfa3111ced5f" dependencies: buffer-from "^1.0.0" source-map "^0.6.0" @@ -12331,17 +12222,9 @@ source-map-url@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" -source-map-url@~0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.3.0.tgz#7ecaf13b57bcd09da8a40c5d269db33799d4aaf9" - -source-map@0.5.x, source-map@^0.5.0, source-map@^0.5.1, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.1: - version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - source-map@0.X: - version "0.7.2" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.2.tgz#115c3e891aaa9a484869fd2b89391a225feba344" + version "0.7.3" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" source-map@^0.1.38, source-map@~0.1.30: version "0.1.43" @@ -12355,7 +12238,11 @@ source-map@^0.4.2, source-map@^0.4.4: dependencies: amdefine ">=0.0.4" -source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: +source-map@^0.5.0, source-map@^0.5.1, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.1: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + +source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" @@ -12436,8 +12323,8 @@ spdx-satisfies@^0.1.3: spdx-expression-parse "^1.0.0" spdx@^0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/spdx/-/spdx-0.5.1.tgz#d36c275088b48d75a9046cd44a838ce4b5339998" + version "0.5.2" + resolved "https://registry.yarnpkg.com/spdx/-/spdx-0.5.2.tgz#76a428b9b97e7904ef83e62a4af0d06fdb50c265" dependencies: spdx-exceptions "^1.0.0" spdx-license-ids "^1.0.0" @@ -12454,22 +12341,19 @@ split@0.3: dependencies: through "2" -sprintf-js@^1.0.3: - version "1.1.1" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.1.tgz#36be78320afe5801f6cea3ee78b6e5aab940ea0c" - sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" sshpk@^1.7.0: - version "1.14.1" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.14.1.tgz#130f5975eddad963f1d56f92b9ac6c51fa9f83eb" + version "1.14.2" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.14.2.tgz#c6fc61648a3d9c4e764fd3fcdf4ea105e492ba98" dependencies: asn1 "~0.2.3" assert-plus "^1.0.0" dashdash "^1.12.0" getpass "^0.1.1" + safer-buffer "^2.0.2" optionalDependencies: bcrypt-pbkdf "^1.0.0" ecc-jsbn "~0.1.1" @@ -12487,8 +12371,8 @@ stack-utils@^1.0.1: resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.1.tgz#d4f33ab54e8e38778b0ca5cfd3b3afb12db68620" state-toggle@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/state-toggle/-/state-toggle-1.0.0.tgz#d20f9a616bb4f0c3b98b91922d25b640aa2bc425" + version "1.0.1" + resolved "https://registry.yarnpkg.com/state-toggle/-/state-toggle-1.0.1.tgz#c3cb0974f40a6a0f8e905b96789eb41afa1cde3a" statehood@4.x.x: version "4.1.0" @@ -12526,8 +12410,8 @@ static-extend@^0.1.1: object-copy "^0.1.0" static-module@^2.2.0: - version "2.2.2" - resolved "https://registry.yarnpkg.com/static-module/-/static-module-2.2.2.tgz#496e9bf1d29cb5177a30fae224511953d7dd291b" + version "2.2.5" + resolved "https://registry.yarnpkg.com/static-module/-/static-module-2.2.5.tgz#bd40abceae33da6b7afb84a0e4329ff8852bfbbf" dependencies: concat-stream "~1.6.0" convert-source-map "^1.5.1" @@ -12544,9 +12428,9 @@ static-module@^2.2.0: static-eval "^2.0.0" through2 "~2.0.3" -statuses@1, "statuses@>= 1.3.1 < 2": - version "1.4.0" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087" +"statuses@>= 1.4.0 < 2": + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" statuses@~1.3.1: version "1.3.1" @@ -12583,12 +12467,12 @@ stream-each@^1.1.0: stream-shift "^1.0.0" stream-http@^2.7.2: - version "2.8.1" - resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.1.tgz#d0441be1a457a73a733a8a7b53570bebd9ef66a4" + version "2.8.3" + resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc" dependencies: builtin-status-codes "^3.0.0" inherits "^2.0.1" - readable-stream "^2.3.3" + readable-stream "^2.3.6" to-arraybuffer "^1.0.0" xtend "^4.0.0" @@ -12623,6 +12507,10 @@ string-length@^2.0.0: astral-regex "^1.0.0" strip-ansi "^4.0.0" +string-template@~0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add" + string-width@^1.0.1, string-width@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" @@ -12631,39 +12519,23 @@ string-width@^1.0.1, string-width@^1.0.2: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -string-width@^2.0.0, string-width@^2.1.0, string-width@^2.1.1: +"string-width@^1.0.2 || 2", string-width@^2.0.0, string-width@^2.1.0, string-width@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" dependencies: is-fullwidth-code-point "^2.0.0" strip-ansi "^4.0.0" -string_decoder@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.0.tgz#384f322ee8a848e500effde99901bba849c5d403" - dependencies: - safe-buffer "~5.1.0" - -string_decoder@~0.10.x: +string_decoder@0.10, string_decoder@~0.10.x: version "0.10.31" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" -string_decoder@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab" - dependencies: - safe-buffer "~5.1.0" - -string_decoder@~1.1.1: +string_decoder@^1.0.0, string_decoder@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" dependencies: safe-buffer "~5.1.0" -stringstream@~0.0.4, stringstream@~0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878" - strip-ansi@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-0.3.0.tgz#25f48ea22ca79187f3174a4db8759347bb126220" @@ -12717,8 +12589,8 @@ strip-json-comments@^2.0.1, strip-json-comments@~2.0.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" strip-outer@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/strip-outer/-/strip-outer-1.0.0.tgz#aac0ba60d2e90c5d4f275fd8869fd9a2d310ffb8" + version "1.0.1" + resolved "https://registry.yarnpkg.com/strip-outer/-/strip-outer-1.0.1.tgz#b2fd2abf6604b9d1e6013057195df836b8a9d631" dependencies: escape-string-regexp "^1.0.2" @@ -12749,8 +12621,8 @@ stylis-rule-sheet@^0.0.10: resolved "https://registry.yarnpkg.com/stylis-rule-sheet/-/stylis-rule-sheet-0.0.10.tgz#44e64a2b076643f4b52e5ff71efc04d8c3c4a430" stylis@^3.5.0: - version "3.5.1" - resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.5.1.tgz#fd341d59f57f9aeb412bc14c9d8a8670b438e03b" + version "3.5.3" + resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.5.3.tgz#99fdc46afba6af4deff570825994181a5e6ce546" subarg@^1.0.0: version "1.0.0" @@ -12783,19 +12655,19 @@ suffix@^0.1.0: resolved "https://registry.yarnpkg.com/suffix/-/suffix-0.1.1.tgz#cc58231646a0ef1102f79478ef3a9248fd9c842f" superagent@^3.0.0: - version "3.8.2" - resolved "https://registry.yarnpkg.com/superagent/-/superagent-3.8.2.tgz#e4a11b9d047f7d3efeb3bbe536d9ec0021d16403" + version "3.8.3" + resolved "https://registry.yarnpkg.com/superagent/-/superagent-3.8.3.tgz#460ea0dbdb7d5b11bc4f78deba565f86a178e128" dependencies: component-emitter "^1.2.0" cookiejar "^2.1.0" debug "^3.1.0" extend "^3.0.0" form-data "^2.3.1" - formidable "^1.1.1" + formidable "^1.2.0" methods "^1.1.1" mime "^1.4.1" qs "^6.5.1" - readable-stream "^2.0.5" + readable-stream "^2.3.5" supertest-as-promised@4.0.2: version "4.0.2" @@ -12837,15 +12709,9 @@ supports-color@^4.2.1: dependencies: has-flag "^2.0.0" -supports-color@^5.1.0, supports-color@^5.4.0: - version "5.4.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54" - dependencies: - has-flag "^3.0.0" - -supports-color@^5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.3.0.tgz#5b24ac15db80fa927cf5227a4a33fd3c4c7676c0" +supports-color@^5.3.0, supports-color@^5.4.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" dependencies: has-flag "^3.0.0" @@ -12874,8 +12740,8 @@ tabbable@1.1.0: resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-1.1.0.tgz#2c9a9c9f09db5bb0659f587d532548dd6ef2067b" tabbable@^1.0.3, tabbable@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-1.1.2.tgz#b171680aea6e0a3e9281ff23532e2e5de11c0d94" + version "1.1.3" + resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-1.1.3.tgz#0e4ee376f3631e42d7977a074dbd2b3827843081" table@^4.0.1: version "4.0.3" @@ -12904,44 +12770,25 @@ tar-fs@1.13.0: pump "^1.0.0" tar-stream "^1.1.2" -tar-fs@^1.16.0: - version "1.16.0" - resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-1.16.0.tgz#e877a25acbcc51d8c790da1c57c9cf439817b896" - dependencies: - chownr "^1.0.1" - mkdirp "^0.5.1" - pump "^1.0.0" - tar-stream "^1.1.2" - -tar-fs@^1.16.2: - version "1.16.2" - resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-1.16.2.tgz#17e5239747e399f7e77344f5f53365f04af53577" +tar-fs@^1.16.0, tar-fs@^1.16.2: + version "1.16.3" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-1.16.3.tgz#966a628841da2c4010406a82167cbd5e0c72d509" dependencies: chownr "^1.0.1" mkdirp "^0.5.1" pump "^1.0.0" tar-stream "^1.1.2" -tar-pack@^3.4.0: - version "3.4.1" - resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.4.1.tgz#e1dbc03a9b9d3ba07e896ad027317eb679a10a1f" - dependencies: - debug "^2.2.0" - fstream "^1.0.10" - fstream-ignore "^1.0.5" - once "^1.3.3" - readable-stream "^2.1.4" - rimraf "^2.5.1" - tar "^2.2.1" - uid-number "^0.0.6" - tar-stream@^1.1.2, tar-stream@^1.5.2: - version "1.5.5" - resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.5.5.tgz#5cad84779f45c83b1f2508d96b09d88c7218af55" + version "1.6.1" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.1.tgz#f84ef1696269d6223ca48f6e1eeede3f7e81f395" dependencies: bl "^1.0.0" + buffer-alloc "^1.1.0" end-of-stream "^1.0.0" - readable-stream "^2.0.0" + fs-constants "^1.0.0" + readable-stream "^2.3.0" + to-buffer "^1.1.0" xtend "^4.0.0" tar@2.2.0: @@ -12962,7 +12809,7 @@ tar@4.0.2: mkdirp "^0.5.0" yallist "^3.0.2" -tar@^2.0.0, tar@^2.2.1: +tar@^2.0.0: version "2.2.1" resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1" dependencies: @@ -12970,6 +12817,18 @@ tar@^2.0.0, tar@^2.2.1: fstream "^1.0.2" inherits "2" +tar@^4: + version "4.4.6" + resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.6.tgz#63110f09c00b4e60ac8bcfe1bf3c8660235fbc9b" + dependencies: + chownr "^1.0.1" + fs-minipass "^1.2.5" + minipass "^2.3.3" + minizlib "^1.1.0" + mkdirp "^0.5.0" + safe-buffer "^5.1.2" + yallist "^3.0.2" + teamwork@3.x.x: version "3.0.1" resolved "https://registry.yarnpkg.com/teamwork/-/teamwork-3.0.1.tgz#ff38c7161f41f8070b7813716eb6154036ece196" @@ -12980,7 +12839,7 @@ term-size@^1.2.0: dependencies: execa "^0.7.0" -test-exclude@^4.1.1, test-exclude@^4.2.1: +test-exclude@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-4.2.1.tgz#dfa222f03480bca69207ca728b37d74b45f724fa" dependencies: @@ -12991,8 +12850,8 @@ test-exclude@^4.1.1, test-exclude@^4.2.1: require-main-filename "^1.0.1" tether@^1.3.7: - version "1.4.3" - resolved "https://registry.yarnpkg.com/tether/-/tether-1.4.3.tgz#fd547024c47b6e5c9b87e1880f997991a9a6ad54" + version "1.4.4" + resolved "https://registry.yarnpkg.com/tether/-/tether-1.4.4.tgz#9dc6eb2b3e601da2098fd264e7f7a8b264de1125" text-encoding@^0.6.4: version "0.6.4" @@ -13043,8 +12902,8 @@ timed-out@^4.0.0, timed-out@^4.0.1: resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" timers-browserify@^2.0.4: - version "2.0.6" - resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.6.tgz#241e76927d9ca05f4d959819022f5b3664b64bae" + version "2.0.10" + resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.10.tgz#1d28e3d2aadf1d5a5996c4e9f95601cd053480ae" dependencies: setimmediate "^1.0.4" @@ -13056,16 +12915,16 @@ tiny-inflate@^1.0.0, tiny-inflate@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/tiny-inflate/-/tiny-inflate-1.0.2.tgz#93d9decffc8805bd57eae4310f0b745e9b6fb3a7" -tiny-lr@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/tiny-lr/-/tiny-lr-0.2.1.tgz#b3fdba802e5d56a33c2f6f10794b32e477ac729d" +tiny-lr@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/tiny-lr/-/tiny-lr-1.1.1.tgz#9fa547412f238fedb068ee295af8b682c98b2aab" dependencies: - body-parser "~1.14.0" - debug "~2.2.0" + body "^5.1.0" + debug "^3.1.0" faye-websocket "~0.10.0" - livereload-js "^2.2.0" - parseurl "~1.3.0" - qs "~5.1.0" + livereload-js "^2.3.0" + object-assign "^4.1.0" + qs "^6.4.0" tinycolor2@1.3.0: version "1.3.0" @@ -13126,6 +12985,10 @@ to-arraybuffer@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" +to-buffer@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80" + to-fast-properties@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" @@ -13147,7 +13010,7 @@ to-regex-range@^2.1.0: is-number "^3.0.0" repeat-string "^1.6.1" -to-regex@^3.0.1: +to-regex@^3.0.1, to-regex@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" dependencies: @@ -13190,13 +13053,20 @@ topojson-client@3, topojson-client@3.0.0: dependencies: commander "2" -tough-cookie@>=2.3.3, tough-cookie@^2.3.1, tough-cookie@^2.3.3, tough-cookie@~2.3.0, tough-cookie@~2.3.3: +tough-cookie@>=2.3.3, tough-cookie@^2.3.1, tough-cookie@^2.3.4, tough-cookie@~2.4.3: + version "2.4.3" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" + dependencies: + psl "^1.1.24" + punycode "^1.4.1" + +tough-cookie@~2.3.3: version "2.3.4" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.4.tgz#ec60cee38ac675063ffc97a5c18970578ee83655" dependencies: punycode "^1.4.1" -tr46@^1.0.0: +tr46@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" dependencies: @@ -13219,10 +13089,10 @@ treeify@^1.0.1: resolved "https://registry.yarnpkg.com/treeify/-/treeify-1.1.0.tgz#4e31c6a463accd0943879f30667c4fdaff411bb8" trie-search@^1.0.1: - version "1.0.9" - resolved "https://registry.yarnpkg.com/trie-search/-/trie-search-1.0.9.tgz#1587757478c3900b0120c5aaf53e048cfaaff24a" + version "1.2.8" + resolved "https://registry.yarnpkg.com/trie-search/-/trie-search-1.2.8.tgz#9cca9c4d0df7b6638ad56e5e6b1a90307b1a94af" dependencies: - hasharray "^1.0.0" + hasharray "^1.1.1" trim-newlines@^1.0.0: version "1.0.0" @@ -13239,16 +13109,16 @@ trim-right@^1.0.1: resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" trim-trailing-lines@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/trim-trailing-lines/-/trim-trailing-lines-1.1.0.tgz#7aefbb7808df9d669f6da2e438cac8c46ada7684" + version "1.1.1" + resolved "https://registry.yarnpkg.com/trim-trailing-lines/-/trim-trailing-lines-1.1.1.tgz#e0ec0810fd3c3f1730516b45f49083caaf2774d9" trim@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/trim/-/trim-0.0.1.tgz#5858547f6b290757ee95cccc666fb50084c460dd" trough@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.1.tgz#a9fd8b0394b0ae8fff82e0633a0a36ccad5b5f86" + version "1.0.3" + resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.3.tgz#e29bd1614c6458d44869fc28b255ab7857ef7c24" "true-case-path@^1.0.2": version "1.0.2" @@ -13299,10 +13169,11 @@ ts-loader@^3.5.0: semver "^5.0.1" ts-node@^6.1.1: - version "6.1.1" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-6.1.1.tgz#19607140acb06150441fcdb61be11f73f7b6657e" + version "6.2.0" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-6.2.0.tgz#65a0ae2acce319ea4fd7ac8d7c9f1f90c5da6baf" dependencies: arrify "^1.0.0" + buffer-from "^1.1.0" diff "^3.1.0" make-error "^1.1.1" minimist "^1.2.0" @@ -13310,17 +13181,13 @@ ts-node@^6.1.1: source-map-support "^0.5.6" yn "^2.0.0" -tslib@^1.7.1, tslib@^1.8.0, tslib@^1.8.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.1.tgz#a5d1f0532a49221c87755cfcc89ca37197242ba7" - -tslib@^1.9.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.0.tgz#e37a86fda8cbbaf23a057f473c9f4dc64e5fc2e8" +tslib@^1.7.1, tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.2: + version "1.9.3" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" tslint-config-prettier@^1.12.0: - version "1.12.0" - resolved "https://registry.yarnpkg.com/tslint-config-prettier/-/tslint-config-prettier-1.12.0.tgz#bc8504f286ecf42b906f3d1126a093114f5729cc" + version "1.15.0" + resolved "https://registry.yarnpkg.com/tslint-config-prettier/-/tslint-config-prettier-1.15.0.tgz#76b9714399004ab6831fdcf76d89b73691c812cf" tslint-plugin-prettier@^1.3.0: version "1.3.0" @@ -13330,8 +13197,8 @@ tslint-plugin-prettier@^1.3.0: tslib "^1.7.1" tslint@^5.10.0: - version "5.10.0" - resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.10.0.tgz#11e26bccb88afa02dd0d9956cae3d4540b5f54c3" + version "5.11.0" + resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.11.0.tgz#98f30c02eae3cde7006201e4c33cb08b48581eed" dependencies: babel-code-frame "^6.22.0" builtin-modules "^1.1.1" @@ -13344,11 +13211,11 @@ tslint@^5.10.0: resolve "^1.3.2" semver "^5.3.0" tslib "^1.8.0" - tsutils "^2.12.1" + tsutils "^2.27.2" -tsutils@^2.12.1: - version "2.27.0" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.27.0.tgz#9efb252b188eaa0ca3ade41dc410d6ce7eaab816" +tsutils@^2.27.2: + version "2.29.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.29.0.tgz#32b488501467acbedd4b85498673a0812aca0b99" dependencies: tslib "^1.8.1" @@ -13362,10 +13229,6 @@ tunnel-agent@^0.6.0: dependencies: safe-buffer "^5.0.1" -tunnel-agent@~0.4.1: - version "0.4.3" - resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb" - tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" @@ -13384,11 +13247,11 @@ type-detect@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-1.0.0.tgz#762217cc06db258ec48908a1298e8b95121e8ea2" -type-detect@^4.0.5, type-detect@^4.0.8: +type-detect@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" -type-is@~1.6.10, type-is@~1.6.15: +type-is@~1.6.16: version "1.6.16" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.16.tgz#f89ce341541c672b25ee7ae3c73dee3b2be50194" dependencies: @@ -13409,9 +13272,9 @@ typescript@^2.9.2: version "2.9.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.2.tgz#1cbf61d05d6b96269244eb6a3bce4bd914e0f00c" -ua-parser-js@^0.7.9: - version "0.7.17" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.17.tgz#e9ec5f9498b9ec910e7ae3ac626a805c4d09ecac" +ua-parser-js@^0.7.18: + version "0.7.18" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.18.tgz#a7bfd92f56edfb117083b69e31d2aa8882d4b1ed" uc.micro@^1.0.1, uc.micro@^1.0.5: version "1.0.5" @@ -13446,8 +13309,8 @@ uglifyjs-webpack-plugin@^0.4.6: webpack-sources "^1.0.1" uglifyjs-webpack-plugin@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.2.7.tgz#57638dd99c853a1ebfe9d97b42160a8a507f9d00" + version "1.3.0" + resolved "https://registry.yarnpkg.com/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.3.0.tgz#75f548160858163a08643e086d5fefe18a5d67de" dependencies: cacache "^10.0.4" find-cache-dir "^1.0.0" @@ -13466,10 +13329,6 @@ ui-select@0.19.6: version "0.19.6" resolved "https://registry.yarnpkg.com/ui-select/-/ui-select-0.19.6.tgz#9a824ec9d5b04c3fdc7483fa4746dd3c528d87ab" -uid-number@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" - ultron@1.0.x: version "1.0.2" resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.0.2.tgz#ace116ab557cd197386a4e88f4685378c8b2e4fa" @@ -13504,13 +13363,6 @@ underscore.string@~3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/underscore.string/-/underscore.string-3.2.3.tgz#806992633665d5e5fcb4db1fb3a862eb68e9e6da" -underscore.string@~3.3.4: - version "3.3.4" - resolved "https://registry.yarnpkg.com/underscore.string/-/underscore.string-3.3.4.tgz#2c2a3f9f83e64762fdc45e6ceac65142864213db" - dependencies: - sprintf-js "^1.0.3" - util-deprecate "^1.0.2" - underscore@~1.4.4: version "1.4.4" resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.4.4.tgz#61a6a32010622afa07963bf325203cf12239d604" @@ -13520,8 +13372,8 @@ underscore@~1.7.0: resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.7.0.tgz#6bbaf0877500d36be34ecaa584e0db9fef035209" unherit@^1.0.4: - version "1.1.0" - resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.0.tgz#6b9aaedfbf73df1756ad9e316dd981885840cd7d" + version "1.1.1" + resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.1.tgz#132748da3e88eab767e08fabfbb89c5e9d28628c" dependencies: inherits "^2.0.1" xtend "^4.0.1" @@ -13541,15 +13393,14 @@ unicode-trie@^0.3.0: tiny-inflate "^1.0.0" unified@^6.1.5: - version "6.1.6" - resolved "https://registry.yarnpkg.com/unified/-/unified-6.1.6.tgz#5ea7f807a0898f1f8acdeefe5f25faa010cc42b1" + version "6.2.0" + resolved "https://registry.yarnpkg.com/unified/-/unified-6.2.0.tgz#7fbd630f719126d67d40c644b7e3f617035f6dba" dependencies: bail "^1.0.0" extend "^3.0.0" is-plain-obj "^1.1.0" trough "^1.0.0" vfile "^2.0.0" - x-is-function "^1.0.4" x-is-string "^0.1.0" union-value@^1.0.0: @@ -13565,12 +13416,6 @@ uniq@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" -uniqid@^4.0.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/uniqid/-/uniqid-4.1.1.tgz#89220ddf6b751ae52b5f72484863528596bb84c1" - dependencies: - macaddress "^0.2.8" - uniqs@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02" @@ -13600,29 +13445,39 @@ unique-string@^1.0.0: dependencies: crypto-random-string "^1.0.0" -unist-util-is@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-2.1.1.tgz#0c312629e3f960c66e931e812d3d80e77010947b" +unist-util-is@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-2.1.2.tgz#1193fa8f2bfbbb82150633f3a8d2eb9a1c1d55db" unist-util-remove-position@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/unist-util-remove-position/-/unist-util-remove-position-1.1.1.tgz#5a85c1555fc1ba0c101b86707d15e50fa4c871bb" + version "1.1.2" + resolved "https://registry.yarnpkg.com/unist-util-remove-position/-/unist-util-remove-position-1.1.2.tgz#86b5dad104d0bbfbeb1db5f5c92f3570575c12cb" dependencies: unist-util-visit "^1.1.0" unist-util-stringify-position@^1.0.0, unist-util-stringify-position@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-1.1.1.tgz#3ccbdc53679eed6ecf3777dd7f5e3229c1b6aa3c" + version "1.1.2" + resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-1.1.2.tgz#3f37fcf351279dcbca7480ab5889bb8a832ee1c6" + +unist-util-visit-parents@1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-1.1.2.tgz#f6e3afee8bdbf961c0e6f028ea3c0480028c3d06" + +unist-util-visit-parents@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-2.0.1.tgz#63fffc8929027bee04bfef7d2cce474f71cb6217" + dependencies: + unist-util-is "^2.1.2" unist-util-visit@^1.1.0, unist-util-visit@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-1.3.0.tgz#41ca7c82981fd1ce6c762aac397fc24e35711444" + version "1.4.0" + resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-1.4.0.tgz#1cb763647186dc26f5e1df5db6bd1e48b3cc2fb1" dependencies: - unist-util-is "^2.1.1" + unist-util-visit-parents "^2.0.0" universalify@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.1.tgz#fa71badd4437af4c148841e3b3b165f9e9e590b7" + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" @@ -13643,9 +13498,9 @@ unzip-response@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-2.0.1.tgz#d2f0f737d16b0615e72a6935ed04214572d56f97" -upath@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/upath/-/upath-1.0.4.tgz#ee2321ba0a786c50973db043a50b7bcba822361d" +upath@^1.0.5: + version "1.1.0" + resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.0.tgz#35256597e46a581db4793d0ce47fa9aebfc9fabd" update-notifier@^0.5.0: version "0.5.0" @@ -13660,26 +13515,27 @@ update-notifier@^0.5.0: string-length "^1.0.0" update-notifier@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-2.3.0.tgz#4e8827a6bb915140ab093559d7014e3ebb837451" + version "2.5.0" + resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-2.5.0.tgz#d0744593e13f161e406acb1d9408b72cad08aff6" dependencies: boxen "^1.2.1" chalk "^2.0.1" configstore "^3.0.0" import-lazy "^2.1.0" + is-ci "^1.0.10" is-installed-globally "^0.1.0" is-npm "^1.0.0" latest-version "^3.0.0" semver-diff "^2.0.0" xdg-basedir "^3.0.0" -uri-js@^4.2.1: +uri-js@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" dependencies: punycode "^2.1.0" -urix@^0.1.0, urix@~0.1.0: +urix@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" @@ -13724,10 +13580,8 @@ url@^0.11.0: querystring "0.2.0" use@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/use/-/use-3.1.0.tgz#14716bf03fdfefd03040aef58d8b4b85f3a7c544" - dependencies: - kind-of "^6.0.2" + version "3.1.1" + resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" user-home@^2.0.0: version "2.0.0" @@ -13742,7 +13596,7 @@ useragent@^2.1.12: lru-cache "4.1.x" tmp "0.0.x" -util-deprecate@^1.0.2, util-deprecate@~1.0.1: +util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -13757,12 +13611,18 @@ util.promisify@^1.0.0: define-properties "^1.1.2" object.getownpropertydescriptors "^2.0.3" -util@0.10.3, util@^0.10.3: +util@0.10.3: version "0.10.3" resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" dependencies: inherits "2.0.1" +util@^0.10.3: + version "0.10.4" + resolved "https://registry.yarnpkg.com/util/-/util-0.10.4.tgz#3aa0125bfe668a4672de58857d3ace27ecb76901" + dependencies: + inherits "2.0.3" + utils-merge@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" @@ -13775,19 +13635,26 @@ uuid@^2.0.1: version "2.0.3" resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a" -uuid@^3.0.0, uuid@^3.1.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.2.1.tgz#12c528bb9d58d0b9265d9a2f6f0fe8be17ff1f14" +uuid@^3.1.0, uuid@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" + +v8flags@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-3.0.2.tgz#ad6a78a20a6b23d03a8debc11211e3cc23149477" + dependencies: + homedir-polyfill "^1.0.1" val-loader@^1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/val-loader/-/val-loader-1.1.0.tgz#ed91537424d62a4ded98e846ccf07367756bf506" + version "1.1.1" + resolved "https://registry.yarnpkg.com/val-loader/-/val-loader-1.1.1.tgz#32ba8ed5c3607504134977251db2966499e15ef7" dependencies: loader-utils "^1.0.0" + schema-utils "^0.4.5" validate-npm-package-license@^3.0.1: - version "3.0.3" - resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz#81643bcbef1bdfecd4623793dc4648948ba98338" + version "3.0.4" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" dependencies: spdx-correct "^3.0.0" spdx-expression-parse "^3.0.0" @@ -13806,9 +13673,9 @@ value-or-function@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/value-or-function/-/value-or-function-3.0.0.tgz#1c243a50b595c1be54a754bfece8563b9ff8d813" -vega-canvas@1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/vega-canvas/-/vega-canvas-1.0.1.tgz#22cfa510af0cfbd920fc6af8b6111d3de5e63c44" +vega-canvas@1, vega-canvas@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/vega-canvas/-/vega-canvas-1.1.0.tgz#99ce74d4510a46fc9ed1a8721014da725898ec9f" vega-crossfilter@2: version "2.0.0" @@ -13901,18 +13768,18 @@ vega-lib@^3.3.1: vega-wordcloud "^2.1" vega-lite@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/vega-lite/-/vega-lite-2.4.0.tgz#e2bd0eabf5288c470a698377a10d4118f4f610e1" + version "2.6.0" + resolved "https://registry.yarnpkg.com/vega-lite/-/vega-lite-2.6.0.tgz#ce79c2db0311b0b920afdf2cd7384556a334e2f0" dependencies: "@types/json-stable-stringify" "^1.0.32" json-stable-stringify "^1.0.1" - tslib "^1.9.0" + tslib "^1.9.2" vega-event-selector "^2.0.0" - vega-typings "^0.2.15" + vega-typings "^0.3.17" vega-util "^1.7.0" yargs "^11.0.0" -vega-loader@2: +vega-loader@2, vega-loader@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/vega-loader/-/vega-loader-2.1.0.tgz#036bc573944559cc3895867f0c37fd1d9956ceef" dependencies: @@ -13940,10 +13807,10 @@ vega-parser@2, vega-parser@^2.5: vega-util "^1.7" vega-projection@1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/vega-projection/-/vega-projection-1.1.0.tgz#46452fd12927201d1a7f7ef8f61d6e24253dbcd2" + version "1.2.0" + resolved "https://registry.yarnpkg.com/vega-projection/-/vega-projection-1.2.0.tgz#812c955251dab495fda83d9406ba72d9833a2014" dependencies: - d3-geo "1" + d3-geo "^1.10.0" vega-runtime@2: version "2.0.1" @@ -13953,25 +13820,25 @@ vega-runtime@2: vega-util "1" vega-scale@2, vega-scale@^2.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/vega-scale/-/vega-scale-2.1.1.tgz#6ccdb796d9bcf86ceb677af5f9474a08cb01aaea" + version "2.4.0" + resolved "https://registry.yarnpkg.com/vega-scale/-/vega-scale-2.4.0.tgz#184b11979e643463ed45dfae9142e42b5a35eecc" dependencies: - d3-array "1" - d3-interpolate "1" - d3-scale "2" - d3-scale-chromatic "^1.2" - d3-time "1" - vega-util "1" + d3-array "^1.2.1" + d3-interpolate "^1.2.0" + d3-scale "^2.1.0" + d3-scale-chromatic "^1.3.0" + d3-time "^1.0.8" + vega-util "^1.7.0" vega-scenegraph@2, vega-scenegraph@^2.3: - version "2.4.2" - resolved "https://registry.yarnpkg.com/vega-scenegraph/-/vega-scenegraph-2.4.2.tgz#1835c63a66984a368633dfcfd8fa7495ee12ff25" + version "2.5.1" + resolved "https://registry.yarnpkg.com/vega-scenegraph/-/vega-scenegraph-2.5.1.tgz#0e86a18a5561a3f5d5ec2c31fd1bef4b6acf9291" dependencies: - d3-path "1" - d3-shape "1" - vega-canvas "1" - vega-loader "2" - vega-util "^1.7" + d3-path "^1.0.5" + d3-shape "^1.2.0" + vega-canvas "^1.0.1" + vega-loader "^2.1.0" + vega-util "^1.7.0" vega-schema-url-parser@1.0.0: version "1.0.0" @@ -13988,10 +13855,9 @@ vega-statistics@^1.2: d3-array "1" vega-tooltip@^0.9.14: - version "0.9.14" - resolved "https://registry.yarnpkg.com/vega-tooltip/-/vega-tooltip-0.9.14.tgz#c10bcacf69bf60a02c598ec46b905f94f28c54ac" + version "0.9.16" + resolved "https://registry.yarnpkg.com/vega-tooltip/-/vega-tooltip-0.9.16.tgz#1a2c01aa53ce8fbe9478ded50c3e556e72c7ae66" dependencies: - json-stringify-safe "^5.0.1" vega-util "^1.7.0" vega-transforms@^1.2: @@ -14003,9 +13869,11 @@ vega-transforms@^1.2: vega-statistics "^1.2" vega-util "1" -vega-typings@*, vega-typings@^0.2.15: - version "0.2.15" - resolved "https://registry.yarnpkg.com/vega-typings/-/vega-typings-0.2.15.tgz#7c973f7f342efe82fb9c4ad96833431c11cd5911" +vega-typings@*, vega-typings@^0.3.17: + version "0.3.40" + resolved "https://registry.yarnpkg.com/vega-typings/-/vega-typings-0.3.40.tgz#45737e2ff8ca4d70963b2d1f97c71c80ad597def" + dependencies: + vega-util "^1.7.0" vega-util@1, vega-util@^1.7, vega-util@^1.7.0: version "1.7.0" @@ -14049,8 +13917,8 @@ vega-wordcloud@^2.1: vega-util "1" vendors@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.1.tgz#37ad73c8ee417fb3d580e785312307d274847f22" + version "1.0.2" + resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.2.tgz#7fcb5eef9f5623b156bcea89ec37d63676f21801" venn.js@0.2.9: version "0.2.9" @@ -14065,12 +13933,12 @@ verror@1.10.0: extsprintf "^1.2.0" vfile-location@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-2.0.2.tgz#d3675c59c877498e492b4756ff65e4af1a752255" + version "2.0.3" + resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-2.0.3.tgz#083ba80e50968e8d420be49dd1ea9a992131df77" vfile-message@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-1.0.0.tgz#a6adb0474ea400fa25d929f1d673abea6a17e359" + version "1.0.1" + resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-1.0.1.tgz#51a2ccd8a6b97a7980bb34efb9ebde9632e93677" dependencies: unist-util-stringify-position "^1.1.1" @@ -14084,8 +13952,8 @@ vfile@^2.0.0: vfile-message "^1.0.0" vinyl-fs@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-3.0.2.tgz#1b86258844383f57581fcaac081fe09ef6d6d752" + version "3.0.3" + resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-3.0.3.tgz#c85849405f67428feabbbd5c5dbdd64f47d31bc7" dependencies: fs-mkdirp-stream "^1.0.0" glob-stream "^6.1.0" @@ -14132,8 +14000,8 @@ vinyl@1.X: replace-ext "0.0.1" vinyl@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-2.1.0.tgz#021f9c2cf951d6b939943c89eb5ee5add4fd924c" + version "2.2.0" + resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-2.2.0.tgz#d85b07da96e458d25b2ffe19fece9f2caa13ed86" dependencies: clone "^2.1.1" clone-buffer "^1.0.0" @@ -14189,12 +14057,6 @@ walker@~1.0.5: dependencies: makeerror "1.0.x" -ware@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/ware/-/ware-1.3.0.tgz#d1b14f39d2e2cb4ab8c4098f756fe4b164e473d4" - dependencies: - wrap-fn "^0.1.0" - warning@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/warning/-/warning-2.1.0.tgz#21220d9c63afc77a8c92111e011af705ce0c6901" @@ -14207,6 +14069,12 @@ warning@^3.0.0: dependencies: loose-envify "^1.0.0" +warning@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.2.tgz#aa6876480872116fa3e11d434b0d0d8d91e44607" + dependencies: + loose-envify "^1.0.0" + watch@~0.18.0: version "0.18.0" resolved "https://registry.yarnpkg.com/watch/-/watch-0.18.0.tgz#28095476c6df7c90c963138990c0a5423eb4b986" @@ -14215,8 +14083,8 @@ watch@~0.18.0: minimist "^1.2.0" watchpack@^1.4.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.5.0.tgz#231e783af830a22f8966f65c4c4bacc814072eed" + version "1.6.0" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.0.tgz#4bc12c2ebe8aa277a71f1d3f14d685c7b446cd00" dependencies: chokidar "^2.0.2" graceful-fs "^4.1.2" @@ -14232,7 +14100,7 @@ webidl-conversions@^3.0.0, webidl-conversions@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" -webidl-conversions@^4.0.1, webidl-conversions@^4.0.2: +webidl-conversions@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" @@ -14288,14 +14156,18 @@ websocket-extensions@>=0.1.1: resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.3.tgz#5d2ff22977003ec687a4b87073dfbbac146ccf29" whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.3.tgz#57c235bc8657e914d24e1a397d3c82daee0a6ba3" + version "1.0.4" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.4.tgz#63fb016b7435b795d9025632c086a5209dbd2621" dependencies: - iconv-lite "0.4.19" + iconv-lite "0.4.23" whatwg-fetch@>=0.10.0, whatwg-fetch@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84" + version "2.0.4" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f" + +whatwg-mimetype@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.1.0.tgz#f0f21d76cbba72362eb609dbed2a30cd17fcc7d4" whatwg-url@^4.1.0: version "4.8.0" @@ -14304,13 +14176,21 @@ whatwg-url@^4.1.0: tr46 "~0.0.3" webidl-conversions "^3.0.0" -whatwg-url@^6.4.0: - version "6.4.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.4.0.tgz#08fdf2b9e872783a7a1f6216260a1d66cc722e08" +whatwg-url@^6.4.1: + version "6.5.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.5.0.tgz#f2df02bff176fd65070df74ad5ccbb5a199965a8" dependencies: lodash.sortby "^4.7.0" - tr46 "^1.0.0" - webidl-conversions "^4.0.1" + tr46 "^1.0.1" + webidl-conversions "^4.0.2" + +whatwg-url@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.0.0.tgz#fde926fa54a599f3adf82dff25a9f7be02dc6edd" + dependencies: + lodash.sortby "^4.7.0" + tr46 "^1.0.1" + webidl-conversions "^4.0.2" whet.extend@~0.9.9: version "0.9.9" @@ -14324,18 +14204,12 @@ which-module@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" -which@1: +which@1, which@^1.1.1, which@^1.2.1, which@^1.2.12, which@^1.2.14, which@^1.2.9, which@^1.3.0: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" dependencies: isexe "^2.0.0" -which@^1.1.1, which@^1.2.1, which@^1.2.12, which@^1.2.9, which@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a" - dependencies: - isexe "^2.0.0" - which@~1.2.1: version "1.2.14" resolved "https://registry.yarnpkg.com/which/-/which-1.2.14.tgz#9a87c4378f03e827cecaf1acdf56c736c01c14e5" @@ -14343,10 +14217,10 @@ which@~1.2.1: isexe "^2.0.0" wide-align@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.2.tgz#571e0f1b0604636ebc0dfc21b0339bbe31341710" + version "1.1.3" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" dependencies: - string-width "^1.0.2" + string-width "^1.0.2 || 2" widest-line@^2.0.0: version "2.0.0" @@ -14390,12 +14264,6 @@ wrap-ansi@^2.0.0: string-width "^1.0.1" strip-ansi "^3.0.1" -wrap-fn@^0.1.0: - version "0.1.5" - resolved "https://registry.yarnpkg.com/wrap-fn/-/wrap-fn-0.1.5.tgz#f21b6e41016ff4a7e31720dbc63a09016bdf9845" - dependencies: - co "3.1.0" - wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" @@ -14415,8 +14283,8 @@ wreck@12.x.x: hoek "4.x.x" wreck@14.x.x: - version "14.0.2" - resolved "https://registry.yarnpkg.com/wreck/-/wreck-14.0.2.tgz#89c17a9061c745ed1c3aebcb66ea181dbaab454c" + version "14.1.0" + resolved "https://registry.yarnpkg.com/wreck/-/wreck-14.1.0.tgz#b13e526b6a8318e5ebc6969c0b21075c06337067" dependencies: boom "7.x.x" hoek "5.x.x" @@ -14470,21 +14338,16 @@ ws@2.0.x: dependencies: ultron "~1.1.0" -ws@^4.0.0, ws@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-4.1.0.tgz#a979b5d7d4da68bf54efe0408967c324869a7289" +ws@^5.2.0: + version "5.2.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-5.2.2.tgz#dffef14866b8e8dc9133582514d1befaf96e980f" dependencies: async-limiter "~1.0.0" - safe-buffer "~5.1.0" wtf-8@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/wtf-8/-/wtf-8-1.0.0.tgz#392d8ba2d0f1c34d1ee2d630f15d0efb68e1048a" -x-is-function@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/x-is-function/-/x-is-function-1.0.4.tgz#5d294dc3d268cbdd062580e0c5df77a391d1fa1e" - x-is-string@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/x-is-string/-/x-is-string-0.1.0.tgz#474b50865af3a49a9c4657f05acd145458f77d82" @@ -14504,8 +14367,8 @@ xdg-basedir@^3.0.0: resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4" xhr@^2.0.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/xhr/-/xhr-2.4.1.tgz#ba982cced205ae5eec387169ac9dc77ca4853d38" + version "2.5.0" + resolved "https://registry.yarnpkg.com/xhr/-/xhr-2.5.0.tgz#bed8d1676d5ca36108667692b74b316c496e49dd" dependencies: global "~4.3.0" is-function "^1.0.1" @@ -14617,8 +14480,8 @@ yargs@^10.0.3: yargs-parser "^8.1.0" yargs@^11.0.0: - version "11.0.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-11.0.0.tgz#c052931006c5eee74610e5fc0354bedfd08a201b" + version "11.1.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-11.1.0.tgz#90b869934ed6e871115ea2ff58b03f4724ed2d77" dependencies: cliui "^4.0.0" decamelize "^1.1.1" @@ -14684,9 +14547,9 @@ yargs@~3.10.0: decamelize "^1.0.0" window-size "0.1.0" -yarn-install@^0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/yarn-install/-/yarn-install-0.5.1.tgz#f3c55e8646b6ac8da360b2f8e31afe5c4a067340" +yarn-install@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/yarn-install/-/yarn-install-1.0.0.tgz#57f45050b82efd57182b3973c54aa05cb5d25230" dependencies: cac "^3.0.3" chalk "^1.1.3" @@ -14705,20 +14568,13 @@ yauzl@2.7.0: buffer-crc32 "~0.2.3" fd-slicer "~1.0.1" -yauzl@^2.10.0: +yauzl@^2.10.0, yauzl@^2.4.2: version "2.10.0" resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" dependencies: buffer-crc32 "~0.2.3" fd-slicer "~1.1.0" -yauzl@^2.4.2: - version "2.9.1" - resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.9.1.tgz#a81981ea70a57946133883f029c5821a89359a7f" - dependencies: - buffer-crc32 "~0.2.3" - fd-slicer "~1.0.1" - yeast@0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419" From e4b3ad20ab57bca98c266b520f66648e4665ddd7 Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Thu, 30 Aug 2018 12:23:21 -0400 Subject: [PATCH 33/94] Beats/security (#22500) * client side security working * check license on route * forgot a file --- .../framework/kibana_framework_adapter.ts | 83 +++++++++++++++---- .../public/lib/compose/kibana.ts | 15 +++- .../public/lib/compose/memory.ts | 9 +- .../lib/adapters/framework/adapter_types.ts | 1 + .../framework/kibana_framework_adapter.ts | 83 ++++++++++++++++++- .../server/rest_api/beats/enroll.ts | 2 +- .../server/rest_api/beats/list.ts | 2 +- .../server/rest_api/beats/tag_assignment.ts | 2 +- .../server/rest_api/beats/tag_removal.ts | 2 +- .../server/rest_api/beats/update.ts | 2 +- .../server/rest_api/tags/delete.ts | 1 + .../server/rest_api/tags/get.ts | 1 + .../server/rest_api/tags/list.ts | 1 + .../server/rest_api/tags/set.ts | 2 +- .../server/rest_api/tokens/create.ts | 3 +- 15 files changed, 182 insertions(+), 27 deletions(-) diff --git a/x-pack/plugins/beats_management/public/lib/adapters/framework/kibana_framework_adapter.ts b/x-pack/plugins/beats_management/public/lib/adapters/framework/kibana_framework_adapter.ts index 497f47a48d57d..d17c3b1577d7f 100644 --- a/x-pack/plugins/beats_management/public/lib/adapters/framework/kibana_framework_adapter.ts +++ b/x-pack/plugins/beats_management/public/lib/adapters/framework/kibana_framework_adapter.ts @@ -23,13 +23,28 @@ export class KibanaFrameworkAdapter implements FrameworkAdapter { private rootComponent: React.ReactElement | null = null; private uiModule: IModule; private routes: any; - - constructor(uiModule: IModule, management: any, routes: any) { + private XPackInfoProvider: any; + private xpackInfo: null | any; + private notifier: any; + private kbnUrlService: any; + private chrome: any; + + constructor( + uiModule: IModule, + management: any, + routes: any, + chrome: any, + XPackInfoProvider: any, + Notifier: any + ) { this.adapterService = new KibanaAdapterServiceProvider(); this.management = management; this.uiModule = uiModule; this.routes = routes; + this.chrome = chrome; + this.XPackInfoProvider = XPackInfoProvider; this.appState = {}; + this.notifier = new Notifier({ location: 'Beats' }); } public setUISettings = (key: string, value: any) => { @@ -42,24 +57,48 @@ export class KibanaFrameworkAdapter implements FrameworkAdapter { this.rootComponent = component; }; - public registerManagementSection(pluginId: string, displayName: string, basePath: string) { - const registerSection = () => - this.management.register(pluginId, { - display: displayName, - order: 30, - }); - const getSection = () => this.management.getSection(pluginId); + public hadValidLicense() { + if (!this.xpackInfo) { + return false; + } + return this.xpackInfo.get('features.beats_management.licenseValid', false); + } - const section = this.management.hasItem(pluginId) ? getSection() : registerSection(); + public securityEnabled() { + if (!this.xpackInfo) { + return false; + } - section.register(pluginId, { - visible: true, - display: displayName, - order: 30, - url: `#${basePath}`, - }); + return this.xpackInfo.get('features.beats_management.securityEnabled', false); + } + public registerManagementSection(pluginId: string, displayName: string, basePath: string) { this.register(this.uiModule); + + this.hookAngular(() => { + if (this.hadValidLicense() && this.securityEnabled()) { + const registerSection = () => + this.management.register(pluginId, { + display: displayName, + order: 30, + }); + const getSection = () => this.management.getSection(pluginId); + + const section = this.management.hasItem(pluginId) ? getSection() : registerSection(); + + section.register(pluginId, { + visible: true, + display: displayName, + order: 30, + url: `#${basePath}`, + }); + } + + if (!this.securityEnabled()) { + this.notifier.error(this.xpackInfo.get(`features.beats_management.message`)); + this.kbnUrlService.redirect('/management'); + } + }); } private manageAngularLifecycle($scope: any, $route: any, elem: any) { @@ -83,6 +122,18 @@ export class KibanaFrameworkAdapter implements FrameworkAdapter { }); } + private hookAngular(done: () => any) { + this.chrome.dangerouslyGetActiveInjector().then(($injector: any) => { + const Private = $injector.get('Private'); + const xpackInfo = Private(this.XPackInfoProvider); + const kbnUrlService = $injector.get('kbnUrl'); + + this.xpackInfo = xpackInfo; + this.kbnUrlService = kbnUrlService; + done(); + }); + } + private register = (adapterModule: IModule) => { const adapter = this; this.routes.when(`/management/beats_management/:view?/:id?/:other?/:other2?`, { diff --git a/x-pack/plugins/beats_management/public/lib/compose/kibana.ts b/x-pack/plugins/beats_management/public/lib/compose/kibana.ts index 0d313c3184356..3488a5d23a1ef 100644 --- a/x-pack/plugins/beats_management/public/lib/compose/kibana.ts +++ b/x-pack/plugins/beats_management/public/lib/compose/kibana.ts @@ -4,6 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ +// @ts-ignore +import { XPackInfoProvider } from 'plugins/xpack_main/services/xpack_info'; +// @ts-ignore import 'ui/autoload/all'; // @ts-ignore: path dynamic for kibana import chrome from 'ui/chrome'; @@ -11,8 +14,11 @@ import chrome from 'ui/chrome'; import { management } from 'ui/management'; // @ts-ignore: path dynamic for kibana import { uiModules } from 'ui/modules'; +// @ts-ignore +import { Notifier } from 'ui/notify'; // @ts-ignore: path dynamic for kibana import routes from 'ui/routes'; + import { supportedConfigs } from '../../config_schemas'; import { RestBeatsAdapter } from '../adapters/beats/rest_beats_adapter'; import { KibanaFrameworkAdapter } from '../adapters/framework/kibana_framework_adapter'; @@ -39,7 +45,14 @@ export function compose(): FrontendLibs { }; const pluginUIModule = uiModules.get('app/beats_management'); - const framework = new KibanaFrameworkAdapter(pluginUIModule, management, routes); + const framework = new KibanaFrameworkAdapter( + pluginUIModule, + management, + routes, + chrome, + XPackInfoProvider, + Notifier + ); const libs: FrontendLibs = { framework, diff --git a/x-pack/plugins/beats_management/public/lib/compose/memory.ts b/x-pack/plugins/beats_management/public/lib/compose/memory.ts index e5f515014652c..ab56f6708123d 100644 --- a/x-pack/plugins/beats_management/public/lib/compose/memory.ts +++ b/x-pack/plugins/beats_management/public/lib/compose/memory.ts @@ -35,7 +35,14 @@ export function compose(): FrontendLibs { }; const pluginUIModule = uiModules.get('app/beats_management'); - const framework = new KibanaFrameworkAdapter(pluginUIModule, management, routes); + const framework = new KibanaFrameworkAdapter( + pluginUIModule, + management, + routes, + null, + null, + null + ); const libs: FrontendLibs = { ...domainLibs, framework, diff --git a/x-pack/plugins/beats_management/server/lib/adapters/framework/adapter_types.ts b/x-pack/plugins/beats_management/server/lib/adapters/framework/adapter_types.ts index 014c041b11f0a..0e690b53fe8c2 100644 --- a/x-pack/plugins/beats_management/server/lib/adapters/framework/adapter_types.ts +++ b/x-pack/plugins/beats_management/server/lib/adapters/framework/adapter_types.ts @@ -50,6 +50,7 @@ export interface FrameworkRouteOptions< path: string; method: string | string[]; vhost?: string; + licenseRequired?: boolean; handler: FrameworkRouteHandler; config?: {}; } diff --git a/x-pack/plugins/beats_management/server/lib/adapters/framework/kibana_framework_adapter.ts b/x-pack/plugins/beats_management/server/lib/adapters/framework/kibana_framework_adapter.ts index d07170acb7050..d2efd7e49bb39 100644 --- a/x-pack/plugins/beats_management/server/lib/adapters/framework/kibana_framework_adapter.ts +++ b/x-pack/plugins/beats_management/server/lib/adapters/framework/kibana_framework_adapter.ts @@ -4,6 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ +import Boom from 'boom'; +// @ts-ignore +import { mirrorPluginStatus } from '../../../../../../server/lib/mirror_plugin_status'; +import { PLUGIN } from '../../../../common/constants/plugin'; import { wrapRequest } from '../../../utils/wrap_request'; import { BackendFrameworkAdapter, @@ -29,6 +33,18 @@ export class KibanaBackendFrameworkAdapter implements BackendFrameworkAdapter { } this.cryptoHash = null; this.validateConfig(); + + const xpackMainPlugin = hapiServer.plugins.xpack_main; + const thisPlugin = hapiServer.plugins.beats_management; + + mirrorPluginStatus(xpackMainPlugin, thisPlugin); + xpackMainPlugin.status.once('green', () => { + // Register a function that is called whenever the xpack info changes, + // to re-compute the license check results for this plugin + xpackMainPlugin.info + .feature(PLUGIN.ID) + .registerLicenseCheckResultsGenerator(this.checkLicense); + }); } public getSetting(settingPath: string) { @@ -56,10 +72,17 @@ export class KibanaBackendFrameworkAdapter implements BackendFrameworkAdapter { public registerRoute( route: FrameworkRouteOptions ) { - const wrappedHandler = (request: any, reply: any) => route.handler(wrapRequest(request), reply); + const wrappedHandler = (licenseRequired: boolean) => (request: any, reply: any) => { + const xpackMainPlugin = this.server.plugins.xpack_main; + const licenseCheckResults = xpackMainPlugin.info.feature(PLUGIN.ID).getLicenseCheckResults(); + if (licenseRequired && !licenseCheckResults.licenseValid) { + reply(Boom.forbidden(licenseCheckResults.message)); + } + return route.handler(wrapRequest(request), reply); + }; this.server.route({ - handler: wrappedHandler, + handler: wrappedHandler(route.licenseRequired), method: route.method, path: route.path, config: route.config, @@ -78,4 +101,60 @@ export class KibanaBackendFrameworkAdapter implements BackendFrameworkAdapter { this.cryptoHash = 'xpack_beats_default_encryptionKey'; } } + + private checkLicense(xPackInfo: any) { + // If, for some reason, we cannot get the license information + // from Elasticsearch, assume worst case and disable the Logstash pipeline UI + if (!xPackInfo || !xPackInfo.isAvailable()) { + return { + securityEnabled: false, + licenseValid: false, + message: + 'You cannot manage Beats centeral management because license information is not available at this time.', + }; + } + + const VALID_LICENSE_MODES = ['trial', 'gold', 'platinum']; + + const isLicenseValid = xPackInfo.license.isOneOf(VALID_LICENSE_MODES); + const isLicenseActive = xPackInfo.license.isActive(); + const licenseType = xPackInfo.license.getType(); + const isSecurityEnabled = xPackInfo.feature('security').isEnabled(); + + // Security is not enabled in ES + if (!isSecurityEnabled) { + const message = + 'Security must be enabled in order to use Beats centeral management features.' + + ' Please set xpack.security.enabled: true in your elasticsearch.yml.'; + return { + securityEnabled: false, + licenseValid: true, + message, + }; + } + + // License is not valid + if (!isLicenseValid) { + return { + securityEnabled: true, + licenseValid: false, + message: `Your ${licenseType} license does not support Beats centeral management features. Please upgrade your license.`, + }; + } + + // License is valid but not active, we go into a read-only mode. + if (!isLicenseActive) { + return { + securityEnabled: true, + licenseValid: false, + message: `You cannot edit, create, or delete your Beats centeral management configurations because your ${licenseType} license has expired.`, + }; + } + + // License is valid and active + return { + securityEnabled: true, + licenseValid: true, + }; + } } diff --git a/x-pack/plugins/beats_management/server/rest_api/beats/enroll.ts b/x-pack/plugins/beats_management/server/rest_api/beats/enroll.ts index c1c23537218bd..8e7d2aee956ee 100644 --- a/x-pack/plugins/beats_management/server/rest_api/beats/enroll.ts +++ b/x-pack/plugins/beats_management/server/rest_api/beats/enroll.ts @@ -10,11 +10,11 @@ import { CMServerLibs } from '../../lib/lib'; import { BeatEnrollmentStatus } from '../../lib/lib'; import { wrapEsError } from '../../utils/error_wrappers'; -// TODO: add license check pre-hook // TODO: write to Kibana audit log file export const createBeatEnrollmentRoute = (libs: CMServerLibs) => ({ method: 'POST', path: '/api/beats/agent/{beatId}', + licenseRequired: true, config: { auth: false, validate: { diff --git a/x-pack/plugins/beats_management/server/rest_api/beats/list.ts b/x-pack/plugins/beats_management/server/rest_api/beats/list.ts index 3bfb249b39b6f..21f5a9d799b2d 100644 --- a/x-pack/plugins/beats_management/server/rest_api/beats/list.ts +++ b/x-pack/plugins/beats_management/server/rest_api/beats/list.ts @@ -9,10 +9,10 @@ import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types'; import { CMServerLibs } from '../../lib/lib'; import { wrapEsError } from '../../utils/error_wrappers'; -// TODO: add license check pre-hook export const createListAgentsRoute = (libs: CMServerLibs) => ({ method: 'GET', path: '/api/beats/agents/{listByAndValue*}', + licenseRequired: true, handler: async (request: FrameworkRequest, reply: any) => { const listByAndValueParts = request.params.listByAndValue.split('/'); let listBy: 'tag' | null = null; diff --git a/x-pack/plugins/beats_management/server/rest_api/beats/tag_assignment.ts b/x-pack/plugins/beats_management/server/rest_api/beats/tag_assignment.ts index 857b68a921597..b0a73f1706571 100644 --- a/x-pack/plugins/beats_management/server/rest_api/beats/tag_assignment.ts +++ b/x-pack/plugins/beats_management/server/rest_api/beats/tag_assignment.ts @@ -11,11 +11,11 @@ import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types'; import { CMServerLibs } from '../../lib/lib'; import { wrapEsError } from '../../utils/error_wrappers'; -// TODO: add license check pre-hook // TODO: write to Kibana audit log file export const createTagAssignmentsRoute = (libs: CMServerLibs) => ({ method: 'POST', path: '/api/beats/agents_tags/assignments', + licenseRequired: true, config: { validate: { payload: Joi.object({ diff --git a/x-pack/plugins/beats_management/server/rest_api/beats/tag_removal.ts b/x-pack/plugins/beats_management/server/rest_api/beats/tag_removal.ts index 0cdd9f2f28c2b..e8d395b27eaa6 100644 --- a/x-pack/plugins/beats_management/server/rest_api/beats/tag_removal.ts +++ b/x-pack/plugins/beats_management/server/rest_api/beats/tag_removal.ts @@ -9,11 +9,11 @@ import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types'; import { CMServerLibs } from '../../lib/lib'; import { wrapEsError } from '../../utils/error_wrappers'; -// TODO: add license check pre-hook // TODO: write to Kibana audit log file export const createTagRemovalsRoute = (libs: CMServerLibs) => ({ method: 'POST', path: '/api/beats/agents_tags/removals', + licenseRequired: true, config: { validate: { payload: Joi.object({ diff --git a/x-pack/plugins/beats_management/server/rest_api/beats/update.ts b/x-pack/plugins/beats_management/server/rest_api/beats/update.ts index bf94f83ec22d7..e33c5b5d11e53 100644 --- a/x-pack/plugins/beats_management/server/rest_api/beats/update.ts +++ b/x-pack/plugins/beats_management/server/rest_api/beats/update.ts @@ -9,11 +9,11 @@ import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types'; import { CMServerLibs } from '../../lib/lib'; import { wrapEsError } from '../../utils/error_wrappers'; -// TODO: add license check pre-hook // TODO: write to Kibana audit log file (include who did the verification as well) export const createBeatUpdateRoute = (libs: CMServerLibs) => ({ method: 'PUT', path: '/api/beats/agent/{beatId}', + licenseRequired: true, config: { auth: { mode: 'optional', diff --git a/x-pack/plugins/beats_management/server/rest_api/tags/delete.ts b/x-pack/plugins/beats_management/server/rest_api/tags/delete.ts index 451d800122a04..34dde929b4586 100644 --- a/x-pack/plugins/beats_management/server/rest_api/tags/delete.ts +++ b/x-pack/plugins/beats_management/server/rest_api/tags/delete.ts @@ -10,6 +10,7 @@ import { wrapEsError } from '../../utils/error_wrappers'; export const createDeleteTagsWithIdsRoute = (libs: CMServerLibs) => ({ method: 'DELETE', path: '/api/beats/tags/{tagIds}', + licenseRequired: true, handler: async (request: any, reply: any) => { const tagIdString: string = request.params.tagIds; const tagIds = tagIdString.split(',').filter((id: string) => id.length > 0); diff --git a/x-pack/plugins/beats_management/server/rest_api/tags/get.ts b/x-pack/plugins/beats_management/server/rest_api/tags/get.ts index 97aa6b413aeb3..c65640ee3eb2f 100644 --- a/x-pack/plugins/beats_management/server/rest_api/tags/get.ts +++ b/x-pack/plugins/beats_management/server/rest_api/tags/get.ts @@ -11,6 +11,7 @@ import { wrapEsError } from '../../utils/error_wrappers'; export const createGetTagsWithIdsRoute = (libs: CMServerLibs) => ({ method: 'GET', path: '/api/beats/tags/{tagIds}', + licenseRequired: true, handler: async (request: any, reply: any) => { const tagIdString: string = request.params.tagIds; const tagIds = tagIdString.split(',').filter((id: string) => id.length > 0); diff --git a/x-pack/plugins/beats_management/server/rest_api/tags/list.ts b/x-pack/plugins/beats_management/server/rest_api/tags/list.ts index 0569e29bb60db..63ab0d5b52e7e 100644 --- a/x-pack/plugins/beats_management/server/rest_api/tags/list.ts +++ b/x-pack/plugins/beats_management/server/rest_api/tags/list.ts @@ -11,6 +11,7 @@ import { wrapEsError } from '../../utils/error_wrappers'; export const createListTagsRoute = (libs: CMServerLibs) => ({ method: 'GET', path: '/api/beats/tags', + licenseRequired: true, handler: async (request: any, reply: any) => { let tags: BeatTag[]; try { diff --git a/x-pack/plugins/beats_management/server/rest_api/tags/set.ts b/x-pack/plugins/beats_management/server/rest_api/tags/set.ts index 4ec082ba99de6..a69f102b127c8 100644 --- a/x-pack/plugins/beats_management/server/rest_api/tags/set.ts +++ b/x-pack/plugins/beats_management/server/rest_api/tags/set.ts @@ -11,11 +11,11 @@ import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types'; import { CMServerLibs } from '../../lib/lib'; import { wrapEsError } from '../../utils/error_wrappers'; -// TODO: add license check pre-hook // TODO: write to Kibana audit log file export const createSetTagRoute = (libs: CMServerLibs) => ({ method: 'PUT', path: '/api/beats/tag/{tag}', + licenseRequired: true, config: { validate: { params: Joi.object({ diff --git a/x-pack/plugins/beats_management/server/rest_api/tokens/create.ts b/x-pack/plugins/beats_management/server/rest_api/tokens/create.ts index 15ab7b872df8a..08205bcba43dd 100644 --- a/x-pack/plugins/beats_management/server/rest_api/tokens/create.ts +++ b/x-pack/plugins/beats_management/server/rest_api/tokens/create.ts @@ -9,12 +9,13 @@ import { get } from 'lodash'; import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types'; import { CMServerLibs } from '../../lib/lib'; import { wrapEsError } from '../../utils/error_wrappers'; -// TODO: add license check pre-hook + // TODO: write to Kibana audit log file const DEFAULT_NUM_TOKENS = 1; export const createTokensRoute = (libs: CMServerLibs) => ({ method: 'POST', path: '/api/beats/enrollment_tokens', + licenseRequired: true, config: { validate: { payload: Joi.object({ From fdc428660fbd14171400e837985b89eeac716bb7 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Thu, 30 Aug 2018 17:26:21 -0400 Subject: [PATCH 34/94] [Beats CM] Add beats details pages (#22455) * Move edits from previous details branch. * Add tag view to beat details. * Added notifications for add/remove tag from beat. * Fix dependencies upgrade/downgrade add/removes. * Create new page files for each sub-section of details view. * Move page functionality from dedicated components to pages. --- x-pack/package.json | 6 +- .../public/components/table/action_button.tsx | 33 ++-- .../public/components/table/controls.tsx | 12 +- .../public/components/table/index.ts | 8 +- .../components/table/primary_options.tsx | 61 ++++++++ .../public/components/table/table.tsx | 11 +- .../components/table/table_type_configs.tsx | 75 +++++++-- .../beats_management/public/lib/lib.ts | 2 +- .../public/pages/beat/action_section.tsx | 42 +++++ .../public/pages/beat/activity.tsx | 14 ++ .../public/pages/beat/detail.tsx | 92 +++++++++++ .../public/pages/beat/index.tsx | 128 ++++++++++++++- .../public/pages/beat/tags.tsx | 148 ++++++++++++++++++ .../public/pages/main/tags.tsx | 10 +- x-pack/yarn.lock | 109 ++++++++++++- yarn.lock | 101 +++++++++++- 16 files changed, 801 insertions(+), 51 deletions(-) create mode 100644 x-pack/plugins/beats_management/public/components/table/primary_options.tsx create mode 100644 x-pack/plugins/beats_management/public/pages/beat/action_section.tsx create mode 100644 x-pack/plugins/beats_management/public/pages/beat/activity.tsx create mode 100644 x-pack/plugins/beats_management/public/pages/beat/detail.tsx create mode 100644 x-pack/plugins/beats_management/public/pages/beat/tags.tsx diff --git a/x-pack/package.json b/x-pack/package.json index 00a915d8bca9c..4a73280eb0421 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -90,8 +90,8 @@ "@elastic/node-phantom-simple": "2.2.4", "@elastic/numeral": "2.3.2", "@kbn/datemath": "link:../packages/kbn-datemath", - "@kbn/i18n": "link:../packages/kbn-i18n", "@kbn/ui-framework": "link:../packages/kbn-ui-framework", + "@kbn/i18n": "link:../packages/kbn-i18n", "@samverschueren/stream-to-observable": "^0.3.0", "@slack/client": "^4.2.2", "@types/uuid": "^3.4.3", @@ -137,6 +137,7 @@ "moment-timezone": "^0.5.14", "ngreact": "^0.5.1", "nodemailer": "^4.6.4", + "path-match": "1.2.4", "pdfmake": "0.1.33", "pivotal-ui": "13.0.1", "pluralize": "3.1.0", @@ -173,9 +174,10 @@ "unbzip2-stream": "1.0.9", "uuid": "3.0.1", "venn.js": "0.2.9", + "webcola": "3.3.6", "xregexp": "3.2.0" }, "engines": { "yarn": "^1.6.0" } -} +} \ No newline at end of file diff --git a/x-pack/plugins/beats_management/public/components/table/action_button.tsx b/x-pack/plugins/beats_management/public/components/table/action_button.tsx index 1f9d2141cba6a..54be343bbd5ab 100644 --- a/x-pack/plugins/beats_management/public/components/table/action_button.tsx +++ b/x-pack/plugins/beats_management/public/components/table/action_button.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiButton, EuiContextMenu, EuiPopover } from '@elastic/eui'; +import { EuiButton, EuiContextMenu, EuiFlexGroup, EuiFlexItem, EuiPopover } from '@elastic/eui'; import React from 'react'; import { ActionDefinition } from './table_type_configs'; @@ -16,20 +16,33 @@ interface ActionButtonProps { showPopover(): void; } +const Action = (props: { + action: string; + danger?: boolean; + name: string; + actionHandler(action: string, payload?: any): void; +}) => { + const { action, actionHandler, danger, name } = props; + return ( + actionHandler(action)}> + {name} + + ); +}; + export function ActionButton(props: ActionButtonProps) { const { actions, actionHandler, hidePopover, isPopoverVisible, showPopover } = props; - if (actions.length === 0) { return null; - } else if (actions.length === 1) { - const action = actions[0]; + } else if (actions.length <= 2) { return ( - actionHandler(action.action)} - > - {action.name} - + + {actions.map(({ action, danger, name }) => ( + + + + ))} + ); } return ( diff --git a/x-pack/plugins/beats_management/public/components/table/controls.tsx b/x-pack/plugins/beats_management/public/components/table/controls.tsx index 07de3cddadfdd..d45f4aa7f4bb8 100644 --- a/x-pack/plugins/beats_management/public/components/table/controls.tsx +++ b/x-pack/plugins/beats_management/public/components/table/controls.tsx @@ -4,12 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - // @ts-ignore typings for EuiSearchar not included in EUI - EuiSearchBar, -} from '@elastic/eui'; import React from 'react'; import { AssignmentOptions } from './assignment_options'; +import { PrimaryOptions } from './primary_options'; import { ControlDefinitions } from './table_type_configs'; interface ControlBarProps { @@ -45,10 +42,11 @@ export function ControlBar(props: ControlBarProps) { selectionCount={selectionCount} /> ) : ( - actionHandler('search', query)} + onSearchQueryChange={(query: any) => actionHandler('search', query)} + primaryActions={controlDefinitions.primaryActions || []} /> ); } diff --git a/x-pack/plugins/beats_management/public/components/table/index.ts b/x-pack/plugins/beats_management/public/components/table/index.ts index 459002dd49b82..2a7bb2f90d0e6 100644 --- a/x-pack/plugins/beats_management/public/components/table/index.ts +++ b/x-pack/plugins/beats_management/public/components/table/index.ts @@ -6,4 +6,10 @@ export { Table } from './table'; export { ControlBar } from './controls'; -export { BeatsTableType, TagsTableType } from './table_type_configs'; +export { + ActionDefinition, + BeatDetailTagsTable, + BeatsTableType, + FilterDefinition, + TagsTableType, +} from './table_type_configs'; diff --git a/x-pack/plugins/beats_management/public/components/table/primary_options.tsx b/x-pack/plugins/beats_management/public/components/table/primary_options.tsx new file mode 100644 index 0000000000000..bd92fb8e827e4 --- /dev/null +++ b/x-pack/plugins/beats_management/public/components/table/primary_options.tsx @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + EuiFlexGroup, + EuiFlexItem, + // @ts-ignore typings for EuiSearchar not included in EUI + EuiSearchBar, +} from '@elastic/eui'; +import React from 'react'; +import { ActionDefinition, FilterDefinition } from '../table'; +import { ActionButton } from './action_button'; + +interface PrimaryOptionsProps { + filters: FilterDefinition[] | null; + primaryActions: ActionDefinition[]; + actionHandler(actionType: string, payload?: any): void; + onSearchQueryChange(query: any): void; +} + +interface PrimaryOptionsState { + isPopoverVisible: boolean; +} + +export class PrimaryOptions extends React.PureComponent { + constructor(props: PrimaryOptionsProps) { + super(props); + + this.state = { + isPopoverVisible: false, + }; + } + public render() { + const { actionHandler, filters, primaryActions, onSearchQueryChange } = this.props; + return ( + + + + + + + + + ); + } + private hidePopover = () => this.setState({ isPopoverVisible: false }); + private showPopover = () => this.setState({ isPopoverVisible: true }); +} diff --git a/x-pack/plugins/beats_management/public/components/table/table.tsx b/x-pack/plugins/beats_management/public/components/table/table.tsx index 7bfcd2c97de32..36829dba8abf6 100644 --- a/x-pack/plugins/beats_management/public/components/table/table.tsx +++ b/x-pack/plugins/beats_management/public/components/table/table.tsx @@ -15,17 +15,17 @@ import { TABLE_CONFIG } from '../../../common/constants'; import { ControlBar } from './controls'; import { TableType } from './table_type_configs'; -interface BeatsTableProps { +interface TableProps { assignmentOptions: any[] | null; assignmentTitle: string | null; - renderAssignmentOptions?: (item: any) => any; items: any[]; + renderAssignmentOptions?: (item: any) => any; showAssignmentOptions: boolean; type: TableType; actionHandler(action: string, payload?: any): void; } -interface BeatsTableState { +interface TableState { selection: any[]; } @@ -33,8 +33,8 @@ const TableContainer = styled.div` padding: 16px; `; -export class Table extends React.Component { - constructor(props: BeatsTableProps) { +export class Table extends React.Component { + constructor(props: TableProps) { super(props); this.state = { @@ -47,7 +47,6 @@ export class Table extends React.Component { }; public render() { - const { actionHandler, assignmentOptions, diff --git a/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx b/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx index 7ddd11a11e807..f3e525d7c15e6 100644 --- a/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx +++ b/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx @@ -5,13 +5,14 @@ */ // @ts-ignore -import { EuiBadge, EuiFlexGroup, EuiIcon, EuiLink } from '@elastic/eui'; +import { EuiBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { flatten, uniq } from 'lodash'; import moment from 'moment'; import React from 'react'; import { TABLE_CONFIG } from '../../../common/constants'; import { BeatTag, CMPopulatedBeat, ConfigurationBlock } from '../../../common/domain_types'; +import { ClientSideConfigurationBlock } from '../../lib/lib'; import { ConnectedLink } from '../connected_link'; export interface ColumnDefinition { @@ -44,6 +45,7 @@ export interface FilterDefinition { export interface ControlDefinitions { actions: ActionDefinition[]; filters: FilterDefinition[]; + primaryActions?: ActionDefinition[]; } export interface TableType { @@ -68,11 +70,13 @@ export const BeatsTableType: TableType = { field: 'full_tags', name: 'Tags', render: (value: string, beat: CMPopulatedBeat) => ( - + {(beat.full_tags || []).map(tag => ( - - {tag.id} - + + + {tag.id} + + ))} ), @@ -122,6 +126,17 @@ export const BeatsTableType: TableType = { }), }; +const TagBadge = (props: { tag: { color?: string; id: string } }) => { + const { tag } = props; + return ( + + {tag.id.length > TABLE_CONFIG.TRUNCATE_TAG_LENGTH + ? `${tag.id.substring(0, TABLE_CONFIG.TRUNCATE_TAG_LENGTH)}...` + : tag.id} + + ); +}; + export const TagsTableType: TableType = { columnDefinitions: [ { @@ -129,11 +144,7 @@ export const TagsTableType: TableType = { name: 'Tag name', render: (id: string, tag: BeatTag) => ( - - {tag.id.length > TABLE_CONFIG.TRUNCATE_TAG_LENGTH - ? `${tag.id.substring(0, TABLE_CONFIG.TRUNCATE_TAG_LENGTH)}...` - : tag.id} - + ), sortable: true, @@ -167,3 +178,47 @@ export const TagsTableType: TableType = { filters: [], }), }; + +export const BeatDetailTagsTable: TableType = { + columnDefinitions: [ + { + field: 'id', + name: 'Tag name', + render: (id: string, tag: BeatTag) => , + sortable: true, + width: '55%', + }, + { + align: 'right', + field: 'configurations', + name: 'Configurations', + render: (configurations: ClientSideConfigurationBlock[]) => ( + {configurations.length} + ), + sortable: true, + }, + { + align: 'right', + field: 'last_updated', + name: 'Last update', + render: (lastUpdate: string) => {moment(lastUpdate).fromNow()}, + sortable: true, + }, + ], + controlDefinitions: (data: any) => ({ + actions: [], + filters: [], + primaryActions: [ + { + name: 'Add Tag', + action: 'add', + danger: false, + }, + { + name: 'Remove Selected', + action: 'remove', + danger: true, + }, + ], + }), +}; diff --git a/x-pack/plugins/beats_management/public/lib/lib.ts b/x-pack/plugins/beats_management/public/lib/lib.ts index 67659914efc01..34b5cb08ddfc4 100644 --- a/x-pack/plugins/beats_management/public/lib/lib.ts +++ b/x-pack/plugins/beats_management/public/lib/lib.ts @@ -93,7 +93,7 @@ export interface MetricbeatModuleConfig { } export interface FrameworkAdapter { - // Insstance vars + // Instance vars appState?: object; kbnVersion?: string; registerManagementSection(pluginId: string, displayName: string, basePath: string): void; diff --git a/x-pack/plugins/beats_management/public/pages/beat/action_section.tsx b/x-pack/plugins/beats_management/public/pages/beat/action_section.tsx new file mode 100644 index 0000000000000..b310706022bfe --- /dev/null +++ b/x-pack/plugins/beats_management/public/pages/beat/action_section.tsx @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; +import moment from 'moment'; +import React from 'react'; +import { CMPopulatedBeat } from '../../../common/domain_types'; + +interface BeatDetailsActionSectionProps { + beat: CMPopulatedBeat | undefined; +} + +export const BeatDetailsActionSection = ({ beat }: BeatDetailsActionSectionProps) => ( +
+ {beat ? ( + + + + Version:  + {beat.version}. + + + + {/* TODO: What field is used to populate this value? */} + + Uptime: 12min. + + + + + Last Config Update: {moment(beat.last_updated).fromNow()} + + + + ) : ( +
Beat not found
+ )} +
+); diff --git a/x-pack/plugins/beats_management/public/pages/beat/activity.tsx b/x-pack/plugins/beats_management/public/pages/beat/activity.tsx new file mode 100644 index 0000000000000..b2d40ad00427f --- /dev/null +++ b/x-pack/plugins/beats_management/public/pages/beat/activity.tsx @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { FrontendLibs } from '../../lib/lib'; + +interface BeatActivityPageProps { + libs: FrontendLibs; +} + +export const BeatActivityPage = (props: BeatActivityPageProps) =>
Beat Activity View
; diff --git a/x-pack/plugins/beats_management/public/pages/beat/detail.tsx b/x-pack/plugins/beats_management/public/pages/beat/detail.tsx new file mode 100644 index 0000000000000..f3306c739abb9 --- /dev/null +++ b/x-pack/plugins/beats_management/public/pages/beat/detail.tsx @@ -0,0 +1,92 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + EuiBadge, + EuiFlexGroup, + EuiFlexItem, + // @ts-ignore EuiInMemoryTable typings not yet available + EuiInMemoryTable, + EuiLink, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import { flatten } from 'lodash'; +import React from 'react'; +import { CMPopulatedBeat } from '../../../common/domain_types'; +import { ConnectedLink } from '../../components/connected_link'; +import { ClientSideBeatTag } from '../../lib/lib'; + +interface BeatDetailPageProps { + beat: CMPopulatedBeat | undefined; +} + +export const BeatDetailPage = (props: BeatDetailPageProps) => { + const { beat } = props; + if (!beat) { + return
Beat not found
; + } + const configurationBlocks = flatten( + beat.full_tags.map((tag: ClientSideBeatTag) => { + return tag.configurations.map(configuration => ({ + ...configuration, + // @ts-ignore one of the types on ClientSideConfigurationBlock doesn't define a "module" property + module: configuration.block_obj.module || null, + tagId: tag.id, + tagColor: tag.color, + ...beat, + })); + }) + ); + + const columns = [ + { + field: 'type', + name: 'Type', + sortable: true, + render: (type: string) => {type}, + }, + { + field: 'module', + name: 'Module', + sortable: true, + }, + { + field: 'description', + name: 'Description', + sortable: true, + }, + { + field: 'tagId', + name: 'Tag', + render: (id: string, block: any) => ( + + {id} + + ), + sortable: true, + }, + ]; + return ( + + + +

Configurations

+
+ +

+ You can have multiple configurations applied to an individual tag. These configurations + can repeat or mix types as necessary. For example, you may utilize three metricbeat + configurations alongside one input and filebeat configuration. +

+
+
+ + + +
+ ); +}; diff --git a/x-pack/plugins/beats_management/public/pages/beat/index.tsx b/x-pack/plugins/beats_management/public/pages/beat/index.tsx index ace411b47b13b..7f9510655aabe 100644 --- a/x-pack/plugins/beats_management/public/pages/beat/index.tsx +++ b/x-pack/plugins/beats_management/public/pages/beat/index.tsx @@ -4,15 +4,137 @@ * you may not use this file except in compliance with the Elastic License. */ +import { + EuiSpacer, + // @ts-ignore types for EuiTab not currently available + EuiTab, + // @ts-ignore types for EuiTabs not currently available + EuiTabs, +} from '@elastic/eui'; import React from 'react'; +import { Route, Switch } from 'react-router-dom'; +import { CMPopulatedBeat } from '../../../common/domain_types'; import { PrimaryLayout } from '../../components/layouts/primary'; +import { FrontendLibs } from '../../lib/lib'; +import { BeatDetailsActionSection } from './action_section'; +import { BeatActivityPage } from './activity'; +import { BeatDetailPage } from './detail'; +import { BeatTagsPage } from './tags'; + +interface Match { + params: any; +} + +interface BeatDetailsPageProps { + history: any; + libs: FrontendLibs; + match: Match; +} + +interface BeatDetailsPageState { + beat: CMPopulatedBeat | undefined; + isLoading: boolean; +} + +export class BeatDetailsPage extends React.PureComponent< + BeatDetailsPageProps, + BeatDetailsPageState +> { + constructor(props: BeatDetailsPageProps) { + super(props); + + this.state = { + beat: undefined, + isLoading: true, + }; + this.loadBeat(); + } + + public onSelectedTabChanged = (id: string) => { + this.props.history.push(id); + }; -export class BeatDetailsPage extends React.PureComponent { public render() { + const { beat } = this.state; + let id; + + if (beat) { + id = beat.id; + } + const title = this.state.isLoading ? 'Loading' : `Beat: ${id}`; + const tabs = [ + { + id: `/beat/${id}`, + name: 'Config', + disabled: false, + }, + { + id: `/beat/${id}/activity`, + name: 'Beat Activity', + disabled: false, + }, + { + id: `/beat/${id}/tags`, + name: 'Tags', + disabled: false, + }, + ]; + return ( - -
beat details view
+ }> + + {tabs.map((tab, index) => ( + { + this.props.history.push(tab.id); + }} + > + {tab.name} + + ))} + + + + } + /> + ( + this.loadBeat()} + {...props} + /> + )} + /> + ( + + )} + /> + ); } + + private async loadBeat() { + const { beatId } = this.props.match.params; + let beat; + try { + beat = await this.props.libs.beats.get(beatId); + if (!beat) { + throw new Error('beat not found'); + } + } catch (e) { + throw new Error(e); + } + this.setState({ beat, isLoading: false }); + } } diff --git a/x-pack/plugins/beats_management/public/pages/beat/tags.tsx b/x-pack/plugins/beats_management/public/pages/beat/tags.tsx new file mode 100644 index 0000000000000..fa8fcc74d25b0 --- /dev/null +++ b/x-pack/plugins/beats_management/public/pages/beat/tags.tsx @@ -0,0 +1,148 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiGlobalToastList } from '@elastic/eui'; +import { get } from 'lodash'; +import React from 'react'; +import { CMPopulatedBeat } from '../../../common/domain_types'; +import { BeatDetailTagsTable, Table } from '../../components/table'; +import { ClientSideBeatTag, FrontendLibs } from '../../lib/lib'; + +interface BeatTagsPageProps { + beat: CMPopulatedBeat; + libs: FrontendLibs; + refreshBeat(): void; +} + +interface BeatTagsPageState { + notifications: any[]; + tags: ClientSideBeatTag[]; +} + +export class BeatTagsPage extends React.PureComponent { + private tableRef = React.createRef
(); + constructor(props: BeatTagsPageProps) { + super(props); + + this.state = { + notifications: [], + tags: [], + }; + + this.getTags(); + } + + public render() { + return ( +
+
+ this.setState({ notifications: [] })} + toastLifeTimeMs={5000} + /> + + ); + } + + private getSelectedTags = () => { + return get(this.tableRef, 'current.state.selection', []); + }; + + private setUpdatedTagNotification = ( + numRemoved: number, + totalTags: number, + action: 'remove' | 'add' + ) => { + const actionName = action === 'remove' ? 'Removed' : 'Added'; + const preposition = action === 'remove' ? 'from' : 'to'; + this.setState({ + notifications: this.state.notifications.concat({ + title: `Tags ${actionName} ${preposition} Beat`, + color: 'success', + text: ( +

{`${actionName} ${numRemoved} of ${totalTags} tags ${preposition} ${ + this.props.beat ? this.props.beat.id : 'beat' + }`}

+ ), + }), + }); + }; + + private handleTableAction = async (action: string, payload: any) => { + switch (action) { + case 'add': + await this.associateTagsToBeat(); + break; + case 'remove': + await this.disassociateTagsFromBeat(); + break; + case 'search': + // TODO: add search filtering for tag names + // awaiting an ES filter endpoint + break; + } + this.props.refreshBeat(); + }; + + private associateTagsToBeat = async () => { + const { beat } = this.props; + + if (!beat) { + throw new Error('Beat cannot be undefined'); + } + + const tagsToAssign = this.getSelectedTags().filter( + (tag: any) => !beat.full_tags.some(({ id }) => tag.id === id) + ); + const assignments = tagsToAssign.map((tag: any) => { + return { + beatId: beat.id, + tag: tag.id, + }; + }); + + await this.props.libs.beats.assignTagsToBeats(assignments); + this.setUpdatedTagNotification(assignments.length, tagsToAssign.length, 'add'); + }; + + private disassociateTagsFromBeat = async () => { + const { beat } = this.props; + + if (!beat) { + throw new Error('Beat cannot be undefined'); + } + + const tagsToDisassociate = this.getSelectedTags().filter((tag: any) => + beat.full_tags.some(({ id }) => tag.id === id) + ); + const assignments = tagsToDisassociate.map((tag: any) => { + return { + beatId: beat.id, + tag: tag.id, + }; + }); + + await this.props.libs.beats.removeTagsFromBeats(assignments); + this.setUpdatedTagNotification(assignments.length, tagsToDisassociate.length, 'remove'); + }; + + private getTags = async () => { + try { + this.setState({ tags: await this.props.libs.tags.getAll() }); + } catch (e) { + throw new Error(e); + } + }; +} diff --git a/x-pack/plugins/beats_management/public/pages/main/tags.tsx b/x-pack/plugins/beats_management/public/pages/main/tags.tsx index b8a5c36a1ed0b..55c48f3ccb7d3 100644 --- a/x-pack/plugins/beats_management/public/pages/main/tags.tsx +++ b/x-pack/plugins/beats_management/public/pages/main/tags.tsx @@ -60,6 +60,7 @@ export class TagsPage extends React.PureComponent assignmentOptions={this.state.beats} assignmentTitle={'Assign Beats'} items={this.state.tags} + renderAssignmentOptions={item => item} ref={this.tableRef} showAssignmentOptions={true} type={TagsTableType} @@ -114,7 +115,10 @@ export class TagsPage extends React.PureComponent const tagIcons = tags.map((tagId: string) => { const associatedTag = this.state.tags.find(tag => tag.id === tagId); return ( - + Last updated: {associatedTag ? associatedTag.last_updated : null}

} + title={tagId} + > return ( - {tagIcons.map(icon => ( - + {tagIcons.map((icon, index) => ( + {icon} ))} diff --git a/x-pack/yarn.lock b/x-pack/yarn.lock index 23616139ac1ea..10efbe9c35f05 100644 --- a/x-pack/yarn.lock +++ b/x-pack/yarn.lock @@ -288,6 +288,10 @@ acorn@^5.0.0, acorn@^5.5.3: version "5.7.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.1.tgz#f095829297706a7c9776958c0afc8930a9b9d9d8" +add-event-listener@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/add-event-listener/-/add-event-listener-0.0.1.tgz#a76229ebc64c8aefae204a16273a2f255abea2d0" + agentkeepalive@^3.4.1: version "3.5.1" resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-3.5.1.tgz#4eba75cf2ad258fc09efd506cdb8d8c2971d35a4" @@ -1752,6 +1756,21 @@ d3-contour@^1.1.0: dependencies: d3-array "^1.1.1" +d3-dispatch@1, d3-dispatch@^1.0.3: + version "1.0.5" + resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-1.0.5.tgz#e25c10a186517cd6c82dd19ea018f07e01e39015" + +d3-drag@1, d3-drag@^1.0.4: + version "1.2.3" + resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-1.2.3.tgz#46e206ad863ec465d88c588098a1df444cd33c64" + dependencies: + d3-dispatch "1" + d3-selection "1" + +d3-ease@1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-1.0.5.tgz#8ce59276d81241b1b72042d6af2d40e76d936ffb" + d3-format@1, d3-format@^1.2.0: version "1.3.0" resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.3.0.tgz#a3ac44269a2011cdb87c7b5693040c18cddfff11" @@ -1808,6 +1827,10 @@ d3-scale@^1.0.5: d3-time "1" d3-time-format "2" +d3-selection@1, d3-selection@^1.1.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-1.3.2.tgz#6e70a9df60801c8af28ac24d10072d82cbfdf652" + d3-shape@^1.1.0, d3-shape@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.2.0.tgz#45d01538f064bafd05ea3d6d2cb748fd8c41f777" @@ -1824,10 +1847,35 @@ d3-time@1: version "1.0.8" resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-1.0.8.tgz#dbd2d6007bf416fe67a76d17947b784bffea1e84" +d3-timer@1, d3-timer@^1.0.5: + version "1.0.9" + resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-1.0.9.tgz#f7bb8c0d597d792ff7131e1c24a36dd471a471ba" + +d3-transition@1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-1.1.3.tgz#3a435b05ce9cef9524fe0d38121cfb6905331ca6" + dependencies: + d3-color "1" + d3-dispatch "1" + d3-ease "1" + d3-interpolate "1" + d3-selection "^1.1.0" + d3-timer "1" + d3-voronoi@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/d3-voronoi/-/d3-voronoi-1.1.2.tgz#1687667e8f13a2d158c80c1480c5a29cb0d8973c" +d3-zoom@^1.1.4: + version "1.7.3" + resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-1.7.3.tgz#f444effdc9055c38077c4299b4df999eb1d47ccb" + dependencies: + d3-dispatch "1" + d3-drag "1" + d3-interpolate "1" + d3-selection "1" + d3-transition "1" + d3@3.5.6: version "3.5.6" resolved "https://registry.yarnpkg.com/d3/-/d3-3.5.6.tgz#9451c651ca733fb9672c81fb7f2655164a73a42d" @@ -2783,6 +2831,12 @@ fstream@^1.0.0, fstream@^1.0.2: mkdirp ">=0.5 0" rimraf "2" +fullscreen@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/fullscreen/-/fullscreen-1.1.1.tgz#b9017c3bf9b23e07effd1bbce910cf5ec2459129" + dependencies: + add-event-listener "0.0.1" + function-bind@^1.1.0, function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" @@ -3103,6 +3157,19 @@ graceful-fs@~1.2.0: version "1.0.1" resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" +graphlib-dot@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/graphlib-dot/-/graphlib-dot-0.6.2.tgz#872e933d0bb349430cb813a2491943d1c5f1e952" + dependencies: + graphlib "^1.0.1" + lodash "^2.4.1" + +graphlib@^1.0.1: + version "1.0.7" + resolved "https://registry.yarnpkg.com/graphlib/-/graphlib-1.0.7.tgz#0cab2df0ffe6abe070b2625bfa1edb6ec967b8b1" + dependencies: + lodash "^3.10.0" + growl@1.9.2: version "1.9.2" resolved "https://registry.yarnpkg.com/growl/-/growl-1.9.2.tgz#0ea7743715db8d8de2c5ede1775e1b45ac85c02f" @@ -3444,6 +3511,13 @@ http-cache-semantics@3.8.1: version "3.8.1" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2" +http-errors@~1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.4.0.tgz#6c0242dea6b3df7afda153c71089b31c6e82aabf" + dependencies: + inherits "2.0.1" + statuses ">= 1.2.1 < 2" + http-signature@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" @@ -3524,6 +3598,10 @@ inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" +inherits@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" + ini@^1.3.4, ini@~1.3.0: version "1.3.5" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" @@ -4286,7 +4364,7 @@ joi@9.x.x: moment "2.x.x" topo "2.x.x" -jquery@^3.3.1: +jquery@^3.1.1, jquery@^3.3.1: version "3.3.1" resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.3.1.tgz#958ce29e81c9790f31be7792df5d4d95fc57fbca" @@ -4814,11 +4892,11 @@ lodash.uniqueid@^3.0.0: dependencies: lodash._root "^3.0.0" -lodash@2.4.2: +lodash@2.4.2, lodash@^2.4.1: version "2.4.2" resolved "https://registry.yarnpkg.com/lodash/-/lodash-2.4.2.tgz#fadd834b9683073da179b3eae6d9c0d15053f73e" -lodash@3.10.1, lodash@^3.10.1: +lodash@3.10.1, lodash@^3.10.0, lodash@^3.10.1: version "3.10.1" resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" @@ -5781,6 +5859,13 @@ path-key@^2.0.0, path-key@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" +path-match@1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/path-match/-/path-match-1.2.4.tgz#a62747f3c7e0c2514762697f24443585b09100ea" + dependencies: + http-errors "~1.4.0" + path-to-regexp "^1.0.0" + path-parse@^1.0.5: version "1.0.6" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" @@ -5795,7 +5880,7 @@ path-root@^0.1.1: dependencies: path-root-regex "^0.1.0" -path-to-regexp@^1.7.0: +path-to-regexp@^1.0.0, path-to-regexp@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d" dependencies: @@ -7192,6 +7277,10 @@ static-module@^2.2.0: static-eval "^2.0.0" through2 "~2.0.3" +"statuses@>= 1.2.1 < 2": + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + stdout-stream@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/stdout-stream/-/stdout-stream-1.4.0.tgz#a2c7c8587e54d9427ea9edb3ac3f2cd522df378b" @@ -7984,6 +8073,18 @@ watch@~0.18.0: exec-sh "^0.2.0" minimist "^1.2.0" +webcola@3.3.6: + version "3.3.6" + resolved "https://registry.yarnpkg.com/webcola/-/webcola-3.3.6.tgz#6ec0bc7a72b3c467a2f2346a8667d88b439a03b4" + dependencies: + d3-dispatch "^1.0.3" + d3-drag "^1.0.4" + d3-timer "^1.0.5" + d3-zoom "^1.1.4" + fullscreen "^1.1.0" + graphlib-dot "^0.6.2" + jquery "^3.1.1" + webidl-conversions@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" diff --git a/yarn.lock b/yarn.lock index c768fd3ffee84..f0aca61b53936 100644 --- a/yarn.lock +++ b/yarn.lock @@ -652,6 +652,10 @@ acorn@^5.0.0, acorn@^5.5.0, acorn@^5.5.3: version "5.7.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.1.tgz#f095829297706a7c9776958c0afc8930a9b9d9d8" +add-event-listener@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/add-event-listener/-/add-event-listener-0.0.1.tgz#a76229ebc64c8aefae204a16273a2f255abea2d0" + adm-zip@0.4.7: version "0.4.7" resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.4.7.tgz#8606c2cbf1c426ce8c8ec00174447fd49b6eafc1" @@ -3524,6 +3528,17 @@ d3-dispatch@1: version "1.0.3" resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-1.0.3.tgz#46e1491eaa9b58c358fce5be4e8bed626e7871f8" +d3-dispatch@^1.0.3: + version "1.0.5" + resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-1.0.5.tgz#e25c10a186517cd6c82dd19ea018f07e01e39015" + +d3-drag@1, d3-drag@^1.0.4: + version "1.2.3" + resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-1.2.3.tgz#46e206ad863ec465d88c588098a1df444cd33c64" + dependencies: + d3-dispatch "1" + d3-selection "1" + d3-dsv@1: version "1.0.8" resolved "https://registry.yarnpkg.com/d3-dsv/-/d3-dsv-1.0.8.tgz#907e240d57b386618dc56468bacfe76bf19764ae" @@ -3532,6 +3547,10 @@ d3-dsv@1: iconv-lite "0.4" rw "1" +d3-ease@1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-1.0.5.tgz#8ce59276d81241b1b72042d6af2d40e76d936ffb" + d3-force@1: version "1.1.0" resolved "https://registry.yarnpkg.com/d3-force/-/d3-force-1.1.0.tgz#cebf3c694f1078fcc3d4daf8e567b2fbd70d4ea3" @@ -3628,6 +3647,10 @@ d3-scale@^2.1.0: d3-time "1" d3-time-format "2" +d3-selection@1, d3-selection@^1.1.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-1.3.2.tgz#6e70a9df60801c8af28ac24d10072d82cbfdf652" + d3-shape@^1.1.0, d3-shape@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.2.0.tgz#45d01538f064bafd05ea3d6d2cb748fd8c41f777" @@ -3648,10 +3671,35 @@ d3-timer@1: version "1.0.7" resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-1.0.7.tgz#df9650ca587f6c96607ff4e60cc38229e8dd8531" +d3-timer@^1.0.5: + version "1.0.9" + resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-1.0.9.tgz#f7bb8c0d597d792ff7131e1c24a36dd471a471ba" + +d3-transition@1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-1.1.3.tgz#3a435b05ce9cef9524fe0d38121cfb6905331ca6" + dependencies: + d3-color "1" + d3-dispatch "1" + d3-ease "1" + d3-interpolate "1" + d3-selection "^1.1.0" + d3-timer "1" + d3-voronoi@1, d3-voronoi@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/d3-voronoi/-/d3-voronoi-1.1.2.tgz#1687667e8f13a2d158c80c1480c5a29cb0d8973c" +d3-zoom@^1.1.4: + version "1.7.3" + resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-1.7.3.tgz#f444effdc9055c38077c4299b4df999eb1d47ccb" + dependencies: + d3-dispatch "1" + d3-drag "1" + d3-interpolate "1" + d3-selection "1" + d3-transition "1" + d3@3.5.6: version "3.5.6" resolved "https://registry.yarnpkg.com/d3/-/d3-3.5.6.tgz#9451c651ca733fb9672c81fb7f2655164a73a42d" @@ -5433,6 +5481,12 @@ fstream@^1.0.0, fstream@^1.0.2: mkdirp ">=0.5 0" rimraf "2" +fullscreen@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/fullscreen/-/fullscreen-1.1.1.tgz#b9017c3bf9b23e07effd1bbce910cf5ec2459129" + dependencies: + add-event-listener "0.0.1" + function-bind@^1.1.0, function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" @@ -5844,6 +5898,19 @@ graceful-fs@4.X, graceful-fs@^4.0.0, graceful-fs@^4.1.10, graceful-fs@^4.1.11, g version "1.0.1" resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" +graphlib-dot@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/graphlib-dot/-/graphlib-dot-0.6.2.tgz#872e933d0bb349430cb813a2491943d1c5f1e952" + dependencies: + graphlib "^1.0.1" + lodash "^2.4.1" + +graphlib@^1.0.1: + version "1.0.7" + resolved "https://registry.yarnpkg.com/graphlib/-/graphlib-1.0.7.tgz#0cab2df0ffe6abe070b2625bfa1edb6ec967b8b1" + dependencies: + lodash "^3.10.0" + growl@1.9.2: version "1.9.2" resolved "https://registry.yarnpkg.com/growl/-/growl-1.9.2.tgz#0ea7743715db8d8de2c5ede1775e1b45ac85c02f" @@ -6324,6 +6391,13 @@ http-errors@1.6.3, http-errors@~1.6.3: setprototypeof "1.1.0" statuses ">= 1.4.0 < 2" +http-errors@~1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.4.0.tgz#6c0242dea6b3df7afda153c71089b31c6e82aabf" + dependencies: + inherits "2.0.1" + statuses ">= 1.2.1 < 2" + http-parser-js@>=0.4.0: version "0.4.13" resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.4.13.tgz#3bd6d6fde6e3172c9334c3b33b6c193d80fe1137" @@ -7547,7 +7621,7 @@ jpeg-js@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.2.0.tgz#53e448ec9d263e683266467e9442d2c5a2ef5482" -jquery@^3.3.1: +jquery@^3.1.1, jquery@^3.3.1: version "3.3.1" resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.3.1.tgz#958ce29e81c9790f31be7792df5d4d95fc57fbca" @@ -8530,7 +8604,7 @@ lodash.uniqueid@^3.0.0: dependencies: lodash._root "^3.0.0" -lodash@2.4.2, lodash@~2.4.1: +lodash@2.4.2, lodash@^2.4.1, lodash@~2.4.1: version "2.4.2" resolved "https://registry.yarnpkg.com/lodash/-/lodash-2.4.2.tgz#fadd834b9683073da179b3eae6d9c0d15053f73e" @@ -9940,6 +10014,13 @@ path-key@^2.0.0, path-key@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" +path-match@1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/path-match/-/path-match-1.2.4.tgz#a62747f3c7e0c2514762697f24443585b09100ea" + dependencies: + http-errors "~1.4.0" + path-to-regexp "^1.0.0" + path-parse@^1.0.5: version "1.0.6" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" @@ -9954,7 +10035,7 @@ path-root@^0.1.1: dependencies: path-root-regex "^0.1.0" -path-to-regexp@^1.7.0: +path-to-regexp@^1.0.0, path-to-regexp@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d" dependencies: @@ -12428,7 +12509,7 @@ static-module@^2.2.0: static-eval "^2.0.0" through2 "~2.0.3" -"statuses@>= 1.4.0 < 2": +"statuses@>= 1.2.1 < 2", "statuses@>= 1.4.0 < 2": version "1.5.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" @@ -14096,6 +14177,18 @@ wcwidth@^1.0.1: dependencies: defaults "^1.0.3" +webcola@3.3.6: + version "3.3.6" + resolved "https://registry.yarnpkg.com/webcola/-/webcola-3.3.6.tgz#6ec0bc7a72b3c467a2f2346a8667d88b439a03b4" + dependencies: + d3-dispatch "^1.0.3" + d3-drag "^1.0.4" + d3-timer "^1.0.5" + d3-zoom "^1.1.4" + fullscreen "^1.1.0" + graphlib-dot "^0.6.2" + jquery "^3.1.1" + webidl-conversions@^3.0.0, webidl-conversions@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" From c4e55d4e5b931040d7a5de862fc3af46c8d6a01d Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Tue, 4 Sep 2018 21:29:09 -0400 Subject: [PATCH 35/94] [Beats CM] move to json for configs (#22693) * move to json for configs, fix some tests * tweaks --- .../beats_management/common/domain_types.ts | 44 ++++++- .../public/components/config_list.tsx | 12 +- .../components/table/table_type_configs.tsx | 5 +- .../tag/config_view/config_form.tsx | 13 +- .../components/tag/config_view/index.tsx | 8 +- .../public/components/tag/tag_edit.tsx | 31 ++--- .../beats_management/public/config_schemas.ts | 5 +- .../public/lib/domains/__tests__/tags.test.ts | 114 ++++++++++++++++++ .../public/lib/domains/tags.ts | 69 +++++++---- .../beats_management/public/lib/lib.ts | 56 --------- .../public/pages/beat/detail.tsx | 11 +- .../public/pages/beat/tags.tsx | 6 +- .../public/pages/main/beats.tsx | 12 +- .../public/pages/main/tags.tsx | 4 +- .../public/pages/tag/index.tsx | 10 +- x-pack/plugins/beats_management/readme.md | 6 + .../framework/kibana_framework_adapter.ts | 2 +- .../server/rest_api/beats/configuration.ts | 2 +- .../server/rest_api/beats/list.ts | 4 +- .../server/rest_api/tags/set.ts | 4 +- .../utils/index_templates/beats_template.json | 11 +- x-pack/plugins/beats_management/wallaby.js | 17 +-- .../api_integration/apis/beats/get_beat.js | 22 ++-- .../es_archives/beats/list/data.json | 21 +++- .../es_archives/beats/list/mappings.json | 90 -------------- 25 files changed, 306 insertions(+), 273 deletions(-) create mode 100644 x-pack/plugins/beats_management/public/lib/domains/__tests__/tags.test.ts delete mode 100644 x-pack/test/functional/es_archives/beats/list/mappings.json diff --git a/x-pack/plugins/beats_management/common/domain_types.ts b/x-pack/plugins/beats_management/common/domain_types.ts index e825cfe654bfb..2bc0a8946e6e0 100644 --- a/x-pack/plugins/beats_management/common/domain_types.ts +++ b/x-pack/plugins/beats_management/common/domain_types.ts @@ -3,13 +3,51 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { ClientSideBeatTag } from '../public/lib/lib'; import { ConfigurationBlockTypes } from './constants'; +export enum FilebeatModuleName { + system = 'system', + apache2 = 'apache2', + nginx = 'nginx', + mongodb = 'mongodb', + elasticsearch = 'elasticsearch', +} + +export enum MetricbeatModuleName { + system = 'system', + apache2 = 'apache2', + nginx = 'nginx', + mongodb = 'mongodb', + elasticsearch = 'elasticsearch', +} + +export enum OutputType { + elasticsearch = 'elasticsearch', + logstash = 'logstash', + kafka = 'kafka', + console = 'console', +} + +export interface FilebeatInputsConfig { + paths: string[]; + other: string; +} +export interface FilebeatModuleConfig { + module: FilebeatModuleName; + other: string; +} +export interface MetricbeatModuleConfig { + module: MetricbeatModuleName; + hosts?: string[]; + period: string; + other: string; +} + +export type ConfigContent = FilebeatInputsConfig | FilebeatModuleConfig | MetricbeatModuleConfig; export interface ConfigurationBlock { type: ConfigurationBlockTypes; description: string; - block_yml: string; + configs: ConfigContent[]; } export interface CMBeat { @@ -32,7 +70,7 @@ export interface CMBeat { } export interface CMPopulatedBeat extends CMBeat { - full_tags: ClientSideBeatTag[]; + full_tags: BeatTag[]; } export interface BeatTag { diff --git a/x-pack/plugins/beats_management/public/components/config_list.tsx b/x-pack/plugins/beats_management/public/components/config_list.tsx index 36ea07cfc86f5..e22f0c2ef5c4f 100644 --- a/x-pack/plugins/beats_management/public/components/config_list.tsx +++ b/x-pack/plugins/beats_management/public/components/config_list.tsx @@ -7,12 +7,12 @@ // @ts-ignore import { EuiBasicTable, EuiLink } from '@elastic/eui'; import React from 'react'; +import { ConfigurationBlock } from '../../common/domain_types'; import { supportedConfigs } from '../config_schemas'; -import { ClientSideConfigurationBlock } from '../lib/lib'; interface ComponentProps { - configs: ClientSideConfigurationBlock[]; - onConfigClick: (action: 'edit' | 'delete', config: ClientSideConfigurationBlock) => any; + configs: ConfigurationBlock[]; + onConfigClick: (action: 'edit' | 'delete', config: ConfigurationBlock) => any; } export const ConfigList: React.SFC = props => ( @@ -23,7 +23,7 @@ export const ConfigList: React.SFC = props => ( field: 'type', name: 'Type', truncateText: false, - render: (value: string, config: ClientSideConfigurationBlock) => { + render: (value: string, config: ConfigurationBlock) => { const type = supportedConfigs.find((sc: any) => sc.value === config.type); return ( @@ -34,7 +34,7 @@ export const ConfigList: React.SFC = props => ( }, }, { - field: 'block_obj.module', + field: 'module', name: 'Module', truncateText: false, render: (value: string) => { @@ -53,7 +53,7 @@ export const ConfigList: React.SFC = props => ( description: 'Remove this config from tag', type: 'icon', icon: 'trash', - onClick: (item: ClientSideConfigurationBlock) => props.onConfigClick('delete', item), + onClick: (item: ConfigurationBlock) => props.onConfigClick('delete', item), }, ], }, diff --git a/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx b/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx index f3e525d7c15e6..962081293eb89 100644 --- a/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx +++ b/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx @@ -12,7 +12,6 @@ import React from 'react'; import { TABLE_CONFIG } from '../../../common/constants'; import { BeatTag, CMPopulatedBeat, ConfigurationBlock } from '../../../common/domain_types'; -import { ClientSideConfigurationBlock } from '../../lib/lib'; import { ConnectedLink } from '../connected_link'; export interface ColumnDefinition { @@ -192,9 +191,7 @@ export const BeatDetailTagsTable: TableType = { align: 'right', field: 'configurations', name: 'Configurations', - render: (configurations: ClientSideConfigurationBlock[]) => ( - {configurations.length} - ), + render: (configurations: ConfigurationBlock[]) => {configurations.length}, sortable: true, }, { diff --git a/x-pack/plugins/beats_management/public/components/tag/config_view/config_form.tsx b/x-pack/plugins/beats_management/public/components/tag/config_view/config_form.tsx index f4cec9230c059..27d9260dcc08a 100644 --- a/x-pack/plugins/beats_management/public/components/tag/config_view/config_form.tsx +++ b/x-pack/plugins/beats_management/public/components/tag/config_view/config_form.tsx @@ -8,7 +8,8 @@ import Formsy, { addValidationRule, FieldValue, FormData } from 'formsy-react'; import yaml from 'js-yaml'; import { get } from 'lodash'; import React from 'react'; -import { ClientSideConfigurationBlock, YamlConfigSchema } from '../../../lib/lib'; +import { ConfigurationBlock } from '../../../../common/domain_types'; +import { YamlConfigSchema } from '../../../lib/lib'; import { FormsyEuiCodeEditor } from '../../inputs/code_editor'; import { FormsyEuiFieldText } from '../../inputs/input'; import { FormsyEuiMultiFieldText } from '../../inputs/multi_input'; @@ -52,7 +53,7 @@ addValidationRule('isYaml', (values: FormData, value: FieldValue) => { }); interface ComponentProps { - values: ClientSideConfigurationBlock; + values: ConfigurationBlock; schema: YamlConfigSchema[]; id: string; canSubmit(canIt: boolean): any; @@ -115,7 +116,7 @@ export class ConfigForm extends React.Component { { { key={schema.id} id={schema.id} name={schema.id} - defaultValue={get(this.props, `values.block_obj.${schema.id}`)} + defaultValue={get(this.props, `values.configs[0].${schema.id}`)} helpText={schema.ui.helpText} label={schema.ui.label} options={[{ value: '', text: 'Please Select An Option' }].concat( @@ -161,7 +162,7 @@ export class ConfigForm extends React.Component { key={`${schema.id}-${this.props.id}`} mode="yaml" id={schema.id} - defaultValue={get(this.props, `values.block_obj.${schema.id}`)} + defaultValue={get(this.props, `values.configs[0].${schema.id}`)} name={schema.id} helpText={schema.ui.helpText} label={schema.ui.label} diff --git a/x-pack/plugins/beats_management/public/components/tag/config_view/index.tsx b/x-pack/plugins/beats_management/public/components/tag/config_view/index.tsx index c6a26bb021a7b..ad1e46a9a7a75 100644 --- a/x-pack/plugins/beats_management/public/components/tag/config_view/index.tsx +++ b/x-pack/plugins/beats_management/public/components/tag/config_view/index.tsx @@ -28,14 +28,14 @@ import { EuiTitle, } from '@elastic/eui'; import React from 'react'; +import { ConfigurationBlock } from '../../../../common/domain_types'; import { supportedConfigs } from '../../../config_schemas'; -import { ClientSideConfigurationBlock } from '../../../lib/lib'; import { ConfigForm } from './config_form'; interface ComponentProps { - configBlock?: ClientSideConfigurationBlock; + configBlock?: ConfigurationBlock; onClose(): any; - onSave(config: ClientSideConfigurationBlock): any; + onSave(config: ConfigurationBlock): any; } export class ConfigView extends React.Component { @@ -99,7 +99,7 @@ export class ConfigView extends React.Component { onSubmit={data => { this.props.onSave({ ...this.state.configBlock, - block_obj: data, + configs: [data], }); this.props.onClose(); }} diff --git a/x-pack/plugins/beats_management/public/components/tag/tag_edit.tsx b/x-pack/plugins/beats_management/public/components/tag/tag_edit.tsx index bc3b5315eba1a..4443a1d133b22 100644 --- a/x-pack/plugins/beats_management/public/components/tag/tag_edit.tsx +++ b/x-pack/plugins/beats_management/public/components/tag/tag_edit.tsx @@ -25,8 +25,7 @@ import 'brace/mode/yaml'; import 'brace/theme/github'; import { isEqual } from 'lodash'; import React from 'react'; -import { CMBeat } from '../../../common/domain_types'; -import { ClientSideBeatTag, ClientSideConfigurationBlock } from '../../lib/lib'; +import { BeatTag, CMBeat, ConfigurationBlock } from '../../../common/domain_types'; import { ConfigList } from '../config_list'; import { Table } from '../table'; import { BeatsTableType } from '../table'; @@ -34,8 +33,8 @@ import { ConfigView } from './config_view'; interface TagEditProps { mode: 'edit' | 'create'; - tag: Pick>; - onTagChange: (field: keyof ClientSideBeatTag, value: string) => any; + tag: Pick>; + onTagChange: (field: keyof BeatTag, value: string) => any; attachedBeats: CMBeat[] | null; } @@ -105,7 +104,9 @@ export class TagEdit extends React.PureComponent { @@ -122,15 +123,15 @@ export class TagEdit extends React.PureComponent {
{ - const selectedIndex = tag.configurations.findIndex(c => { + configs={tag.configuration_blocks} + onConfigClick={(action: string, config: ConfigurationBlock) => { + const selectedIndex = tag.configuration_blocks.findIndex(c => { return isEqual(config, c); }); if (action === 'delete') { - const configs = [...tag.configurations]; + const configs = [...tag.configuration_blocks]; configs.splice(selectedIndex, 1); - this.updateTag('configurations', configs); + this.updateTag('configuration_blocks', configs); } else { this.setState({ showFlyout: true, @@ -176,18 +177,18 @@ export class TagEdit extends React.PureComponent { this.setState({ showFlyout: false, selectedConfigIndex: undefined })} onSave={(config: any) => { this.setState({ showFlyout: false, selectedConfigIndex: undefined }); if (this.state.selectedConfigIndex !== undefined) { - const configs = [...tag.configurations]; + const configs = [...tag.configuration_blocks]; configs[this.state.selectedConfigIndex] = config; - this.updateTag('configurations', configs); + this.updateTag('configuration_blocks', configs); } else { - this.updateTag('configurations', [...tag.configurations, config]); + this.updateTag('configuration_blocks', [...tag.configuration_blocks, config]); } }} /> @@ -205,7 +206,7 @@ export class TagEdit extends React.PureComponent { }; // TODO this should disable save button on bad validations - private updateTag = (key: keyof ClientSideBeatTag, value?: any) => + private updateTag = (key: keyof BeatTag, value?: any) => value !== undefined ? this.props.onTagChange(key, value) : (e: any) => this.props.onTagChange(key, e.target ? e.target.value : e); diff --git a/x-pack/plugins/beats_management/public/config_schemas.ts b/x-pack/plugins/beats_management/public/config_schemas.ts index 1b7da87ef8d58..97024bbdd0c25 100644 --- a/x-pack/plugins/beats_management/public/config_schemas.ts +++ b/x-pack/plugins/beats_management/public/config_schemas.ts @@ -108,20 +108,19 @@ const metricbeatModuleConfig: YamlConfigSchema[] = [ }, validations: 'isHost', error: 'One file host per line', - required: true, + required: false, parseValidResult: v => v.split('\n'), }, { id: 'period', ui: { label: 'Period', - type: 'multi-input', + type: 'input', }, defaultValue: '10s', validations: 'isPeriod', error: 'Invalid Period, must be formatted as `10s` for 10 seconds', required: true, - parseValidResult: v => v.split('\n'), }, { id: 'other', diff --git a/x-pack/plugins/beats_management/public/lib/domains/__tests__/tags.test.ts b/x-pack/plugins/beats_management/public/lib/domains/__tests__/tags.test.ts new file mode 100644 index 0000000000000..4db61bcac3fbe --- /dev/null +++ b/x-pack/plugins/beats_management/public/lib/domains/__tests__/tags.test.ts @@ -0,0 +1,114 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { BeatTag } from '../../../../common/domain_types'; +import { supportedConfigs } from '../../../config_schemas'; +import { CMTagsAdapter } from '../../adapters/tags/adapter_types'; +import { TagsLib } from '../tags'; + +describe('Tags Client Domain Lib', () => { + let tagsLib: TagsLib; + + beforeEach(async () => { + tagsLib = new TagsLib({} as CMTagsAdapter, supportedConfigs); + }); + + it('should use helper function to convert users yaml in tag to config object', async () => { + const convertedTag = tagsLib.userConfigsToJson([ + { + id: 'foo', + configuration_blocks: [ + { + type: 'filebeat.inputs', + description: 'string', + configs: [{ paths: ['adad/adasd'], other: "something: 'here'" }], + }, + ], + color: 'red', + last_updated: new Date(), + } as BeatTag, + ]); + + expect(convertedTag.length).toBe(1); + expect(convertedTag[0].configuration_blocks.length).toBe(1); + expect(convertedTag[0].configuration_blocks[0].configs.length).toBe(1); + expect(convertedTag[0].configuration_blocks[0].configs[0]).not.toHaveProperty('other'); + expect(convertedTag[0].configuration_blocks[0].configs[0]).toHaveProperty('something'); + expect((convertedTag[0].configuration_blocks[0].configs[0] as any).something).toBe('here'); + }); + + it('should use helper function to convert users yaml in tag to config object, where empty other leads to no other fields saved', async () => { + const convertedTag = tagsLib.userConfigsToJson([ + { + id: 'foo', + configuration_blocks: [ + { + type: 'filebeat.inputs', + description: 'string', + configs: [{ paths: ['adad/adasd'], other: '' }], + }, + ], + color: 'red', + last_updated: new Date(), + } as BeatTag, + ]); + + expect(convertedTag.length).toBe(1); + expect(convertedTag[0].configuration_blocks.length).toBe(1); + expect(convertedTag[0].configuration_blocks[0].configs.length).toBe(1); + expect(convertedTag[0].configuration_blocks[0].configs[0]).not.toHaveProperty('other'); + }); + + it('should use helper function to convert config object to users yaml', async () => { + const convertedTag = tagsLib.jsonConfigToUserYaml([ + { + id: 'fsdfsdfs', + color: '#DD0A73', + configuration_blocks: [ + { + type: 'filebeat.inputs', + description: 'sdfsdf', + configs: [{ paths: ['sdfsfsdf'], something: 'here' }], + }, + ], + last_updated: '2018-09-04T15:52:08.983Z', + } as any, + ]); + + expect(convertedTag.length).toBe(1); + expect(convertedTag[0].configuration_blocks.length).toBe(1); + expect(convertedTag[0].configuration_blocks[0].configs.length).toBe(1); + expect(convertedTag[0].configuration_blocks[0].configs[0]).not.toHaveProperty('something'); + expect(convertedTag[0].configuration_blocks[0].configs[0]).toHaveProperty('other'); + + expect(convertedTag[0].configuration_blocks[0].configs[0].other).toBe('something: here\n'); + }); + + it('should use helper function to convert config object to users yaml with empty `other`', async () => { + const convertedTag = tagsLib.jsonConfigToUserYaml([ + { + id: 'fsdfsdfs', + color: '#DD0A73', + configuration_blocks: [ + { + type: 'filebeat.inputs', + description: 'sdfsdf', + configs: [{ paths: ['sdfsfsdf'] }], + }, + ], + last_updated: '2018-09-04T15:52:08.983Z', + } as any, + ]); + + expect(convertedTag.length).toBe(1); + expect(convertedTag[0].configuration_blocks.length).toBe(1); + expect(convertedTag[0].configuration_blocks[0].configs.length).toBe(1); + expect(convertedTag[0].configuration_blocks[0].configs[0]).not.toHaveProperty('something'); + expect(convertedTag[0].configuration_blocks[0].configs[0]).toHaveProperty('other'); + + expect(convertedTag[0].configuration_blocks[0].configs[0].other).toBe(''); + }); +}); diff --git a/x-pack/plugins/beats_management/public/lib/domains/tags.ts b/x-pack/plugins/beats_management/public/lib/domains/tags.ts index 3f09cc23ae926..18fbb0d3143d1 100644 --- a/x-pack/plugins/beats_management/public/lib/domains/tags.ts +++ b/x-pack/plugins/beats_management/public/lib/domains/tags.ts @@ -4,31 +4,32 @@ * you may not use this file except in compliance with the Elastic License. */ import yaml from 'js-yaml'; -import { omit } from 'lodash'; +import { omit, pick } from 'lodash'; import { BeatTag, ConfigurationBlock } from '../../../common/domain_types'; import { CMTagsAdapter } from '../adapters/tags/adapter_types'; -import { ClientSideBeatTag, ClientSideConfigurationBlock } from '../lib'; +import { ConfigContent } from './../../../common/domain_types'; export class TagsLib { constructor(private readonly adapter: CMTagsAdapter, private readonly tagConfigs: any) {} - public async getTagsWithIds(tagIds: string[]): Promise { - return this.tagYamlToConfigs(await this.adapter.getTagsWithIds(tagIds)); + public async getTagsWithIds(tagIds: string[]): Promise { + return this.jsonConfigToUserYaml(await this.adapter.getTagsWithIds(tagIds)); } public async delete(tagIds: string[]): Promise { return await this.adapter.delete(tagIds); } - public async getAll(): Promise { - return this.tagYamlToConfigs(await this.adapter.getAll()); + public async getAll(): Promise { + return this.jsonConfigToUserYaml(await this.adapter.getAll()); } - public async upsertTag(tag: ClientSideBeatTag): Promise { + public async upsertTag(tag: BeatTag): Promise { tag.id = tag.id.replace(' ', '-'); - return await this.adapter.upsertTag(this.tagConfigsToYaml([tag])[0]); + + return await this.adapter.upsertTag(this.userConfigsToJson([tag])[0]); } - private tagYamlToConfigs(tags: BeatTag[]): ClientSideBeatTag[] { + public jsonConfigToUserYaml(tags: BeatTag[]): BeatTag[] { return tags.map(tag => { - const transformedTag: ClientSideBeatTag = tag as any; + const transformedTag: BeatTag = tag as any; // configuration_blocks yaml, JS cant read YAML so we parse it into JS, // because beats flattens all fields, and we need more structure. // we take tagConfigs, grab the config that applies here, render what we can into @@ -37,42 +38,60 @@ export class TagsLib { // NOTE: The perk of this, is that as we support more features via controls // vs yaml editing, it should "just work", and things that were in YAML // will now be in the UI forms... - transformedTag.configurations = tag.configuration_blocks.map(block => { - const { type, description, block_yml } = block; - const rawConfig = yaml.safeLoad(block_yml); + transformedTag.configuration_blocks = tag.configuration_blocks.map(block => { + const { type, description, configs } = block; + const activeConfig = configs[0]; const thisConfig = this.tagConfigs.find((conf: any) => conf.value === type).config; const knownConfigIds = thisConfig.map((config: any) => config.id); + const convertedConfig = knownConfigIds.reduce((blockObj: any, id: keyof ConfigContent) => { + blockObj[id] = + id === 'other' ? yaml.dump(omit(activeConfig, knownConfigIds)) : activeConfig[id]; + + return blockObj; + }, {}); + + // Workaround to empty object passed into dump resulting in this odd output + if (convertedConfig.other && convertedConfig.other === '{}\n') { + convertedConfig.other = ''; + } + return { type, description, - block_obj: knownConfigIds.reduce((blockObj: any, id: string) => { - blockObj[id] = - id === 'other' ? yaml.dump(omit(rawConfig, knownConfigIds)) : rawConfig[id]; - - return blockObj; - }, {}), - } as ClientSideConfigurationBlock; + configs: [convertedConfig], + } as ConfigurationBlock; }); return transformedTag; }); } - private tagConfigsToYaml(tags: ClientSideBeatTag[]): BeatTag[] { + public userConfigsToJson(tags: BeatTag[]): BeatTag[] { return tags.map(tag => { const transformedTag: BeatTag = tag as any; // configurations is the JS representation of the config yaml, // so here we take that JS and convert it into a YAML string. // we do so while also flattening "other" into the flat yaml beats expect - transformedTag.configuration_blocks = tag.configurations.map(block => { - const { type, description, block_obj } = block; - const { other, ...configs } = block_obj; + transformedTag.configuration_blocks = tag.configuration_blocks.map(block => { + const { type, description, configs } = block; + const activeConfig = configs[0]; + const thisConfig = this.tagConfigs.find((conf: any) => conf.value === type).config; + const knownConfigIds = thisConfig + .map((config: any) => config.id) + .filter((id: string) => id !== 'other'); + + const convertedConfig = { + ...yaml.safeLoad(activeConfig.other), + ...pick(activeConfig, knownConfigIds), + }; + return { type, description, - block_yml: yaml.safeDump({ ...configs, ...yaml.safeLoad(other) }), + configs: [convertedConfig], } as ConfigurationBlock; }); + return transformedTag; }); } diff --git a/x-pack/plugins/beats_management/public/lib/lib.ts b/x-pack/plugins/beats_management/public/lib/lib.ts index 34b5cb08ddfc4..7fae363fc9937 100644 --- a/x-pack/plugins/beats_management/public/lib/lib.ts +++ b/x-pack/plugins/beats_management/public/lib/lib.ts @@ -7,7 +7,6 @@ import { IModule, IScope } from 'angular'; import { AxiosRequestConfig } from 'axios'; import React from 'react'; -import { ConfigurationBlock } from '../../common/domain_types'; import { CMTokensAdapter } from './adapters/tokens/adapter_types'; import { BeatsLib } from './domains/beats'; import { TagsLib } from './domains/tags'; @@ -22,34 +21,6 @@ export interface FrontendLibs extends FrontendDomainLibs { framework: FrameworkAdapter; } -export enum FilebeatModuleName { - system = 'system', - apache2 = 'apache2', - nginx = 'nginx', - mongodb = 'mongodb', - elasticsearch = 'elasticsearch', -} - -export enum MetricbeatModuleName { - system = 'system', - apache2 = 'apache2', - nginx = 'nginx', - mongodb = 'mongodb', - elasticsearch = 'elasticsearch', -} - -export enum OutputType { - elasticsearch = 'elasticsearch', - logstash = 'logstash', - kafka = 'kafka', - console = 'console', -} - -export type ClientConfigContent = - | FilebeatInputsConfig - | FilebeatModuleConfig - | MetricbeatModuleConfig; - export interface YamlConfigSchema { id: string; ui: { @@ -65,33 +36,6 @@ export interface YamlConfigSchema { parseValidResult?: (value: any) => any; } -export interface ClientSideBeatTag { - id: string; - configurations: ClientSideConfigurationBlock[]; - color?: string; - last_updated: Date; -} - -export interface ClientSideConfigurationBlock - extends Pick> { - block_obj: ClientConfigContent; -} - -export interface FilebeatInputsConfig { - paths: string[]; - other: string; -} -export interface FilebeatModuleConfig { - module: FilebeatModuleName; - other: string; -} -export interface MetricbeatModuleConfig { - module: MetricbeatModuleName; - hosts: string[]; - period: string; - other: string; -} - export interface FrameworkAdapter { // Instance vars appState?: object; diff --git a/x-pack/plugins/beats_management/public/pages/beat/detail.tsx b/x-pack/plugins/beats_management/public/pages/beat/detail.tsx index f3306c739abb9..72436798741e4 100644 --- a/x-pack/plugins/beats_management/public/pages/beat/detail.tsx +++ b/x-pack/plugins/beats_management/public/pages/beat/detail.tsx @@ -16,9 +16,8 @@ import { } from '@elastic/eui'; import { flatten } from 'lodash'; import React from 'react'; -import { CMPopulatedBeat } from '../../../common/domain_types'; +import { BeatTag, CMPopulatedBeat } from '../../../common/domain_types'; import { ConnectedLink } from '../../components/connected_link'; -import { ClientSideBeatTag } from '../../lib/lib'; interface BeatDetailPageProps { beat: CMPopulatedBeat | undefined; @@ -30,11 +29,11 @@ export const BeatDetailPage = (props: BeatDetailPageProps) => { return
Beat not found
; } const configurationBlocks = flatten( - beat.full_tags.map((tag: ClientSideBeatTag) => { - return tag.configurations.map(configuration => ({ + beat.full_tags.map((tag: BeatTag) => { + return tag.configuration_blocks.map(configuration => ({ ...configuration, - // @ts-ignore one of the types on ClientSideConfigurationBlock doesn't define a "module" property - module: configuration.block_obj.module || null, + // @ts-ignore one of the types on ConfigurationBlock doesn't define a "module" property + module: configuration.configs[0].module || null, tagId: tag.id, tagColor: tag.color, ...beat, diff --git a/x-pack/plugins/beats_management/public/pages/beat/tags.tsx b/x-pack/plugins/beats_management/public/pages/beat/tags.tsx index fa8fcc74d25b0..8c3ef5e547e7e 100644 --- a/x-pack/plugins/beats_management/public/pages/beat/tags.tsx +++ b/x-pack/plugins/beats_management/public/pages/beat/tags.tsx @@ -7,9 +7,9 @@ import { EuiGlobalToastList } from '@elastic/eui'; import { get } from 'lodash'; import React from 'react'; -import { CMPopulatedBeat } from '../../../common/domain_types'; +import { BeatTag, CMPopulatedBeat } from '../../../common/domain_types'; import { BeatDetailTagsTable, Table } from '../../components/table'; -import { ClientSideBeatTag, FrontendLibs } from '../../lib/lib'; +import { FrontendLibs } from '../../lib/lib'; interface BeatTagsPageProps { beat: CMPopulatedBeat; @@ -19,7 +19,7 @@ interface BeatTagsPageProps { interface BeatTagsPageState { notifications: any[]; - tags: ClientSideBeatTag[]; + tags: BeatTag[]; } export class BeatTagsPage extends React.PureComponent { diff --git a/x-pack/plugins/beats_management/public/pages/main/beats.tsx b/x-pack/plugins/beats_management/public/pages/main/beats.tsx index d3980400ced34..5248bf413ff1d 100644 --- a/x-pack/plugins/beats_management/public/pages/main/beats.tsx +++ b/x-pack/plugins/beats_management/public/pages/main/beats.tsx @@ -11,10 +11,10 @@ import { } from '@elastic/eui'; import React from 'react'; -import { CMBeat, CMPopulatedBeat } from '../../../common/domain_types'; +import { BeatTag, CMBeat, CMPopulatedBeat } from '../../../common/domain_types'; import { BeatsTagAssignment } from '../../../server/lib/adapters/beats/adapter_types'; import { BeatsTableType, Table } from '../../components/table'; -import { ClientSideBeatTag, FrontendLibs } from '../../lib/lib'; +import { FrontendLibs } from '../../lib/lib'; import { BeatsActionArea } from './beats_action_area'; interface BeatsPageProps { @@ -55,7 +55,7 @@ export class BeatsPage extends React.PureComponent { + renderAssignmentOptions={(tag: BeatTag) => { const selectedBeats = this.getSelectedBeats(); const hasMatches = selectedBeats.some((beat: any) => (beat.tags || []).some((t: string) => t === tag.id) @@ -136,16 +136,16 @@ export class BeatsPage extends React.PureComponent beats.map(({ id }) => ({ beatId: id, tag: tag.id })); - private removeTagsFromBeats = async (beats: CMPopulatedBeat[], tag: ClientSideBeatTag) => { + private removeTagsFromBeats = async (beats: CMPopulatedBeat[], tag: BeatTag) => { await this.props.libs.beats.removeTagsFromBeats(this.createBeatTagAssignments(beats, tag)); await this.loadBeats(); await this.loadTags(); }; - private assignTagsToBeats = async (beats: CMPopulatedBeat[], tag: ClientSideBeatTag) => { + private assignTagsToBeats = async (beats: CMPopulatedBeat[], tag: BeatTag) => { await this.props.libs.beats.assignTagsToBeats(this.createBeatTagAssignments(beats, tag)); await this.loadBeats(); await this.loadTags(); diff --git a/x-pack/plugins/beats_management/public/pages/main/tags.tsx b/x-pack/plugins/beats_management/public/pages/main/tags.tsx index 55c48f3ccb7d3..90e8ca88ac7c0 100644 --- a/x-pack/plugins/beats_management/public/pages/main/tags.tsx +++ b/x-pack/plugins/beats_management/public/pages/main/tags.tsx @@ -17,7 +17,7 @@ import React from 'react'; import { BeatTag, CMBeat } from '../../../common/domain_types'; import { BeatsTagAssignment } from '../../../server/lib/adapters/beats/adapter_types'; import { Table, TagsTableType } from '../../components/table'; -import { ClientSideBeatTag, FrontendLibs } from '../../lib/lib'; +import { FrontendLibs } from '../../lib/lib'; interface TagsPageProps { libs: FrontendLibs; @@ -25,7 +25,7 @@ interface TagsPageProps { interface TagsPageState { beats: any; - tags: ClientSideBeatTag[]; + tags: BeatTag[]; } export class TagsPage extends React.PureComponent { diff --git a/x-pack/plugins/beats_management/public/pages/tag/index.tsx b/x-pack/plugins/beats_management/public/pages/tag/index.tsx index af9840c63fefc..f5e65493d3241 100644 --- a/x-pack/plugins/beats_management/public/pages/tag/index.tsx +++ b/x-pack/plugins/beats_management/public/pages/tag/index.tsx @@ -9,10 +9,10 @@ import 'brace/mode/yaml'; import 'brace/theme/github'; import React from 'react'; -import { CMPopulatedBeat } from '../../../common/domain_types'; +import { BeatTag, CMPopulatedBeat } from '../../../common/domain_types'; import { PrimaryLayout } from '../../components/layouts/primary'; import { TagEdit } from '../../components/tag'; -import { ClientSideBeatTag, FrontendLibs } from '../../lib/lib'; +import { FrontendLibs } from '../../lib/lib'; interface TagPageProps { libs: FrontendLibs; @@ -23,7 +23,7 @@ interface TagPageProps { interface TagPageState { showFlyout: boolean; attachedBeats: CMPopulatedBeat[] | null; - tag: ClientSideBeatTag; + tag: BeatTag; } export class TagPage extends React.PureComponent { @@ -36,7 +36,7 @@ export class TagPage extends React.PureComponent { tag: { id: props.match.params.action === 'create' ? '' : props.match.params.tagid, color: '#DD0A73', - configurations: [], + configuration_blocks: [], last_updated: new Date(), }, }; @@ -105,7 +105,7 @@ export class TagPage extends React.PureComponent { }); }; private saveTag = async () => { - await this.props.libs.tags.upsertTag(this.state.tag as ClientSideBeatTag); + await this.props.libs.tags.upsertTag(this.state.tag as BeatTag); this.props.history.push('/overview/tags'); }; } diff --git a/x-pack/plugins/beats_management/readme.md b/x-pack/plugins/beats_management/readme.md index 4fca7e635fcfc..3a9717a58f9a5 100644 --- a/x-pack/plugins/beats_management/readme.md +++ b/x-pack/plugins/beats_management/readme.md @@ -9,6 +9,12 @@ Falure to have auth enabled in Kibana will make for a broken UI. UI based errors node scripts/jest.js plugins/beats --watch ``` +and for functional... (from x-pack root) + +``` + node scripts/functional_tests --config test/api_integration/config +``` + ### Run command to fake an enrolling beat (from beats_management dir) ``` diff --git a/x-pack/plugins/beats_management/server/lib/adapters/framework/kibana_framework_adapter.ts b/x-pack/plugins/beats_management/server/lib/adapters/framework/kibana_framework_adapter.ts index d2efd7e49bb39..05d51fe405258 100644 --- a/x-pack/plugins/beats_management/server/lib/adapters/framework/kibana_framework_adapter.ts +++ b/x-pack/plugins/beats_management/server/lib/adapters/framework/kibana_framework_adapter.ts @@ -82,7 +82,7 @@ export class KibanaBackendFrameworkAdapter implements BackendFrameworkAdapter { }; this.server.route({ - handler: wrappedHandler(route.licenseRequired), + handler: wrappedHandler(route.licenseRequired || false), method: route.method, path: route.path, config: route.config, diff --git a/x-pack/plugins/beats_management/server/rest_api/beats/configuration.ts b/x-pack/plugins/beats_management/server/rest_api/beats/configuration.ts index dad9909ddd1e0..590cd51d017c6 100644 --- a/x-pack/plugins/beats_management/server/rest_api/beats/configuration.ts +++ b/x-pack/plugins/beats_management/server/rest_api/beats/configuration.ts @@ -29,7 +29,7 @@ export const createGetBeatConfigurationRoute = (libs: CMServerLibs) => ({ try { beat = await libs.beats.getById(libs.framework.internalUser, beatId); if (beat === null) { - return reply({ message: 'Beat not found' }).code(404); + return reply({ message: `Beat "${beatId}" not found` }).code(404); } const isAccessTokenValid = beat.access_token === accessToken; diff --git a/x-pack/plugins/beats_management/server/rest_api/beats/list.ts b/x-pack/plugins/beats_management/server/rest_api/beats/list.ts index 21f5a9d799b2d..b0ec9fc30526a 100644 --- a/x-pack/plugins/beats_management/server/rest_api/beats/list.ts +++ b/x-pack/plugins/beats_management/server/rest_api/beats/list.ts @@ -14,7 +14,9 @@ export const createListAgentsRoute = (libs: CMServerLibs) => ({ path: '/api/beats/agents/{listByAndValue*}', licenseRequired: true, handler: async (request: FrameworkRequest, reply: any) => { - const listByAndValueParts = request.params.listByAndValue.split('/'); + const listByAndValueParts = request.params.listByAndValue + ? request.params.listByAndValue.split('/') + : []; let listBy: 'tag' | null = null; let listByValue: string | null = null; diff --git a/x-pack/plugins/beats_management/server/rest_api/tags/set.ts b/x-pack/plugins/beats_management/server/rest_api/tags/set.ts index a69f102b127c8..85e3d2a3b03a4 100644 --- a/x-pack/plugins/beats_management/server/rest_api/tags/set.ts +++ b/x-pack/plugins/beats_management/server/rest_api/tags/set.ts @@ -25,7 +25,9 @@ export const createSetTagRoute = (libs: CMServerLibs) => ({ color: Joi.string(), configuration_blocks: Joi.array().items( Joi.object({ - block_yml: Joi.string().required(), + configs: Joi.array() + .items(Joi.object()) + .required(), description: Joi.string(), type: Joi.string() .only(values(ConfigurationBlockTypes)) diff --git a/x-pack/plugins/beats_management/server/utils/index_templates/beats_template.json b/x-pack/plugins/beats_management/server/utils/index_templates/beats_template.json index 54dc9fce1e469..f1a6b98b9f29a 100644 --- a/x-pack/plugins/beats_management/server/utils/index_templates/beats_template.json +++ b/x-pack/plugins/beats_management/server/utils/index_templates/beats_template.json @@ -45,8 +45,9 @@ "description": { "type": "text" }, - "block_yml": { - "type": "text" + "configs": { + "type": "nested", + "dynamic": true } } } @@ -84,15 +85,9 @@ "ephemeral_id": { "type": "keyword" }, - "local_configuration_yml": { - "type": "text" - }, "tags": { "type": "keyword" }, - "orig_configuration_yml": { - "type": "text" - }, "metadata": { "dynamic": "true", "type": "object" diff --git a/x-pack/plugins/beats_management/wallaby.js b/x-pack/plugins/beats_management/wallaby.js index e9901ff5bf23e..54c91f84f1c3c 100644 --- a/x-pack/plugins/beats_management/wallaby.js +++ b/x-pack/plugins/beats_management/wallaby.js @@ -34,12 +34,7 @@ module.exports = function (wallaby) { setup: wallaby => { const path = require('path'); - const kibanaDirectory = path.resolve( - wallaby.localProjectDir, - '..', - '..', - '..' - ); + const kibanaDirectory = path.resolve(wallaby.localProjectDir, '..', '..', '..'); wallaby.testFramework.configure({ rootDir: wallaby.localProjectDir, moduleNameMapper: { @@ -48,13 +43,9 @@ module.exports = function (wallaby) { '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': `${kibanaDirectory}/src/dev/jest/mocks/file_mock.js`, '\\.(css|less|scss)$': `${kibanaDirectory}/src/dev/jest/mocks/style_mock.js`, }, - - setupFiles: [ - `${kibanaDirectory}/x-pack/dev-tools/jest/setup/enzyme.js`, - ], - snapshotSerializers: [ - `${kibanaDirectory}/node_modules/enzyme-to-json/serializer`, - ], + testURL: 'http://localhost', + setupFiles: [`${kibanaDirectory}/x-pack/dev-tools/jest/setup/enzyme.js`], + snapshotSerializers: [`${kibanaDirectory}/node_modules/enzyme-to-json/serializer`], transform: { '^.+\\.js$': `${kibanaDirectory}/src/dev/jest/babel_transform.js`, //"^.+\\.tsx?$": `${kibanaDirectory}/src/dev/jest/ts_transform.js`, diff --git a/x-pack/test/api_integration/apis/beats/get_beat.js b/x-pack/test/api_integration/apis/beats/get_beat.js index 8d6a81d100eb4..d59f12204ef52 100644 --- a/x-pack/test/api_integration/apis/beats/get_beat.js +++ b/x-pack/test/api_integration/apis/beats/get_beat.js @@ -32,20 +32,20 @@ export default function ({ getService }) { expect(configurationBlocks).to.be.an(Array); expect(configurationBlocks.length).to.be(3); - expect(configurationBlocks[0].type).to.be('output'); - expect(configurationBlocks[0].block_yml).to.be( - 'elasticsearch:\n hosts: ["localhost:9200"]\n username: "..."' - ); - expect(configurationBlocks[1].type).to.be('metricbeat.modules'); - expect(configurationBlocks[1].block_yml).to.be( - 'module: memcached\nhosts: ["localhost:11211"]' - ); + expect(configurationBlocks[1].configs).to.be.an('array'); + expect(configurationBlocks[1].configs[0]).to.eql({ + module: 'memcached', + hosts: ['localhost:11211'], + }); expect(configurationBlocks[2].type).to.be('metricbeat.modules'); - expect(configurationBlocks[2].block_yml).to.be( - 'module: munin\nhosts: ["localhost:4949"]\nnode.namespace: node' - ); + expect(configurationBlocks[2].configs).to.be.an('array'); + expect(configurationBlocks[2].configs[0]).to.eql({ + module: 'memcached', + hosts: ['localhost:4949'], + 'node.namespace': 'node', + }); }); }); } diff --git a/x-pack/test/functional/es_archives/beats/list/data.json b/x-pack/test/functional/es_archives/beats/list/data.json index bbe5bc8dbe3e5..a9c28d8e132d0 100644 --- a/x-pack/test/functional/es_archives/beats/list/data.json +++ b/x-pack/test/functional/es_archives/beats/list/data.json @@ -8,6 +8,7 @@ "type": "beat", "beat": { "type": "filebeat", + "active": true, "host_ip": "1.2.3.4", "host_name": "foo.bar.com", "id": "qux", @@ -27,6 +28,7 @@ "type": "beat", "beat": { "type": "metricbeat", + "active": true, "host_ip": "22.33.11.44", "host_name": "baz.bar.com", "id": "baz", @@ -46,6 +48,7 @@ "type": "beat", "beat": { "type": "metricbeat", + "active": true, "host_ip": "1.2.3.4", "host_name": "foo.bar.com", "id": "foo", @@ -70,6 +73,7 @@ "type": "beat", "beat": { "type": "filebeat", + "active": true, "host_ip": "11.22.33.44", "host_name": "foo.com", "id": "bar", @@ -91,11 +95,18 @@ "configuration_blocks": [ { "type": "output", - "block_yml": "elasticsearch:\n hosts: [\"localhost:9200\"]\n username: \"...\"" + "description": "some description", + "configs": [{ + "hosts": ["localhost:9200"], + "username": "some-username" + }] }, { "type": "metricbeat.modules", - "block_yml": "module: memcached\nhosts: [\"localhost:11211\"]" + "configs": [{ + "module": "memcached", + "hosts": ["localhost:11211"] + }] } ] } @@ -130,7 +141,11 @@ "configuration_blocks": [ { "type": "metricbeat.modules", - "block_yml": "module: munin\nhosts: [\"localhost:4949\"]\nnode.namespace: node" + "configs": [{ + "module": "memcached", + "node.namespace": "node", + "hosts": ["localhost:4949"] + }] } ] } diff --git a/x-pack/test/functional/es_archives/beats/list/mappings.json b/x-pack/test/functional/es_archives/beats/list/mappings.json deleted file mode 100644 index caabcc13a9353..0000000000000 --- a/x-pack/test/functional/es_archives/beats/list/mappings.json +++ /dev/null @@ -1,90 +0,0 @@ -{ - "type": "index", - "value": { - "index": ".management-beats", - "settings": { - "index": { - "codec": "best_compression", - "number_of_shards": "1", - "auto_expand_replicas": "0-1", - "number_of_replicas": "0" - } - }, - "mappings": { - "_doc": { - "dynamic": "strict", - "properties": { - "type": { - "type": "keyword" - }, - "enrollment_token": { - "properties": { - "token": { - "type": "keyword" - }, - "expires_on": { - "type": "date" - } - } - }, - "tag": { - "properties": { - "id": { - "type": "keyword" - }, - "configuration_blocks": { - "type": "nested", - "properties": { - "type": { - "type": "keyword" - }, - "block_yml": { - "type": "text" - } - } - } - } - }, - "beat": { - "properties": { - "id": { - "type": "keyword" - }, - "access_token": { - "type": "keyword" - }, - "verified_on": { - "type": "date" - }, - "type": { - "type": "keyword" - }, - "version": { - "type": "keyword" - }, - "host_ip": { - "type": "ip" - }, - "host_name": { - "type": "keyword" - }, - "ephemeral_id": { - "type": "keyword" - }, - "local_configuration_yml": { - "type": "text" - }, - "tags": { - "type": "keyword" - }, - "metadata": { - "dynamic": "true", - "type": "object" - } - } - } - } - } - } - } -} \ No newline at end of file From 09042bb58be9ea61e90f7df6be4fe73b659097da Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Wed, 5 Sep 2018 12:56:41 -0400 Subject: [PATCH 36/94] add fixes (#22711) * add fixes * return only one config * much improved validation for hosts, no longer require not required fields, no longer have JS in the config form config * fix condition argument --- .../beats_management/common/domain_types.ts | 5 +++ .../public/components/config_list.tsx | 2 +- .../public/components/inputs/code_editor.tsx | 4 +-- .../public/components/inputs/multi_input.tsx | 6 ++-- .../tag/config_view/config_form.tsx | 32 +++++++++++-------- .../public/components/tag/tag_edit.tsx | 5 ++- .../beats_management/public/config_schemas.ts | 4 +-- .../public/lib/domains/__tests__/tags.test.ts | 24 +++++++++++++- .../beats_management/public/lib/lib.ts | 2 +- .../server/rest_api/beats/configuration.ts | 25 ++++++++++++--- .../server/rest_api/tags/set.ts | 2 +- .../api_integration/apis/beats/get_beat.js | 8 ++--- 12 files changed, 83 insertions(+), 36 deletions(-) diff --git a/x-pack/plugins/beats_management/common/domain_types.ts b/x-pack/plugins/beats_management/common/domain_types.ts index 2bc0a8946e6e0..735249922116b 100644 --- a/x-pack/plugins/beats_management/common/domain_types.ts +++ b/x-pack/plugins/beats_management/common/domain_types.ts @@ -50,6 +50,11 @@ export interface ConfigurationBlock { configs: ConfigContent[]; } +export interface ReturnedConfigurationBlock + extends Pick> { + config: ConfigContent; +} + export interface CMBeat { id: string; enrollment_token: string; diff --git a/x-pack/plugins/beats_management/public/components/config_list.tsx b/x-pack/plugins/beats_management/public/components/config_list.tsx index e22f0c2ef5c4f..b04163a183264 100644 --- a/x-pack/plugins/beats_management/public/components/config_list.tsx +++ b/x-pack/plugins/beats_management/public/components/config_list.tsx @@ -17,7 +17,7 @@ interface ComponentProps { export const ConfigList: React.SFC = props => ( ) => { - const { value } = e.currentTarget; + const value = e.currentTarget.value.split('\n'); this.props.setValue(value); if (this.props.onChange) { - this.props.onChange(e, e.currentTarget.value); + this.props.onChange(e, value); } if (this.props.instantValidation) { this.showError(); @@ -96,7 +96,7 @@ class MultiFieldText extends Component< { - return value && value.length > 0; +addValidationRule('isHosts', (form: FormData, values: FieldValue | string[]) => { + if (values && values.length > 0 && values instanceof Array) { + return values.reduce((pass: boolean, value: string) => { + if ( + pass && + value.match( + new RegExp( + '^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]).)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9])$' + ) + ) !== null + ) { + return true; + } + return false; + }, true); + } else { + return true; + } }); addValidationRule('isString', (values: FormData, value: FieldValue) => { @@ -88,17 +104,7 @@ export class ConfigForm extends React.Component { } }; public onValidSubmit = (model: ModelType) => { - const newModel: any = {}; - - Object.keys(model).forEach(field => { - const fieldSchema = this.props.schema.find(s => s.id === field); - if (fieldSchema && fieldSchema.parseValidResult) { - newModel[field] = fieldSchema.parseValidResult(model[field]); - } else { - newModel[field] = model[field]; - } - }); - this.props.onSubmit(newModel); + this.props.onSubmit(model); }; public render() { return ( diff --git a/x-pack/plugins/beats_management/public/components/tag/tag_edit.tsx b/x-pack/plugins/beats_management/public/components/tag/tag_edit.tsx index 4443a1d133b22..9364f7e21313f 100644 --- a/x-pack/plugins/beats_management/public/components/tag/tag_edit.tsx +++ b/x-pack/plugins/beats_management/public/components/tag/tag_edit.tsx @@ -188,7 +188,10 @@ export class TagEdit extends React.PureComponent { configs[this.state.selectedConfigIndex] = config; this.updateTag('configuration_blocks', configs); } else { - this.updateTag('configuration_blocks', [...tag.configuration_blocks, config]); + this.updateTag('configuration_blocks', [ + ...(tag.configuration_blocks || []), + config, + ]); } }} /> diff --git a/x-pack/plugins/beats_management/public/config_schemas.ts b/x-pack/plugins/beats_management/public/config_schemas.ts index 97024bbdd0c25..383e9d76db787 100644 --- a/x-pack/plugins/beats_management/public/config_schemas.ts +++ b/x-pack/plugins/beats_management/public/config_schemas.ts @@ -16,7 +16,6 @@ const filebeatInputConfig: YamlConfigSchema[] = [ validations: 'isPaths', error: 'One file path per line', required: true, - parseValidResult: v => v.split('\n'), }, { id: 'other', @@ -106,10 +105,9 @@ const metricbeatModuleConfig: YamlConfigSchema[] = [ label: 'Hosts', type: 'multi-input', }, - validations: 'isHost', + validations: 'isHosts', error: 'One file host per line', required: false, - parseValidResult: v => v.split('\n'), }, { id: 'period', diff --git a/x-pack/plugins/beats_management/public/lib/domains/__tests__/tags.test.ts b/x-pack/plugins/beats_management/public/lib/domains/__tests__/tags.test.ts index 4db61bcac3fbe..f48ab021d8271 100644 --- a/x-pack/plugins/beats_management/public/lib/domains/__tests__/tags.test.ts +++ b/x-pack/plugins/beats_management/public/lib/domains/__tests__/tags.test.ts @@ -40,6 +40,28 @@ describe('Tags Client Domain Lib', () => { expect((convertedTag[0].configuration_blocks[0].configs[0] as any).something).toBe('here'); }); + it('should use helper function to convert user config to json with undefined `other`', async () => { + const convertedTag = tagsLib.userConfigsToJson([ + { + id: 'fsdfsdfs', + color: '#DD0A73', + configuration_blocks: [ + { + type: 'filebeat.inputs', + description: 'sdfsdf', + configs: [{ paths: ['sdfsfsdf'], other: undefined }], + }, + ], + last_updated: '2018-09-04T15:52:08.983Z', + } as any, + ]); + + expect(convertedTag.length).toBe(1); + expect(convertedTag[0].configuration_blocks.length).toBe(1); + expect(convertedTag[0].configuration_blocks[0].configs.length).toBe(1); + expect(convertedTag[0].configuration_blocks[0].configs[0]).not.toHaveProperty('other'); + }); + it('should use helper function to convert users yaml in tag to config object, where empty other leads to no other fields saved', async () => { const convertedTag = tagsLib.userConfigsToJson([ { @@ -95,7 +117,7 @@ describe('Tags Client Domain Lib', () => { configuration_blocks: [ { type: 'filebeat.inputs', - description: 'sdfsdf', + description: undefined, configs: [{ paths: ['sdfsfsdf'] }], }, ], diff --git a/x-pack/plugins/beats_management/public/lib/lib.ts b/x-pack/plugins/beats_management/public/lib/lib.ts index 7fae363fc9937..261357d5fce47 100644 --- a/x-pack/plugins/beats_management/public/lib/lib.ts +++ b/x-pack/plugins/beats_management/public/lib/lib.ts @@ -29,7 +29,7 @@ export interface YamlConfigSchema { helpText?: string; }; options?: Array<{ value: string; text: string }>; - validations?: 'isHost' | 'isString' | 'isPeriod' | 'isPath' | 'isPaths' | 'isYaml'; + validations?: 'isHosts' | 'isString' | 'isPeriod' | 'isPath' | 'isPaths' | 'isYaml'; error: string; defaultValue?: string; required?: boolean; diff --git a/x-pack/plugins/beats_management/server/rest_api/beats/configuration.ts b/x-pack/plugins/beats_management/server/rest_api/beats/configuration.ts index 590cd51d017c6..7d283633b9f09 100644 --- a/x-pack/plugins/beats_management/server/rest_api/beats/configuration.ts +++ b/x-pack/plugins/beats_management/server/rest_api/beats/configuration.ts @@ -3,11 +3,12 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - import Joi from 'joi'; +import { omit } from 'lodash'; import { BeatTag, ConfigurationBlock } from '../../../common/domain_types'; import { CMServerLibs } from '../../lib/lib'; import { wrapEsError } from '../../utils/error_wrappers'; +import { ReturnedConfigurationBlock } from './../../../common/domain_types'; export const createGetBeatConfigurationRoute = (libs: CMServerLibs) => ({ method: 'GET', @@ -42,10 +43,24 @@ export const createGetBeatConfigurationRoute = (libs: CMServerLibs) => ({ return reply(wrapEsError(err)); } - const configurationBlocks = tags.reduce((blocks: ConfigurationBlock[], tag: BeatTag) => { - blocks = blocks.concat(tag.configuration_blocks); - return blocks; - }, []); + const configurationBlocks = tags.reduce( + (blocks: ReturnedConfigurationBlock[], tag: BeatTag) => { + blocks = blocks.concat( + tag.configuration_blocks.reduce( + (acc: ReturnedConfigurationBlock[], block: ConfigurationBlock) => { + acc.push({ + ...omit(block, ['configs']), + config: block.configs[0], + }); + return acc; + }, + [] + ) + ); + return blocks; + }, + [] + ); reply({ configuration_blocks: configurationBlocks, diff --git a/x-pack/plugins/beats_management/server/rest_api/tags/set.ts b/x-pack/plugins/beats_management/server/rest_api/tags/set.ts index 85e3d2a3b03a4..580af102319b3 100644 --- a/x-pack/plugins/beats_management/server/rest_api/tags/set.ts +++ b/x-pack/plugins/beats_management/server/rest_api/tags/set.ts @@ -28,7 +28,7 @@ export const createSetTagRoute = (libs: CMServerLibs) => ({ configs: Joi.array() .items(Joi.object()) .required(), - description: Joi.string(), + description: Joi.string().allow(''), type: Joi.string() .only(values(ConfigurationBlockTypes)) .required(), diff --git a/x-pack/test/api_integration/apis/beats/get_beat.js b/x-pack/test/api_integration/apis/beats/get_beat.js index d59f12204ef52..c15c7e66f7e29 100644 --- a/x-pack/test/api_integration/apis/beats/get_beat.js +++ b/x-pack/test/api_integration/apis/beats/get_beat.js @@ -33,15 +33,15 @@ export default function ({ getService }) { expect(configurationBlocks.length).to.be(3); expect(configurationBlocks[1].type).to.be('metricbeat.modules'); - expect(configurationBlocks[1].configs).to.be.an('array'); - expect(configurationBlocks[1].configs[0]).to.eql({ + expect(configurationBlocks[1].config).not.to.be.an('array'); + expect(configurationBlocks[1].config).to.eql({ module: 'memcached', hosts: ['localhost:11211'], }); expect(configurationBlocks[2].type).to.be('metricbeat.modules'); - expect(configurationBlocks[2].configs).to.be.an('array'); - expect(configurationBlocks[2].configs[0]).to.eql({ + expect(configurationBlocks[2].config).not.to.be.an('array'); + expect(configurationBlocks[2].config).to.eql({ module: 'memcached', hosts: ['localhost:4949'], 'node.namespace': 'node', From ff64079428e4102a21d515937cc52655cb017705 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Wed, 5 Sep 2018 13:15:30 -0400 Subject: [PATCH 37/94] [Beats CM] Improve UX for assignment of tag to list of beats (#22687) * Improve UX for assignment of tag to list of beats. * Revert a change. --- .../public/components/table/table.tsx | 14 +- .../public/components/tag/index.ts | 1 + .../public/components/tag/tag_assignment.tsx | 71 ++++++++++ .../public/pages/main/beats.tsx | 122 +++++++++++------- 4 files changed, 155 insertions(+), 53 deletions(-) create mode 100644 x-pack/plugins/beats_management/public/components/tag/tag_assignment.tsx diff --git a/x-pack/plugins/beats_management/public/components/table/table.tsx b/x-pack/plugins/beats_management/public/components/table/table.tsx index 36829dba8abf6..b1d95391c367d 100644 --- a/x-pack/plugins/beats_management/public/components/table/table.tsx +++ b/x-pack/plugins/beats_management/public/components/table/table.tsx @@ -46,6 +46,12 @@ export class Table extends React.Component { this.setSelection([]); }; + public setSelection = (selection: any[]) => { + this.setState({ + selection, + }); + }; + public render() { const { actionHandler, @@ -72,7 +78,7 @@ export class Table extends React.Component { return ( actionHandler(action, payload)} + actionHandler={actionHandler} assignmentOptions={assignmentOptions} renderAssignmentOptions={renderAssignmentOptions} assignmentTitle={assignmentTitle} @@ -93,10 +99,4 @@ export class Table extends React.Component { ); } - - private setSelection = (selection: any[]) => { - this.setState({ - selection, - }); - }; } diff --git a/x-pack/plugins/beats_management/public/components/tag/index.ts b/x-pack/plugins/beats_management/public/components/tag/index.ts index bfea59263fc44..4bbc9767f39ae 100644 --- a/x-pack/plugins/beats_management/public/components/tag/index.ts +++ b/x-pack/plugins/beats_management/public/components/tag/index.ts @@ -4,4 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ +export { TagAssignment } from './tag_assignment'; export { TagEdit } from './tag_edit'; diff --git a/x-pack/plugins/beats_management/public/components/tag/tag_assignment.tsx b/x-pack/plugins/beats_management/public/components/tag/tag_assignment.tsx new file mode 100644 index 0000000000000..ed5bb706b1092 --- /dev/null +++ b/x-pack/plugins/beats_management/public/components/tag/tag_assignment.tsx @@ -0,0 +1,71 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; +import React from 'react'; +import { CMPopulatedBeat } from '../../../common/domain_types'; +import { ClientSideBeatTag } from '../../lib/lib'; + +interface TagAssignmentProps { + selectedBeats: CMPopulatedBeat[]; + tag: ClientSideBeatTag; + assignTagsToBeats(selectedBeats: any, tag: any): void; + removeTagsFromBeats(selectedBeats: any, tag: any): void; +} + +interface TagAssignmentState { + isFetchingTags: boolean; +} + +export class TagAssignment extends React.PureComponent { + constructor(props: TagAssignmentProps) { + super(props); + + this.state = { + isFetchingTags: false, + }; + } + + public render() { + const { + assignTagsToBeats, + removeTagsFromBeats, + selectedBeats, + tag, + tag: { id, color }, + } = this.props; + + const hasMatches = selectedBeats.some(({ tags }: CMPopulatedBeat) => + (tags || []).some((t: string) => t === id) + ); + + return ( + + {this.state.isFetchingTags && ( + + + + )} + + { + this.setState({ isFetchingTags: true }); + hasMatches + ? removeTagsFromBeats(selectedBeats, tag) + : assignTagsToBeats(selectedBeats, tag); + this.setState({ isFetchingTags: false }); + }} + onClickAriaLabel={id} + > + {id} + + + + ); + } +} diff --git a/x-pack/plugins/beats_management/public/pages/main/beats.tsx b/x-pack/plugins/beats_management/public/pages/main/beats.tsx index 5248bf413ff1d..92fa0cf1c8cbd 100644 --- a/x-pack/plugins/beats_management/public/pages/main/beats.tsx +++ b/x-pack/plugins/beats_management/public/pages/main/beats.tsx @@ -4,16 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - // @ts-ignore typings for EuiBadge not present in current version - EuiBadge, - EuiFlexItem, -} from '@elastic/eui'; - +import { EuiGlobalToastList } from '@elastic/eui'; import React from 'react'; -import { BeatTag, CMBeat, CMPopulatedBeat } from '../../../common/domain_types'; +import { BeatTag, CMPopulatedBeat } from '../../../common/domain_types'; import { BeatsTagAssignment } from '../../../server/lib/adapters/beats/adapter_types'; import { BeatsTableType, Table } from '../../components/table'; +import { TagAssignment } from '../../components/tag'; import { FrontendLibs } from '../../lib/lib'; import { BeatsActionArea } from './beats_action_area'; @@ -23,7 +19,8 @@ interface BeatsPageProps { } interface BeatsPageState { - beats: CMBeat[]; + beats: CMPopulatedBeat[]; + notifications: any[]; tableRef: any; tags: any[] | null; } @@ -35,6 +32,7 @@ export class BeatsPage extends React.PureComponent { - const selectedBeats = this.getSelectedBeats(); - const hasMatches = selectedBeats.some((beat: any) => - (beat.tags || []).some((t: string) => t === tag.id) - ); - - return ( - - this.removeTagsFromBeats(selectedBeats, tag) - : () => this.assignTagsToBeats(selectedBeats, tag) - } - onClickAriaLabel={tag.id} - > - {tag.id} - - - ); - }} - type={BeatsTableType} - /> +
+
+ this.setState({ notifications: [] })} + toastLifeTimeMs={5000} + /> + ); } + private sortBeats = (a: CMPopulatedBeat, b: CMPopulatedBeat) => (a.id < b.id ? 0 : 1); + + private renderTagAssignment = (tag: BeatTag) => ( + + ); + private handleBeatsActions = (action: string, payload: any) => { switch (action) { case 'edit': @@ -140,18 +134,54 @@ export class BeatsPage extends React.PureComponent beats.map(({ id }) => ({ beatId: id, tag: tag.id })); private removeTagsFromBeats = async (beats: CMPopulatedBeat[], tag: BeatTag) => { - await this.props.libs.beats.removeTagsFromBeats(this.createBeatTagAssignments(beats, tag)); - await this.loadBeats(); - await this.loadTags(); + const assignments = this.createBeatTagAssignments(beats, tag); + await this.props.libs.beats.removeTagsFromBeats(assignments); + await this.refreshData(); + this.notifyUpdatedTagAssociation('remove', assignments, tag.id); }; private assignTagsToBeats = async (beats: CMPopulatedBeat[], tag: BeatTag) => { - await this.props.libs.beats.assignTagsToBeats(this.createBeatTagAssignments(beats, tag)); - await this.loadBeats(); + const assignments = this.createBeatTagAssignments(beats, tag); + await this.props.libs.beats.assignTagsToBeats(assignments); + await this.refreshData(); + this.notifyUpdatedTagAssociation('add', assignments, tag.id); + }; + + private notifyUpdatedTagAssociation = ( + action: 'add' | 'remove', + assignments: BeatsTagAssignment[], + tag: string + ) => { + const actionName = action === 'remove' ? 'Removed' : 'Added'; + const preposition = action === 'remove' ? 'from' : 'to'; + const beatMessage = + assignments.length && assignments.length === 1 + ? `beat "${assignments[0].beatId}"` + : `${assignments.length} beats`; + this.setState({ + notifications: this.state.notifications.concat({ + title: `Tag ${actionName}`, + color: 'success', + text:

{`${actionName} tag "${tag}" ${preposition} ${beatMessage}.`}

, + }), + }); + }; + + private refreshData = async () => { await this.loadTags(); + await this.loadBeats(); + this.state.tableRef.current.setSelection(this.getSelectedBeats()); }; private getSelectedBeats = (): CMPopulatedBeat[] => { - return this.state.tableRef.current.state.selection; + const selectedIds = this.state.tableRef.current.state.selection.map((beat: any) => beat.id); + const beats: CMPopulatedBeat[] = []; + selectedIds.forEach((id: any) => { + const beat: CMPopulatedBeat | undefined = this.state.beats.find(b => b.id === id); + if (beat) { + beats.push(beat); + } + }); + return beats; }; } From 6ecce0f8ca82b1b8f84110fb637930fe14dbd509 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Fri, 7 Sep 2018 10:21:30 -0400 Subject: [PATCH 38/94] [Beats CM] Sort beat list tags (#22729) * Add logic to sort tags by ID when rendering in Beats List. * Prefer lodash sortBy over inline sort implementation. --- .../public/components/table/table_type_configs.tsx | 4 ++-- x-pack/plugins/beats_management/public/pages/main/beats.tsx | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx b/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx index 962081293eb89..721da6ed7a406 100644 --- a/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx +++ b/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx @@ -6,7 +6,7 @@ // @ts-ignore import { EuiBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { flatten, uniq } from 'lodash'; +import { flatten, sortBy, uniq } from 'lodash'; import moment from 'moment'; import React from 'react'; @@ -70,7 +70,7 @@ export const BeatsTableType: TableType = { name: 'Tags', render: (value: string, beat: CMPopulatedBeat) => ( - {(beat.full_tags || []).map(tag => ( + {(sortBy(beat.full_tags, 'id') || []).map(tag => ( {tag.id} diff --git a/x-pack/plugins/beats_management/public/pages/main/beats.tsx b/x-pack/plugins/beats_management/public/pages/main/beats.tsx index 92fa0cf1c8cbd..8d13899328cbe 100644 --- a/x-pack/plugins/beats_management/public/pages/main/beats.tsx +++ b/x-pack/plugins/beats_management/public/pages/main/beats.tsx @@ -5,6 +5,7 @@ */ import { EuiGlobalToastList } from '@elastic/eui'; +import { sortBy } from 'lodash'; import React from 'react'; import { BeatTag, CMPopulatedBeat } from '../../../common/domain_types'; import { BeatsTagAssignment } from '../../../server/lib/adapters/beats/adapter_types'; @@ -51,7 +52,7 @@ export class BeatsPage extends React.PureComponent (a.id < b.id ? 0 : 1); - private renderTagAssignment = (tag: BeatTag) => ( Date: Fri, 7 Sep 2018 15:07:33 -0400 Subject: [PATCH 39/94] [Beats CM] Remove key warnings from beat list components (#22772) * Add logic to sort tags by ID when rendering in Beats List. * Remove key warnings from beats list and associated components. * Prefer lodash sortBy over inline sort implementation. --- .../public/components/table/assignment_options.tsx | 6 ++++-- .../public/components/table/controls.tsx | 2 +- .../beats_management/public/components/table/table.tsx | 2 +- .../public/components/table/table_type_configs.tsx | 6 ++---- .../public/components/tag/tag_assignment.tsx | 9 ++++----- .../plugins/beats_management/public/pages/main/beats.tsx | 7 +++++-- 6 files changed, 17 insertions(+), 15 deletions(-) diff --git a/x-pack/plugins/beats_management/public/components/table/assignment_options.tsx b/x-pack/plugins/beats_management/public/components/table/assignment_options.tsx index 89affe7ac2a72..67d32ba15ea33 100644 --- a/x-pack/plugins/beats_management/public/components/table/assignment_options.tsx +++ b/x-pack/plugins/beats_management/public/components/table/assignment_options.tsx @@ -12,7 +12,7 @@ import { ControlDefinitions } from './table_type_configs'; interface AssignmentOptionsProps { assignmentOptions: any[] | null; assignmentTitle: string | null; - renderAssignmentOptions?: (item: any) => any; + renderAssignmentOptions?: (item: any, key: string) => any; controlDefinitions: ControlDefinitions; selectionCount: number; actionHandler(action: string, payload?: any): void; @@ -91,7 +91,9 @@ export class AssignmentOptions extends React.Component< {assignmentOptions && renderAssignmentOptions ? ( // @ts-ignore direction prop not available on current typing - {assignmentOptions.map(options => renderAssignmentOptions(options))} + {assignmentOptions.map((options, index) => + renderAssignmentOptions(options, `${index}`) + )} ) : (
diff --git a/x-pack/plugins/beats_management/public/components/table/controls.tsx b/x-pack/plugins/beats_management/public/components/table/controls.tsx index d45f4aa7f4bb8..c887f2e1521eb 100644 --- a/x-pack/plugins/beats_management/public/components/table/controls.tsx +++ b/x-pack/plugins/beats_management/public/components/table/controls.tsx @@ -12,7 +12,7 @@ import { ControlDefinitions } from './table_type_configs'; interface ControlBarProps { assignmentOptions: any[] | null; assignmentTitle: string | null; - renderAssignmentOptions?: (item: any) => any; + renderAssignmentOptions?: (item: any, key: string) => any; showAssignmentOptions: boolean; controlDefinitions: ControlDefinitions; diff --git a/x-pack/plugins/beats_management/public/components/table/table.tsx b/x-pack/plugins/beats_management/public/components/table/table.tsx index b1d95391c367d..d8390a16c8e4a 100644 --- a/x-pack/plugins/beats_management/public/components/table/table.tsx +++ b/x-pack/plugins/beats_management/public/components/table/table.tsx @@ -19,7 +19,7 @@ interface TableProps { assignmentOptions: any[] | null; assignmentTitle: string | null; items: any[]; - renderAssignmentOptions?: (item: any) => any; + renderAssignmentOptions?: (item: any, key: string) => any; showAssignmentOptions: boolean; type: TableType; actionHandler(action: string, payload?: any): void; diff --git a/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx b/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx index 721da6ed7a406..b745be8439760 100644 --- a/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx +++ b/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx @@ -71,10 +71,8 @@ export const BeatsTableType: TableType = { render: (value: string, beat: CMPopulatedBeat) => ( {(sortBy(beat.full_tags, 'id') || []).map(tag => ( - - - {tag.id} - + + {tag.id} ))} diff --git a/x-pack/plugins/beats_management/public/components/tag/tag_assignment.tsx b/x-pack/plugins/beats_management/public/components/tag/tag_assignment.tsx index ed5bb706b1092..e0822a6badd49 100644 --- a/x-pack/plugins/beats_management/public/components/tag/tag_assignment.tsx +++ b/x-pack/plugins/beats_management/public/components/tag/tag_assignment.tsx @@ -6,12 +6,11 @@ import { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; import React from 'react'; -import { CMPopulatedBeat } from '../../../common/domain_types'; -import { ClientSideBeatTag } from '../../lib/lib'; +import { BeatTag, CMPopulatedBeat } from '../../../common/domain_types'; interface TagAssignmentProps { selectedBeats: CMPopulatedBeat[]; - tag: ClientSideBeatTag; + tag: BeatTag; assignTagsToBeats(selectedBeats: any, tag: any): void; removeTagsFromBeats(selectedBeats: any, tag: any): void; } @@ -43,13 +42,13 @@ export class TagAssignment extends React.PureComponent + {this.state.isFetchingTags && ( )} - + ( + private renderTagAssignment = (tag: BeatTag, key: string) => ( {`${actionName} tag "${tag}" ${preposition} ${beatMessage}.`}

, + title: `Tag ${actionName}`, }), }); }; From 27a14859647738a2e6c9c68cdd39ca907046e365 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Mon, 10 Sep 2018 10:06:34 -0400 Subject: [PATCH 40/94] [Beats CM] Add check for BeatsPage component to avoid setState when unmounted (#22836) * Add check if component is unmounted when loading Beats List page. * Move call to loadBeats() to componentDidMount lifecycle function. --- .../public/pages/main/beats.tsx | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/beats_management/public/pages/main/beats.tsx b/x-pack/plugins/beats_management/public/pages/main/beats.tsx index ad51bb1ceed69..52d9091048a8e 100644 --- a/x-pack/plugins/beats_management/public/pages/main/beats.tsx +++ b/x-pack/plugins/beats_management/public/pages/main/beats.tsx @@ -29,6 +29,7 @@ interface BeatsPageState { export class BeatsPage extends React.PureComponent { public static ActionArea = BeatsActionArea; + private mounted: boolean = false; constructor(props: BeatsPageProps) { super(props); @@ -38,9 +39,14 @@ export class BeatsPage extends React.PureComponent { const tags = await this.props.libs.tags.getAll(); - this.setState({ tags, }); From 3047e08bce034909791c92c729b92ae0524adad5 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Mon, 17 Sep 2018 09:51:56 -0400 Subject: [PATCH 41/94] Update field name in table type config. (#23228) --- .../public/components/table/table_type_configs.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx b/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx index b745be8439760..514e6d30d61a3 100644 --- a/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx +++ b/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx @@ -187,7 +187,7 @@ export const BeatDetailTagsTable: TableType = { }, { align: 'right', - field: 'configurations', + field: 'configuration_blocks', name: 'Configurations', render: (configurations: ConfigurationBlock[]) => {configurations.length}, sortable: true, From 1c8ccdaddeb0c5ac20729d0bd52e61928de18ab5 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Mon, 17 Sep 2018 18:24:51 -0400 Subject: [PATCH 42/94] Utilize TagBadge functional component to standardize display of tag names. (#23253) --- .../common/constants/table.ts | 1 + .../components/table/table_type_configs.tsx | 24 +++++------- .../public/components/tag/index.ts | 1 + .../public/components/tag/tag_assignment.tsx | 15 +++---- .../public/components/tag/tag_badge.tsx | 39 +++++++++++++++++++ .../public/components/tag/tag_edit.tsx | 6 ++- .../public/pages/beat/detail.tsx | 8 +++- 7 files changed, 68 insertions(+), 26 deletions(-) create mode 100644 x-pack/plugins/beats_management/public/components/tag/tag_badge.tsx diff --git a/x-pack/plugins/beats_management/common/constants/table.ts b/x-pack/plugins/beats_management/common/constants/table.ts index e5c2f6a029d67..119b4f3da4596 100644 --- a/x-pack/plugins/beats_management/common/constants/table.ts +++ b/x-pack/plugins/beats_management/common/constants/table.ts @@ -8,4 +8,5 @@ export const TABLE_CONFIG = { INITIAL_ROW_SIZE: 5, PAGE_SIZE_OPTIONS: [3, 5, 10, 20], TRUNCATE_TAG_LENGTH: 33, + TRUNCATE_TAG_LENGTH_SMALL: 20, }; diff --git a/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx b/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx index 514e6d30d61a3..1af5a6830ba23 100644 --- a/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx +++ b/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx @@ -9,10 +9,9 @@ import { EuiBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { flatten, sortBy, uniq } from 'lodash'; import moment from 'moment'; import React from 'react'; - -import { TABLE_CONFIG } from '../../../common/constants'; import { BeatTag, CMPopulatedBeat, ConfigurationBlock } from '../../../common/domain_types'; import { ConnectedLink } from '../connected_link'; +import { TagBadge } from '../tag'; export interface ColumnDefinition { align?: string; @@ -72,7 +71,9 @@ export const BeatsTableType: TableType = { {(sortBy(beat.full_tags, 'id') || []).map(tag => ( - {tag.id} + + + ))} @@ -123,17 +124,6 @@ export const BeatsTableType: TableType = { }), }; -const TagBadge = (props: { tag: { color?: string; id: string } }) => { - const { tag } = props; - return ( - - {tag.id.length > TABLE_CONFIG.TRUNCATE_TAG_LENGTH - ? `${tag.id.substring(0, TABLE_CONFIG.TRUNCATE_TAG_LENGTH)}...` - : tag.id} - - ); -}; - export const TagsTableType: TableType = { columnDefinitions: [ { @@ -181,7 +171,11 @@ export const BeatDetailTagsTable: TableType = { { field: 'id', name: 'Tag name', - render: (id: string, tag: BeatTag) => , + render: (id: string, tag: BeatTag) => ( + + + + ), sortable: true, width: '55%', }, diff --git a/x-pack/plugins/beats_management/public/components/tag/index.ts b/x-pack/plugins/beats_management/public/components/tag/index.ts index 4bbc9767f39ae..dfa61ec5c3a0a 100644 --- a/x-pack/plugins/beats_management/public/components/tag/index.ts +++ b/x-pack/plugins/beats_management/public/components/tag/index.ts @@ -5,4 +5,5 @@ */ export { TagAssignment } from './tag_assignment'; +export { TagBadge } from './tag_badge'; export { TagEdit } from './tag_edit'; diff --git a/x-pack/plugins/beats_management/public/components/tag/tag_assignment.tsx b/x-pack/plugins/beats_management/public/components/tag/tag_assignment.tsx index e0822a6badd49..61509eacc95e9 100644 --- a/x-pack/plugins/beats_management/public/components/tag/tag_assignment.tsx +++ b/x-pack/plugins/beats_management/public/components/tag/tag_assignment.tsx @@ -4,9 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; import React from 'react'; +import { TABLE_CONFIG } from '../../../common/constants'; import { BeatTag, CMPopulatedBeat } from '../../../common/domain_types'; +import { TagBadge } from './tag_badge'; interface TagAssignmentProps { selectedBeats: CMPopulatedBeat[]; @@ -34,7 +36,7 @@ export class TagAssignment extends React.PureComponent @@ -49,9 +51,9 @@ export class TagAssignment extends React.PureComponent )} - { this.setState({ isFetchingTags: true }); hasMatches @@ -60,9 +62,8 @@ export class TagAssignment extends React.PureComponent - {id} - + tag={tag} + />
); diff --git a/x-pack/plugins/beats_management/public/components/tag/tag_badge.tsx b/x-pack/plugins/beats_management/public/components/tag/tag_badge.tsx new file mode 100644 index 0000000000000..f0cca09b0181c --- /dev/null +++ b/x-pack/plugins/beats_management/public/components/tag/tag_badge.tsx @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiBadge } from '@elastic/eui'; +import React from 'react'; +import { TABLE_CONFIG } from '../../../common/constants'; + +interface TagBadgeProps { + iconType?: any; + onClick?: () => void; + onClickAriaLabel?: string; + maxIdRenderSize?: number; + tag: { color?: string; id: string }; +} + +export const TagBadge = (props: TagBadgeProps) => { + const { + iconType, + onClick, + onClickAriaLabel, + tag: { color, id }, + } = props; + + const maxIdRenderSize = props.maxIdRenderSize || TABLE_CONFIG.TRUNCATE_TAG_LENGTH; + const idToRender = id.length > maxIdRenderSize ? `${id.substring(0, maxIdRenderSize)}...` : id; + return ( + + {idToRender} + + ); +}; diff --git a/x-pack/plugins/beats_management/public/components/tag/tag_edit.tsx b/x-pack/plugins/beats_management/public/components/tag/tag_edit.tsx index 9364f7e21313f..e682e5b34f7ee 100644 --- a/x-pack/plugins/beats_management/public/components/tag/tag_edit.tsx +++ b/x-pack/plugins/beats_management/public/components/tag/tag_edit.tsx @@ -30,6 +30,7 @@ import { ConfigList } from '../config_list'; import { Table } from '../table'; import { BeatsTableType } from '../table'; import { ConfigView } from './config_view'; +import { TagBadge } from './tag_badge'; interface TagEditProps { mode: 'edit' | 'create'; @@ -71,9 +72,10 @@ export class TagEdit extends React.PureComponent {

- + + {/* {tag.id ? tag.id : 'Tag name'} - + */}
diff --git a/x-pack/plugins/beats_management/public/pages/beat/detail.tsx b/x-pack/plugins/beats_management/public/pages/beat/detail.tsx index 72436798741e4..ee85c12c0e218 100644 --- a/x-pack/plugins/beats_management/public/pages/beat/detail.tsx +++ b/x-pack/plugins/beats_management/public/pages/beat/detail.tsx @@ -5,7 +5,6 @@ */ import { - EuiBadge, EuiFlexGroup, EuiFlexItem, // @ts-ignore EuiInMemoryTable typings not yet available @@ -16,8 +15,10 @@ import { } from '@elastic/eui'; import { flatten } from 'lodash'; import React from 'react'; +import { TABLE_CONFIG } from '../../../common/constants'; import { BeatTag, CMPopulatedBeat } from '../../../common/domain_types'; import { ConnectedLink } from '../../components/connected_link'; +import { TagBadge } from '../../components/tag'; interface BeatDetailPageProps { beat: CMPopulatedBeat | undefined; @@ -63,7 +64,10 @@ export const BeatDetailPage = (props: BeatDetailPageProps) => { name: 'Tag', render: (id: string, block: any) => ( - {id} + ), sortable: true, From e53366aeeb8bce8c569e165517acd3e5fd901fb3 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Thu, 20 Sep 2018 12:39:51 -0400 Subject: [PATCH 43/94] [Beats CM] Add beat name to update endpoint (#23291) * Update beat index template and update endpoints to expect name type. Add test file for update. * Update enroll script and endpoint to create a 'name' field for beats. * Add name field to CMBeat domain type. * Update functional tests to include name field. * Fix broken tag assignment functional test. --- .../beats_management/common/domain_types.ts | 1 + .../components/table/table_type_configs.tsx | 6 +- .../beats_management/scripts/enroll.js | 1 + .../domains/__tests__/beats/update.test.ts | 116 ++++++++++++++++++ .../server/rest_api/beats/enroll.ts | 1 + .../server/rest_api/beats/update.ts | 1 + .../utils/index_templates/beats_template.json | 3 + .../apis/beats/assign_tags_to_beats.js | 2 +- .../api_integration/apis/beats/enroll_beat.js | 1 + .../api_integration/apis/beats/update_beat.js | 2 + .../es_archives/beats/list/data.json | 4 + 11 files changed, 135 insertions(+), 3 deletions(-) create mode 100644 x-pack/plugins/beats_management/server/lib/domains/__tests__/beats/update.test.ts diff --git a/x-pack/plugins/beats_management/common/domain_types.ts b/x-pack/plugins/beats_management/common/domain_types.ts index 735249922116b..e65a7192780a7 100644 --- a/x-pack/plugins/beats_management/common/domain_types.ts +++ b/x-pack/plugins/beats_management/common/domain_types.ts @@ -72,6 +72,7 @@ export interface CMBeat { tags?: string[]; central_configuration_yml?: string; metadata?: {}; + name?: string; } export interface CMPopulatedBeat extends CMBeat { diff --git a/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx b/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx index 1af5a6830ba23..2e12589828dc4 100644 --- a/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx +++ b/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx @@ -54,9 +54,11 @@ export interface TableType { export const BeatsTableType: TableType = { columnDefinitions: [ { - field: 'id', + field: 'name', name: 'Beat name', - render: (id: string) => {id}, + render: (name: string, beat: CMPopulatedBeat) => ( + {name} + ), sortable: true, }, { diff --git a/x-pack/plugins/beats_management/scripts/enroll.js b/x-pack/plugins/beats_management/scripts/enroll.js index 996ece4136605..e1639be281da4 100644 --- a/x-pack/plugins/beats_management/scripts/enroll.js +++ b/x-pack/plugins/beats_management/scripts/enroll.js @@ -22,6 +22,7 @@ const enroll = async token => { body: JSON.stringify({ type: 'filebeat', host_name: `${chance.word()}.bar.com`, + name: chance.word(), version: '6.3.0', }), }, diff --git a/x-pack/plugins/beats_management/server/lib/domains/__tests__/beats/update.test.ts b/x-pack/plugins/beats_management/server/lib/domains/__tests__/beats/update.test.ts new file mode 100644 index 0000000000000..a068a1a50b6a8 --- /dev/null +++ b/x-pack/plugins/beats_management/server/lib/domains/__tests__/beats/update.test.ts @@ -0,0 +1,116 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Chance from 'chance'; +import { BeatTag, CMBeat } from '../../../../../common/domain_types'; +import { MemoryBeatsAdapter } from '../../../adapters/beats/memory_beats_adapter'; +import { TestingBackendFrameworkAdapter } from '../../../adapters/framework/testing_framework_adapter'; +import { MemoryTagsAdapter } from '../../../adapters/tags/memory_tags_adapter'; +import { TokenEnrollmentData } from '../../../adapters/tokens/adapter_types'; +import { MemoryTokensAdapter } from '../../../adapters/tokens/memory_tokens_adapter'; +import { CMBeatsDomain } from '../../beats'; +import { CMTagsDomain } from '../../tags'; +import { CMTokensDomain } from '../../tokens'; + +const seed = Date.now(); +const chance = new Chance(seed); + +const settings = { + encryptionKey: `it's_a_secret`, + enrollmentTokensTtlInSeconds: 10 * 60, // 10 minutes +}; + +describe('Beats Domain lib', () => { + describe('update_beat', () => { + let beatsLib: CMBeatsDomain; + let tokensLib: CMTokensDomain; + let token: TokenEnrollmentData; + let beatsDB: CMBeat[] = []; + let tagsDB: BeatTag[] = []; + let tokensDB: TokenEnrollmentData[]; + let beatId: string; + let beat: Partial; + + const getBeatsLib = async () => { + const framework = new TestingBackendFrameworkAdapter(settings); + + tokensLib = new CMTokensDomain(new MemoryTokensAdapter(tokensDB), { framework }); + const tagsLib = new CMTagsDomain(new MemoryTagsAdapter(tagsDB)); + + beatsLib = new CMBeatsDomain(new MemoryBeatsAdapter(beatsDB), { + framework, + tags: tagsLib, + tokens: tokensLib, + }); + + await tokensLib.createEnrollmentTokens(framework.internalUser, 1); + token = tokensDB[0]; + }; + + beforeEach(async () => { + beatId = chance.word(); + beat = { + host_name: 'foo.bar.com', + type: 'filebeat', + version: '6.4.0', + }; + beatsDB = []; + tagsDB = []; + tokensDB = []; + + getBeatsLib(); + }); + + it('should return a not-found message if beat does not exist', async () => { + const tokenString = token.token || ''; + const result = await beatsLib.update(tokenString, beatId, beat); + + expect(result).toBe('beat-not-found'); + }); + + it('should return an invalid message if token validation fails', async () => { + const beatToFind: CMBeat = { + id: beatId, + enrollment_token: '', + active: true, + access_token: token.token || '', + type: 'filebeat', + host_ip: 'localhost', + host_name: 'foo.bar.com', + }; + beatsDB = [beatToFind]; + + getBeatsLib(); + + const result = await beatsLib.update('something_invalid', beatId, beat); + + expect(result).toBe('invalid-access-token'); + }); + + it('should update the beat when a valid token is provided', async () => { + const beatToFind: CMBeat = { + id: beatId, + enrollment_token: '', + active: true, + access_token: token.token || '', + type: 'metricbeat', + host_ip: 'localhost', + host_name: 'bar.foo.com', + version: '6.3.5', + }; + beatsDB = [beatToFind]; + getBeatsLib(); + // @ts-ignore + await beatsLib.update(token, beatId, beat); + expect(beatsDB).toHaveLength(1); + const updatedBeat = beatsDB[0]; + expect(updatedBeat.id).toBe(beatId); + expect(updatedBeat.host_name).toBe('foo.bar.com'); + expect(updatedBeat.version).toBe('6.4.0'); + expect(updatedBeat.type).toBe('filebeat'); + }); + }); +}); diff --git a/x-pack/plugins/beats_management/server/rest_api/beats/enroll.ts b/x-pack/plugins/beats_management/server/rest_api/beats/enroll.ts index 8e7d2aee956ee..03b46603c4d6c 100644 --- a/x-pack/plugins/beats_management/server/rest_api/beats/enroll.ts +++ b/x-pack/plugins/beats_management/server/rest_api/beats/enroll.ts @@ -25,6 +25,7 @@ export const createBeatEnrollmentRoute = (libs: CMServerLibs) => ({ }), payload: Joi.object({ host_name: Joi.string().required(), + name: Joi.string().required(), type: Joi.string().required(), version: Joi.string().required(), }).required(), diff --git a/x-pack/plugins/beats_management/server/rest_api/beats/update.ts b/x-pack/plugins/beats_management/server/rest_api/beats/update.ts index e33c5b5d11e53..6e64c0bea502a 100644 --- a/x-pack/plugins/beats_management/server/rest_api/beats/update.ts +++ b/x-pack/plugins/beats_management/server/rest_api/beats/update.ts @@ -33,6 +33,7 @@ export const createBeatUpdateRoute = (libs: CMServerLibs) => ({ host_name: Joi.string(), local_configuration_yml: Joi.string(), metadata: Joi.object(), + name: Joi.string(), type: Joi.string(), version: Joi.string(), }), diff --git a/x-pack/plugins/beats_management/server/utils/index_templates/beats_template.json b/x-pack/plugins/beats_management/server/utils/index_templates/beats_template.json index f1a6b98b9f29a..8b5806b3c24cd 100644 --- a/x-pack/plugins/beats_management/server/utils/index_templates/beats_template.json +++ b/x-pack/plugins/beats_management/server/utils/index_templates/beats_template.json @@ -91,6 +91,9 @@ "metadata": { "dynamic": "true", "type": "object" + }, + "name": { + "type": "keyword" } } } diff --git a/x-pack/test/api_integration/apis/beats/assign_tags_to_beats.js b/x-pack/test/api_integration/apis/beats/assign_tags_to_beats.js index 621a6187693bd..4f003df0a654f 100644 --- a/x-pack/test/api_integration/apis/beats/assign_tags_to_beats.js +++ b/x-pack/test/api_integration/apis/beats/assign_tags_to_beats.js @@ -210,7 +210,7 @@ export default function ({ getService }) { .post('/api/beats/agents_tags/assignments') .set('kbn-xsrf', 'xxx') .send({ - assignments: [{ beatID: 'bar', tag: nonExistentTag }], + assignments: [{ beatId: 'bar', tag: nonExistentTag }], }) .expect(200); diff --git a/x-pack/test/api_integration/apis/beats/enroll_beat.js b/x-pack/test/api_integration/apis/beats/enroll_beat.js index 61c9ec79eb74b..809734b741906 100644 --- a/x-pack/test/api_integration/apis/beats/enroll_beat.js +++ b/x-pack/test/api_integration/apis/beats/enroll_beat.js @@ -33,6 +33,7 @@ export default function ({ getService }) { beat = { type: 'filebeat', host_name: 'foo.bar.com', + name: chance.word(), version, }; diff --git a/x-pack/test/api_integration/apis/beats/update_beat.js b/x-pack/test/api_integration/apis/beats/update_beat.js index 697e644cb2221..163ddd931a625 100644 --- a/x-pack/test/api_integration/apis/beats/update_beat.js +++ b/x-pack/test/api_integration/apis/beats/update_beat.js @@ -35,6 +35,7 @@ export default function ({ getService }) { beat = { type: `${chance.word()}beat`, host_name: `www.${chance.word()}.net`, + name: chance.word(), version, ephemeral_id: chance.word(), }; @@ -77,6 +78,7 @@ export default function ({ getService }) { expect(beatInEs._source.beat.host_name).to.be(beat.host_name); expect(beatInEs._source.beat.version).to.be(beat.version); expect(beatInEs._source.beat.ephemeral_id).to.be(beat.ephemeral_id); + expect(beatInEs._source.beat.name).to.be(beat.name); }); it('should return an error for an invalid access token', async () => { diff --git a/x-pack/test/functional/es_archives/beats/list/data.json b/x-pack/test/functional/es_archives/beats/list/data.json index a9c28d8e132d0..f263eff1a5bd4 100644 --- a/x-pack/test/functional/es_archives/beats/list/data.json +++ b/x-pack/test/functional/es_archives/beats/list/data.json @@ -12,6 +12,7 @@ "host_ip": "1.2.3.4", "host_name": "foo.bar.com", "id": "qux", + "name": "qux_filebeat", "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjcmVhdGVkIjoiMjAxOC0wNi0zMFQwMzo0MjoxNS4yMzBaIiwiaWF0IjoxNTMwMzMwMTM1fQ.SSsX2Byyo1B1bGxV8C3G4QldhE5iH87EY_1r21-bwbI" } } @@ -32,6 +33,7 @@ "host_ip": "22.33.11.44", "host_name": "baz.bar.com", "id": "baz", + "name": "baz_metricbeat", "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjcmVhdGVkIjoiMjAxOC0wNi0zMFQwMzo0MjoxNS4yMzBaIiwiaWF0IjoxNTMwMzMwMTM1fQ.SSsX2Byyo1B1bGxV8C3G4QldhE5iH87EY_1r21-bwbI" } } @@ -52,6 +54,7 @@ "host_ip": "1.2.3.4", "host_name": "foo.bar.com", "id": "foo", + "name": "foo_metricbeat", "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjcmVhdGVkIjoiMjAxOC0wNi0zMFQwMzo0MjoxNS4yMzBaIiwiaWF0IjoxNTMwMzMwMTM1fQ.SSsX2Byyo1B1bGxV8C3G4QldhE5iH87EY_1r21-bwbI", "verified_on": "2018-05-15T16:25:38.924Z", "tags": [ @@ -77,6 +80,7 @@ "host_ip": "11.22.33.44", "host_name": "foo.com", "id": "bar", + "name": "bar_filebeat", "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjcmVhdGVkIjoiMjAxOC0wNi0zMFQwMzo0MjoxNS4yMzBaIiwiaWF0IjoxNTMwMzMwMTM1fQ.SSsX2Byyo1B1bGxV8C3G4QldhE5iH87EY_1r21-bwbI" } } From 689a740d7e085ae4c397dfec4dbaf0c0f852c440 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Fri, 21 Sep 2018 08:58:42 -0400 Subject: [PATCH 44/94] Edit beats list table config to display most recently-updated tag time. (#23337) --- .../public/components/table/table_type_configs.tsx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx b/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx index 2e12589828dc4..0c70f2ac67f85 100644 --- a/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx +++ b/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx @@ -4,9 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -// @ts-ignore -import { EuiBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { flatten, sortBy, uniq } from 'lodash'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { first, flatten, sortBy, sortByOrder, uniq } from 'lodash'; import moment from 'moment'; import React from 'react'; import { BeatTag, CMPopulatedBeat, ConfigurationBlock } from '../../../common/domain_types'; @@ -90,9 +89,14 @@ export const BeatsTableType: TableType = { }, { // TODO: update to use actual metadata field - field: 'last_updated', + field: 'full_tags', name: 'Last config update', - render: (value: Date) =>
{moment(value).fromNow()}
, + render: (tags: BeatTag[]) => + tags.length ? ( + + {moment(first(sortByOrder(tags, ['last_updated'], ['desc'])).last_updated).fromNow()} + + ) : null, sortable: true, }, ], From 43c09a4fdf2702e316ad13f472cfec769ec72930 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Fri, 21 Sep 2018 08:59:09 -0400 Subject: [PATCH 45/94] Update beat detail view info. (#23369) * Update beat detail view info. * Add period to end of update field. --- .../public/pages/beat/action_section.tsx | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/beats_management/public/pages/beat/action_section.tsx b/x-pack/plugins/beats_management/public/pages/beat/action_section.tsx index b310706022bfe..74507557df0a4 100644 --- a/x-pack/plugins/beats_management/public/pages/beat/action_section.tsx +++ b/x-pack/plugins/beats_management/public/pages/beat/action_section.tsx @@ -5,6 +5,7 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; +import { first, sortByOrder } from 'lodash'; import moment from 'moment'; import React from 'react'; import { CMPopulatedBeat } from '../../../common/domain_types'; @@ -23,17 +24,26 @@ export const BeatDetailsActionSection = ({ beat }: BeatDetailsActionSectionProps {beat.version}.
+ {/* TODO: We need a populated field before we can run this code - {/* TODO: What field is used to populate this value? */} Uptime: 12min. - - - - Last Config Update: {moment(beat.last_updated).fromNow()} - - + */} + {beat.full_tags && + beat.full_tags.length > 0 && ( + + + Last Config Update:{' '} + + {moment( + first(sortByOrder(beat.full_tags, 'last_updated')).last_updated + ).fromNow()} + + . + + + )} ) : (
Beat not found
From c1cd328d1ce0e0be11e38a67f87da099e2c92f36 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Fri, 21 Sep 2018 10:55:11 -0400 Subject: [PATCH 46/94] [Beat CM] Show only tags associated with selected beat (#23398) * Reenable output config. * Make beat detail tag page only show tags associated with that tag. --- .../public/pages/beat/index.tsx | 4 ++- .../public/pages/beat/tags.tsx | 31 ++++++++++++------- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/x-pack/plugins/beats_management/public/pages/beat/index.tsx b/x-pack/plugins/beats_management/public/pages/beat/index.tsx index 7f9510655aabe..3819fe4661318 100644 --- a/x-pack/plugins/beats_management/public/pages/beat/index.tsx +++ b/x-pack/plugins/beats_management/public/pages/beat/index.tsx @@ -33,6 +33,7 @@ interface BeatDetailsPageProps { interface BeatDetailsPageState { beat: CMPopulatedBeat | undefined; + beatId: string; isLoading: boolean; } @@ -45,6 +46,7 @@ export class BeatDetailsPage extends React.PureComponent< this.state = { beat: undefined, + beatId: this.props.match.params.beatId, isLoading: true, }; this.loadBeat(); @@ -106,7 +108,7 @@ export class BeatDetailsPage extends React.PureComponent< path="/beat/:beatId/tags" render={(props: any) => ( this.loadBeat()} {...props} diff --git a/x-pack/plugins/beats_management/public/pages/beat/tags.tsx b/x-pack/plugins/beats_management/public/pages/beat/tags.tsx index 8c3ef5e547e7e..3f5bec3c27a63 100644 --- a/x-pack/plugins/beats_management/public/pages/beat/tags.tsx +++ b/x-pack/plugins/beats_management/public/pages/beat/tags.tsx @@ -6,20 +6,21 @@ import { EuiGlobalToastList } from '@elastic/eui'; import { get } from 'lodash'; +import moment from 'moment'; import React from 'react'; -import { BeatTag, CMPopulatedBeat } from '../../../common/domain_types'; +import { CMPopulatedBeat } from '../../../common/domain_types'; import { BeatDetailTagsTable, Table } from '../../components/table'; import { FrontendLibs } from '../../lib/lib'; interface BeatTagsPageProps { - beat: CMPopulatedBeat; + beatId: string; libs: FrontendLibs; refreshBeat(): void; } interface BeatTagsPageState { + beat: CMPopulatedBeat | null; notifications: any[]; - tags: BeatTag[]; } export class BeatTagsPage extends React.PureComponent { @@ -28,21 +29,24 @@ export class BeatTagsPage extends React.PureComponent
{ + const { beat } = this.state; const actionName = action === 'remove' ? 'Removed' : 'Added'; const preposition = action === 'remove' ? 'from' : 'to'; this.setState({ notifications: this.state.notifications.concat({ title: `Tags ${actionName} ${preposition} Beat`, color: 'success', + id: moment.now(), text: (

{`${actionName} ${numRemoved} of ${totalTags} tags ${preposition} ${ - this.props.beat ? this.props.beat.id : 'beat' + beat ? beat.name || beat.id : 'beat' }`}

), }), @@ -93,11 +99,11 @@ export class BeatTagsPage extends React.PureComponent { - const { beat } = this.props; + const { beat } = this.state; if (!beat) { throw new Error('Beat cannot be undefined'); @@ -118,7 +124,7 @@ export class BeatTagsPage extends React.PureComponent { - const { beat } = this.props; + const { beat } = this.state; if (!beat) { throw new Error('Beat cannot be undefined'); @@ -138,9 +144,10 @@ export class BeatTagsPage extends React.PureComponent { + private getBeat = async () => { try { - this.setState({ tags: await this.props.libs.tags.getAll() }); + const beat = await this.props.libs.beats.get(this.props.beatId); + this.setState({ beat }); } catch (e) { throw new Error(e); } From 02bedce93ca87cf4a1152d3a472373a74e3348d0 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Fri, 21 Sep 2018 16:45:20 -0400 Subject: [PATCH 47/94] Display beat name instead of ID on details screen. (#23410) --- x-pack/plugins/beats_management/public/pages/beat/index.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/beats_management/public/pages/beat/index.tsx b/x-pack/plugins/beats_management/public/pages/beat/index.tsx index 3819fe4661318..14ae90609b648 100644 --- a/x-pack/plugins/beats_management/public/pages/beat/index.tsx +++ b/x-pack/plugins/beats_management/public/pages/beat/index.tsx @@ -59,11 +59,13 @@ export class BeatDetailsPage extends React.PureComponent< public render() { const { beat } = this.state; let id; + let name; if (beat) { id = beat.id; + name = beat.name; } - const title = this.state.isLoading ? 'Loading' : `Beat: ${id}`; + const title = this.state.isLoading ? 'Loading' : `Beat: ${name || id}`; const tabs = [ { id: `/beat/${id}`, From 0dc44ba76dda54efca6e49e3311e2a83ad8f5f4a Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Fri, 21 Sep 2018 16:45:39 -0400 Subject: [PATCH 48/94] [Beat CM] Display config name instead of beat type in beat detail view (#23411) * Update config table in beat detail view to show config type instead of beat type. * Modify Beat Detail view to display human-friendly names for config block types. --- .../public/pages/beat/detail.tsx | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/beats_management/public/pages/beat/detail.tsx b/x-pack/plugins/beats_management/public/pages/beat/detail.tsx index ee85c12c0e218..915cbb9d1f91a 100644 --- a/x-pack/plugins/beats_management/public/pages/beat/detail.tsx +++ b/x-pack/plugins/beats_management/public/pages/beat/detail.tsx @@ -13,12 +13,13 @@ import { EuiText, EuiTitle, } from '@elastic/eui'; -import { flatten } from 'lodash'; +import { flatten, get } from 'lodash'; import React from 'react'; import { TABLE_CONFIG } from '../../../common/constants'; import { BeatTag, CMPopulatedBeat } from '../../../common/domain_types'; import { ConnectedLink } from '../../components/connected_link'; import { TagBadge } from '../../components/tag'; +import { supportedConfigs } from '../../config_schemas'; interface BeatDetailPageProps { beat: CMPopulatedBeat | undefined; @@ -32,22 +33,29 @@ export const BeatDetailPage = (props: BeatDetailPageProps) => { const configurationBlocks = flatten( beat.full_tags.map((tag: BeatTag) => { return tag.configuration_blocks.map(configuration => ({ - ...configuration, // @ts-ignore one of the types on ConfigurationBlock doesn't define a "module" property module: configuration.configs[0].module || null, tagId: tag.id, tagColor: tag.color, ...beat, + ...configuration, + displayValue: get( + supportedConfigs.find(config => config.value === configuration.type), + 'text', + null + ), })); }) ); const columns = [ { - field: 'type', + field: 'displayValue', name: 'Type', sortable: true, - render: (type: string) => {type}, + render: (value: string | null, configuration: any) => ( + {value || configuration.type} + ), }, { field: 'module', From 27e96f057ce92179e6dd4eb3da92c22b8633ac5f Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Mon, 24 Sep 2018 13:27:05 -0400 Subject: [PATCH 49/94] [Beats CM] Add password input and re-enable output config schema (#23417) * wip defining controls * Complete adding formsy password field. * Re-enable output config schema definition. * Simplify import/export for formsy components. --- .../public/components/inputs/index.ts | 11 ++ .../components/inputs/password_input.tsx | 110 +++++++++++++++++ .../tag/config_view/config_form.tsx | 25 +++- .../beats_management/public/config_schemas.ts | 116 +++++++++--------- .../beats_management/public/lib/lib.ts | 2 +- 5 files changed, 201 insertions(+), 63 deletions(-) create mode 100644 x-pack/plugins/beats_management/public/components/inputs/index.ts create mode 100644 x-pack/plugins/beats_management/public/components/inputs/password_input.tsx diff --git a/x-pack/plugins/beats_management/public/components/inputs/index.ts b/x-pack/plugins/beats_management/public/components/inputs/index.ts new file mode 100644 index 0000000000000..50a5674f6fd7c --- /dev/null +++ b/x-pack/plugins/beats_management/public/components/inputs/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { FormsyEuiCodeEditor } from './code_editor'; +export { FormsyEuiFieldText } from './input'; +export { FormsyEuiPasswordText } from './password_input'; +export { FormsyEuiMultiFieldText } from './multi_input'; +export { FormsyEuiSelect } from './select'; diff --git a/x-pack/plugins/beats_management/public/components/inputs/password_input.tsx b/x-pack/plugins/beats_management/public/components/inputs/password_input.tsx new file mode 100644 index 0000000000000..30318f76bb90f --- /dev/null +++ b/x-pack/plugins/beats_management/public/components/inputs/password_input.tsx @@ -0,0 +1,110 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +// @ts-ignore currently no definition for EuiFieldPassword +import { CommonProps, EuiFieldPassword, EuiFieldPasswordProps, EuiFormRow } from '@elastic/eui'; +import { FormsyInputProps, withFormsy } from 'formsy-react'; +import React, { Component, InputHTMLAttributes } from 'react'; + +interface ComponentProps extends FormsyInputProps, CommonProps, EuiFieldPasswordProps { + instantValidation?: boolean; + label: string; + errorText: string; + fullWidth: boolean; + helpText: React.ReactElement; + compressed: boolean; + onChange?(e: React.ChangeEvent, value: any): void; + onBlur?(e: React.ChangeEvent, value: any): void; +} + +interface ComponentState { + allowError: boolean; +} + +class FieldPassword extends Component< + InputHTMLAttributes & ComponentProps, + ComponentState +> { + constructor(props: any) { + super(props); + + this.state = { + allowError: false, + }; + } + + public componentDidMount() { + const { defaultValue, setValue } = this.props; + if (defaultValue) { + setValue(defaultValue); + } + } + + public handleChange = (e: React.ChangeEvent) => { + const { value } = e.currentTarget; + this.props.setValue(value); + if (this.props.onChange) { + this.props.onChange(e, value); + } + if (this.props.instantValidation) { + this.showError(); + } + }; + + public handleBlur = (e: React.ChangeEvent) => { + this.showError(); + if (this.props.onBlur) { + this.props.onBlur(e, e.currentTarget.value); + } + }; + + public showError = () => this.setState({ allowError: true }); + + public render() { + const { + id, + required, + label, + getValue, + isValid, + isPristine, + getErrorMessage, + fullWidth, + className, + disabled, + helpText, + onBlur, + } = this.props; + + const { allowError } = this.state; + const error = !isPristine() && !isValid() && allowError; + + return ( + + + + ); + } +} + +export const FormsyEuiPasswordText = withFormsy(FieldPassword); diff --git a/x-pack/plugins/beats_management/public/components/tag/config_view/config_form.tsx b/x-pack/plugins/beats_management/public/components/tag/config_view/config_form.tsx index 5944fa62ed596..ed30a98e9ed97 100644 --- a/x-pack/plugins/beats_management/public/components/tag/config_view/config_form.tsx +++ b/x-pack/plugins/beats_management/public/components/tag/config_view/config_form.tsx @@ -10,10 +10,13 @@ import { get } from 'lodash'; import React from 'react'; import { ConfigurationBlock } from '../../../../common/domain_types'; import { YamlConfigSchema } from '../../../lib/lib'; -import { FormsyEuiCodeEditor } from '../../inputs/code_editor'; -import { FormsyEuiFieldText } from '../../inputs/input'; -import { FormsyEuiMultiFieldText } from '../../inputs/multi_input'; -import { FormsyEuiSelect } from '../../inputs/select'; +import { + FormsyEuiCodeEditor, + FormsyEuiFieldText, + FormsyEuiMultiFieldText, + FormsyEuiPasswordText, + FormsyEuiSelect, +} from '../../inputs'; addValidationRule('isHosts', (form: FormData, values: FieldValue | string[]) => { if (values && values.length > 0 && values instanceof Array) { @@ -131,6 +134,20 @@ export class ConfigForm extends React.Component { required={schema.required} /> ); + case 'password': + return ( + + ); case 'multi-input': return ( v.split('\n'), -// }, -// { -// id: 'username', -// ui: { -// label: 'Username', -// type: 'input', -// }, -// validations: 'isString', -// error: 'Unprocessable username', -// }, -// { -// id: 'password', -// ui: { -// label: 'Password', -// type: 'input', -// }, -// validations: 'isString', -// error: 'Unprocessable password', -// }, -// ]; +const outputConfig: YamlConfigSchema[] = [ + { + id: 'output', + ui: { + label: 'Output Type', + type: 'select', + }, + options: [ + { + value: 'elasticsearch', + text: 'Elasticsearch', + }, + { + value: 'logstash', + text: 'Logstash', + }, + { + value: 'kafka', + text: 'Kafka', + }, + { + value: 'console', + text: 'Console', + }, + ], + error: 'Please select an output type', + required: true, + }, + { + id: 'hosts', + ui: { + label: 'Hosts', + type: 'multi-input', + }, + validations: 'isHosts', + error: 'One file host per line', + parseValidResult: v => v.split('\n'), + }, + { + id: 'username', + ui: { + label: 'Username', + type: 'input', + }, + validations: 'isString', + error: 'Unprocessable username', + }, + { + id: 'password', + ui: { + label: 'Password', + type: 'input', + }, + validations: 'isString', + error: 'Unprocessable password', + }, +]; export const supportedConfigs = [ { text: 'Filebeat Input', value: 'filebeat.inputs', config: filebeatInputConfig }, { text: 'Filebeat Module', value: 'filebeat.modules', config: filebeatModuleConfig }, { text: 'Metricbeat Input', value: 'metricbeat.modules', config: metricbeatModuleConfig }, - // { text: 'Output', value: 'output', config: outputConfig }, + { text: 'Output', value: 'output', config: outputConfig }, ]; diff --git a/x-pack/plugins/beats_management/public/lib/lib.ts b/x-pack/plugins/beats_management/public/lib/lib.ts index 261357d5fce47..7c059aa2440e4 100644 --- a/x-pack/plugins/beats_management/public/lib/lib.ts +++ b/x-pack/plugins/beats_management/public/lib/lib.ts @@ -25,7 +25,7 @@ export interface YamlConfigSchema { id: string; ui: { label: string; - type: 'input' | 'multi-input' | 'select' | 'code'; + type: 'input' | 'multi-input' | 'select' | 'code' | 'password'; helpText?: string; }; options?: Array<{ value: string; text: string }>; From 6231db6cd9a8dff6f772ae18f6d7a31227ce69bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20P=C3=A9rez-Aradros=20Herce?= Date: Tue, 25 Sep 2018 18:46:41 +0200 Subject: [PATCH 50/94] Add full list of current Filebeat & Metricbeat module (#23258) This change also renames `Metricbeat input` to `Metricbeat module` --- .../beats_management/public/config_schemas.ts | 202 ++++++++++++++++-- 1 file changed, 187 insertions(+), 15 deletions(-) diff --git a/x-pack/plugins/beats_management/public/config_schemas.ts b/x-pack/plugins/beats_management/public/config_schemas.ts index eaaf862884eda..2fcb6a8c48d00 100644 --- a/x-pack/plugins/beats_management/public/config_schemas.ts +++ b/x-pack/plugins/beats_management/public/config_schemas.ts @@ -36,25 +36,73 @@ const filebeatModuleConfig: YamlConfigSchema[] = [ type: 'select', }, options: [ - { - value: 'system', - text: 'system', - }, { value: 'apache2', text: 'apache2', }, { - value: 'nginx', - text: 'nginx', + value: 'auditd', + text: 'auditd', + }, + { + value: 'elasticsearch', + text: 'elasticsearch', + }, + { + value: 'haproxy', + text: 'haproxy', + }, + { + value: 'icinga', + text: 'icinga', + }, + { + value: 'iis', + text: 'iis', + }, + { + value: 'kafka', + text: 'kafka', + }, + { + value: 'kibana', + text: 'kibana', + }, + { + value: 'logstash', + text: 'logstash', }, { value: 'mongodb', text: 'mongodb', }, { - value: 'elasticsearch', - text: 'elasticsearch', + value: 'mysql', + text: 'mysql', + }, + { + value: 'nginx', + text: 'nginx', + }, + { + value: 'osquery', + text: 'osquery', + }, + { + value: 'postgresql', + text: 'postgresql', + }, + { + value: 'redis', + text: 'redis', + }, + { + value: 'system', + text: 'system', + }, + { + value: 'traefik', + text: 'traefik', }, ], error: 'Please select a module', @@ -80,21 +128,145 @@ const metricbeatModuleConfig: YamlConfigSchema[] = [ }, options: [ { - value: 'system', - text: 'system', + value: 'aerospike', + text: 'aerospike', }, { - value: 'apache2', - text: 'apache2', + value: 'apache', + text: 'apache', }, { - value: 'nginx', - text: 'nginx', + value: 'ceph', + text: 'ceph', + }, + { + value: 'couchbase', + text: 'couchbase', + }, + { + value: 'docker', + text: 'docker', + }, + { + value: 'dropwizard', + text: 'dropwizard', + }, + { + value: 'elasticsearch', + text: 'elasticsearch', + }, + { + value: 'envoyproxy', + text: 'envoyproxy', + }, + { + value: 'etcd', + text: 'etcd', + }, + { + value: 'golang', + text: 'golang', + }, + { + value: 'graphite', + text: 'graphite', + }, + { + value: 'haproxy', + text: 'haproxy', + }, + { + value: 'http', + text: 'http', + }, + { + value: 'jolokia', + text: 'jolokia', + }, + { + value: 'kafka', + text: 'kafka', + }, + { + value: 'kibana', + text: 'kibana', + }, + { + value: 'kubernetes', + text: 'kubernetes', + }, + { + value: 'kvm', + text: 'kvm', + }, + { + value: 'logstash', + text: 'logstash', + }, + { + value: 'memcached', + text: 'memcached', }, { value: 'mongodb', text: 'mongodb', }, + { + value: 'munin', + text: 'munin', + }, + { + value: 'mysql', + text: 'mysql', + }, + { + value: 'nginx', + text: 'nginx', + }, + { + value: 'php_fpm', + text: 'php_fpm', + }, + { + value: 'postgresql', + text: 'postgresql', + }, + { + value: 'prometheus', + text: 'prometheus', + }, + { + value: 'rabbitmq', + text: 'rabbitmq', + }, + { + value: 'redis', + text: 'redis', + }, + { + value: 'system', + text: 'system', + }, + { + value: 'traefik', + text: 'traefik', + }, + { + value: 'uwsgi', + text: 'uwsgi', + }, + { + value: 'vsphere', + text: 'vsphere', + }, + { + value: 'windows', + text: 'windows', + }, + { + value: 'zookeeper', + text: 'zookeeper', + }, ], error: 'Please select a module', required: true, @@ -192,6 +364,6 @@ const outputConfig: YamlConfigSchema[] = [ export const supportedConfigs = [ { text: 'Filebeat Input', value: 'filebeat.inputs', config: filebeatInputConfig }, { text: 'Filebeat Module', value: 'filebeat.modules', config: filebeatModuleConfig }, - { text: 'Metricbeat Input', value: 'metricbeat.modules', config: metricbeatModuleConfig }, + { text: 'Metricbeat Module', value: 'metricbeat.modules', config: metricbeatModuleConfig }, { text: 'Output', value: 'output', config: outputConfig }, ]; From f93d7157ca98878ce991e81fb9a259111228cd00 Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Tue, 2 Oct 2018 19:33:57 -0400 Subject: [PATCH 51/94] Beats/ui tweaks (#23655) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add initial breadcrumbs * prevent errors * New routing in place for supporting URLState needed for Kuery bar * beats table kuery bar “working” (but not submitting) * pulling activity view from current phase’s spec * setup link now at correct URL * kuery bar * autocomplete bar now working --- .../plugins/beats_management/public/app.d.ts | 12 + .../components/autocomplete_field/index.tsx | 290 ++++++++++++++++++ .../autocomplete_field/suggestion_item.tsx | 123 ++++++++ .../public/components/connected_link.tsx | 4 +- .../public/components/layouts/header.tsx | 36 +++ .../public/components/table/controls.tsx | 25 ++ .../components/table/primary_options.tsx | 45 ++- .../public/components/table/table.tsx | 33 +- .../containers/with_kuery_autocompletion.tsx | 89 ++++++ .../public/containers/with_url_state.tsx | 92 ++++++ .../plugins/beats_management/public/index.tsx | 8 +- .../lib/{domains => }/__tests__/tags.test.ts | 6 +- .../lib/adapters/beats/adapter_types.ts | 2 +- .../lib/adapters/beats/rest_beats_adapter.ts | 4 +- .../adapters/elasticsearch/adapter_types.ts | 12 + .../public/lib/adapters/elasticsearch/rest.ts | 77 +++++ .../public/lib/adapters/elasticsearch/test.ts | 20 ++ .../lib/adapters/rest_api/adapter_types.ts | 3 +- .../rest_api/axios_rest_api_adapter.ts | 5 +- .../public/lib/{domains => }/beats.ts | 10 +- .../public/lib/compose/kibana.ts | 9 +- .../public/lib/compose/memory.ts | 8 +- .../public/lib/elasticsearch.ts | 69 +++++ .../beats_management/public/lib/lib.ts | 6 +- .../public/lib/{domains => }/tags.ts | 10 +- .../public/pages/beat/detail.tsx | 1 + .../public/pages/beat/index.tsx | 28 +- .../public/pages/main/beats.tsx | 48 ++- .../public/pages/main/beats_action_area.tsx | 208 +++++++------ .../public/pages/main/index.tsx | 44 ++- .../public/pages/main/tags.tsx | 37 ++- .../public/pages/tag/index.tsx | 12 +- .../beats_management/public/router.tsx | 48 ++- .../public/utils/typed_react.ts | 65 ++++ .../lib/adapters/beats/adapter_types.ts | 2 +- .../beats/elasticsearch_beats_adapter.ts | 32 +- .../server/lib/adapters/tags/adapter_types.ts | 2 +- .../tags/elasticsearch_tags_adapter.ts | 20 +- .../server/lib/domains/beats.ts | 6 +- .../server/lib/domains/tags.ts | 4 +- .../server/rest_api/beats/list.ts | 17 +- .../server/rest_api/tags/list.ts | 16 +- .../plugins/beats_management/types/eui.d.ts | 47 +++ .../beats_management/types/kibana.d.ts | 57 ++++ 44 files changed, 1465 insertions(+), 227 deletions(-) create mode 100644 x-pack/plugins/beats_management/public/app.d.ts create mode 100644 x-pack/plugins/beats_management/public/components/autocomplete_field/index.tsx create mode 100644 x-pack/plugins/beats_management/public/components/autocomplete_field/suggestion_item.tsx create mode 100644 x-pack/plugins/beats_management/public/components/layouts/header.tsx create mode 100644 x-pack/plugins/beats_management/public/containers/with_kuery_autocompletion.tsx create mode 100644 x-pack/plugins/beats_management/public/containers/with_url_state.tsx rename x-pack/plugins/beats_management/public/lib/{domains => }/__tests__/tags.test.ts (96%) create mode 100644 x-pack/plugins/beats_management/public/lib/adapters/elasticsearch/adapter_types.ts create mode 100644 x-pack/plugins/beats_management/public/lib/adapters/elasticsearch/rest.ts create mode 100644 x-pack/plugins/beats_management/public/lib/adapters/elasticsearch/test.ts rename x-pack/plugins/beats_management/public/lib/{domains => }/beats.ts (88%) create mode 100644 x-pack/plugins/beats_management/public/lib/elasticsearch.ts rename x-pack/plugins/beats_management/public/lib/{domains => }/tags.ts (90%) create mode 100644 x-pack/plugins/beats_management/public/utils/typed_react.ts create mode 100644 x-pack/plugins/beats_management/types/eui.d.ts create mode 100644 x-pack/plugins/beats_management/types/kibana.d.ts diff --git a/x-pack/plugins/beats_management/public/app.d.ts b/x-pack/plugins/beats_management/public/app.d.ts new file mode 100644 index 0000000000000..736b6c2d59dcc --- /dev/null +++ b/x-pack/plugins/beats_management/public/app.d.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export type FlatObject = { [Key in keyof T]: string }; + +export interface AppURLState { + beatsKBar: string; + tagsKBar: string; +} diff --git a/x-pack/plugins/beats_management/public/components/autocomplete_field/index.tsx b/x-pack/plugins/beats_management/public/components/autocomplete_field/index.tsx new file mode 100644 index 0000000000000..7ff5296ef4d92 --- /dev/null +++ b/x-pack/plugins/beats_management/public/components/autocomplete_field/index.tsx @@ -0,0 +1,290 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + EuiFieldSearch, + EuiFieldSearchProps, + EuiOutsideClickDetector, + EuiPanel, +} from '@elastic/eui'; +import React from 'react'; +import styled from 'styled-components'; + +// @ts-ignore +import { AutocompleteSuggestion } from 'ui/autocomplete_providers'; + +import { composeStateUpdaters } from '../../utils/typed_react'; +import { SuggestionItem } from './suggestion_item'; + +interface AutocompleteFieldProps { + isLoadingSuggestions: boolean; + isValid: boolean; + loadSuggestions: (value: string, cursorPosition: number, maxCount?: number) => void; + onSubmit?: (value: string) => void; + onChange?: (value: string) => void; + placeholder?: string; + suggestions: AutocompleteSuggestion[]; + value: string; +} + +interface AutocompleteFieldState { + areSuggestionsVisible: boolean; + selectedIndex: number | null; +} + +export class AutocompleteField extends React.Component< + AutocompleteFieldProps, + AutocompleteFieldState +> { + public readonly state: AutocompleteFieldState = { + areSuggestionsVisible: false, + selectedIndex: null, + }; + + private inputElement: HTMLInputElement | null = null; + + public render() { + const { suggestions, isLoadingSuggestions, isValid, placeholder, value } = this.props; + const { areSuggestionsVisible, selectedIndex } = this.state; + + return ( + + + + {areSuggestionsVisible && !isLoadingSuggestions && suggestions.length > 0 ? ( + + {suggestions.map((suggestion, suggestionIndex) => ( + + ))} + + ) : null} + + + ); + } + + public componentDidUpdate(prevProps: AutocompleteFieldProps, prevState: AutocompleteFieldState) { + const hasNewSuggestions = prevProps.suggestions !== this.props.suggestions; + const hasNewValue = prevProps.value !== this.props.value; + + if (hasNewValue) { + this.updateSuggestions(); + } + + if (hasNewSuggestions) { + this.showSuggestions(); + } + } + + private handleChangeInputRef = (element: HTMLInputElement | null) => { + this.inputElement = element; + }; + + private handleChange = (evt: React.ChangeEvent) => { + this.changeValue(evt.currentTarget.value); + }; + + private handleKeyDown = (evt: React.KeyboardEvent) => { + const { suggestions } = this.props; + + switch (evt.key) { + case 'ArrowUp': + evt.preventDefault(); + if (suggestions.length > 0) { + this.setState( + composeStateUpdaters(withSuggestionsVisible, withPreviousSuggestionSelected) + ); + } + break; + case 'ArrowDown': + evt.preventDefault(); + if (suggestions.length > 0) { + this.setState(composeStateUpdaters(withSuggestionsVisible, withNextSuggestionSelected)); + } else { + this.updateSuggestions(); + } + break; + case 'Enter': + evt.preventDefault(); + if (this.state.selectedIndex !== null) { + this.applySelectedSuggestion(); + } else { + this.submit(); + } + break; + case 'Escape': + evt.preventDefault(); + this.setState(withSuggestionsHidden); + break; + } + }; + + private handleKeyUp = (evt: React.KeyboardEvent) => { + switch (evt.key) { + case 'ArrowLeft': + case 'ArrowRight': + case 'Home': + case 'End': + this.updateSuggestions(); + break; + } + }; + + private selectSuggestionAt = (index: number) => () => { + this.setState(withSuggestionAtIndexSelected(index)); + }; + + private applySelectedSuggestion = () => { + if (this.state.selectedIndex !== null) { + this.applySuggestionAt(this.state.selectedIndex)(); + } + }; + + private applySuggestionAt = (index: number) => () => { + const { value, suggestions } = this.props; + const selectedSuggestion = suggestions[index]; + + if (!selectedSuggestion) { + return; + } + + const newValue = + value.substr(0, selectedSuggestion.start) + + selectedSuggestion.text + + value.substr(selectedSuggestion.end); + + this.setState(withSuggestionsHidden); + this.changeValue(newValue); + this.focusInputElement(); + }; + + private changeValue = (value: string) => { + const { onChange } = this.props; + if (onChange) { + onChange(value); + } + }; + + private focusInputElement = () => { + if (this.inputElement) { + this.inputElement.focus(); + } + }; + + private showSuggestions = () => { + this.setState(withSuggestionsVisible); + }; + + private hideSuggestions = () => { + this.setState(withSuggestionsHidden); + }; + + private submit = () => { + const { isValid, onSubmit, value } = this.props; + + if (isValid && onSubmit) { + onSubmit(value); + } + + this.setState(withSuggestionsHidden); + }; + + private updateSuggestions = (value?: string) => { + const inputCursorPosition = this.inputElement ? this.inputElement.selectionStart || 0 : 0; + this.props.loadSuggestions(value || this.props.value, inputCursorPosition, 10); + }; +} + +const withPreviousSuggestionSelected = ( + state: AutocompleteFieldState, + props: AutocompleteFieldProps +): AutocompleteFieldState => ({ + ...state, + selectedIndex: + props.suggestions.length === 0 + ? null + : state.selectedIndex !== null + ? (state.selectedIndex + props.suggestions.length - 1) % props.suggestions.length + : Math.max(props.suggestions.length - 1, 0), +}); + +const withNextSuggestionSelected = ( + state: AutocompleteFieldState, + props: AutocompleteFieldProps +): AutocompleteFieldState => ({ + ...state, + selectedIndex: + props.suggestions.length === 0 + ? null + : state.selectedIndex !== null + ? (state.selectedIndex + 1) % props.suggestions.length + : 0, +}); + +const withSuggestionAtIndexSelected = (suggestionIndex: number) => ( + state: AutocompleteFieldState, + props: AutocompleteFieldProps +): AutocompleteFieldState => ({ + ...state, + selectedIndex: + props.suggestions.length === 0 + ? null + : suggestionIndex >= 0 && suggestionIndex < props.suggestions.length + ? suggestionIndex + : 0, +}); + +const withSuggestionsVisible = (state: AutocompleteFieldState) => ({ + ...state, + areSuggestionsVisible: true, +}); + +const withSuggestionsHidden = (state: AutocompleteFieldState) => ({ + ...state, + areSuggestionsVisible: false, + selectedIndex: null, +}); + +const FixedEuiFieldSearch: React.SFC< + React.InputHTMLAttributes & + EuiFieldSearchProps & { + inputRef?: (element: HTMLInputElement | null) => void; + onSearch: (value: string) => void; + } +> = EuiFieldSearch as any; + +const AutocompleteContainer = styled.div` + position: relative; +`; + +const SuggestionsPanel = styled(EuiPanel).attrs({ + paddingSize: 'none', + hasShadow: true, +})` + position: absolute; + width: 100%; + margin-top: 2px; + overflow: hidden; + z-index: 1000; +`; diff --git a/x-pack/plugins/beats_management/public/components/autocomplete_field/suggestion_item.tsx b/x-pack/plugins/beats_management/public/components/autocomplete_field/suggestion_item.tsx new file mode 100644 index 0000000000000..c9dc832cf37b7 --- /dev/null +++ b/x-pack/plugins/beats_management/public/components/autocomplete_field/suggestion_item.tsx @@ -0,0 +1,123 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiIcon } from '@elastic/eui'; +import { tint } from 'polished'; +import React from 'react'; +import styled from 'styled-components'; + +// @ts-ignore +import { AutocompleteSuggestion } from 'ui/autocomplete_providers'; + +interface SuggestionItemProps { + isSelected?: boolean; + onClick?: React.MouseEventHandler; + onMouseEnter?: React.MouseEventHandler; + suggestion: AutocompleteSuggestion; +} + +export class SuggestionItem extends React.Component { + public static defaultProps: Partial = { + isSelected: false, + }; + + public render() { + const { isSelected, onClick, onMouseEnter, suggestion } = this.props; + + return ( + + + + + {suggestion.text} + + + ); + } +} + +const SuggestionItemContainer = styled.div<{ + isSelected?: boolean; +}>` + display: flex; + flex-direction: row; + font-size: ${props => props.theme.eui.euiFontSizeS}; + height: ${props => props.theme.eui.euiSizeXl}; + white-space: nowrap; + background-color: ${props => + props.isSelected ? props.theme.eui.euiColorLightestShade : 'transparent'}; +`; + +const SuggestionItemField = styled.div` + align-items: center; + cursor: pointer; + display: flex; + flex-direction: row; + height: ${props => props.theme.eui.euiSizeXl}; + padding: ${props => props.theme.eui.euiSizeXs}; +`; + +const SuggestionItemIconField = SuggestionItemField.extend<{ suggestionType: string }>` + background-color: ${props => tint(0.1, getEuiIconColor(props.theme, props.suggestionType))}; + color: ${props => getEuiIconColor(props.theme, props.suggestionType)}; + flex: 0 0 auto; + justify-content: center; + width: ${props => props.theme.eui.euiSizeXl}; +`; + +const SuggestionItemTextField = SuggestionItemField.extend` + flex: 2 0 0; + font-family: ${props => props.theme.eui.euiCodeFontFamily}; +`; + +const SuggestionItemDescriptionField = SuggestionItemField.extend` + flex: 3 0 0; + p { + display: inline; + span { + font-family: ${props => props.theme.eui.euiCodeFontFamily}; + } + } +`; + +const getEuiIconType = (suggestionType: string) => { + switch (suggestionType) { + case 'field': + return 'kqlField'; + case 'value': + return 'kqlValue'; + case 'recentSearch': + return 'search'; + case 'conjunction': + return 'kqlSelector'; + case 'operator': + return 'kqlOperand'; + default: + return 'empty'; + } +}; + +const getEuiIconColor = (theme: any, suggestionType: string): string => { + switch (suggestionType) { + case 'field': + return theme.eui.euiColorVis7; + case 'value': + return theme.eui.euiColorVis0; + case 'operator': + return theme.eui.euiColorVis1; + case 'conjunction': + return theme.eui.euiColorVis2; + case 'recentSearch': + default: + return theme.eui.euiColorMediumShade; + } +}; diff --git a/x-pack/plugins/beats_management/public/components/connected_link.tsx b/x-pack/plugins/beats_management/public/components/connected_link.tsx index c4b26b0ad93af..b2c0e8ad607af 100644 --- a/x-pack/plugins/beats_management/public/components/connected_link.tsx +++ b/x-pack/plugins/beats_management/public/components/connected_link.tsx @@ -11,12 +11,14 @@ import { Link, withRouter } from 'react-router-dom'; export function ConnectedLinkComponent({ location, path, + query, disabled, ...props }: { location: any; path: string; disabled: boolean; + query: any; [key: string]: any; }) { if (disabled) { @@ -29,7 +31,7 @@ export function ConnectedLinkComponent({ return ( ); diff --git a/x-pack/plugins/beats_management/public/components/layouts/header.tsx b/x-pack/plugins/beats_management/public/components/layouts/header.tsx new file mode 100644 index 0000000000000..4ad567b73fc77 --- /dev/null +++ b/x-pack/plugins/beats_management/public/components/layouts/header.tsx @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + EuiBreadcrumbDefinition, + EuiHeader, + EuiHeaderBreadcrumbs, + EuiHeaderSection, +} from '@elastic/eui'; +import React from 'react'; +import styled from 'styled-components'; + +interface HeaderProps { + breadcrumbs?: EuiBreadcrumbDefinition[]; +} + +export class Header extends React.PureComponent { + public render() { + const { breadcrumbs = [] } = this.props; + + return ( + + + + + + ); + } +} + +const HeaderWrapper = styled(EuiHeader)` + height: 29px; +`; diff --git a/x-pack/plugins/beats_management/public/components/table/controls.tsx b/x-pack/plugins/beats_management/public/components/table/controls.tsx index c887f2e1521eb..27e0255602675 100644 --- a/x-pack/plugins/beats_management/public/components/table/controls.tsx +++ b/x-pack/plugins/beats_management/public/components/table/controls.tsx @@ -17,6 +17,15 @@ interface ControlBarProps { showAssignmentOptions: boolean; controlDefinitions: ControlDefinitions; selectionCount: number; + + isLoadingSuggestions: any; + onKueryBarSubmit: any; + kueryValue: any; + isKueryValid: any; + onKueryBarChange: any; + loadSuggestions: any; + suggestions: any; + filterQueryDraft: any; actionHandler(actionType: string, payload?: any): void; } @@ -29,6 +38,14 @@ export function ControlBar(props: ControlBarProps) { controlDefinitions, selectionCount, showAssignmentOptions, + isLoadingSuggestions, + isKueryValid, + kueryValue, + loadSuggestions, + onKueryBarChange, + onKueryBarSubmit, + suggestions, + filterQueryDraft, } = props; const filters = controlDefinitions.filters.length === 0 ? null : controlDefinitions.filters; @@ -43,6 +60,14 @@ export function ControlBar(props: ControlBarProps) { /> ) : ( actionHandler('search', query)} diff --git a/x-pack/plugins/beats_management/public/components/table/primary_options.tsx b/x-pack/plugins/beats_management/public/components/table/primary_options.tsx index bd92fb8e827e4..d804438212efd 100644 --- a/x-pack/plugins/beats_management/public/components/table/primary_options.tsx +++ b/x-pack/plugins/beats_management/public/components/table/primary_options.tsx @@ -4,19 +4,25 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - EuiFlexGroup, - EuiFlexItem, - // @ts-ignore typings for EuiSearchar not included in EUI - EuiSearchBar, -} from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import React from 'react'; +// @ts-ignore +import { AutocompleteSuggestion } from 'ui/autocomplete_providers'; +import { AutocompleteField } from '../autocomplete_field/index'; import { ActionDefinition, FilterDefinition } from '../table'; import { ActionButton } from './action_button'; interface PrimaryOptionsProps { filters: FilterDefinition[] | null; primaryActions: ActionDefinition[]; + isLoadingSuggestions: boolean; + loadSuggestions: () => any; + suggestions: AutocompleteSuggestion[]; + onKueryBarSubmit: any; + kueryValue: any; + filterQueryDraft: any; + isKueryValid: any; + onKueryBarChange: any; actionHandler(actionType: string, payload?: any): void; onSearchQueryChange(query: any): void; } @@ -34,7 +40,19 @@ export class PrimaryOptions extends React.PureComponent @@ -47,10 +65,15 @@ export class PrimaryOptions extends React.PureComponent - diff --git a/x-pack/plugins/beats_management/public/components/table/table.tsx b/x-pack/plugins/beats_management/public/components/table/table.tsx index d8390a16c8e4a..7cdbe14905077 100644 --- a/x-pack/plugins/beats_management/public/components/table/table.tsx +++ b/x-pack/plugins/beats_management/public/components/table/table.tsx @@ -16,12 +16,21 @@ import { ControlBar } from './controls'; import { TableType } from './table_type_configs'; interface TableProps { - assignmentOptions: any[] | null; - assignmentTitle: string | null; + assignmentOptions?: any[] | null; + assignmentTitle?: string | null; items: any[]; renderAssignmentOptions?: (item: any, key: string) => any; showAssignmentOptions: boolean; type: TableType; + + isLoadingSuggestions: boolean; + loadSuggestions: any; + onKueryBarSubmit: any; + isKueryValid: any; + kueryValue: any; + onKueryBarChange: any; + suggestions: any; + filterQueryDraft: any; actionHandler(action: string, payload?: any): void; } @@ -61,6 +70,14 @@ export class Table extends React.Component { items, showAssignmentOptions, type, + isLoadingSuggestions, + loadSuggestions, + onKueryBarSubmit, + isKueryValid, + kueryValue, + onKueryBarChange, + suggestions, + filterQueryDraft, } = this.props; const pagination = { @@ -78,10 +95,18 @@ export class Table extends React.Component { return ( void; + suggestions: AutocompleteSuggestion[]; + }>; +} + +interface WithKueryAutocompletionLifecycleState { + // lacking cancellation support in the autocompletion api, + // this is used to keep older, slower requests from clobbering newer ones + currentRequest: { + expression: string; + cursorPosition: number; + } | null; + suggestions: AutocompleteSuggestion[]; +} + +export class WithKueryAutocompletion extends React.Component< + WithKueryAutocompletionLifecycleProps, + WithKueryAutocompletionLifecycleState +> { + public readonly state: WithKueryAutocompletionLifecycleState = { + currentRequest: null, + suggestions: [], + }; + + public render() { + const { currentRequest, suggestions } = this.state; + + return this.props.children({ + isLoadingSuggestions: currentRequest !== null, + loadSuggestions: this.loadSuggestions, + suggestions, + }); + } + + private loadSuggestions = async ( + expression: string, + cursorPosition: number, + maxSuggestions?: number + ) => { + this.setState({ + currentRequest: { + expression, + cursorPosition, + }, + suggestions: [], + }); + let suggestions: any[] = []; + try { + suggestions = await this.props.libs.elasticsearch.getSuggestions( + expression, + cursorPosition, + this.props.fieldPrefix + ); + } catch (e) { + suggestions = []; + } + + this.setState( + state => + state.currentRequest && + state.currentRequest.expression !== expression && + state.currentRequest.cursorPosition !== cursorPosition + ? state // ignore this result, since a newer request is in flight + : { + ...state, + currentRequest: null, + suggestions: maxSuggestions ? suggestions.slice(0, maxSuggestions) : suggestions, + } + ); + }; +} diff --git a/x-pack/plugins/beats_management/public/containers/with_url_state.tsx b/x-pack/plugins/beats_management/public/containers/with_url_state.tsx new file mode 100644 index 0000000000000..384a42ce463b9 --- /dev/null +++ b/x-pack/plugins/beats_management/public/containers/with_url_state.tsx @@ -0,0 +1,92 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { parse, stringify } from 'querystring'; +import React from 'react'; +import { withRouter } from 'react-router-dom'; +import { FlatObject } from '../app'; +import { RendererFunction } from '../utils/typed_react'; + +type StateCallback = (previousState: T) => T; + +export interface URLStateProps { + goTo: (path: string) => void; + setUrlState: ( + newState: FlatObject | StateCallback | Promise> + ) => void; + urlState: URLState; +} +interface ComponentProps { + history: any; + match: any; + children: RendererFunction>; +} + +export class WithURLStateComponent extends React.Component< + ComponentProps +> { + private get URLState(): URLState { + // slice because parse does not account for the initial ? in the search string + return parse(decodeURIComponent(this.props.history.location.search).substring(1)) as URLState; + } + + private historyListener: (() => void) | null = null; + + public componentWillUnmount() { + if (this.historyListener) { + this.historyListener(); + } + } + public render() { + return this.props.children({ + goTo: this.goTo, + setUrlState: this.setURLState, + urlState: this.URLState || {}, + }); + } + + private setURLState = async ( + state: FlatObject | StateCallback | Promise> + ) => { + let newState; + const pastState = this.URLState; + if (typeof state === 'function') { + newState = await state(pastState); + } else { + newState = state; + } + + const search: string = stringify({ + ...(pastState as any), + ...(newState as any), + }); + + const newLocation = { + ...this.props.history.location, + search, + }; + + this.props.history.replace(newLocation); + }; + + private goTo = (path: string) => { + this.props.history.push({ + pathname: path, + search: this.props.history.location.search, + }); + }; +} +export const WithURLState = withRouter(WithURLStateComponent); + +export function withUrlState(UnwrappedComponent: React.ComponentType): React.SFC { + return (origProps: OP) => { + return ( + + {(URLProps: URLStateProps) => } + + ); + }; +} diff --git a/x-pack/plugins/beats_management/public/index.tsx b/x-pack/plugins/beats_management/public/index.tsx index 9da5a99fc7028..ac869eb83c6ac 100644 --- a/x-pack/plugins/beats_management/public/index.tsx +++ b/x-pack/plugins/beats_management/public/index.tsx @@ -14,11 +14,17 @@ import { FrontendLibs } from './lib/lib'; import { PageRouter } from './router'; // TODO use theme provided from parentApp when kibana supports it +import * as euiVars from '@elastic/eui/dist/eui_theme_k6_light.json'; import '@elastic/eui/dist/eui_theme_light.css'; +import { ThemeProvider } from 'styled-components'; function startApp(libs: FrontendLibs) { libs.framework.registerManagementSection('beats', 'Beats Management', BASE_PATH); - libs.framework.render(); + libs.framework.render( + + + + ); } startApp(compose()); diff --git a/x-pack/plugins/beats_management/public/lib/domains/__tests__/tags.test.ts b/x-pack/plugins/beats_management/public/lib/__tests__/tags.test.ts similarity index 96% rename from x-pack/plugins/beats_management/public/lib/domains/__tests__/tags.test.ts rename to x-pack/plugins/beats_management/public/lib/__tests__/tags.test.ts index f48ab021d8271..0c950be147f15 100644 --- a/x-pack/plugins/beats_management/public/lib/domains/__tests__/tags.test.ts +++ b/x-pack/plugins/beats_management/public/lib/__tests__/tags.test.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { BeatTag } from '../../../../common/domain_types'; -import { supportedConfigs } from '../../../config_schemas'; -import { CMTagsAdapter } from '../../adapters/tags/adapter_types'; +import { BeatTag } from '../../../common/domain_types'; +import { supportedConfigs } from '../../config_schemas'; +import { CMTagsAdapter } from '../adapters/tags/adapter_types'; import { TagsLib } from '../tags'; describe('Tags Client Domain Lib', () => { diff --git a/x-pack/plugins/beats_management/public/lib/adapters/beats/adapter_types.ts b/x-pack/plugins/beats_management/public/lib/adapters/beats/adapter_types.ts index aaa41871cd068..3808ec1d57422 100644 --- a/x-pack/plugins/beats_management/public/lib/adapters/beats/adapter_types.ts +++ b/x-pack/plugins/beats_management/public/lib/adapters/beats/adapter_types.ts @@ -10,7 +10,7 @@ export interface CMBeatsAdapter { get(id: string): Promise; update(id: string, beatData: Partial): Promise; getBeatsWithTag(tagId: string): Promise; - getAll(): Promise; + getAll(ESQuery?: any): Promise; removeTagsFromBeats(removals: BeatsTagAssignment[]): Promise; assignTagsToBeats(assignments: BeatsTagAssignment[]): Promise; getBeatWithToken(enrollmentToken: string): Promise; diff --git a/x-pack/plugins/beats_management/public/lib/adapters/beats/rest_beats_adapter.ts b/x-pack/plugins/beats_management/public/lib/adapters/beats/rest_beats_adapter.ts index e0e8051e14a94..8649bf9c37e0e 100644 --- a/x-pack/plugins/beats_management/public/lib/adapters/beats/rest_beats_adapter.ts +++ b/x-pack/plugins/beats_management/public/lib/adapters/beats/rest_beats_adapter.ts @@ -24,8 +24,8 @@ export class RestBeatsAdapter implements CMBeatsAdapter { return beat; } - public async getAll(): Promise { - return (await this.REST.get<{ beats: CMBeat[] }>('/api/beats/agents/all')).beats; + public async getAll(ESQuery?: any): Promise { + return (await this.REST.get<{ beats: CMBeat[] }>('/api/beats/agents/all', { ESQuery })).beats; } public async getBeatsWithTag(tagId: string): Promise { diff --git a/x-pack/plugins/beats_management/public/lib/adapters/elasticsearch/adapter_types.ts b/x-pack/plugins/beats_management/public/lib/adapters/elasticsearch/adapter_types.ts new file mode 100644 index 0000000000000..4940857493275 --- /dev/null +++ b/x-pack/plugins/beats_management/public/lib/adapters/elasticsearch/adapter_types.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { AutocompleteSuggestion } from 'ui/autocomplete_providers'; + +export interface ElasticsearchAdapter { + convertKueryToEsQuery: (kuery: string) => Promise; + getSuggestions: (kuery: string, selectionStart: any) => Promise; + isKueryValid(kuery: string): boolean; +} diff --git a/x-pack/plugins/beats_management/public/lib/adapters/elasticsearch/rest.ts b/x-pack/plugins/beats_management/public/lib/adapters/elasticsearch/rest.ts new file mode 100644 index 0000000000000..75b201c8a4968 --- /dev/null +++ b/x-pack/plugins/beats_management/public/lib/adapters/elasticsearch/rest.ts @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { isEmpty } from 'lodash'; +import { AutocompleteSuggestion, getAutocompleteProvider } from 'ui/autocomplete_providers'; +// @ts-ignore TODO type this +import { fromKueryExpression, toElasticsearchQuery } from 'ui/kuery'; +import { RestAPIAdapter } from '../rest_api/adapter_types'; +import { ElasticsearchAdapter } from './adapter_types'; + +export class RestElasticsearchAdapter implements ElasticsearchAdapter { + private cachedIndexPattern: any = null; + constructor(private readonly api: RestAPIAdapter, private readonly indexPatternName: string) {} + + public isKueryValid(kuery: string): boolean { + try { + fromKueryExpression(kuery); + } catch (err) { + return false; + } + + return true; + } + public async convertKueryToEsQuery(kuery: string): Promise { + if (!this.isKueryValid(kuery)) { + return ''; + } + const ast = fromKueryExpression(kuery); + const indexPattern = await this.getIndexPattern(); + return toElasticsearchQuery(ast, indexPattern); + } + public async getSuggestions( + kuery: string, + selectionStart: any + ): Promise { + const autocompleteProvider = getAutocompleteProvider('kuery'); + if (!autocompleteProvider) { + return []; + } + const config = { + get: () => true, + }; + const indexPattern = await this.getIndexPattern(); + + const getAutocompleteSuggestions = autocompleteProvider({ + config, + indexPatterns: [indexPattern], + boolFilter: null, + }); + const results = getAutocompleteSuggestions({ + query: kuery || '', + selectionStart, + selectionEnd: selectionStart, + }); + return results; + } + + private async getIndexPattern() { + if (this.cachedIndexPattern) { + return this.cachedIndexPattern; + } + const res = await this.api.get( + `/api/index_patterns/_fields_for_wildcard?pattern=${this.indexPatternName}` + ); + if (isEmpty(res.fields)) { + return; + } + this.cachedIndexPattern = { + fields: res.fields, + title: `${this.indexPatternName}`, + }; + return this.cachedIndexPattern; + } +} diff --git a/x-pack/plugins/beats_management/public/lib/adapters/elasticsearch/test.ts b/x-pack/plugins/beats_management/public/lib/adapters/elasticsearch/test.ts new file mode 100644 index 0000000000000..1d45378451f1d --- /dev/null +++ b/x-pack/plugins/beats_management/public/lib/adapters/elasticsearch/test.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { AutocompleteSuggestion } from 'ui/autocomplete_providers'; +import { ElasticsearchAdapter } from './adapter_types'; + +export class TestElasticsearchAdapter implements ElasticsearchAdapter { + public async convertKueryToEsQuery(kuery: string): Promise { + return 'foo'; + } + public async getSuggestions( + kuery: string, + selectionStart: any + ): Promise { + return []; + } +} diff --git a/x-pack/plugins/beats_management/public/lib/adapters/rest_api/adapter_types.ts b/x-pack/plugins/beats_management/public/lib/adapters/rest_api/adapter_types.ts index 222807e7f6948..e9d9bf551f739 100644 --- a/x-pack/plugins/beats_management/public/lib/adapters/rest_api/adapter_types.ts +++ b/x-pack/plugins/beats_management/public/lib/adapters/rest_api/adapter_types.ts @@ -3,9 +3,10 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { FlatObject } from '../../../app'; export interface RestAPIAdapter { - get(url: string): Promise; + get(url: string, query?: FlatObject): Promise; post(url: string, body?: { [key: string]: any }): Promise; delete(url: string): Promise; put(url: string, body?: any): Promise; diff --git a/x-pack/plugins/beats_management/public/lib/adapters/rest_api/axios_rest_api_adapter.ts b/x-pack/plugins/beats_management/public/lib/adapters/rest_api/axios_rest_api_adapter.ts index 56bd9b63df686..690843bbb1cf8 100644 --- a/x-pack/plugins/beats_management/public/lib/adapters/rest_api/axios_rest_api_adapter.ts +++ b/x-pack/plugins/beats_management/public/lib/adapters/rest_api/axios_rest_api_adapter.ts @@ -5,14 +5,15 @@ */ import axios, { AxiosInstance } from 'axios'; +import { FlatObject } from '../../../app'; import { RestAPIAdapter } from './adapter_types'; let globalAPI: AxiosInstance; export class AxiosRestAPIAdapter implements RestAPIAdapter { constructor(private readonly xsrfToken: string, private readonly basePath: string) {} - public async get(url: string): Promise { - return await this.REST.get(url).then(resp => resp.data); + public async get(url: string, query?: FlatObject): Promise { + return await this.REST.get(url, query ? { params: query } : {}).then(resp => resp.data); } public async post( diff --git a/x-pack/plugins/beats_management/public/lib/domains/beats.ts b/x-pack/plugins/beats_management/public/lib/beats.ts similarity index 88% rename from x-pack/plugins/beats_management/public/lib/domains/beats.ts rename to x-pack/plugins/beats_management/public/lib/beats.ts index a0aeaabbcfe67..f676f4611be63 100644 --- a/x-pack/plugins/beats_management/public/lib/domains/beats.ts +++ b/x-pack/plugins/beats_management/public/lib/beats.ts @@ -5,14 +5,14 @@ */ import { flatten } from 'lodash'; -import { CMBeat, CMPopulatedBeat } from '../../../common/domain_types'; +import { CMBeat, CMPopulatedBeat } from './../../common/domain_types'; import { BeatsRemovalReturn, BeatsTagAssignment, CMAssignmentReturn, CMBeatsAdapter, -} from '../adapters/beats/adapter_types'; -import { FrontendDomainLibs } from '../lib'; +} from './adapters/beats/adapter_types'; +import { FrontendDomainLibs } from './lib'; export class BeatsLib { constructor( @@ -35,8 +35,8 @@ export class BeatsLib { return await this.mergeInTags(beats); } - public async getAll(): Promise { - const beats = await this.adapter.getAll(); + public async getAll(ESQuery?: any): Promise { + const beats = await this.adapter.getAll(ESQuery); return await this.mergeInTags(beats); } diff --git a/x-pack/plugins/beats_management/public/lib/compose/kibana.ts b/x-pack/plugins/beats_management/public/lib/compose/kibana.ts index 3488a5d23a1ef..9ff3bbe613d83 100644 --- a/x-pack/plugins/beats_management/public/lib/compose/kibana.ts +++ b/x-pack/plugins/beats_management/public/lib/compose/kibana.ts @@ -19,18 +19,22 @@ import { Notifier } from 'ui/notify'; // @ts-ignore: path dynamic for kibana import routes from 'ui/routes'; +import { INDEX_NAMES } from '../../../common/constants/index_names'; import { supportedConfigs } from '../../config_schemas'; import { RestBeatsAdapter } from '../adapters/beats/rest_beats_adapter'; +import { RestElasticsearchAdapter } from '../adapters/elasticsearch/rest'; import { KibanaFrameworkAdapter } from '../adapters/framework/kibana_framework_adapter'; import { AxiosRestAPIAdapter } from '../adapters/rest_api/axios_rest_api_adapter'; import { RestTagsAdapter } from '../adapters/tags/rest_tags_adapter'; import { RestTokensAdapter } from '../adapters/tokens/rest_tokens_adapter'; -import { BeatsLib } from '../domains/beats'; -import { TagsLib } from '../domains/tags'; +import { BeatsLib } from '../beats'; +import { ElasticsearchLib } from '../elasticsearch'; import { FrontendDomainLibs, FrontendLibs } from '../lib'; +import { TagsLib } from '../tags'; export function compose(): FrontendLibs { const api = new AxiosRestAPIAdapter(chrome.getXsrfToken(), chrome.getBasePath()); + const esAdapter = new RestElasticsearchAdapter(api, INDEX_NAMES.BEATS); const tags = new TagsLib(new RestTagsAdapter(api), supportedConfigs); const tokens = new RestTokensAdapter(api); @@ -56,6 +60,7 @@ export function compose(): FrontendLibs { const libs: FrontendLibs = { framework, + elasticsearch: new ElasticsearchLib(esAdapter), ...domainLibs, }; return libs; diff --git a/x-pack/plugins/beats_management/public/lib/compose/memory.ts b/x-pack/plugins/beats_management/public/lib/compose/memory.ts index ab56f6708123d..d96719ecfe129 100644 --- a/x-pack/plugins/beats_management/public/lib/compose/memory.ts +++ b/x-pack/plugins/beats_management/public/lib/compose/memory.ts @@ -17,13 +17,16 @@ import { KibanaFrameworkAdapter } from '../adapters/framework/kibana_framework_a import { MemoryTagsAdapter } from '../adapters/tags/memory_tags_adapter'; import { MemoryTokensAdapter } from '../adapters/tokens/memory_tokens_adapter'; -import { BeatsLib } from '../domains/beats'; +import { BeatsLib } from '../beats'; import { FrontendDomainLibs, FrontendLibs } from '../lib'; import { supportedConfigs } from '../../config_schemas'; -import { TagsLib } from '../domains/tags'; +import { TagsLib } from '../tags'; +import { TestElasticsearchAdapter } from './../adapters/elasticsearch/test'; +import { ElasticsearchLib } from './../elasticsearch'; export function compose(): FrontendLibs { + const esAdapter = new TestElasticsearchAdapter(); const tags = new TagsLib(new MemoryTagsAdapter([]), supportedConfigs); const tokens = new MemoryTokensAdapter(); const beats = new BeatsLib(new MemoryBeatsAdapter([]), { tags }); @@ -45,6 +48,7 @@ export function compose(): FrontendLibs { ); const libs: FrontendLibs = { ...domainLibs, + elasticsearch: new ElasticsearchLib(esAdapter), framework, }; return libs; diff --git a/x-pack/plugins/beats_management/public/lib/elasticsearch.ts b/x-pack/plugins/beats_management/public/lib/elasticsearch.ts new file mode 100644 index 0000000000000..7ea32a2eb9467 --- /dev/null +++ b/x-pack/plugins/beats_management/public/lib/elasticsearch.ts @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { AutocompleteSuggestion } from 'ui/autocomplete_providers'; +import { ElasticsearchAdapter } from './adapters/elasticsearch/adapter_types'; + +interface HiddenFields { + op: 'is' | 'startsWith' | 'withoutPrefix'; + value: string; +} + +export class ElasticsearchLib { + private readonly hiddenFields: HiddenFields[] = [ + { op: 'startsWith', value: 'enrollment_token' }, + { op: 'is', value: 'beat.active' }, + { op: 'is', value: 'beat.enrollment_token' }, + { op: 'is', value: 'beat.access_token' }, + { op: 'is', value: 'beat.ephemeral_id' }, + { op: 'is', value: 'beat.verified_on' }, + ]; + + constructor(private readonly adapter: ElasticsearchAdapter) {} + + public isKueryValid(kuery: string): boolean { + return this.adapter.isKueryValid(kuery); + } + public async convertKueryToEsQuery(kuery: string): Promise { + return await this.adapter.convertKueryToEsQuery(kuery); + } + + public async getSuggestions( + kuery: string, + selectionStart: any, + fieldPrefix?: string + ): Promise { + const suggestions = await this.adapter.getSuggestions(kuery, selectionStart); + + const filteredSuggestions = suggestions.filter(suggestion => { + const hiddenFieldsCheck = this.hiddenFields; + + if (fieldPrefix) { + hiddenFieldsCheck.push({ + op: 'withoutPrefix', + value: `${fieldPrefix}.`, + }); + } + + return hiddenFieldsCheck.reduce((isvalid, field) => { + if (!isvalid) { + return false; + } + + switch (field.op) { + case 'startsWith': + return !suggestion.text.startsWith(field.value); + case 'is': + return suggestion.text.trim() !== field.value; + case 'withoutPrefix': + return suggestion.text.startsWith(field.value); + } + }, true); + }); + + return filteredSuggestions; + } +} diff --git a/x-pack/plugins/beats_management/public/lib/lib.ts b/x-pack/plugins/beats_management/public/lib/lib.ts index 7c059aa2440e4..9f589af5344f7 100644 --- a/x-pack/plugins/beats_management/public/lib/lib.ts +++ b/x-pack/plugins/beats_management/public/lib/lib.ts @@ -8,8 +8,9 @@ import { IModule, IScope } from 'angular'; import { AxiosRequestConfig } from 'axios'; import React from 'react'; import { CMTokensAdapter } from './adapters/tokens/adapter_types'; -import { BeatsLib } from './domains/beats'; -import { TagsLib } from './domains/tags'; +import { BeatsLib } from './beats'; +import { ElasticsearchLib } from './elasticsearch'; +import { TagsLib } from './tags'; export interface FrontendDomainLibs { beats: BeatsLib; @@ -18,6 +19,7 @@ export interface FrontendDomainLibs { } export interface FrontendLibs extends FrontendDomainLibs { + elasticsearch: ElasticsearchLib; framework: FrameworkAdapter; } diff --git a/x-pack/plugins/beats_management/public/lib/domains/tags.ts b/x-pack/plugins/beats_management/public/lib/tags.ts similarity index 90% rename from x-pack/plugins/beats_management/public/lib/domains/tags.ts rename to x-pack/plugins/beats_management/public/lib/tags.ts index 18fbb0d3143d1..e204585e85698 100644 --- a/x-pack/plugins/beats_management/public/lib/domains/tags.ts +++ b/x-pack/plugins/beats_management/public/lib/tags.ts @@ -5,9 +5,9 @@ */ import yaml from 'js-yaml'; import { omit, pick } from 'lodash'; -import { BeatTag, ConfigurationBlock } from '../../../common/domain_types'; -import { CMTagsAdapter } from '../adapters/tags/adapter_types'; -import { ConfigContent } from './../../../common/domain_types'; +import { BeatTag, ConfigurationBlock } from '../../common/domain_types'; +import { ConfigContent } from '../../common/domain_types'; +import { CMTagsAdapter } from './adapters/tags/adapter_types'; export class TagsLib { constructor(private readonly adapter: CMTagsAdapter, private readonly tagConfigs: any) {} @@ -38,7 +38,7 @@ export class TagsLib { // NOTE: The perk of this, is that as we support more features via controls // vs yaml editing, it should "just work", and things that were in YAML // will now be in the UI forms... - transformedTag.configuration_blocks = tag.configuration_blocks.map(block => { + transformedTag.configuration_blocks = (tag.configuration_blocks || []).map(block => { const { type, description, configs } = block; const activeConfig = configs[0]; const thisConfig = this.tagConfigs.find((conf: any) => conf.value === type).config; @@ -72,7 +72,7 @@ export class TagsLib { // configurations is the JS representation of the config yaml, // so here we take that JS and convert it into a YAML string. // we do so while also flattening "other" into the flat yaml beats expect - transformedTag.configuration_blocks = tag.configuration_blocks.map(block => { + transformedTag.configuration_blocks = (tag.configuration_blocks || []).map(block => { const { type, description, configs } = block; const activeConfig = configs[0]; const thisConfig = this.tagConfigs.find((conf: any) => conf.value === type).config; diff --git a/x-pack/plugins/beats_management/public/pages/beat/detail.tsx b/x-pack/plugins/beats_management/public/pages/beat/detail.tsx index 915cbb9d1f91a..1a5d260b89d93 100644 --- a/x-pack/plugins/beats_management/public/pages/beat/detail.tsx +++ b/x-pack/plugins/beats_management/public/pages/beat/detail.tsx @@ -32,6 +32,7 @@ export const BeatDetailPage = (props: BeatDetailPageProps) => { } const configurationBlocks = flatten( beat.full_tags.map((tag: BeatTag) => { + return tag.configuration_blocks.map(configuration => ({ // @ts-ignore one of the types on ConfigurationBlock doesn't define a "module" property module: configuration.configs[0].module || null, diff --git a/x-pack/plugins/beats_management/public/pages/beat/index.tsx b/x-pack/plugins/beats_management/public/pages/beat/index.tsx index 14ae90609b648..ef08dcdd7bb48 100644 --- a/x-pack/plugins/beats_management/public/pages/beat/index.tsx +++ b/x-pack/plugins/beats_management/public/pages/beat/index.tsx @@ -14,7 +14,9 @@ import { import React from 'react'; import { Route, Switch } from 'react-router-dom'; import { CMPopulatedBeat } from '../../../common/domain_types'; +import { AppURLState } from '../../app'; import { PrimaryLayout } from '../../components/layouts/primary'; +import { URLStateProps, withUrlState } from '../../containers/with_url_state'; import { FrontendLibs } from '../../lib/lib'; import { BeatDetailsActionSection } from './action_section'; import { BeatActivityPage } from './activity'; @@ -25,7 +27,8 @@ interface Match { params: any; } -interface BeatDetailsPageProps { +interface BeatDetailsPageProps extends URLStateProps { + location: any; history: any; libs: FrontendLibs; match: Match; @@ -37,7 +40,7 @@ interface BeatDetailsPageState { isLoading: boolean; } -export class BeatDetailsPage extends React.PureComponent< +class BeatDetailsPageComponent extends React.PureComponent< BeatDetailsPageProps, BeatDetailsPageState > { @@ -53,7 +56,10 @@ export class BeatDetailsPage extends React.PureComponent< } public onSelectedTabChanged = (id: string) => { - this.props.history.push(id); + this.props.history.push({ + pathname: id, + search: this.props.location.search, + }); }; public render() { @@ -72,11 +78,11 @@ export class BeatDetailsPage extends React.PureComponent< name: 'Config', disabled: false, }, - { - id: `/beat/${id}/activity`, - name: 'Beat Activity', - disabled: false, - }, + // { + // id: `/beat/${id}/activity`, + // name: 'Beat Activity', + // disabled: false, + // }, { id: `/beat/${id}/tags`, name: 'Tags', @@ -93,7 +99,10 @@ export class BeatDetailsPage extends React.PureComponent< key={index} isSelected={tab.id === this.props.history.location.pathname} onClick={() => { - this.props.history.push(tab.id); + this.props.history.push({ + pathname: tab.id, + search: this.props.location.search, + }); }} > {tab.name} @@ -142,3 +151,4 @@ export class BeatDetailsPage extends React.PureComponent< this.setState({ beat, isLoading: false }); } } +export const BeatDetailsPage = withUrlState(BeatDetailsPageComponent); diff --git a/x-pack/plugins/beats_management/public/pages/main/beats.tsx b/x-pack/plugins/beats_management/public/pages/main/beats.tsx index 52d9091048a8e..d524e6684218a 100644 --- a/x-pack/plugins/beats_management/public/pages/main/beats.tsx +++ b/x-pack/plugins/beats_management/public/pages/main/beats.tsx @@ -10,12 +10,15 @@ import moment from 'moment'; import React from 'react'; import { BeatTag, CMPopulatedBeat } from '../../../common/domain_types'; import { BeatsTagAssignment } from '../../../server/lib/adapters/beats/adapter_types'; +import { AppURLState } from '../../app'; import { BeatsTableType, Table } from '../../components/table'; import { TagAssignment } from '../../components/tag'; +import { WithKueryAutocompletion } from '../../containers/with_kuery_autocompletion'; +import { URLStateProps } from '../../containers/with_url_state'; import { FrontendLibs } from '../../lib/lib'; import { BeatsActionArea } from './beats_action_area'; -interface BeatsPageProps { +interface BeatsPageProps extends URLStateProps { libs: FrontendLibs; location: any; } @@ -44,6 +47,7 @@ export class BeatsPage extends React.PureComponent -
+ + {autocompleteProps => ( +
this.props.setUrlState({ beatsKBar: value })} // todo + onKueryBarSubmit={() => null} // todo + filterQueryDraft={'false'} // todo + actionHandler={this.handleBeatsActions} + assignmentOptions={this.state.tags} + assignmentTitle="Set tags" + items={sortBy(this.state.beats, 'id') || []} + ref={this.state.tableRef} + showAssignmentOptions={true} + renderAssignmentOptions={this.renderTagAssignment} + type={BeatsTableType} + /> + )} + + this.setState({ notifications: [] })} @@ -116,7 +133,14 @@ export class BeatsPage extends React.PureComponent { +interface BeatsProps extends URLStateProps { + match: any + libs: FrontendLibs; +} +export class BeatsActionArea extends React.Component { private pinging = false - constructor(props: any) { + constructor(props: BeatsProps) { super(props) this.state = { @@ -55,108 +60,111 @@ export class BeatsActionArea extends React.Component { this.pinging = false } public render() { + const { match, - history, + goTo, libs, - } = this.props + } = this.props; return ( -
- { - window.alert('This will later go to more general beats install instructions.'); - window.location.href = '#/home/tutorial/dockerMetrics'; - }} - > - Learn how to install beats - - { - const token = await libs.tokens.createEnrollmentToken(); - history.push(`/overview/beats/enroll/${token}`); - this.waitForToken(token); - }} - > - Enroll Beats - +
+ { + // random, but spacific number ensures new tab does not overwrite another _newtab in chrome + // and at the same time not truly random so that many clicks of the link open many tabs at this same URL + window.open('https://www.elastic.co/guide/en/beats/libbeat/current/getting-started.html','_newtab35628937456'); + }} + > + Learn how to install beats + + { + const token = await libs.tokens.createEnrollmentToken(); + this.props.goTo(`/overview/beats/enroll/${token}`); + this.waitForToken(token); + }} + > + Enroll Beats + - {match.params.enrollmentToken != null && ( - - { - this.pinging = false; - this.setState({ - enrolledBeat: null - }, () => history.push('/overview/beats')) - }} style={{ width: '640px' }}> - - Enroll a new Beat - - {!this.state.enrolledBeat && ( - - To enroll a Beat with Centeral Management, run this command on the host that has Beats - installed. -
-
-
-
-
- $ beats enroll {window.location.protocol}//{window.location.host} {match.params.enrollmentToken} + {match.params.enrollmentToken != null && ( + + { + this.pinging = false; + this.setState({ + enrolledBeat: null + }, () => goTo(`/overview/beats`)) + }} style={{ width: '640px' }}> + + Enroll a new Beat + + {!this.state.enrolledBeat && ( + + To enroll a Beat with Centeral Management, run this command on the host that has Beats + installed. +
+
+
+
+
+ $ beats enroll {window.location.protocol}//{window.location.host} {match.params.enrollmentToken} +
-
-
-
- -
-
- Waiting for enroll command to be run... +
+
+ +
+
+ Waiting for enroll command to be run... - - )} - {this.state.enrolledBeat && ( - - A Beat was enrolled with the following data: -
-
-
- -
-
- { - this.setState({ - enrolledBeat: null - }) - const token = await libs.tokens.createEnrollmentToken(); - history.push(`/overview/beats/enroll/${token}`); - this.waitForToken(token); - }} - > - Enroll Another Beat - -
- )} + + )} + {this.state.enrolledBeat && ( + + A Beat was enrolled with the following data: +
+
+
+ +
+
+ { + this.setState({ + enrolledBeat: null + }) + const token = await libs.tokens.createEnrollmentToken(); + goTo(`/overview/beats/enroll/${token}`) + this.waitForToken(token); + }} + > + Enroll Another Beat + +
+ )} - - - )} -
-)}} \ No newline at end of file +
+
+ )} +
+ )} +} \ No newline at end of file diff --git a/x-pack/plugins/beats_management/public/pages/main/index.tsx b/x-pack/plugins/beats_management/public/pages/main/index.tsx index dfac6ac994e2f..989af24d0a0e2 100644 --- a/x-pack/plugins/beats_management/public/pages/main/index.tsx +++ b/x-pack/plugins/beats_management/public/pages/main/index.tsx @@ -12,15 +12,17 @@ import { } from '@elastic/eui'; import React from 'react'; import { Route, Switch } from 'react-router-dom'; +import { AppURLState } from '../../app'; import { PrimaryLayout } from '../../components/layouts/primary'; +import { URLStateProps, withUrlState } from '../../containers/with_url_state'; import { FrontendLibs } from '../../lib/lib'; import { ActivityPage } from './activity'; import { BeatsPage } from './beats'; import { TagsPage } from './tags'; -interface MainPagesProps { - history: any; +interface MainPagesProps extends URLStateProps { libs: FrontendLibs; + location: any; } interface MainPagesState { @@ -29,13 +31,12 @@ interface MainPagesState { } | null; } -export class MainPages extends React.PureComponent { - constructor(props: any) { +class MainPagesComponent extends React.PureComponent { + constructor(props: MainPagesProps) { super(props); } - public onSelectedTabChanged = (id: string) => { - this.props.history.push(id); + this.props.goTo(id); }; public render() { @@ -45,11 +46,11 @@ export class MainPages extends React.PureComponent ( this.onSelectedTabChanged(tab.id)} - isSelected={tab.id === this.props.history.location.pathname} + isSelected={tab.id === this.props.location.pathname} disabled={tab.disabled} key={index} > {tab.name} )); + return ( } + render={(props: any) => ( + + )} /> } + render={(props: any) => ( + + )} /> } @@ -88,20 +94,24 @@ export class MainPages extends React.PureComponent } + render={(props: any) => } /> } + render={(props: any) => ( + + )} /> } + render={(props: any) => } /> ); } } + +export const MainPages = withUrlState(MainPagesComponent); diff --git a/x-pack/plugins/beats_management/public/pages/main/tags.tsx b/x-pack/plugins/beats_management/public/pages/main/tags.tsx index 90e8ca88ac7c0..08f811303df5f 100644 --- a/x-pack/plugins/beats_management/public/pages/main/tags.tsx +++ b/x-pack/plugins/beats_management/public/pages/main/tags.tsx @@ -16,10 +16,13 @@ import { import React from 'react'; import { BeatTag, CMBeat } from '../../../common/domain_types'; import { BeatsTagAssignment } from '../../../server/lib/adapters/beats/adapter_types'; +import { AppURLState } from '../../app'; import { Table, TagsTableType } from '../../components/table'; +import { WithKueryAutocompletion } from '../../containers/with_kuery_autocompletion'; +import { URLStateProps } from '../../containers/with_url_state'; import { FrontendLibs } from '../../lib/lib'; -interface TagsPageProps { +interface TagsPageProps extends URLStateProps { libs: FrontendLibs; } @@ -29,12 +32,12 @@ interface TagsPageState { } export class TagsPage extends React.PureComponent { - public static ActionArea = ({ history }: any) => ( + public static ActionArea = ({ goTo }: TagsPageProps) => ( { - history.push(`/tag/create`); + goTo('/tag/create'); }} > Add Tag @@ -55,16 +58,24 @@ export class TagsPage extends React.PureComponent public render() { return ( -
item} - ref={this.tableRef} - showAssignmentOptions={true} - type={TagsTableType} - /> + + {autocompleteProps => ( +
this.props.setUrlState({ tagsKBar: value })} // todo + onKueryBarSubmit={() => null} // todo + filterQueryDraft={'false'} // todo + actionHandler={this.handleTagsAction} + items={this.state.tags || []} + renderAssignmentOptions={item => item} + ref={this.tableRef} + showAssignmentOptions={true} + type={TagsTableType} + /> + )} + ); } diff --git a/x-pack/plugins/beats_management/public/pages/tag/index.tsx b/x-pack/plugins/beats_management/public/pages/tag/index.tsx index f5e65493d3241..8b288db28a18e 100644 --- a/x-pack/plugins/beats_management/public/pages/tag/index.tsx +++ b/x-pack/plugins/beats_management/public/pages/tag/index.tsx @@ -10,13 +10,14 @@ import 'brace/mode/yaml'; import 'brace/theme/github'; import React from 'react'; import { BeatTag, CMPopulatedBeat } from '../../../common/domain_types'; +import { AppURLState } from '../../app'; import { PrimaryLayout } from '../../components/layouts/primary'; import { TagEdit } from '../../components/tag'; +import { URLStateProps, withUrlState } from '../../containers/with_url_state'; import { FrontendLibs } from '../../lib/lib'; -interface TagPageProps { +interface TagPageProps extends URLStateProps { libs: FrontendLibs; - history: any; match: any; } @@ -26,7 +27,7 @@ interface TagPageState { tag: BeatTag; } -export class TagPage extends React.PureComponent { +export class TagPageComponent extends React.PureComponent { private mode: 'edit' | 'create' = 'create'; constructor(props: TagPageProps) { super(props); @@ -78,7 +79,7 @@ export class TagPage extends React.PureComponent { - this.props.history.push('/overview/tags')}> + this.props.goTo('/overview/tags')}> Cancel @@ -106,6 +107,7 @@ export class TagPage extends React.PureComponent { }; private saveTag = async () => { await this.props.libs.tags.upsertTag(this.state.tag as BeatTag); - this.props.history.push('/overview/tags'); + this.props.goTo(`/overview/tags`); }; } +export const TagPage = withUrlState(TagPageComponent); diff --git a/x-pack/plugins/beats_management/public/router.tsx b/x-pack/plugins/beats_management/public/router.tsx index 03faf8f2d07a4..ef4b03c2e439e 100644 --- a/x-pack/plugins/beats_management/public/router.tsx +++ b/x-pack/plugins/beats_management/public/router.tsx @@ -7,29 +7,45 @@ import React from 'react'; import { HashRouter, Redirect, Route, Switch } from 'react-router-dom'; +import { Header } from './components/layouts/header'; +import { FrontendLibs } from './lib/lib'; import { BeatDetailsPage } from './pages/beat'; import { MainPages } from './pages/main'; import { TagPage } from './pages/tag'; -export const PageRouter: React.SFC<{ libs: any }> = ({ libs }) => { +export const PageRouter: React.SFC<{ libs: FrontendLibs }> = ({ libs }) => { return ( - - } +
+
- } /> - } - /> - } - /> - + + } + /> + } /> + } + /> + } + /> + +
); }; diff --git a/x-pack/plugins/beats_management/public/utils/typed_react.ts b/x-pack/plugins/beats_management/public/utils/typed_react.ts new file mode 100644 index 0000000000000..5557befa9d7e5 --- /dev/null +++ b/x-pack/plugins/beats_management/public/utils/typed_react.ts @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { omit } from 'lodash'; +import React from 'react'; +import { InferableComponentEnhancerWithProps } from 'react-redux'; + +export type RendererResult = React.ReactElement | null; +export type RendererFunction = (args: RenderArgs) => Result; + +export type ChildFunctionRendererProps = { + children: RendererFunction; + initializeOnMount?: boolean; + resetOnUnmount?: boolean; +} & RenderArgs; + +interface ChildFunctionRendererOptions { + onInitialize?: (props: RenderArgs) => void; + onCleanup?: (props: RenderArgs) => void; +} + +export const asChildFunctionRenderer = ( + hoc: InferableComponentEnhancerWithProps, + { onInitialize, onCleanup }: ChildFunctionRendererOptions = {} +) => + hoc( + class ChildFunctionRenderer extends React.Component> { + public displayName = 'ChildFunctionRenderer'; + + public componentDidMount() { + if (this.props.initializeOnMount && onInitialize) { + onInitialize(this.getRendererArgs()); + } + } + + public componentWillUnmount() { + if (this.props.resetOnUnmount && onCleanup) { + onCleanup(this.getRendererArgs()); + } + } + + public render() { + return this.props.children(this.getRendererArgs()); + } + + private getRendererArgs = () => + omit(['children', 'initializeOnMount', 'resetOnUnmount'], this.props) as Pick< + ChildFunctionRendererProps, + keyof InjectedProps + >; + } + ); + +export type StateUpdater = ( + prevState: Readonly, + prevProps: Readonly +) => State | null; + +export function composeStateUpdaters(...updaters: Array>) { + return (state: State, props: Props) => + updaters.reduce((currentState, updater) => updater(currentState, props) || currentState, state); +} diff --git a/x-pack/plugins/beats_management/server/lib/adapters/beats/adapter_types.ts b/x-pack/plugins/beats_management/server/lib/adapters/beats/adapter_types.ts index c8d96a77df9f7..74c778fbe5672 100644 --- a/x-pack/plugins/beats_management/server/lib/adapters/beats/adapter_types.ts +++ b/x-pack/plugins/beats_management/server/lib/adapters/beats/adapter_types.ts @@ -11,7 +11,7 @@ export interface CMBeatsAdapter { insert(user: FrameworkUser, beat: CMBeat): Promise; update(user: FrameworkUser, beat: CMBeat): Promise; get(user: FrameworkUser, id: string): Promise; - getAll(user: FrameworkUser): Promise; + getAll(user: FrameworkUser, ESQuery?: any): Promise; getWithIds(user: FrameworkUser, beatIds: string[]): Promise; getAllWithTags(user: FrameworkUser, tagIds: string[]): Promise; getBeatWithToken(user: FrameworkUser, enrollmentToken: string): Promise; diff --git a/x-pack/plugins/beats_management/server/lib/adapters/beats/elasticsearch_beats_adapter.ts b/x-pack/plugins/beats_management/server/lib/adapters/beats/elasticsearch_beats_adapter.ts index c7f728c9c00d8..66fd00a300fa9 100644 --- a/x-pack/plugins/beats_management/server/lib/adapters/beats/elasticsearch_beats_adapter.ts +++ b/x-pack/plugins/beats_management/server/lib/adapters/beats/elasticsearch_beats_adapter.ts @@ -131,14 +131,40 @@ export class ElasticsearchBeatsAdapter implements CMBeatsAdapter { return omit(_get(beats[0], '_source.beat'), ['access_token']); } - public async getAll(user: FrameworkUser) { + public async getAll(user: FrameworkUser, ESQuery?: any) { const params = { index: INDEX_NAMES.BEATS, - q: 'type:beat', size: 10000, type: '_doc', + body: { + query: { + bool: { + must: { + term: { + type: 'beat', + }, + }, + }, + }, + }, }; - const response = await this.database.search(user, params); + + if (ESQuery) { + params.body.query = { + ...params.body.query, + ...ESQuery, + }; + } + + let response; + try { + response = await this.database.search(user, params); + } catch (e) { + // TODO something + } + if (!response) { + return []; + } const beats = _get(response, 'hits.hits', []); return beats.map((beat: any) => omit(beat._source.beat, ['access_token'])); diff --git a/x-pack/plugins/beats_management/server/lib/adapters/tags/adapter_types.ts b/x-pack/plugins/beats_management/server/lib/adapters/tags/adapter_types.ts index a5e01541ce386..f826115f9efd9 100644 --- a/x-pack/plugins/beats_management/server/lib/adapters/tags/adapter_types.ts +++ b/x-pack/plugins/beats_management/server/lib/adapters/tags/adapter_types.ts @@ -7,7 +7,7 @@ import { BeatTag } from '../../../../common/domain_types'; import { FrameworkUser } from '../framework/adapter_types'; export interface CMTagsAdapter { - getAll(user: FrameworkUser): Promise; + getAll(user: FrameworkUser, ESQuery?: any): Promise; delete(user: FrameworkUser, tagIds: string[]): Promise; getTagsWithIds(user: FrameworkUser, tagIds: string[]): Promise; upsertTag(user: FrameworkUser, tag: BeatTag): Promise<{}>; diff --git a/x-pack/plugins/beats_management/server/lib/adapters/tags/elasticsearch_tags_adapter.ts b/x-pack/plugins/beats_management/server/lib/adapters/tags/elasticsearch_tags_adapter.ts index c53cd2836d9ba..d44f3915555ad 100644 --- a/x-pack/plugins/beats_management/server/lib/adapters/tags/elasticsearch_tags_adapter.ts +++ b/x-pack/plugins/beats_management/server/lib/adapters/tags/elasticsearch_tags_adapter.ts @@ -19,13 +19,29 @@ export class ElasticsearchTagsAdapter implements CMTagsAdapter { this.database = database; } - public async getAll(user: FrameworkUser) { + public async getAll(user: FrameworkUser, ESQuery?: any) { const params = { _source: true, index: INDEX_NAMES.BEATS, - q: 'type:tag', type: '_doc', + body: { + query: { + bool: { + must: { + term: { + type: 'tag', + }, + }, + }, + }, + }, }; + if (ESQuery) { + params.body.query = { + ...params.body.query, + ...ESQuery, + }; + } const response = await this.database.search(user, params); const tags = get(response, 'hits.hits', []); diff --git a/x-pack/plugins/beats_management/server/lib/domains/beats.ts b/x-pack/plugins/beats_management/server/lib/domains/beats.ts index 04c43440822bd..2b0fe09afff1d 100644 --- a/x-pack/plugins/beats_management/server/lib/domains/beats.ts +++ b/x-pack/plugins/beats_management/server/lib/domains/beats.ts @@ -40,8 +40,10 @@ export class CMBeatsDomain { return beat && beat.active ? beat : null; } - public async getAll(user: FrameworkUser) { - return (await this.adapter.getAll(user)).filter((beat: CMBeat) => beat.active === true); + public async getAll(user: FrameworkUser, ESQuery?: any) { + return (await this.adapter.getAll(user, ESQuery)).filter( + (beat: CMBeat) => beat.active === true + ); } public async getAllWithTag(user: FrameworkUser, tagId: string) { diff --git a/x-pack/plugins/beats_management/server/lib/domains/tags.ts b/x-pack/plugins/beats_management/server/lib/domains/tags.ts index 192b16c0b9ed9..79ff2007d1160 100644 --- a/x-pack/plugins/beats_management/server/lib/domains/tags.ts +++ b/x-pack/plugins/beats_management/server/lib/domains/tags.ts @@ -15,8 +15,8 @@ import { CMTagsAdapter } from '../adapters/tags/adapter_types'; export class CMTagsDomain { constructor(private readonly adapter: CMTagsAdapter) {} - public async getAll(user: FrameworkUser) { - return await this.adapter.getAll(user); + public async getAll(user: FrameworkUser, ESQuery?: any) { + return await this.adapter.getAll(user, ESQuery); } public async getTagsWithIds(user: FrameworkUser, tagIds: string[]) { diff --git a/x-pack/plugins/beats_management/server/rest_api/beats/list.ts b/x-pack/plugins/beats_management/server/rest_api/beats/list.ts index b0ec9fc30526a..cdaaf05679a33 100644 --- a/x-pack/plugins/beats_management/server/rest_api/beats/list.ts +++ b/x-pack/plugins/beats_management/server/rest_api/beats/list.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import * as Joi from 'joi'; import { CMBeat } from '../../../common/domain_types'; import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types'; import { CMServerLibs } from '../../lib/lib'; @@ -12,6 +13,16 @@ import { wrapEsError } from '../../utils/error_wrappers'; export const createListAgentsRoute = (libs: CMServerLibs) => ({ method: 'GET', path: '/api/beats/agents/{listByAndValue*}', + validate: { + headers: Joi.object({ + 'kbn-beats-enrollment-token': Joi.string().required(), + }).options({ + allowUnknown: true, + }), + query: Joi.object({ + ESQuery: Joi.string(), + }), + }, licenseRequired: true, handler: async (request: FrameworkRequest, reply: any) => { const listByAndValueParts = request.params.listByAndValue @@ -27,13 +38,17 @@ export const createListAgentsRoute = (libs: CMServerLibs) => ({ try { let beats: CMBeat[]; + switch (listBy) { case 'tag': beats = await libs.beats.getAllWithTag(request.user, listByValue || ''); break; default: - beats = await libs.beats.getAll(request.user); + beats = await libs.beats.getAll( + request.user, + request.query && request.query.ESQuery ? JSON.parse(request.query.ESQuery) : undefined + ); break; } diff --git a/x-pack/plugins/beats_management/server/rest_api/tags/list.ts b/x-pack/plugins/beats_management/server/rest_api/tags/list.ts index 63ab0d5b52e7e..6ef3f70c7f6f2 100644 --- a/x-pack/plugins/beats_management/server/rest_api/tags/list.ts +++ b/x-pack/plugins/beats_management/server/rest_api/tags/list.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import * as Joi from 'joi'; import { BeatTag } from '../../../common/domain_types'; import { CMServerLibs } from '../../lib/lib'; import { wrapEsError } from '../../utils/error_wrappers'; @@ -11,11 +12,24 @@ import { wrapEsError } from '../../utils/error_wrappers'; export const createListTagsRoute = (libs: CMServerLibs) => ({ method: 'GET', path: '/api/beats/tags', + validate: { + headers: Joi.object({ + 'kbn-beats-enrollment-token': Joi.string().required(), + }).options({ + allowUnknown: true, + }), + query: Joi.object({ + ESQuery: Joi.string(), + }), + }, licenseRequired: true, handler: async (request: any, reply: any) => { let tags: BeatTag[]; try { - tags = await libs.tags.getAll(request.user); + tags = await libs.tags.getAll( + request.user + // request.query ? JSON.parse(request.query.ESQuery) : undefined + ); } catch (err) { return reply(wrapEsError(err)); } diff --git a/x-pack/plugins/beats_management/types/eui.d.ts b/x-pack/plugins/beats_management/types/eui.d.ts new file mode 100644 index 0000000000000..289e77bbdb04f --- /dev/null +++ b/x-pack/plugins/beats_management/types/eui.d.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/** + * /!\ These type definitions are temporary until the upstream @elastic/eui + * package includes them. + */ + +import { EuiToolTipPosition } from '@elastic/eui'; +import { Moment } from 'moment'; +import { ChangeEventHandler, MouseEventHandler, ReactType, Ref, SFC } from 'react'; + +declare module '@elastic/eui' { + export interface EuiBreadcrumbDefinition { + text: React.ReactNode; + href?: string; + onClick?: React.MouseEventHandler; + } + type EuiBreadcrumbsProps = CommonProps & { + responsive?: boolean; + truncate?: boolean; + max?: number; + breadcrumbs: EuiBreadcrumbDefinition[]; + }; + + type EuiHeaderProps = CommonProps; + export const EuiHeader: React.SFC; + + export type EuiHeaderSectionSide = 'left' | 'right'; + type EuiHeaderSectionProps = CommonProps & { + side?: EuiHeaderSectionSide; + }; + export const EuiHeaderSection: React.SFC; + + type EuiHeaderBreadcrumbsProps = EuiBreadcrumbsProps; + export const EuiHeaderBreadcrumbs: React.SFC; + + interface EuiOutsideClickDetectorProps { + children: React.ReactNode; + isDisabled?: boolean; + onOutsideClick: React.MouseEventHandler; + } + export const EuiOutsideClickDetector: React.SFC; +} diff --git a/x-pack/plugins/beats_management/types/kibana.d.ts b/x-pack/plugins/beats_management/types/kibana.d.ts new file mode 100644 index 0000000000000..e95dc0df93bea --- /dev/null +++ b/x-pack/plugins/beats_management/types/kibana.d.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +declare module 'ui/index_patterns' { + export type IndexPattern = any; + + export interface StaticIndexPatternField { + name: string; + type: string; + aggregatable: boolean; + searchable: boolean; + } + + export interface StaticIndexPattern { + fields: StaticIndexPatternField[]; + title: string; + } +} + +declare module 'ui/autocomplete_providers' { + import { StaticIndexPattern } from 'ui/index_patterns'; + + export type AutocompleteProvider = ( + args: { + config: { + get(configKey: string): any; + }; + indexPatterns: StaticIndexPattern[]; + boolFilter: any; + } + ) => GetSuggestions; + + export type GetSuggestions = ( + args: { + query: string; + selectionStart: number; + selectionEnd: number; + } + ) => Promise; + + export type AutocompleteSuggestionType = 'field' | 'value' | 'operator' | 'conjunction'; + + export interface AutocompleteSuggestion { + description: string; + end: number; + start: number; + text: string; + type: AutocompleteSuggestionType; + } + + export function addAutocompleteProvider(language: string, provider: AutocompleteProvider): void; + + export function getAutocompleteProvider(language: string): AutocompleteProvider | undefined; +} From e85d867ad82b65601ee846ba581c4f4c0d7c92dc Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Thu, 4 Oct 2018 09:55:15 -0400 Subject: [PATCH 52/94] Add walkthrough (#23785) * add test system, framework, and example tests * adding walkthrough * clicking walkthrough step should take you to that step * try/catch beats loading to prevent unhandeled error when there are no beats * fix walkthrough not redirecting to main beats page --- .../plugins/beats_management/public/app.d.ts | 6 +- .../public/components/layouts/no_data.tsx | 62 +++++ .../public/components/layouts/walkthrough.tsx | 61 +++++ .../public/components/tag/tag_edit.tsx | 21 +- .../public/containers/with_url_state.tsx | 11 +- .../elasticsearch/{test.ts => memory.ts} | 15 +- .../public/lib/compose/memory.ts | 15 +- .../public/pages/main/beats.tsx | 101 ++++--- .../public/pages/main/create_tag_fragment.tsx | 101 +++++++ ...ts_action_area.tsx => enroll_fragment.tsx} | 122 +++++---- .../public/pages/main/index.tsx | 135 +++++++++- .../public/pages/main/tags.tsx | 6 +- .../public/pages/main/walkthrough_review.tsx | 106 ++++++++ .../adapters/beats/memory_beats_adapter.ts | 4 + .../database/memory_database_adapter.ts | 69 +++++ ...k_adapter.ts => hapi_framework_adapter.ts} | 43 ++- .../lib/adapters/tags/memory_tags_adapter.ts | 4 + .../adapters/tokens/memory_tokens_adapter.ts | 4 + .../server/lib/compose/testing.ts | 24 +- .../__tests__/beats/assign_tags.test.ts | 4 +- .../domains/__tests__/beats/enroll.test.ts | 4 +- .../domains/__tests__/beats/update.test.ts | 4 +- .../lib/domains/__tests__/tokens.test.ts | 4 +- .../__tests__/beats_assignments.test.ts | 253 ++++++++++++++++++ .../__tests__/beats_configurations.test.ts | 5 + .../rest_api/__tests__/beats_enroll.test.ts | 5 + .../rest_api/__tests__/beats_get.test.ts | 5 + .../rest_api/__tests__/beats_list.test.ts | 5 + .../rest_api/__tests__/beats_removals.test.ts | 5 + .../rest_api/__tests__/beats_updates.test.ts | 5 + .../server/rest_api/__tests__/data.json | 158 +++++++++++ .../server/rest_api/__tests__/test_harnes.ts | 102 +++++++ x-pack/plugins/beats_management/wallaby.js | 6 +- .../api_integration/apis/beats/list_beats.js | 12 +- 34 files changed, 1327 insertions(+), 160 deletions(-) create mode 100644 x-pack/plugins/beats_management/public/components/layouts/no_data.tsx create mode 100644 x-pack/plugins/beats_management/public/components/layouts/walkthrough.tsx rename x-pack/plugins/beats_management/public/lib/adapters/elasticsearch/{test.ts => memory.ts} (55%) create mode 100644 x-pack/plugins/beats_management/public/pages/main/create_tag_fragment.tsx rename x-pack/plugins/beats_management/public/pages/main/{beats_action_area.tsx => enroll_fragment.tsx} (62%) create mode 100644 x-pack/plugins/beats_management/public/pages/main/walkthrough_review.tsx create mode 100644 x-pack/plugins/beats_management/server/lib/adapters/database/memory_database_adapter.ts rename x-pack/plugins/beats_management/server/lib/adapters/framework/{testing_framework_adapter.ts => hapi_framework_adapter.ts} (55%) create mode 100644 x-pack/plugins/beats_management/server/rest_api/__tests__/beats_assignments.test.ts create mode 100644 x-pack/plugins/beats_management/server/rest_api/__tests__/beats_configurations.test.ts create mode 100644 x-pack/plugins/beats_management/server/rest_api/__tests__/beats_enroll.test.ts create mode 100644 x-pack/plugins/beats_management/server/rest_api/__tests__/beats_get.test.ts create mode 100644 x-pack/plugins/beats_management/server/rest_api/__tests__/beats_list.test.ts create mode 100644 x-pack/plugins/beats_management/server/rest_api/__tests__/beats_removals.test.ts create mode 100644 x-pack/plugins/beats_management/server/rest_api/__tests__/beats_updates.test.ts create mode 100644 x-pack/plugins/beats_management/server/rest_api/__tests__/data.json create mode 100644 x-pack/plugins/beats_management/server/rest_api/__tests__/test_harnes.ts diff --git a/x-pack/plugins/beats_management/public/app.d.ts b/x-pack/plugins/beats_management/public/app.d.ts index 736b6c2d59dcc..4e806e12d3843 100644 --- a/x-pack/plugins/beats_management/public/app.d.ts +++ b/x-pack/plugins/beats_management/public/app.d.ts @@ -7,6 +7,8 @@ export type FlatObject = { [Key in keyof T]: string }; export interface AppURLState { - beatsKBar: string; - tagsKBar: string; + beatsKBar?: string; + tagsKBar?: string; + enrollmentToken?: string; + createdTag?: string; } diff --git a/x-pack/plugins/beats_management/public/components/layouts/no_data.tsx b/x-pack/plugins/beats_management/public/components/layouts/no_data.tsx new file mode 100644 index 0000000000000..8f31b90ff507e --- /dev/null +++ b/x-pack/plugins/beats_management/public/components/layouts/no_data.tsx @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { withRouter } from 'react-router-dom'; + +import { + EuiEmptyPrompt, + EuiFlexGroup, + EuiFlexItem, + EuiModal, + EuiOverlayMask, + EuiPage, + EuiPageBody, + EuiPageContent, +} from '@elastic/eui'; + +interface LayoutProps { + title: string; + actionSection?: React.ReactNode; + modalRender?: () => React.ReactNode; + modalClosePath?: string; +} + +export const NoDataLayout: React.SFC = withRouter( + ({ actionSection, title, modalRender, modalClosePath, children, history }) => { + const modalContent = modalRender && modalRender(); + return ( + + + + + + {title}} + body={children} + actions={actionSection} + /> + + + + + {modalContent && ( + + { + history.push(modalClosePath); + }} + style={{ width: '640px' }} + > + {modalContent} + + + )} + + ); + } +) as any; diff --git a/x-pack/plugins/beats_management/public/components/layouts/walkthrough.tsx b/x-pack/plugins/beats_management/public/components/layouts/walkthrough.tsx new file mode 100644 index 0000000000000..32cfd4cb43316 --- /dev/null +++ b/x-pack/plugins/beats_management/public/components/layouts/walkthrough.tsx @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +import { + EuiPage, + EuiPageBody, + EuiPageContent, + EuiPageContentBody, + // @ts-ignore + EuiStepsHorizontal, + EuiTitle, +} from '@elastic/eui'; + +interface LayoutProps { + title: string; + goTo: (path: string) => any; + walkthroughSteps: Array<{ + id: string; + name: string; + disabled: boolean; + }>; + activePath: string; +} + +export const WalkthroughLayout: React.SFC = ({ + walkthroughSteps, + title, + activePath, + goTo, + children, +}) => { + const indexOfCurrent = walkthroughSteps.findIndex(step => activePath === step.id); + return ( + + + + +

{title}

+
+
+
+ ({ + title: step.name, + isComplete: i <= indexOfCurrent, + onClick: () => goTo(step.id), + }))} + /> +
+
+ {children} +
+
+
+ ); +}; diff --git a/x-pack/plugins/beats_management/public/components/tag/tag_edit.tsx b/x-pack/plugins/beats_management/public/components/tag/tag_edit.tsx index e682e5b34f7ee..2726a83ac1b61 100644 --- a/x-pack/plugins/beats_management/public/components/tag/tag_edit.tsx +++ b/x-pack/plugins/beats_management/public/components/tag/tag_edit.tsx @@ -62,26 +62,23 @@ export class TagEdit extends React.PureComponent { -

Define this tag

+

Tag details

- Tags will apply a set configuration to a group of beats. + Tags will apply the configurations below to all beats assigned this tag.
The tag type defines the options available.

- {/* - {tag.id ? tag.id : 'Tag name'} - */}
@@ -95,7 +92,7 @@ export class TagEdit extends React.PureComponent { /> {this.props.mode === 'create' && ( - + )} @@ -112,13 +109,13 @@ export class TagEdit extends React.PureComponent { > -

Configurations

+

Tag Configurations

- You can have multiple configurations applied to an individual tag. These - configurations can repeat or mix types as necessary. For example, you may utilize - three metricbeat configurations alongside one input and filebeat configuration. + Tags can contain multiple configurations. These configurations can repeat or mix + types as necessary. For example, you may utilize three metricbeat configurations + alongside one input and filebeat configuration.

@@ -148,7 +145,7 @@ export class TagEdit extends React.PureComponent { this.setState({ showFlyout: true }); }} > - Add a new configuration + Add configuration
diff --git a/x-pack/plugins/beats_management/public/containers/with_url_state.tsx b/x-pack/plugins/beats_management/public/containers/with_url_state.tsx index 384a42ce463b9..1630c2c20cda8 100644 --- a/x-pack/plugins/beats_management/public/containers/with_url_state.tsx +++ b/x-pack/plugins/beats_management/public/containers/with_url_state.tsx @@ -15,7 +15,10 @@ type StateCallback = (previousState: T) => T; export interface URLStateProps { goTo: (path: string) => void; setUrlState: ( - newState: FlatObject | StateCallback | Promise> + newState: + | Partial> + | StateCallback + | Promise> ) => void; urlState: URLState; } @@ -49,7 +52,10 @@ export class WithURLStateComponent extends React.Compon } private setURLState = async ( - state: FlatObject | StateCallback | Promise> + state: + | Partial> + | StateCallback + | Promise> ) => { let newState; const pastState = this.URLState; @@ -70,6 +76,7 @@ export class WithURLStateComponent extends React.Compon }; this.props.history.replace(newLocation); + this.forceUpdate(); }; private goTo = (path: string) => { diff --git a/x-pack/plugins/beats_management/public/lib/adapters/elasticsearch/test.ts b/x-pack/plugins/beats_management/public/lib/adapters/elasticsearch/memory.ts similarity index 55% rename from x-pack/plugins/beats_management/public/lib/adapters/elasticsearch/test.ts rename to x-pack/plugins/beats_management/public/lib/adapters/elasticsearch/memory.ts index 1d45378451f1d..1b918fb72c809 100644 --- a/x-pack/plugins/beats_management/public/lib/adapters/elasticsearch/test.ts +++ b/x-pack/plugins/beats_management/public/lib/adapters/elasticsearch/memory.ts @@ -7,14 +7,23 @@ import { AutocompleteSuggestion } from 'ui/autocomplete_providers'; import { ElasticsearchAdapter } from './adapter_types'; -export class TestElasticsearchAdapter implements ElasticsearchAdapter { +export class MemoryElasticsearchAdapter implements ElasticsearchAdapter { + constructor( + private readonly mockIsKueryValid: (kuery: string) => boolean, + private readonly mockKueryToEsQuery: (kuery: string) => string, + private readonly suggestions: AutocompleteSuggestion[] + ) {} + + public isKueryValid(kuery: string): boolean { + return this.mockIsKueryValid(kuery); + } public async convertKueryToEsQuery(kuery: string): Promise { - return 'foo'; + return this.mockKueryToEsQuery(kuery); } public async getSuggestions( kuery: string, selectionStart: any ): Promise { - return []; + return this.suggestions; } } diff --git a/x-pack/plugins/beats_management/public/lib/compose/memory.ts b/x-pack/plugins/beats_management/public/lib/compose/memory.ts index d96719ecfe129..adfba95df01d8 100644 --- a/x-pack/plugins/beats_management/public/lib/compose/memory.ts +++ b/x-pack/plugins/beats_management/public/lib/compose/memory.ts @@ -20,13 +20,22 @@ import { MemoryTokensAdapter } from '../adapters/tokens/memory_tokens_adapter'; import { BeatsLib } from '../beats'; import { FrontendDomainLibs, FrontendLibs } from '../lib'; +import { AutocompleteSuggestion } from 'ui/autocomplete_providers'; import { supportedConfigs } from '../../config_schemas'; import { TagsLib } from '../tags'; -import { TestElasticsearchAdapter } from './../adapters/elasticsearch/test'; +import { MemoryElasticsearchAdapter } from './../adapters/elasticsearch/memory'; import { ElasticsearchLib } from './../elasticsearch'; -export function compose(): FrontendLibs { - const esAdapter = new TestElasticsearchAdapter(); +export function compose( + mockIsKueryValid: (kuery: string) => boolean, + mockKueryToEsQuery: (kuery: string) => string, + suggestions: AutocompleteSuggestion[] +): FrontendLibs { + const esAdapter = new MemoryElasticsearchAdapter( + mockIsKueryValid, + mockKueryToEsQuery, + suggestions + ); const tags = new TagsLib(new MemoryTagsAdapter([]), supportedConfigs); const tokens = new MemoryTokensAdapter(); const beats = new BeatsLib(new MemoryBeatsAdapter([]), { tags }); diff --git a/x-pack/plugins/beats_management/public/pages/main/beats.tsx b/x-pack/plugins/beats_management/public/pages/main/beats.tsx index d524e6684218a..350fc439d00ed 100644 --- a/x-pack/plugins/beats_management/public/pages/main/beats.tsx +++ b/x-pack/plugins/beats_management/public/pages/main/beats.tsx @@ -4,10 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiGlobalToastList } from '@elastic/eui'; +import { + EuiButton, + EuiButtonEmpty, + EuiGlobalToastList, + EuiModal, + EuiModalHeader, + EuiModalHeaderTitle, + EuiOverlayMask, +} from '@elastic/eui'; import { sortBy } from 'lodash'; import moment from 'moment'; import React from 'react'; +import { RouteComponentProps } from 'react-router'; import { BeatTag, CMPopulatedBeat } from '../../../common/domain_types'; import { BeatsTagAssignment } from '../../../server/lib/adapters/beats/adapter_types'; import { AppURLState } from '../../app'; @@ -16,44 +25,80 @@ import { TagAssignment } from '../../components/tag'; import { WithKueryAutocompletion } from '../../containers/with_kuery_autocompletion'; import { URLStateProps } from '../../containers/with_url_state'; import { FrontendLibs } from '../../lib/lib'; -import { BeatsActionArea } from './beats_action_area'; +import { EnrollBeatPage } from './enroll_fragment'; interface BeatsPageProps extends URLStateProps { libs: FrontendLibs; location: any; + beats: CMPopulatedBeat[]; + loadBeats: () => any; } interface BeatsPageState { - beats: CMPopulatedBeat[]; notifications: any[]; tableRef: any; tags: any[] | null; } +interface ActionAreaProps extends URLStateProps, RouteComponentProps { + libs: FrontendLibs; +} + export class BeatsPage extends React.PureComponent { - public static ActionArea = BeatsActionArea; - private mounted: boolean = false; + public static ActionArea = (props: ActionAreaProps) => ( + + { + // random, but specific number ensures new tab does not overwrite another _newtab in chrome + // and at the same time not truly random so that many clicks of the link open many tabs at this same URL + window.open( + 'https://www.elastic.co/guide/en/beats/libbeat/current/getting-started.html', + '_newtab35628937456' + ); + }} + > + Learn how to install beats + + { + props.goTo(`/overview/beats/enroll`); + }} + > + Enroll Beats + + + {props.location.pathname === '/overview/beats/enroll' && ( + + { + props.goTo(`/overview/beats`); + }} + style={{ width: '640px' }} + > + + Enroll a new Beat + + + + + )} + + ); constructor(props: BeatsPageProps) { super(props); this.state = { - beats: [], notifications: [], tableRef: React.createRef(), tags: null, }; } - public componentDidMount() { - this.mounted = true; - this.loadBeats(); - } - public componentWillUnmount() { - this.mounted = false; - } public componentDidUpdate(prevProps: any) { if (this.props.location !== prevProps.location) { - this.loadBeats(); + this.props.loadBeats(); } } public render() { @@ -64,7 +109,7 @@ export class BeatsPage extends React.PureComponent this.props.setUrlState({ beatsKBar: value })} // todo @@ -73,7 +118,7 @@ export class BeatsPage extends React.PureComponent { @@ -128,26 +173,10 @@ export class BeatsPage extends React.PureComponent { - await this.loadBeats(); + await this.props.loadBeats(); }, 100); }; - private async loadBeats() { - let query; - if (this.props.urlState.beatsKBar) { - query = await this.props.libs.elasticsearch.convertKueryToEsQuery( - this.props.urlState.beatsKBar - ); - } - - const beats = await this.props.libs.beats.getAll(query); - if (this.mounted) { - this.setState({ - beats, - }); - } - } - // todo: add reference to ES filter endpoint private handleSearchQuery = (query: any) => { // await this.props.libs.beats.searach(query); @@ -202,7 +231,7 @@ export class BeatsPage extends React.PureComponent { await this.loadTags(); - await this.loadBeats(); + await this.props.loadBeats(); this.state.tableRef.current.setSelection(this.getSelectedBeats()); }; @@ -210,7 +239,7 @@ export class BeatsPage extends React.PureComponent beat.id); const beats: CMPopulatedBeat[] = []; selectedIds.forEach((id: any) => { - const beat: CMPopulatedBeat | undefined = this.state.beats.find(b => b.id === id); + const beat: CMPopulatedBeat | undefined = this.props.beats.find(b => b.id === id); if (beat) { beats.push(beat); } diff --git a/x-pack/plugins/beats_management/public/pages/main/create_tag_fragment.tsx b/x-pack/plugins/beats_management/public/pages/main/create_tag_fragment.tsx new file mode 100644 index 0000000000000..a8947a5628f64 --- /dev/null +++ b/x-pack/plugins/beats_management/public/pages/main/create_tag_fragment.tsx @@ -0,0 +1,101 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, EuiSpacer } from '@elastic/eui'; +import 'brace/mode/yaml'; + +import 'brace/theme/github'; +import React from 'react'; +import { BeatTag } from '../../../common/domain_types'; +import { AppURLState } from '../../app'; +import { TagEdit } from '../../components/tag'; +import { URLStateProps, withUrlState } from '../../containers/with_url_state'; +import { FrontendLibs } from '../../lib/lib'; + +interface TagPageProps extends URLStateProps { + libs: FrontendLibs; + match: any; +} + +interface TagPageState { + showFlyout: boolean; + tag: BeatTag; +} + +export class CreateTagFragment extends React.PureComponent { + private mode: 'edit' | 'create' = 'create'; + constructor(props: TagPageProps) { + super(props); + this.state = { + showFlyout: false, + tag: { + id: props.urlState.createdTag ? props.urlState.createdTag : '', + color: '#DD0A73', + configuration_blocks: [], + last_updated: new Date(), + }, + }; + + if (props.urlState.createdTag) { + this.mode = 'edit'; + this.loadTag(); + } + } + + public render() { + return ( + + + this.setState(oldState => ({ + tag: { ...oldState.tag, [field]: value }, + })) + } + attachedBeats={null} + /> + + + + + + + Save & Continue + + + + + ); + } + + private loadTag = async () => { + const tags = await this.props.libs.tags.getTagsWithIds([this.state.tag.id]); + if (tags.length > 0) { + this.setState({ + tag: tags[0], + }); + } + }; + + private saveTag = async () => { + const newTag = await this.props.libs.tags.upsertTag(this.state.tag as BeatTag); + if (!newTag) { + return alert('error saving tag'); + } + this.props.setUrlState({ + createdTag: newTag.id, + }); + this.props.goTo(`/overview/initial/review`); + }; +} +export const CreateTagPageFragment = withUrlState(CreateTagFragment); diff --git a/x-pack/plugins/beats_management/public/pages/main/beats_action_area.tsx b/x-pack/plugins/beats_management/public/pages/main/enroll_fragment.tsx similarity index 62% rename from x-pack/plugins/beats_management/public/pages/main/beats_action_area.tsx rename to x-pack/plugins/beats_management/public/pages/main/enroll_fragment.tsx index e5419184e6512..69ce792591f8e 100644 --- a/x-pack/plugins/beats_management/public/pages/main/beats_action_area.tsx +++ b/x-pack/plugins/beats_management/public/pages/main/enroll_fragment.tsx @@ -7,24 +7,20 @@ import { // @ts-ignore typings for EuiBasicTable not present in current version EuiBasicTable, EuiButton, - EuiButtonEmpty, EuiLoadingSpinner, - EuiModal, EuiModalBody, - EuiModalHeader, - EuiModalHeaderTitle, - EuiOverlayMask } from '@elastic/eui'; import React from 'react'; +import { RouteComponentProps } from 'react-router'; import { CMBeat } from '../../../common/domain_types'; import { AppURLState } from '../../app'; -import { URLStateProps } from '../../containers/with_url_state'; +import { URLStateProps, withUrlState } from '../../containers/with_url_state'; import { FrontendLibs } from '../../lib/lib'; -interface BeatsProps extends URLStateProps { +interface BeatsProps extends URLStateProps, RouteComponentProps { match: any libs: FrontendLibs; } -export class BeatsActionArea extends React.Component { +export class EnrollBeat extends React.Component { private pinging = false constructor(props: BeatsProps) { super(props) @@ -47,59 +43,57 @@ export class BeatsActionArea extends React.Component { } } public async componentDidMount() { - if(this.props.match.params.enrollmentToken) { - this.waitForToken(this.props.match.params.enrollmentToken) + if(!this.props.urlState.enrollmentToken) { + const enrollmentToken = await this.props.libs.tokens.createEnrollmentToken(); + this.props.setUrlState({ + enrollmentToken + }) } } public waitForToken = async (token: string) => { + if(this.pinging) { return } this.pinging = true; const enrolledBeat = await this.pingForBeatWithToken(this.props.libs, token) as CMBeat; + this.setState({ enrolledBeat }) this.pinging = false } public render() { - + if(this.props.urlState.enrollmentToken && !this.state.enrolledBeat) { + this.waitForToken(this.props.urlState.enrollmentToken) + } const { - match, goTo, - libs, } = this.props; + + const actions = []; + + switch(this.props.location.pathname) { + case '/overview/initial/beats': + actions.push({ + goTo: '/overview/initial/tag', + name: 'Continue' + }) + break + case '/overview/beats/enroll': + actions.push({ + goTo: '/overview/beats/enroll', + name: 'Enroll another Beat', + newToken: true + }) + actions.push({ + goTo: '/overview/beats', + name: 'Done', + clearToken: true + }) + break; + } return (
- { - // random, but spacific number ensures new tab does not overwrite another _newtab in chrome - // and at the same time not truly random so that many clicks of the link open many tabs at this same URL - window.open('https://www.elastic.co/guide/en/beats/libbeat/current/getting-started.html','_newtab35628937456'); - }} - > - Learn how to install beats - - { - const token = await libs.tokens.createEnrollmentToken(); - this.props.goTo(`/overview/beats/enroll/${token}`); - this.waitForToken(token); - }} - > - Enroll Beats - - - {match.params.enrollmentToken != null && ( - - { - this.pinging = false; - this.setState({ - enrolledBeat: null - }, () => goTo(`/overview/beats`)) - }} style={{ width: '640px' }}> - - Enroll a new Beat - + {this.props.urlState.enrollmentToken && ( +
{!this.state.enrolledBeat && ( To enroll a Beat with Centeral Management, run this command on the host that has Beats @@ -109,7 +103,7 @@ export class BeatsActionArea extends React.Component {
- $ beats enroll {window.location.protocol}//{window.location.host} {match.params.enrollmentToken} + $ beats enroll {window.location.protocol}//{window.location.host} {this.props.urlState.enrollmentToken}

@@ -145,26 +139,40 @@ export class BeatsActionArea extends React.Component { />

- ( + { - this.setState({ - enrolledBeat: null - }) - const token = await libs.tokens.createEnrollmentToken(); - goTo(`/overview/beats/enroll/${token}`) - this.waitForToken(token); + if(action.clearToken) { + this.props.setUrlState({enrollmentToken: ''}) + } + + if(action.newToken) { + const enrollmentToken = await this.props.libs.tokens.createEnrollmentToken(); + + this.props.setUrlState({enrollmentToken}); + return this.setState({ + enrolledBeat: null + }) + } + goTo(action.goTo) + }} > - Enroll Another Beat + {action.name} + ))} +
)} - - +
)}
)} -} \ No newline at end of file +} + +export const EnrollBeatPage = withUrlState(EnrollBeat); diff --git a/x-pack/plugins/beats_management/public/pages/main/index.tsx b/x-pack/plugins/beats_management/public/pages/main/index.tsx index 989af24d0a0e2..87604583a0af9 100644 --- a/x-pack/plugins/beats_management/public/pages/main/index.tsx +++ b/x-pack/plugins/beats_management/public/pages/main/index.tsx @@ -5,20 +5,29 @@ */ import { + EuiCode, // @ts-ignore EuiTab, // @ts-ignore EuiTabs, } from '@elastic/eui'; +import { EuiButton } from '@elastic/eui'; import React from 'react'; -import { Route, Switch } from 'react-router-dom'; +import { Redirect, Route, Switch } from 'react-router-dom'; +import { CMPopulatedBeat } from '../../../common/domain_types'; import { AppURLState } from '../../app'; +import { ConnectedLink } from '../../components/connected_link'; +import { NoDataLayout } from '../../components/layouts/no_data'; import { PrimaryLayout } from '../../components/layouts/primary'; +import { WalkthroughLayout } from '../../components/layouts/walkthrough'; import { URLStateProps, withUrlState } from '../../containers/with_url_state'; import { FrontendLibs } from '../../lib/lib'; import { ActivityPage } from './activity'; import { BeatsPage } from './beats'; +import { CreateTagPageFragment } from './create_tag_fragment'; +import { EnrollBeatPage } from './enroll_fragment'; import { TagsPage } from './tags'; +import { ReviewWalkthroughPage } from './walkthrough_review'; interface MainPagesProps extends URLStateProps { libs: FrontendLibs; @@ -29,17 +38,41 @@ interface MainPagesState { enrollBeat?: { enrollmentToken: string; } | null; + beats: CMPopulatedBeat[]; + loadedBeatsAtLeastOnce: boolean; } class MainPagesComponent extends React.PureComponent { + private mounted: boolean = false; + constructor(props: MainPagesProps) { super(props); + this.state = { + loadedBeatsAtLeastOnce: false, + beats: [], + }; } public onSelectedTabChanged = (id: string) => { this.props.goTo(id); }; + public componentDidMount() { + this.mounted = true; + this.loadBeats(); + } + + public componentWillUnmount() { + this.mounted = false; + } + public render() { + if ( + this.state.loadedBeatsAtLeastOnce && + this.state.beats.length === 0 && + !this.props.location.pathname.includes('/overview/initial') + ) { + return ; + } const tabs = [ { id: '/overview/beats', @@ -58,6 +91,74 @@ class MainPagesComponent extends React.PureComponent + + Enroll Beat + + + } + > +

+ You don’t have any Beat configured to use Central Management, click on{' '} + Enroll Beat to add one now. +

+ + ); + } + + if (this.props.location.pathname.includes('/overview/initial')) { + return ( + + + {walkthroughSteps.map(step => ( + ( + + )} + /> + ))} + + + ); + } + const renderedTabs = tabs.map((tab, index) => ( this.onSelectedTabChanged(tab.id)} @@ -94,7 +195,15 @@ class MainPagesComponent extends React.PureComponent } + render={(props: any) => ( + + )} /> ); } + + private loadBeats = async () => { + let query; + if (this.props.urlState.beatsKBar) { + query = await this.props.libs.elasticsearch.convertKueryToEsQuery( + this.props.urlState.beatsKBar + ); + } + + let beats: CMPopulatedBeat[]; + try { + beats = await this.props.libs.beats.getAll(query); + } catch (e) { + beats = []; + } + if (this.mounted) { + this.setState({ + loadedBeatsAtLeastOnce: true, + beats, + }); + } + }; } export const MainPages = withUrlState(MainPagesComponent); diff --git a/x-pack/plugins/beats_management/public/pages/main/tags.tsx b/x-pack/plugins/beats_management/public/pages/main/tags.tsx index 08f811303df5f..f561390b9a0aa 100644 --- a/x-pack/plugins/beats_management/public/pages/main/tags.tsx +++ b/x-pack/plugins/beats_management/public/pages/main/tags.tsx @@ -62,9 +62,11 @@ export class TagsPage extends React.PureComponent {autocompleteProps => (
this.props.setUrlState({ tagsKBar: value })} // todo + onKueryBarChange={(value: any) => this.props.setUrlState({ tagsKBar: value })} onKueryBarSubmit={() => null} // todo filterQueryDraft={'false'} // todo actionHandler={this.handleTagsAction} diff --git a/x-pack/plugins/beats_management/public/pages/main/walkthrough_review.tsx b/x-pack/plugins/beats_management/public/pages/main/walkthrough_review.tsx new file mode 100644 index 0000000000000..83d39d54389b1 --- /dev/null +++ b/x-pack/plugins/beats_management/public/pages/main/walkthrough_review.tsx @@ -0,0 +1,106 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { EuiButton, EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, EuiPageContent } from '@elastic/eui'; +import React from 'react'; +import { RouteComponentProps } from 'react-router'; +import { BeatTag, CMBeat } from '../../../common/domain_types'; +import { BeatsTagAssignment } from '../../../server/lib/adapters/beats/adapter_types'; +import { AppURLState } from '../../app'; +import { URLStateProps, withUrlState } from '../../containers/with_url_state'; +import { FrontendLibs } from '../../lib/lib'; +interface PageProps extends URLStateProps, RouteComponentProps { + loadBeats: any; + libs: FrontendLibs; +} +export class ReviewWalkthrough extends React.Component { + constructor(props: PageProps) { + super(props); + + this.state = { + assigned: false, + }; + } + + public componentDidMount() { + setTimeout(async () => { + await this.props.loadBeats(); + + const done = await this.assignTagToBeat(); + + if (done) { + this.setState({ + assigned: true, + }); + } + }, 300); + } + + public render() { + const { goTo } = this.props; + + return ( + + + + Congratulations!} + body={ + +

+ You have enrolled your first beat, and we have assigned your new tag with its + configurations to it +

+

Next Steps

+

All that is left to do is to start the beat you just enrolled.

+
+ } + actions={ + { + goTo('/overview/beats'); + }} + > + Done + + } + /> +
+
+
+ ); + } + + private createBeatTagAssignments = (beats: CMBeat[], tag: BeatTag): BeatsTagAssignment[] => + beats.map(({ id }) => ({ beatId: id, tag: tag.id })); + + private assignTagToBeat = async () => { + if (!this.props.urlState.enrollmentToken) { + return alert('Invalid URL, no enrollmentToken found'); + } + if (!this.props.urlState.createdTag) { + return alert('Invalid URL, no createdTag found'); + } + + const beat = await this.props.libs.beats.getBeatWithToken(this.props.urlState.enrollmentToken); + if (!beat) { + return alert('Error: Beat not enrolled properly'); + } + const tags = await this.props.libs.tags.getTagsWithIds([this.props.urlState.createdTag]); + + const assignments = this.createBeatTagAssignments([beat], tags[0]); + await this.props.libs.beats.assignTagsToBeats(assignments); + this.props.setUrlState({ + createdTag: '', + enrollmentToken: '', + }); + return true; + }; +} + +export const ReviewWalkthroughPage = withUrlState(ReviewWalkthrough); diff --git a/x-pack/plugins/beats_management/server/lib/adapters/beats/memory_beats_adapter.ts b/x-pack/plugins/beats_management/server/lib/adapters/beats/memory_beats_adapter.ts index f5bcf91e07d52..6ae1c018a5490 100644 --- a/x-pack/plugins/beats_management/server/lib/adapters/beats/memory_beats_adapter.ts +++ b/x-pack/plugins/beats_management/server/lib/adapters/beats/memory_beats_adapter.ts @@ -107,4 +107,8 @@ export class MemoryBeatsAdapter implements CMBeatsAdapter { status: 200, })); } + + public setDB(beatsDB: CMBeat[]) { + this.beatsDB = beatsDB; + } } diff --git a/x-pack/plugins/beats_management/server/lib/adapters/database/memory_database_adapter.ts b/x-pack/plugins/beats_management/server/lib/adapters/database/memory_database_adapter.ts new file mode 100644 index 0000000000000..9851027ce2218 --- /dev/null +++ b/x-pack/plugins/beats_management/server/lib/adapters/database/memory_database_adapter.ts @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { FrameworkUser } from '../framework/adapter_types'; +import { + DatabaseAdapter, + DatabaseBulkIndexDocumentsParams, + DatabaseCreateDocumentParams, + DatabaseCreateDocumentResponse, + DatabaseDeleteDocumentParams, + DatabaseDeleteDocumentResponse, + DatabaseGetDocumentResponse, + DatabaseGetParams, + DatabaseIndexDocumentParams, + DatabaseMGetParams, + DatabaseMGetResponse, + DatabasePutTemplateParams, + DatabaseSearchParams, + DatabaseSearchResponse, +} from './adapter_types'; + +export class MemoryDatabaseAdapter implements DatabaseAdapter { + public async putTemplate(user: FrameworkUser, params: DatabasePutTemplateParams): Promise { + return null; + } + + public async get( + user: FrameworkUser, + params: DatabaseGetParams + ): Promise> { + throw new Error('get not implamented in memory'); + } + + public async mget( + user: FrameworkUser, + params: DatabaseMGetParams + ): Promise> { + throw new Error('mget not implamented in memory'); + } + + public async bulk(user: FrameworkUser, params: DatabaseBulkIndexDocumentsParams): Promise { + throw new Error('mget not implamented in memory'); + } + + public async create( + user: FrameworkUser, + params: DatabaseCreateDocumentParams + ): Promise { + throw new Error('create not implamented in memory'); + } + public async index(user: FrameworkUser, params: DatabaseIndexDocumentParams): Promise { + throw new Error('index not implamented in memory'); + } + public async delete( + user: FrameworkUser, + params: DatabaseDeleteDocumentParams + ): Promise { + throw new Error('delete not implamented in memory'); + } + + public async search( + user: FrameworkUser, + params: DatabaseSearchParams + ): Promise> { + throw new Error('search not implamented in memory'); + } +} diff --git a/x-pack/plugins/beats_management/server/lib/adapters/framework/testing_framework_adapter.ts b/x-pack/plugins/beats_management/server/lib/adapters/framework/hapi_framework_adapter.ts similarity index 55% rename from x-pack/plugins/beats_management/server/lib/adapters/framework/testing_framework_adapter.ts rename to x-pack/plugins/beats_management/server/lib/adapters/framework/hapi_framework_adapter.ts index 5462bd36b48ae..dd122e59bdaef 100644 --- a/x-pack/plugins/beats_management/server/lib/adapters/framework/testing_framework_adapter.ts +++ b/x-pack/plugins/beats_management/server/lib/adapters/framework/hapi_framework_adapter.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import { wrapRequest } from '../../../utils/wrap_request'; import { FrameworkInternalUser } from './adapter_types'; - import { BackendFrameworkAdapter, FrameworkRouteOptions, @@ -17,19 +17,22 @@ interface TestSettings { encryptionKey: string; } -export class TestingBackendFrameworkAdapter implements BackendFrameworkAdapter { +export class HapiBackendFrameworkAdapter implements BackendFrameworkAdapter { public readonly internalUser: FrameworkInternalUser = { kind: 'internal', }; public version: string; private settings: TestSettings; + private server: any; constructor( settings: TestSettings = { encryptionKey: 'something_who_cares', enrollmentTokensTtlInSeconds: 10 * 60, // 10 minutes - } + }, + hapiServer?: any ) { + this.server = hapiServer; this.settings = settings; this.version = 'testing'; } @@ -44,12 +47,42 @@ export class TestingBackendFrameworkAdapter implements BackendFrameworkAdapter { } public exposeStaticDir(urlPath: string, dir: string): void { - // not yet testable + if (!this.server) { + throw new Error('Must pass a hapi server into the adapter to use exposeStaticDir'); + } + this.server.route({ + handler: { + directory: { + path: dir, + }, + }, + method: 'GET', + path: urlPath, + }); } public registerRoute( route: FrameworkRouteOptions ) { - // not yet testable + if (!this.server) { + throw new Error('Must pass a hapi server into the adapter to use registerRoute'); + } + const wrappedHandler = (licenseRequired: boolean) => (request: any, reply: any) => { + return route.handler(wrapRequest(request), reply); + }; + + this.server.route({ + handler: wrappedHandler(route.licenseRequired || false), + method: route.method, + path: route.path, + config: { + ...route.config, + auth: false, + }, + }); + } + + public async injectRequstForTesting({ method, url, headers, payload }: any) { + return await this.server.inject({ method, url, headers, payload }); } } diff --git a/x-pack/plugins/beats_management/server/lib/adapters/tags/memory_tags_adapter.ts b/x-pack/plugins/beats_management/server/lib/adapters/tags/memory_tags_adapter.ts index 4d2ed434b8760..a730f55db98ee 100644 --- a/x-pack/plugins/beats_management/server/lib/adapters/tags/memory_tags_adapter.ts +++ b/x-pack/plugins/beats_management/server/lib/adapters/tags/memory_tags_adapter.ts @@ -36,4 +36,8 @@ export class MemoryTagsAdapter implements CMTagsAdapter { } return tag; } + + public setDB(tagsDB: BeatTag[]) { + this.tagsDB = tagsDB; + } } diff --git a/x-pack/plugins/beats_management/server/lib/adapters/tokens/memory_tokens_adapter.ts b/x-pack/plugins/beats_management/server/lib/adapters/tokens/memory_tokens_adapter.ts index 767f161615ef8..fabbafc040969 100644 --- a/x-pack/plugins/beats_management/server/lib/adapters/tokens/memory_tokens_adapter.ts +++ b/x-pack/plugins/beats_management/server/lib/adapters/tokens/memory_tokens_adapter.ts @@ -39,4 +39,8 @@ export class MemoryTokensAdapter implements CMTokensAdapter { }); return tokens; } + + public setDB(tokenDB: TokenEnrollmentData[]) { + this.tokenDB = tokenDB; + } } diff --git a/x-pack/plugins/beats_management/server/lib/compose/testing.ts b/x-pack/plugins/beats_management/server/lib/compose/testing.ts index 7928891443708..41fc18b80aeef 100644 --- a/x-pack/plugins/beats_management/server/lib/compose/testing.ts +++ b/x-pack/plugins/beats_management/server/lib/compose/testing.ts @@ -8,32 +8,22 @@ import { MemoryBeatsAdapter } from '../adapters/beats/memory_beats_adapter'; import { MemoryTagsAdapter } from '../adapters/tags/memory_tags_adapter'; import { MemoryTokensAdapter } from '../adapters/tokens/memory_tokens_adapter'; -import { TestingBackendFrameworkAdapter } from '../adapters/framework/testing_framework_adapter'; +import { HapiBackendFrameworkAdapter } from '../adapters/framework/hapi_framework_adapter'; import { CMBeatsDomain } from '../domains/beats'; import { CMTagsDomain } from '../domains/tags'; import { CMTokensDomain } from '../domains/tokens'; -import { BeatTag, CMBeat } from '../../../common/domain_types'; -import { TokenEnrollmentData } from '../adapters/tokens/adapter_types'; import { CMDomainLibs, CMServerLibs } from '../lib'; -export function compose({ - tagsDB = [], - tokensDB = [], - beatsDB = [], -}: { - tagsDB?: BeatTag[]; - tokensDB?: TokenEnrollmentData[]; - beatsDB?: CMBeat[]; -}): CMServerLibs { - const framework = new TestingBackendFrameworkAdapter(); - - const tags = new CMTagsDomain(new MemoryTagsAdapter(tagsDB)); - const tokens = new CMTokensDomain(new MemoryTokensAdapter(tokensDB), { +export function compose(server: any): CMServerLibs { + const framework = new HapiBackendFrameworkAdapter(undefined, server); + + const tags = new CMTagsDomain(new MemoryTagsAdapter(server.tagsDB || [])); + const tokens = new CMTokensDomain(new MemoryTokensAdapter(server.tokensDB || []), { framework, }); - const beats = new CMBeatsDomain(new MemoryBeatsAdapter(beatsDB), { + const beats = new CMBeatsDomain(new MemoryBeatsAdapter(server.beatsDB || []), { tags, tokens, framework, diff --git a/x-pack/plugins/beats_management/server/lib/domains/__tests__/beats/assign_tags.test.ts b/x-pack/plugins/beats_management/server/lib/domains/__tests__/beats/assign_tags.test.ts index 48b1f63335ec7..a84f01bf4fd0f 100644 --- a/x-pack/plugins/beats_management/server/lib/domains/__tests__/beats/assign_tags.test.ts +++ b/x-pack/plugins/beats_management/server/lib/domains/__tests__/beats/assign_tags.test.ts @@ -7,7 +7,7 @@ import { FrameworkInternalUser } from '../../../adapters/framework/adapter_types'; import { MemoryBeatsAdapter } from '../../../adapters/beats/memory_beats_adapter'; -import { TestingBackendFrameworkAdapter } from '../../../adapters/framework/testing_framework_adapter'; +import { HapiBackendFrameworkAdapter } from '../../../adapters/framework/hapi_framework_adapter'; import { MemoryTagsAdapter } from '../../../adapters/tags/memory_tags_adapter'; import { MemoryTokensAdapter } from '../../../adapters/tokens/memory_tokens_adapter'; @@ -93,7 +93,7 @@ describe('Beats Domain Lib', () => { last_updated: new Date(), }, ]; - const framework = new TestingBackendFrameworkAdapter(settings); + const framework = new HapiBackendFrameworkAdapter(settings); const tokensLib = new CMTokensDomain(new MemoryTokensAdapter([]), { framework, diff --git a/x-pack/plugins/beats_management/server/lib/domains/__tests__/beats/enroll.test.ts b/x-pack/plugins/beats_management/server/lib/domains/__tests__/beats/enroll.test.ts index 5f8a0b683a5b5..d115c49244c65 100644 --- a/x-pack/plugins/beats_management/server/lib/domains/__tests__/beats/enroll.test.ts +++ b/x-pack/plugins/beats_management/server/lib/domains/__tests__/beats/enroll.test.ts @@ -5,7 +5,7 @@ */ import { MemoryBeatsAdapter } from '../../../adapters/beats/memory_beats_adapter'; -import { TestingBackendFrameworkAdapter } from '../../../adapters/framework/testing_framework_adapter'; +import { HapiBackendFrameworkAdapter } from '../../../adapters/framework/hapi_framework_adapter'; import { MemoryTagsAdapter } from '../../../adapters/tags/memory_tags_adapter'; import { MemoryTokensAdapter } from '../../../adapters/tokens/memory_tokens_adapter'; import { BeatEnrollmentStatus } from '../../../lib'; @@ -70,7 +70,7 @@ describe('Beats Domain Lib', () => { version, }; - const framework = new TestingBackendFrameworkAdapter(settings); + const framework = new HapiBackendFrameworkAdapter(settings); tokensLib = new CMTokensDomain(new MemoryTokensAdapter(tokensDB), { framework, diff --git a/x-pack/plugins/beats_management/server/lib/domains/__tests__/beats/update.test.ts b/x-pack/plugins/beats_management/server/lib/domains/__tests__/beats/update.test.ts index a068a1a50b6a8..0808d399326df 100644 --- a/x-pack/plugins/beats_management/server/lib/domains/__tests__/beats/update.test.ts +++ b/x-pack/plugins/beats_management/server/lib/domains/__tests__/beats/update.test.ts @@ -7,7 +7,7 @@ import Chance from 'chance'; import { BeatTag, CMBeat } from '../../../../../common/domain_types'; import { MemoryBeatsAdapter } from '../../../adapters/beats/memory_beats_adapter'; -import { TestingBackendFrameworkAdapter } from '../../../adapters/framework/testing_framework_adapter'; +import { HapiBackendFrameworkAdapter } from '../../../adapters/framework/hapi_framework_adapter'; import { MemoryTagsAdapter } from '../../../adapters/tags/memory_tags_adapter'; import { TokenEnrollmentData } from '../../../adapters/tokens/adapter_types'; import { MemoryTokensAdapter } from '../../../adapters/tokens/memory_tokens_adapter'; @@ -35,7 +35,7 @@ describe('Beats Domain lib', () => { let beat: Partial; const getBeatsLib = async () => { - const framework = new TestingBackendFrameworkAdapter(settings); + const framework = new HapiBackendFrameworkAdapter(settings); tokensLib = new CMTokensDomain(new MemoryTokensAdapter(tokensDB), { framework }); const tagsLib = new CMTagsDomain(new MemoryTagsAdapter(tagsDB)); diff --git a/x-pack/plugins/beats_management/server/lib/domains/__tests__/tokens.test.ts b/x-pack/plugins/beats_management/server/lib/domains/__tests__/tokens.test.ts index eca217b3c8ed1..91c504cd9f503 100644 --- a/x-pack/plugins/beats_management/server/lib/domains/__tests__/tokens.test.ts +++ b/x-pack/plugins/beats_management/server/lib/domains/__tests__/tokens.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { TestingBackendFrameworkAdapter } from '../../adapters/framework/testing_framework_adapter'; +import { HapiBackendFrameworkAdapter } from '../../adapters/framework/hapi_framework_adapter'; import { TokenEnrollmentData } from '../../adapters/tokens/adapter_types'; import { MemoryTokensAdapter } from '../../adapters/tokens/memory_tokens_adapter'; import { CMTokensDomain } from '../tokens'; @@ -28,7 +28,7 @@ describe('Token Domain Lib', () => { beforeEach(async () => { tokensDB = []; - framework = new TestingBackendFrameworkAdapter(settings); + framework = new HapiBackendFrameworkAdapter(settings); tokensLib = new CMTokensDomain(new MemoryTokensAdapter(tokensDB), { framework, diff --git a/x-pack/plugins/beats_management/server/rest_api/__tests__/beats_assignments.test.ts b/x-pack/plugins/beats_management/server/rest_api/__tests__/beats_assignments.test.ts new file mode 100644 index 0000000000000..331403e8145c3 --- /dev/null +++ b/x-pack/plugins/beats_management/server/rest_api/__tests__/beats_assignments.test.ts @@ -0,0 +1,253 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CMServerLibs } from '../../lib/lib'; +import { HapiBackendFrameworkAdapter } from './../../lib/adapters/framework/hapi_framework_adapter'; +import { testHarnes } from './test_harnes'; + +describe('assign_tags_to_beats', () => { + let serverLibs: CMServerLibs; + + beforeAll(async () => { + jest.setTimeout(100000); // 1 second + + serverLibs = await testHarnes.getServerLibs(); + }); + beforeEach(async () => await testHarnes.loadData()); + + it('should add a single tag to a single beat', async () => { + const { + result, + statusCode, + } = await (serverLibs.framework as HapiBackendFrameworkAdapter).injectRequstForTesting({ + method: 'POST', + url: '/api/beats/agents_tags/assignments', + headers: { + 'kbn-xsrf': 'xxx', + authorization: 'loggedin', + }, + payload: { + assignments: [{ beatId: 'bar', tag: 'production' }], + }, + }); + + expect(statusCode).toEqual(200); + expect(result.assignments).toEqual([{ status: 200, result: 'updated' }]); + }); + + it('should not re-add an existing tag to a beat', async () => { + const { + result, + statusCode, + } = await (serverLibs.framework as HapiBackendFrameworkAdapter).injectRequstForTesting({ + method: 'POST', + url: '/api/beats/agents_tags/assignments', + headers: { + 'kbn-xsrf': 'xxx', + authorization: 'loggedin', + }, + payload: { + assignments: [{ beatId: 'foo', tag: 'production' }], + }, + }); + + expect(statusCode).toEqual(200); + + expect(result.assignments).toEqual([{ status: 200, result: 'updated' }]); + + let beat; + + beat = await serverLibs.beats.getById( + { + kind: 'internal', + }, + 'foo' + ); + expect(beat!.tags).toEqual(['production', 'qa']); // as + }); + + it('should add a single tag to a multiple beats', async () => { + const { + result, + statusCode, + } = await (serverLibs.framework as HapiBackendFrameworkAdapter).injectRequstForTesting({ + method: 'POST', + url: '/api/beats/agents_tags/assignments', + headers: { + 'kbn-xsrf': 'xxx', + authorization: 'loggedin', + }, + payload: { + assignments: [{ beatId: 'foo', tag: 'development' }, { beatId: 'bar', tag: 'development' }], + }, + }); + + expect(statusCode).toEqual(200); + + expect(result.assignments).toEqual([ + { status: 200, result: 'updated' }, + { status: 200, result: 'updated' }, + ]); + + let beat; + + beat = await serverLibs.beats.getById( + { + kind: 'internal', + }, + 'foo' + ); + expect(beat!.tags).toEqual(['production', 'qa', 'development']); // as beat 'foo' already had 'production' and 'qa' tags attached to it + + // Beat bar + beat = await serverLibs.beats.getById( + { + kind: 'internal', + }, + 'bar' + ); + + expect(beat!.tags).toEqual(['development']); + }); + + it('should add multiple tags to a single beat', async () => { + const { + result, + statusCode, + } = await (serverLibs.framework as HapiBackendFrameworkAdapter).injectRequstForTesting({ + method: 'POST', + url: '/api/beats/agents_tags/assignments', + headers: { + 'kbn-xsrf': 'xxx', + authorization: 'loggedin', + }, + payload: { + assignments: [{ beatId: 'bar', tag: 'development' }, { beatId: 'bar', tag: 'production' }], + }, + }); + + expect(statusCode).toEqual(200); + + expect(result.assignments).toEqual([ + { status: 200, result: 'updated' }, + { status: 200, result: 'updated' }, + ]); + + const beat = await serverLibs.beats.getById( + { + kind: 'internal', + }, + 'bar' + ); + + expect(beat!.tags).toEqual(['development', 'production']); + }); + + // it('should add multiple tags to a multiple beats', async () => { + // const { body: apiResponse } = await supertest + // .post('/api/beats/agents_tags/assignments') + // .set('kbn-xsrf', 'xxx') + // .send({ + // assignments: [{ beatId: 'foo', tag: 'development' }, { beatId: 'bar', tag: 'production' }], + // }) + // .expect(200); + + // expect(apiResponse.assignments).to.eql([ + // { status: 200, result: 'updated' }, + // { status: 200, result: 'updated' }, + // ]); + + // let esResponse; + // let beat; + + // // Beat foo + // esResponse = await es.get({ + // index: ES_INDEX_NAME, + // type: ES_TYPE_NAME, + // id: `beat:foo`, + // }); + + // beat = esResponse._source.beat; + // expect(beat.tags).to.eql(['production', 'qa', 'development']); // as beat 'foo' already had 'production' and 'qa' tags attached to it + + // // Beat bar + // esResponse = await es.get({ + // index: ES_INDEX_NAME, + // type: ES_TYPE_NAME, + // id: `beat:bar`, + // }); + + // beat = esResponse._source.beat; + // expect(beat.tags).to.eql(['production']); + // }); + + // it('should return errors for non-existent beats', async () => { + // const nonExistentBeatId = chance.word(); + + // const { body: apiResponse } = await supertest + // .post('/api/beats/agents_tags/assignments') + // .set('kbn-xsrf', 'xxx') + // .send({ + // assignments: [{ beatId: nonExistentBeatId, tag: 'production' }], + // }) + // .expect(200); + + // expect(apiResponse.assignments).to.eql([ + // { status: 404, result: `Beat ${nonExistentBeatId} not found` }, + // ]); + // }); + + // it('should return errors for non-existent tags', async () => { + // const nonExistentTag = chance.word(); + + // const { body: apiResponse } = await supertest + // .post('/api/beats/agents_tags/assignments') + // .set('kbn-xsrf', 'xxx') + // .send({ + // assignments: [{ beatId: 'bar', tag: nonExistentTag }], + // }) + // .expect(200); + + // expect(apiResponse.assignments).to.eql([ + // { status: 404, result: `Tag ${nonExistentTag} not found` }, + // ]); + + // const esResponse = await es.get({ + // index: ES_INDEX_NAME, + // type: ES_TYPE_NAME, + // id: `beat:bar`, + // }); + + // const beat = esResponse._source.beat; + // expect(beat).to.not.have.property('tags'); + // }); + + // it('should return errors for non-existent beats and tags', async () => { + // const nonExistentBeatId = chance.word(); + // const nonExistentTag = chance.word(); + + // const { body: apiResponse } = await supertest + // .post('/api/beats/agents_tags/assignments') + // .set('kbn-xsrf', 'xxx') + // .send({ + // assignments: [{ beatID: nonExistentBeatId, tag: nonExistentTag }], + // }) + // .expect(200); + + // expect(apiResponse.assignments).to.eql([ + // { status: 404, result: `Beat ${nonExistentBeatId} and tag ${nonExistentTag} not found` }, + // ]); + + // const esResponse = await es.get({ + // index: ES_INDEX_NAME, + // type: ES_TYPE_NAME, + // id: `beat:bar`, + // }); + + // const beat = esResponse._source.beat; + // expect(beat).to.not.have.property('tags'); + // }); +}); diff --git a/x-pack/plugins/beats_management/server/rest_api/__tests__/beats_configurations.test.ts b/x-pack/plugins/beats_management/server/rest_api/__tests__/beats_configurations.test.ts new file mode 100644 index 0000000000000..41bc2aa258807 --- /dev/null +++ b/x-pack/plugins/beats_management/server/rest_api/__tests__/beats_configurations.test.ts @@ -0,0 +1,5 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ diff --git a/x-pack/plugins/beats_management/server/rest_api/__tests__/beats_enroll.test.ts b/x-pack/plugins/beats_management/server/rest_api/__tests__/beats_enroll.test.ts new file mode 100644 index 0000000000000..41bc2aa258807 --- /dev/null +++ b/x-pack/plugins/beats_management/server/rest_api/__tests__/beats_enroll.test.ts @@ -0,0 +1,5 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ diff --git a/x-pack/plugins/beats_management/server/rest_api/__tests__/beats_get.test.ts b/x-pack/plugins/beats_management/server/rest_api/__tests__/beats_get.test.ts new file mode 100644 index 0000000000000..41bc2aa258807 --- /dev/null +++ b/x-pack/plugins/beats_management/server/rest_api/__tests__/beats_get.test.ts @@ -0,0 +1,5 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ diff --git a/x-pack/plugins/beats_management/server/rest_api/__tests__/beats_list.test.ts b/x-pack/plugins/beats_management/server/rest_api/__tests__/beats_list.test.ts new file mode 100644 index 0000000000000..41bc2aa258807 --- /dev/null +++ b/x-pack/plugins/beats_management/server/rest_api/__tests__/beats_list.test.ts @@ -0,0 +1,5 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ diff --git a/x-pack/plugins/beats_management/server/rest_api/__tests__/beats_removals.test.ts b/x-pack/plugins/beats_management/server/rest_api/__tests__/beats_removals.test.ts new file mode 100644 index 0000000000000..41bc2aa258807 --- /dev/null +++ b/x-pack/plugins/beats_management/server/rest_api/__tests__/beats_removals.test.ts @@ -0,0 +1,5 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ diff --git a/x-pack/plugins/beats_management/server/rest_api/__tests__/beats_updates.test.ts b/x-pack/plugins/beats_management/server/rest_api/__tests__/beats_updates.test.ts new file mode 100644 index 0000000000000..41bc2aa258807 --- /dev/null +++ b/x-pack/plugins/beats_management/server/rest_api/__tests__/beats_updates.test.ts @@ -0,0 +1,5 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ diff --git a/x-pack/plugins/beats_management/server/rest_api/__tests__/data.json b/x-pack/plugins/beats_management/server/rest_api/__tests__/data.json new file mode 100644 index 0000000000000..f263eff1a5bd4 --- /dev/null +++ b/x-pack/plugins/beats_management/server/rest_api/__tests__/data.json @@ -0,0 +1,158 @@ +{ + "type": "doc", + "value": { + "index": ".management-beats", + "type": "_doc", + "id": "beat:qux", + "source": { + "type": "beat", + "beat": { + "type": "filebeat", + "active": true, + "host_ip": "1.2.3.4", + "host_name": "foo.bar.com", + "id": "qux", + "name": "qux_filebeat", + "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjcmVhdGVkIjoiMjAxOC0wNi0zMFQwMzo0MjoxNS4yMzBaIiwiaWF0IjoxNTMwMzMwMTM1fQ.SSsX2Byyo1B1bGxV8C3G4QldhE5iH87EY_1r21-bwbI" + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".management-beats", + "type": "_doc", + "id": "beat:baz", + "source": { + "type": "beat", + "beat": { + "type": "metricbeat", + "active": true, + "host_ip": "22.33.11.44", + "host_name": "baz.bar.com", + "id": "baz", + "name": "baz_metricbeat", + "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjcmVhdGVkIjoiMjAxOC0wNi0zMFQwMzo0MjoxNS4yMzBaIiwiaWF0IjoxNTMwMzMwMTM1fQ.SSsX2Byyo1B1bGxV8C3G4QldhE5iH87EY_1r21-bwbI" + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".management-beats", + "type": "_doc", + "id": "beat:foo", + "source": { + "type": "beat", + "beat": { + "type": "metricbeat", + "active": true, + "host_ip": "1.2.3.4", + "host_name": "foo.bar.com", + "id": "foo", + "name": "foo_metricbeat", + "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjcmVhdGVkIjoiMjAxOC0wNi0zMFQwMzo0MjoxNS4yMzBaIiwiaWF0IjoxNTMwMzMwMTM1fQ.SSsX2Byyo1B1bGxV8C3G4QldhE5iH87EY_1r21-bwbI", + "verified_on": "2018-05-15T16:25:38.924Z", + "tags": [ + "production", + "qa" + ] + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".management-beats", + "type": "_doc", + "id": "beat:bar", + "source": { + "type": "beat", + "beat": { + "type": "filebeat", + "active": true, + "host_ip": "11.22.33.44", + "host_name": "foo.com", + "id": "bar", + "name": "bar_filebeat", + "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjcmVhdGVkIjoiMjAxOC0wNi0zMFQwMzo0MjoxNS4yMzBaIiwiaWF0IjoxNTMwMzMwMTM1fQ.SSsX2Byyo1B1bGxV8C3G4QldhE5iH87EY_1r21-bwbI" + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".management-beats", + "type": "_doc", + "id": "tag:production", + "source": { + "type": "tag", + "tag": { + "configuration_blocks": [ + { + "type": "output", + "description": "some description", + "configs": [{ + "hosts": ["localhost:9200"], + "username": "some-username" + }] + }, + { + "type": "metricbeat.modules", + "configs": [{ + "module": "memcached", + "hosts": ["localhost:11211"] + }] + } + ] + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".management-beats", + "type": "_doc", + "id": "tag:development", + "source": { + "type": "tag", + "tag": { + "configuration_blocks": [] + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".management-beats", + "type": "_doc", + "id": "tag:qa", + "source": { + "type": "tag", + "tag": { + "configuration_blocks": [ + { + "type": "metricbeat.modules", + "configs": [{ + "module": "memcached", + "node.namespace": "node", + "hosts": ["localhost:4949"] + }] + } + ] + } + } + } +} \ No newline at end of file diff --git a/x-pack/plugins/beats_management/server/rest_api/__tests__/test_harnes.ts b/x-pack/plugins/beats_management/server/rest_api/__tests__/test_harnes.ts new file mode 100644 index 0000000000000..7f1fa9fa5e620 --- /dev/null +++ b/x-pack/plugins/beats_management/server/rest_api/__tests__/test_harnes.ts @@ -0,0 +1,102 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { badRequest } from 'boom'; +import { readFile } from 'fs'; +import Hapi from 'hapi'; +import { resolve } from 'path'; +import { promisify } from 'util'; +import { BeatTag, CMBeat } from '../../../common/domain_types'; +import { TokenEnrollmentData } from '../../lib/adapters/tokens/adapter_types'; +import { compose } from '../../lib/compose/testing'; +import { CMServerLibs } from '../../lib/lib'; +import { initManagementServer } from './../../management_server'; + +const readFileAsync = promisify(readFile); +let serverLibs: CMServerLibs; + +export const testHarnes = { + description: 'API Development Tests', + loadData: async () => { + if (!serverLibs) { + throw new Error('Server libs not composed yet...'); + } + const contents = await readFileAsync(resolve(__dirname, './data.json'), 'utf8'); + const database = contents.split(/\n\n/); + + // @ts-ignore the private access + serverLibs.beats.adapter.setDB( + database.reduce((inserts: CMBeat[], source) => { + const type = 'beat'; + const data = JSON.parse(source); + + if (data.value.source.type === type) { + inserts.push({ + id: data.value.id.substring(data.value.id.indexOf(':') + 1), + ...data.value.source[type], + }); + } + return inserts; + }, []) + ); + + // @ts-ignore the private access + serverLibs.tags.adapter.setDB( + database.reduce((inserts: BeatTag[], source) => { + const type = 'tag'; + const data = JSON.parse(source); + + if (data.value.source.type === type) { + inserts.push({ + id: data.value.id.substring(data.value.id.indexOf(':') + 1), + ...data.value.source[type], + }); + } + return inserts; + }, []) + ); + + // @ts-ignore the private access + serverLibs.tokens.adapter.setDB( + database.reduce((inserts: TokenEnrollmentData[], source) => { + const type = 'token'; + const data = JSON.parse(source); + + if (data.value.source.type === type) { + inserts.push({ + id: data.value.id.substring(data.value.id.indexOf(':') + 1), + ...data.value.source[type], + }); + } + return inserts; + }, []) + ); + }, + getServerLibs: async () => { + if (!serverLibs) { + const server = new Hapi.Server(); + server.connection({ port: 111111 }); + const versionHeader = 'kbn-version'; + const xsrfHeader = 'kbn-xsrf'; + + server.ext('onPostAuth', (req, reply) => { + const isSafeMethod = req.method === 'get' || req.method === 'head'; + const hasVersionHeader = versionHeader in req.headers; + const hasXsrfHeader = xsrfHeader in req.headers; + + if (!isSafeMethod && !hasVersionHeader && !hasXsrfHeader) { + return reply(badRequest(`Request must contain a ${xsrfHeader} header.`)); + } + + return reply.continue(); + }); + + serverLibs = compose(server); + initManagementServer(serverLibs); + } + return serverLibs; + }, +}; diff --git a/x-pack/plugins/beats_management/wallaby.js b/x-pack/plugins/beats_management/wallaby.js index 54c91f84f1c3c..bb57d22afafb3 100644 --- a/x-pack/plugins/beats_management/wallaby.js +++ b/x-pack/plugins/beats_management/wallaby.js @@ -25,16 +25,20 @@ module.exports = function (wallaby) { }, testFramework: 'jest', compilers: { - '**/*.ts?(x)': wallaby.compilers.typeScript({ module: 'commonjs' }), + '**/*.ts?(x)': wallaby.compilers.typeScript({ + typescript: require('typescript'), // eslint-disable-line + }), '**/*.js': wallaby.compilers.babel({ babelrc: false, presets: [require.resolve('@kbn/babel-preset/node_preset')], }), }, + setup: wallaby => { const path = require('path'); const kibanaDirectory = path.resolve(wallaby.localProjectDir, '..', '..', '..'); + wallaby.testFramework.configure({ rootDir: wallaby.localProjectDir, moduleNameMapper: { diff --git a/x-pack/test/api_integration/apis/beats/list_beats.js b/x-pack/test/api_integration/apis/beats/list_beats.js index c8e1c754b622c..b78939b30b62f 100644 --- a/x-pack/test/api_integration/apis/beats/list_beats.js +++ b/x-pack/test/api_integration/apis/beats/list_beats.js @@ -17,11 +17,7 @@ export default function ({ getService }) { afterEach('unload beats archive', () => esArchiver.unload(archive)); it('should return all beats', async () => { - const { body: apiResponse } = await supertest - .get( - '/api/beats/agents' - ) - .expect(200); + const { body: apiResponse } = await supertest.get('/api/beats/agents').expect(200); const beatsFromApi = apiResponse.beats; @@ -31,11 +27,7 @@ export default function ({ getService }) { }); it('should not return access tokens', async () => { - const { body: apiResponse } = await supertest - .get( - '/api/beats/agents' - ) - .expect(200); + const { body: apiResponse } = await supertest.get('/api/beats/agents').expect(200); const beatsFromApi = apiResponse.beats; From c0ccab5ca1fc3e9c427a01650d0fe0650b3462eb Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Thu, 4 Oct 2018 12:21:47 -0400 Subject: [PATCH 53/94] update yarn lock files --- x-pack/yarn.lock | 251 ++++++++++++++++++++++++++++++++++++++++++++++- yarn.lock | 195 +++++++++++++++++++++++++++++++++--- 2 files changed, 427 insertions(+), 19 deletions(-) diff --git a/x-pack/yarn.lock b/x-pack/yarn.lock index 4c4367062237f..842cf95f542d5 100644 --- a/x-pack/yarn.lock +++ b/x-pack/yarn.lock @@ -143,6 +143,10 @@ url-join "^4.0.0" ws "^4.1.0" +"@types/boom@^4.3.8": + version "4.3.10" + resolved "https://registry.yarnpkg.com/@types/boom/-/boom-4.3.10.tgz#39dad8c0614c26b91ef016a57d7eee4ffe4f8a25" + "@types/cookiejar@*": version "2.1.0" resolved "https://registry.yarnpkg.com/@types/cookiejar/-/cookiejar-2.1.0.tgz#4b7daf2c51696cfc70b942c11690528229d1a1ce" @@ -171,6 +175,16 @@ dependencies: "@types/node" "*" +"@types/hapi@15.0.1": + version "15.0.1" + resolved "https://registry.yarnpkg.com/@types/hapi/-/hapi-15.0.1.tgz#919e1d3a9160a080c9fdefaccc892239772e1258" + dependencies: + "@types/node" "*" + +"@types/history@*": + version "4.7.0" + resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.0.tgz#2fac51050c68f7d6f96c5aafc631132522f4aa3f" + "@types/is-stream@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@types/is-stream/-/is-stream-1.1.0.tgz#b84d7bb207a210f2af9bed431dc0fbe9c4143be1" @@ -185,6 +199,16 @@ version "10.6.4" resolved "https://registry.yarnpkg.com/@types/joi/-/joi-10.6.4.tgz#0989d69e792a7db13e951852e6949df6787f113f" +"@types/jsonwebtoken@^7.2.8": + version "7.2.8" + resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-7.2.8.tgz#8d199dab4ddb5bba3234f8311b804d2027af2b3a" + dependencies: + "@types/node" "*" + +"@types/lodash@^3.10.0": + version "3.10.2" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-3.10.2.tgz#c1fbda1562ef5603c8192fe1fe65b017849d5873" + "@types/loglevel@^1.5.3": version "1.5.3" resolved "https://registry.yarnpkg.com/@types/loglevel/-/loglevel-1.5.3.tgz#adfce55383edc5998a2170ad581b3e23d6adb5b8" @@ -227,6 +251,32 @@ dependencies: "@types/node" "*" +"@types/prop-types@*": + version "15.5.6" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.5.6.tgz#9c03d3fed70a8d517c191b7734da2879b50ca26c" + +"@types/react-router-dom@^4.2.7": + version "4.3.1" + resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-4.3.1.tgz#71fe2918f8f60474a891520def40a63997dafe04" + dependencies: + "@types/history" "*" + "@types/react" "*" + "@types/react-router" "*" + +"@types/react-router@*": + version "4.0.31" + resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-4.0.31.tgz#416bac49d746800810886c7b8582a622ed9604fc" + dependencies: + "@types/history" "*" + "@types/react" "*" + +"@types/react@*": + version "16.4.14" + resolved "https://registry.yarnpkg.com/@types/react/-/react-16.4.14.tgz#47c604c8e46ed674bbdf4aabf82b34b9041c6a04" + dependencies: + "@types/prop-types" "*" + csstype "^2.2.0" + "@types/retry@*", "@types/retry@^0.10.2": version "0.10.2" resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.10.2.tgz#bd1740c4ad51966609b058803ee6874577848b37" @@ -248,6 +298,12 @@ version "0.8.2" resolved "https://registry.yarnpkg.com/@types/url-join/-/url-join-0.8.2.tgz#1181ecbe1d97b7034e0ea1e35e62e86cc26b422d" +"@types/uuid@^3.4.3": + version "3.4.4" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-3.4.4.tgz#7af69360fa65ef0decb41fd150bf4ca5c0cefdf5" + dependencies: + "@types/node" "*" + "@types/ws@^4.0.1": version "4.0.2" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-4.0.2.tgz#b29037627dd7ba31ec49a4f1584840422efb856f" @@ -310,6 +366,10 @@ acorn@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.0.1.tgz#66e6147e1027704479dc6d9b20d884c572db3cc1" +add-event-listener@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/add-event-listener/-/add-event-listener-0.0.1.tgz#a76229ebc64c8aefae204a16273a2f255abea2d0" + after@0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f" @@ -374,6 +434,10 @@ ammo@2.x.x: boom "5.x.x" hoek "4.x.x" +angular-paging@2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/angular-paging/-/angular-paging-2.2.1.tgz#8090864f71bc4c9b89307b02ab02afb205983c43" + angular-resource@1.4.9: version "1.4.9" resolved "https://registry.yarnpkg.com/angular-resource/-/angular-resource-1.4.9.tgz#67f09382b623fd7e61540b0d127dba99fda99d45" @@ -1411,6 +1475,10 @@ buffer-crc32@~0.2.3: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" +buffer-equal-constant-time@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + buffer-equal@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-0.0.1.tgz#91bc74b11ea405bc916bc6aa908faafa5b4aac4b" @@ -2049,6 +2117,10 @@ cssstyle@^1.0.0: dependencies: cssom "0.3.x" +csstype@^2.2.0: + version "2.5.7" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.5.7.tgz#bf9235d5872141eccfb2d16d82993c6b149179ff" + currently-unhandled@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" @@ -2077,6 +2149,21 @@ d3-contour@^1.1.0: dependencies: d3-array "^1.1.1" +d3-dispatch@1, d3-dispatch@^1.0.3: + version "1.0.5" + resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-1.0.5.tgz#e25c10a186517cd6c82dd19ea018f07e01e39015" + +d3-drag@1, d3-drag@^1.0.4: + version "1.2.3" + resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-1.2.3.tgz#46e206ad863ec465d88c588098a1df444cd33c64" + dependencies: + d3-dispatch "1" + d3-selection "1" + +d3-ease@1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-1.0.5.tgz#8ce59276d81241b1b72042d6af2d40e76d936ffb" + d3-format@1, d3-format@^1.2.0: version "1.2.1" resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.2.1.tgz#4e19ecdb081a341dafaf5f555ee956bcfdbf167f" @@ -2133,6 +2220,10 @@ d3-scale@^1.0.5: d3-time "1" d3-time-format "2" +d3-selection@1, d3-selection@^1.1.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-1.3.2.tgz#6e70a9df60801c8af28ac24d10072d82cbfdf652" + d3-shape@^1.1.0, d3-shape@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.2.0.tgz#45d01538f064bafd05ea3d6d2cb748fd8c41f777" @@ -2149,10 +2240,35 @@ d3-time@1: version "1.0.8" resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-1.0.8.tgz#dbd2d6007bf416fe67a76d17947b784bffea1e84" +d3-timer@1, d3-timer@^1.0.5: + version "1.0.9" + resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-1.0.9.tgz#f7bb8c0d597d792ff7131e1c24a36dd471a471ba" + +d3-transition@1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-1.1.3.tgz#3a435b05ce9cef9524fe0d38121cfb6905331ca6" + dependencies: + d3-color "1" + d3-dispatch "1" + d3-ease "1" + d3-interpolate "1" + d3-selection "^1.1.0" + d3-timer "1" + d3-voronoi@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/d3-voronoi/-/d3-voronoi-1.1.2.tgz#1687667e8f13a2d158c80c1480c5a29cb0d8973c" +d3-zoom@^1.1.4: + version "1.7.3" + resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-1.7.3.tgz#f444effdc9055c38077c4299b4df999eb1d47ccb" + dependencies: + d3-dispatch "1" + d3-drag "1" + d3-interpolate "1" + d3-selection "1" + d3-transition "1" + d3@3.5.6: version "3.5.6" resolved "https://registry.yarnpkg.com/d3/-/d3-3.5.6.tgz#9451c651ca733fb9672c81fb7f2655164a73a42d" @@ -2469,6 +2585,12 @@ ecc-jsbn@~0.1.1: dependencies: jsbn "~0.1.0" +ecdsa-sig-formatter@1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz#1c595000f04a8897dfb85000892a0f4c33af86c3" + dependencies: + safe-buffer "^5.0.1" + elasticsearch@^15.1.1: version "15.1.1" resolved "https://registry.yarnpkg.com/elasticsearch/-/elasticsearch-15.1.1.tgz#242f2378fccd601586ff763a8a933cd5d41c945f" @@ -3155,6 +3277,10 @@ forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" +form-data-to-object@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/form-data-to-object/-/form-data-to-object-0.2.0.tgz#f7a8e68ddd910a1100a65e25ac6a484143ff8168" + form-data@^2.3.1, form-data@~2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.1.tgz#6fb94fbd71885306d73d15cc497fe4cc4ecd44bf" @@ -3183,6 +3309,13 @@ formidable@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.1.1.tgz#96b8886f7c3c3508b932d6bd70c4d3a88f35f1a9" +formsy-react@^1.1.4: + version "1.1.5" + resolved "https://registry.yarnpkg.com/formsy-react/-/formsy-react-1.1.5.tgz#ee0911bb70712eb6fb9924d56fdb974a19006955" + dependencies: + form-data-to-object "^0.2.0" + prop-types "^15.5.10" + fragment-cache@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" @@ -3244,6 +3377,12 @@ fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2: mkdirp ">=0.5 0" rimraf "2" +fullscreen@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/fullscreen/-/fullscreen-1.1.1.tgz#b9017c3bf9b23e07effd1bbce910cf5ec2459129" + dependencies: + add-event-listener "0.0.1" + function-bind@^1.0.2, function-bind@^1.1.0, function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" @@ -3576,6 +3715,19 @@ graceful-fs@~1.2.0: version "1.0.1" resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" +graphlib-dot@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/graphlib-dot/-/graphlib-dot-0.6.2.tgz#872e933d0bb349430cb813a2491943d1c5f1e952" + dependencies: + graphlib "^1.0.1" + lodash "^2.4.1" + +graphlib@^1.0.1: + version "1.0.7" + resolved "https://registry.yarnpkg.com/graphlib/-/graphlib-1.0.7.tgz#0cab2df0ffe6abe070b2625bfa1edb6ec967b8b1" + dependencies: + lodash "^3.10.0" + growl@1.9.2: version "1.9.2" resolved "https://registry.yarnpkg.com/growl/-/growl-1.9.2.tgz#0ea7743715db8d8de2c5ede1775e1b45ac85c02f" @@ -3960,6 +4112,13 @@ http-cache-semantics@3.8.1: version "3.8.1" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2" +http-errors@~1.4.0: + version "1.4.0" + resolved "http://registry.npmjs.org/http-errors/-/http-errors-1.4.0.tgz#6c0242dea6b3df7afda153c71089b31c6e82aabf" + dependencies: + inherits "2.0.1" + statuses ">= 1.2.1 < 2" + http-signature@~1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" @@ -4071,6 +4230,10 @@ inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" +inherits@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" + ini@^1.3.4, ini@~1.3.0: version "1.3.5" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" @@ -4891,7 +5054,7 @@ joi@9.x.x: moment "2.x.x" topo "2.x.x" -jquery@^3.3.1: +jquery@^3.1.1, jquery@^3.3.1: version "3.3.1" resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.3.1.tgz#958ce29e81c9790f31be7792df5d4d95fc57fbca" @@ -5040,6 +5203,20 @@ jsonpointer@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" +jsonwebtoken@^8.3.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.3.0.tgz#056c90eee9a65ed6e6c72ddb0a1d325109aaf643" + dependencies: + jws "^3.1.5" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.1.1" + jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -5061,6 +5238,21 @@ just-reduce-object@^1.0.3: version "1.1.0" resolved "https://registry.yarnpkg.com/just-reduce-object/-/just-reduce-object-1.1.0.tgz#d29d172264f8511c74462de30d72d5838b6967e6" +jwa@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.1.6.tgz#87240e76c9808dbde18783cf2264ef4929ee50e6" + dependencies: + buffer-equal-constant-time "1.0.1" + ecdsa-sig-formatter "1.0.10" + safe-buffer "^5.0.1" + +jws@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.1.5.tgz#80d12d05b293d1e841e7cb8b4e69e561adcf834f" + dependencies: + jwa "^1.1.5" + safe-buffer "^5.0.1" + keymirror@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/keymirror/-/keymirror-0.1.1.tgz#918889ea13f8d0a42e7c557250eee713adc95c35" @@ -5334,6 +5526,10 @@ lodash.get@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + lodash.isarguments@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" @@ -5342,10 +5538,22 @@ lodash.isarray@^3.0.0: version "3.0.4" resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + lodash.isequal@^4.1.1, lodash.isequal@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + lodash.isobject@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/lodash.isobject/-/lodash.isobject-3.0.2.tgz#3c8fb8d5b5bf4bf90ae06e14f2a530a4ed935e1d" @@ -5354,6 +5562,10 @@ lodash.isplainobject@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + lodash.istypedarray@^3.0.0: version "3.0.6" resolved "https://registry.yarnpkg.com/lodash.istypedarray/-/lodash.istypedarray-3.0.6.tgz#c9a477498607501d8e8494d283b87c39281cef62" @@ -5386,6 +5598,10 @@ lodash.omitby@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.omitby/-/lodash.omitby-4.6.0.tgz#5c15ff4754ad555016b53c041311e8f079204791" +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + lodash.orderby@4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.orderby/-/lodash.orderby-4.6.0.tgz#e697f04ce5d78522f54d9338b32b81a3393e4eb3" @@ -5465,10 +5681,14 @@ lodash.uniqueid@^3.0.0: dependencies: lodash._root "^3.0.0" -lodash@3.10.1, lodash@^3.10.1: +lodash@3.10.1, lodash@^3.10.0, lodash@^3.10.1: version "3.10.1" resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" +lodash@^2.4.1: + version "2.4.2" + resolved "http://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz#fadd834b9683073da179b3eae6d9c0d15053f73e" + lodash@^4.0.0, lodash@^4.17.5, lodash@~4.17.10: version "4.17.10" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7" @@ -5901,7 +6121,7 @@ ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" -ms@^2.0.0: +ms@^2.0.0, ms@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" @@ -6557,6 +6777,13 @@ path-key@^2.0.0, path-key@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" +path-match@1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/path-match/-/path-match-1.2.4.tgz#a62747f3c7e0c2514762697f24443585b09100ea" + dependencies: + http-errors "~1.4.0" + path-to-regexp "^1.0.0" + path-parse@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" @@ -6571,7 +6798,7 @@ path-root@^0.1.1: dependencies: path-root-regex "^0.1.0" -path-to-regexp@^1.7.0: +path-to-regexp@^1.0.0, path-to-regexp@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d" dependencies: @@ -8395,6 +8622,10 @@ static-module@^1.1.0: static-eval "~0.2.0" through2 "~0.4.1" +"statuses@>= 1.2.1 < 2": + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + stdout-stream@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/stdout-stream/-/stdout-stream-1.4.0.tgz#a2c7c8587e54d9427ea9edb3ac3f2cd522df378b" @@ -9324,6 +9555,18 @@ watch@~0.18.0: exec-sh "^0.2.0" minimist "^1.2.0" +webcola@3.3.6: + version "3.3.6" + resolved "https://registry.yarnpkg.com/webcola/-/webcola-3.3.6.tgz#6ec0bc7a72b3c467a2f2346a8667d88b439a03b4" + dependencies: + d3-dispatch "^1.0.3" + d3-drag "^1.0.4" + d3-timer "^1.0.5" + d3-zoom "^1.1.4" + fullscreen "^1.1.0" + graphlib-dot "^0.6.2" + jquery "^3.1.1" + webidl-conversions@^4.0.1, webidl-conversions@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" diff --git a/yarn.lock b/yarn.lock index d886f05025624..8e50d3c0fa02b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -525,12 +525,6 @@ version "2.0.29" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-2.0.29.tgz#5002e14f75e2d71e564281df0431c8c1b4a2a36a" -"@types/moment-timezone@^0.5.8": - version "0.5.8" - resolved "https://registry.yarnpkg.com/@types/moment-timezone/-/moment-timezone-0.5.8.tgz#92aba9bc238cabf69a27a1a4f52e0ebb8f10f896" - dependencies: - moment ">=2.14.0" - "@types/node@*": version "9.4.7" resolved "https://registry.yarnpkg.com/@types/node/-/node-9.4.7.tgz#57d81cd98719df2c9de118f2d5f3b1120dcd7275" @@ -746,6 +740,10 @@ acorn@^5.0.3, acorn@^5.6.0: version "5.7.3" resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279" +add-event-listener@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/add-event-listener/-/add-event-listener-0.0.1.tgz#a76229ebc64c8aefae204a16273a2f255abea2d0" + adm-zip@0.4.11: version "0.4.11" resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.4.11.tgz#2aa54c84c4b01a9d0fb89bb11982a51f13e3d62a" @@ -863,6 +861,10 @@ angular-mocks@1.4.7: version "1.4.7" resolved "https://registry.yarnpkg.com/angular-mocks/-/angular-mocks-1.4.7.tgz#d7343ee0a033f9216770bda573950f6814d95227" +angular-paging@2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/angular-paging/-/angular-paging-2.2.1.tgz#8090864f71bc4c9b89307b02ab02afb205983c43" + angular-recursion@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/angular-recursion/-/angular-recursion-1.0.5.tgz#cd405428a0bf55faf52eaa7988c1fe69cd930543" @@ -2354,6 +2356,10 @@ buffer-crc32@~0.2.3: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" +buffer-equal-constant-time@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + buffer-equal@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-0.0.1.tgz#91bc74b11ea405bc916bc6aa908faafa5b4aac4b" @@ -3665,6 +3671,17 @@ d3-dispatch@1: version "1.0.3" resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-1.0.3.tgz#46e1491eaa9b58c358fce5be4e8bed626e7871f8" +d3-dispatch@^1.0.3: + version "1.0.5" + resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-1.0.5.tgz#e25c10a186517cd6c82dd19ea018f07e01e39015" + +d3-drag@1, d3-drag@^1.0.4: + version "1.2.3" + resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-1.2.3.tgz#46e206ad863ec465d88c588098a1df444cd33c64" + dependencies: + d3-dispatch "1" + d3-selection "1" + d3-dsv@1: version "1.0.8" resolved "https://registry.yarnpkg.com/d3-dsv/-/d3-dsv-1.0.8.tgz#907e240d57b386618dc56468bacfe76bf19764ae" @@ -3673,6 +3690,10 @@ d3-dsv@1: iconv-lite "0.4" rw "1" +d3-ease@1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-1.0.5.tgz#8ce59276d81241b1b72042d6af2d40e76d936ffb" + d3-force@1: version "1.1.0" resolved "https://registry.yarnpkg.com/d3-force/-/d3-force-1.1.0.tgz#cebf3c694f1078fcc3d4daf8e567b2fbd70d4ea3" @@ -3769,6 +3790,10 @@ d3-scale@^1.0.5: d3-time "1" d3-time-format "2" +d3-selection@1, d3-selection@^1.1.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-1.3.2.tgz#6e70a9df60801c8af28ac24d10072d82cbfdf652" + d3-shape@1, d3-shape@^1.1.0, d3-shape@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.2.0.tgz#45d01538f064bafd05ea3d6d2cb748fd8c41f777" @@ -3789,10 +3814,35 @@ d3-timer@1: version "1.0.7" resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-1.0.7.tgz#df9650ca587f6c96607ff4e60cc38229e8dd8531" +d3-timer@^1.0.5: + version "1.0.9" + resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-1.0.9.tgz#f7bb8c0d597d792ff7131e1c24a36dd471a471ba" + +d3-transition@1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-1.1.3.tgz#3a435b05ce9cef9524fe0d38121cfb6905331ca6" + dependencies: + d3-color "1" + d3-dispatch "1" + d3-ease "1" + d3-interpolate "1" + d3-selection "^1.1.0" + d3-timer "1" + d3-voronoi@1, d3-voronoi@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/d3-voronoi/-/d3-voronoi-1.1.2.tgz#1687667e8f13a2d158c80c1480c5a29cb0d8973c" +d3-zoom@^1.1.4: + version "1.7.3" + resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-1.7.3.tgz#f444effdc9055c38077c4299b4df999eb1d47ccb" + dependencies: + d3-dispatch "1" + d3-drag "1" + d3-interpolate "1" + d3-selection "1" + d3-transition "1" + d3@3.5.6: version "3.5.6" resolved "https://registry.yarnpkg.com/d3/-/d3-3.5.6.tgz#9451c651ca733fb9672c81fb7f2655164a73a42d" @@ -4289,6 +4339,12 @@ ecc-jsbn@~0.1.1: dependencies: jsbn "~0.1.0" +ecdsa-sig-formatter@1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz#1c595000f04a8897dfb85000892a0f4c33af86c3" + dependencies: + safe-buffer "^5.0.1" + editions@^1.1.1, editions@^1.3.4: version "1.3.4" resolved "https://registry.yarnpkg.com/editions/-/editions-1.3.4.tgz#3662cb592347c3168eb8e498a0ff73271d67f50b" @@ -5488,6 +5544,10 @@ forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" +form-data-to-object@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/form-data-to-object/-/form-data-to-object-0.2.0.tgz#f7a8e68ddd910a1100a65e25ac6a484143ff8168" + form-data@^2.3.1, form-data@~2.3.1: version "2.3.2" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.2.tgz#4970498be604c20c005d4f5c23aecd21d6b49099" @@ -5508,6 +5568,13 @@ formidable@^1.1.1: version "1.2.1" resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.2.1.tgz#70fb7ca0290ee6ff961090415f4b3df3d2082659" +formsy-react@^1.1.4: + version "1.1.5" + resolved "https://registry.yarnpkg.com/formsy-react/-/formsy-react-1.1.5.tgz#ee0911bb70712eb6fb9924d56fdb974a19006955" + dependencies: + form-data-to-object "^0.2.0" + prop-types "^15.5.10" + fragment-cache@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" @@ -5603,6 +5670,12 @@ fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2: mkdirp ">=0.5 0" rimraf "2" +fullscreen@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/fullscreen/-/fullscreen-1.1.1.tgz#b9017c3bf9b23e07effd1bbce910cf5ec2459129" + dependencies: + add-event-listener "0.0.1" + function-bind@^1.0.2, function-bind@^1.1.0, function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" @@ -6001,6 +6074,19 @@ graceful-fs@4.X, graceful-fs@^4.0.0, graceful-fs@^4.1.10, graceful-fs@^4.1.11, g version "1.0.1" resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" +graphlib-dot@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/graphlib-dot/-/graphlib-dot-0.6.2.tgz#872e933d0bb349430cb813a2491943d1c5f1e952" + dependencies: + graphlib "^1.0.1" + lodash "^2.4.1" + +graphlib@^1.0.1: + version "1.0.7" + resolved "https://registry.yarnpkg.com/graphlib/-/graphlib-1.0.7.tgz#0cab2df0ffe6abe070b2625bfa1edb6ec967b8b1" + dependencies: + lodash "^3.10.0" + growl@1.9.2: version "1.9.2" resolved "https://registry.yarnpkg.com/growl/-/growl-1.9.2.tgz#0ea7743715db8d8de2c5ede1775e1b45ac85c02f" @@ -6536,6 +6622,13 @@ http-errors@~1.3.1: inherits "~2.0.1" statuses "1" +http-errors@~1.4.0: + version "1.4.0" + resolved "http://registry.npmjs.org/http-errors/-/http-errors-1.4.0.tgz#6c0242dea6b3df7afda153c71089b31c6e82aabf" + dependencies: + inherits "2.0.1" + statuses ">= 1.2.1 < 2" + http-parser-js@>=0.4.0: version "0.4.9" resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.4.9.tgz#ea1a04fb64adff0242e9974f297dd4c3cad271e1" @@ -7841,7 +7934,7 @@ jpeg-js@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.2.0.tgz#53e448ec9d263e683266467e9442d2c5a2ef5482" -jquery@^3.3.1: +jquery@^3.1.1, jquery@^3.3.1: version "3.3.1" resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.3.1.tgz#958ce29e81c9790f31be7792df5d4d95fc57fbca" @@ -8037,6 +8130,20 @@ jsonpointer@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" +jsonwebtoken@^8.3.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.3.0.tgz#056c90eee9a65ed6e6c72ddb0a1d325109aaf643" + dependencies: + jws "^3.1.5" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.1.1" + jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -8089,6 +8196,21 @@ just-reduce-object@^1.0.3: version "1.1.0" resolved "https://registry.yarnpkg.com/just-reduce-object/-/just-reduce-object-1.1.0.tgz#d29d172264f8511c74462de30d72d5838b6967e6" +jwa@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.1.6.tgz#87240e76c9808dbde18783cf2264ef4929ee50e6" + dependencies: + buffer-equal-constant-time "1.0.1" + ecdsa-sig-formatter "1.0.10" + safe-buffer "^5.0.1" + +jws@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.1.5.tgz#80d12d05b293d1e841e7cb8b4e69e561adcf834f" + dependencies: + jwa "^1.1.5" + safe-buffer "^5.0.1" + karma-chrome-launcher@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/karma-chrome-launcher/-/karma-chrome-launcher-2.1.1.tgz#216879c68ac04d8d5140e99619ba04b59afd46cf" @@ -8612,6 +8734,10 @@ lodash.get@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + lodash.isarguments@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" @@ -8620,6 +8746,10 @@ lodash.isarray@^3.0.0: version "3.0.4" resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + lodash.isempty@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.isempty/-/lodash.isempty-4.4.0.tgz#6f86cbedd8be4ec987be9aaf33c9684db1b31e7e" @@ -8628,10 +8758,22 @@ lodash.isequal@^4.0.0, lodash.isequal@^4.1.1, lodash.isequal@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + lodash.isobject@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/lodash.isobject/-/lodash.isobject-3.0.2.tgz#3c8fb8d5b5bf4bf90ae06e14f2a530a4ed935e1d" +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + lodash.isstring@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" @@ -8684,6 +8826,10 @@ lodash.omitby@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.omitby/-/lodash.omitby-4.6.0.tgz#5c15ff4754ad555016b53c041311e8f079204791" +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + lodash.orderby@4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.orderby/-/lodash.orderby-4.6.0.tgz#e697f04ce5d78522f54d9338b32b81a3393e4eb3" @@ -8766,9 +8912,9 @@ lodash.uniqueid@^3.0.0: dependencies: lodash._root "^3.0.0" -lodash@2.4.2, lodash@~2.4.1: +lodash@2.4.2, lodash@^2.4.1, lodash@~2.4.1: version "2.4.2" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-2.4.2.tgz#fadd834b9683073da179b3eae6d9c0d15053f73e" + resolved "http://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz#fadd834b9683073da179b3eae6d9c0d15053f73e" lodash@3.10.1, lodash@^3.10.0, lodash@^3.10.1, lodash@^3.3.1, lodash@^3.8.0, lodash@~3.10.1: version "3.10.1" @@ -9277,10 +9423,6 @@ moment@2.x.x, "moment@>= 2.9.0", moment@^2.10.6, moment@^2.13.0, moment@^2.20.1: version "2.21.0" resolved "https://registry.yarnpkg.com/moment/-/moment-2.21.0.tgz#2a114b51d2a6ec9e6d83cf803f838a878d8a023a" -moment@>=2.14.0: - version "2.22.2" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66" - move-concurrently@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" @@ -9304,7 +9446,7 @@ ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" -ms@^2.0.0: +ms@^2.0.0, ms@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" @@ -10146,11 +10288,18 @@ path-key@^2.0.0, path-key@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" +path-match@1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/path-match/-/path-match-1.2.4.tgz#a62747f3c7e0c2514762697f24443585b09100ea" + dependencies: + http-errors "~1.4.0" + path-to-regexp "^1.0.0" + path-parse@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" -path-to-regexp@^1.7.0: +path-to-regexp@^1.0.0, path-to-regexp@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d" dependencies: @@ -12884,6 +13033,10 @@ statuses@1, "statuses@>= 1.3.1 < 2": version "1.4.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087" +"statuses@>= 1.2.1 < 2": + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + statuses@~1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" @@ -14577,6 +14730,18 @@ wcwidth@^1.0.1: dependencies: defaults "^1.0.3" +webcola@3.3.6: + version "3.3.6" + resolved "https://registry.yarnpkg.com/webcola/-/webcola-3.3.6.tgz#6ec0bc7a72b3c467a2f2346a8667d88b439a03b4" + dependencies: + d3-dispatch "^1.0.3" + d3-drag "^1.0.4" + d3-timer "^1.0.5" + d3-zoom "^1.1.4" + fullscreen "^1.1.0" + graphlib-dot "^0.6.2" + jquery "^3.1.1" + webidl-conversions@^4.0.1, webidl-conversions@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" From 77dbe529d36af17f62ef80882b76ca80ec3058e3 Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Thu, 4 Oct 2018 12:45:02 -0400 Subject: [PATCH 54/94] fix prettier --- x-pack/plugins/beats_management/public/pages/beat/detail.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/beats_management/public/pages/beat/detail.tsx b/x-pack/plugins/beats_management/public/pages/beat/detail.tsx index 1a5d260b89d93..915cbb9d1f91a 100644 --- a/x-pack/plugins/beats_management/public/pages/beat/detail.tsx +++ b/x-pack/plugins/beats_management/public/pages/beat/detail.tsx @@ -32,7 +32,6 @@ export const BeatDetailPage = (props: BeatDetailPageProps) => { } const configurationBlocks = flatten( beat.full_tags.map((tag: BeatTag) => { - return tag.configuration_blocks.map(configuration => ({ // @ts-ignore one of the types on ConfigurationBlock doesn't define a "module" property module: configuration.configs[0].module || null, From 07d915edf7d3b7554f94d0b22d9ba2cba287d00c Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Mon, 8 Oct 2018 21:44:12 +0100 Subject: [PATCH 55/94] fix x-pack package.json formatting --- x-pack/package.json | 173 +++++++++++++++++++-------------------- yarn.lock | 193 ++++---------------------------------------- 2 files changed, 95 insertions(+), 271 deletions(-) diff --git a/x-pack/package.json b/x-pack/package.json index de029871d8191..a1dc4986433d2 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -1,19 +1,94 @@ { "name": "x-pack", "version": "7.0.0-alpha1", + "author": "Elastic", "private": true, "license": "Elastic-License", - "author": "Elastic", "scripts": { - "build": "gulp build", "preinstall": "node ../preinstall_check", "kbn": "node ../scripts/kbn", "start": "gulp dev", + "build": "gulp build", + "testonly": "gulp testonly", "test": "gulp test", - "test:browser": "gulp testbrowser", "test:browser:dev": "gulp testbrowser-dev", - "test:server": "gulp testserver", - "testonly": "gulp testonly" + "test:browser": "gulp testbrowser", + "test:server": "gulp testserver" + }, + "kibana": { + "build": { + "intermediateBuildDirectory": "build/plugin/kibana/x-pack" + } + }, + "devDependencies": { + "@kbn/dev-utils": "link:../packages/kbn-dev-utils", + "@kbn/es": "link:../packages/kbn-es", + "@kbn/plugin-helpers": "link:../packages/kbn-plugin-helpers", + "@kbn/test": "link:../packages/kbn-test", + "@types/expect.js": "^0.3.29", + "@types/jest": "^23.3.1", + "@types/joi": "^10.4.4", + "@types/mocha": "^5.2.5", + "@types/pngjs": "^3.3.1", + "@types/supertest": "^2.0.5", + "abab": "^1.0.4", + "ansi-colors": "^3.0.5", + "ansicolors": "0.3.2", + "aws-sdk": "2.2.33", + "axios": "^0.18.0", + "babel-jest": "^23.4.2", + "babel-plugin-inline-react-svg": "^0.5.4", + "babel-plugin-mock-imports": "^0.0.5", + "babel-plugin-pegjs-inline-precompile": "^0.1.0", + "babel-plugin-transform-react-remove-prop-types": "^0.4.14", + "babel-register": "^6.26.0", + "chalk": "^2.4.1", + "chance": "1.0.10", + "checksum": "0.1.1", + "commander": "2.12.2", + "copy-webpack-plugin": "^4.5.2", + "del": "^3.0.0", + "dotenv": "2.0.0", + "enzyme": "3.2.0", + "enzyme-adapter-react-16": "^1.1.1", + "enzyme-to-json": "3.3.1", + "expect.js": "0.3.1", + "fancy-log": "^1.3.2", + "fetch-mock": "^5.13.1", + "gulp": "3.9.1", + "gulp-mocha": "2.2.0", + "gulp-multi-process": "^1.3.1", + "gulp-pegjs": "^0.1.0", + "hapi": "14.2.0", + "jest": "^23.5.0", + "jest-cli": "^23.5.0", + "jest-styled-components": "^6.1.1", + "jsdom": "^12.0.0", + "mocha": "3.3.0", + "mustache": "^2.3.0", + "mutation-observer": "^1.0.3", + "node-fetch": "^2.1.2", + "pdf-image": "2.0.0", + "pdfjs-dist": "^2.0.489", + "pixelmatch": "4.0.2", + "proxyquire": "1.7.11", + "react-test-renderer": "^16.2.0", + "redux-test-utils": "0.2.2", + "rsync": "0.4.0", + "run-sequence": "^2.2.1", + "sass-loader": "^7.1.0", + "simple-git": "1.37.0", + "sinon": "^5.0.7", + "squel": "^5.12.2", + "supertest": "^3.1.0", + "supertest-as-promised": "^4.0.2", + "tmp": "0.0.31", + "tree-kill": "^1.1.0", + "typescript": "^3.0.3", + "vinyl-fs": "^3.0.2", + "xml-crypto": "^0.10.1", + "xml2js": "^0.4.19", + "yargs": "4.7.1" }, "dependencies": { "@elastic/datemath": "^4.0.2", @@ -27,7 +102,7 @@ "@samverschueren/stream-to-observable": "^0.3.0", "@scant/router": "^0.1.0", "@slack/client": "^4.2.2", - "angular-paging": "2.2.1", + "@types/moment-timezone": "^0.5.8", "angular-resource": "1.4.9", "angular-sanitize": "1.4.9", "angular-ui-ace": "0.2.3", @@ -51,7 +126,6 @@ "extract-zip": "1.5.0", "file-saver": "^1.3.8", "font-awesome": "4.4.0", - "formsy-react": "^1.1.4", "get-port": "2.1.0", "getos": "^3.1.0", "glob": "6.0.4", @@ -64,7 +138,6 @@ "isomorphic-fetch": "2.2.1", "joi": "6.10.1", "jquery": "^3.3.1", - "jsonwebtoken": "^8.3.0", "jstimezonedetect": "1.0.5", "lodash": "3.10.1", "lodash.clone": "^4.5.0", @@ -87,7 +160,6 @@ "nodemailer": "^4.6.4", "object-path-immutable": "^0.5.3", "papaparse": "^4.6.0", - "path-match": "1.2.4", "pdfmake": "0.1.33", "pivotal-ui": "13.0.1", "pluralize": "3.1.0", @@ -140,92 +212,9 @@ "unbzip2-stream": "1.0.9", "uuid": "3.0.1", "venn.js": "0.2.9", - "webcola": "3.3.6", "xregexp": "3.2.0" }, - "devDependencies": { - "@kbn/dev-utils": "link:../packages/kbn-dev-utils", - "@kbn/es": "link:../packages/kbn-es", - "@kbn/plugin-helpers": "link:../packages/kbn-plugin-helpers", - "@kbn/test": "link:../packages/kbn-test", - "@types/boom": "^4.3.8", - "@types/expect.js": "^0.3.29", - "@types/hapi": "15.0.1", - "@types/jest": "^23.3.1", - "@types/joi": "^10.4.4", - "@types/jsonwebtoken": "^7.2.8", - "@types/lodash": "^3.10.0", - "@types/mocha": "^5.2.5", - "@types/moment-timezone": "^0.5.8", - "@types/pngjs": "^3.3.1", - "@types/react-router-dom": "^4.2.7", - "@types/supertest": "^2.0.5", - "@types/uuid": "^3.4.3", - "abab": "^1.0.4", - "ansi-colors": "^3.0.5", - "ansicolors": "0.3.2", - "aws-sdk": "2.2.33", - "axios": "^0.18.0", - "babel-jest": "^23.4.2", - "babel-plugin-inline-react-svg": "^0.5.4", - "babel-plugin-mock-imports": "^0.0.5", - "babel-plugin-pegjs-inline-precompile": "^0.1.0", - "babel-plugin-transform-react-remove-prop-types": "^0.4.14", - "babel-register": "^6.26.0", - "chalk": "^2.4.1", - "chance": "1.0.10", - "checksum": "0.1.1", - "commander": "2.12.2", - "copy-webpack-plugin": "^4.5.2", - "del": "^3.0.0", - "dotenv": "2.0.0", - "enzyme": "3.2.0", - "enzyme-adapter-react-16": "^1.1.1", - "enzyme-to-json": "3.3.1", - "expect.js": "0.3.1", - "fancy-log": "^1.3.2", - "fetch-mock": "^5.13.1", - "gulp": "3.9.1", - "gulp-mocha": "2.2.0", - "gulp-multi-process": "^1.3.1", - "gulp-pegjs": "^0.1.0", - "hapi": "14.2.0", - "jest": "^23.5.0", - "jest-cli": "^23.5.0", - "jest-styled-components": "^6.1.1", - "jsdom": "^12.0.0", - "mocha": "3.3.0", - "mustache": "^2.3.0", - "mutation-observer": "^1.0.3", - "node-fetch": "^2.1.2", - "pdf-image": "2.0.0", - "pdfjs-dist": "^2.0.489", - "pixelmatch": "4.0.2", - "proxyquire": "1.7.11", - "react-test-renderer": "^16.2.0", - "redux-test-utils": "0.2.2", - "rsync": "0.4.0", - "run-sequence": "^2.2.1", - "sass-loader": "^7.1.0", - "simple-git": "1.37.0", - "sinon": "^5.0.7", - "squel": "^5.12.2", - "supertest": "^3.1.0", - "supertest-as-promised": "^4.0.2", - "tmp": "0.0.31", - "tree-kill": "^1.1.0", - "typescript": "^3.0.3", - "vinyl-fs": "^3.0.2", - "xml-crypto": "^0.10.1", - "xml2js": "^0.4.19", - "yargs": "4.7.1" - }, "engines": { "yarn": "^1.6.0" - }, - "kibana": { - "build": { - "intermediateBuildDirectory": "build/plugin/kibana/x-pack" - } } } diff --git a/yarn.lock b/yarn.lock index 0ea053c712a75..60588453d5b89 100644 --- a/yarn.lock +++ b/yarn.lock @@ -525,6 +525,12 @@ version "2.0.29" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-2.0.29.tgz#5002e14f75e2d71e564281df0431c8c1b4a2a36a" +"@types/moment-timezone@^0.5.8": + version "0.5.9" + resolved "https://registry.yarnpkg.com/@types/moment-timezone/-/moment-timezone-0.5.9.tgz#1a34ef6ba2e4578b9ca399ed5eec57525ad1d4a7" + dependencies: + moment ">=2.14.0" + "@types/node@*": version "9.4.7" resolved "https://registry.yarnpkg.com/@types/node/-/node-9.4.7.tgz#57d81cd98719df2c9de118f2d5f3b1120dcd7275" @@ -740,10 +746,6 @@ acorn@^5.0.3, acorn@^5.6.0: version "5.7.3" resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279" -add-event-listener@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/add-event-listener/-/add-event-listener-0.0.1.tgz#a76229ebc64c8aefae204a16273a2f255abea2d0" - adm-zip@0.4.11: version "0.4.11" resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.4.11.tgz#2aa54c84c4b01a9d0fb89bb11982a51f13e3d62a" @@ -861,10 +863,6 @@ angular-mocks@1.4.7: version "1.4.7" resolved "https://registry.yarnpkg.com/angular-mocks/-/angular-mocks-1.4.7.tgz#d7343ee0a033f9216770bda573950f6814d95227" -angular-paging@2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/angular-paging/-/angular-paging-2.2.1.tgz#8090864f71bc4c9b89307b02ab02afb205983c43" - angular-recursion@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/angular-recursion/-/angular-recursion-1.0.5.tgz#cd405428a0bf55faf52eaa7988c1fe69cd930543" @@ -2356,10 +2354,6 @@ buffer-crc32@~0.2.3: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" -buffer-equal-constant-time@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" - buffer-equal@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-0.0.1.tgz#91bc74b11ea405bc916bc6aa908faafa5b4aac4b" @@ -3671,17 +3665,6 @@ d3-dispatch@1: version "1.0.3" resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-1.0.3.tgz#46e1491eaa9b58c358fce5be4e8bed626e7871f8" -d3-dispatch@^1.0.3: - version "1.0.5" - resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-1.0.5.tgz#e25c10a186517cd6c82dd19ea018f07e01e39015" - -d3-drag@1, d3-drag@^1.0.4: - version "1.2.3" - resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-1.2.3.tgz#46e206ad863ec465d88c588098a1df444cd33c64" - dependencies: - d3-dispatch "1" - d3-selection "1" - d3-dsv@1: version "1.0.8" resolved "https://registry.yarnpkg.com/d3-dsv/-/d3-dsv-1.0.8.tgz#907e240d57b386618dc56468bacfe76bf19764ae" @@ -3690,10 +3673,6 @@ d3-dsv@1: iconv-lite "0.4" rw "1" -d3-ease@1: - version "1.0.5" - resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-1.0.5.tgz#8ce59276d81241b1b72042d6af2d40e76d936ffb" - d3-force@1: version "1.1.0" resolved "https://registry.yarnpkg.com/d3-force/-/d3-force-1.1.0.tgz#cebf3c694f1078fcc3d4daf8e567b2fbd70d4ea3" @@ -3790,10 +3769,6 @@ d3-scale@^1.0.5: d3-time "1" d3-time-format "2" -d3-selection@1, d3-selection@^1.1.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-1.3.2.tgz#6e70a9df60801c8af28ac24d10072d82cbfdf652" - d3-shape@1, d3-shape@^1.1.0, d3-shape@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.2.0.tgz#45d01538f064bafd05ea3d6d2cb748fd8c41f777" @@ -3814,35 +3789,10 @@ d3-timer@1: version "1.0.7" resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-1.0.7.tgz#df9650ca587f6c96607ff4e60cc38229e8dd8531" -d3-timer@^1.0.5: - version "1.0.9" - resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-1.0.9.tgz#f7bb8c0d597d792ff7131e1c24a36dd471a471ba" - -d3-transition@1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-1.1.3.tgz#3a435b05ce9cef9524fe0d38121cfb6905331ca6" - dependencies: - d3-color "1" - d3-dispatch "1" - d3-ease "1" - d3-interpolate "1" - d3-selection "^1.1.0" - d3-timer "1" - d3-voronoi@1, d3-voronoi@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/d3-voronoi/-/d3-voronoi-1.1.2.tgz#1687667e8f13a2d158c80c1480c5a29cb0d8973c" -d3-zoom@^1.1.4: - version "1.7.3" - resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-1.7.3.tgz#f444effdc9055c38077c4299b4df999eb1d47ccb" - dependencies: - d3-dispatch "1" - d3-drag "1" - d3-interpolate "1" - d3-selection "1" - d3-transition "1" - d3@3.5.6: version "3.5.6" resolved "https://registry.yarnpkg.com/d3/-/d3-3.5.6.tgz#9451c651ca733fb9672c81fb7f2655164a73a42d" @@ -4339,12 +4289,6 @@ ecc-jsbn@~0.1.1: dependencies: jsbn "~0.1.0" -ecdsa-sig-formatter@1.0.10: - version "1.0.10" - resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz#1c595000f04a8897dfb85000892a0f4c33af86c3" - dependencies: - safe-buffer "^5.0.1" - editions@^1.1.1, editions@^1.3.4: version "1.3.4" resolved "https://registry.yarnpkg.com/editions/-/editions-1.3.4.tgz#3662cb592347c3168eb8e498a0ff73271d67f50b" @@ -5544,10 +5488,6 @@ forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" -form-data-to-object@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/form-data-to-object/-/form-data-to-object-0.2.0.tgz#f7a8e68ddd910a1100a65e25ac6a484143ff8168" - form-data@^2.3.1, form-data@~2.3.1: version "2.3.2" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.2.tgz#4970498be604c20c005d4f5c23aecd21d6b49099" @@ -5568,13 +5508,6 @@ formidable@^1.1.1: version "1.2.1" resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.2.1.tgz#70fb7ca0290ee6ff961090415f4b3df3d2082659" -formsy-react@^1.1.4: - version "1.1.5" - resolved "https://registry.yarnpkg.com/formsy-react/-/formsy-react-1.1.5.tgz#ee0911bb70712eb6fb9924d56fdb974a19006955" - dependencies: - form-data-to-object "^0.2.0" - prop-types "^15.5.10" - fragment-cache@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" @@ -5670,12 +5603,6 @@ fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2: mkdirp ">=0.5 0" rimraf "2" -fullscreen@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/fullscreen/-/fullscreen-1.1.1.tgz#b9017c3bf9b23e07effd1bbce910cf5ec2459129" - dependencies: - add-event-listener "0.0.1" - function-bind@^1.0.2, function-bind@^1.1.0, function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" @@ -6074,19 +6001,6 @@ graceful-fs@4.X, graceful-fs@^4.0.0, graceful-fs@^4.1.10, graceful-fs@^4.1.11, g version "1.0.1" resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" -graphlib-dot@^0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/graphlib-dot/-/graphlib-dot-0.6.2.tgz#872e933d0bb349430cb813a2491943d1c5f1e952" - dependencies: - graphlib "^1.0.1" - lodash "^2.4.1" - -graphlib@^1.0.1: - version "1.0.7" - resolved "https://registry.yarnpkg.com/graphlib/-/graphlib-1.0.7.tgz#0cab2df0ffe6abe070b2625bfa1edb6ec967b8b1" - dependencies: - lodash "^3.10.0" - growl@1.9.2: version "1.9.2" resolved "https://registry.yarnpkg.com/growl/-/growl-1.9.2.tgz#0ea7743715db8d8de2c5ede1775e1b45ac85c02f" @@ -6622,13 +6536,6 @@ http-errors@~1.3.1: inherits "~2.0.1" statuses "1" -http-errors@~1.4.0: - version "1.4.0" - resolved "http://registry.npmjs.org/http-errors/-/http-errors-1.4.0.tgz#6c0242dea6b3df7afda153c71089b31c6e82aabf" - dependencies: - inherits "2.0.1" - statuses ">= 1.2.1 < 2" - http-parser-js@>=0.4.0: version "0.4.9" resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.4.9.tgz#ea1a04fb64adff0242e9974f297dd4c3cad271e1" @@ -7934,7 +7841,7 @@ jpeg-js@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.2.0.tgz#53e448ec9d263e683266467e9442d2c5a2ef5482" -jquery@^3.1.1, jquery@^3.3.1: +jquery@^3.3.1: version "3.3.1" resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.3.1.tgz#958ce29e81c9790f31be7792df5d4d95fc57fbca" @@ -8130,20 +8037,6 @@ jsonpointer@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" -jsonwebtoken@^8.3.0: - version "8.3.0" - resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.3.0.tgz#056c90eee9a65ed6e6c72ddb0a1d325109aaf643" - dependencies: - jws "^3.1.5" - lodash.includes "^4.3.0" - lodash.isboolean "^3.0.3" - lodash.isinteger "^4.0.4" - lodash.isnumber "^3.0.3" - lodash.isplainobject "^4.0.6" - lodash.isstring "^4.0.1" - lodash.once "^4.0.0" - ms "^2.1.1" - jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -8196,21 +8089,6 @@ just-reduce-object@^1.0.3: version "1.1.0" resolved "https://registry.yarnpkg.com/just-reduce-object/-/just-reduce-object-1.1.0.tgz#d29d172264f8511c74462de30d72d5838b6967e6" -jwa@^1.1.5: - version "1.1.6" - resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.1.6.tgz#87240e76c9808dbde18783cf2264ef4929ee50e6" - dependencies: - buffer-equal-constant-time "1.0.1" - ecdsa-sig-formatter "1.0.10" - safe-buffer "^5.0.1" - -jws@^3.1.5: - version "3.1.5" - resolved "https://registry.yarnpkg.com/jws/-/jws-3.1.5.tgz#80d12d05b293d1e841e7cb8b4e69e561adcf834f" - dependencies: - jwa "^1.1.5" - safe-buffer "^5.0.1" - karma-chrome-launcher@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/karma-chrome-launcher/-/karma-chrome-launcher-2.1.1.tgz#216879c68ac04d8d5140e99619ba04b59afd46cf" @@ -8734,10 +8612,6 @@ lodash.get@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" -lodash.includes@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" - lodash.isarguments@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" @@ -8746,10 +8620,6 @@ lodash.isarray@^3.0.0: version "3.0.4" resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" -lodash.isboolean@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" - lodash.isempty@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.isempty/-/lodash.isempty-4.4.0.tgz#6f86cbedd8be4ec987be9aaf33c9684db1b31e7e" @@ -8758,22 +8628,10 @@ lodash.isequal@^4.0.0, lodash.isequal@^4.1.1, lodash.isequal@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" -lodash.isinteger@^4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" - -lodash.isnumber@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" - lodash.isobject@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/lodash.isobject/-/lodash.isobject-3.0.2.tgz#3c8fb8d5b5bf4bf90ae06e14f2a530a4ed935e1d" -lodash.isplainobject@^4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" - lodash.isstring@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" @@ -8826,10 +8684,6 @@ lodash.omitby@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.omitby/-/lodash.omitby-4.6.0.tgz#5c15ff4754ad555016b53c041311e8f079204791" -lodash.once@^4.0.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" - lodash.orderby@4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.orderby/-/lodash.orderby-4.6.0.tgz#e697f04ce5d78522f54d9338b32b81a3393e4eb3" @@ -8912,7 +8766,7 @@ lodash.uniqueid@^3.0.0: dependencies: lodash._root "^3.0.0" -lodash@2.4.2, lodash@^2.4.1, lodash@~2.4.1: +lodash@2.4.2, lodash@~2.4.1: version "2.4.2" resolved "http://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz#fadd834b9683073da179b3eae6d9c0d15053f73e" @@ -9427,6 +9281,10 @@ moment@2.x.x, "moment@>= 2.9.0", moment@^2.10.6, moment@^2.13.0, moment@^2.20.1: version "2.21.0" resolved "https://registry.yarnpkg.com/moment/-/moment-2.21.0.tgz#2a114b51d2a6ec9e6d83cf803f838a878d8a023a" +moment@>=2.14.0: + version "2.22.2" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66" + move-concurrently@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" @@ -9450,7 +9308,7 @@ ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" -ms@^2.0.0, ms@^2.1.1: +ms@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" @@ -10292,18 +10150,11 @@ path-key@^2.0.0, path-key@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" -path-match@1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/path-match/-/path-match-1.2.4.tgz#a62747f3c7e0c2514762697f24443585b09100ea" - dependencies: - http-errors "~1.4.0" - path-to-regexp "^1.0.0" - path-parse@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" -path-to-regexp@^1.0.0, path-to-regexp@^1.7.0: +path-to-regexp@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d" dependencies: @@ -13037,10 +12888,6 @@ statuses@1, "statuses@>= 1.3.1 < 2": version "1.4.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087" -"statuses@>= 1.2.1 < 2": - version "1.5.0" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" - statuses@~1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" @@ -14734,18 +14581,6 @@ wcwidth@^1.0.1: dependencies: defaults "^1.0.3" -webcola@3.3.6: - version "3.3.6" - resolved "https://registry.yarnpkg.com/webcola/-/webcola-3.3.6.tgz#6ec0bc7a72b3c467a2f2346a8667d88b439a03b4" - dependencies: - d3-dispatch "^1.0.3" - d3-drag "^1.0.4" - d3-timer "^1.0.5" - d3-zoom "^1.1.4" - fullscreen "^1.1.0" - graphlib-dot "^0.6.2" - jquery "^3.1.1" - webidl-conversions@^4.0.1, webidl-conversions@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" From 3e6d4753dcc0daef4cd65b24cbc24579d8a8d646 Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Tue, 9 Oct 2018 13:11:40 +0100 Subject: [PATCH 56/94] update kbn server creation --- .../server/lib/adapters/database/__tests__/kibana.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/beats_management/server/lib/adapters/database/__tests__/kibana.test.ts b/x-pack/plugins/beats_management/server/lib/adapters/database/__tests__/kibana.test.ts index c18c5b5bc4c76..33e4046d1d63e 100644 --- a/x-pack/plugins/beats_management/server/lib/adapters/database/__tests__/kibana.test.ts +++ b/x-pack/plugins/beats_management/server/lib/adapters/database/__tests__/kibana.test.ts @@ -13,7 +13,8 @@ import { DatabaseKbnESPlugin } from '../adapter_types'; import { KibanaDatabaseAdapter } from '../kibana_database_adapter'; import { contractTests } from './test_contract'; -const kbnServer = kbnTestServer.createServerWithCorePlugins(); +const kbnServer = kbnTestServer.getKbnServer(kbnTestServer.createRootWithCorePlugins()); + const es = createEsTestCluster({}); contractTests('Kibana Database Adapter', { From bd117a43c1387524145f7a35f14959e1ba0c2046 Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Tue, 9 Oct 2018 13:17:54 +0100 Subject: [PATCH 57/94] remove types from old table implementation --- .../public/components/table/table.tsx | 24 ++----------------- .../public/pages/beat/tags.tsx | 1 + 2 files changed, 3 insertions(+), 22 deletions(-) diff --git a/x-pack/plugins/beats_management/public/components/table/table.tsx b/x-pack/plugins/beats_management/public/components/table/table.tsx index 7cdbe14905077..b07182beaeefb 100644 --- a/x-pack/plugins/beats_management/public/components/table/table.tsx +++ b/x-pack/plugins/beats_management/public/components/table/table.tsx @@ -13,26 +13,6 @@ import React from 'react'; import styled from 'styled-components'; import { TABLE_CONFIG } from '../../../common/constants'; import { ControlBar } from './controls'; -import { TableType } from './table_type_configs'; - -interface TableProps { - assignmentOptions?: any[] | null; - assignmentTitle?: string | null; - items: any[]; - renderAssignmentOptions?: (item: any, key: string) => any; - showAssignmentOptions: boolean; - type: TableType; - - isLoadingSuggestions: boolean; - loadSuggestions: any; - onKueryBarSubmit: any; - isKueryValid: any; - kueryValue: any; - onKueryBarChange: any; - suggestions: any; - filterQueryDraft: any; - actionHandler(action: string, payload?: any): void; -} interface TableState { selection: any[]; @@ -42,8 +22,8 @@ const TableContainer = styled.div` padding: 16px; `; -export class Table extends React.Component { - constructor(props: TableProps) { +export class Table extends React.Component { + constructor(props: any) { super(props); this.state = { diff --git a/x-pack/plugins/beats_management/public/pages/beat/tags.tsx b/x-pack/plugins/beats_management/public/pages/beat/tags.tsx index 3f5bec3c27a63..847160b2fb312 100644 --- a/x-pack/plugins/beats_management/public/pages/beat/tags.tsx +++ b/x-pack/plugins/beats_management/public/pages/beat/tags.tsx @@ -51,6 +51,7 @@ export class BeatTagsPage extends React.PureComponent + this.setState({ notifications: [] })} From 1cc3296a5b11640bdc0186542c844bec16e20de9 Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Tue, 9 Oct 2018 13:19:21 +0100 Subject: [PATCH 58/94] move to Boom.boomify --- .../server/utils/error_wrappers/wrap_es_error.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/beats_management/server/utils/error_wrappers/wrap_es_error.ts b/x-pack/plugins/beats_management/server/utils/error_wrappers/wrap_es_error.ts index 30328f2c6a833..b8202ded5d2d2 100644 --- a/x-pack/plugins/beats_management/server/utils/error_wrappers/wrap_es_error.ts +++ b/x-pack/plugins/beats_management/server/utils/error_wrappers/wrap_es_error.ts @@ -18,5 +18,5 @@ export function wrapEsError(err: any) { if (statusCode === 403) { return Boom.forbidden('Insufficient user permissions for managing Beats configuration'); } - return Boom.wrap(err, err.statusCode); + return Boom.boomify(err, { statusCode: err.statusCode }); } From d38123b6f0fc354dd5e8cd7700d57657ee2a2c70 Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Tue, 9 Oct 2018 13:20:15 +0100 Subject: [PATCH 59/94] fix TS errors --- package.json | 6 +- x-pack/package.json | 2 +- .../public/components/tag/tag_edit.tsx | 2 +- .../framework/__tests__/kibana.test.ts | 2 +- .../server/rest_api/__tests__/test_harnes.ts | 3 +- .../plugins/beats_management/types/json.t.ts | 10 -- x-pack/yarn.lock | 142 +++++++++++------- yarn.lock | 135 ++++++++++++++++- 8 files changed, 225 insertions(+), 77 deletions(-) delete mode 100644 x-pack/plugins/beats_management/types/json.t.ts diff --git a/package.json b/package.json index bb81e4908cfe6..f88afd94b3903 100644 --- a/package.json +++ b/package.json @@ -217,6 +217,8 @@ "@types/angular": "^1.6.50", "@types/angular-mocks": "^1.7.0", "@octokit/rest": "^15.10.0", + "@types/jsonwebtoken": "^7.2.8", + "@types/react-router-dom": "^4.3.1", "@types/babel-core": "^6.25.5", "@types/bluebird": "^3.1.1", "@types/boom": "^7.2.0", @@ -231,8 +233,6 @@ "@types/glob": "^5.0.35", "@types/hapi-latest": "npm:@types/hapi@17.0.12", "@types/has-ansi": "^3.0.0", - "@types/jest": "^23.3.1", - "@types/joi": "^10.4.4", "@types/jquery": "^3.3.6", "@types/js-yaml": "^3.11.1", "@types/listr": "^0.13.0", @@ -250,7 +250,6 @@ "@types/semver": "^5.5.0", "@types/sinon": "^5.0.1", "@types/strip-ansi": "^3.0.0", - "@types/supertest": "^2.0.5", "@types/type-detect": "^4.0.1", "@types/uuid": "^3.4.4", "angular-mocks": "1.4.7", @@ -301,6 +300,7 @@ "jest-raw-loader": "^1.0.1", "jimp": "0.2.28", "json5": "^1.0.1", + "jsonwebtoken": "^8.3.0", "karma": "1.7.0", "karma-chrome-launcher": "2.1.1", "karma-coverage": "1.1.1", diff --git a/x-pack/package.json b/x-pack/package.json index a1dc4986433d2..8bd26d51f8338 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -180,7 +180,7 @@ "react-redux": "^5.0.7", "react-redux-request": "^1.5.6", "react-router-breadcrumbs-hoc": "1.1.2", - "react-router-dom": "^4.2.2", + "react-router-dom": "^4.3.1", "react-select": "^1.2.1", "react-shortcuts": "^2.0.0", "react-sticky": "^6.0.1", diff --git a/x-pack/plugins/beats_management/public/components/tag/tag_edit.tsx b/x-pack/plugins/beats_management/public/components/tag/tag_edit.tsx index 2726a83ac1b61..6763a837daa9f 100644 --- a/x-pack/plugins/beats_management/public/components/tag/tag_edit.tsx +++ b/x-pack/plugins/beats_management/public/components/tag/tag_edit.tsx @@ -159,7 +159,7 @@ export class TagEdit extends React.PureComponent {

Attached Beats

{ + actionHandler={() => { /* TODO: this prop should be optional */ }} assignmentOptions={[]} diff --git a/x-pack/plugins/beats_management/server/lib/adapters/framework/__tests__/kibana.test.ts b/x-pack/plugins/beats_management/server/lib/adapters/framework/__tests__/kibana.test.ts index 5a539ebe6e5e7..54cc0c0de1af6 100644 --- a/x-pack/plugins/beats_management/server/lib/adapters/framework/__tests__/kibana.test.ts +++ b/x-pack/plugins/beats_management/server/lib/adapters/framework/__tests__/kibana.test.ts @@ -13,7 +13,7 @@ import * as kbnTestServer from '../../../../../../../../src/test_utils/kbn_serve import { KibanaBackendFrameworkAdapter } from '../kibana_framework_adapter'; import { contractTests } from './test_contract'; -const kbnServer = kbnTestServer.createServerWithCorePlugins(); +const kbnServer = kbnTestServer.getKbnServer(kbnTestServer.createRootWithCorePlugins()); contractTests('Kibana Framework Adapter', { before: async () => { diff --git a/x-pack/plugins/beats_management/server/rest_api/__tests__/test_harnes.ts b/x-pack/plugins/beats_management/server/rest_api/__tests__/test_harnes.ts index 7f1fa9fa5e620..18b59e0d9aeee 100644 --- a/x-pack/plugins/beats_management/server/rest_api/__tests__/test_harnes.ts +++ b/x-pack/plugins/beats_management/server/rest_api/__tests__/test_harnes.ts @@ -6,6 +6,7 @@ import { badRequest } from 'boom'; import { readFile } from 'fs'; +// @ts-ignore import Hapi from 'hapi'; import { resolve } from 'path'; import { promisify } from 'util'; @@ -82,7 +83,7 @@ export const testHarnes = { const versionHeader = 'kbn-version'; const xsrfHeader = 'kbn-xsrf'; - server.ext('onPostAuth', (req, reply) => { + server.ext('onPostAuth', (req: any, reply: any) => { const isSafeMethod = req.method === 'get' || req.method === 'head'; const hasVersionHeader = versionHeader in req.headers; const hasXsrfHeader = xsrfHeader in req.headers; diff --git a/x-pack/plugins/beats_management/types/json.t.ts b/x-pack/plugins/beats_management/types/json.t.ts deleted file mode 100644 index 46af99f7f740b..0000000000000 --- a/x-pack/plugins/beats_management/types/json.t.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -declare module '*.json' { - const value: any; - export default value; -} diff --git a/x-pack/yarn.lock b/x-pack/yarn.lock index 04c5942c8c965..242cec6ae12bc 100644 --- a/x-pack/yarn.lock +++ b/x-pack/yarn.lock @@ -143,10 +143,6 @@ url-join "^4.0.0" ws "^4.1.0" -"@types/cookiejar@*": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@types/cookiejar/-/cookiejar-2.1.0.tgz#4b7daf2c51696cfc70b942c11690528229d1a1ce" - "@types/delay@^2.0.1": version "2.0.1" resolved "https://registry.yarnpkg.com/@types/delay/-/delay-2.0.1.tgz#61bcf318a74b61e79d1658fbf054f984c90ef901" @@ -155,10 +151,6 @@ version "1.2.0" resolved "https://registry.yarnpkg.com/@types/events/-/events-1.2.0.tgz#81a6731ce4df43619e5c8c945383b3e62a89ea86" -"@types/expect.js@^0.3.29": - version "0.3.29" - resolved "https://registry.yarnpkg.com/@types/expect.js/-/expect.js-0.3.29.tgz#28dd359155b84b8ecb094afc3f4b74c3222dca3b" - "@types/form-data@^2.2.1": version "2.2.1" resolved "https://registry.yarnpkg.com/@types/form-data/-/form-data-2.2.1.tgz#ee2b3b8eaa11c0938289953606b745b738c54b1e" @@ -177,22 +169,10 @@ dependencies: "@types/node" "*" -"@types/jest@^23.3.1": - version "23.3.1" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-23.3.1.tgz#a4319aedb071d478e6f407d1c4578ec8156829cf" - -"@types/joi@^10.4.4": - version "10.6.4" - resolved "https://registry.yarnpkg.com/@types/joi/-/joi-10.6.4.tgz#0989d69e792a7db13e951852e6949df6787f113f" - "@types/loglevel@^1.5.3": version "1.5.3" resolved "https://registry.yarnpkg.com/@types/loglevel/-/loglevel-1.5.3.tgz#adfce55383edc5998a2170ad581b3e23d6adb5b8" -"@types/mocha@^5.2.5": - version "5.2.5" - resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.5.tgz#8a4accfc403c124a0bafe8a9fc61a05ec1032073" - "@types/moment-timezone@^0.5.8": version "0.5.8" resolved "https://registry.yarnpkg.com/@types/moment-timezone/-/moment-timezone-0.5.8.tgz#92aba9bc238cabf69a27a1a4f52e0ebb8f10f896" @@ -221,29 +201,10 @@ dependencies: "@types/retry" "*" -"@types/pngjs@^3.3.1": - version "3.3.1" - resolved "https://registry.yarnpkg.com/@types/pngjs/-/pngjs-3.3.1.tgz#47d97bd29dd6372856050e9e5e366517dd1ba2d8" - dependencies: - "@types/node" "*" - "@types/retry@*", "@types/retry@^0.10.2": version "0.10.2" resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.10.2.tgz#bd1740c4ad51966609b058803ee6874577848b37" -"@types/superagent@*": - version "3.8.4" - resolved "https://registry.yarnpkg.com/@types/superagent/-/superagent-3.8.4.tgz#24a5973c7d1a9c024b4bbda742a79267c33fb86a" - dependencies: - "@types/cookiejar" "*" - "@types/node" "*" - -"@types/supertest@^2.0.5": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@types/supertest/-/supertest-2.0.5.tgz#18d082a667eaed22759be98f4923e0061ae70c62" - dependencies: - "@types/superagent" "*" - "@types/url-join@^0.8.2": version "0.8.2" resolved "https://registry.yarnpkg.com/@types/url-join/-/url-join-0.8.2.tgz#1181ecbe1d97b7034e0ea1e35e62e86cc26b422d" @@ -1411,6 +1372,10 @@ buffer-crc32@~0.2.3: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" +buffer-equal-constant-time@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + buffer-equal@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-0.0.1.tgz#91bc74b11ea405bc916bc6aa908faafa5b4aac4b" @@ -2469,6 +2434,12 @@ ecc-jsbn@~0.1.1: dependencies: jsbn "~0.1.0" +ecdsa-sig-formatter@1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz#1c595000f04a8897dfb85000892a0f4c33af86c3" + dependencies: + safe-buffer "^5.0.1" + elasticsearch@^15.1.1: version "15.1.1" resolved "https://registry.yarnpkg.com/elasticsearch/-/elasticsearch-15.1.1.tgz#242f2378fccd601586ff763a8a933cd5d41c945f" @@ -3908,10 +3879,6 @@ hoek@4.x.x: version "4.2.0" resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.0.tgz#72d9d0754f7fe25ca2d01ad8f8f9a9449a89526d" -hoist-non-react-statics@^2.3.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.3.1.tgz#343db84c6018c650778898240135a1420ee22ce0" - hoist-non-react-statics@^2.3.1, hoist-non-react-statics@^2.5.0, hoist-non-react-statics@^2.5.5: version "2.5.5" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47" @@ -5040,6 +5007,20 @@ jsonpointer@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" +jsonwebtoken@^8.3.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.3.0.tgz#056c90eee9a65ed6e6c72ddb0a1d325109aaf643" + dependencies: + jws "^3.1.5" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.1.1" + jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -5061,6 +5042,21 @@ just-reduce-object@^1.0.3: version "1.1.0" resolved "https://registry.yarnpkg.com/just-reduce-object/-/just-reduce-object-1.1.0.tgz#d29d172264f8511c74462de30d72d5838b6967e6" +jwa@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.1.6.tgz#87240e76c9808dbde18783cf2264ef4929ee50e6" + dependencies: + buffer-equal-constant-time "1.0.1" + ecdsa-sig-formatter "1.0.10" + safe-buffer "^5.0.1" + +jws@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.1.5.tgz#80d12d05b293d1e841e7cb8b4e69e561adcf834f" + dependencies: + jwa "^1.1.5" + safe-buffer "^5.0.1" + keymirror@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/keymirror/-/keymirror-0.1.1.tgz#918889ea13f8d0a42e7c557250eee713adc95c35" @@ -5334,6 +5330,10 @@ lodash.get@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + lodash.isarguments@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" @@ -5342,10 +5342,22 @@ lodash.isarray@^3.0.0: version "3.0.4" resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + lodash.isequal@^4.1.1, lodash.isequal@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + lodash.isobject@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/lodash.isobject/-/lodash.isobject-3.0.2.tgz#3c8fb8d5b5bf4bf90ae06e14f2a530a4ed935e1d" @@ -5354,6 +5366,10 @@ lodash.isplainobject@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + lodash.istypedarray@^3.0.0: version "3.0.6" resolved "https://registry.yarnpkg.com/lodash.istypedarray/-/lodash.istypedarray-3.0.6.tgz#c9a477498607501d8e8494d283b87c39281cef62" @@ -5386,6 +5402,10 @@ lodash.omitby@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.omitby/-/lodash.omitby-4.6.0.tgz#5c15ff4754ad555016b53c041311e8f079204791" +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + lodash.orderby@4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.orderby/-/lodash.orderby-4.6.0.tgz#e697f04ce5d78522f54d9338b32b81a3393e4eb3" @@ -5905,7 +5925,7 @@ ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" -ms@^2.0.0: +ms@^2.0.0, ms@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" @@ -7239,28 +7259,28 @@ react-router-breadcrumbs-hoc@1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/react-router-breadcrumbs-hoc/-/react-router-breadcrumbs-hoc-1.1.2.tgz#4fafb620e7c6b876d98f7151f4c85ae5c3157dc0" -react-router-dom@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-4.2.2.tgz#c8a81df3adc58bba8a76782e946cbd4eae649b8d" +react-router-dom@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-4.3.1.tgz#4c2619fc24c4fa87c9fd18f4fb4a43fe63fbd5c6" dependencies: history "^4.7.2" - invariant "^2.2.2" + invariant "^2.2.4" loose-envify "^1.3.1" - prop-types "^15.5.4" - react-router "^4.2.0" - warning "^3.0.0" + prop-types "^15.6.1" + react-router "^4.3.1" + warning "^4.0.1" -react-router@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-4.2.0.tgz#61f7b3e3770daeb24062dae3eedef1b054155986" +react-router@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-4.3.1.tgz#aada4aef14c809cb2e686b05cee4742234506c4e" dependencies: history "^4.7.2" - hoist-non-react-statics "^2.3.0" - invariant "^2.2.2" + hoist-non-react-statics "^2.5.0" + invariant "^2.2.4" loose-envify "^1.3.1" path-to-regexp "^1.7.0" - prop-types "^15.5.4" - warning "^3.0.0" + prop-types "^15.6.1" + warning "^4.0.1" react-select@^1.2.1: version "1.2.1" @@ -9321,6 +9341,12 @@ warning@^3.0.0: dependencies: loose-envify "^1.0.0" +warning@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.2.tgz#aa6876480872116fa3e11d434b0d0d8d91e44607" + dependencies: + loose-envify "^1.0.0" + watch@~0.18.0: version "0.18.0" resolved "https://registry.yarnpkg.com/watch/-/watch-0.18.0.tgz#28095476c6df7c90c963138990c0a5423eb4b986" diff --git a/yarn.lock b/yarn.lock index 60588453d5b89..9ac9360bbe154 100644 --- a/yarn.lock +++ b/yarn.lock @@ -408,6 +408,10 @@ dependencies: "@types/node" "*" +"@types/expect.js@^0.3.29": + version "0.3.29" + resolved "https://registry.yarnpkg.com/@types/expect.js/-/expect.js-0.3.29.tgz#28dd359155b84b8ecb094afc3f4b74c3222dca3b" + "@types/fetch-mock@^5.12.2": version "5.12.2" resolved "https://registry.yarnpkg.com/@types/fetch-mock/-/fetch-mock-5.12.2.tgz#8c96517ff74303031c65c5da2d99858e34c844d2" @@ -453,6 +457,10 @@ version "3.0.0" resolved "https://registry.yarnpkg.com/@types/has-ansi/-/has-ansi-3.0.0.tgz#636403dc4e0b2649421c4158e5c404416f3f0330" +"@types/history@*": + version "4.7.0" + resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.0.tgz#2fac51050c68f7d6f96c5aafc631132522f4aa3f" + "@types/iron@*": version "5.0.1" resolved "https://registry.yarnpkg.com/@types/iron/-/iron-5.0.1.tgz#5420bbda8623c48ee51b9a78ebad05d7305b4b24" @@ -493,6 +501,12 @@ version "1.0.32" resolved "https://registry.yarnpkg.com/@types/json-stable-stringify/-/json-stable-stringify-1.0.32.tgz#121f6917c4389db3923640b2e68de5fa64dda88e" +"@types/jsonwebtoken@^7.2.8": + version "7.2.8" + resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-7.2.8.tgz#8d199dab4ddb5bba3234f8311b804d2027af2b3a" + dependencies: + "@types/node" "*" + "@types/listr@^0.13.0": version "0.13.0" resolved "https://registry.yarnpkg.com/@types/listr/-/listr-0.13.0.tgz#6250bc4a04123cafa24fc73d1b880653a6ae6721" @@ -525,6 +539,10 @@ version "2.0.29" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-2.0.29.tgz#5002e14f75e2d71e564281df0431c8c1b4a2a36a" +"@types/mocha@^5.2.5": + version "5.2.5" + resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.5.tgz#8a4accfc403c124a0bafe8a9fc61a05ec1032073" + "@types/moment-timezone@^0.5.8": version "0.5.9" resolved "https://registry.yarnpkg.com/@types/moment-timezone/-/moment-timezone-0.5.9.tgz#1a34ef6ba2e4578b9ca399ed5eec57525ad1d4a7" @@ -557,6 +575,12 @@ dependencies: "@types/retry" "*" +"@types/pngjs@^3.3.1": + version "3.3.2" + resolved "https://registry.yarnpkg.com/@types/pngjs/-/pngjs-3.3.2.tgz#8ed3bd655ab3a92ea32ada7a21f618e63b93b1d4" + dependencies: + "@types/node" "*" + "@types/podium@*": version "1.0.0" resolved "https://registry.yarnpkg.com/@types/podium/-/podium-1.0.0.tgz#bfaa2151be2b1d6109cc69f7faa9dac2cba3bb20" @@ -592,6 +616,21 @@ "@types/react" "*" redux "^4.0.0" +"@types/react-router-dom@^4.3.1": + version "4.3.1" + resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-4.3.1.tgz#71fe2918f8f60474a891520def40a63997dafe04" + dependencies: + "@types/history" "*" + "@types/react" "*" + "@types/react-router" "*" + +"@types/react-router@*": + version "4.0.31" + resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-4.0.31.tgz#416bac49d746800810886c7b8582a622ed9604fc" + dependencies: + "@types/history" "*" + "@types/react" "*" + "@types/react-virtualized@^9.18.7": version "9.18.7" resolved "https://registry.yarnpkg.com/@types/react-virtualized/-/react-virtualized-9.18.7.tgz#8703d8904236819facff90b8b320f29233160c90" @@ -2354,6 +2393,10 @@ buffer-crc32@~0.2.3: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" +buffer-equal-constant-time@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + buffer-equal@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-0.0.1.tgz#91bc74b11ea405bc916bc6aa908faafa5b4aac4b" @@ -4289,6 +4332,12 @@ ecc-jsbn@~0.1.1: dependencies: jsbn "~0.1.0" +ecdsa-sig-formatter@1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz#1c595000f04a8897dfb85000892a0f4c33af86c3" + dependencies: + safe-buffer "^5.0.1" + editions@^1.1.1, editions@^1.3.4: version "1.3.4" resolved "https://registry.yarnpkg.com/editions/-/editions-1.3.4.tgz#3662cb592347c3168eb8e498a0ff73271d67f50b" @@ -8037,6 +8086,20 @@ jsonpointer@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" +jsonwebtoken@^8.3.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.3.0.tgz#056c90eee9a65ed6e6c72ddb0a1d325109aaf643" + dependencies: + jws "^3.1.5" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.1.1" + jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -8089,6 +8152,21 @@ just-reduce-object@^1.0.3: version "1.1.0" resolved "https://registry.yarnpkg.com/just-reduce-object/-/just-reduce-object-1.1.0.tgz#d29d172264f8511c74462de30d72d5838b6967e6" +jwa@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.1.6.tgz#87240e76c9808dbde18783cf2264ef4929ee50e6" + dependencies: + buffer-equal-constant-time "1.0.1" + ecdsa-sig-formatter "1.0.10" + safe-buffer "^5.0.1" + +jws@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.1.5.tgz#80d12d05b293d1e841e7cb8b4e69e561adcf834f" + dependencies: + jwa "^1.1.5" + safe-buffer "^5.0.1" + karma-chrome-launcher@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/karma-chrome-launcher/-/karma-chrome-launcher-2.1.1.tgz#216879c68ac04d8d5140e99619ba04b59afd46cf" @@ -8612,6 +8690,10 @@ lodash.get@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + lodash.isarguments@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" @@ -8620,6 +8702,10 @@ lodash.isarray@^3.0.0: version "3.0.4" resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + lodash.isempty@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.isempty/-/lodash.isempty-4.4.0.tgz#6f86cbedd8be4ec987be9aaf33c9684db1b31e7e" @@ -8628,10 +8714,22 @@ lodash.isequal@^4.0.0, lodash.isequal@^4.1.1, lodash.isequal@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + lodash.isobject@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/lodash.isobject/-/lodash.isobject-3.0.2.tgz#3c8fb8d5b5bf4bf90ae06e14f2a530a4ed935e1d" +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + lodash.isstring@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" @@ -8684,6 +8782,10 @@ lodash.omitby@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.omitby/-/lodash.omitby-4.6.0.tgz#5c15ff4754ad555016b53c041311e8f079204791" +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + lodash.orderby@4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.orderby/-/lodash.orderby-4.6.0.tgz#e697f04ce5d78522f54d9338b32b81a3393e4eb3" @@ -9308,7 +9410,7 @@ ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" -ms@^2.0.0: +ms@^2.0.0, ms@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" @@ -11360,7 +11462,7 @@ react-router-breadcrumbs-hoc@1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/react-router-breadcrumbs-hoc/-/react-router-breadcrumbs-hoc-1.1.2.tgz#4fafb620e7c6b876d98f7151f4c85ae5c3157dc0" -react-router-dom@4.2.2, react-router-dom@^4.2.2: +react-router-dom@4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-4.2.2.tgz#c8a81df3adc58bba8a76782e946cbd4eae649b8d" dependencies: @@ -11371,6 +11473,17 @@ react-router-dom@4.2.2, react-router-dom@^4.2.2: react-router "^4.2.0" warning "^3.0.0" +react-router-dom@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-4.3.1.tgz#4c2619fc24c4fa87c9fd18f4fb4a43fe63fbd5c6" + dependencies: + history "^4.7.2" + invariant "^2.2.4" + loose-envify "^1.3.1" + prop-types "^15.6.1" + react-router "^4.3.1" + warning "^4.0.1" + react-router@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/react-router/-/react-router-4.2.0.tgz#61f7b3e3770daeb24062dae3eedef1b054155986" @@ -11383,6 +11496,18 @@ react-router@^4.2.0: prop-types "^15.5.4" warning "^3.0.0" +react-router@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-4.3.1.tgz#aada4aef14c809cb2e686b05cee4742234506c4e" + dependencies: + history "^4.7.2" + hoist-non-react-statics "^2.5.0" + invariant "^2.2.4" + loose-envify "^1.3.1" + path-to-regexp "^1.7.0" + prop-types "^15.6.1" + warning "^4.0.1" + react-select@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/react-select/-/react-select-1.2.1.tgz#a2fe58a569eb14dcaa6543816260b97e538120d1" @@ -14560,6 +14685,12 @@ warning@^3.0.0: dependencies: loose-envify "^1.0.0" +warning@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.2.tgz#aa6876480872116fa3e11d434b0d0d8d91e44607" + dependencies: + loose-envify "^1.0.0" + watch@~0.18.0: version "0.18.0" resolved "https://registry.yarnpkg.com/watch/-/watch-0.18.0.tgz#28095476c6df7c90c963138990c0a5423eb4b986" From c1468a5b2dbc58adbd4272ac07f9a61a7fc5a132 Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Tue, 9 Oct 2018 14:06:55 +0100 Subject: [PATCH 60/94] fix type --- x-pack/plugins/beats_management/public/pages/main/tags.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/beats_management/public/pages/main/tags.tsx b/x-pack/plugins/beats_management/public/pages/main/tags.tsx index f561390b9a0aa..cc46e5f7a3fa6 100644 --- a/x-pack/plugins/beats_management/public/pages/main/tags.tsx +++ b/x-pack/plugins/beats_management/public/pages/main/tags.tsx @@ -71,7 +71,7 @@ export class TagsPage extends React.PureComponent filterQueryDraft={'false'} // todo actionHandler={this.handleTagsAction} items={this.state.tags || []} - renderAssignmentOptions={item => item} + renderAssignmentOptions={(item: any) => item} ref={this.tableRef} showAssignmentOptions={true} type={TagsTableType} From 0639f3369361d2a8b9ce0121961e7297917ae682 Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Tue, 9 Oct 2018 14:08:10 +0100 Subject: [PATCH 61/94] rollback dep version --- x-pack/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/package.json b/x-pack/package.json index 8bd26d51f8338..a1dc4986433d2 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -180,7 +180,7 @@ "react-redux": "^5.0.7", "react-redux-request": "^1.5.6", "react-router-breadcrumbs-hoc": "1.1.2", - "react-router-dom": "^4.3.1", + "react-router-dom": "^4.2.2", "react-select": "^1.2.1", "react-shortcuts": "^2.0.0", "react-sticky": "^6.0.1", From edd7e00600be214a6ea89bec5c982cbe04a5114e Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Tue, 9 Oct 2018 14:28:26 +0100 Subject: [PATCH 62/94] fix more conflicting dep issues --- package.json | 8 ++-- x-pack/yarn.lock | 106 ++++++++++++++++++----------------------------- yarn.lock | 97 +++++-------------------------------------- 3 files changed, 56 insertions(+), 155 deletions(-) diff --git a/package.json b/package.json index f88afd94b3903..0a2575d917a09 100644 --- a/package.json +++ b/package.json @@ -217,8 +217,6 @@ "@types/angular": "^1.6.50", "@types/angular-mocks": "^1.7.0", "@octokit/rest": "^15.10.0", - "@types/jsonwebtoken": "^7.2.8", - "@types/react-router-dom": "^4.3.1", "@types/babel-core": "^6.25.5", "@types/bluebird": "^3.1.1", "@types/boom": "^7.2.0", @@ -233,8 +231,11 @@ "@types/glob": "^5.0.35", "@types/hapi-latest": "npm:@types/hapi@17.0.12", "@types/has-ansi": "^3.0.0", + "@types/jest": "^23.3.1", + "@types/joi": "^10.4.4", "@types/jquery": "^3.3.6", "@types/js-yaml": "^3.11.1", + "@types/jsonwebtoken": "^7.2.8", "@types/listr": "^0.13.0", "@types/lodash": "^3.10.1", "@types/minimatch": "^2.0.29", @@ -244,12 +245,14 @@ "@types/react": "^16.3.14", "@types/react-dom": "^16.0.5", "@types/react-redux": "^6.0.6", + "@types/react-router-dom": "^4.3.1", "@types/react-virtualized": "^9.18.7", "@types/redux": "^3.6.31", "@types/redux-actions": "^2.2.1", "@types/semver": "^5.5.0", "@types/sinon": "^5.0.1", "@types/strip-ansi": "^3.0.0", + "@types/supertest": "^2.0.5", "@types/type-detect": "^4.0.1", "@types/uuid": "^3.4.4", "angular-mocks": "1.4.7", @@ -300,7 +303,6 @@ "jest-raw-loader": "^1.0.1", "jimp": "0.2.28", "json5": "^1.0.1", - "jsonwebtoken": "^8.3.0", "karma": "1.7.0", "karma-chrome-launcher": "2.1.1", "karma-coverage": "1.1.1", diff --git a/x-pack/yarn.lock b/x-pack/yarn.lock index 242cec6ae12bc..5bf491b00e441 100644 --- a/x-pack/yarn.lock +++ b/x-pack/yarn.lock @@ -143,6 +143,10 @@ url-join "^4.0.0" ws "^4.1.0" +"@types/cookiejar@*": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@types/cookiejar/-/cookiejar-2.1.0.tgz#4b7daf2c51696cfc70b942c11690528229d1a1ce" + "@types/delay@^2.0.1": version "2.0.1" resolved "https://registry.yarnpkg.com/@types/delay/-/delay-2.0.1.tgz#61bcf318a74b61e79d1658fbf054f984c90ef901" @@ -151,6 +155,10 @@ version "1.2.0" resolved "https://registry.yarnpkg.com/@types/events/-/events-1.2.0.tgz#81a6731ce4df43619e5c8c945383b3e62a89ea86" +"@types/expect.js@^0.3.29": + version "0.3.29" + resolved "https://registry.yarnpkg.com/@types/expect.js/-/expect.js-0.3.29.tgz#28dd359155b84b8ecb094afc3f4b74c3222dca3b" + "@types/form-data@^2.2.1": version "2.2.1" resolved "https://registry.yarnpkg.com/@types/form-data/-/form-data-2.2.1.tgz#ee2b3b8eaa11c0938289953606b745b738c54b1e" @@ -169,10 +177,22 @@ dependencies: "@types/node" "*" +"@types/jest@^23.3.1": + version "23.3.4" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-23.3.4.tgz#cc43ae176a91dcb1504839b0b9d6659386cf0af5" + +"@types/joi@^10.4.4": + version "10.6.4" + resolved "https://registry.yarnpkg.com/@types/joi/-/joi-10.6.4.tgz#0989d69e792a7db13e951852e6949df6787f113f" + "@types/loglevel@^1.5.3": version "1.5.3" resolved "https://registry.yarnpkg.com/@types/loglevel/-/loglevel-1.5.3.tgz#adfce55383edc5998a2170ad581b3e23d6adb5b8" +"@types/mocha@^5.2.5": + version "5.2.5" + resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.5.tgz#8a4accfc403c124a0bafe8a9fc61a05ec1032073" + "@types/moment-timezone@^0.5.8": version "0.5.8" resolved "https://registry.yarnpkg.com/@types/moment-timezone/-/moment-timezone-0.5.8.tgz#92aba9bc238cabf69a27a1a4f52e0ebb8f10f896" @@ -201,10 +221,29 @@ dependencies: "@types/retry" "*" +"@types/pngjs@^3.3.1": + version "3.3.2" + resolved "https://registry.yarnpkg.com/@types/pngjs/-/pngjs-3.3.2.tgz#8ed3bd655ab3a92ea32ada7a21f618e63b93b1d4" + dependencies: + "@types/node" "*" + "@types/retry@*", "@types/retry@^0.10.2": version "0.10.2" resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.10.2.tgz#bd1740c4ad51966609b058803ee6874577848b37" +"@types/superagent@*": + version "3.8.4" + resolved "https://registry.yarnpkg.com/@types/superagent/-/superagent-3.8.4.tgz#24a5973c7d1a9c024b4bbda742a79267c33fb86a" + dependencies: + "@types/cookiejar" "*" + "@types/node" "*" + +"@types/supertest@^2.0.5": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/supertest/-/supertest-2.0.6.tgz#a0665350c0e36315e1bccdf4785f2b76fcb71b6b" + dependencies: + "@types/superagent" "*" + "@types/url-join@^0.8.2": version "0.8.2" resolved "https://registry.yarnpkg.com/@types/url-join/-/url-join-0.8.2.tgz#1181ecbe1d97b7034e0ea1e35e62e86cc26b422d" @@ -1372,10 +1411,6 @@ buffer-crc32@~0.2.3: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" -buffer-equal-constant-time@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" - buffer-equal@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-0.0.1.tgz#91bc74b11ea405bc916bc6aa908faafa5b4aac4b" @@ -2434,12 +2469,6 @@ ecc-jsbn@~0.1.1: dependencies: jsbn "~0.1.0" -ecdsa-sig-formatter@1.0.10: - version "1.0.10" - resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz#1c595000f04a8897dfb85000892a0f4c33af86c3" - dependencies: - safe-buffer "^5.0.1" - elasticsearch@^15.1.1: version "15.1.1" resolved "https://registry.yarnpkg.com/elasticsearch/-/elasticsearch-15.1.1.tgz#242f2378fccd601586ff763a8a933cd5d41c945f" @@ -5007,20 +5036,6 @@ jsonpointer@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" -jsonwebtoken@^8.3.0: - version "8.3.0" - resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.3.0.tgz#056c90eee9a65ed6e6c72ddb0a1d325109aaf643" - dependencies: - jws "^3.1.5" - lodash.includes "^4.3.0" - lodash.isboolean "^3.0.3" - lodash.isinteger "^4.0.4" - lodash.isnumber "^3.0.3" - lodash.isplainobject "^4.0.6" - lodash.isstring "^4.0.1" - lodash.once "^4.0.0" - ms "^2.1.1" - jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -5042,21 +5057,6 @@ just-reduce-object@^1.0.3: version "1.1.0" resolved "https://registry.yarnpkg.com/just-reduce-object/-/just-reduce-object-1.1.0.tgz#d29d172264f8511c74462de30d72d5838b6967e6" -jwa@^1.1.5: - version "1.1.6" - resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.1.6.tgz#87240e76c9808dbde18783cf2264ef4929ee50e6" - dependencies: - buffer-equal-constant-time "1.0.1" - ecdsa-sig-formatter "1.0.10" - safe-buffer "^5.0.1" - -jws@^3.1.5: - version "3.1.5" - resolved "https://registry.yarnpkg.com/jws/-/jws-3.1.5.tgz#80d12d05b293d1e841e7cb8b4e69e561adcf834f" - dependencies: - jwa "^1.1.5" - safe-buffer "^5.0.1" - keymirror@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/keymirror/-/keymirror-0.1.1.tgz#918889ea13f8d0a42e7c557250eee713adc95c35" @@ -5330,10 +5330,6 @@ lodash.get@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" -lodash.includes@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" - lodash.isarguments@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" @@ -5342,22 +5338,10 @@ lodash.isarray@^3.0.0: version "3.0.4" resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" -lodash.isboolean@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" - lodash.isequal@^4.1.1, lodash.isequal@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" -lodash.isinteger@^4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" - -lodash.isnumber@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" - lodash.isobject@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/lodash.isobject/-/lodash.isobject-3.0.2.tgz#3c8fb8d5b5bf4bf90ae06e14f2a530a4ed935e1d" @@ -5366,10 +5350,6 @@ lodash.isplainobject@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" -lodash.isstring@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" - lodash.istypedarray@^3.0.0: version "3.0.6" resolved "https://registry.yarnpkg.com/lodash.istypedarray/-/lodash.istypedarray-3.0.6.tgz#c9a477498607501d8e8494d283b87c39281cef62" @@ -5402,10 +5382,6 @@ lodash.omitby@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.omitby/-/lodash.omitby-4.6.0.tgz#5c15ff4754ad555016b53c041311e8f079204791" -lodash.once@^4.0.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" - lodash.orderby@4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.orderby/-/lodash.orderby-4.6.0.tgz#e697f04ce5d78522f54d9338b32b81a3393e4eb3" @@ -5925,7 +5901,7 @@ ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" -ms@^2.0.0, ms@^2.1.1: +ms@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" @@ -7259,7 +7235,7 @@ react-router-breadcrumbs-hoc@1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/react-router-breadcrumbs-hoc/-/react-router-breadcrumbs-hoc-1.1.2.tgz#4fafb620e7c6b876d98f7151f4c85ae5c3157dc0" -react-router-dom@^4.3.1: +react-router-dom@^4.2.2: version "4.3.1" resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-4.3.1.tgz#4c2619fc24c4fa87c9fd18f4fb4a43fe63fbd5c6" dependencies: diff --git a/yarn.lock b/yarn.lock index 9ac9360bbe154..3a5cb783fc67f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -408,10 +408,6 @@ dependencies: "@types/node" "*" -"@types/expect.js@^0.3.29": - version "0.3.29" - resolved "https://registry.yarnpkg.com/@types/expect.js/-/expect.js-0.3.29.tgz#28dd359155b84b8ecb094afc3f4b74c3222dca3b" - "@types/fetch-mock@^5.12.2": version "5.12.2" resolved "https://registry.yarnpkg.com/@types/fetch-mock/-/fetch-mock-5.12.2.tgz#8c96517ff74303031c65c5da2d99858e34c844d2" @@ -474,16 +470,16 @@ "@types/node" "*" "@types/jest@^23.3.1": - version "23.3.1" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-23.3.1.tgz#a4319aedb071d478e6f407d1c4578ec8156829cf" + version "23.3.4" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-23.3.4.tgz#cc43ae176a91dcb1504839b0b9d6659386cf0af5" "@types/joi@*": version "13.3.0" resolved "https://registry.yarnpkg.com/@types/joi/-/joi-13.3.0.tgz#bdfa2e49d8d258ba79f23304228d0c4d5cfc848c" "@types/joi@^10.4.4": - version "10.6.2" - resolved "https://registry.yarnpkg.com/@types/joi/-/joi-10.6.2.tgz#0e7d632fe918c337784e87b16c7cc0098876179a" + version "10.6.4" + resolved "https://registry.yarnpkg.com/@types/joi/-/joi-10.6.4.tgz#0989d69e792a7db13e951852e6949df6787f113f" "@types/jquery@^3.3.6": version "3.3.6" @@ -539,10 +535,6 @@ version "2.0.29" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-2.0.29.tgz#5002e14f75e2d71e564281df0431c8c1b4a2a36a" -"@types/mocha@^5.2.5": - version "5.2.5" - resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.5.tgz#8a4accfc403c124a0bafe8a9fc61a05ec1032073" - "@types/moment-timezone@^0.5.8": version "0.5.9" resolved "https://registry.yarnpkg.com/@types/moment-timezone/-/moment-timezone-0.5.9.tgz#1a34ef6ba2e4578b9ca399ed5eec57525ad1d4a7" @@ -575,12 +567,6 @@ dependencies: "@types/retry" "*" -"@types/pngjs@^3.3.1": - version "3.3.2" - resolved "https://registry.yarnpkg.com/@types/pngjs/-/pngjs-3.3.2.tgz#8ed3bd655ab3a92ea32ada7a21f618e63b93b1d4" - dependencies: - "@types/node" "*" - "@types/podium@*": version "1.0.0" resolved "https://registry.yarnpkg.com/@types/podium/-/podium-1.0.0.tgz#bfaa2151be2b1d6109cc69f7faa9dac2cba3bb20" @@ -675,15 +661,15 @@ resolved "https://registry.yarnpkg.com/@types/strip-ansi/-/strip-ansi-3.0.0.tgz#9b63d453a6b54aa849182207711a08be8eea48ae" "@types/superagent@*": - version "3.8.2" - resolved "https://registry.yarnpkg.com/@types/superagent/-/superagent-3.8.2.tgz#ffdda92843f8966fb4c5f482755ee641ffc53aa7" + version "3.8.4" + resolved "https://registry.yarnpkg.com/@types/superagent/-/superagent-3.8.4.tgz#24a5973c7d1a9c024b4bbda742a79267c33fb86a" dependencies: "@types/cookiejar" "*" "@types/node" "*" "@types/supertest@^2.0.5": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@types/supertest/-/supertest-2.0.5.tgz#18d082a667eaed22759be98f4923e0061ae70c62" + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/supertest/-/supertest-2.0.6.tgz#a0665350c0e36315e1bccdf4785f2b76fcb71b6b" dependencies: "@types/superagent" "*" @@ -2393,10 +2379,6 @@ buffer-crc32@~0.2.3: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" -buffer-equal-constant-time@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" - buffer-equal@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-0.0.1.tgz#91bc74b11ea405bc916bc6aa908faafa5b4aac4b" @@ -4332,12 +4314,6 @@ ecc-jsbn@~0.1.1: dependencies: jsbn "~0.1.0" -ecdsa-sig-formatter@1.0.10: - version "1.0.10" - resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz#1c595000f04a8897dfb85000892a0f4c33af86c3" - dependencies: - safe-buffer "^5.0.1" - editions@^1.1.1, editions@^1.3.4: version "1.3.4" resolved "https://registry.yarnpkg.com/editions/-/editions-1.3.4.tgz#3662cb592347c3168eb8e498a0ff73271d67f50b" @@ -8086,20 +8062,6 @@ jsonpointer@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" -jsonwebtoken@^8.3.0: - version "8.3.0" - resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.3.0.tgz#056c90eee9a65ed6e6c72ddb0a1d325109aaf643" - dependencies: - jws "^3.1.5" - lodash.includes "^4.3.0" - lodash.isboolean "^3.0.3" - lodash.isinteger "^4.0.4" - lodash.isnumber "^3.0.3" - lodash.isplainobject "^4.0.6" - lodash.isstring "^4.0.1" - lodash.once "^4.0.0" - ms "^2.1.1" - jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -8152,21 +8114,6 @@ just-reduce-object@^1.0.3: version "1.1.0" resolved "https://registry.yarnpkg.com/just-reduce-object/-/just-reduce-object-1.1.0.tgz#d29d172264f8511c74462de30d72d5838b6967e6" -jwa@^1.1.5: - version "1.1.6" - resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.1.6.tgz#87240e76c9808dbde18783cf2264ef4929ee50e6" - dependencies: - buffer-equal-constant-time "1.0.1" - ecdsa-sig-formatter "1.0.10" - safe-buffer "^5.0.1" - -jws@^3.1.5: - version "3.1.5" - resolved "https://registry.yarnpkg.com/jws/-/jws-3.1.5.tgz#80d12d05b293d1e841e7cb8b4e69e561adcf834f" - dependencies: - jwa "^1.1.5" - safe-buffer "^5.0.1" - karma-chrome-launcher@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/karma-chrome-launcher/-/karma-chrome-launcher-2.1.1.tgz#216879c68ac04d8d5140e99619ba04b59afd46cf" @@ -8690,10 +8637,6 @@ lodash.get@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" -lodash.includes@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" - lodash.isarguments@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" @@ -8702,10 +8645,6 @@ lodash.isarray@^3.0.0: version "3.0.4" resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" -lodash.isboolean@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" - lodash.isempty@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.isempty/-/lodash.isempty-4.4.0.tgz#6f86cbedd8be4ec987be9aaf33c9684db1b31e7e" @@ -8714,22 +8653,10 @@ lodash.isequal@^4.0.0, lodash.isequal@^4.1.1, lodash.isequal@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" -lodash.isinteger@^4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" - -lodash.isnumber@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" - lodash.isobject@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/lodash.isobject/-/lodash.isobject-3.0.2.tgz#3c8fb8d5b5bf4bf90ae06e14f2a530a4ed935e1d" -lodash.isplainobject@^4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" - lodash.isstring@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" @@ -8782,10 +8709,6 @@ lodash.omitby@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.omitby/-/lodash.omitby-4.6.0.tgz#5c15ff4754ad555016b53c041311e8f079204791" -lodash.once@^4.0.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" - lodash.orderby@4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.orderby/-/lodash.orderby-4.6.0.tgz#e697f04ce5d78522f54d9338b32b81a3393e4eb3" @@ -9410,7 +9333,7 @@ ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" -ms@^2.0.0, ms@^2.1.1: +ms@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" @@ -11473,7 +11396,7 @@ react-router-dom@4.2.2: react-router "^4.2.0" warning "^3.0.0" -react-router-dom@^4.3.1: +react-router-dom@^4.2.2: version "4.3.1" resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-4.3.1.tgz#4c2619fc24c4fa87c9fd18f4fb4a43fe63fbd5c6" dependencies: From 8b97759df67d1258f5989d3a71cbe3966449298d Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Tue, 9 Oct 2018 16:54:34 +0100 Subject: [PATCH 63/94] deps --- x-pack/package.json | 1 + x-pack/yarn.lock | 65 ++++++++++++++++++++++++++++++++++++++++++++- yarn.lock | 65 ++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 129 insertions(+), 2 deletions(-) diff --git a/x-pack/package.json b/x-pack/package.json index a1dc4986433d2..59b1dffbe1aa6 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -138,6 +138,7 @@ "isomorphic-fetch": "2.2.1", "joi": "6.10.1", "jquery": "^3.3.1", + "jsonwebtoken": "^8.3.0", "jstimezonedetect": "1.0.5", "lodash": "3.10.1", "lodash.clone": "^4.5.0", diff --git a/x-pack/yarn.lock b/x-pack/yarn.lock index 5bf491b00e441..c93482299eb4e 100644 --- a/x-pack/yarn.lock +++ b/x-pack/yarn.lock @@ -1411,6 +1411,10 @@ buffer-crc32@~0.2.3: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" +buffer-equal-constant-time@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + buffer-equal@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-0.0.1.tgz#91bc74b11ea405bc916bc6aa908faafa5b4aac4b" @@ -2469,6 +2473,12 @@ ecc-jsbn@~0.1.1: dependencies: jsbn "~0.1.0" +ecdsa-sig-formatter@1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz#1c595000f04a8897dfb85000892a0f4c33af86c3" + dependencies: + safe-buffer "^5.0.1" + elasticsearch@^15.1.1: version "15.1.1" resolved "https://registry.yarnpkg.com/elasticsearch/-/elasticsearch-15.1.1.tgz#242f2378fccd601586ff763a8a933cd5d41c945f" @@ -5036,6 +5046,20 @@ jsonpointer@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" +jsonwebtoken@^8.3.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.3.0.tgz#056c90eee9a65ed6e6c72ddb0a1d325109aaf643" + dependencies: + jws "^3.1.5" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.1.1" + jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -5057,6 +5081,21 @@ just-reduce-object@^1.0.3: version "1.1.0" resolved "https://registry.yarnpkg.com/just-reduce-object/-/just-reduce-object-1.1.0.tgz#d29d172264f8511c74462de30d72d5838b6967e6" +jwa@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.1.6.tgz#87240e76c9808dbde18783cf2264ef4929ee50e6" + dependencies: + buffer-equal-constant-time "1.0.1" + ecdsa-sig-formatter "1.0.10" + safe-buffer "^5.0.1" + +jws@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.1.5.tgz#80d12d05b293d1e841e7cb8b4e69e561adcf834f" + dependencies: + jwa "^1.1.5" + safe-buffer "^5.0.1" + keymirror@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/keymirror/-/keymirror-0.1.1.tgz#918889ea13f8d0a42e7c557250eee713adc95c35" @@ -5330,6 +5369,10 @@ lodash.get@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + lodash.isarguments@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" @@ -5338,10 +5381,22 @@ lodash.isarray@^3.0.0: version "3.0.4" resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + lodash.isequal@^4.1.1, lodash.isequal@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + lodash.isobject@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/lodash.isobject/-/lodash.isobject-3.0.2.tgz#3c8fb8d5b5bf4bf90ae06e14f2a530a4ed935e1d" @@ -5350,6 +5405,10 @@ lodash.isplainobject@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + lodash.istypedarray@^3.0.0: version "3.0.6" resolved "https://registry.yarnpkg.com/lodash.istypedarray/-/lodash.istypedarray-3.0.6.tgz#c9a477498607501d8e8494d283b87c39281cef62" @@ -5382,6 +5441,10 @@ lodash.omitby@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.omitby/-/lodash.omitby-4.6.0.tgz#5c15ff4754ad555016b53c041311e8f079204791" +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + lodash.orderby@4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.orderby/-/lodash.orderby-4.6.0.tgz#e697f04ce5d78522f54d9338b32b81a3393e4eb3" @@ -5901,7 +5964,7 @@ ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" -ms@^2.0.0: +ms@^2.0.0, ms@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" diff --git a/yarn.lock b/yarn.lock index 3a5cb783fc67f..320770c7ff5f0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2379,6 +2379,10 @@ buffer-crc32@~0.2.3: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" +buffer-equal-constant-time@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + buffer-equal@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-0.0.1.tgz#91bc74b11ea405bc916bc6aa908faafa5b4aac4b" @@ -4314,6 +4318,12 @@ ecc-jsbn@~0.1.1: dependencies: jsbn "~0.1.0" +ecdsa-sig-formatter@1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz#1c595000f04a8897dfb85000892a0f4c33af86c3" + dependencies: + safe-buffer "^5.0.1" + editions@^1.1.1, editions@^1.3.4: version "1.3.4" resolved "https://registry.yarnpkg.com/editions/-/editions-1.3.4.tgz#3662cb592347c3168eb8e498a0ff73271d67f50b" @@ -8062,6 +8072,20 @@ jsonpointer@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" +jsonwebtoken@^8.3.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.3.0.tgz#056c90eee9a65ed6e6c72ddb0a1d325109aaf643" + dependencies: + jws "^3.1.5" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.1.1" + jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -8114,6 +8138,21 @@ just-reduce-object@^1.0.3: version "1.1.0" resolved "https://registry.yarnpkg.com/just-reduce-object/-/just-reduce-object-1.1.0.tgz#d29d172264f8511c74462de30d72d5838b6967e6" +jwa@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.1.6.tgz#87240e76c9808dbde18783cf2264ef4929ee50e6" + dependencies: + buffer-equal-constant-time "1.0.1" + ecdsa-sig-formatter "1.0.10" + safe-buffer "^5.0.1" + +jws@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.1.5.tgz#80d12d05b293d1e841e7cb8b4e69e561adcf834f" + dependencies: + jwa "^1.1.5" + safe-buffer "^5.0.1" + karma-chrome-launcher@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/karma-chrome-launcher/-/karma-chrome-launcher-2.1.1.tgz#216879c68ac04d8d5140e99619ba04b59afd46cf" @@ -8637,6 +8676,10 @@ lodash.get@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + lodash.isarguments@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" @@ -8645,6 +8688,10 @@ lodash.isarray@^3.0.0: version "3.0.4" resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + lodash.isempty@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.isempty/-/lodash.isempty-4.4.0.tgz#6f86cbedd8be4ec987be9aaf33c9684db1b31e7e" @@ -8653,10 +8700,22 @@ lodash.isequal@^4.0.0, lodash.isequal@^4.1.1, lodash.isequal@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + lodash.isobject@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/lodash.isobject/-/lodash.isobject-3.0.2.tgz#3c8fb8d5b5bf4bf90ae06e14f2a530a4ed935e1d" +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + lodash.isstring@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" @@ -8709,6 +8768,10 @@ lodash.omitby@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.omitby/-/lodash.omitby-4.6.0.tgz#5c15ff4754ad555016b53c041311e8f079204791" +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + lodash.orderby@4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.orderby/-/lodash.orderby-4.6.0.tgz#e697f04ce5d78522f54d9338b32b81a3393e4eb3" @@ -9333,7 +9396,7 @@ ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" -ms@^2.0.0: +ms@^2.0.0, ms@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" From 9b5bd6ecc951c67a10010d1226244c481990fc84 Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Tue, 9 Oct 2018 17:52:48 +0100 Subject: [PATCH 64/94] force correct node type resolution --- package.json | 5 ++++- x-pack/package.json | 3 +++ x-pack/yarn.lock | 10 +++------- yarn.lock | 12 ++++-------- 4 files changed, 14 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index 0a2575d917a09..3e4522613df29 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,9 @@ "type": "git", "url": "https://github.com/elastic/kibana.git" }, + "resolutions": { + "**/@types/node": "8.10.20" + }, "dependencies": { "@elastic/eui": "4.4.1", "@elastic/filesaver": "1.1.2", @@ -235,7 +238,7 @@ "@types/joi": "^10.4.4", "@types/jquery": "^3.3.6", "@types/js-yaml": "^3.11.1", - "@types/jsonwebtoken": "^7.2.8", + "@types/jsonwebtoken": "^7.2.7", "@types/listr": "^0.13.0", "@types/lodash": "^3.10.1", "@types/minimatch": "^2.0.29", diff --git a/x-pack/package.json b/x-pack/package.json index 59b1dffbe1aa6..aeed5381cdf8a 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -20,6 +20,9 @@ "intermediateBuildDirectory": "build/plugin/kibana/x-pack" } }, + "resolutions": { + "**/@types/node": "8.10.20" + }, "devDependencies": { "@kbn/dev-utils": "link:../packages/kbn-dev-utils", "@kbn/es": "link:../packages/kbn-es", diff --git a/x-pack/yarn.lock b/x-pack/yarn.lock index c93482299eb4e..c7731d54fa174 100644 --- a/x-pack/yarn.lock +++ b/x-pack/yarn.lock @@ -199,13 +199,9 @@ dependencies: moment ">=2.14.0" -"@types/node@*": - version "9.3.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-9.3.0.tgz#3a129cda7c4e5df2409702626892cb4b96546dd5" - -"@types/node@^9.4.7": - version "9.6.18" - resolved "https://registry.yarnpkg.com/@types/node/-/node-9.6.18.tgz#092e13ef64c47e986802c9c45a61c1454813b31d" +"@types/node@*", "@types/node@8.10.20", "@types/node@^9.4.7": + version "8.10.20" + resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.20.tgz#fe674ea52e13950ab10954433a7824438aabbcac" "@types/p-cancelable@^0.3.0": version "0.3.0" diff --git a/yarn.lock b/yarn.lock index 320770c7ff5f0..cbbff12d2d6d7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -497,7 +497,7 @@ version "1.0.32" resolved "https://registry.yarnpkg.com/@types/json-stable-stringify/-/json-stable-stringify-1.0.32.tgz#121f6917c4389db3923640b2e68de5fa64dda88e" -"@types/jsonwebtoken@^7.2.8": +"@types/jsonwebtoken@^7.2.7": version "7.2.8" resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-7.2.8.tgz#8d199dab4ddb5bba3234f8311b804d2027af2b3a" dependencies: @@ -541,18 +541,14 @@ dependencies: moment ">=2.14.0" -"@types/node@*": - version "9.4.7" - resolved "https://registry.yarnpkg.com/@types/node/-/node-9.4.7.tgz#57d81cd98719df2c9de118f2d5f3b1120dcd7275" +"@types/node@*", "@types/node@8.10.20", "@types/node@^9.4.7": + version "8.10.20" + resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.20.tgz#fe674ea52e13950ab10954433a7824438aabbcac" "@types/node@^8.10.20": version "8.10.21" resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.21.tgz#12b3f2359b27aa05a45d886c8ba1eb8d1a77e285" -"@types/node@^9.4.7": - version "9.6.18" - resolved "https://registry.yarnpkg.com/@types/node/-/node-9.6.18.tgz#092e13ef64c47e986802c9c45a61c1454813b31d" - "@types/p-cancelable@^0.3.0": version "0.3.0" resolved "https://registry.yarnpkg.com/@types/p-cancelable/-/p-cancelable-0.3.0.tgz#3e4fcc54a3dfd81d0f5b93546bb68d0df50553bb" From 6c3314de4a0808db8eab60fb3981f324ec01bd6e Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Wed, 10 Oct 2018 08:55:57 +0100 Subject: [PATCH 65/94] cleanup and skip tests (kbn test util is broken) --- .../database/__tests__/kibana.test.ts | 19 ++++++++++++------- .../database/__tests__/test_contract.ts | 2 +- .../framework/__tests__/kibana.test.ts | 11 ++++++----- .../framework/__tests__/test_contract.ts | 6 ++---- .../__tests__/beats_configurations.test.ts | 5 ----- .../rest_api/__tests__/beats_enroll.test.ts | 5 ----- .../rest_api/__tests__/beats_get.test.ts | 5 ----- .../rest_api/__tests__/beats_list.test.ts | 5 ----- .../rest_api/__tests__/beats_removals.test.ts | 5 ----- .../rest_api/__tests__/beats_updates.test.ts | 5 ----- .../utils/error_wrappers/wrap_es_error.ts | 8 ++++++++ 11 files changed, 29 insertions(+), 47 deletions(-) delete mode 100644 x-pack/plugins/beats_management/server/rest_api/__tests__/beats_configurations.test.ts delete mode 100644 x-pack/plugins/beats_management/server/rest_api/__tests__/beats_enroll.test.ts delete mode 100644 x-pack/plugins/beats_management/server/rest_api/__tests__/beats_get.test.ts delete mode 100644 x-pack/plugins/beats_management/server/rest_api/__tests__/beats_list.test.ts delete mode 100644 x-pack/plugins/beats_management/server/rest_api/__tests__/beats_removals.test.ts delete mode 100644 x-pack/plugins/beats_management/server/rest_api/__tests__/beats_updates.test.ts diff --git a/x-pack/plugins/beats_management/server/lib/adapters/database/__tests__/kibana.test.ts b/x-pack/plugins/beats_management/server/lib/adapters/database/__tests__/kibana.test.ts index 33e4046d1d63e..bcbb1e9a29b49 100644 --- a/x-pack/plugins/beats_management/server/lib/adapters/database/__tests__/kibana.test.ts +++ b/x-pack/plugins/beats_management/server/lib/adapters/database/__tests__/kibana.test.ts @@ -7,28 +7,33 @@ // @ts-ignore import { createEsTestCluster } from '@kbn/test'; +import { Root } from 'src/core/server/root'; // @ts-ignore import * as kbnTestServer from '../../../../../../../../src/test_utils/kbn_server'; import { DatabaseKbnESPlugin } from '../adapter_types'; import { KibanaDatabaseAdapter } from '../kibana_database_adapter'; import { contractTests } from './test_contract'; - -const kbnServer = kbnTestServer.getKbnServer(kbnTestServer.createRootWithCorePlugins()); - const es = createEsTestCluster({}); +let legacyServer: any; +let rootServer: Root; contractTests('Kibana Database Adapter', { before: async () => { await es.start(); - await kbnServer.ready(); - return await kbnServer.server.plugins.elasticsearch.waitUntilReady(); + rootServer = kbnTestServer.createRootWithCorePlugins({ + server: { maxPayloadBytes: 100 }, + }); + + await rootServer.start(); + legacyServer = kbnTestServer.getKbnServer(rootServer); + return await legacyServer.plugins.elasticsearch.waitUntilReady(); }, after: async () => { - await kbnServer.close(); + await rootServer.shutdown(); return await es.cleanup(); }, adapterSetup: () => { - return new KibanaDatabaseAdapter(kbnServer.server.plugins.elasticsearch as DatabaseKbnESPlugin); + return new KibanaDatabaseAdapter(legacyServer.plugins.elasticsearch as DatabaseKbnESPlugin); }, }); diff --git a/x-pack/plugins/beats_management/server/lib/adapters/database/__tests__/test_contract.ts b/x-pack/plugins/beats_management/server/lib/adapters/database/__tests__/test_contract.ts index ec30d4578e938..a54fc9537fcaa 100644 --- a/x-pack/plugins/beats_management/server/lib/adapters/database/__tests__/test_contract.ts +++ b/x-pack/plugins/beats_management/server/lib/adapters/database/__tests__/test_contract.ts @@ -13,7 +13,7 @@ interface ContractConfig { } export const contractTests = (testName: string, config: ContractConfig) => { - describe(testName, () => { + describe.skip(testName, () => { let database: DatabaseAdapter; beforeAll(async () => { jest.setTimeout(100000); // 1 second diff --git a/x-pack/plugins/beats_management/server/lib/adapters/framework/__tests__/kibana.test.ts b/x-pack/plugins/beats_management/server/lib/adapters/framework/__tests__/kibana.test.ts index 54cc0c0de1af6..b483379d444c0 100644 --- a/x-pack/plugins/beats_management/server/lib/adapters/framework/__tests__/kibana.test.ts +++ b/x-pack/plugins/beats_management/server/lib/adapters/framework/__tests__/kibana.test.ts @@ -13,21 +13,22 @@ import * as kbnTestServer from '../../../../../../../../src/test_utils/kbn_serve import { KibanaBackendFrameworkAdapter } from '../kibana_framework_adapter'; import { contractTests } from './test_contract'; -const kbnServer = kbnTestServer.getKbnServer(kbnTestServer.createRootWithCorePlugins()); +const kbnServer = kbnTestServer.createRootWithCorePlugins({ server: { maxPayloadBytes: 100 } }); +const legacyServer = kbnTestServer.getKbnServer(kbnServer); contractTests('Kibana Framework Adapter', { before: async () => { - await kbnServer.ready(); + await kbnServer.start(); - const config = kbnServer.server.config(); + const config = legacyServer.server.config(); config.extendSchema(beatsPluginConfig, {}, configPrefix); config.set('xpack.beats.encryptionKey', 'foo'); }, after: async () => { - await kbnServer.close(); + await kbnServer.shutdown(); }, adapterSetup: () => { - return new KibanaBackendFrameworkAdapter(kbnServer.server); + return new KibanaBackendFrameworkAdapter(legacyServer.server); }, }); diff --git a/x-pack/plugins/beats_management/server/lib/adapters/framework/__tests__/test_contract.ts b/x-pack/plugins/beats_management/server/lib/adapters/framework/__tests__/test_contract.ts index b17d424e1baf2..5d870d1cadc4b 100644 --- a/x-pack/plugins/beats_management/server/lib/adapters/framework/__tests__/test_contract.ts +++ b/x-pack/plugins/beats_management/server/lib/adapters/framework/__tests__/test_contract.ts @@ -12,7 +12,7 @@ interface ContractConfig { } export const contractTests = (testName: string, config: ContractConfig) => { - describe(testName, () => { + describe.skip(testName, () => { // let frameworkAdapter: BackendFrameworkAdapter; beforeAll(async () => { jest.setTimeout(100000); // 1 second @@ -24,9 +24,7 @@ export const contractTests = (testName: string, config: ContractConfig) => { afterAll(async () => config.after && (await config.after())); beforeEach(async () => { // FIXME: one of these always should exist, type ContractConfig as such - // frameworkAdapter = (config.adapterSetup - // ? config.adapterSetup() - // : config.adapter) as BackendFrameworkAdapter; + // const frameworkAdapter = config.adapterSetup(); }); it('Should have tests here', () => { diff --git a/x-pack/plugins/beats_management/server/rest_api/__tests__/beats_configurations.test.ts b/x-pack/plugins/beats_management/server/rest_api/__tests__/beats_configurations.test.ts deleted file mode 100644 index 41bc2aa258807..0000000000000 --- a/x-pack/plugins/beats_management/server/rest_api/__tests__/beats_configurations.test.ts +++ /dev/null @@ -1,5 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ diff --git a/x-pack/plugins/beats_management/server/rest_api/__tests__/beats_enroll.test.ts b/x-pack/plugins/beats_management/server/rest_api/__tests__/beats_enroll.test.ts deleted file mode 100644 index 41bc2aa258807..0000000000000 --- a/x-pack/plugins/beats_management/server/rest_api/__tests__/beats_enroll.test.ts +++ /dev/null @@ -1,5 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ diff --git a/x-pack/plugins/beats_management/server/rest_api/__tests__/beats_get.test.ts b/x-pack/plugins/beats_management/server/rest_api/__tests__/beats_get.test.ts deleted file mode 100644 index 41bc2aa258807..0000000000000 --- a/x-pack/plugins/beats_management/server/rest_api/__tests__/beats_get.test.ts +++ /dev/null @@ -1,5 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ diff --git a/x-pack/plugins/beats_management/server/rest_api/__tests__/beats_list.test.ts b/x-pack/plugins/beats_management/server/rest_api/__tests__/beats_list.test.ts deleted file mode 100644 index 41bc2aa258807..0000000000000 --- a/x-pack/plugins/beats_management/server/rest_api/__tests__/beats_list.test.ts +++ /dev/null @@ -1,5 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ diff --git a/x-pack/plugins/beats_management/server/rest_api/__tests__/beats_removals.test.ts b/x-pack/plugins/beats_management/server/rest_api/__tests__/beats_removals.test.ts deleted file mode 100644 index 41bc2aa258807..0000000000000 --- a/x-pack/plugins/beats_management/server/rest_api/__tests__/beats_removals.test.ts +++ /dev/null @@ -1,5 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ diff --git a/x-pack/plugins/beats_management/server/rest_api/__tests__/beats_updates.test.ts b/x-pack/plugins/beats_management/server/rest_api/__tests__/beats_updates.test.ts deleted file mode 100644 index 41bc2aa258807..0000000000000 --- a/x-pack/plugins/beats_management/server/rest_api/__tests__/beats_updates.test.ts +++ /dev/null @@ -1,5 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ diff --git a/x-pack/plugins/beats_management/server/utils/error_wrappers/wrap_es_error.ts b/x-pack/plugins/beats_management/server/utils/error_wrappers/wrap_es_error.ts index b8202ded5d2d2..9c76847df640a 100644 --- a/x-pack/plugins/beats_management/server/utils/error_wrappers/wrap_es_error.ts +++ b/x-pack/plugins/beats_management/server/utils/error_wrappers/wrap_es_error.ts @@ -18,5 +18,13 @@ export function wrapEsError(err: any) { if (statusCode === 403) { return Boom.forbidden('Insufficient user permissions for managing Beats configuration'); } + + // This is due to a typings error in the Boom typedef. + // @ts-ignore + if (Boom.wrap) { + // @ts-ignore + return Boom.wrap(err, err.statusCode); + } + return Boom.boomify(err, { statusCode: err.statusCode }); } From c437c1b669816b382f064cd4d562968b58413597 Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Wed, 10 Oct 2018 11:38:02 +0100 Subject: [PATCH 66/94] added formsy and basePath --- x-pack/package.json | 1 + .../adapters/framework/kibana_framework_adapter.ts | 4 ++++ x-pack/plugins/beats_management/public/lib/lib.ts | 2 +- .../public/pages/main/enroll_fragment.tsx | 2 +- x-pack/yarn.lock | 11 +++++++++++ 5 files changed, 18 insertions(+), 2 deletions(-) diff --git a/x-pack/package.json b/x-pack/package.json index aeed5381cdf8a..e028017251c17 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -129,6 +129,7 @@ "extract-zip": "1.5.0", "file-saver": "^1.3.8", "font-awesome": "4.4.0", + "formsy-react": "^1.1.5", "get-port": "2.1.0", "getos": "^3.1.0", "glob": "6.0.4", diff --git a/x-pack/plugins/beats_management/public/lib/adapters/framework/kibana_framework_adapter.ts b/x-pack/plugins/beats_management/public/lib/adapters/framework/kibana_framework_adapter.ts index d17c3b1577d7f..30c838893d271 100644 --- a/x-pack/plugins/beats_management/public/lib/adapters/framework/kibana_framework_adapter.ts +++ b/x-pack/plugins/beats_management/public/lib/adapters/framework/kibana_framework_adapter.ts @@ -47,6 +47,10 @@ export class KibanaFrameworkAdapter implements FrameworkAdapter { this.notifier = new Notifier({ location: 'Beats' }); } + public get baseURLPath(): string { + return this.chrome.getBasePath(); + } + public setUISettings = (key: string, value: any) => { this.adapterService.callOrBuffer(({ config }) => { config.set(key, value); diff --git a/x-pack/plugins/beats_management/public/lib/lib.ts b/x-pack/plugins/beats_management/public/lib/lib.ts index 9f589af5344f7..08b15957c8cba 100644 --- a/x-pack/plugins/beats_management/public/lib/lib.ts +++ b/x-pack/plugins/beats_management/public/lib/lib.ts @@ -42,8 +42,8 @@ export interface FrameworkAdapter { // Instance vars appState?: object; kbnVersion?: string; + baseURLPath: string; registerManagementSection(pluginId: string, displayName: string, basePath: string): void; - // Methods setUISettings(key: string, value: any): void; render(component: React.ReactElement): void; diff --git a/x-pack/plugins/beats_management/public/pages/main/enroll_fragment.tsx b/x-pack/plugins/beats_management/public/pages/main/enroll_fragment.tsx index 69ce792591f8e..3dfdf40a73335 100644 --- a/x-pack/plugins/beats_management/public/pages/main/enroll_fragment.tsx +++ b/x-pack/plugins/beats_management/public/pages/main/enroll_fragment.tsx @@ -103,7 +103,7 @@ export class EnrollBeat extends React.Component {
- $ beats enroll {window.location.protocol}//{window.location.host} {this.props.urlState.enrollmentToken} + $ beats enroll {window.location.protocol}//{window.location.host}{this.props.libs.framework.baseURLPath ? `/${this.props.libs.framework.baseURLPath}` : ''} {this.props.urlState.enrollmentToken}

diff --git a/x-pack/yarn.lock b/x-pack/yarn.lock index c7731d54fa174..beeeccfa905d7 100644 --- a/x-pack/yarn.lock +++ b/x-pack/yarn.lock @@ -3161,6 +3161,10 @@ forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" +form-data-to-object@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/form-data-to-object/-/form-data-to-object-0.2.0.tgz#f7a8e68ddd910a1100a65e25ac6a484143ff8168" + form-data@^2.3.1, form-data@~2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.1.tgz#6fb94fbd71885306d73d15cc497fe4cc4ecd44bf" @@ -3189,6 +3193,13 @@ formidable@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.1.1.tgz#96b8886f7c3c3508b932d6bd70c4d3a88f35f1a9" +formsy-react@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/formsy-react/-/formsy-react-1.1.5.tgz#ee0911bb70712eb6fb9924d56fdb974a19006955" + dependencies: + form-data-to-object "^0.2.0" + prop-types "^15.5.10" + fragment-cache@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" From b84233d9f17bdad5a349794de63fc548432785ba Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Wed, 10 Oct 2018 12:03:53 +0100 Subject: [PATCH 67/94] update yarn lock file --- .../adapters/framework/kibana_framework_adapter.ts | 1 + .../server/utils/error_wrappers/wrap_es_error.ts | 1 + yarn.lock | 11 +++++++++++ 3 files changed, 13 insertions(+) diff --git a/x-pack/plugins/beats_management/server/lib/adapters/framework/kibana_framework_adapter.ts b/x-pack/plugins/beats_management/server/lib/adapters/framework/kibana_framework_adapter.ts index 05d51fe405258..a819eaa37b35d 100644 --- a/x-pack/plugins/beats_management/server/lib/adapters/framework/kibana_framework_adapter.ts +++ b/x-pack/plugins/beats_management/server/lib/adapters/framework/kibana_framework_adapter.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +// @ts-ignore import Boom from 'boom'; // @ts-ignore import { mirrorPluginStatus } from '../../../../../../server/lib/mirror_plugin_status'; diff --git a/x-pack/plugins/beats_management/server/utils/error_wrappers/wrap_es_error.ts b/x-pack/plugins/beats_management/server/utils/error_wrappers/wrap_es_error.ts index 9c76847df640a..f0b683ce4aa66 100644 --- a/x-pack/plugins/beats_management/server/utils/error_wrappers/wrap_es_error.ts +++ b/x-pack/plugins/beats_management/server/utils/error_wrappers/wrap_es_error.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +// @ts-ignore import Boom from 'boom'; /** diff --git a/yarn.lock b/yarn.lock index cbbff12d2d6d7..83d0a43b53c75 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5519,6 +5519,10 @@ forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" +form-data-to-object@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/form-data-to-object/-/form-data-to-object-0.2.0.tgz#f7a8e68ddd910a1100a65e25ac6a484143ff8168" + form-data@^2.3.1, form-data@~2.3.1: version "2.3.2" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.2.tgz#4970498be604c20c005d4f5c23aecd21d6b49099" @@ -5539,6 +5543,13 @@ formidable@^1.1.1: version "1.2.1" resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.2.1.tgz#70fb7ca0290ee6ff961090415f4b3df3d2082659" +formsy-react@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/formsy-react/-/formsy-react-1.1.5.tgz#ee0911bb70712eb6fb9924d56fdb974a19006955" + dependencies: + form-data-to-object "^0.2.0" + prop-types "^15.5.10" + fragment-cache@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" From eded0a48d8e03a01c9e32ed8a14732a2c4763e80 Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Wed, 10 Oct 2018 16:16:04 +0100 Subject: [PATCH 68/94] add beats management icon --- .../public/lib/adapters/framework/kibana_framework_adapter.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/beats_management/public/lib/adapters/framework/kibana_framework_adapter.ts b/x-pack/plugins/beats_management/public/lib/adapters/framework/kibana_framework_adapter.ts index 30c838893d271..39479e1d0af2a 100644 --- a/x-pack/plugins/beats_management/public/lib/adapters/framework/kibana_framework_adapter.ts +++ b/x-pack/plugins/beats_management/public/lib/adapters/framework/kibana_framework_adapter.ts @@ -84,6 +84,7 @@ export class KibanaFrameworkAdapter implements FrameworkAdapter { const registerSection = () => this.management.register(pluginId, { display: displayName, + icon: 'logoBeats', order: 30, }); const getSection = () => this.management.getSection(pluginId); From 3d41c9f003f5272f4c1d6b5344aa42ac0c000c2f Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Wed, 10 Oct 2018 16:18:18 +0100 Subject: [PATCH 69/94] rename beats management main section --- .../public/lib/adapters/framework/kibana_framework_adapter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/beats_management/public/lib/adapters/framework/kibana_framework_adapter.ts b/x-pack/plugins/beats_management/public/lib/adapters/framework/kibana_framework_adapter.ts index 39479e1d0af2a..a0c24557267a2 100644 --- a/x-pack/plugins/beats_management/public/lib/adapters/framework/kibana_framework_adapter.ts +++ b/x-pack/plugins/beats_management/public/lib/adapters/framework/kibana_framework_adapter.ts @@ -83,7 +83,7 @@ export class KibanaFrameworkAdapter implements FrameworkAdapter { if (this.hadValidLicense() && this.securityEnabled()) { const registerSection = () => this.management.register(pluginId, { - display: displayName, + display: 'Beats', // TODO these need to be config options not hard coded in the adapter icon: 'logoBeats', order: 30, }); From 2d83d47a39559143ca934f141817fc381868ac20 Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Sun, 14 Oct 2018 22:00:45 +0100 Subject: [PATCH 70/94] Tags now called Configuration Tags in the tabs --- x-pack/plugins/beats_management/public/pages/beat/index.tsx | 2 +- x-pack/plugins/beats_management/public/pages/main/index.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/beats_management/public/pages/beat/index.tsx b/x-pack/plugins/beats_management/public/pages/beat/index.tsx index ef08dcdd7bb48..cbabd72adec3e 100644 --- a/x-pack/plugins/beats_management/public/pages/beat/index.tsx +++ b/x-pack/plugins/beats_management/public/pages/beat/index.tsx @@ -85,7 +85,7 @@ class BeatDetailsPageComponent extends React.PureComponent< // }, { id: `/beat/${id}/tags`, - name: 'Tags', + name: 'Configuration Tags', disabled: false, }, ]; diff --git a/x-pack/plugins/beats_management/public/pages/main/index.tsx b/x-pack/plugins/beats_management/public/pages/main/index.tsx index 87604583a0af9..3b935a191062b 100644 --- a/x-pack/plugins/beats_management/public/pages/main/index.tsx +++ b/x-pack/plugins/beats_management/public/pages/main/index.tsx @@ -86,7 +86,7 @@ class MainPagesComponent extends React.PureComponent Date: Sun, 14 Oct 2018 22:03:01 +0100 Subject: [PATCH 71/94] tokens must expire at most after 2 weeks --- x-pack/plugins/beats_management/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/x-pack/plugins/beats_management/index.ts b/x-pack/plugins/beats_management/index.ts index 951022bff9052..32384fd4425bd 100644 --- a/x-pack/plugins/beats_management/index.ts +++ b/x-pack/plugins/beats_management/index.ts @@ -5,6 +5,7 @@ */ import Joi from 'joi'; import { resolve } from 'path'; +import { max } from 'rxjs/operators'; import { PLUGIN } from './common/constants'; import { initServerWithKibana } from './server/kibana.index'; @@ -16,6 +17,7 @@ export const config = Joi.object({ enrollmentTokensTtlInSeconds: Joi.number() .integer() .min(1) + .max(10 * 60 * 14) // No more then 2 weeks for security reasons .default(DEFAULT_ENROLLMENT_TOKENS_TTL_S), }).default(); export const configPrefix = 'xpack.beats'; From f46e714a25aad14b1d46648b5b0e25637df1b82a Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Sun, 14 Oct 2018 22:04:00 +0100 Subject: [PATCH 72/94] fix bad auto-import --- x-pack/plugins/beats_management/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/beats_management/index.ts b/x-pack/plugins/beats_management/index.ts index 32384fd4425bd..00853dc82af98 100644 --- a/x-pack/plugins/beats_management/index.ts +++ b/x-pack/plugins/beats_management/index.ts @@ -5,7 +5,6 @@ */ import Joi from 'joi'; import { resolve } from 'path'; -import { max } from 'rxjs/operators'; import { PLUGIN } from './common/constants'; import { initServerWithKibana } from './server/kibana.index'; From e78beb667ec5fefe58e5af33577a563e32a27cbb Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Sun, 14 Oct 2018 22:09:31 +0100 Subject: [PATCH 73/94] beat details now shows the extra data needed --- .../beats_management/public/pages/beat/action_section.tsx | 6 ++++++ x-pack/plugins/beats_management/public/pages/beat/index.tsx | 5 ++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/beats_management/public/pages/beat/action_section.tsx b/x-pack/plugins/beats_management/public/pages/beat/action_section.tsx index 74507557df0a4..0ab8181794628 100644 --- a/x-pack/plugins/beats_management/public/pages/beat/action_section.tsx +++ b/x-pack/plugins/beats_management/public/pages/beat/action_section.tsx @@ -18,6 +18,12 @@ export const BeatDetailsActionSection = ({ beat }: BeatDetailsActionSectionProps
{beat ? ( + + + Type:  + {beat.type}. + + Version:  diff --git a/x-pack/plugins/beats_management/public/pages/beat/index.tsx b/x-pack/plugins/beats_management/public/pages/beat/index.tsx index cbabd72adec3e..0b61d8da2fcce 100644 --- a/x-pack/plugins/beats_management/public/pages/beat/index.tsx +++ b/x-pack/plugins/beats_management/public/pages/beat/index.tsx @@ -71,7 +71,10 @@ class BeatDetailsPageComponent extends React.PureComponent< id = beat.id; name = beat.name; } - const title = this.state.isLoading ? 'Loading' : `Beat: ${name || id}`; + const title = this.state.isLoading + ? 'Loading' + : `Beat: ${name || 'No name receved from beat'} (id: ${id})`; + const tabs = [ { id: `/beat/${id}`, From 0ffe1d0e11be98a0d16491dd2477aef976b2e291 Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Tue, 16 Oct 2018 07:45:41 -0400 Subject: [PATCH 74/94] tweak package.json deps for continuity --- package.json | 3 +-- x-pack/package.json | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 3e4522613df29..99013a9b964d7 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "url": "https://github.com/elastic/kibana.git" }, "resolutions": { - "**/@types/node": "8.10.20" + "**/@types/node": "8.10.21" }, "dependencies": { "@elastic/eui": "4.4.1", @@ -238,7 +238,6 @@ "@types/joi": "^10.4.4", "@types/jquery": "^3.3.6", "@types/js-yaml": "^3.11.1", - "@types/jsonwebtoken": "^7.2.7", "@types/listr": "^0.13.0", "@types/lodash": "^3.10.1", "@types/minimatch": "^2.0.29", diff --git a/x-pack/package.json b/x-pack/package.json index e028017251c17..a50fdd3021aca 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -21,7 +21,7 @@ } }, "resolutions": { - "**/@types/node": "8.10.20" + "**/@types/node": "8.10.21" }, "devDependencies": { "@kbn/dev-utils": "link:../packages/kbn-dev-utils", @@ -31,6 +31,7 @@ "@types/expect.js": "^0.3.29", "@types/jest": "^23.3.1", "@types/joi": "^10.4.4", + "@types/jsonwebtoken": "^7.2.7", "@types/mocha": "^5.2.5", "@types/pngjs": "^3.3.1", "@types/supertest": "^2.0.5", From 66f8afc07eef11cc0c6838264c31904014364e61 Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Tue, 16 Oct 2018 07:54:28 -0400 Subject: [PATCH 75/94] update yarn lock for new yarn version --- x-pack/yarn.lock | 109 +++++++++++++++++++++++++++++++++++++---- yarn.lock | 123 ++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 210 insertions(+), 22 deletions(-) diff --git a/x-pack/yarn.lock b/x-pack/yarn.lock index 9719ac4783daf..60c39b8e81baa 100644 --- a/x-pack/yarn.lock +++ b/x-pack/yarn.lock @@ -206,6 +206,13 @@ resolved "https://registry.yarnpkg.com/@types/joi/-/joi-10.6.4.tgz#0989d69e792a7db13e951852e6949df6787f113f" integrity sha512-eS6EeSGueXvS16CsHa7OKkRK1xBb6L+rXuXlzbWSWvb4v7zgNFPmY8l6aWWgEkHFeITVBadeQHQhVUpx0sd1tw== +"@types/jsonwebtoken@^7.2.7": + version "7.2.8" + resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-7.2.8.tgz#8d199dab4ddb5bba3234f8311b804d2027af2b3a" + integrity sha512-XENN3YzEB8D6TiUww0O8SRznzy1v+77lH7UmuN54xq/IHIsyWjWOzZuFFTtoiRuaE782uAoRwBe/wwow+vQXZw== + dependencies: + "@types/node" "*" + "@types/loglevel@^1.5.3": version "1.5.3" resolved "https://registry.yarnpkg.com/@types/loglevel/-/loglevel-1.5.3.tgz#adfce55383edc5998a2170ad581b3e23d6adb5b8" @@ -223,15 +230,10 @@ dependencies: moment ">=2.14.0" -"@types/node@*": - version "9.3.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-9.3.0.tgz#3a129cda7c4e5df2409702626892cb4b96546dd5" - integrity sha512-wNBfvNjzsJl4tswIZKXCFQY0lss9nKUyJnG6T94X/eqjRgI2jHZ4evdjhQYBSan/vGtF6XVXPApOmNH2rf0KKw== - -"@types/node@^9.4.7": - version "9.6.18" - resolved "https://registry.yarnpkg.com/@types/node/-/node-9.6.18.tgz#092e13ef64c47e986802c9c45a61c1454813b31d" - integrity sha512-lywCnJQRSsu0kitHQ5nkb7Ay/ScdJPQjhWRtuf+G1DmNKJnPcdVyP0pYvdiDFKjzReC6NLWLgSyimno3kKfIig== +"@types/node@*", "@types/node@8.10.21", "@types/node@^9.4.7": + version "8.10.21" + resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.21.tgz#12b3f2359b27aa05a45d886c8ba1eb8d1a77e285" + integrity sha512-87XkD9qDXm8fIax+5y7drx84cXsu34ZZqfB7Cial3Q/2lxSoJ/+DRaWckkCbxP41wFSIrrb939VhzaNxj4eY1w== "@types/p-cancelable@^0.3.0": version "0.3.0" @@ -1636,6 +1638,11 @@ buffer-crc32@~0.2.3: resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= +buffer-equal-constant-time@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= + buffer-equal@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-0.0.1.tgz#91bc74b11ea405bc916bc6aa908faafa5b4aac4b" @@ -2879,6 +2886,13 @@ ecc-jsbn@~0.1.1: dependencies: jsbn "~0.1.0" +ecdsa-sig-formatter@1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz#1c595000f04a8897dfb85000892a0f4c33af86c3" + integrity sha1-HFlQAPBKiJffuFAAiSoPTDOvhsM= + dependencies: + safe-buffer "^5.0.1" + elasticsearch@^15.1.1: version "15.1.1" resolved "https://registry.yarnpkg.com/elasticsearch/-/elasticsearch-15.1.1.tgz#242f2378fccd601586ff763a8a933cd5d41c945f" @@ -3666,6 +3680,11 @@ forever-agent@~0.6.1: resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= +form-data-to-object@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/form-data-to-object/-/form-data-to-object-0.2.0.tgz#f7a8e68ddd910a1100a65e25ac6a484143ff8168" + integrity sha1-96jmjd2RChEApl4lrGpIQUP/gWg= + form-data@^2.3.1, form-data@~2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.1.tgz#6fb94fbd71885306d73d15cc497fe4cc4ecd44bf" @@ -3698,6 +3717,14 @@ formidable@^1.1.1: resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.1.1.tgz#96b8886f7c3c3508b932d6bd70c4d3a88f35f1a9" integrity sha1-lriIb3w8NQi5Mta9cMTTqI818ak= +formsy-react@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/formsy-react/-/formsy-react-1.1.5.tgz#ee0911bb70712eb6fb9924d56fdb974a19006955" + integrity sha512-nNWe4Vbp6aDQ/zSxJ7gVQgD5+avFRVbydcjA2Om42flxpQFrKE54AAbuyEj3Jvv+2b9LVl+WLMAPalyvLjwNcQ== + dependencies: + form-data-to-object "^0.2.0" + prop-types "^15.5.10" + fragment-cache@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" @@ -5826,6 +5853,21 @@ jsonpointer@^4.0.0: resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" integrity sha1-T9kss04OnbPInIYi7PUfm5eMbLk= +jsonwebtoken@^8.3.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.3.0.tgz#056c90eee9a65ed6e6c72ddb0a1d325109aaf643" + integrity sha512-oge/hvlmeJCH+iIz1DwcO7vKPkNGJHhgkspk8OH3VKlw+mbi42WtD4ig1+VXRln765vxptAv+xT26Fd3cteqag== + dependencies: + jws "^3.1.5" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.1.1" + jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -5851,6 +5893,23 @@ just-reduce-object@^1.0.3: resolved "https://registry.yarnpkg.com/just-reduce-object/-/just-reduce-object-1.1.0.tgz#d29d172264f8511c74462de30d72d5838b6967e6" integrity sha512-nGyg7N9FEZsyrGQNilkyVLxKPsf96iel5v0DrozQ19ML+96HntyS/53bOP68iK/kZUGvsL3FKygV8nQYYhgTFw== +jwa@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.1.6.tgz#87240e76c9808dbde18783cf2264ef4929ee50e6" + integrity sha512-tBO/cf++BUsJkYql/kBbJroKOgHWEigTKBAjjBEmrMGYd1QMBC74Hr4Wo2zCZw6ZrVhlJPvoMrkcOnlWR/DJfw== + dependencies: + buffer-equal-constant-time "1.0.1" + ecdsa-sig-formatter "1.0.10" + safe-buffer "^5.0.1" + +jws@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.1.5.tgz#80d12d05b293d1e841e7cb8b4e69e561adcf834f" + integrity sha512-GsCSexFADNQUr8T5HPJvayTjvPIfoyJPtLQBwn5a4WZQchcrPMPMAWcC1AzJVRDKyD6ZPROPAxgv6rfHViO4uQ== + dependencies: + jwa "^1.1.5" + safe-buffer "^5.0.1" + keymirror@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/keymirror/-/keymirror-0.1.1.tgz#918889ea13f8d0a42e7c557250eee713adc95c35" @@ -6175,6 +6234,11 @@ lodash.get@^4.4.2: resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + integrity sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8= + lodash.isarguments@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" @@ -6185,11 +6249,26 @@ lodash.isarray@^3.0.0: resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" integrity sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U= +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY= + lodash.isequal@^4.1.1, lodash.isequal@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + integrity sha1-YZwK89A/iwTDH1iChAt3sRzWg0M= + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + integrity sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w= + lodash.isobject@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/lodash.isobject/-/lodash.isobject-3.0.2.tgz#3c8fb8d5b5bf4bf90ae06e14f2a530a4ed935e1d" @@ -6200,6 +6279,11 @@ lodash.isplainobject@^4.0.6: resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs= +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE= + lodash.istypedarray@^3.0.0: version "3.0.6" resolved "https://registry.yarnpkg.com/lodash.istypedarray/-/lodash.istypedarray-3.0.6.tgz#c9a477498607501d8e8494d283b87c39281cef62" @@ -6239,6 +6323,11 @@ lodash.omitby@^4.6.0: resolved "https://registry.yarnpkg.com/lodash.omitby/-/lodash.omitby-4.6.0.tgz#5c15ff4754ad555016b53c041311e8f079204791" integrity sha1-XBX/R1StVVAWtTwEExHo8HkgR5E= +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= + lodash.orderby@4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.orderby/-/lodash.orderby-4.6.0.tgz#e697f04ce5d78522f54d9338b32b81a3393e4eb3" @@ -6840,7 +6929,7 @@ ms@2.0.0: resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= -ms@^2.0.0: +ms@^2.0.0, ms@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== diff --git a/yarn.lock b/yarn.lock index 74d2093bff2a9..1f8e11f6effc6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -510,6 +510,11 @@ resolved "https://registry.yarnpkg.com/@types/has-ansi/-/has-ansi-3.0.0.tgz#636403dc4e0b2649421c4158e5c404416f3f0330" integrity sha512-H3vFOwfLlFEC0MOOrcSkus8PCnMCzz4N0EqUbdJZCdDhBTfkAu86aRYA+MTxjKW6jCpUvxcn4715US8g+28BMA== +"@types/history@*": + version "4.7.1" + resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.1.tgz#66849a23ceef917b57f0787c885f41ce03480f5c" + integrity sha512-g7RRtPg2f2OJm3kvYVTAGEr3R+YN53XwZgpP8r4cl3ugJB+95hbPfOU5tjOoAOz4bTLQuiHVUJh8rl4hEDUUjQ== + "@types/iron@*": version "5.0.1" resolved "https://registry.yarnpkg.com/@types/iron/-/iron-5.0.1.tgz#5420bbda8623c48ee51b9a78ebad05d7305b4b24" @@ -605,21 +610,11 @@ dependencies: moment ">=2.14.0" -"@types/node@*": - version "9.4.7" - resolved "https://registry.yarnpkg.com/@types/node/-/node-9.4.7.tgz#57d81cd98719df2c9de118f2d5f3b1120dcd7275" - integrity sha512-4Ba90mWNx8ddbafuyGGwjkZMigi+AWfYLSDCpovwsE63ia8w93r3oJ8PIAQc3y8U+XHcnMOHPIzNe3o438Ywcw== - -"@types/node@^8.10.20": +"@types/node@*", "@types/node@8.10.21", "@types/node@^8.10.20", "@types/node@^9.4.7": version "8.10.21" resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.21.tgz#12b3f2359b27aa05a45d886c8ba1eb8d1a77e285" integrity sha512-87XkD9qDXm8fIax+5y7drx84cXsu34ZZqfB7Cial3Q/2lxSoJ/+DRaWckkCbxP41wFSIrrb939VhzaNxj4eY1w== -"@types/node@^9.4.7": - version "9.6.18" - resolved "https://registry.yarnpkg.com/@types/node/-/node-9.6.18.tgz#092e13ef64c47e986802c9c45a61c1454813b31d" - integrity sha512-lywCnJQRSsu0kitHQ5nkb7Ay/ScdJPQjhWRtuf+G1DmNKJnPcdVyP0pYvdiDFKjzReC6NLWLgSyimno3kKfIig== - "@types/p-cancelable@^0.3.0": version "0.3.0" resolved "https://registry.yarnpkg.com/@types/p-cancelable/-/p-cancelable-0.3.0.tgz#3e4fcc54a3dfd81d0f5b93546bb68d0df50553bb" @@ -678,6 +673,23 @@ "@types/react" "*" redux "^4.0.0" +"@types/react-router-dom@^4.3.1": + version "4.3.1" + resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-4.3.1.tgz#71fe2918f8f60474a891520def40a63997dafe04" + integrity sha512-GbztJAScOmQ/7RsQfO4cd55RuH1W4g6V1gDW3j4riLlt+8yxYLqqsiMzmyuXBLzdFmDtX/uU2Bpcm0cmudv44A== + dependencies: + "@types/history" "*" + "@types/react" "*" + "@types/react-router" "*" + +"@types/react-router@*": + version "4.0.32" + resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-4.0.32.tgz#501529e3d7aa7d5c738d339367e1a7dd5338b2a7" + integrity sha512-VLQSifCIKCTpfMFrJN/nO5a45LduB6qSMkO9ASbcGdCHiDwJnrLNzk91Q895yG0qWY7RqT2jR16giBRpRG1HQw== + dependencies: + "@types/history" "*" + "@types/react" "*" + "@types/react-virtualized@^9.18.7": version "9.18.7" resolved "https://registry.yarnpkg.com/@types/react-virtualized/-/react-virtualized-9.18.7.tgz#8703d8904236819facff90b8b320f29233160c90" @@ -2717,6 +2729,11 @@ buffer-crc32@~0.2.3: resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= +buffer-equal-constant-time@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= + buffer-equal@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-0.0.1.tgz#91bc74b11ea405bc916bc6aa908faafa5b4aac4b" @@ -4959,6 +4976,13 @@ ecc-jsbn@~0.1.1: dependencies: jsbn "~0.1.0" +ecdsa-sig-formatter@1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz#1c595000f04a8897dfb85000892a0f4c33af86c3" + integrity sha1-HFlQAPBKiJffuFAAiSoPTDOvhsM= + dependencies: + safe-buffer "^5.0.1" + editions@^1.1.1, editions@^1.3.4: version "1.3.4" resolved "https://registry.yarnpkg.com/editions/-/editions-1.3.4.tgz#3662cb592347c3168eb8e498a0ff73271d67f50b" @@ -6329,6 +6353,11 @@ forever-agent@~0.6.1: resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= +form-data-to-object@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/form-data-to-object/-/form-data-to-object-0.2.0.tgz#f7a8e68ddd910a1100a65e25ac6a484143ff8168" + integrity sha1-96jmjd2RChEApl4lrGpIQUP/gWg= + form-data@^2.3.1, form-data@~2.3.1: version "2.3.2" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.2.tgz#4970498be604c20c005d4f5c23aecd21d6b49099" @@ -6352,6 +6381,14 @@ formidable@^1.1.1: resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.2.1.tgz#70fb7ca0290ee6ff961090415f4b3df3d2082659" integrity sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg== +formsy-react@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/formsy-react/-/formsy-react-1.1.5.tgz#ee0911bb70712eb6fb9924d56fdb974a19006955" + integrity sha512-nNWe4Vbp6aDQ/zSxJ7gVQgD5+avFRVbydcjA2Om42flxpQFrKE54AAbuyEj3Jvv+2b9LVl+WLMAPalyvLjwNcQ== + dependencies: + form-data-to-object "^0.2.0" + prop-types "^15.5.10" + fragment-cache@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" @@ -9254,6 +9291,21 @@ jsonpointer@^4.0.0: resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" integrity sha1-T9kss04OnbPInIYi7PUfm5eMbLk= +jsonwebtoken@^8.3.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.3.0.tgz#056c90eee9a65ed6e6c72ddb0a1d325109aaf643" + integrity sha512-oge/hvlmeJCH+iIz1DwcO7vKPkNGJHhgkspk8OH3VKlw+mbi42WtD4ig1+VXRln765vxptAv+xT26Fd3cteqag== + dependencies: + jws "^3.1.5" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.1.1" + jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -9315,6 +9367,23 @@ just-reduce-object@^1.0.3: resolved "https://registry.yarnpkg.com/just-reduce-object/-/just-reduce-object-1.1.0.tgz#d29d172264f8511c74462de30d72d5838b6967e6" integrity sha512-nGyg7N9FEZsyrGQNilkyVLxKPsf96iel5v0DrozQ19ML+96HntyS/53bOP68iK/kZUGvsL3FKygV8nQYYhgTFw== +jwa@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.1.6.tgz#87240e76c9808dbde18783cf2264ef4929ee50e6" + integrity sha512-tBO/cf++BUsJkYql/kBbJroKOgHWEigTKBAjjBEmrMGYd1QMBC74Hr4Wo2zCZw6ZrVhlJPvoMrkcOnlWR/DJfw== + dependencies: + buffer-equal-constant-time "1.0.1" + ecdsa-sig-formatter "1.0.10" + safe-buffer "^5.0.1" + +jws@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.1.5.tgz#80d12d05b293d1e841e7cb8b4e69e561adcf834f" + integrity sha512-GsCSexFADNQUr8T5HPJvayTjvPIfoyJPtLQBwn5a4WZQchcrPMPMAWcC1AzJVRDKyD6ZPROPAxgv6rfHViO4uQ== + dependencies: + jwa "^1.1.5" + safe-buffer "^5.0.1" + karma-chrome-launcher@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/karma-chrome-launcher/-/karma-chrome-launcher-2.1.1.tgz#216879c68ac04d8d5140e99619ba04b59afd46cf" @@ -9919,6 +9988,11 @@ lodash.get@^4.4.2: resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + integrity sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8= + lodash.isarguments@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" @@ -9929,6 +10003,11 @@ lodash.isarray@^3.0.0: resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" integrity sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U= +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY= + lodash.isempty@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.isempty/-/lodash.isempty-4.4.0.tgz#6f86cbedd8be4ec987be9aaf33c9684db1b31e7e" @@ -9939,11 +10018,26 @@ lodash.isequal@^4.0.0, lodash.isequal@^4.1.1, lodash.isequal@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + integrity sha1-YZwK89A/iwTDH1iChAt3sRzWg0M= + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + integrity sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w= + lodash.isobject@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/lodash.isobject/-/lodash.isobject-3.0.2.tgz#3c8fb8d5b5bf4bf90ae06e14f2a530a4ed935e1d" integrity sha1-PI+41bW/S/kK4G4U8qUwpO2TXh0= +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs= + lodash.isstring@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" @@ -10008,6 +10102,11 @@ lodash.omitby@^4.6.0: resolved "https://registry.yarnpkg.com/lodash.omitby/-/lodash.omitby-4.6.0.tgz#5c15ff4754ad555016b53c041311e8f079204791" integrity sha1-XBX/R1StVVAWtTwEExHo8HkgR5E= +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= + lodash.orderby@4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.orderby/-/lodash.orderby-4.6.0.tgz#e697f04ce5d78522f54d9338b32b81a3393e4eb3" @@ -10742,7 +10841,7 @@ ms@2.0.0: resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= -ms@^2.0.0: +ms@^2.0.0, ms@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== From 234eae050199c677f0fce17ca3a3c690c744cb92 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Tue, 16 Oct 2018 12:18:49 -0400 Subject: [PATCH 76/94] [Beats CM] Re-arch table assignment control definitions (#23663) * Patch changes to latest feature branch. * Update table controls to offer standard search bar if kuery bar props not provided. --- .../components/table/assignment_options.tsx | 109 -------------- .../components/table/assignment_schema.ts | 70 +++++++++ .../public/components/table/controls.tsx | 109 +++++++------- .../public/components/table/index.ts | 8 +- .../components/table/primary_options.tsx | 84 ----------- .../public/components/table/table.tsx | 97 +++++++----- .../components/table/table_search_control.tsx | 29 ++++ .../components/table/table_type_configs.tsx | 13 +- .../table_controls/action_control.tsx | 79 ++++++++++ .../public/components/table_controls/index.ts | 7 + .../table_controls/option_control.tsx | 54 +++++++ .../table_controls/popover_control.tsx | 90 +++++++++++ .../table_controls/selection_count.tsx | 17 +++ .../tag_assignment.tsx | 28 +--- .../table_controls/tag_badge_list.tsx | 29 ++++ .../public/components/tag/index.ts | 1 - .../public/components/tag/tag_edit.tsx | 24 ++- .../public/pages/beat/tags.tsx | 91 +---------- .../public/pages/main/beats.tsx | 141 ++++++++++++------ .../public/pages/main/create_tag_fragment.tsx | 7 + .../public/pages/main/tags.tsx | 141 ++---------------- .../public/pages/tag/index.tsx | 8 + 22 files changed, 637 insertions(+), 599 deletions(-) delete mode 100644 x-pack/plugins/beats_management/public/components/table/assignment_options.tsx create mode 100644 x-pack/plugins/beats_management/public/components/table/assignment_schema.ts delete mode 100644 x-pack/plugins/beats_management/public/components/table/primary_options.tsx create mode 100644 x-pack/plugins/beats_management/public/components/table/table_search_control.tsx create mode 100644 x-pack/plugins/beats_management/public/components/table_controls/action_control.tsx create mode 100644 x-pack/plugins/beats_management/public/components/table_controls/index.ts create mode 100644 x-pack/plugins/beats_management/public/components/table_controls/option_control.tsx create mode 100644 x-pack/plugins/beats_management/public/components/table_controls/popover_control.tsx create mode 100644 x-pack/plugins/beats_management/public/components/table_controls/selection_count.tsx rename x-pack/plugins/beats_management/public/components/{tag => table_controls}/tag_assignment.tsx (56%) create mode 100644 x-pack/plugins/beats_management/public/components/table_controls/tag_badge_list.tsx diff --git a/x-pack/plugins/beats_management/public/components/table/assignment_options.tsx b/x-pack/plugins/beats_management/public/components/table/assignment_options.tsx deleted file mode 100644 index 67d32ba15ea33..0000000000000 --- a/x-pack/plugins/beats_management/public/components/table/assignment_options.tsx +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner, EuiPopover } from '@elastic/eui'; -import React from 'react'; -import { ActionButton } from './action_button'; -import { ControlDefinitions } from './table_type_configs'; - -interface AssignmentOptionsProps { - assignmentOptions: any[] | null; - assignmentTitle: string | null; - renderAssignmentOptions?: (item: any, key: string) => any; - controlDefinitions: ControlDefinitions; - selectionCount: number; - actionHandler(action: string, payload?: any): void; -} - -interface AssignmentOptionsState { - isAssignmentPopoverVisible: boolean; - isActionPopoverVisible: boolean; -} - -export class AssignmentOptions extends React.Component< - AssignmentOptionsProps, - AssignmentOptionsState -> { - constructor(props: AssignmentOptionsProps) { - super(props); - - this.state = { - isAssignmentPopoverVisible: false, - isActionPopoverVisible: false, - }; - } - - public render() { - const { - actionHandler, - assignmentOptions, - renderAssignmentOptions, - assignmentTitle, - controlDefinitions: { actions }, - selectionCount, - } = this.props; - const { isActionPopoverVisible, isAssignmentPopoverVisible } = this.state; - return ( - - {selectionCount} selected - - { - this.setState({ isActionPopoverVisible: false }); - }} - isPopoverVisible={isActionPopoverVisible} - showPopover={() => { - this.setState({ isActionPopoverVisible: true }); - }} - /> - - {assignmentTitle && ( - - { - this.setState({ - isAssignmentPopoverVisible: true, - }); - actionHandler('loadAssignmentOptions'); - }} - > - {assignmentTitle} - - } - closePopover={() => { - this.setState({ isAssignmentPopoverVisible: false }); - }} - id="assignmentList" - isOpen={isAssignmentPopoverVisible} - panelPaddingSize="s" - withTitle - > - {assignmentOptions && renderAssignmentOptions ? ( - // @ts-ignore direction prop not available on current typing - - {assignmentOptions.map((options, index) => - renderAssignmentOptions(options, `${index}`) - )} - - ) : ( -
- Loading -
- )} -
-
- )} -
- ); - } -} diff --git a/x-pack/plugins/beats_management/public/components/table/assignment_schema.ts b/x-pack/plugins/beats_management/public/components/table/assignment_schema.ts new file mode 100644 index 0000000000000..305a6f3470662 --- /dev/null +++ b/x-pack/plugins/beats_management/public/components/table/assignment_schema.ts @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { AssignmentActionType } from './table'; + +export enum AssignmentComponentType { + Action, + Popover, + SelectionCount, + TagBadgeList, +} + +export interface AssignmentControlSchema { + name: string; + type: AssignmentComponentType; + danger?: boolean; + action?: AssignmentActionType; + showWarning?: boolean; + warningHeading?: string; + warningMessage?: string; + lazyLoad?: boolean; + children?: AssignmentControlSchema[]; + grow?: boolean; +} + +export const beatsListAssignmentOptions: AssignmentControlSchema[] = [ + { + type: AssignmentComponentType.Action, + grow: false, + name: 'Disenroll selected', + showWarning: true, + warningHeading: 'Disenroll beats', + warningMessage: 'This will disenroll the selected beat(s) from centralized management', + action: AssignmentActionType.Delete, + danger: true, + }, + { + type: AssignmentComponentType.Popover, + name: 'Set tags', + grow: false, + lazyLoad: true, + children: [ + { + name: 'Assign tags', + type: AssignmentComponentType.TagBadgeList, + }, + ], + }, + { + type: AssignmentComponentType.SelectionCount, + grow: true, + name: 'selectionCount', + }, +]; + +export const tagConfigAssignmentOptions: AssignmentControlSchema[] = [ + { + type: AssignmentComponentType.Action, + danger: true, + grow: false, + name: 'Detach beat(s)', + showWarning: true, + warningHeading: 'Detatch beats', + warningMessage: 'This will detatch the selected beat(s) from this tag.', + action: AssignmentActionType.Delete, + }, +]; diff --git a/x-pack/plugins/beats_management/public/components/table/controls.tsx b/x-pack/plugins/beats_management/public/components/table/controls.tsx index 27e0255602675..065cdbc0ed6b3 100644 --- a/x-pack/plugins/beats_management/public/components/table/controls.tsx +++ b/x-pack/plugins/beats_management/public/components/table/controls.tsx @@ -4,74 +4,67 @@ * you may not use this file except in compliance with the Elastic License. */ +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import React from 'react'; -import { AssignmentOptions } from './assignment_options'; -import { PrimaryOptions } from './primary_options'; -import { ControlDefinitions } from './table_type_configs'; +import { AutocompleteField } from '../autocomplete_field/index'; +import { OptionControl } from '../table_controls'; +import { AssignmentOptions as AssignmentOptionsType, KueryBarProps } from './table'; +import { TableSearchControl } from './table_search_control'; interface ControlBarProps { - assignmentOptions: any[] | null; - assignmentTitle: string | null; - renderAssignmentOptions?: (item: any, key: string) => any; - - showAssignmentOptions: boolean; - controlDefinitions: ControlDefinitions; + assignmentOptions: AssignmentOptionsType; + kueryBarProps?: KueryBarProps; selectionCount: number; - - isLoadingSuggestions: any; - onKueryBarSubmit: any; - kueryValue: any; - isKueryValid: any; - onKueryBarChange: any; - loadSuggestions: any; - suggestions: any; - filterQueryDraft: any; - actionHandler(actionType: string, payload?: any): void; } export function ControlBar(props: ControlBarProps) { const { - actionHandler, - assignmentOptions, - renderAssignmentOptions, - assignmentTitle, - controlDefinitions, + assignmentOptions: { actionHandler, items, schema, type }, + kueryBarProps, selectionCount, - showAssignmentOptions, - isLoadingSuggestions, - isKueryValid, - kueryValue, - loadSuggestions, - onKueryBarChange, - onKueryBarSubmit, - suggestions, - filterQueryDraft, } = props; - const filters = controlDefinitions.filters.length === 0 ? null : controlDefinitions.filters; - return selectionCount !== 0 && showAssignmentOptions ? ( - - ) : ( - actionHandler('search', query)} - primaryActions={controlDefinitions.primaryActions || []} - /> + if (type === 'none') { + return null; + } + + const showSearch = type !== 'assignment' || selectionCount === 0; + const showAssignmentOptions = type === 'assignment' && selectionCount > 0; + const showPrimaryOptions = type === 'primary' && selectionCount > 0; + + return ( + + {showPrimaryOptions && + schema.map(def => ( + + + + ))} + {showSearch && ( + + {kueryBarProps ? ( + + ) : ( + + )} + + )} + {showAssignmentOptions && + schema.map(def => ( + + + + ))} + ); } diff --git a/x-pack/plugins/beats_management/public/components/table/index.ts b/x-pack/plugins/beats_management/public/components/table/index.ts index 2a7bb2f90d0e6..7025a5f334241 100644 --- a/x-pack/plugins/beats_management/public/components/table/index.ts +++ b/x-pack/plugins/beats_management/public/components/table/index.ts @@ -4,7 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -export { Table } from './table'; +export { AssignmentActionType, AssignmentOptions, KueryBarProps, Table } from './table'; +export { + AssignmentComponentType, + AssignmentControlSchema, + beatsListAssignmentOptions, + tagConfigAssignmentOptions, +} from './assignment_schema'; export { ControlBar } from './controls'; export { ActionDefinition, diff --git a/x-pack/plugins/beats_management/public/components/table/primary_options.tsx b/x-pack/plugins/beats_management/public/components/table/primary_options.tsx deleted file mode 100644 index d804438212efd..0000000000000 --- a/x-pack/plugins/beats_management/public/components/table/primary_options.tsx +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import React from 'react'; -// @ts-ignore -import { AutocompleteSuggestion } from 'ui/autocomplete_providers'; -import { AutocompleteField } from '../autocomplete_field/index'; -import { ActionDefinition, FilterDefinition } from '../table'; -import { ActionButton } from './action_button'; - -interface PrimaryOptionsProps { - filters: FilterDefinition[] | null; - primaryActions: ActionDefinition[]; - isLoadingSuggestions: boolean; - loadSuggestions: () => any; - suggestions: AutocompleteSuggestion[]; - onKueryBarSubmit: any; - kueryValue: any; - filterQueryDraft: any; - isKueryValid: any; - onKueryBarChange: any; - actionHandler(actionType: string, payload?: any): void; - onSearchQueryChange(query: any): void; -} - -interface PrimaryOptionsState { - isPopoverVisible: boolean; -} - -export class PrimaryOptions extends React.PureComponent { - constructor(props: PrimaryOptionsProps) { - super(props); - - this.state = { - isPopoverVisible: false, - }; - } - public render() { - // filterQueryDraft, - - const { - actionHandler, - primaryActions, - isLoadingSuggestions, - loadSuggestions, - suggestions, - onKueryBarSubmit, - isKueryValid, - kueryValue, - onKueryBarChange, - } = this.props; - return ( - - - - - - - - - ); - } - private hidePopover = () => this.setState({ isPopoverVisible: false }); - private showPopover = () => this.setState({ isPopoverVisible: true }); -} diff --git a/x-pack/plugins/beats_management/public/components/table/table.tsx b/x-pack/plugins/beats_management/public/components/table/table.tsx index b07182beaeefb..8b29ad34203f1 100644 --- a/x-pack/plugins/beats_management/public/components/table/table.tsx +++ b/x-pack/plugins/beats_management/public/components/table/table.tsx @@ -11,8 +11,46 @@ import { } from '@elastic/eui'; import React from 'react'; import styled from 'styled-components'; +import { AutocompleteSuggestion } from 'ui/autocomplete_providers'; import { TABLE_CONFIG } from '../../../common/constants'; +import { AssignmentControlSchema } from './assignment_schema'; import { ControlBar } from './controls'; +import { TableType } from './table_type_configs'; + +export enum AssignmentActionType { + Add, + Assign, + Delete, + Edit, + Reload, + Search, +} + +export interface AssignmentOptions { + schema: AssignmentControlSchema[]; + items: any[]; + type?: 'none' | 'primary' | 'assignment'; + actionHandler(action: AssignmentActionType, payload?: any): void; +} + +export interface KueryBarProps { + filterQueryDraft: string; + isLoadingSuggestions: boolean; + isValid: boolean; + loadSuggestions: (value: string, cursorPosition: number, maxCount?: number) => void; + onChange?: (value: string) => void; + onSubmit?: (value: string) => void; + suggestions: AutocompleteSuggestion[]; + value: string; +} + +interface TableProps { + assignmentOptions?: AssignmentOptions; + hideTableControls?: boolean; + kueryBarProps?: KueryBarProps; + items: any[]; + type: TableType; +} interface TableState { selection: any[]; @@ -22,7 +60,7 @@ const TableContainer = styled.div` padding: 16px; `; -export class Table extends React.Component { +export class Table extends React.Component { constructor(props: any) { super(props); @@ -42,55 +80,32 @@ export class Table extends React.Component { }; public render() { - const { - actionHandler, - assignmentOptions, - renderAssignmentOptions, - assignmentTitle, - items, - showAssignmentOptions, - type, - isLoadingSuggestions, - loadSuggestions, - onKueryBarSubmit, - isKueryValid, - kueryValue, - onKueryBarChange, - suggestions, - filterQueryDraft, - } = this.props; + const { assignmentOptions, hideTableControls, items, kueryBarProps, type } = this.props; const pagination = { initialPageSize: TABLE_CONFIG.INITIAL_ROW_SIZE, pageSizeOptions: TABLE_CONFIG.PAGE_SIZE_OPTIONS, }; - const selectionOptions = { - onSelectionChange: this.setSelection, - selectable: () => true, - selectableMessage: () => 'Select this beat', - selection: this.state.selection, - }; + const selectionOptions = hideTableControls + ? null + : { + onSelectionChange: this.setSelection, + selectable: () => true, + selectableMessage: () => 'Select this beat', + selection: this.state.selection, + }; return ( - + {!hideTableControls && + assignmentOptions && ( + + )} { + const { actionHandler, filters } = props; + return ( + actionHandler(AssignmentActionType.Search, query)} + /> + ); +}; diff --git a/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx b/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx index 0c70f2ac67f85..f5a3e9c441844 100644 --- a/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx +++ b/x-pack/plugins/beats_management/public/components/table/table_type_configs.tsx @@ -5,7 +5,7 @@ */ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { first, flatten, sortBy, sortByOrder, uniq } from 'lodash'; +import { first, sortBy, sortByOrder, uniq } from 'lodash'; import moment from 'moment'; import React from 'react'; import { BeatTag, CMPopulatedBeat, ConfigurationBlock } from '../../../common/domain_types'; @@ -115,17 +115,6 @@ export const BeatsTableType: TableType = { name: 'Type', options: uniq(data.map(({ type }: { type: any }) => ({ value: type })), 'value'), }, - { - type: 'field_value_selection', - field: 'full_tags', - name: 'Tags', - options: uniq( - flatten(data.map((item: any) => item.full_tags || [])).map((tag: any) => ({ - value: tag.id, - })), - 'value' - ), - }, ], }), }; diff --git a/x-pack/plugins/beats_management/public/components/table_controls/action_control.tsx b/x-pack/plugins/beats_management/public/components/table_controls/action_control.tsx new file mode 100644 index 0000000000000..74c9a0f404c5c --- /dev/null +++ b/x-pack/plugins/beats_management/public/components/table_controls/action_control.tsx @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + EuiButton, + // @ts-ignore EuiConfirmModal typings not included in current EUI + EuiConfirmModal, + EuiOverlayMask, +} from '@elastic/eui'; +import React from 'react'; +import { AssignmentActionType } from '../table'; + +interface ActionControlProps { + action: AssignmentActionType; + danger?: boolean; + name: string; + showWarning?: boolean; + warningHeading?: string; + warningMessage?: string; + actionHandler(action: AssignmentActionType, payload?: any): void; +} + +interface ActionControlState { + showModal: boolean; +} + +export class ActionControl extends React.PureComponent { + constructor(props: ActionControlProps) { + super(props); + + this.state = { + showModal: false, + }; + } + + public render() { + const { + action, + actionHandler, + danger, + name, + showWarning, + warningHeading, + warningMessage, + } = this.props; + return ( +
+ this.setState({ showModal: true }) : () => actionHandler(action) + } + > + {name} + + {this.state.showModal && ( + + { + actionHandler(action); + this.setState({ showModal: false }); + }} + onCancel={() => this.setState({ showModal: false })} + title={warningHeading ? warningHeading : 'Confirm'} + > + {warningMessage} + + + )} +
+ ); + } +} diff --git a/x-pack/plugins/beats_management/public/components/table_controls/index.ts b/x-pack/plugins/beats_management/public/components/table_controls/index.ts new file mode 100644 index 0000000000000..f97cd9e727d2a --- /dev/null +++ b/x-pack/plugins/beats_management/public/components/table_controls/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { OptionControl } from './option_control'; diff --git a/x-pack/plugins/beats_management/public/components/table_controls/option_control.tsx b/x-pack/plugins/beats_management/public/components/table_controls/option_control.tsx new file mode 100644 index 0000000000000..7640d3a8965e2 --- /dev/null +++ b/x-pack/plugins/beats_management/public/components/table_controls/option_control.tsx @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { AssignmentComponentType, AssignmentControlSchema } from '../table'; +import { AssignmentActionType } from '../table'; +import { ActionControl } from './action_control'; +import { PopoverControl } from './popover_control'; +import { SelectionCount } from './selection_count'; +import { TagBadgeList } from './tag_badge_list'; + +interface OptionControlProps { + items: any[]; + schema: AssignmentControlSchema; + selectionCount: number; + actionHandler(action: AssignmentActionType, payload?: any): void; +} + +export const OptionControl = (props: OptionControlProps) => { + const { + actionHandler, + items, + schema, + schema: { action, danger, name, showWarning, warningHeading, warningMessage }, + selectionCount, + } = props; + switch (schema.type) { + case AssignmentComponentType.Action: + if (!action) { + throw Error('Action cannot be undefined'); + } + return ( + + ); + case AssignmentComponentType.Popover: + return ; + case AssignmentComponentType.SelectionCount: + return ; + case AssignmentComponentType.TagBadgeList: + return ; + } + return
{schema.type}
; +}; diff --git a/x-pack/plugins/beats_management/public/components/table_controls/popover_control.tsx b/x-pack/plugins/beats_management/public/components/table_controls/popover_control.tsx new file mode 100644 index 0000000000000..bb66ab50aaef8 --- /dev/null +++ b/x-pack/plugins/beats_management/public/components/table_controls/popover_control.tsx @@ -0,0 +1,90 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiButton, EuiPopover } from '@elastic/eui'; +import React from 'react'; +import { AssignmentActionType } from '../table'; +import { AssignmentControlSchema } from '../table/assignment_schema'; +import { OptionControl } from './option_control'; + +interface PopoverControlProps { + items: any[]; + schema: AssignmentControlSchema; + selectionCount: number; + actionHandler(action: AssignmentActionType, payload?: any): void; +} + +interface PopoverControlState { + showPopover: boolean; +} + +export class PopoverControl extends React.PureComponent { + constructor(props: PopoverControlProps) { + super(props); + + this.state = { + showPopover: false, + }; + } + + public componentDidMount() { + const { + schema: { lazyLoad }, + } = this.props; + if (!lazyLoad) { + this.props.actionHandler(AssignmentActionType.Reload); + } + } + + public render() { + const { + actionHandler, + items, + schema: { children, lazyLoad, name }, + selectionCount, + } = this.props; + return ( + { + if (lazyLoad) { + actionHandler(AssignmentActionType.Reload); + } + this.setState({ + showPopover: true, + }); + }} + > + {name} + + } + closePopover={() => { + this.setState({ showPopover: false }); + }} + id="assignmentList" + isOpen={this.state.showPopover} + panelPaddingSize="s" + withTitle + > + {children + ? children.map(def => ( + + )) + : null} + + ); + } +} diff --git a/x-pack/plugins/beats_management/public/components/table_controls/selection_count.tsx b/x-pack/plugins/beats_management/public/components/table_controls/selection_count.tsx new file mode 100644 index 0000000000000..8a4a525a2ddbf --- /dev/null +++ b/x-pack/plugins/beats_management/public/components/table_controls/selection_count.tsx @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +interface SelectionCountProps { + selectionCount: number; +} + +export const SelectionCount = (props: SelectionCountProps) => ( +
+ {props.selectionCount} {`item${props.selectionCount === 1 ? '' : 's'}`} selected +
+); diff --git a/x-pack/plugins/beats_management/public/components/tag/tag_assignment.tsx b/x-pack/plugins/beats_management/public/components/table_controls/tag_assignment.tsx similarity index 56% rename from x-pack/plugins/beats_management/public/components/tag/tag_assignment.tsx rename to x-pack/plugins/beats_management/public/components/table_controls/tag_assignment.tsx index 61509eacc95e9..952636d9b9804 100644 --- a/x-pack/plugins/beats_management/public/components/tag/tag_assignment.tsx +++ b/x-pack/plugins/beats_management/public/components/table_controls/tag_assignment.tsx @@ -7,14 +7,11 @@ import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; import React from 'react'; import { TABLE_CONFIG } from '../../../common/constants'; -import { BeatTag, CMPopulatedBeat } from '../../../common/domain_types'; -import { TagBadge } from './tag_badge'; +import { TagBadge } from '../tag/tag_badge'; interface TagAssignmentProps { - selectedBeats: CMPopulatedBeat[]; - tag: BeatTag; - assignTagsToBeats(selectedBeats: any, tag: any): void; - removeTagsFromBeats(selectedBeats: any, tag: any): void; + tag: any; + assignTag(id: string): void; } interface TagAssignmentState { @@ -32,19 +29,13 @@ export class TagAssignment extends React.PureComponent - (tags || []).some((t: string) => t === id) - ); - return ( - + {this.state.isFetchingTags && ( @@ -52,15 +43,8 @@ export class TagAssignment extends React.PureComponent { - this.setState({ isFetchingTags: true }); - hasMatches - ? removeTagsFromBeats(selectedBeats, tag) - : assignTagsToBeats(selectedBeats, tag); - this.setState({ isFetchingTags: false }); - }} + onClick={() => assignTag(id)} onClickAriaLabel={id} tag={tag} /> diff --git a/x-pack/plugins/beats_management/public/components/table_controls/tag_badge_list.tsx b/x-pack/plugins/beats_management/public/components/table_controls/tag_badge_list.tsx new file mode 100644 index 0000000000000..357091aa96d6c --- /dev/null +++ b/x-pack/plugins/beats_management/public/components/table_controls/tag_badge_list.tsx @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import React from 'react'; +import { AssignmentActionType } from '../table/table'; +import { TagAssignment } from './tag_assignment'; + +interface TagBadgeListProps { + items: any[]; + actionHandler(action: AssignmentActionType, payload?: any): void; +} + +export const TagBadgeList = (props: TagBadgeListProps) => ( + // @ts-ignore direction prop type "column" not defined in current EUI version + + {props.items.map((item: any) => ( + + props.actionHandler(AssignmentActionType.Assign, id)} + /> + + ))} + +); diff --git a/x-pack/plugins/beats_management/public/components/tag/index.ts b/x-pack/plugins/beats_management/public/components/tag/index.ts index dfa61ec5c3a0a..24a1c3f6f8b1e 100644 --- a/x-pack/plugins/beats_management/public/components/tag/index.ts +++ b/x-pack/plugins/beats_management/public/components/tag/index.ts @@ -4,6 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ -export { TagAssignment } from './tag_assignment'; export { TagBadge } from './tag_badge'; export { TagEdit } from './tag_edit'; diff --git a/x-pack/plugins/beats_management/public/components/tag/tag_edit.tsx b/x-pack/plugins/beats_management/public/components/tag/tag_edit.tsx index 6763a837daa9f..7d80b180e2f87 100644 --- a/x-pack/plugins/beats_management/public/components/tag/tag_edit.tsx +++ b/x-pack/plugins/beats_management/public/components/tag/tag_edit.tsx @@ -5,8 +5,6 @@ */ import { - // @ts-ignore - EuiBadge, EuiButton, // @ts-ignore EuiColorPicker, @@ -27,14 +25,16 @@ import { isEqual } from 'lodash'; import React from 'react'; import { BeatTag, CMBeat, ConfigurationBlock } from '../../../common/domain_types'; import { ConfigList } from '../config_list'; -import { Table } from '../table'; +import { AssignmentActionType, Table } from '../table'; import { BeatsTableType } from '../table'; +import { tagConfigAssignmentOptions } from '../table'; import { ConfigView } from './config_view'; import { TagBadge } from './tag_badge'; interface TagEditProps { mode: 'edit' | 'create'; tag: Pick>; + onDetachBeat: (beatIds: string[]) => void; onTagChange: (field: keyof BeatTag, value: string) => any; attachedBeats: CMBeat[] | null; } @@ -159,14 +159,14 @@ export class TagEdit extends React.PureComponent {

Attached Beats

{ - /* TODO: this prop should be optional */ + assignmentOptions={{ + schema: tagConfigAssignmentOptions, + items: [], + type: 'primary', + actionHandler: this.handleAssignmentActions, }} - assignmentOptions={[]} - assignmentTitle={null} items={attachedBeats} ref={this.state.tableRef} - showAssignmentOptions={false} type={BeatsTableType} /> @@ -207,6 +207,14 @@ export class TagEdit extends React.PureComponent { } }; + private handleAssignmentActions = (action: AssignmentActionType) => { + switch (action) { + case AssignmentActionType.Delete: + const { selection } = this.state.tableRef.current.state; + this.props.onDetachBeat(selection.map((beat: any) => beat.id)); + } + }; + // TODO this should disable save button on bad validations private updateTag = (key: keyof BeatTag, value?: any) => value !== undefined diff --git a/x-pack/plugins/beats_management/public/pages/beat/tags.tsx b/x-pack/plugins/beats_management/public/pages/beat/tags.tsx index 847160b2fb312..9d0a693e8efa7 100644 --- a/x-pack/plugins/beats_management/public/pages/beat/tags.tsx +++ b/x-pack/plugins/beats_management/public/pages/beat/tags.tsx @@ -5,8 +5,6 @@ */ import { EuiGlobalToastList } from '@elastic/eui'; -import { get } from 'lodash'; -import moment from 'moment'; import React from 'react'; import { CMPopulatedBeat } from '../../../common/domain_types'; import { BeatDetailTagsTable, Table } from '../../components/table'; @@ -43,12 +41,9 @@ export class BeatTagsPage extends React.PureComponent
@@ -61,90 +56,6 @@ export class BeatTagsPage extends React.PureComponent { - return get(this.tableRef, 'current.state.selection', []); - }; - - private setUpdatedTagNotification = ( - numRemoved: number, - totalTags: number, - action: 'remove' | 'add' - ) => { - const { beat } = this.state; - const actionName = action === 'remove' ? 'Removed' : 'Added'; - const preposition = action === 'remove' ? 'from' : 'to'; - this.setState({ - notifications: this.state.notifications.concat({ - title: `Tags ${actionName} ${preposition} Beat`, - color: 'success', - id: moment.now(), - text: ( -

{`${actionName} ${numRemoved} of ${totalTags} tags ${preposition} ${ - beat ? beat.name || beat.id : 'beat' - }`}

- ), - }), - }); - }; - - private handleTableAction = async (action: string, payload: any) => { - switch (action) { - case 'add': - await this.associateTagsToBeat(); - break; - case 'remove': - await this.disassociateTagsFromBeat(); - break; - case 'search': - // TODO: add search filtering for tag names - // awaiting an ES filter endpoint - break; - } - this.getBeat(); - }; - - private associateTagsToBeat = async () => { - const { beat } = this.state; - - if (!beat) { - throw new Error('Beat cannot be undefined'); - } - - const tagsToAssign = this.getSelectedTags().filter( - (tag: any) => !beat.full_tags.some(({ id }) => tag.id === id) - ); - const assignments = tagsToAssign.map((tag: any) => { - return { - beatId: beat.id, - tag: tag.id, - }; - }); - - await this.props.libs.beats.assignTagsToBeats(assignments); - this.setUpdatedTagNotification(assignments.length, tagsToAssign.length, 'add'); - }; - - private disassociateTagsFromBeat = async () => { - const { beat } = this.state; - - if (!beat) { - throw new Error('Beat cannot be undefined'); - } - - const tagsToDisassociate = this.getSelectedTags().filter((tag: any) => - beat.full_tags.some(({ id }) => tag.id === id) - ); - const assignments = tagsToDisassociate.map((tag: any) => { - return { - beatId: beat.id, - tag: tag.id, - }; - }); - - await this.props.libs.beats.removeTagsFromBeats(assignments); - this.setUpdatedTagNotification(assignments.length, tagsToDisassociate.length, 'remove'); - }; - private getBeat = async () => { try { const beat = await this.props.libs.beats.get(this.props.beatId); diff --git a/x-pack/plugins/beats_management/public/pages/main/beats.tsx b/x-pack/plugins/beats_management/public/pages/main/beats.tsx index 350fc439d00ed..5e3541fb7919b 100644 --- a/x-pack/plugins/beats_management/public/pages/main/beats.tsx +++ b/x-pack/plugins/beats_management/public/pages/main/beats.tsx @@ -17,11 +17,12 @@ import { sortBy } from 'lodash'; import moment from 'moment'; import React from 'react'; import { RouteComponentProps } from 'react-router'; -import { BeatTag, CMPopulatedBeat } from '../../../common/domain_types'; +import { CMPopulatedBeat } from '../../../common/domain_types'; import { BeatsTagAssignment } from '../../../server/lib/adapters/beats/adapter_types'; import { AppURLState } from '../../app'; import { BeatsTableType, Table } from '../../components/table'; -import { TagAssignment } from '../../components/tag'; +import { beatsListAssignmentOptions } from '../../components/table/assignment_schema'; +import { AssignmentActionType } from '../../components/table/table'; import { WithKueryAutocompletion } from '../../containers/with_kuery_autocompletion'; import { URLStateProps } from '../../containers/with_url_state'; import { FrontendLibs } from '../../lib/lib'; @@ -107,26 +108,28 @@ export class BeatsPage extends React.PureComponent {autocompleteProps => (
this.props.setUrlState({ beatsKBar: value })} // todo - onKueryBarSubmit={() => null} // todo - filterQueryDraft={'false'} // todo - actionHandler={this.handleBeatsActions} - assignmentOptions={this.state.tags} - assignmentTitle="Set tags" - items={sortBy(this.props.beats || [], 'id') || []} + kueryBarProps={{ + ...autocompleteProps, + filterQueryDraft: 'false', // todo + isValid: this.props.libs.elasticsearch.isKueryValid( + this.props.urlState.beatsKBar || '' + ), // todo check if query converts to es query correctly + onChange: (value: any) => this.props.setUrlState({ beatsKBar: value }), // todo + onSubmit: () => null, // todo + value: this.props.urlState.beatsKBar || '', + }} + assignmentOptions={{ + items: this.state.tags || [], + schema: beatsListAssignmentOptions, + type: 'assignment', + actionHandler: this.handleBeatsActions, + }} + items={sortBy(this.props.beats, 'id') || []} ref={this.state.tableRef} - showAssignmentOptions={true} - renderAssignmentOptions={this.renderTagAssignment} type={BeatsTableType} /> )} - this.setState({ notifications: [] })} @@ -136,28 +139,21 @@ export class BeatsPage extends React.PureComponent ( - - ); - - private handleBeatsActions = (action: string, payload: any) => { + private handleBeatsActions = (action: AssignmentActionType, payload: any) => { switch (action) { - case 'edit': + case AssignmentActionType.Assign: + this.handleBeatTagAssignment(payload); + break; + case AssignmentActionType.Edit: // TODO: navigate to edit page break; - case 'delete': + case AssignmentActionType.Delete: this.deleteSelected(); break; - case 'search': + case AssignmentActionType.Search: this.handleSearchQuery(payload); break; - case 'loadAssignmentOptions': + case AssignmentActionType.Reload: this.loadTags(); break; } @@ -165,11 +161,32 @@ export class BeatsPage extends React.PureComponent { + const selected = this.getSelectedBeats(); + const removals: CMPopulatedBeat[] = []; + const additions: CMPopulatedBeat[] = []; + selected.forEach(beat => { + if (beat.full_tags.some(tag => tag.id === tagId)) { + removals.push(beat); + } else { + additions.push(beat); + } + }); + + await Promise.all([ + this.removeTagsFromBeats(removals, tagId), + this.assignTagsToBeats(additions, tagId), + ]); + }; + private deleteSelected = async () => { const selected = this.getSelectedBeats(); for (const beat of selected) { await this.props.libs.beats.update(beat.id, { active: false }); } + + this.notifyBeatDisenrolled(selected); + // because the compile code above has a very minor race condition, we wait, // the max race condition time is really 10ms but doing 100 to be safe setTimeout(async () => { @@ -191,21 +208,45 @@ export class BeatsPage extends React.PureComponent beats.map(({ id }) => ({ beatId: id, tag: tag.id })); - - private removeTagsFromBeats = async (beats: CMPopulatedBeat[], tag: BeatTag) => { - const assignments = this.createBeatTagAssignments(beats, tag); - await this.props.libs.beats.removeTagsFromBeats(assignments); - await this.refreshData(); - this.notifyUpdatedTagAssociation('remove', assignments, tag.id); + tagId: string + ): BeatsTagAssignment[] => beats.map(({ id }) => ({ beatId: id, tag: tagId })); + + private removeTagsFromBeats = async (beats: CMPopulatedBeat[], tagId: string) => { + if (beats.length) { + const assignments = this.createBeatTagAssignments(beats, tagId); + await this.props.libs.beats.removeTagsFromBeats(assignments); + await this.refreshData(); + this.notifyUpdatedTagAssociation('remove', assignments, tagId); + } }; - private assignTagsToBeats = async (beats: CMPopulatedBeat[], tag: BeatTag) => { - const assignments = this.createBeatTagAssignments(beats, tag); - await this.props.libs.beats.assignTagsToBeats(assignments); - await this.refreshData(); - this.notifyUpdatedTagAssociation('add', assignments, tag.id); + private assignTagsToBeats = async (beats: CMPopulatedBeat[], tagId: string) => { + if (beats.length) { + const assignments = this.createBeatTagAssignments(beats, tagId); + await this.props.libs.beats.assignTagsToBeats(assignments); + await this.refreshData(); + this.notifyUpdatedTagAssociation('add', assignments, tagId); + } + }; + + private notifyBeatDisenrolled = async (beats: CMPopulatedBeat[]) => { + let title; + let text; + if (beats.length === 1) { + title = `"${beats[0].name || beats[0].id}" disenrolled`; + text = `Beat with ID "${beats[0].id}" was disenrolled.`; + } else { + title = `${beats.length} beats disenrolled`; + } + + this.setState({ + notifications: this.state.notifications.concat({ + color: 'warning', + id: `disenroll_${new Date()}`, + title, + text, + }), + }); }; private notifyUpdatedTagAssociation = ( @@ -217,7 +258,7 @@ export class BeatsPage extends React.PureComponent { + const beat = this.props.beats.find(b => b.id === beatId); + if (beat) { + return beat.name; + } + return null; + }; + private refreshData = async () => { await this.loadTags(); await this.props.loadBeats(); diff --git a/x-pack/plugins/beats_management/public/pages/main/create_tag_fragment.tsx b/x-pack/plugins/beats_management/public/pages/main/create_tag_fragment.tsx index a8947a5628f64..e0f561d10e6f4 100644 --- a/x-pack/plugins/beats_management/public/pages/main/create_tag_fragment.tsx +++ b/x-pack/plugins/beats_management/public/pages/main/create_tag_fragment.tsx @@ -51,6 +51,13 @@ export class CreateTagFragment extends React.PureComponent { + this.props.libs.beats.removeTagsFromBeats( + beatIds.map(id => { + return { beatId: id, tag: this.state.tag.id }; + }) + ); + }} onTagChange={(field: string, value: string | number) => this.setState(oldState => ({ tag: { ...oldState.tag, [field]: value }, diff --git a/x-pack/plugins/beats_management/public/pages/main/tags.tsx b/x-pack/plugins/beats_management/public/pages/main/tags.tsx index cc46e5f7a3fa6..ad5ebdaca615e 100644 --- a/x-pack/plugins/beats_management/public/pages/main/tags.tsx +++ b/x-pack/plugins/beats_management/public/pages/main/tags.tsx @@ -4,18 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - EuiButton, - EuiButtonEmpty, - EuiFlexGroup, - EuiFlexItem, - EuiIcon, - // @ts-ignore EuiToolTip has no typings in current version - EuiToolTip, -} from '@elastic/eui'; +import { EuiButton } from '@elastic/eui'; import React from 'react'; -import { BeatTag, CMBeat } from '../../../common/domain_types'; -import { BeatsTagAssignment } from '../../../server/lib/adapters/beats/adapter_types'; +import { BeatTag } from '../../../common/domain_types'; import { AppURLState } from '../../app'; import { Table, TagsTableType } from '../../components/table'; import { WithKueryAutocompletion } from '../../containers/with_kuery_autocompletion'; @@ -27,7 +18,6 @@ interface TagsPageProps extends URLStateProps { } interface TagsPageState { - beats: any; tags: BeatTag[]; } @@ -43,13 +33,11 @@ export class TagsPage extends React.PureComponent Add Tag ); - private tableRef = React.createRef
(); constructor(props: TagsPageProps) { super(props); this.state = { - beats: [], tags: [], }; @@ -61,19 +49,18 @@ export class TagsPage extends React.PureComponent {autocompleteProps => (
this.props.setUrlState({ tagsKBar: value })} - onKueryBarSubmit={() => null} // todo - filterQueryDraft={'false'} // todo - actionHandler={this.handleTagsAction} - items={this.state.tags || []} - renderAssignmentOptions={(item: any) => item} - ref={this.tableRef} - showAssignmentOptions={true} + kueryBarProps={{ + ...autocompleteProps, + filterQueryDraft: 'false', // todo + isValid: this.props.libs.elasticsearch.isKueryValid( + this.props.urlState.tagsKBar || '' + ), + onChange: (value: any) => this.props.setUrlState({ tagsKBar: value }), + onSubmit: () => null, // todo + value: this.props.urlState.tagsKBar || '', + }} + hideTableControls={true} + items={this.state.tags} type={TagsTableType} /> )} @@ -81,110 +68,10 @@ export class TagsPage extends React.PureComponent ); } - private handleTagsAction = async (action: string, payload: any) => { - switch (action) { - case 'loadAssignmentOptions': - this.loadBeats(); - break; - case 'delete': - const tags = this.getSelectedTags().map(tag => tag.id); - const success = await this.props.libs.tags.delete(tags); - if (!success) { - alert( - 'Some of these tags might be assigned to beats. Please ensure tags being removed are not activly assigned' - ); - } else { - this.loadTags(); - if (this.tableRef && this.tableRef.current) { - this.tableRef.current.resetSelection(); - } - } - break; - } - - this.loadTags(); - }; - private async loadTags() { const tags = await this.props.libs.tags.getAll(); this.setState({ tags, }); } - - private async loadBeats() { - const beats = await this.props.libs.beats.getAll(); - const selectedTags = this.getSelectedTags(); - const renderedBeats = beats.map((beat: CMBeat) => { - const tagsToRemove: BeatTag[] = []; - const tagsToAdd: BeatTag[] = []; - const tags = beat.tags || []; - selectedTags.forEach((tag: BeatTag) => { - tags.some((tagId: string) => tagId === tag.id) - ? tagsToRemove.push(tag) - : tagsToAdd.push(tag); - }); - - const tagIcons = tags.map((tagId: string) => { - const associatedTag = this.state.tags.find(tag => tag.id === tagId); - return ( - Last updated: {associatedTag ? associatedTag.last_updated : null}

} - title={tagId} - > - -
- ); - }); - - return ( - - - {tagIcons.map((icon, index) => ( - - {icon} - - ))} - - { - this.assignTagsToBeats(beat, tagsToAdd); - this.removeTagsFromBeats(beat, tagsToRemove); - this.loadBeats(); - }} - > - {beat.id} - - - - - ); - }); - - this.setState({ - beats: renderedBeats, - }); - } - - private createBeatTagAssignments = (beat: CMBeat, tags: BeatTag[]): BeatsTagAssignment[] => - tags.map(({ id }) => ({ tag: id, beatId: beat.id })); - - private removeTagsFromBeats = async (beat: CMBeat, tags: BeatTag[]) => { - const assignments = this.createBeatTagAssignments(beat, tags); - await this.props.libs.beats.removeTagsFromBeats(assignments); - }; - - private assignTagsToBeats = async (beat: CMBeat, tags: BeatTag[]) => { - const assignments = this.createBeatTagAssignments(beat, tags); - await this.props.libs.beats.assignTagsToBeats(assignments); - }; - - private getSelectedTags = () => { - return this.tableRef.current ? this.tableRef.current.state.selection : []; - }; } diff --git a/x-pack/plugins/beats_management/public/pages/tag/index.tsx b/x-pack/plugins/beats_management/public/pages/tag/index.tsx index 8b288db28a18e..90b20b60fe2da 100644 --- a/x-pack/plugins/beats_management/public/pages/tag/index.tsx +++ b/x-pack/plugins/beats_management/public/pages/tag/index.tsx @@ -58,6 +58,14 @@ export class TagPageComponent extends React.PureComponent { + await this.props.libs.beats.removeTagsFromBeats( + beatIds.map(id => { + return { beatId: id, tag: this.state.tag.id }; + }) + ); + await this.loadAttachedBeats(); + }} onTagChange={(field: string, value: string | number) => this.setState(oldState => ({ tag: { ...oldState.tag, [field]: value }, From c95b30dc44bfe6b8148ac37b76f9ca9ee1b44844 Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Tue, 16 Oct 2018 14:08:34 -0400 Subject: [PATCH 77/94] Tweaked enrollment UI to inlcude beat type selection --- .../public/pages/main/beats.tsx | 5 +- .../public/pages/main/enroll_fragment.tsx | 351 +++++++++++------- 2 files changed, 230 insertions(+), 126 deletions(-) diff --git a/x-pack/plugins/beats_management/public/pages/main/beats.tsx b/x-pack/plugins/beats_management/public/pages/main/beats.tsx index 350fc439d00ed..54c3ee10615d4 100644 --- a/x-pack/plugins/beats_management/public/pages/main/beats.tsx +++ b/x-pack/plugins/beats_management/public/pages/main/beats.tsx @@ -9,6 +9,7 @@ import { EuiButtonEmpty, EuiGlobalToastList, EuiModal, + EuiModalBody, EuiModalHeader, EuiModalHeaderTitle, EuiOverlayMask, @@ -80,7 +81,9 @@ export class BeatsPage extends React.PureComponent Enroll a new Beat - + + + )} diff --git a/x-pack/plugins/beats_management/public/pages/main/enroll_fragment.tsx b/x-pack/plugins/beats_management/public/pages/main/enroll_fragment.tsx index 3dfdf40a73335..717932ced8386 100644 --- a/x-pack/plugins/beats_management/public/pages/main/enroll_fragment.tsx +++ b/x-pack/plugins/beats_management/public/pages/main/enroll_fragment.tsx @@ -7,172 +7,273 @@ import { // @ts-ignore typings for EuiBasicTable not present in current version EuiBasicTable, EuiButton, + EuiFlexGroup, + EuiFlexItem, EuiLoadingSpinner, EuiModalBody, + // @ts-ignore + EuiSelect, + EuiTitle, } from '@elastic/eui'; +import { capitalize } from 'lodash'; import React from 'react'; import { RouteComponentProps } from 'react-router'; import { CMBeat } from '../../../common/domain_types'; import { AppURLState } from '../../app'; import { URLStateProps, withUrlState } from '../../containers/with_url_state'; import { FrontendLibs } from '../../lib/lib'; + interface BeatsProps extends URLStateProps, RouteComponentProps { - match: any + match: any; libs: FrontendLibs; } export class EnrollBeat extends React.Component { - private pinging = false + private pinging = false; constructor(props: BeatsProps) { - super(props) + super(props); this.state = { - enrolledBeat: null - } + enrolledBeat: null, + command: 'sudo filebeat', + }; } - public pingForBeatWithToken = async(libs: FrontendLibs,token: string): Promise => { + public pingForBeatWithToken = async ( + libs: FrontendLibs, + token: string + ): Promise => { try { - const beats = await libs.beats.getBeatWithToken(token); - if(!beats) { throw new Error('no beats') } - return beats; - } catch(err) { - if(this.pinging) { - const timeout = (ms:number) => new Promise(res => setTimeout(res, ms)) - await timeout(5000) + const beats = await libs.beats.getBeatWithToken(token); + if (!beats) { + throw new Error('no beats'); + } + return beats; + } catch (err) { + if (this.pinging) { + const timeout = (ms: number) => new Promise(res => setTimeout(res, ms)); + await timeout(5000); return await this.pingForBeatWithToken(libs, token); } } - } + }; public async componentDidMount() { - if(!this.props.urlState.enrollmentToken) { + if (!this.props.urlState.enrollmentToken) { const enrollmentToken = await this.props.libs.tokens.createEnrollmentToken(); this.props.setUrlState({ - enrollmentToken - }) + enrollmentToken, + }); } } public waitForToken = async (token: string) => { - if(this.pinging) { return } + if (this.pinging) { + return; + } this.pinging = true; - const enrolledBeat = await this.pingForBeatWithToken(this.props.libs, token) as CMBeat; + const enrolledBeat = (await this.pingForBeatWithToken(this.props.libs, token)) as CMBeat; this.setState({ - enrolledBeat - }) - this.pinging = false - } + enrolledBeat, + }); + this.pinging = false; + }; public render() { - if(this.props.urlState.enrollmentToken && !this.state.enrolledBeat) { - this.waitForToken(this.props.urlState.enrollmentToken) - } - const { - goTo, - } = this.props; + if (!this.props.urlState.enrollmentToken) { + return null; + } + if (this.props.urlState.enrollmentToken && !this.state.enrolledBeat) { + this.waitForToken(this.props.urlState.enrollmentToken); + } + const { goTo } = this.props; const actions = []; - switch(this.props.location.pathname) { + switch (this.props.location.pathname) { case '/overview/initial/beats': - actions.push({ - goTo: '/overview/initial/tag', - name: 'Continue' - }) - break - case '/overview/beats/enroll': - actions.push({ - goTo: '/overview/beats/enroll', - name: 'Enroll another Beat', - newToken: true - }) - actions.push({ - goTo: '/overview/beats', - name: 'Done', - clearToken: true - }) - break; + actions.push({ + goTo: '/overview/initial/tag', + name: 'Continue', + }); + break; + case '/overview/beats/enroll': + actions.push({ + goTo: '/overview/beats/enroll', + name: 'Enroll another Beat', + newToken: true, + }); + actions.push({ + goTo: '/overview/beats', + name: 'Done', + clearToken: true, + }); + break; } - return ( -
- {this.props.urlState.enrollmentToken && ( -
- {!this.state.enrolledBeat && ( - - To enroll a Beat with Centeral Management, run this command on the host that has Beats - installed. -
-
-
-
-
- $ beats enroll {window.location.protocol}//{window.location.host}{this.props.libs.framework.baseURLPath ? `/${this.props.libs.framework.baseURLPath}` : ''} {this.props.urlState.enrollmentToken} -
-
-
-
- -
-
- Waiting for enroll command to be run... -
- )} - {this.state.enrolledBeat && ( - - A Beat was enrolled with the following data: -
-
-
- + {!this.state.enrolledBeat && ( + + + + + + +

Select your beat type:

+
+
+
+ this.setState({ beatType: e.target.value })} + fullWidth={true} /> -
-
- {actions.map(action => ( - { - if(action.clearToken) { - this.props.setUrlState({enrollmentToken: ''}) - } - - if(action.newToken) { - const enrollmentToken = await this.props.libs.tokens.createEnrollmentToken(); - - this.props.setUrlState({enrollmentToken}); - return this.setState({ - enrolledBeat: null - }) - } - goTo(action.goTo) +
+
- }} - > - {action.name} - - ))} - -
+
+
+ + + + + +

Select your operating system:

+
+
+
+ ${ + this.state.beatType + }.exe`, + label: 'Windows', + }, + { + value: `./${this.state.beatType}`, + label: 'MacOS', + }, + { + value: `sudo ${this.state.beatType}`, + label: 'RPM', + }, + ]} + onChange={(e: any) => this.setState({ command: e.target.value })} + fullWidth={true} + /> +
+
+
+
+ {this.state.command && ( + + + + + +

Run the following command to enroll your beat

+
+
+
+
+
+ $ {this.state.command} enroll {window.location.protocol} + {`//`} + {window.location.host} + {this.props.libs.framework.baseURLPath + ? this.props.libs.framework.baseURLPath + : ''}{' '} + {this.props.urlState.enrollmentToken} +
+
+
+
+ + + + + +

Waiting for enroll command to be run...

+
+
+
+
+
+
+ +
+
)} + + )} + {this.state.enrolledBeat && ( + + A Beat was enrolled with the following data: +
+
+
+ +
+
+ {actions.map(action => ( + { + if (action.clearToken) { + this.props.setUrlState({ enrollmentToken: '' }); + } + + if (action.newToken) { + const enrollmentToken = await this.props.libs.tokens.createEnrollmentToken(); -
- )} -
- )} + this.props.setUrlState({ enrollmentToken }); + return this.setState({ + enrolledBeat: null, + }); + } + goTo(action.goTo); + }} + > + {action.name} + + ))} + + )} + + ); + } } export const EnrollBeatPage = withUrlState(EnrollBeat); From 8f472fd5fc2981693f828d3d0dca08930a6c27a7 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Tue, 16 Oct 2018 14:15:06 -0400 Subject: [PATCH 78/94] Reduce badge list gutter size, change logic for tag assignment to unassign all if some selected. (#24091) --- .../table_controls/tag_badge_list.tsx | 4 ++-- .../public/pages/main/beats.tsx | 19 +++++-------------- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/x-pack/plugins/beats_management/public/components/table_controls/tag_badge_list.tsx b/x-pack/plugins/beats_management/public/components/table_controls/tag_badge_list.tsx index 357091aa96d6c..0801dc211e1ea 100644 --- a/x-pack/plugins/beats_management/public/components/table_controls/tag_badge_list.tsx +++ b/x-pack/plugins/beats_management/public/components/table_controls/tag_badge_list.tsx @@ -16,9 +16,9 @@ interface TagBadgeListProps { export const TagBadgeList = (props: TagBadgeListProps) => ( // @ts-ignore direction prop type "column" not defined in current EUI version - + {props.items.map((item: any) => ( - + props.actionHandler(AssignmentActionType.Assign, id)} diff --git a/x-pack/plugins/beats_management/public/pages/main/beats.tsx b/x-pack/plugins/beats_management/public/pages/main/beats.tsx index 5e3541fb7919b..b621132e291d3 100644 --- a/x-pack/plugins/beats_management/public/pages/main/beats.tsx +++ b/x-pack/plugins/beats_management/public/pages/main/beats.tsx @@ -163,20 +163,11 @@ export class BeatsPage extends React.PureComponent { const selected = this.getSelectedBeats(); - const removals: CMPopulatedBeat[] = []; - const additions: CMPopulatedBeat[] = []; - selected.forEach(beat => { - if (beat.full_tags.some(tag => tag.id === tagId)) { - removals.push(beat); - } else { - additions.push(beat); - } - }); - - await Promise.all([ - this.removeTagsFromBeats(removals, tagId), - this.assignTagsToBeats(additions, tagId), - ]); + if (selected.some(beat => beat.full_tags.some(({ id }) => id === tagId))) { + await this.removeTagsFromBeats(selected, tagId); + } else { + await this.assignTagsToBeats(selected, tagId); + } }; private deleteSelected = async () => { From a4b90e597f946bcaac1e95f91a495fa903b51a7c Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Tue, 16 Oct 2018 14:25:05 -0400 Subject: [PATCH 79/94] dont use genaric search bar --- .../public/components/table/controls.tsx | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/beats_management/public/components/table/controls.tsx b/x-pack/plugins/beats_management/public/components/table/controls.tsx index 065cdbc0ed6b3..7adc0ea33a975 100644 --- a/x-pack/plugins/beats_management/public/components/table/controls.tsx +++ b/x-pack/plugins/beats_management/public/components/table/controls.tsx @@ -45,15 +45,12 @@ export function ControlBar(props: ControlBarProps) { /> ))} - {showSearch && ( - - {kueryBarProps ? ( + {showSearch && + kueryBarProps && ( + - ) : ( - - )} - - )} + + )} {showAssignmentOptions && schema.map(def => ( From 7c6efe3f59681a1862aa71f5fc2c0f1696731c05 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Tue, 16 Oct 2018 14:42:55 -0400 Subject: [PATCH 80/94] Add logic to gray out tag badges in list. --- .../components/tag/disabled_tag_badge.tsx | 25 +++++++++++++++ .../public/components/tag/tag_badge.tsx | 9 ++++-- .../beats_management/public/config_schemas.ts | 2 +- .../public/pages/main/beats.tsx | 31 +++++++++++++++++-- 4 files changed, 60 insertions(+), 7 deletions(-) create mode 100644 x-pack/plugins/beats_management/public/components/tag/disabled_tag_badge.tsx diff --git a/x-pack/plugins/beats_management/public/components/tag/disabled_tag_badge.tsx b/x-pack/plugins/beats_management/public/components/tag/disabled_tag_badge.tsx new file mode 100644 index 0000000000000..67f0811261523 --- /dev/null +++ b/x-pack/plugins/beats_management/public/components/tag/disabled_tag_badge.tsx @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiBadge, EuiToolTip } from '@elastic/eui'; +import React from 'react'; +import { TABLE_CONFIG } from '../../../common/constants'; + +interface TagBadgeProps { + maxIdRenderSize?: number; + id: string; +} + +export const DisabledTagBadge = (props: TagBadgeProps) => { + const { id, maxIdRenderSize } = props; + const idRenderSize = maxIdRenderSize || TABLE_CONFIG.TRUNCATE_TAG_LENGTH; + const idToRender = id.length > idRenderSize ? `${id.substring(0, idRenderSize)}...` : id; + return ( + + {idToRender} + + ); +}; diff --git a/x-pack/plugins/beats_management/public/components/tag/tag_badge.tsx b/x-pack/plugins/beats_management/public/components/tag/tag_badge.tsx index f0cca09b0181c..f81c16f59ee12 100644 --- a/x-pack/plugins/beats_management/public/components/tag/tag_badge.tsx +++ b/x-pack/plugins/beats_management/public/components/tag/tag_badge.tsx @@ -7,13 +7,14 @@ import { EuiBadge } from '@elastic/eui'; import React from 'react'; import { TABLE_CONFIG } from '../../../common/constants'; +import { DisabledTagBadge } from './disabled_tag_badge'; interface TagBadgeProps { iconType?: any; onClick?: () => void; onClickAriaLabel?: string; maxIdRenderSize?: number; - tag: { color?: string; id: string }; + tag: { color?: string; disabled?: boolean; id: string }; } export const TagBadge = (props: TagBadgeProps) => { @@ -21,12 +22,14 @@ export const TagBadge = (props: TagBadgeProps) => { iconType, onClick, onClickAriaLabel, - tag: { color, id }, + tag: { color, disabled, id }, } = props; const maxIdRenderSize = props.maxIdRenderSize || TABLE_CONFIG.TRUNCATE_TAG_LENGTH; const idToRender = id.length > maxIdRenderSize ? `${id.substring(0, maxIdRenderSize)}...` : id; - return ( + return disabled ? ( + + ) : ( { + if (!this.state.tags) { + return []; + } + return this.selectedBeatsContainOutput() + ? this.state.tags.map(this.mapTagToDisabled) + : this.state.tags; + }; + + private mapTagToDisabled = (tag: BeatTag) => + tag.configuration_blocks.some(config => config.type === ConfigurationBlockTypes.Output) + ? { ...tag, disabled: true } + : tag; + + private selectedBeatsContainOutput = () => + // union beat tags + flatten(this.getSelectedBeats().map(beat => beat.full_tags)) + // map tag list to bool + .map(tag => + tag.configuration_blocks.some(config => config.type === ConfigurationBlockTypes.Output) + ) + // reduce to result + .reduce((acc, cur) => acc || cur, false); } From cb6fc738766642a99d31d257e4433f7f26f380eb Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Tue, 16 Oct 2018 18:12:56 -0400 Subject: [PATCH 81/94] Update logic for mapping tags to unassigned state. --- .../public/components/tag/disabled_tag_badge.tsx | 6 +++--- x-pack/plugins/beats_management/public/pages/main/beats.tsx | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/beats_management/public/components/tag/disabled_tag_badge.tsx b/x-pack/plugins/beats_management/public/components/tag/disabled_tag_badge.tsx index 67f0811261523..22421cb23f159 100644 --- a/x-pack/plugins/beats_management/public/components/tag/disabled_tag_badge.tsx +++ b/x-pack/plugins/beats_management/public/components/tag/disabled_tag_badge.tsx @@ -18,8 +18,8 @@ export const DisabledTagBadge = (props: TagBadgeProps) => { const idRenderSize = maxIdRenderSize || TABLE_CONFIG.TRUNCATE_TAG_LENGTH; const idToRender = id.length > idRenderSize ? `${id.substring(0, idRenderSize)}...` : id; return ( - - {idToRender} - + + {idToRender} + ); }; diff --git a/x-pack/plugins/beats_management/public/pages/main/beats.tsx b/x-pack/plugins/beats_management/public/pages/main/beats.tsx index 84a529fdf25a9..bc07844f9ad81 100644 --- a/x-pack/plugins/beats_management/public/pages/main/beats.tsx +++ b/x-pack/plugins/beats_management/public/pages/main/beats.tsx @@ -301,7 +301,9 @@ export class BeatsPage extends React.PureComponent - tag.configuration_blocks.some(config => config.type === ConfigurationBlockTypes.Output) + tag.configuration_blocks.some(config => config.type === ConfigurationBlockTypes.Output) && + // if > 0 beats are associated with the tag, it will result in disassociation, so do not disable it + !this.getSelectedBeats().some(beat => beat.full_tags.some(({ id }) => id === tag.id)) ? { ...tag, disabled: true } : tag; From 63f5d79ea9d9456c0e23cf01d5adb1a63699eecb Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Tue, 16 Oct 2018 18:39:21 -0400 Subject: [PATCH 82/94] Destructure anonymous function parameters. --- x-pack/plugins/beats_management/public/pages/main/beats.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/beats_management/public/pages/main/beats.tsx b/x-pack/plugins/beats_management/public/pages/main/beats.tsx index bc07844f9ad81..a67d320a28e16 100644 --- a/x-pack/plugins/beats_management/public/pages/main/beats.tsx +++ b/x-pack/plugins/beats_management/public/pages/main/beats.tsx @@ -309,10 +309,10 @@ export class BeatsPage extends React.PureComponent // union beat tags - flatten(this.getSelectedBeats().map(beat => beat.full_tags)) + flatten(this.getSelectedBeats().map(({ full_tags }) => full_tags)) // map tag list to bool - .map(tag => - tag.configuration_blocks.some(config => config.type === ConfigurationBlockTypes.Output) + .map(({ configuration_blocks }) => + configuration_blocks.some(config => config.type === ConfigurationBlockTypes.Output) ) // reduce to result .reduce((acc, cur) => acc || cur, false); From f6279579a38130fcdbc4bd1965a066e0f9b6d961 Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Tue, 16 Oct 2018 20:27:40 -0400 Subject: [PATCH 83/94] removed unused import --- .../beats_management/public/components/table/controls.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/beats_management/public/components/table/controls.tsx b/x-pack/plugins/beats_management/public/components/table/controls.tsx index 7adc0ea33a975..302c96d7c05a4 100644 --- a/x-pack/plugins/beats_management/public/components/table/controls.tsx +++ b/x-pack/plugins/beats_management/public/components/table/controls.tsx @@ -9,7 +9,6 @@ import React from 'react'; import { AutocompleteField } from '../autocomplete_field/index'; import { OptionControl } from '../table_controls'; import { AssignmentOptions as AssignmentOptionsType, KueryBarProps } from './table'; -import { TableSearchControl } from './table_search_control'; interface ControlBarProps { assignmentOptions: AssignmentOptionsType; From d6d7c8761fe1f9a1d57629a8289c7cd6af2c9e77 Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Wed, 17 Oct 2018 12:13:19 -0400 Subject: [PATCH 84/94] fix es-lint error --- x-pack/plugins/infra/scripts/generate_types_from_graphql.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/infra/scripts/generate_types_from_graphql.js b/x-pack/plugins/infra/scripts/generate_types_from_graphql.js index 5270985905002..f36979c159376 100644 --- a/x-pack/plugins/infra/scripts/generate_types_from_graphql.js +++ b/x-pack/plugins/infra/scripts/generate_types_from_graphql.js @@ -5,13 +5,13 @@ */ const { join, resolve } = require('path'); -// eslint-disable-next-line import/no-extraneous-dependencies +// eslint-disable-next-line import/no-extraneous-dependencies, import/no-unresolved const { generate } = require('graphql-code-generator'); const GRAPHQL_GLOBS = [ join('public', 'containers', '**', '*.gql_query.ts{,x}'), join('public', 'store', '**', '*.gql_query.ts{,x}'), - join('common', 'graphql', '**', '*.gql_query.ts{,x}') + join('common', 'graphql', '**', '*.gql_query.ts{,x}'), ]; const CONFIG_PATH = resolve(__dirname, 'gql_gen.json'); const OUTPUT_INTROSPECTION_PATH = resolve('common', 'graphql', 'introspection.json'); From 7033d536688d41841c02c1984302fc6e79c0a766 Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Wed, 17 Oct 2018 12:36:18 -0400 Subject: [PATCH 85/94] fix outputs --- .../tag/config_view/config_form.tsx | 24 +++++++++++++++---- .../beats_management/public/config_schemas.ts | 9 +++---- .../beats_management/public/lib/lib.ts | 1 + 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/beats_management/public/components/tag/config_view/config_form.tsx b/x-pack/plugins/beats_management/public/components/tag/config_view/config_form.tsx index ed30a98e9ed97..70273740b2fde 100644 --- a/x-pack/plugins/beats_management/public/components/tag/config_view/config_form.tsx +++ b/x-pack/plugins/beats_management/public/components/tag/config_view/config_form.tsx @@ -39,7 +39,7 @@ addValidationRule('isHosts', (form: FormData, values: FieldValue | string[]) => }); addValidationRule('isString', (values: FormData, value: FieldValue) => { - return value && value.length > 0; + return true; }); addValidationRule('isPeriod', (values: FormData, value: FieldValue) => { @@ -68,7 +68,6 @@ addValidationRule('isYaml', (values: FormData, value: FieldValue) => { } catch (e) { return false; } - return true; }); interface ComponentProps { @@ -107,7 +106,22 @@ export class ConfigForm extends React.Component { } }; public onValidSubmit = (model: ModelType) => { - this.props.onSubmit(model); + const processed = JSON.parse(JSON.stringify(model), (key, value) => { + return _.isObject(value) && !_.isArray(value) + ? _.mapKeys(value, (v, k: string) => { + return k.replace(/{{[^{}]+}}/g, (token: string) => { + return model[token.replace(/[{}]+/g, '')] || 'error'; + }); + }) + : value; + }); + + this.props.schema.forEach(s => { + if (s.ui.transform && s.ui.transform === 'removed') { + delete processed[s.id]; + } + }); + this.props.onSubmit(processed); }; public render() { return ( @@ -131,7 +145,7 @@ export class ConfigForm extends React.Component { label={schema.ui.label} validations={schema.validations} validationError={schema.error} - required={schema.required} + required={schema.required || false} /> ); case 'password': @@ -145,7 +159,7 @@ export class ConfigForm extends React.Component { label={schema.ui.label} validations={schema.validations} validationError={schema.error} - required={schema.required} + required={schema.required || false} /> ); case 'multi-input': diff --git a/x-pack/plugins/beats_management/public/config_schemas.ts b/x-pack/plugins/beats_management/public/config_schemas.ts index 2fcb6a8c48d00..0c6d1ded95cb3 100644 --- a/x-pack/plugins/beats_management/public/config_schemas.ts +++ b/x-pack/plugins/beats_management/public/config_schemas.ts @@ -309,6 +309,7 @@ const outputConfig: YamlConfigSchema[] = [ ui: { label: 'Output Type', type: 'select', + transform: 'removed', }, options: [ { @@ -332,7 +333,7 @@ const outputConfig: YamlConfigSchema[] = [ required: true, }, { - id: 'hosts', + id: '{{output}}.hosts', ui: { label: 'Hosts', type: 'multi-input', @@ -342,7 +343,7 @@ const outputConfig: YamlConfigSchema[] = [ parseValidResult: v => v.split('\n'), }, { - id: 'username', + id: '{{output}}.username', ui: { label: 'Username', type: 'input', @@ -351,10 +352,10 @@ const outputConfig: YamlConfigSchema[] = [ error: 'Unprocessable username', }, { - id: 'password', + id: '{{output}}.password', ui: { label: 'Password', - type: 'input', + type: 'password', }, validations: 'isString', error: 'Unprocessable password', diff --git a/x-pack/plugins/beats_management/public/lib/lib.ts b/x-pack/plugins/beats_management/public/lib/lib.ts index 08b15957c8cba..51d3f1fd657d9 100644 --- a/x-pack/plugins/beats_management/public/lib/lib.ts +++ b/x-pack/plugins/beats_management/public/lib/lib.ts @@ -29,6 +29,7 @@ export interface YamlConfigSchema { label: string; type: 'input' | 'multi-input' | 'select' | 'code' | 'password'; helpText?: string; + transform?: 'removed'; }; options?: Array<{ value: string; text: string }>; validations?: 'isHosts' | 'isString' | 'isPeriod' | 'isPath' | 'isPaths' | 'isYaml'; From b488c9818eac2cfb38595fa81f9d665d34d8039b Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Wed, 17 Oct 2018 13:37:05 -0400 Subject: [PATCH 86/94] fix types from infra merge --- .../public/components/inputs/select.tsx | 13 ++++++-- .../public/lib/adapters/elasticsearch/rest.ts | 2 +- .../public/pages/main/enroll_fragment.tsx | 5 +-- .../plugins/beats_management/types/eui.d.ts | 31 +------------------ 4 files changed, 15 insertions(+), 36 deletions(-) diff --git a/x-pack/plugins/beats_management/public/components/inputs/select.tsx b/x-pack/plugins/beats_management/public/components/inputs/select.tsx index 858637463bfd9..8fa192090a99a 100644 --- a/x-pack/plugins/beats_management/public/components/inputs/select.tsx +++ b/x-pack/plugins/beats_management/public/components/inputs/select.tsx @@ -3,12 +3,19 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -// @ts-ignore -import { CommonProps, EuiFormRow, EuiSelect } from '@elastic/eui'; + +import { + CommonProps, + EuiFormRow, + // @ts-ignore + EuiSelect, +} from '@elastic/eui'; // @ts-ignore import { FormsyInputProps, withFormsy } from 'formsy-react'; import React, { Component, InputHTMLAttributes } from 'react'; +const FixedSelect = EuiSelect as React.SFC; + interface ComponentProps extends FormsyInputProps, CommonProps { instantValidation: boolean; options: Array<{ value: string; text: string }>; @@ -96,7 +103,7 @@ class FieldSelect extends Component< isInvalid={!disabled && error} error={!disabled && error ? getErrorMessage() : []} > - { this.state = { enrolledBeat: null, command: 'sudo filebeat', + beatType: 'filebeat', }; } public pingForBeatWithToken = async ( @@ -121,7 +122,7 @@ export class EnrollBeat extends React.Component { { ; - } - type EuiBreadcrumbsProps = CommonProps & { - responsive?: boolean; - truncate?: boolean; - max?: number; - breadcrumbs: EuiBreadcrumbDefinition[]; - }; - type EuiHeaderProps = CommonProps; - export const EuiHeader: React.SFC; - - export type EuiHeaderSectionSide = 'left' | 'right'; - type EuiHeaderSectionProps = CommonProps & { - side?: EuiHeaderSectionSide; - }; - export const EuiHeaderSection: React.SFC; - - type EuiHeaderBreadcrumbsProps = EuiBreadcrumbsProps; - export const EuiHeaderBreadcrumbs: React.SFC; - - interface EuiOutsideClickDetectorProps { - children: React.ReactNode; - isDisabled?: boolean; - onOutsideClick: React.MouseEventHandler; - } - export const EuiOutsideClickDetector: React.SFC; } From 30ac542a66465ad33152389959238f13e32b04c4 Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Wed, 17 Oct 2018 14:15:23 -0400 Subject: [PATCH 87/94] remove dupe dep --- package.json | 1 - x-pack/package.json | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index e259b0408a765..963e93ed068f3 100644 --- a/package.json +++ b/package.json @@ -249,7 +249,6 @@ "@types/react": "^16.3.14", "@types/react-dom": "^16.0.5", "@types/react-redux": "^6.0.6", - "@types/react-router-dom": "^4.3.1", "@types/react-virtualized": "^9.18.7", "@types/redux": "^3.6.31", "@types/redux-actions": "^2.2.1", diff --git a/x-pack/package.json b/x-pack/package.json index 9f881c541bf58..d7e4f08313809 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -50,7 +50,7 @@ "@types/react-datepicker": "^1.1.5", "@types/react-dom": "^16.0.5", "@types/react-redux": "^6.0.6", - "@types/react-router-dom": "^4.2.6", + "@types/react-router-dom": "^4.3.1", "@types/reduce-reducers": "^0.1.3", "@types/sinon": "^5.0.1", "@types/supertest": "^2.0.5", From 2e4b9d2844894c6dad18e2e540d5db4fa9d018ad Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Wed, 17 Oct 2018 14:24:00 -0400 Subject: [PATCH 88/94] Update tag disable logic to check for uniqueness requirements. --- .../public/pages/main/beats.tsx | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/beats_management/public/pages/main/beats.tsx b/x-pack/plugins/beats_management/public/pages/main/beats.tsx index a67d320a28e16..c032986814e66 100644 --- a/x-pack/plugins/beats_management/public/pages/main/beats.tsx +++ b/x-pack/plugins/beats_management/public/pages/main/beats.tsx @@ -14,12 +14,15 @@ import { EuiModalHeaderTitle, EuiOverlayMask, } from '@elastic/eui'; -import { flatten, sortBy } from 'lodash'; +import { flatten, intersection, sortBy } from 'lodash'; import moment from 'moment'; import React from 'react'; import { RouteComponentProps } from 'react-router'; -import { ConfigurationBlockTypes } from 'x-pack/plugins/beats_management/common/constants'; -import { BeatTag, CMPopulatedBeat } from '../../../common/domain_types'; +import { + ConfigurationBlockTypes, + UNIQUENESS_ENFORCING_TYPES, +} from 'x-pack/plugins/beats_management/common/constants'; +import { BeatTag, CMPopulatedBeat, ConfigurationBlock } from '../../../common/domain_types'; import { BeatsTagAssignment } from '../../../server/lib/adapters/beats/adapter_types'; import { AppURLState } from '../../app'; import { BeatsTableType, Table } from '../../components/table'; @@ -295,25 +298,27 @@ export class BeatsPage extends React.PureComponent - tag.configuration_blocks.some(config => config.type === ConfigurationBlockTypes.Output) && + private configBlocksRequireUniqueness = (configurationBlocks: ConfigurationBlock[]) => + intersection(UNIQUENESS_ENFORCING_TYPES, configurationBlocks.map(block => block.type)) + .length !== 0; + + private disableTagForUniquenessEnforcement = (tag: BeatTag) => + this.configBlocksRequireUniqueness(tag.configuration_blocks) && // if > 0 beats are associated with the tag, it will result in disassociation, so do not disable it !this.getSelectedBeats().some(beat => beat.full_tags.some(({ id }) => id === tag.id)) ? { ...tag, disabled: true } : tag; - private selectedBeatsContainOutput = () => + private selectedBeatConfigsRequireUniqueness = () => // union beat tags flatten(this.getSelectedBeats().map(({ full_tags }) => full_tags)) // map tag list to bool - .map(({ configuration_blocks }) => - configuration_blocks.some(config => config.type === ConfigurationBlockTypes.Output) - ) + .map(({ configuration_blocks }) => this.configBlocksRequireUniqueness(configuration_blocks)) // reduce to result .reduce((acc, cur) => acc || cur, false); } From 638ba5b8f7cf1c17f8fa352eb816f218d610a924 Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Wed, 17 Oct 2018 14:26:56 -0400 Subject: [PATCH 89/94] update lock file --- yarn.lock | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/yarn.lock b/yarn.lock index 79001be22d921..d9e0548a8be17 100644 --- a/yarn.lock +++ b/yarn.lock @@ -520,11 +520,6 @@ resolved "https://registry.yarnpkg.com/@types/has-ansi/-/has-ansi-3.0.0.tgz#636403dc4e0b2649421c4158e5c404416f3f0330" integrity sha512-H3vFOwfLlFEC0MOOrcSkus8PCnMCzz4N0EqUbdJZCdDhBTfkAu86aRYA+MTxjKW6jCpUvxcn4715US8g+28BMA== -"@types/history@*": - version "4.7.1" - resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.1.tgz#66849a23ceef917b57f0787c885f41ce03480f5c" - integrity sha512-g7RRtPg2f2OJm3kvYVTAGEr3R+YN53XwZgpP8r4cl3ugJB+95hbPfOU5tjOoAOz4bTLQuiHVUJh8rl4hEDUUjQ== - "@types/iron@*": version "5.0.1" resolved "https://registry.yarnpkg.com/@types/iron/-/iron-5.0.1.tgz#5420bbda8623c48ee51b9a78ebad05d7305b4b24" @@ -688,23 +683,6 @@ "@types/react" "*" redux "^4.0.0" -"@types/react-router-dom@^4.3.1": - version "4.3.1" - resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-4.3.1.tgz#71fe2918f8f60474a891520def40a63997dafe04" - integrity sha512-GbztJAScOmQ/7RsQfO4cd55RuH1W4g6V1gDW3j4riLlt+8yxYLqqsiMzmyuXBLzdFmDtX/uU2Bpcm0cmudv44A== - dependencies: - "@types/history" "*" - "@types/react" "*" - "@types/react-router" "*" - -"@types/react-router@*": - version "4.0.32" - resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-4.0.32.tgz#501529e3d7aa7d5c738d339367e1a7dd5338b2a7" - integrity sha512-VLQSifCIKCTpfMFrJN/nO5a45LduB6qSMkO9ASbcGdCHiDwJnrLNzk91Q895yG0qWY7RqT2jR16giBRpRG1HQw== - dependencies: - "@types/history" "*" - "@types/react" "*" - "@types/react-virtualized@^9.18.7": version "9.18.7" resolved "https://registry.yarnpkg.com/@types/react-virtualized/-/react-virtualized-9.18.7.tgz#8703d8904236819facff90b8b320f29233160c90" From 8be69a579745d9b73e3878bc5393ceba93d508a8 Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Wed, 17 Oct 2018 14:43:02 -0400 Subject: [PATCH 90/94] push another lock file --- x-pack/yarn.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/yarn.lock b/x-pack/yarn.lock index a58dfa9fe8908..95bc20adacb02 100644 --- a/x-pack/yarn.lock +++ b/x-pack/yarn.lock @@ -387,10 +387,10 @@ "@types/react" "*" redux "^4.0.0" -"@types/react-router-dom@^4.2.6": - version "4.2.7" - resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-4.2.7.tgz#9d36bfe175f916dd8d7b6b0237feed6cce376b4c" - integrity sha512-6sIP3dIj6xquvcAuYDaxpbeLjr9954OuhCXnniMhnDgykAw2tVji9b0jKHofPJGUoHEMBsWzO83tjnk7vfzozA== +"@types/react-router-dom@^4.3.1": + version "4.3.1" + resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-4.3.1.tgz#71fe2918f8f60474a891520def40a63997dafe04" + integrity sha512-GbztJAScOmQ/7RsQfO4cd55RuH1W4g6V1gDW3j4riLlt+8yxYLqqsiMzmyuXBLzdFmDtX/uU2Bpcm0cmudv44A== dependencies: "@types/history" "*" "@types/react" "*" From d0f0557fa40f65f9e5836fea815fe599ad4ce0bb Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Wed, 17 Oct 2018 16:47:16 -0400 Subject: [PATCH 91/94] Add unfilteredBeats field to beat list state to prevent inappropriate redirect. (#24146) --- .../beats_management/public/pages/main/index.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/beats_management/public/pages/main/index.tsx b/x-pack/plugins/beats_management/public/pages/main/index.tsx index 3b935a191062b..ae016f8f4b73a 100644 --- a/x-pack/plugins/beats_management/public/pages/main/index.tsx +++ b/x-pack/plugins/beats_management/public/pages/main/index.tsx @@ -39,6 +39,7 @@ interface MainPagesState { enrollmentToken: string; } | null; beats: CMPopulatedBeat[]; + unfilteredBeats: CMPopulatedBeat[]; loadedBeatsAtLeastOnce: boolean; } @@ -50,6 +51,7 @@ class MainPagesComponent extends React.PureComponent { @@ -68,7 +70,7 @@ class MainPagesComponent extends React.PureComponent; @@ -231,15 +233,21 @@ class MainPagesComponent extends React.PureComponent Date: Wed, 17 Oct 2018 17:21:49 -0400 Subject: [PATCH 92/94] fix yarn lock because yarn is dumb --- x-pack/package.json | 2 +- x-pack/yarn.lock | 50 +++++++++++++++++++++++---------------------- yarn.lock | 47 +++++++++++++++++++++++++++++------------- 3 files changed, 60 insertions(+), 39 deletions(-) diff --git a/x-pack/package.json b/x-pack/package.json index d7e4f08313809..8336e56bd9866 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -50,7 +50,7 @@ "@types/react-datepicker": "^1.1.5", "@types/react-dom": "^16.0.5", "@types/react-redux": "^6.0.6", - "@types/react-router-dom": "^4.3.1", + "@types/react-router-dom": "4.2.6", "@types/reduce-reducers": "^0.1.3", "@types/sinon": "^5.0.1", "@types/supertest": "^2.0.5", diff --git a/x-pack/yarn.lock b/x-pack/yarn.lock index 95bc20adacb02..5dd1172501e98 100644 --- a/x-pack/yarn.lock +++ b/x-pack/yarn.lock @@ -387,10 +387,10 @@ "@types/react" "*" redux "^4.0.0" -"@types/react-router-dom@^4.3.1": - version "4.3.1" - resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-4.3.1.tgz#71fe2918f8f60474a891520def40a63997dafe04" - integrity sha512-GbztJAScOmQ/7RsQfO4cd55RuH1W4g6V1gDW3j4riLlt+8yxYLqqsiMzmyuXBLzdFmDtX/uU2Bpcm0cmudv44A== +"@types/react-router-dom@4.2.6": + version "4.2.6" + resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-4.2.6.tgz#9f7eb3c0e6661a9607d878ff8675cc4ea95cd276" + integrity sha512-K7SdbkF8xgecp2WCeXw51IMySYvQ1EuVPKfjU1fymyTSX9bZk5Qx8T5cipwtAY8Zhb/4GIjhYKm0ZGVEbCKEzQ== dependencies: "@types/history" "*" "@types/react" "*" @@ -5099,11 +5099,6 @@ hoek@5.x.x: resolved "https://registry.yarnpkg.com/hoek/-/hoek-5.0.3.tgz#b71d40d943d0a95da01956b547f83c4a5b4a34ac" integrity sha512-Bmr56pxML1c9kU+NS51SMFkiVQAb+9uFfXwyqR2tn4w2FPvmPt65eZ9aCcEfRXd9G74HkZnILC6p967pED4aiw== -hoist-non-react-statics@^2.3.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.3.1.tgz#343db84c6018c650778898240135a1420ee22ce0" - integrity sha1-ND24TGAYxlB3iJgkATWhQg7iLOA= - hoist-non-react-statics@^2.3.1, hoist-non-react-statics@^2.5.0, hoist-non-react-statics@^2.5.5: version "2.5.5" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47" @@ -9107,29 +9102,29 @@ react-router-breadcrumbs-hoc@1.1.2: integrity sha1-T6+2IOfGuHbZj3FR9Mha5cMVfcA= react-router-dom@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-4.2.2.tgz#c8a81df3adc58bba8a76782e946cbd4eae649b8d" - integrity sha512-cHMFC1ZoLDfEaMFoKTjN7fry/oczMgRt5BKfMAkTu5zEuJvUiPp1J8d0eXSVTnBh6pxlbdqDhozunOOLtmKfPA== + version "4.3.1" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-4.3.1.tgz#4c2619fc24c4fa87c9fd18f4fb4a43fe63fbd5c6" + integrity sha512-c/MlywfxDdCp7EnB7YfPMOfMD3tOtIjrQlj/CKfNMBxdmpJP8xcz5P/UAFn3JbnQCNUxsHyVVqllF9LhgVyFCA== dependencies: history "^4.7.2" - invariant "^2.2.2" + invariant "^2.2.4" loose-envify "^1.3.1" - prop-types "^15.5.4" - react-router "^4.2.0" - warning "^3.0.0" + prop-types "^15.6.1" + react-router "^4.3.1" + warning "^4.0.1" -react-router@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-4.2.0.tgz#61f7b3e3770daeb24062dae3eedef1b054155986" - integrity sha512-DY6pjwRhdARE4TDw7XjxjZsbx9lKmIcyZoZ+SDO7SBJ1KUeWNxT22Kara2AC7u6/c2SYEHlEDLnzBCcNhLE8Vg== +react-router@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-4.3.1.tgz#aada4aef14c809cb2e686b05cee4742234506c4e" + integrity sha512-yrvL8AogDh2X42Dt9iknk4wF4V8bWREPirFfS9gLU1huk6qK41sg7Z/1S81jjTrGHxa3B8R3J6xIkDAA6CVarg== dependencies: history "^4.7.2" - hoist-non-react-statics "^2.3.0" - invariant "^2.2.2" + hoist-non-react-statics "^2.5.0" + invariant "^2.2.4" loose-envify "^1.3.1" path-to-regexp "^1.7.0" - prop-types "^15.5.4" - warning "^3.0.0" + prop-types "^15.6.1" + warning "^4.0.1" react-select@^1.2.1: version "1.2.1" @@ -11590,6 +11585,13 @@ warning@^3.0.0: dependencies: loose-envify "^1.0.0" +warning@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.2.tgz#aa6876480872116fa3e11d434b0d0d8d91e44607" + integrity sha512-wbTp09q/9C+jJn4KKJfJfoS6VleK/Dti0yqWSm6KMvJ4MRCXFQNapHuJXutJIrWV0Cf4AhTdeIe4qdKHR1+Hug== + dependencies: + loose-envify "^1.0.0" + watch@~0.18.0: version "0.18.0" resolved "https://registry.yarnpkg.com/watch/-/watch-0.18.0.tgz#28095476c6df7c90c963138990c0a5423eb4b986" diff --git a/yarn.lock b/yarn.lock index d9e0548a8be17..a4d6a663ce1a8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7677,16 +7677,16 @@ hoek@5.x.x: resolved "https://registry.yarnpkg.com/hoek/-/hoek-5.0.3.tgz#b71d40d943d0a95da01956b547f83c4a5b4a34ac" integrity sha512-Bmr56pxML1c9kU+NS51SMFkiVQAb+9uFfXwyqR2tn4w2FPvmPt65eZ9aCcEfRXd9G74HkZnILC6p967pED4aiw== -hoist-non-react-statics@^2.3.0, hoist-non-react-statics@^2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.0.tgz#d2ca2dfc19c5a91c5a6615ce8e564ef0347e2a40" - integrity sha512-6Bl6XsDT1ntE0lHbIhr4Kp2PGcleGZ66qu5Jqk8lc0Xc/IeG6gVLmwUGs/K0Us+L8VWoKgj0uWdPMataOsm31w== - hoist-non-react-statics@^2.3.1, hoist-non-react-statics@^2.5.5: version "2.5.5" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47" integrity sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw== +hoist-non-react-statics@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.0.tgz#d2ca2dfc19c5a91c5a6615ce8e564ef0347e2a40" + integrity sha512-6Bl6XsDT1ntE0lHbIhr4Kp2PGcleGZ66qu5Jqk8lc0Xc/IeG6gVLmwUGs/K0Us+L8VWoKgj0uWdPMataOsm31w== + home-or-tmp@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8" @@ -13442,7 +13442,7 @@ react-router-breadcrumbs-hoc@1.1.2: resolved "https://registry.yarnpkg.com/react-router-breadcrumbs-hoc/-/react-router-breadcrumbs-hoc-1.1.2.tgz#4fafb620e7c6b876d98f7151f4c85ae5c3157dc0" integrity sha1-T6+2IOfGuHbZj3FR9Mha5cMVfcA= -react-router-dom@4.2.2, react-router-dom@^4.2.2: +react-router-dom@4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-4.2.2.tgz#c8a81df3adc58bba8a76782e946cbd4eae649b8d" integrity sha512-cHMFC1ZoLDfEaMFoKTjN7fry/oczMgRt5BKfMAkTu5zEuJvUiPp1J8d0eXSVTnBh6pxlbdqDhozunOOLtmKfPA== @@ -13454,18 +13454,30 @@ react-router-dom@4.2.2, react-router-dom@^4.2.2: react-router "^4.2.0" warning "^3.0.0" -react-router@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-4.2.0.tgz#61f7b3e3770daeb24062dae3eedef1b054155986" - integrity sha512-DY6pjwRhdARE4TDw7XjxjZsbx9lKmIcyZoZ+SDO7SBJ1KUeWNxT22Kara2AC7u6/c2SYEHlEDLnzBCcNhLE8Vg== +react-router-dom@^4.2.2: + version "4.3.1" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-4.3.1.tgz#4c2619fc24c4fa87c9fd18f4fb4a43fe63fbd5c6" + integrity sha512-c/MlywfxDdCp7EnB7YfPMOfMD3tOtIjrQlj/CKfNMBxdmpJP8xcz5P/UAFn3JbnQCNUxsHyVVqllF9LhgVyFCA== dependencies: history "^4.7.2" - hoist-non-react-statics "^2.3.0" - invariant "^2.2.2" + invariant "^2.2.4" + loose-envify "^1.3.1" + prop-types "^15.6.1" + react-router "^4.3.1" + warning "^4.0.1" + +react-router@^4.2.0, react-router@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-4.3.1.tgz#aada4aef14c809cb2e686b05cee4742234506c4e" + integrity sha512-yrvL8AogDh2X42Dt9iknk4wF4V8bWREPirFfS9gLU1huk6qK41sg7Z/1S81jjTrGHxa3B8R3J6xIkDAA6CVarg== + dependencies: + history "^4.7.2" + hoist-non-react-statics "^2.5.0" + invariant "^2.2.4" loose-envify "^1.3.1" path-to-regexp "^1.7.0" - prop-types "^15.5.4" - warning "^3.0.0" + prop-types "^15.6.1" + warning "^4.0.1" react-select@^1.2.1: version "1.2.1" @@ -17138,6 +17150,13 @@ warning@^3.0.0: dependencies: loose-envify "^1.0.0" +warning@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.2.tgz#aa6876480872116fa3e11d434b0d0d8d91e44607" + integrity sha512-wbTp09q/9C+jJn4KKJfJfoS6VleK/Dti0yqWSm6KMvJ4MRCXFQNapHuJXutJIrWV0Cf4AhTdeIe4qdKHR1+Hug== + dependencies: + loose-envify "^1.0.0" + watch@~0.18.0: version "0.18.0" resolved "https://registry.yarnpkg.com/watch/-/watch-0.18.0.tgz#28095476c6df7c90c963138990c0a5423eb4b986" From 1e90c7325c17a69209d195f258301d6902df9bf9 Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Wed, 17 Oct 2018 20:06:00 -0400 Subject: [PATCH 93/94] Fix broken tests --- .../server/lib/domains/beats.ts | 5 + .../server/rest_api/tags/set.ts | 4 +- x-pack/plugins/monitoring/public/index.css | 98 +--------- .../apis/beats/assign_tags_to_beats.js | 2 +- .../api_integration/apis/beats/enroll_beat.js | 2 +- .../apis/beats/remove_tags_from_beats.js | 2 +- .../api_integration/apis/beats/set_tag.js | 174 ++++++++++++------ 7 files changed, 138 insertions(+), 149 deletions(-) diff --git a/x-pack/plugins/beats_management/server/lib/domains/beats.ts b/x-pack/plugins/beats_management/server/lib/domains/beats.ts index 2b0fe09afff1d..a1b890119fc3e 100644 --- a/x-pack/plugins/beats_management/server/lib/domains/beats.ts +++ b/x-pack/plugins/beats_management/server/lib/domains/beats.ts @@ -99,6 +99,11 @@ export class CMBeatsDomain { return { status: BeatEnrollmentStatus.InvalidEnrollmentToken }; } + const existingBeat = await this.getById(this.framework.internalUser, beatId); + if (existingBeat) { + return { status: BeatEnrollmentStatus.Success }; + } + const accessToken = this.tokens.generateAccessToken(); const verifiedOn = moment().toJSON(); diff --git a/x-pack/plugins/beats_management/server/rest_api/tags/set.ts b/x-pack/plugins/beats_management/server/rest_api/tags/set.ts index 580af102319b3..79503e995ac60 100644 --- a/x-pack/plugins/beats_management/server/rest_api/tags/set.ts +++ b/x-pack/plugins/beats_management/server/rest_api/tags/set.ts @@ -38,7 +38,9 @@ export const createSetTagRoute = (libs: CMServerLibs) => ({ }, }, handler: async (request: FrameworkRequest, reply: any) => { - const config = get(request, 'payload', { configuration_blocks: [], color: '#DD0A73' }); + const defaultConfig = { configuration_blocks: [], color: '#DD0A73' }; + const config = get(request, 'payload', defaultConfig) || defaultConfig; + try { const { isValid, result } = await libs.tags.saveTag(request.user, request.params.tag, config); if (!isValid) { diff --git a/x-pack/plugins/monitoring/public/index.css b/x-pack/plugins/monitoring/public/index.css index 026bf66180cce..afaa9dfd315d9 100644 --- a/x-pack/plugins/monitoring/public/index.css +++ b/x-pack/plugins/monitoring/public/index.css @@ -1,101 +1,17 @@ -@-webkit-keyframes euiAnimFadeIn { - 0% { - opacity: 0; } - 100% { - opacity: 1; } } - -@keyframes euiAnimFadeIn { - 0% { - opacity: 0; } - 100% { - opacity: 1; } } - -@-webkit-keyframes euiGrow { - 0% { - opacity: 0; } - 1% { - opacity: 0; - -webkit-transform: scale(0); - transform: scale(0); } - 100% { - opacity: 1; - -webkit-transform: scale(1); - transform: scale(1); } } - -@keyframes euiGrow { - 0% { - opacity: 0; } - 1% { - opacity: 0; - -webkit-transform: scale(0); - transform: scale(0); } - 100% { - opacity: 1; - -webkit-transform: scale(1); - transform: scale(1); } } - -/** - * Text truncation - * - * Prevent text from wrapping onto multiple lines, and truncate with an - * ellipsis. - * - * 1. Ensure that the node has a maximum width after which truncation can - * occur. - * 2. Fix for IE 8/9 if `word-wrap: break-word` is in effect on ancestor - * nodes. - */ -/** - * Set scroll bar appearance on Chrome. - */ -/** - * Specifically target IE11, but not Edge. - */ -@-webkit-keyframes focusRingAnimate { - 0% { - -webkit-box-shadow: 0 0 0 6px rgba(0, 121, 165, 0); - box-shadow: 0 0 0 6px rgba(0, 121, 165, 0); } - 100% { - -webkit-box-shadow: 0 0 0 2px rgba(0, 121, 165, 0.3); - box-shadow: 0 0 0 2px rgba(0, 121, 165, 0.3); } } -@keyframes focusRingAnimate { - 0% { - -webkit-box-shadow: 0 0 0 6px rgba(0, 121, 165, 0); - box-shadow: 0 0 0 6px rgba(0, 121, 165, 0); } - 100% { - -webkit-box-shadow: 0 0 0 2px rgba(0, 121, 165, 0.3); - box-shadow: 0 0 0 2px rgba(0, 121, 165, 0.3); } } - -@-webkit-keyframes focusRingAnimateLarge { - 0% { - -webkit-box-shadow: 0 0 0 10px rgba(0, 121, 165, 0); - box-shadow: 0 0 0 10px rgba(0, 121, 165, 0); } - 100% { - -webkit-box-shadow: 0 0 0 4px rgba(0, 121, 165, 0.3); - box-shadow: 0 0 0 4px rgba(0, 121, 165, 0.3); } } - -@keyframes focusRingAnimateLarge { - 0% { - -webkit-box-shadow: 0 0 0 10px rgba(0, 121, 165, 0); - box-shadow: 0 0 0 10px rgba(0, 121, 165, 0); } - 100% { - -webkit-box-shadow: 0 0 0 4px rgba(0, 121, 165, 0.3); - box-shadow: 0 0 0 4px rgba(0, 121, 165, 0.3); } } - -.tab-no-data, .tab-overview, .tab-license { +#monitoring-app .tab-no-data, #monitoring-app .tab-overview, #monitoring-app .tab-license { background: #F5F5F5; } -.pui-tooltip-inner { +#monitoring-app .pui-tooltip-inner { font-size: 12.0px; } -.monitoring-tooltip__trigger, -.monitoring-tooltip__trigger:hover { +#monitoring-app .monitoring-tooltip__trigger, +#monitoring-app .monitoring-tooltip__trigger:hover { color: #2D2D2D; } -.betaIcon { +#monitoring-app .betaIcon { color: #666; } -.xpack-breadcrumbs { +#monitoring-app .xpack-breadcrumbs { min-height: 37px; padding: 8px 10px; margin: 0; } @@ -538,4 +454,4 @@ monitoring-shard-allocation { display: inline-block; margin: 0 5px 0 10px; padding: 0 4px; } -/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL25vZGVfbW9kdWxlcy9AZWxhc3RpYy9ldWkvc3JjL2dsb2JhbF9zdHlsaW5nL3ZhcmlhYmxlcy9fYW5pbWF0aW9ucy5zY3NzIiwiLi4vLi4vLi4vLi4vbm9kZV9tb2R1bGVzL0BlbGFzdGljL2V1aS9zcmMvZ2xvYmFsX3N0eWxpbmcvbWl4aW5zL190eXBvZ3JhcGh5LnNjc3MiLCIuLi8uLi8uLi8uLi9ub2RlX21vZHVsZXMvQGVsYXN0aWMvZXVpL3NyYy9nbG9iYWxfc3R5bGluZy9taXhpbnMvX2hlbHBlcnMuc2NzcyIsIi4uLy4uLy4uLy4uL25vZGVfbW9kdWxlcy9AZWxhc3RpYy9ldWkvc3JjL2dsb2JhbF9zdHlsaW5nL21peGlucy9fc3RhdGVzLnNjc3MiLCIuLi8uLi8uLi8uLi9ub2RlX21vZHVsZXMvQGVsYXN0aWMvZXVpL3NyYy9nbG9iYWxfc3R5bGluZy92YXJpYWJsZXMvX2NvbG9ycy5zY3NzIiwiX2hhY2tzLnNjc3MiLCIuLi8uLi8uLi8uLi9ub2RlX21vZHVsZXMvQGVsYXN0aWMvZXVpL3NyYy90aGVtZXMvazYvazZfZ2xvYmFscy5zY3NzIiwiLi4vLi4vLi4vLi4vbm9kZV9tb2R1bGVzL0BlbGFzdGljL2V1aS9zcmMvdGhlbWVzL2s2L2s2X2NvbG9yc19saWdodC5zY3NzIiwiY29tcG9uZW50cy9jaGFydC9fY2hhcnQuc2NzcyIsIi4uLy4uLy4uLy4uL25vZGVfbW9kdWxlcy9AZWxhc3RpYy9ldWkvc3JjL2dsb2JhbF9zdHlsaW5nL3ZhcmlhYmxlcy9fc2l6ZS5zY3NzIiwiY29tcG9uZW50cy9ub19kYXRhL19ub19kYXRhLnNjc3MiLCJjb21wb25lbnRzL3NwYXJrbGluZS9fc3BhcmtsaW5lLnNjc3MiLCIuLi8uLi8uLi8uLi9ub2RlX21vZHVsZXMvQGVsYXN0aWMvZXVpL3NyYy9nbG9iYWxfc3R5bGluZy92YXJpYWJsZXMvX2JvcmRlcnMuc2NzcyIsIi4uLy4uLy4uLy4uL25vZGVfbW9kdWxlcy9AZWxhc3RpYy9ldWkvc3JjL2dsb2JhbF9zdHlsaW5nL3ZhcmlhYmxlcy9fel9pbmRleC5zY3NzIiwiY29tcG9uZW50cy9zdW1tYXJ5X3N0YXR1cy9fc3VtbWFyeV9zdGF0dXMuc2NzcyIsImNvbXBvbmVudHMvdGFibGUvX3RhYmxlLnNjc3MiLCIuLi8uLi8uLi8uLi9ub2RlX21vZHVsZXMvQGVsYXN0aWMvZXVpL3NyYy9nbG9iYWxfc3R5bGluZy92YXJpYWJsZXMvX3R5cG9ncmFwaHkuc2NzcyIsImNvbXBvbmVudHMvbG9nc3Rhc2gvcGlwZWxpbmVfdmlld2VyL3ZpZXdzL19waXBlbGluZV92aWV3ZXIuc2NzcyIsIi4uLy4uLy4uLy4uL25vZGVfbW9kdWxlcy9AZWxhc3RpYy9ldWkvc3JjL2dsb2JhbF9zdHlsaW5nL2Z1bmN0aW9ucy9fY29sb3JzLnNjc3MiLCIuLi8uLi8uLi8uLi9ub2RlX21vZHVsZXMvQGVsYXN0aWMvZXVpL3NyYy9nbG9iYWxfc3R5bGluZy9taXhpbnMvX3Jlc3BvbnNpdmUuc2NzcyIsImRpcmVjdGl2ZXMvY2hhcnQvX2NoYXJ0LnNjc3MiLCJkaXJlY3RpdmVzL2VsYXN0aWNzZWFyY2gvc2hhcmRfYWxsb2NhdGlvbi9fc2hhcmRfYWxsb2NhdGlvbi5zY3NzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQVdBO0VBQ0U7SUFDRSxXQUFVLEVBQUE7RUFFWjtJQUNFLFdBQVUsRUFBQSxFQUFBOztBQUxkO0VBQ0U7SUFDRSxXQUFVLEVBQUE7RUFFWjtJQUNFLFdBQVUsRUFBQSxFQUFBOztBQUlkO0VBQ0U7SUFDRSxXQUFVLEVBQUE7RUFFWjtJQUNFLFdBQVU7SUFDViw0QkFBbUI7WUFBbkIsb0JBQW1CLEVBQUE7RUFFckI7SUFDRSxXQUFVO0lBQ1YsNEJBQW1CO1lBQW5CLG9CQUFtQixFQUFBLEVBQUE7O0FBVnZCO0VBQ0U7SUFDRSxXQUFVLEVBQUE7RUFFWjtJQUNFLFdBQVU7SUFDViw0QkFBbUI7WUFBbkIsb0JBQW1CLEVBQUE7RUFFckI7SUFDRSxXQUFVO0lBQ1YsNEJBQW1CO1lBQW5CLG9CQUFtQixFQUFBLEVBQUE7O0FDeUR2Qjs7Ozs7Ozs7OztHQVVHO0FDbEVIOztHQUVHO0FBb0JIOztHQUVHO0FDdkNIO0VBQ0U7SUFDRSxtRENoQnFCO1lEZ0JyQiwyQ0NoQnFCLEVBQUE7RURrQnZCO0lBQ0UscURDbkJxQjtZRG1CckIsNkNDbkJxQixFQUFBLEVBQUE7QURjekI7RUFDRTtJQUNFLG1EQ2hCcUI7WURnQnJCLDJDQ2hCcUIsRUFBQTtFRGtCdkI7SUFDRSxxRENuQnFCO1lEbUJyQiw2Q0NuQnFCLEVBQUEsRUFBQTs7QUR1QnpCO0VBQ0U7SUFDRSxvREN6QnFCO1lEeUJyQiw0Q0N6QnFCLEVBQUE7RUQyQnZCO0lBQ0UscURDNUJxQjtZRDRCckIsNkNDNUJxQixFQUFBLEVBQUE7O0FEdUJ6QjtFQUNFO0lBQ0Usb0RDekJxQjtZRHlCckIsNENDekJxQixFQUFBO0VEMkJ2QjtJQUNFLHFEQzVCcUI7WUQ0QnJCLDZDQzVCcUIsRUFBQSxFQUFBOztBQ0R6QjtFQUNFLG9CRGE2QixFQ1o5Qjs7QUFHRDtFQUNFLGtCQ0FzRCxFREN2RDs7QUFFRDs7RUFFRSxlRVpvQixFRmFyQjs7QUFHRDtFQUNFLFlEQ3NCLEVDQXZCOztBQUdEO0VBQ0UsaUJBQWdCO0VBQ2hCLGtCQUFpQjtFQUNqQixVQUFTLEVBQ1Y7O0FHbkJEO0VBQ0UsbUJBQWtCO0VBQ2xCLHFCQUFhO0VBQWIsc0JBQWE7RUFBYixxQkFBYTtFQUFiLGNBQWE7RUFDYiw2QkFBc0I7RUFBdEIsOEJBQXNCO0VBQXRCLCtCQUFzQjtNQUF0QiwyQkFBc0I7VUFBdEIsdUJBQXNCO0VBQ3RCLG9CQUFjO0VBQWQsdUJBQWM7TUFBZCxtQkFBYztVQUFkLGVBQWMsRUFDZjs7QUFFRDtFQUNFLGVEZG9CO0VDZXBCLGdCQ1p5QixFRGExQjs7QUFFRDtFQUNFLG1CQUFrQjtFQUNsQixPQUFNO0VBQ04sU0FBUTtFQUNSLFVBQVM7RUFDVCxRQUFPO0VBQ1AscUJBQWE7RUFBYixzQkFBYTtFQUFiLHFCQUFhO0VBQWIsY0FBYTtFQUNiLG9CQUFjO0VBQWQsdUJBQWM7TUFBZCxtQkFBYztVQUFkLGVBQWMsRUFDZjs7QUFFRDtFQUNFLHFCQUFhO0VBQWIsc0JBQWE7RUFBYixxQkFBYTtFQUFiLGNBQWE7RUFDYiw2QkFBc0I7RUFBdEIsOEJBQXNCO0VBQXRCLCtCQUFzQjtNQUF0QiwyQkFBc0I7VUFBdEIsdUJBQXNCO0VBQ3RCLG9CQUFjO0VBQWQsdUJBQWM7TUFBZCxtQkFBYztVQUFkLGVBQWM7RUFDZCxtQkFBa0IsRUFhbkI7RUFqQkQ7SUFRSSxlQUFjO0lBQ2QsWUFBVztJQUNYLGFBQVksRUFDYjtFQVhIO0lBM0JFLDBCQUFpQjtPQUFqQix1QkFBaUI7UUFBakIsc0JBQWlCO1lBQWpCLGtCQUFpQjtJQUNqQiw0QkFBMkI7SUFDM0IseUNBQXdDLEVBeUN2Qzs7QUFHSDtFQUNFLGtCRnpDc0Q7RUUwQ3RELGdCQUFlO0VBQ2YsZURsRG9CLEVDdURyQjtFQUhDO0lBQ0UsYUFBWSxFQUNiOztBQUdIO0VBQ0UsZ0JDeEQwQixFRHlEM0I7O0FBRUQ7RUFDRSxpQkFBZ0I7RUFDaEIsb0JBQW1CLEVBQ3BCOztBQUNEO0VBQ0UsaUJBQWdCO0VBQ2hCLG9CQUFtQjtFQUNuQixpQkNsRTBCLEVEbUUzQjs7QUVyRUQ7RUFDRSxpQkFBZ0I7RUFDaEIsbUJBQWtCO0VBQ2xCLG1CQUFrQixFQUNuQjs7QUNKRDtFQUNFLFlBQVcsRUFDWjs7QUFHRDtFQUNFLG9CQUFtQjtFQUNuQixrQ0FBcUQ7RUFDckQsa0JMRHNEO0VLRXRELGFGUDBCO0VFUTFCLG1CQ0ptQjtFREtuQixxQkFBb0IsRUFDckI7O0FBRUQ7RUFDRSxnQ0FBeUMsRUFDMUM7O0FBRUQ7RUFDRSxZUGJrQixFT2NuQjs7QUFFRDtFQUNFLGtCTGJzRDtFS2N0RCw2QkFBZ0Q7RUFDaEQsY0FBYSxFQUNkOztBQUVEO0VBQ0UsZ0JBQWU7RUFDZixjRTFCd0I7RUYyQnhCLHFCQUFhO0VBQWIsc0JBQWE7RUFBYixxQkFBYTtFQUFiLGNBQWE7RUFDYiwrQkFBbUI7RUFBbkIsOEJBQW1CO0VBQW5CLDRCQUFtQjtNQUFuQix3QkFBbUI7VUFBbkIsb0JBQW1CO0VBQ25CLHlCQUF1QjtFQUF2QixnQ0FBdUI7TUFBdkIsc0JBQXVCO1VBQXZCLHdCQUF1QjtFQUN2QiwwQkFBbUI7RUFBbkIsNEJBQW1CO01BQW5CLHVCQUFtQjtVQUFuQixvQkFBbUIsRUFDcEI7O0FHbkNEO0VBQ0UsMEJWYzZCO0VVYjdCLGlDVmMwQjtFVWIxQixjTEhnQixFS0lqQjs7QUNKRDs7OztHQUlHO0FBQ0g7RUFDRSxZQUFXLEVBQ1o7O0FBRUQ7O0VBRUUsZVJYb0IsRVFZckI7O0FBRUQ7RUFDRSxnQkFBZSxFQUNoQjs7QUFDRDtFQUNFLFlYQXNCO0VZSnRCLGdCVk5zRDtFVU90RCxnQkFQcUM7RWZpRHJDLGlCZVhxQixFRDFCdEI7O0FBRUQ7OztFQ1JFLGtCVkxzRDtFVU10RCxzQkFQcUM7RWYyRHJDLGlCZXJCcUIsRURwQnRCOztBQUVEO0VBQ0UsWVhYc0I7RVlKdEIsZ0JWTnNEO0VVT3RELGdCQVBxQztFZmlEckMsaUJlWHFCLEVEZnRCOztBQUVEO0VDbkJFLGtCVkhzRDtFVUl0RCxzQkFQcUM7RWZxRXJDLGtCQUFpQjtFQUNqQixpQksvRHNCLEVTb0J2Qjs7QUFFRDtFQ3ZCRSxrQlZMc0Q7RVVNdEQsc0JBUHFDO0VmMkRyQyxpQmVyQnFCLEVEUHRCOztBQUVEO0VBQ0Usc0JBQXFCO0VDNUJyQixrQlZIc0Q7RVVJdEQsc0JBUHFDO0VmcUVyQyxrQkFBaUI7RUFDakIsaUJLL0RzQixFUzZCdkI7O0FBRUQ7RUFDRSxzQkFBcUI7RUFDckIsaUJOOUMwQjtFT1kxQixrQlZIc0Q7RVVJdEQsc0JBUHFDO0VmcUVyQyxrQkFBaUI7RUFDakIsaUJLL0RzQixFU21DdkI7O0FBRUQ7RUFDRSxzQkFBcUI7RUFDckIsa0JBQWlCO0VBQ2pCLGlCTnJEMEI7RU1zRDFCLFlYdENzQjtFWUp0QixnQlZOc0Q7RVVPdEQsZ0JBUHFDO0VmaURyQyxpQmVYcUIsRURZdEI7O0FFekREO0VBQ0Usb0JiYTZCO0VhWjdCLGtCQUFpQixFQUNsQjs7QUFFRDtFQUNFLGtCQUFpQixFQUNsQjs7QUFFRDtFQUNFLG1CUlAwQixFUVEzQjs7QUFFRDtFQUNFLGlCUmIwQixFUWMzQjs7QUFFRDtFQUNFLHVCYkx1QjtFYU12Qiw0QkFBbUI7TUFBbkIsNkJBQW1CO1VBQW5CLG9CQUFtQjtFQUNuQixxQkFBYTtFQUFiLHNCQUFhO0VBQWIscUJBQWE7RUFBYixjQUFhO0VBRWIsOEJiVHVCLEVhVXhCOztBQUVEO0VBQ0UsWVJ2QjBCO0VRd0IxQiw0QkFBbUI7TUFBbkIsNkJBQW1CO1VBQW5CLG9CQUFtQjtFQUNuQixrQlJ6QjBCO0VRMEIxQixnQ0FBdUMsRUFXeEM7RUFmRDtJQVFJLFdBQVUsRUFDWDtFQVRIO0lBYUksa0JBQTRCLEVBQzdCOztBQUdIO0VBQ0Usa0JBQWlCLEVBYWxCO0VBWEM7SUFDRSxZUnhDd0IsRVF5Q3pCO0VBRUQ7SUFDRSxhQUFzQixFQUN2QjtFQUVEO0lBQ0UsWUFBc0IsRUFDdkI7O0FBR0g7RUFDRSxrQlJ2RDBCO0VRd0QxQixZYjNDc0IsRWE0Q3ZCOztBQUVEO0VBRUkscUJBQWE7RUFBYixzQkFBYTtFQUFiLHFCQUFhO0VBQWIsY0FBYTtFQUNiLGlCUjdEc0I7RVE4RHRCLDBCQUFtQjtFQUFuQiw0QkFBbUI7TUFBbkIsdUJBQW1CO1VBQW5CLG9CQUFtQjtFQUNuQixvQlJqRXdCLEVRc0V6QjtFQVZIO0lBUU0sdUJDdEUrQixFRHVFaEM7O0FBSUw7RUFDRSxrQkFBaUIsRUFDbEI7O0FBRUQ7RUFDRSxnQkFBZTtFQUNmLGtCQUF3QjtFQUN4QixZQUFXO0VBQ1gsdUJBQXNCLEVBQ3ZCOztBQUdEO0VBQ0UsV0FBVSxFQUNYOztBRXBEUztFRnVEUjtJQUVJLGFBQVksRUFDYjtFQUhIO0lBTUksOEJBQW9DLEVBQ3JDO0VBUEg7SUFVSSxpQkFBZ0I7SUFDaEIsbUJSbkdvQixFUW9HckIsRUFBQTs7QUcxR0w7RUFDRSxxQkFBYTtFQUFiLHNCQUFhO0VBQWIscUJBQWE7RUFBYixjQUFhO0VBQ2IsNkJBQXNCO0VBQXRCLDhCQUFzQjtFQUF0QiwrQkFBc0I7TUFBdEIsMkJBQXNCO1VBQXRCLHVCQUFzQjtFQUN0QixvQkFBYztFQUFkLHVCQUFjO01BQWQsbUJBQWM7VUFBZCxlQUFjO0VBQ2QsY0FBYTtFQUNiLG9CWExnQixFV01qQjs7QUFFRDtFQUNFLGFBQVk7RUFDWixtQkFBa0I7RUFDbEIsU1hUMEI7RVdVMUIsWVhaZ0IsRVdhakI7O0FBRUQ7O0VBRUUsaUJBQWdCO0VBQ2hCLGtCZFhzRDtFY1l0RCxhWGpCMEI7RVdrQjFCLHNCQUFxQjtFQUNyQixvQkFBbUIsRUFDcEI7O0FBRUQ7RUFDRSxpQko2QnlCLEVJNUIxQjs7QUN6QkQ7RUFDRSxlQUFjO0VBQ2QsOEJqQlk2QixFaUJYOUI7O0FBRUQ7RUFDRSxrQmZHc0Q7RWVGdEQsVUFBUyxFQUNWOztBQUdEO0VBRUksZUFBYyxFQUNmOztBQUhIO0VBS0ksa0JBQWlCO0VBQ2pCLDBDQUFrRCxFQU9uRDtFQWJIO0lBUU0sMENBQWlELEVBQ2xEO0VBVEw7SUFXTSwwQ0FBa0QsRUFDbkQ7O0FBWkw7RUFlSSx1QkFBc0I7RUFDdEIsYUFBWSxFQUNiOztBQWpCSDtFQW1CSSxxQkFBYTtFQUFiLHNCQUFhO0VBQWIscUJBQWE7RUFBYixjQUFhO0VBQ2IsK0JBQW1CO0VBQW5CLDhCQUFtQjtFQUFuQiw0QkFBbUI7TUFBbkIsd0JBQW1CO1VBQW5CLG9CQUFtQixFQUNwQjs7QUFyQkg7RUF1QkkscUJBQWE7RUFBYixzQkFBYTtFQUFiLHFCQUFhO0VBQWIsY0FBYTtFQUNiLDJCQUFrQjtNQUFsQiw0QkFBa0I7VUFBbEIsbUJBQWtCO0VBVWxCLHVCakI1Qm9CO0VpQjZCcEIsWUFBVyxFQXNCWjtFQXpESDtJQTBCTSwrQmpCbkNxQixFaUIwQ3RCO0lBakNMO01BNEJRLCtCakI5QmdCLEVpQitCakI7SUE3QlA7TUErQlEsK0JqQmhDaUIsRWlCaUNsQjtFQWhDUDtJQXFDTSxpQkFBZ0I7SUFDaEIsWUFBVztJQUNYLG1CQUFrQjtJQUNsQixnQkFBZTtJQUNmLHNCQUFxQjtJQUNyQixZakJoRGMsRWlCd0RmO0lBbERMO01BNENRLFlqQmxEWTtNaUJtRFosc0JBQXFCLEVBQ3RCO0lBOUNQO01BZ0RRLGlCQUFnQixFQUNqQjtFQWpEUDtJQXFEUSxZQUFXO0lBQ1gsY0FBYSxFQUNkOztBQXZEUDtFQTRESSxpQkFBZ0IsRUFDakI7O0FBN0RIO0VBZ0VJLGFBQVksRUFDYjs7QUFqRUg7RUFvRUksMkJBQWtCO01BQWxCLDRCQUFrQjtVQUFsQixtQkFBa0I7RUFDbEIsaUJBQWdCO0VBQ2hCLDBCakJoRnFCO0VpQmlGckIsc0JBQXFCO0VBQ3JCLDRCakJ0RXFCO0VpQnVFckIsbUJBQWtCO0VBQ2xCLFlqQmhGZ0IsRWlCa0hqQjtFQTVHSDtJQTZFTSxhQUFZO0lBQ1osYUFBWTtJQUNaLFFBQU87SUFDUCwwQmpCNUVzQjtJaUI2RXRCLG1CQUFrQjtJQUNsQixZakI1RWtCO0lpQjZFbEIsMEJqQi9Fc0I7SWlCZ0Z0QixvQkFBbUIsRUFDcEI7RUFyRkw7SUF3Rk0sMEJIbEcrQixFR21HaEM7RUF6Rkw7SUE0Rk0sa0NBQWlEO0lBQ2pELFlqQnJGa0IsRWlCc0ZuQjtFQTlGTDtJQWlHTSxxQ0FBNEM7SUFDNUMsWWpCMUZrQixFaUIyRm5CO0VBbkdMO0lBc0dNLDBCakIvRWdCLEVpQmdGakI7RUF2R0w7SUEwR00sMEJIcEgrQixFR3FIaEM7O0FBM0dMO0VBK0dJLGdCQUFlO0VBQ2YsdUJqQjlHcUI7RWlCbUhyQixlakI5RzBCO0VpQitHMUIsYUFBWSxFQU9iO0VBN0hIO0lBa0hNLGlCQUFnQjtJQUNoQixrQkFBaUIsRUFDbEI7RUFwSEw7SUF3SE0sWUFBVztJQUNYLHNCQUFxQjtJQUNyQixxQkFBb0I7SUFDcEIsZUFBYyxFQUNmIiwiZmlsZSI6InRvLmNzcyJ9 */ \ No newline at end of file +/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIl9oYWNrcy5zY3NzIiwiLi4vLi4vLi4vLi4vbm9kZV9tb2R1bGVzL0BlbGFzdGljL2V1aS9zcmMvZ2xvYmFsX3N0eWxpbmcvdmFyaWFibGVzL19jb2xvcnMuc2NzcyIsIi4uLy4uLy4uLy4uL25vZGVfbW9kdWxlcy9AZWxhc3RpYy9ldWkvc3JjL3RoZW1lcy9rNi9rNl9nbG9iYWxzLnNjc3MiLCIuLi8uLi8uLi8uLi9ub2RlX21vZHVsZXMvQGVsYXN0aWMvZXVpL3NyYy90aGVtZXMvazYvazZfY29sb3JzX2xpZ2h0LnNjc3MiLCJjb21wb25lbnRzL2NoYXJ0L19jaGFydC5zY3NzIiwiLi4vLi4vLi4vLi4vbm9kZV9tb2R1bGVzL0BlbGFzdGljL2V1aS9zcmMvZ2xvYmFsX3N0eWxpbmcvdmFyaWFibGVzL19zaXplLnNjc3MiLCJjb21wb25lbnRzL25vX2RhdGEvX25vX2RhdGEuc2NzcyIsImNvbXBvbmVudHMvc3BhcmtsaW5lL19zcGFya2xpbmUuc2NzcyIsIi4uLy4uLy4uLy4uL25vZGVfbW9kdWxlcy9AZWxhc3RpYy9ldWkvc3JjL2dsb2JhbF9zdHlsaW5nL3ZhcmlhYmxlcy9fYm9yZGVycy5zY3NzIiwiLi4vLi4vLi4vLi4vbm9kZV9tb2R1bGVzL0BlbGFzdGljL2V1aS9zcmMvZ2xvYmFsX3N0eWxpbmcvdmFyaWFibGVzL196X2luZGV4LnNjc3MiLCJjb21wb25lbnRzL3N1bW1hcnlfc3RhdHVzL19zdW1tYXJ5X3N0YXR1cy5zY3NzIiwiY29tcG9uZW50cy90YWJsZS9fdGFibGUuc2NzcyIsIi4uLy4uLy4uLy4uL25vZGVfbW9kdWxlcy9AZWxhc3RpYy9ldWkvc3JjL2dsb2JhbF9zdHlsaW5nL3ZhcmlhYmxlcy9fdHlwb2dyYXBoeS5zY3NzIiwiLi4vLi4vLi4vLi4vbm9kZV9tb2R1bGVzL0BlbGFzdGljL2V1aS9zcmMvZ2xvYmFsX3N0eWxpbmcvbWl4aW5zL190eXBvZ3JhcGh5LnNjc3MiLCJjb21wb25lbnRzL2xvZ3N0YXNoL3BpcGVsaW5lX3ZpZXdlci92aWV3cy9fcGlwZWxpbmVfdmlld2VyLnNjc3MiLCIuLi8uLi8uLi8uLi9ub2RlX21vZHVsZXMvQGVsYXN0aWMvZXVpL3NyYy9nbG9iYWxfc3R5bGluZy9mdW5jdGlvbnMvX2NvbG9ycy5zY3NzIiwiLi4vLi4vLi4vLi4vbm9kZV9tb2R1bGVzL0BlbGFzdGljL2V1aS9zcmMvZ2xvYmFsX3N0eWxpbmcvbWl4aW5zL19yZXNwb25zaXZlLnNjc3MiLCJkaXJlY3RpdmVzL2NoYXJ0L19jaGFydC5zY3NzIiwiZGlyZWN0aXZlcy9lbGFzdGljc2VhcmNoL3NoYXJkX2FsbG9jYXRpb24vX3NoYXJkX2FsbG9jYXRpb24uc2NzcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTtFQUdJLG9CQ1kyQixFRFg1Qjs7QUFKSDtFQVFJLGtCRURvRCxFRkVyRDs7QUFUSDs7RUFhSSxlR2JrQixFSGNuQjs7QUFkSDtFQWtCSSxZQ0FvQixFRENyQjs7QUFuQkg7RUF1QkksaUJBQWdCO0VBQ2hCLGtCQUFpQjtFQUNqQixVQUFTLEVBQ1Y7O0FJcEJIO0VBQ0UsbUJBQWtCO0VBQ2xCLHFCQUFhO0VBQWIsc0JBQWE7RUFBYixxQkFBYTtFQUFiLGNBQWE7RUFDYiw2QkFBc0I7RUFBdEIsOEJBQXNCO0VBQXRCLCtCQUFzQjtNQUF0QiwyQkFBc0I7VUFBdEIsdUJBQXNCO0VBQ3RCLG9CQUFjO0VBQWQsdUJBQWM7TUFBZCxtQkFBYztVQUFkLGVBQWMsRUFDZjs7QUFFRDtFQUNFLGVEZG9CO0VDZXBCLGdCQ1p5QixFRGExQjs7QUFFRDtFQUNFLG1CQUFrQjtFQUNsQixPQUFNO0VBQ04sU0FBUTtFQUNSLFVBQVM7RUFDVCxRQUFPO0VBQ1AscUJBQWE7RUFBYixzQkFBYTtFQUFiLHFCQUFhO0VBQWIsY0FBYTtFQUNiLG9CQUFjO0VBQWQsdUJBQWM7TUFBZCxtQkFBYztVQUFkLGVBQWMsRUFDZjs7QUFFRDtFQUNFLHFCQUFhO0VBQWIsc0JBQWE7RUFBYixxQkFBYTtFQUFiLGNBQWE7RUFDYiw2QkFBc0I7RUFBdEIsOEJBQXNCO0VBQXRCLCtCQUFzQjtNQUF0QiwyQkFBc0I7VUFBdEIsdUJBQXNCO0VBQ3RCLG9CQUFjO0VBQWQsdUJBQWM7TUFBZCxtQkFBYztVQUFkLGVBQWM7RUFDZCxtQkFBa0IsRUFhbkI7RUFqQkQ7SUFRSSxlQUFjO0lBQ2QsWUFBVztJQUNYLGFBQVksRUFDYjtFQVhIO0lBM0JFLDBCQUFpQjtPQUFqQix1QkFBaUI7UUFBakIsc0JBQWlCO1lBQWpCLGtCQUFpQjtJQUNqQiw0QkFBMkI7SUFDM0IseUNBQXdDLEVBeUN2Qzs7QUFHSDtFQUNFLGtCRnpDc0Q7RUUwQ3RELGdCQUFlO0VBQ2YsZURsRG9CLEVDdURyQjtFQUhDO0lBQ0UsYUFBWSxFQUNiOztBQUdIO0VBQ0UsZ0JDeEQwQixFRHlEM0I7O0FBRUQ7RUFDRSxpQkFBZ0I7RUFDaEIsb0JBQW1CLEVBQ3BCOztBQUNEO0VBQ0UsaUJBQWdCO0VBQ2hCLG9CQUFtQjtFQUNuQixpQkNsRTBCLEVEbUUzQjs7QUVyRUQ7RUFDRSxpQkFBZ0I7RUFDaEIsbUJBQWtCO0VBQ2xCLG1CQUFrQixFQUNuQjs7QUNKRDtFQUNFLFlBQVcsRUFDWjs7QUFHRDtFQUNFLG9CQUFtQjtFQUNuQixrQ0FBcUQ7RUFDckQsa0JMRHNEO0VLRXRELGFGUDBCO0VFUTFCLG1CQ0ptQjtFREtuQixxQkFBb0IsRUFDckI7O0FBRUQ7RUFDRSxnQ0FBeUMsRUFDMUM7O0FBRUQ7RUFDRSxZTmJrQixFTWNuQjs7QUFFRDtFQUNFLGtCTGJzRDtFS2N0RCw2QkFBZ0Q7RUFDaEQsY0FBYSxFQUNkOztBQUVEO0VBQ0UsZ0JBQWU7RUFDZixjRWJ3QjtFRmN4QixxQkFBYTtFQUFiLHNCQUFhO0VBQWIscUJBQWE7RUFBYixjQUFhO0VBQ2IsK0JBQW1CO0VBQW5CLDhCQUFtQjtFQUFuQiw0QkFBbUI7TUFBbkIsd0JBQW1CO1VBQW5CLG9CQUFtQjtFQUNuQix5QkFBdUI7RUFBdkIsZ0NBQXVCO01BQXZCLHNCQUF1QjtVQUF2Qix3QkFBdUI7RUFDdkIsMEJBQW1CO0VBQW5CLDRCQUFtQjtNQUFuQix1QkFBbUI7VUFBbkIsb0JBQW1CLEVBQ3BCOztBR25DRDtFQUNFLDBCVGM2QjtFU2I3QixpQ1RjMEI7RVNiMUIsY0xIZ0IsRUtJakI7O0FDSkQ7Ozs7R0FJRztBQUNIO0VBQ0UsWUFBVyxFQUNaOztBQUVEOztFQUVFLGVSWG9CLEVRWXJCOztBQUVEO0VBQ0UsZ0JBQWUsRUFDaEI7O0FBQ0Q7RUFDRSxZVkFzQjtFV0p0QixnQlZOc0Q7RVVPdEQsZ0JBUHFDO0VDaURyQyxpQkRYcUIsRUQxQnRCOztBQUVEOzs7RUNSRSxrQlZMc0Q7RVVNdEQsc0JBUHFDO0VDMkRyQyxpQkRyQnFCLEVEcEJ0Qjs7QUFFRDtFQUNFLFlWWHNCO0VXSnRCLGdCVk5zRDtFVU90RCxnQkFQcUM7RUNpRHJDLGlCRFhxQixFRGZ0Qjs7QUFFRDtFQ25CRSxrQlZIc0Q7RVVJdEQsc0JBUHFDO0VDcUVyQyxrQkFBaUI7RUFDakIsaUJYL0RzQixFU29CdkI7O0FBRUQ7RUN2QkUsa0JWTHNEO0VVTXRELHNCQVBxQztFQzJEckMsaUJEckJxQixFRFB0Qjs7QUFFRDtFQUNFLHNCQUFxQjtFQzVCckIsa0JWSHNEO0VVSXRELHNCQVBxQztFQ3FFckMsa0JBQWlCO0VBQ2pCLGlCWC9Ec0IsRVM2QnZCOztBQUVEO0VBQ0Usc0JBQXFCO0VBQ3JCLGlCTjlDMEI7RU9ZMUIsa0JWSHNEO0VVSXRELHNCQVBxQztFQ3FFckMsa0JBQWlCO0VBQ2pCLGlCWC9Ec0IsRVNtQ3ZCOztBQUVEO0VBQ0Usc0JBQXFCO0VBQ3JCLGtCQUFpQjtFQUNqQixpQk5yRDBCO0VNc0QxQixZVnRDc0I7RVdKdEIsZ0JWTnNEO0VVT3RELGdCQVBxQztFQ2lEckMsaUJEWHFCLEVEWXRCOztBR3pERDtFQUNFLG9CYmE2QjtFYVo3QixrQkFBaUIsRUFDbEI7O0FBRUQ7RUFDRSxrQkFBaUIsRUFDbEI7O0FBRUQ7RUFDRSxtQlRQMEIsRVNRM0I7O0FBRUQ7RUFDRSxpQlRiMEIsRVNjM0I7O0FBRUQ7RUFDRSx1QmJMdUI7RWFNdkIsNEJBQW1CO01BQW5CLDZCQUFtQjtVQUFuQixvQkFBbUI7RUFDbkIscUJBQWE7RUFBYixzQkFBYTtFQUFiLHFCQUFhO0VBQWIsY0FBYTtFQUViLDhCYlR1QixFYVV4Qjs7QUFFRDtFQUNFLFlUdkIwQjtFU3dCMUIsNEJBQW1CO01BQW5CLDZCQUFtQjtVQUFuQixvQkFBbUI7RUFDbkIsa0JUekIwQjtFUzBCMUIsZ0NBQXVDLEVBV3hDO0VBZkQ7SUFRSSxXQUFVLEVBQ1g7RUFUSDtJQWFJLGtCQUE0QixFQUM3Qjs7QUFHSDtFQUNFLGtCQUFpQixFQWFsQjtFQVhDO0lBQ0UsWVR4Q3dCLEVTeUN6QjtFQUVEO0lBQ0UsYUFBc0IsRUFDdkI7RUFFRDtJQUNFLFlBQXNCLEVBQ3ZCOztBQUdIO0VBQ0Usa0JUdkQwQjtFU3dEMUIsWWIzQ3NCLEVhNEN2Qjs7QUFFRDtFQUVJLHFCQUFhO0VBQWIsc0JBQWE7RUFBYixxQkFBYTtFQUFiLGNBQWE7RUFDYixpQlQ3RHNCO0VTOER0QiwwQkFBbUI7RUFBbkIsNEJBQW1CO01BQW5CLHVCQUFtQjtVQUFuQixvQkFBbUI7RUFDbkIsb0JUakV3QixFU3NFekI7RUFWSDtJQVFNLHVCQ3RFK0IsRUR1RWhDOztBQUlMO0VBQ0Usa0JBQWlCLEVBQ2xCOztBQUVEO0VBQ0UsZ0JBQWU7RUFDZixrQkFBd0I7RUFDeEIsWUFBVztFQUNYLHVCQUFzQixFQUN2Qjs7QUFHRDtFQUNFLFdBQVUsRUFDWDs7QUVwRFM7RUZ1RFI7SUFFSSxhQUFZLEVBQ2I7RUFISDtJQU1JLDhCQUFvQyxFQUNyQztFQVBIO0lBVUksaUJBQWdCO0lBQ2hCLG1CVG5Hb0IsRVNvR3JCLEVBQUE7O0FHMUdMO0VBQ0UscUJBQWE7RUFBYixzQkFBYTtFQUFiLHFCQUFhO0VBQWIsY0FBYTtFQUNiLDZCQUFzQjtFQUF0Qiw4QkFBc0I7RUFBdEIsK0JBQXNCO01BQXRCLDJCQUFzQjtVQUF0Qix1QkFBc0I7RUFDdEIsb0JBQWM7RUFBZCx1QkFBYztNQUFkLG1CQUFjO1VBQWQsZUFBYztFQUNkLGNBQWE7RUFDYixvQlpMZ0IsRVlNakI7O0FBRUQ7RUFDRSxhQUFZO0VBQ1osbUJBQWtCO0VBQ2xCLFNaVDBCO0VZVTFCLFlaWmdCLEVZYWpCOztBQUVEOztFQUVFLGlCQUFnQjtFQUNoQixrQmZYc0Q7RWVZdEQsYVpqQjBCO0VZa0IxQixzQkFBcUI7RUFDckIsb0JBQW1CLEVBQ3BCOztBQUVEO0VBQ0UsaUJMNkJ5QixFSzVCMUI7O0FDekJEO0VBQ0UsZUFBYztFQUNkLDhCakJZNkIsRWlCWDlCOztBQUVEO0VBQ0Usa0JoQkdzRDtFZ0JGdEQsVUFBUyxFQUNWOztBQUdEO0VBRUksZUFBYyxFQUNmOztBQUhIO0VBS0ksa0JBQWlCO0VBQ2pCLDBDQUFrRCxFQU9uRDtFQWJIO0lBUU0sMENBQWlELEVBQ2xEO0VBVEw7SUFXTSwwQ0FBa0QsRUFDbkQ7O0FBWkw7RUFlSSx1QkFBc0I7RUFDdEIsYUFBWSxFQUNiOztBQWpCSDtFQW1CSSxxQkFBYTtFQUFiLHNCQUFhO0VBQWIscUJBQWE7RUFBYixjQUFhO0VBQ2IsK0JBQW1CO0VBQW5CLDhCQUFtQjtFQUFuQiw0QkFBbUI7TUFBbkIsd0JBQW1CO1VBQW5CLG9CQUFtQixFQUNwQjs7QUFyQkg7RUF1QkkscUJBQWE7RUFBYixzQkFBYTtFQUFiLHFCQUFhO0VBQWIsY0FBYTtFQUNiLDJCQUFrQjtNQUFsQiw0QkFBa0I7VUFBbEIsbUJBQWtCO0VBVWxCLHVCakI1Qm9CO0VpQjZCcEIsWUFBVyxFQXNCWjtFQXpESDtJQTBCTSwrQmpCbkNxQixFaUIwQ3RCO0lBakNMO01BNEJRLCtCakI5QmdCLEVpQitCakI7SUE3QlA7TUErQlEsK0JqQmhDaUIsRWlCaUNsQjtFQWhDUDtJQXFDTSxpQkFBZ0I7SUFDaEIsWUFBVztJQUNYLG1CQUFrQjtJQUNsQixnQkFBZTtJQUNmLHNCQUFxQjtJQUNyQixZakJoRGMsRWlCd0RmO0lBbERMO01BNENRLFlqQmxEWTtNaUJtRFosc0JBQXFCLEVBQ3RCO0lBOUNQO01BZ0RRLGlCQUFnQixFQUNqQjtFQWpEUDtJQXFEUSxZQUFXO0lBQ1gsY0FBYSxFQUNkOztBQXZEUDtFQTRESSxpQkFBZ0IsRUFDakI7O0FBN0RIO0VBZ0VJLGFBQVksRUFDYjs7QUFqRUg7RUFvRUksMkJBQWtCO01BQWxCLDRCQUFrQjtVQUFsQixtQkFBa0I7RUFDbEIsaUJBQWdCO0VBQ2hCLDBCakJoRnFCO0VpQmlGckIsc0JBQXFCO0VBQ3JCLDRCakJ0RXFCO0VpQnVFckIsbUJBQWtCO0VBQ2xCLFlqQmhGZ0IsRWlCa0hqQjtFQTVHSDtJQTZFTSxhQUFZO0lBQ1osYUFBWTtJQUNaLFFBQU87SUFDUCwwQmpCNUVzQjtJaUI2RXRCLG1CQUFrQjtJQUNsQixZakI1RWtCO0lpQjZFbEIsMEJqQi9Fc0I7SWlCZ0Z0QixvQkFBbUIsRUFDcEI7RUFyRkw7SUF3Rk0sMEJIbEcrQixFR21HaEM7RUF6Rkw7SUE0Rk0sa0NBQWlEO0lBQ2pELFlqQnJGa0IsRWlCc0ZuQjtFQTlGTDtJQWlHTSxxQ0FBNEM7SUFDNUMsWWpCMUZrQixFaUIyRm5CO0VBbkdMO0lBc0dNLDBCakIvRWdCLEVpQmdGakI7RUF2R0w7SUEwR00sMEJIcEgrQixFR3FIaEM7O0FBM0dMO0VBK0dJLGdCQUFlO0VBQ2YsdUJqQjlHcUI7RWlCbUhyQixlakI5RzBCO0VpQitHMUIsYUFBWSxFQU9iO0VBN0hIO0lBa0hNLGlCQUFnQjtJQUNoQixrQkFBaUIsRUFDbEI7RUFwSEw7SUF3SE0sWUFBVztJQUNYLHNCQUFxQjtJQUNyQixxQkFBb0I7SUFDcEIsZUFBYyxFQUNmIiwiZmlsZSI6InRvLmNzcyJ9 */ \ No newline at end of file diff --git a/x-pack/test/api_integration/apis/beats/assign_tags_to_beats.js b/x-pack/test/api_integration/apis/beats/assign_tags_to_beats.js index 4f003df0a654f..f5a117bfb359a 100644 --- a/x-pack/test/api_integration/apis/beats/assign_tags_to_beats.js +++ b/x-pack/test/api_integration/apis/beats/assign_tags_to_beats.js @@ -236,7 +236,7 @@ export default function ({ getService }) { .post('/api/beats/agents_tags/assignments') .set('kbn-xsrf', 'xxx') .send({ - assignments: [{ beatID: nonExistentBeatId, tag: nonExistentTag }], + assignments: [{ beatId: nonExistentBeatId, tag: nonExistentTag }], }) .expect(200); diff --git a/x-pack/test/api_integration/apis/beats/enroll_beat.js b/x-pack/test/api_integration/apis/beats/enroll_beat.js index 809734b741906..983182184a630 100644 --- a/x-pack/test/api_integration/apis/beats/enroll_beat.js +++ b/x-pack/test/api_integration/apis/beats/enroll_beat.js @@ -181,7 +181,7 @@ export default function ({ getService }) { .set('kbn-xsrf', 'xxx') .set('kbn-beats-enrollment-token', validEnrollmentToken) .send(beat) - .expect(409); + .expect(201); }); }); } diff --git a/x-pack/test/api_integration/apis/beats/remove_tags_from_beats.js b/x-pack/test/api_integration/apis/beats/remove_tags_from_beats.js index 95030a63ac749..999d3614451a2 100644 --- a/x-pack/test/api_integration/apis/beats/remove_tags_from_beats.js +++ b/x-pack/test/api_integration/apis/beats/remove_tags_from_beats.js @@ -147,7 +147,7 @@ export default function ({ getService }) { .post('/api/beats/agents_tags/removals') .set('kbn-xsrf', 'xxx') .send({ - removals: [{ beat_id: nonExistentBeatId, tag: 'production' }], + removals: [{ beatId: nonExistentBeatId, tag: 'production' }], }) .expect(200); diff --git a/x-pack/test/api_integration/apis/beats/set_tag.js b/x-pack/test/api_integration/apis/beats/set_tag.js index 3af3c0372e847..06d98721c85bf 100644 --- a/x-pack/test/api_integration/apis/beats/set_tag.js +++ b/x-pack/test/api_integration/apis/beats/set_tag.js @@ -5,10 +5,7 @@ */ import expect from 'expect.js'; -import { - ES_INDEX_NAME, - ES_TYPE_NAME -} from './constants'; +import { ES_INDEX_NAME, ES_TYPE_NAME } from './constants'; export default function ({ getService }) { const supertest = getService('supertest'); @@ -19,9 +16,7 @@ export default function ({ getService }) { it('should create an empty tag', async () => { const tagId = 'production'; await supertest - .put( - `/api/beats/tag/${tagId}` - ) + .put(`/api/beats/tag/${tagId}`) .set('kbn-xsrf', 'xxx') .send() .expect(201); @@ -29,7 +24,7 @@ export default function ({ getService }) { const esResponse = await es.get({ index: ES_INDEX_NAME, type: ES_TYPE_NAME, - id: `tag:${tagId}` + id: `tag:${tagId}`, }); const tagInEs = esResponse._source; @@ -43,24 +38,30 @@ export default function ({ getService }) { it('should create a tag with one configuration block', async () => { const tagId = 'production'; await supertest - .put( - `/api/beats/tag/${tagId}` - ) + .put(`/api/beats/tag/${tagId}`) .set('kbn-xsrf', 'xxx') .send({ configuration_blocks: [ { type: 'output', - block_yml: 'elasticsearch:\n hosts: [\"localhost:9200\"]\n username: "..."' - } - ] + description: 'smething', + configs: [ + { + elasticsearch: { + hosts: ['localhost:9200'], + username: 'foo', + }, + }, + ], + }, + ], }) .expect(201); const esResponse = await es.get({ index: ES_INDEX_NAME, type: ES_TYPE_NAME, - id: `tag:${tagId}` + id: `tag:${tagId}`, }); const tagInEs = esResponse._source; @@ -70,34 +71,50 @@ export default function ({ getService }) { expect(tagInEs.tag.configuration_blocks).to.be.an(Array); expect(tagInEs.tag.configuration_blocks.length).to.be(1); expect(tagInEs.tag.configuration_blocks[0].type).to.be('output'); - expect(tagInEs.tag.configuration_blocks[0].block_yml).to.be('elasticsearch:\n hosts: [\"localhost:9200\"]\n username: "..."'); + expect(tagInEs.tag.configuration_blocks[0].configs).to.eql([ + { + elasticsearch: { + hosts: ['localhost:9200'], + username: 'foo', + }, + }, + ]); }); it('should create a tag with two configuration blocks', async () => { const tagId = 'production'; await supertest - .put( - `/api/beats/tag/${tagId}` - ) + .put(`/api/beats/tag/${tagId}`) .set('kbn-xsrf', 'xxx') .send({ configuration_blocks: [ { type: 'filebeat.inputs', - block_yml: 'file:\n path: "/var/log/some.log"]\n' + configs: [ + { + paths: ['./foo'], + }, + ], }, { type: 'output', - block_yml: 'elasticsearch:\n hosts: [\"localhost:9200\"]\n username: "..."' - } - ] + configs: [ + { + elasticsearch: { + hosts: ['localhost:9200'], + username: 'foo', + }, + }, + ], + }, + ], }) .expect(201); const esResponse = await es.get({ index: ES_INDEX_NAME, type: ES_TYPE_NAME, - id: `tag:${tagId}` + id: `tag:${tagId}`, }); const tagInEs = esResponse._source; @@ -107,29 +124,52 @@ export default function ({ getService }) { expect(tagInEs.tag.configuration_blocks).to.be.an(Array); expect(tagInEs.tag.configuration_blocks.length).to.be(2); expect(tagInEs.tag.configuration_blocks[0].type).to.be('filebeat.inputs'); - expect(tagInEs.tag.configuration_blocks[0].block_yml).to.be('file:\n path: "/var/log/some.log"]\n'); + expect(tagInEs.tag.configuration_blocks[0].configs).to.eql([ + { + paths: ['./foo'], + }, + ]); expect(tagInEs.tag.configuration_blocks[1].type).to.be('output'); - expect(tagInEs.tag.configuration_blocks[1].block_yml).to.be('elasticsearch:\n hosts: [\"localhost:9200\"]\n username: "..."'); + expect(tagInEs.tag.configuration_blocks[1].configs).to.eql([ + { + elasticsearch: { + hosts: ['localhost:9200'], + username: 'foo', + }, + }, + ]); }); it('should fail when creating a tag with two configuration blocks of type output', async () => { const tagId = 'production'; await supertest - .put( - `/api/beats/tag/${tagId}` - ) + .put(`/api/beats/tag/${tagId}`) .set('kbn-xsrf', 'xxx') .send({ configuration_blocks: [ { type: 'output', - block_yml: 'logstash:\n hosts: ["localhost:9000"]\n' + configs: [ + { + elasticsearch: { + hosts: ['localhost:9200'], + username: 'foo', + }, + }, + ], }, { type: 'output', - block_yml: 'elasticsearch:\n hosts: [\"localhost:9200\"]\n username: "..."' - } - ] + configs: [ + { + elasticsearch: { + hosts: ['localhost:9200'], + username: 'foo', + }, + }, + ], + }, + ], }) .expect(400); }); @@ -137,17 +177,22 @@ export default function ({ getService }) { it('should fail when creating a tag with an invalid configuration block type', async () => { const tagId = 'production'; await supertest - .put( - `/api/beats/tag/${tagId}` - ) + .put(`/api/beats/tag/${tagId}`) .set('kbn-xsrf', 'xxx') .send({ configuration_blocks: [ { type: chance.word(), - block_yml: 'logstash:\n hosts: ["localhost:9000"]\n' - } - ] + configs: [ + { + elasticsearch: { + hosts: ['localhost:9200'], + username: 'foo', + }, + }, + ], + }, + ], }) .expect(400); }); @@ -155,43 +200,57 @@ export default function ({ getService }) { it('should update an existing tag', async () => { const tagId = 'production'; await supertest - .put( - `/api/beats/tag/${tagId}` - ) + .put(`/api/beats/tag/${tagId}`) .set('kbn-xsrf', 'xxx') .send({ configuration_blocks: [ { type: 'filebeat.inputs', - block_yml: 'file:\n path: "/var/log/some.log"]\n' + configs: [ + { + paths: ['./test'], + }, + ], }, { type: 'output', - block_yml: 'elasticsearch:\n hosts: [\"localhost:9200\"]\n username: "..."' - } - ] + configs: [ + { + elasticsearch: { + hosts: ['localhost:9200'], + username: 'foo', + }, + }, + ], + }, + ], }) .expect(201); await supertest - .put( - `/api/beats/tag/${tagId}` - ) + .put(`/api/beats/tag/${tagId}`) .set('kbn-xsrf', 'xxx') .send({ configuration_blocks: [ { type: 'output', - block_yml: 'logstash:\n hosts: ["localhost:9000"]\n' - } - ] + configs: [ + { + elasticsearch: { + hosts: ['localhost:9000'], + username: 'foo', + }, + }, + ], + }, + ], }) .expect(200); const esResponse = await es.get({ index: ES_INDEX_NAME, type: ES_TYPE_NAME, - id: `tag:${tagId}` + id: `tag:${tagId}`, }); const tagInEs = esResponse._source; @@ -201,7 +260,14 @@ export default function ({ getService }) { expect(tagInEs.tag.configuration_blocks).to.be.an(Array); expect(tagInEs.tag.configuration_blocks.length).to.be(1); expect(tagInEs.tag.configuration_blocks[0].type).to.be('output'); - expect(tagInEs.tag.configuration_blocks[0].block_yml).to.be('logstash:\n hosts: ["localhost:9000"]\n'); + expect(tagInEs.tag.configuration_blocks[0].configs).to.eql([ + { + elasticsearch: { + hosts: ['localhost:9000'], + username: 'foo', + }, + }, + ]); }); }); } From 62e36f102cce0ae95e398ec490b28e4fbef7d7ed Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Thu, 18 Oct 2018 11:36:00 -0400 Subject: [PATCH 94/94] Remove unused import. --- .../public/components/tag/disabled_tag_badge.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/beats_management/public/components/tag/disabled_tag_badge.tsx b/x-pack/plugins/beats_management/public/components/tag/disabled_tag_badge.tsx index 22421cb23f159..23d1be68e0da8 100644 --- a/x-pack/plugins/beats_management/public/components/tag/disabled_tag_badge.tsx +++ b/x-pack/plugins/beats_management/public/components/tag/disabled_tag_badge.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiBadge, EuiToolTip } from '@elastic/eui'; +import { EuiBadge } from '@elastic/eui'; import React from 'react'; import { TABLE_CONFIG } from '../../../common/constants';