Skip to content

Commit

Permalink
Beats/beat tags workflow (#21923)
Browse files Browse the repository at this point in the history
* [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
  • Loading branch information
mattapperson committed Aug 27, 2018
1 parent 13bf2bc commit cd8f274
Show file tree
Hide file tree
Showing 73 changed files with 2,433 additions and 394 deletions.
2 changes: 2 additions & 0 deletions x-pack/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <EuiLink aria-disabled="true" {...props} />;
}

// Shorthand for pathname
const pathname = path || _.get(props.to, 'pathname') || location.pathname;

return (
<Link
{...props}
to={{ ...location, ...props.to, pathname }}
className={`euiLink euiLink--primary ${props.className || ''}`}
/>
);
}

export const ConnectedLink = withRouter<any>(ConnectedLinkComponent);
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand All @@ -22,22 +33,35 @@ const HeaderContainer = styled.div`
margin-bottom: 16px;
`;

export const PrimaryLayout: React.SFC<PrimaryLayoutProps> = ({
actionSection,
title,
children,
}) => (
<EuiPage>
<EuiPageBody>
<EuiPageContent>
<HeaderContainer>
<EuiTitle>
<h1>{title}</h1>
</EuiTitle>
{actionSection}
</HeaderContainer>
<EuiPageContentBody>{children}</EuiPageContentBody>
</EuiPageContent>
</EuiPageBody>
</EuiPage>
);
export const PrimaryLayout: React.SFC<PrimaryLayoutProps> = withRouter<any>(
({ actionSection, title, modalRender, modalClosePath, children, history }) => {
const modalContent = modalRender && modalRender();
return (
<EuiPage>
<EuiPageBody>
<EuiPageContent>
<HeaderContainer>
<EuiTitle>
<h1>{title}</h1>
</EuiTitle>
{actionSection}
</HeaderContainer>
<EuiPageContentBody>{children}</EuiPageContentBody>
</EuiPageContent>
</EuiPageBody>
{modalContent && (
<EuiOverlayMask>
<EuiModal
onClose={() => {
history.push(modalClosePath);
}}
style={{ width: '640px' }}
>
{modalContent}
</EuiModal>
</EuiOverlayMask>
)}
</EuiPage>
);
}
) as any;
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -39,6 +40,7 @@ export class AssignmentOptions extends React.Component<
const {
actionHandler,
assignmentOptions,
renderAssignmentOptions,
assignmentTitle,
controlDefinitions: { actions },
selectionCount,
Expand All @@ -60,43 +62,45 @@ export class AssignmentOptions extends React.Component<
}}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiPopover
button={
<EuiButton
color="primary"
iconSide="right"
iconType="arrowDown"
onClick={() => {
this.setState({
isAssignmentPopoverVisible: true,
});
actionHandler('loadAssignmentOptions');
}}
>
{assignmentTitle}
</EuiButton>
}
closePopover={() => {
this.setState({ isAssignmentPopoverVisible: false });
}}
id="assignmentList"
isOpen={isAssignmentPopoverVisible}
panelPaddingSize="s"
withTitle
>
{assignmentOptions ? (
// @ts-ignore direction prop not available on current typing
<EuiFlexGroup direction="column" gutterSize="xs">
{assignmentOptions}
</EuiFlexGroup>
) : (
<div>
<EuiLoadingSpinner size="m" /> Loading
</div>
)}
</EuiPopover>
</EuiFlexItem>
{assignmentTitle && (
<EuiFlexItem grow={false}>
<EuiPopover
button={
<EuiButton
color="primary"
iconSide="right"
iconType="arrowDown"
onClick={() => {
this.setState({
isAssignmentPopoverVisible: true,
});
actionHandler('loadAssignmentOptions');
}}
>
{assignmentTitle}
</EuiButton>
}
closePopover={() => {
this.setState({ isAssignmentPopoverVisible: false });
}}
id="assignmentList"
isOpen={isAssignmentPopoverVisible}
panelPaddingSize="s"
withTitle
>
{assignmentOptions && renderAssignmentOptions ? (
// @ts-ignore direction prop not available on current typing
<EuiFlexGroup direction="column" gutterSize="xs">
{assignmentOptions.map(options => renderAssignmentOptions(options))}
</EuiFlexGroup>
) : (
<div>
<EuiLoadingSpinner size="m" /> Loading
</div>
)}
</EuiPopover>
</EuiFlexItem>
)}
</EuiFlexGroup>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -25,6 +27,7 @@ export function ControlBar(props: ControlBarProps) {
const {
actionHandler,
assignmentOptions,
renderAssignmentOptions,
assignmentTitle,
controlDefinitions,
selectionCount,
Expand All @@ -36,6 +39,7 @@ export function ControlBar(props: ControlBarProps) {
<AssignmentOptions
actionHandler={actionHandler}
assignmentOptions={assignmentOptions}
renderAssignmentOptions={renderAssignmentOptions}
assignmentTitle={assignmentTitle}
controlDefinitions={controlDefinitions}
selectionCount={selectionCount}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,21 @@ import {
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;
renderAssignmentOptions?: (item: any) => any;
items: any[];
showAssignmentOptions: boolean;
type: TableType;
actionHandler(action: string, payload?: any): void;
}

interface BeatsTableState {
selection: CMPopulatedBeat[];
selection: any[];
}

const TableContainer = styled.div`
Expand All @@ -42,11 +42,15 @@ export class Table extends React.Component<BeatsTableProps, BeatsTableState> {
};
}

public render() {
public resetSelection = () => {
this.setSelection([]);
};

public render() {
const {
actionHandler,
assignmentOptions,
renderAssignmentOptions,
assignmentTitle,
items,
showAssignmentOptions,
Expand All @@ -70,6 +74,7 @@ export class Table extends React.Component<BeatsTableProps, BeatsTableState> {
<ControlBar
actionHandler={(action: string, payload: any) => actionHandler(action, payload)}
assignmentOptions={assignmentOptions}
renderAssignmentOptions={renderAssignmentOptions}
assignmentTitle={assignmentTitle}
controlDefinitions={type.controlDefinitions(items)}
selectionCount={this.state.selection.length}
Expand All @@ -89,7 +94,7 @@ export class Table extends React.Component<BeatsTableProps, BeatsTableState> {
);
}

private setSelection = (selection: any) => {
private setSelection = (selection: any[]) => {
this.setState({
selection,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -55,7 +56,7 @@ export const BeatsTableType: TableType = {
{
field: 'id',
name: 'Beat name',
render: (id: string) => <EuiLink>{id}</EuiLink>,
render: (id: string) => <ConnectedLink path={`/beat/${id}`}>{id}</ConnectedLink>,
sortable: true,
},
{
Expand Down Expand Up @@ -154,7 +155,13 @@ export const TagsTableType: TableType = {
},
],
controlDefinitions: (data: any) => ({
actions: [],
actions: [
{
name: 'Remove Selected',
action: 'delete',
danger: true,
},
],
filters: [],
}),
};
Original file line number Diff line number Diff line change
Expand Up @@ -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';

This file was deleted.

Loading

0 comments on commit cd8f274

Please sign in to comment.