[SIEMDPOINT] Move endpoint to siem (#66907)
* adds the stuff * keeps moving stuff * finishes moving the stuff * moves tests * fix type * try moving it all at once. BROKEN * move endpoint to siem * fix package coming from endpoint * missing scripts + change url * fix eslint * temporary disable functional testing for endpoint * fix api integration types * allow api integration test + comment functional test * fix internationalization * fix internationalization II * fix jest test * fix x-pack test * fix i18n * fix api integration * fix circular dependency * add new dependency to cypress test Co-authored-by: Davis Plumlee <davis.plumlee@elastic.co> Co-authored-by: oatkiller <robert.austin@elastic.co> Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
cb1f69aa21
commit
bcfc02987f
|
@ -1,6 +0,0 @@
|
||||||
# Schemas
|
|
||||||
|
|
||||||
These schemas are used to validate, coerce, and provide types for the comms between the client, server, and ES.
|
|
||||||
|
|
||||||
# Future work
|
|
||||||
In the future, we may be able to locate these under 'server'.
|
|
|
@ -1,9 +0,0 @@
|
||||||
{
|
|
||||||
"id": "endpoint",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"kibanaVersion": "kibana",
|
|
||||||
"configPath": ["xpack", "endpoint"],
|
|
||||||
"requiredPlugins": ["features", "embeddable", "data", "dataEnhanced", "ingestManager"],
|
|
||||||
"server": true,
|
|
||||||
"ui": true
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
{
|
|
||||||
"author": "Elastic",
|
|
||||||
"name": "endpoint",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"private": true,
|
|
||||||
"license": "Elastic-License",
|
|
||||||
"scripts": {
|
|
||||||
"test:generate": "ts-node --project scripts/cli_tsconfig.json scripts/resolver_generator.ts"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"react-redux": "^7.1.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/seedrandom": ">=2.0.0 <4.0.0",
|
|
||||||
"@types/react-redux": "^7.1.0",
|
|
||||||
"redux-devtools-extension": "^2.13.8"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
# Endpoint application
|
|
||||||
This application provides the user interface for the Elastic Endpoint
|
|
||||||
|
|
||||||
# Architecture
|
|
||||||
The application consists of a _view_ written in React and a _model_ written in Redux.
|
|
||||||
|
|
||||||
# Modules
|
|
||||||
We structure the modules to match the architecture. `view` contains the _view_ (all React) code. `store` contains the _model_.
|
|
||||||
|
|
||||||
This section covers the conventions of each top level module.
|
|
||||||
|
|
||||||
# `mocks`
|
|
||||||
This contains helper code for unit tests.
|
|
||||||
|
|
||||||
## `models`
|
|
||||||
This contains domain models. By convention, each submodule here contains methods for a single type. Domain model classes would also live here.
|
|
||||||
|
|
||||||
## `store`
|
|
||||||
This contains the _model_ of the application. All Redux and Redux middleware code (including API interactions) happen here. This module also contains the types and interfaces defining Redux actions. Each action type or interface should be commented and if it has fields, each field should be commented. Comments should be of `tsdoc` style.
|
|
||||||
|
|
||||||
## `view`
|
|
||||||
This contains the code which renders elements to the DOM. All React code goes here.
|
|
||||||
|
|
||||||
## `index.tsx`
|
|
||||||
This exports `renderApp` which instantiates the React view with the _model_.
|
|
||||||
|
|
||||||
## `types.ts`
|
|
||||||
This contains the types and interfaces. All `export`ed types or interfaces (except ones defining Redux actions, which live in `store`) should be here. Each type or interface should have a `tsdoc` style comment. Interfaces should have `tsdoc` comments on each field and types which have fields should do the same.
|
|
|
@ -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 * as React from 'react';
|
|
||||||
import ReactDOM from 'react-dom';
|
|
||||||
import { CoreStart, AppMountParameters } from 'kibana/public';
|
|
||||||
import { EndpointPluginStartDependencies } from '../../plugin';
|
|
||||||
import { appStoreFactory } from './store';
|
|
||||||
import { AppRoot } from './view/app_root';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This module will be loaded asynchronously to reduce the bundle size of your plugin's main bundle.
|
|
||||||
*/
|
|
||||||
export function renderApp(
|
|
||||||
coreStart: CoreStart,
|
|
||||||
depsStart: EndpointPluginStartDependencies,
|
|
||||||
{ element, history }: AppMountParameters
|
|
||||||
) {
|
|
||||||
const store = appStoreFactory({ coreStart, depsStart });
|
|
||||||
ReactDOM.render(
|
|
||||||
<AppRoot history={history} store={store} coreStart={coreStart} depsStart={depsStart} />,
|
|
||||||
element
|
|
||||||
);
|
|
||||||
return () => {
|
|
||||||
ReactDOM.unmountComponentAtNode(element);
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -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 { HostAction } from './hosts';
|
|
||||||
import { AlertAction } from './alerts';
|
|
||||||
import { RoutingAction } from './routing';
|
|
||||||
import { PolicyListAction } from './policy_list';
|
|
||||||
import { PolicyDetailsAction } from './policy_details';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The entire set of redux actions recognized by our reducer.
|
|
||||||
*/
|
|
||||||
export type AppAction =
|
|
||||||
| HostAction
|
|
||||||
| AlertAction
|
|
||||||
| RoutingAction
|
|
||||||
| PolicyListAction
|
|
||||||
| PolicyDetailsAction;
|
|
|
@ -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 { alertListReducer } from './reducer';
|
|
||||||
export { AlertAction } from './action';
|
|
|
@ -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 { hostListReducer } from './reducer';
|
|
||||||
export { HostAction } from './action';
|
|
||||||
export { hostMiddlewareFactory } from './middleware';
|
|
|
@ -1,13 +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 { combineReducers } from 'redux';
|
|
||||||
import { ImmutableCombineReducers } from '../types';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Works the same as `combineReducers` from `redux`, but uses the `ImmutableCombineReducers` type.
|
|
||||||
*/
|
|
||||||
export const immutableCombineReducers: ImmutableCombineReducers = combineReducers;
|
|
|
@ -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 { createStore, compose, applyMiddleware, Store } from 'redux';
|
|
||||||
import { CoreStart } from 'kibana/public';
|
|
||||||
import { appReducer } from './reducer';
|
|
||||||
import { alertMiddlewareFactory } from './alerts/middleware';
|
|
||||||
import { hostMiddlewareFactory } from './hosts';
|
|
||||||
import { policyListMiddlewareFactory } from './policy_list';
|
|
||||||
import { policyDetailsMiddlewareFactory } from './policy_details';
|
|
||||||
import { ImmutableMiddlewareFactory, SubstateMiddlewareFactory } from '../types';
|
|
||||||
import { EndpointPluginStartDependencies } from '../../../plugin';
|
|
||||||
|
|
||||||
const composeWithReduxDevTools = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
|
|
||||||
? (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ name: 'EndpointApp' })
|
|
||||||
: compose;
|
|
||||||
|
|
||||||
export const substateMiddlewareFactory: SubstateMiddlewareFactory = (selector, middleware) => {
|
|
||||||
return api => {
|
|
||||||
const substateAPI = {
|
|
||||||
...api,
|
|
||||||
// Return just the substate instead of global state.
|
|
||||||
getState() {
|
|
||||||
return selector(api.getState());
|
|
||||||
},
|
|
||||||
};
|
|
||||||
return middleware(substateAPI);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param middlewareDeps Optionally create the store without any middleware. This is useful for testing the store w/o side effects.
|
|
||||||
*/
|
|
||||||
export const appStoreFactory: (middlewareDeps?: {
|
|
||||||
/**
|
|
||||||
* Allow middleware to communicate with Kibana core.
|
|
||||||
*/
|
|
||||||
coreStart: CoreStart;
|
|
||||||
/**
|
|
||||||
* Give middleware access to plugin start dependencies.
|
|
||||||
*/
|
|
||||||
depsStart: EndpointPluginStartDependencies;
|
|
||||||
/**
|
|
||||||
* Any additional Redux Middlewares
|
|
||||||
* (should only be used for testing - example: to inject the action spy middleware)
|
|
||||||
*/
|
|
||||||
additionalMiddleware?: Array<ReturnType<ImmutableMiddlewareFactory>>;
|
|
||||||
}) => Store = middlewareDeps => {
|
|
||||||
let middleware;
|
|
||||||
if (middlewareDeps) {
|
|
||||||
const { coreStart, depsStart, additionalMiddleware = [] } = middlewareDeps;
|
|
||||||
middleware = composeWithReduxDevTools(
|
|
||||||
applyMiddleware(
|
|
||||||
substateMiddlewareFactory(
|
|
||||||
globalState => globalState.hostList,
|
|
||||||
hostMiddlewareFactory(coreStart, depsStart)
|
|
||||||
),
|
|
||||||
substateMiddlewareFactory(
|
|
||||||
globalState => globalState.policyList,
|
|
||||||
policyListMiddlewareFactory(coreStart, depsStart)
|
|
||||||
),
|
|
||||||
substateMiddlewareFactory(
|
|
||||||
globalState => globalState.policyDetails,
|
|
||||||
policyDetailsMiddlewareFactory(coreStart, depsStart)
|
|
||||||
),
|
|
||||||
substateMiddlewareFactory(
|
|
||||||
globalState => globalState.alertList,
|
|
||||||
alertMiddlewareFactory(coreStart, depsStart)
|
|
||||||
),
|
|
||||||
// Additional Middleware should go last
|
|
||||||
...additionalMiddleware
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// Create the store without any middleware. This is useful for testing the store w/o side effects.
|
|
||||||
middleware = undefined;
|
|
||||||
}
|
|
||||||
const store = createStore(appReducer, middleware);
|
|
||||||
|
|
||||||
return store;
|
|
||||||
};
|
|
|
@ -1,20 +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 { hostListReducer } from './hosts';
|
|
||||||
import { AppAction } from './action';
|
|
||||||
import { alertListReducer } from './alerts';
|
|
||||||
import { GlobalState, ImmutableReducer } from '../types';
|
|
||||||
import { policyListReducer } from './policy_list';
|
|
||||||
import { policyDetailsReducer } from './policy_details';
|
|
||||||
import { immutableCombineReducers } from './immutable_combine_reducers';
|
|
||||||
|
|
||||||
export const appReducer: ImmutableReducer<GlobalState, AppAction> = immutableCombineReducers({
|
|
||||||
hostList: hostListReducer,
|
|
||||||
alertList: alertListReducer,
|
|
||||||
policyList: policyListReducer,
|
|
||||||
policyDetails: policyDetailsReducer,
|
|
||||||
});
|
|
|
@ -1,395 +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 {
|
|
||||||
Dispatch,
|
|
||||||
Action as ReduxAction,
|
|
||||||
AnyAction as ReduxAnyAction,
|
|
||||||
Action,
|
|
||||||
Middleware,
|
|
||||||
} from 'redux';
|
|
||||||
import { IIndexPattern } from 'src/plugins/data/public';
|
|
||||||
import {
|
|
||||||
HostMetadata,
|
|
||||||
AlertData,
|
|
||||||
AlertResultList,
|
|
||||||
Immutable,
|
|
||||||
AlertDetails,
|
|
||||||
MalwareFields,
|
|
||||||
UIPolicyConfig,
|
|
||||||
PolicyData,
|
|
||||||
HostPolicyResponse,
|
|
||||||
HostInfo,
|
|
||||||
} from '../../../common/types';
|
|
||||||
import { EndpointPluginStartDependencies } from '../../plugin';
|
|
||||||
import { AppAction } from './store/action';
|
|
||||||
import { CoreStart } from '../../../../../../src/core/public';
|
|
||||||
import {
|
|
||||||
GetAgentStatusResponse,
|
|
||||||
GetDatasourcesResponse,
|
|
||||||
GetOneDatasourceResponse,
|
|
||||||
UpdateDatasourceResponse,
|
|
||||||
} from '../../../../ingest_manager/common';
|
|
||||||
|
|
||||||
export { AppAction };
|
|
||||||
|
|
||||||
/**
|
|
||||||
* like redux's `MiddlewareAPI` but `getState` returns an `Immutable` version of
|
|
||||||
* state and `dispatch` accepts `Immutable` versions of actions.
|
|
||||||
*/
|
|
||||||
export interface ImmutableMiddlewareAPI<S, A extends Action> {
|
|
||||||
dispatch: Dispatch<A | Immutable<A>>;
|
|
||||||
getState(): Immutable<S>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Like redux's `Middleware` but without the ability to mutate actions or state.
|
|
||||||
* Differences:
|
|
||||||
* * `getState` returns an `Immutable` version of state
|
|
||||||
* * `dispatch` accepts `Immutable` versions of actions
|
|
||||||
* * `action`s received will be `Immutable`
|
|
||||||
*/
|
|
||||||
export type ImmutableMiddleware<S, A extends Action> = (
|
|
||||||
api: ImmutableMiddlewareAPI<S, A>
|
|
||||||
) => (next: Dispatch<A | Immutable<A>>) => (action: Immutable<A>) => unknown;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Takes application-standard middleware dependencies
|
|
||||||
* and returns a redux middleware.
|
|
||||||
* Middleware will be of the `ImmutableMiddleware` variety. Not able to directly
|
|
||||||
* change actions or state.
|
|
||||||
*/
|
|
||||||
export type ImmutableMiddlewareFactory<S = GlobalState> = (
|
|
||||||
coreStart: CoreStart,
|
|
||||||
depsStart: EndpointPluginStartDependencies
|
|
||||||
) => ImmutableMiddleware<S, AppAction>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simple type for a redux selector.
|
|
||||||
*/
|
|
||||||
type Selector<S, R> = (state: S) => R;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Takes a selector and an `ImmutableMiddleware`. The
|
|
||||||
* middleware's version of `getState` will receive
|
|
||||||
* the result of the selector instead of the global state.
|
|
||||||
*
|
|
||||||
* This allows middleware to have knowledge of only a subsection of state.
|
|
||||||
*
|
|
||||||
* `selector` returns an `Immutable` version of the substate.
|
|
||||||
* `middleware` must be an `ImmutableMiddleware`.
|
|
||||||
*
|
|
||||||
* Returns a regular middleware, meant to be used with `applyMiddleware`.
|
|
||||||
*/
|
|
||||||
export type SubstateMiddlewareFactory = <Substate>(
|
|
||||||
selector: Selector<GlobalState, Immutable<Substate>>,
|
|
||||||
middleware: ImmutableMiddleware<Substate, AppAction>
|
|
||||||
) => Middleware<{}, GlobalState, Dispatch<AppAction | Immutable<AppAction>>>;
|
|
||||||
|
|
||||||
export interface HostState {
|
|
||||||
/** list of host **/
|
|
||||||
hosts: HostInfo[];
|
|
||||||
/** number of items per page */
|
|
||||||
pageSize: number;
|
|
||||||
/** which page to show */
|
|
||||||
pageIndex: number;
|
|
||||||
/** total number of hosts returned */
|
|
||||||
total: number;
|
|
||||||
/** list page is retrieving data */
|
|
||||||
loading: boolean;
|
|
||||||
/** api error from retrieving host list */
|
|
||||||
error?: ServerApiError;
|
|
||||||
/** details data for a specific host */
|
|
||||||
details?: Immutable<HostMetadata>;
|
|
||||||
/** details page is retrieving data */
|
|
||||||
detailsLoading: boolean;
|
|
||||||
/** api error from retrieving host details */
|
|
||||||
detailsError?: ServerApiError;
|
|
||||||
/** Holds the Policy Response for the Host currently being displayed in the details */
|
|
||||||
policyResponse?: HostPolicyResponse;
|
|
||||||
/** policyResponse is being retrieved */
|
|
||||||
policyResponseLoading: boolean;
|
|
||||||
/** api error from retrieving the policy response */
|
|
||||||
policyResponseError?: ServerApiError;
|
|
||||||
/** current location info */
|
|
||||||
location?: Immutable<EndpointAppLocation>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Query params on the host page parsed from the URL
|
|
||||||
*/
|
|
||||||
export interface HostIndexUIQueryParams {
|
|
||||||
/** Selected host id shows host details flyout */
|
|
||||||
selected_host?: string;
|
|
||||||
/** How many items to show in list */
|
|
||||||
page_size?: string;
|
|
||||||
/** Which page to show */
|
|
||||||
page_index?: string;
|
|
||||||
/** show the policy response or host details */
|
|
||||||
show?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ServerApiError {
|
|
||||||
statusCode: number;
|
|
||||||
error: string;
|
|
||||||
message: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Policy list store state
|
|
||||||
*/
|
|
||||||
export interface PolicyListState {
|
|
||||||
/** Array of policy items */
|
|
||||||
policyItems: PolicyData[];
|
|
||||||
/** API error if loading data failed */
|
|
||||||
apiError?: ServerApiError;
|
|
||||||
/** total number of policies */
|
|
||||||
total: number;
|
|
||||||
/** Number of policies per page */
|
|
||||||
pageSize: number;
|
|
||||||
/** page number (zero based) */
|
|
||||||
pageIndex: number;
|
|
||||||
/** data is being retrieved from server */
|
|
||||||
isLoading: boolean;
|
|
||||||
/** current location information */
|
|
||||||
location?: Immutable<EndpointAppLocation>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Policy details store state
|
|
||||||
*/
|
|
||||||
export interface PolicyDetailsState {
|
|
||||||
/** A single policy item */
|
|
||||||
policyItem?: PolicyData;
|
|
||||||
/** API error if loading data failed */
|
|
||||||
apiError?: ServerApiError;
|
|
||||||
isLoading: boolean;
|
|
||||||
/** current location of the application */
|
|
||||||
location?: Immutable<EndpointAppLocation>;
|
|
||||||
/** A summary of stats for the agents associated with a given Fleet Agent Configuration */
|
|
||||||
agentStatusSummary: GetAgentStatusResponse['results'];
|
|
||||||
/** Status of an update to the policy */
|
|
||||||
updateStatus?: {
|
|
||||||
success: boolean;
|
|
||||||
error?: ServerApiError;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The URL search params that are supported by the Policy List page view
|
|
||||||
*/
|
|
||||||
export interface PolicyListUrlSearchParams {
|
|
||||||
page_index: number;
|
|
||||||
page_size: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Endpoint Policy configuration
|
|
||||||
*/
|
|
||||||
export interface PolicyConfig {
|
|
||||||
windows: {
|
|
||||||
events: {
|
|
||||||
dll_and_driver_load: boolean;
|
|
||||||
dns: boolean;
|
|
||||||
file: boolean;
|
|
||||||
network: boolean;
|
|
||||||
process: boolean;
|
|
||||||
registry: boolean;
|
|
||||||
security: boolean;
|
|
||||||
};
|
|
||||||
malware: MalwareFields;
|
|
||||||
logging: {
|
|
||||||
stdout: string;
|
|
||||||
file: string;
|
|
||||||
};
|
|
||||||
advanced: PolicyConfigAdvancedOptions;
|
|
||||||
};
|
|
||||||
mac: {
|
|
||||||
events: {
|
|
||||||
file: boolean;
|
|
||||||
process: boolean;
|
|
||||||
network: boolean;
|
|
||||||
};
|
|
||||||
malware: MalwareFields;
|
|
||||||
logging: {
|
|
||||||
stdout: string;
|
|
||||||
file: string;
|
|
||||||
};
|
|
||||||
advanced: PolicyConfigAdvancedOptions;
|
|
||||||
};
|
|
||||||
linux: {
|
|
||||||
events: {
|
|
||||||
file: boolean;
|
|
||||||
process: boolean;
|
|
||||||
network: boolean;
|
|
||||||
};
|
|
||||||
logging: {
|
|
||||||
stdout: string;
|
|
||||||
file: string;
|
|
||||||
};
|
|
||||||
advanced: PolicyConfigAdvancedOptions;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
interface PolicyConfigAdvancedOptions {
|
|
||||||
elasticsearch: {
|
|
||||||
indices: {
|
|
||||||
control: string;
|
|
||||||
event: string;
|
|
||||||
logging: string;
|
|
||||||
};
|
|
||||||
kernel: {
|
|
||||||
connect: boolean;
|
|
||||||
process: boolean;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/** OS used in Policy */
|
|
||||||
export enum OS {
|
|
||||||
windows = 'windows',
|
|
||||||
mac = 'mac',
|
|
||||||
linux = 'linux',
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the keys of an object whose values meet a criteria.
|
|
||||||
* Ex) interface largeNestedObject = {
|
|
||||||
* a: {
|
|
||||||
* food: Foods;
|
|
||||||
* toiletPaper: true;
|
|
||||||
* };
|
|
||||||
* b: {
|
|
||||||
* food: Foods;
|
|
||||||
* streamingServices: Streams;
|
|
||||||
* };
|
|
||||||
* c: {};
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* type hasFoods = KeysByValueCriteria<largeNestedObject, { food: Foods }>;
|
|
||||||
* The above type will be: [a, b] only, and will not include c.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
export type KeysByValueCriteria<O, Criteria> = {
|
|
||||||
[K in keyof O]: O[K] extends Criteria ? K : never;
|
|
||||||
}[keyof O];
|
|
||||||
|
|
||||||
/** Returns an array of the policy OSes that have a malware protection field */
|
|
||||||
export type MalwareProtectionOSes = KeysByValueCriteria<UIPolicyConfig, { malware: MalwareFields }>;
|
|
||||||
|
|
||||||
export interface GlobalState {
|
|
||||||
readonly hostList: HostState;
|
|
||||||
readonly alertList: AlertListState;
|
|
||||||
readonly policyList: PolicyListState;
|
|
||||||
readonly policyDetails: PolicyDetailsState;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A better type for createStructuredSelector. This doesn't support the options object.
|
|
||||||
*/
|
|
||||||
export type CreateStructuredSelector = <
|
|
||||||
SelectorMap extends { [key: string]: (...args: never[]) => unknown }
|
|
||||||
>(
|
|
||||||
selectorMap: SelectorMap
|
|
||||||
) => (
|
|
||||||
state: SelectorMap[keyof SelectorMap] extends (state: infer State) => unknown ? State : never
|
|
||||||
) => {
|
|
||||||
[Key in keyof SelectorMap]: ReturnType<SelectorMap[Key]>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface EndpointAppLocation {
|
|
||||||
pathname: string;
|
|
||||||
search: string;
|
|
||||||
hash: string;
|
|
||||||
key?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface AlertsSearchBarState {
|
|
||||||
patterns: IIndexPattern[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AlertListData = AlertResultList;
|
|
||||||
|
|
||||||
export interface AlertListState {
|
|
||||||
/** Array of alert items. */
|
|
||||||
readonly alerts: Immutable<AlertData[]>;
|
|
||||||
|
|
||||||
/** The total number of alerts on the page. */
|
|
||||||
readonly total: number;
|
|
||||||
|
|
||||||
/** Number of alerts per page. */
|
|
||||||
readonly pageSize: number;
|
|
||||||
|
|
||||||
/** Page number, starting at 0. */
|
|
||||||
readonly pageIndex: number;
|
|
||||||
|
|
||||||
/** Current location object from React Router history. */
|
|
||||||
readonly location?: Immutable<EndpointAppLocation>;
|
|
||||||
|
|
||||||
/** Specific Alert data to be shown in the details view */
|
|
||||||
readonly alertDetails?: Immutable<AlertDetails>;
|
|
||||||
|
|
||||||
/** Search bar state including indexPatterns */
|
|
||||||
readonly searchBar: AlertsSearchBarState;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gotten by parsing the URL from the browser. Used to calculate the new URL when changing views.
|
|
||||||
*/
|
|
||||||
export interface AlertingIndexUIQueryParams {
|
|
||||||
/**
|
|
||||||
* How many items to show in list.
|
|
||||||
*/
|
|
||||||
page_size?: string;
|
|
||||||
/**
|
|
||||||
* Which page to show. If `page_index` is 1, show page 2.
|
|
||||||
*/
|
|
||||||
page_index?: string;
|
|
||||||
/**
|
|
||||||
* If any value is present, show the alert detail view for the selected alert. Should be an ID for an alert event.
|
|
||||||
*/
|
|
||||||
selected_alert?: string;
|
|
||||||
query?: string;
|
|
||||||
date_range?: string;
|
|
||||||
filters?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GetPolicyListResponse extends GetDatasourcesResponse {
|
|
||||||
items: PolicyData[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GetPolicyResponse extends GetOneDatasourceResponse {
|
|
||||||
item: PolicyData;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UpdatePolicyResponse extends UpdateDatasourceResponse {
|
|
||||||
item: PolicyData;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Like `Reducer` from `redux` but it accepts immutable versions of `state` and `action`.
|
|
||||||
* Use this type for all Reducers in order to help enforce our pattern of immutable state.
|
|
||||||
*/
|
|
||||||
export type ImmutableReducer<State, Action> = (
|
|
||||||
state: Immutable<State> | undefined,
|
|
||||||
action: Immutable<Action>
|
|
||||||
) => State | Immutable<State>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A alternate interface for `redux`'s `combineReducers`. Will work with the same underlying implementation,
|
|
||||||
* but will enforce that `Immutable` versions of `state` and `action` are received.
|
|
||||||
*/
|
|
||||||
export type ImmutableCombineReducers = <S, A extends ReduxAction = ReduxAnyAction>(
|
|
||||||
reducers: ImmutableReducersMapObject<S, A>
|
|
||||||
) => ImmutableReducer<S, A>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Like `redux`'s `ReducersMapObject` (which is used by `combineReducers`) but enforces that
|
|
||||||
* the `state` and `action` received are `Immutable` versions.
|
|
||||||
*/
|
|
||||||
type ImmutableReducersMapObject<S, A extends ReduxAction = ReduxAction> = {
|
|
||||||
[K in keyof S]: ImmutableReducer<S[K], A>;
|
|
||||||
};
|
|
|
@ -1,123 +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 React, { memo, useMemo } from 'react';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import { i18n } from '@kbn/i18n';
|
|
||||||
import { FormattedMessage } from '@kbn/i18n/react';
|
|
||||||
import {
|
|
||||||
EuiSpacer,
|
|
||||||
EuiTitle,
|
|
||||||
EuiText,
|
|
||||||
EuiHealth,
|
|
||||||
EuiTabbedContent,
|
|
||||||
EuiTabbedContentTab,
|
|
||||||
} from '@elastic/eui';
|
|
||||||
import { useAlertListSelector } from '../../hooks/use_alerts_selector';
|
|
||||||
import * as selectors from '../../../../store/alerts/selectors';
|
|
||||||
import { MetadataPanel } from './metadata_panel';
|
|
||||||
import { FormattedDate } from '../../formatted_date';
|
|
||||||
import { AlertDetailResolver } from '../../resolver';
|
|
||||||
import { ResolverEvent } from '../../../../../../../common/types';
|
|
||||||
import { TakeActionDropdown } from './take_action_dropdown';
|
|
||||||
|
|
||||||
export const AlertDetailsOverview = styled(
|
|
||||||
memo(() => {
|
|
||||||
const alertDetailsData = useAlertListSelector(selectors.selectedAlertDetailsData);
|
|
||||||
if (alertDetailsData === undefined) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const tabs: EuiTabbedContentTab[] = useMemo(() => {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
id: 'overviewMetadata',
|
|
||||||
'data-test-subj': 'overviewMetadata',
|
|
||||||
name: i18n.translate(
|
|
||||||
'xpack.endpoint.application.endpoint.alertDetails.overview.tabs.overview',
|
|
||||||
{
|
|
||||||
defaultMessage: 'Overview',
|
|
||||||
}
|
|
||||||
),
|
|
||||||
content: (
|
|
||||||
<>
|
|
||||||
<EuiSpacer />
|
|
||||||
<MetadataPanel />
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'overviewResolver',
|
|
||||||
'data-test-subj': 'overviewResolverTab',
|
|
||||||
name: i18n.translate(
|
|
||||||
'xpack.endpoint.application.endpoint.alertDetails.overview.tabs.resolver',
|
|
||||||
{
|
|
||||||
defaultMessage: 'Resolver',
|
|
||||||
}
|
|
||||||
),
|
|
||||||
content: (
|
|
||||||
<>
|
|
||||||
<EuiSpacer />
|
|
||||||
<AlertDetailResolver selectedEvent={(alertDetailsData as unknown) as ResolverEvent} />
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}, [alertDetailsData]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<section className="details-overview-summary">
|
|
||||||
<EuiTitle size="s">
|
|
||||||
<h3>
|
|
||||||
<FormattedMessage
|
|
||||||
id="xpack.endpoint.application.endpoint.alertDetails.overview.title"
|
|
||||||
defaultMessage="Detected Malicious File"
|
|
||||||
/>
|
|
||||||
</h3>
|
|
||||||
</EuiTitle>
|
|
||||||
<EuiSpacer />
|
|
||||||
<EuiText>
|
|
||||||
<p>
|
|
||||||
<FormattedMessage
|
|
||||||
id="xpack.endpoint.application.endpoint.alertDetails.overview.summary"
|
|
||||||
defaultMessage="MalwareScore detected the opening of a document on {hostname} on {date}"
|
|
||||||
values={{
|
|
||||||
hostname: alertDetailsData.host.hostname,
|
|
||||||
date: <FormattedDate timestamp={alertDetailsData['@timestamp']} />,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</EuiText>
|
|
||||||
<EuiSpacer />
|
|
||||||
<EuiText>
|
|
||||||
Endpoint Status:{' '}
|
|
||||||
<EuiHealth color="success">
|
|
||||||
{' '}
|
|
||||||
<FormattedMessage
|
|
||||||
id="xpack.endpoint.application.endpoint.alertDetails.endpoint.status.online"
|
|
||||||
defaultMessage="Online"
|
|
||||||
/>
|
|
||||||
</EuiHealth>
|
|
||||||
</EuiText>
|
|
||||||
<EuiText>
|
|
||||||
{' '}
|
|
||||||
<FormattedMessage
|
|
||||||
id="xpack.endpoint.application.endpoint.alertDetails.alert.status.open"
|
|
||||||
defaultMessage="Alert Status: Open"
|
|
||||||
/>
|
|
||||||
</EuiText>
|
|
||||||
<EuiSpacer />
|
|
||||||
<TakeActionDropdown />
|
|
||||||
<EuiSpacer />
|
|
||||||
</section>
|
|
||||||
<EuiTabbedContent tabs={tabs} initialSelectedTab={tabs[0]} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
)`
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
`;
|
|
|
@ -1,12 +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 { useSelector } from 'react-redux';
|
|
||||||
import { GlobalState, AlertListState } from '../../../types';
|
|
||||||
|
|
||||||
export function useAlertListSelector<TSelected>(selector: (state: AlertListState) => TSelected) {
|
|
||||||
return useSelector((state: GlobalState) => selector(state.alertList));
|
|
||||||
}
|
|
|
@ -1,38 +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 React from 'react';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import { Provider } from 'react-redux';
|
|
||||||
import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public';
|
|
||||||
import { Resolver } from '../../../../embeddables/resolver/view';
|
|
||||||
import { EndpointPluginServices } from '../../../../plugin';
|
|
||||||
import { ResolverEvent } from '../../../../../common/types';
|
|
||||||
import { storeFactory } from '../../../../embeddables/resolver/store';
|
|
||||||
|
|
||||||
export const AlertDetailResolver = styled(
|
|
||||||
React.memo(
|
|
||||||
({ className, selectedEvent }: { className?: string; selectedEvent?: ResolverEvent }) => {
|
|
||||||
const context = useKibana<EndpointPluginServices>();
|
|
||||||
const { store } = storeFactory(context);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={className} data-test-subj="alertResolver">
|
|
||||||
<Provider store={store}>
|
|
||||||
<Resolver selectedEvent={selectedEvent} />
|
|
||||||
</Provider>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)`
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-grow: 1;
|
|
||||||
/* gross demo hack */
|
|
||||||
min-height: calc(100vh - 505px);
|
|
||||||
`;
|
|
|
@ -1,59 +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 React from 'react';
|
|
||||||
import * as reactTestingLibrary from '@testing-library/react';
|
|
||||||
import { Provider } from 'react-redux';
|
|
||||||
import { I18nProvider } from '@kbn/i18n/react';
|
|
||||||
import { createMemoryHistory } from 'history';
|
|
||||||
import { Router } from 'react-router-dom';
|
|
||||||
import { AlertIndex } from '../index';
|
|
||||||
import { appStoreFactory } from '../../../store';
|
|
||||||
import { KibanaContextProvider } from '../../../../../../../../../src/plugins/kibana_react/public';
|
|
||||||
import { RouteCapture } from '../../route_capture';
|
|
||||||
import { depsStartMock } from '../../../mocks';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a 'history' instance that is only in-memory and causes no side effects to the testing environment.
|
|
||||||
*/
|
|
||||||
const history = createMemoryHistory<never>();
|
|
||||||
/**
|
|
||||||
* Create a store, with the middleware disabled. We don't want side effects being created by our code in this test.
|
|
||||||
*/
|
|
||||||
const store = appStoreFactory();
|
|
||||||
|
|
||||||
const depsStart = depsStartMock();
|
|
||||||
depsStart.data.ui.SearchBar.mockImplementation(() => <div />);
|
|
||||||
|
|
||||||
export const alertPageTestRender = {
|
|
||||||
store,
|
|
||||||
history,
|
|
||||||
depsStart,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render the test component, use this after setting up anything in `beforeEach`.
|
|
||||||
*/
|
|
||||||
render: () => {
|
|
||||||
/**
|
|
||||||
* Provide the store via `Provider`, and i18n APIs via `I18nProvider`.
|
|
||||||
* Use react-router via `Router`, passing our in-memory `history` instance.
|
|
||||||
* Use `RouteCapture` to emit url-change actions when the URL is changed.
|
|
||||||
* Finally, render the `AlertIndex` component which we are testing.
|
|
||||||
*/
|
|
||||||
return reactTestingLibrary.render(
|
|
||||||
<Provider store={store}>
|
|
||||||
<KibanaContextProvider services={{ data: depsStart.data }}>
|
|
||||||
<I18nProvider>
|
|
||||||
<Router history={history}>
|
|
||||||
<RouteCapture>
|
|
||||||
<AlertIndex />
|
|
||||||
</RouteCapture>
|
|
||||||
</Router>
|
|
||||||
</I18nProvider>
|
|
||||||
</KibanaContextProvider>
|
|
||||||
</Provider>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -1,59 +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 * as React from 'react';
|
|
||||||
import { FormattedMessage } from '@kbn/i18n/react';
|
|
||||||
import { Route, Switch } from 'react-router-dom';
|
|
||||||
import { Store } from 'redux';
|
|
||||||
import { AlertIndex } from './alerts';
|
|
||||||
import { HostList } from './hosts';
|
|
||||||
import { PolicyList } from './policy';
|
|
||||||
import { PolicyDetails } from './policy';
|
|
||||||
import { HeaderNavigation } from './components/header_navigation';
|
|
||||||
import { AppRootProvider } from './app_root_provider';
|
|
||||||
import { Setup } from './setup';
|
|
||||||
import { EndpointPluginStartDependencies } from '../../../plugin';
|
|
||||||
import { ScopedHistory, CoreStart } from '../../../../../../../src/core/public';
|
|
||||||
|
|
||||||
interface RouterProps {
|
|
||||||
history: ScopedHistory;
|
|
||||||
store: Store;
|
|
||||||
coreStart: CoreStart;
|
|
||||||
depsStart: EndpointPluginStartDependencies;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The root of the Endpoint application react view.
|
|
||||||
*/
|
|
||||||
export const AppRoot: React.FunctionComponent<RouterProps> = React.memo(
|
|
||||||
({ history, store, coreStart, depsStart }) => {
|
|
||||||
return (
|
|
||||||
<AppRootProvider store={store} history={history} coreStart={coreStart} depsStart={depsStart}>
|
|
||||||
<Setup ingestManager={depsStart.ingestManager} notifications={coreStart.notifications} />
|
|
||||||
<HeaderNavigation />
|
|
||||||
<Switch>
|
|
||||||
<Route
|
|
||||||
exact
|
|
||||||
path="/"
|
|
||||||
render={() => (
|
|
||||||
<h1 data-test-subj="welcomeTitle">
|
|
||||||
<FormattedMessage id="xpack.endpoint.welcomeTitle" defaultMessage="Hello World" />
|
|
||||||
</h1>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Route path="/hosts" component={HostList} />
|
|
||||||
<Route path="/alerts" component={AlertIndex} />
|
|
||||||
<Route path="/policy" exact component={PolicyList} />
|
|
||||||
<Route path="/policy/:id" exact component={PolicyDetails} />
|
|
||||||
<Route
|
|
||||||
render={() => (
|
|
||||||
<FormattedMessage id="xpack.endpoint.notFound" defaultMessage="Page Not Found" />
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</Switch>
|
|
||||||
</AppRootProvider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
|
@ -1,78 +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 React, { memo, useMemo } from 'react';
|
|
||||||
import { i18n } from '@kbn/i18n';
|
|
||||||
import { EuiTabs, EuiTab } from '@elastic/eui';
|
|
||||||
import { useLocation } from 'react-router-dom';
|
|
||||||
import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public';
|
|
||||||
import { Immutable } from '../../../../../common/types';
|
|
||||||
import { useNavigateByRouterEventHandler } from '../hooks/use_navigate_by_router_event_handler';
|
|
||||||
|
|
||||||
interface NavTabs {
|
|
||||||
name: string;
|
|
||||||
id: string;
|
|
||||||
href: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const navTabs: Immutable<NavTabs[]> = [
|
|
||||||
{
|
|
||||||
id: 'home',
|
|
||||||
name: i18n.translate('xpack.endpoint.headerNav.home', {
|
|
||||||
defaultMessage: 'Home',
|
|
||||||
}),
|
|
||||||
href: '/',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'hosts',
|
|
||||||
name: i18n.translate('xpack.endpoint.headerNav.hosts', {
|
|
||||||
defaultMessage: 'Hosts',
|
|
||||||
}),
|
|
||||||
href: '/hosts',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'alerts',
|
|
||||||
name: i18n.translate('xpack.endpoint.headerNav.alerts', {
|
|
||||||
defaultMessage: 'Alerts',
|
|
||||||
}),
|
|
||||||
href: '/alerts',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'policies',
|
|
||||||
name: i18n.translate('xpack.endpoint.headerNav.policies', {
|
|
||||||
defaultMessage: 'Policies',
|
|
||||||
}),
|
|
||||||
href: '/policy',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const NavTab = memo<{ tab: NavTabs }>(({ tab }) => {
|
|
||||||
const { pathname } = useLocation();
|
|
||||||
const { services } = useKibana();
|
|
||||||
const onClickHandler = useNavigateByRouterEventHandler(tab.href);
|
|
||||||
const BASE_PATH = services.application.getUrlForApp('endpoint');
|
|
||||||
|
|
||||||
return (
|
|
||||||
<EuiTab
|
|
||||||
data-test-subj={`${tab.id}EndpointTab`}
|
|
||||||
href={`${BASE_PATH}${tab.href}`}
|
|
||||||
onClick={onClickHandler}
|
|
||||||
isSelected={tab.href === pathname || (tab.href !== '/' && pathname.startsWith(tab.href))}
|
|
||||||
>
|
|
||||||
{tab.name}
|
|
||||||
</EuiTab>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
export const HeaderNavigation: React.FunctionComponent = React.memo(() => {
|
|
||||||
const tabList = useMemo(() => {
|
|
||||||
return navTabs.map((tab, index) => {
|
|
||||||
return <NavTab tab={tab} key={index} />;
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return <EuiTabs>{tabList}</EuiTabs>;
|
|
||||||
});
|
|
|
@ -1,49 +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 { LegacyEndpointEvent } from '../../../../common/types';
|
|
||||||
|
|
||||||
type DeepPartial<T> = { [K in keyof T]?: DeepPartial<T[K]> };
|
|
||||||
/**
|
|
||||||
* Creates a mock process event given the 'parts' argument, which can
|
|
||||||
* include all or some process event fields as determined by the ProcessEvent type.
|
|
||||||
* The only field that must be provided is the event's 'node_id' field.
|
|
||||||
* The other fields are populated by the function unless provided in 'parts'
|
|
||||||
*/
|
|
||||||
export function mockProcessEvent(parts: {
|
|
||||||
endgame: {
|
|
||||||
unique_pid: LegacyEndpointEvent['endgame']['unique_pid'];
|
|
||||||
unique_ppid?: LegacyEndpointEvent['endgame']['unique_ppid'];
|
|
||||||
process_name?: LegacyEndpointEvent['endgame']['process_name'];
|
|
||||||
event_subtype_full?: LegacyEndpointEvent['endgame']['event_subtype_full'];
|
|
||||||
event_type_full?: LegacyEndpointEvent['endgame']['event_type_full'];
|
|
||||||
} & DeepPartial<LegacyEndpointEvent>;
|
|
||||||
}): LegacyEndpointEvent {
|
|
||||||
const { endgame: dataBuffer } = parts;
|
|
||||||
return {
|
|
||||||
endgame: {
|
|
||||||
...dataBuffer,
|
|
||||||
event_timestamp: 1,
|
|
||||||
event_type: 1,
|
|
||||||
unique_ppid: 0,
|
|
||||||
unique_pid: 1,
|
|
||||||
machine_id: '',
|
|
||||||
event_subtype_full: 'creation_event',
|
|
||||||
event_type_full: 'process_event',
|
|
||||||
process_name: '',
|
|
||||||
process_path: '',
|
|
||||||
timestamp_utc: '',
|
|
||||||
serial_event_id: 1,
|
|
||||||
},
|
|
||||||
'@timestamp': 1582233383000,
|
|
||||||
agent: {
|
|
||||||
type: '',
|
|
||||||
id: '',
|
|
||||||
version: '',
|
|
||||||
},
|
|
||||||
...parts,
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,78 +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 React from 'react';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import { applyMatrix3, distance, angle } from '../lib/vector2';
|
|
||||||
import { Vector2, Matrix3 } from '../types';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A placeholder line segment view that connects process nodes.
|
|
||||||
*/
|
|
||||||
export const EdgeLine = styled(
|
|
||||||
React.memo(
|
|
||||||
({
|
|
||||||
className,
|
|
||||||
startPosition,
|
|
||||||
endPosition,
|
|
||||||
projectionMatrix,
|
|
||||||
}: {
|
|
||||||
/**
|
|
||||||
* A className string provided by `styled`
|
|
||||||
*/
|
|
||||||
className?: string;
|
|
||||||
/**
|
|
||||||
* The postion of first point in the line segment. In 'world' coordinates.
|
|
||||||
*/
|
|
||||||
startPosition: Vector2;
|
|
||||||
/**
|
|
||||||
* The postion of second point in the line segment. In 'world' coordinates.
|
|
||||||
*/
|
|
||||||
endPosition: Vector2;
|
|
||||||
/**
|
|
||||||
* projectionMatrix which can be used to convert `startPosition` and `endPosition` to screen coordinates.
|
|
||||||
*/
|
|
||||||
projectionMatrix: Matrix3;
|
|
||||||
}) => {
|
|
||||||
/**
|
|
||||||
* Convert the start and end positions, which are in 'world' coordinates,
|
|
||||||
* to `left` and `top` css values.
|
|
||||||
*/
|
|
||||||
const screenStart = applyMatrix3(startPosition, projectionMatrix);
|
|
||||||
const screenEnd = applyMatrix3(endPosition, projectionMatrix);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* We render the line using a short, long, `div` element. The length of this `div`
|
|
||||||
* should be the same as the distance between the start and end points.
|
|
||||||
*/
|
|
||||||
const length = distance(screenStart, screenEnd);
|
|
||||||
|
|
||||||
const style = {
|
|
||||||
left: screenStart[0] + 'px',
|
|
||||||
top: screenStart[1] + 'px',
|
|
||||||
width: length + 'px',
|
|
||||||
/**
|
|
||||||
* Transform from the left of the div, as the left side of the `div` is positioned
|
|
||||||
* at the start point of the line segment.
|
|
||||||
*/
|
|
||||||
transformOrigin: 'top left',
|
|
||||||
/**
|
|
||||||
* Translate the `div` in the y axis to accomodate for the height of the `div`.
|
|
||||||
* Also rotate the `div` in the z axis so that it's angle matches the angle
|
|
||||||
* between the start and end points.
|
|
||||||
*/
|
|
||||||
transform: `translateY(-50%) rotateZ(${angle(screenStart, screenEnd)}rad)`,
|
|
||||||
};
|
|
||||||
return <div role="presentation" className={className} style={style} />;
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)`
|
|
||||||
position: absolute;
|
|
||||||
height: 3px;
|
|
||||||
background-color: #d4d4d4;
|
|
||||||
color: #333333;
|
|
||||||
contain: strict;
|
|
||||||
`;
|
|
|
@ -1,181 +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 React, { useCallback, useMemo, useContext } from 'react';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import { EuiRange, EuiPanel, EuiIcon } from '@elastic/eui';
|
|
||||||
import { useSelector, useDispatch } from 'react-redux';
|
|
||||||
import { SideEffectContext } from './side_effect_context';
|
|
||||||
import { ResolverAction, Vector2 } from '../types';
|
|
||||||
import * as selectors from '../store/selectors';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Controls for zooming, panning, and centering in Resolver
|
|
||||||
*/
|
|
||||||
export const GraphControls = styled(
|
|
||||||
React.memo(
|
|
||||||
({
|
|
||||||
className,
|
|
||||||
}: {
|
|
||||||
/**
|
|
||||||
* A className string provided by `styled`
|
|
||||||
*/
|
|
||||||
className?: string;
|
|
||||||
}) => {
|
|
||||||
const dispatch: (action: ResolverAction) => unknown = useDispatch();
|
|
||||||
const scalingFactor = useSelector(selectors.scalingFactor);
|
|
||||||
const { timestamp } = useContext(SideEffectContext);
|
|
||||||
|
|
||||||
const handleZoomAmountChange = useCallback(
|
|
||||||
(event: React.ChangeEvent<HTMLInputElement> | React.MouseEvent<HTMLButtonElement>) => {
|
|
||||||
const valueAsNumber = parseFloat(
|
|
||||||
(event as React.ChangeEvent<HTMLInputElement>).target.value
|
|
||||||
);
|
|
||||||
if (isNaN(valueAsNumber) === false) {
|
|
||||||
dispatch({
|
|
||||||
type: 'userSetZoomLevel',
|
|
||||||
payload: valueAsNumber,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[dispatch]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleCenterClick = useCallback(() => {
|
|
||||||
dispatch({
|
|
||||||
type: 'userSetPositionOfCamera',
|
|
||||||
payload: [0, 0],
|
|
||||||
});
|
|
||||||
}, [dispatch]);
|
|
||||||
|
|
||||||
const handleZoomOutClick = useCallback(() => {
|
|
||||||
dispatch({
|
|
||||||
type: 'userClickedZoomOut',
|
|
||||||
});
|
|
||||||
}, [dispatch]);
|
|
||||||
|
|
||||||
const handleZoomInClick = useCallback(() => {
|
|
||||||
dispatch({
|
|
||||||
type: 'userClickedZoomIn',
|
|
||||||
});
|
|
||||||
}, [dispatch]);
|
|
||||||
|
|
||||||
const [handleNorth, handleEast, handleSouth, handleWest] = useMemo(() => {
|
|
||||||
const directionVectors: readonly Vector2[] = [
|
|
||||||
[0, 1],
|
|
||||||
[1, 0],
|
|
||||||
[0, -1],
|
|
||||||
[-1, 0],
|
|
||||||
];
|
|
||||||
return directionVectors.map(direction => {
|
|
||||||
return () => {
|
|
||||||
const action: ResolverAction = {
|
|
||||||
type: 'userNudgedCamera',
|
|
||||||
payload: { direction, time: timestamp() },
|
|
||||||
};
|
|
||||||
dispatch(action);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}, [dispatch, timestamp]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={className}>
|
|
||||||
<EuiPanel className="panning-controls" paddingSize="none" hasShadow>
|
|
||||||
<div className="panning-controls-top">
|
|
||||||
<button
|
|
||||||
className="north-button"
|
|
||||||
data-test-subj="north-button"
|
|
||||||
title="North"
|
|
||||||
onClick={handleNorth}
|
|
||||||
>
|
|
||||||
<EuiIcon type="arrowUp" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div className="panning-controls-middle">
|
|
||||||
<button
|
|
||||||
className="west-button"
|
|
||||||
data-test-subj="west-button"
|
|
||||||
title="West"
|
|
||||||
onClick={handleWest}
|
|
||||||
>
|
|
||||||
<EuiIcon type="arrowLeft" />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className="center-button"
|
|
||||||
data-test-subj="center-button"
|
|
||||||
title="Center"
|
|
||||||
onClick={handleCenterClick}
|
|
||||||
>
|
|
||||||
<EuiIcon type="bullseye" />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className="east-button"
|
|
||||||
data-test-subj="east-button"
|
|
||||||
title="East"
|
|
||||||
onClick={handleEast}
|
|
||||||
>
|
|
||||||
<EuiIcon type="arrowRight" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div className="panning-controls-bottom">
|
|
||||||
<button
|
|
||||||
className="south-button"
|
|
||||||
data-test-subj="south-button"
|
|
||||||
title="South"
|
|
||||||
onClick={handleSouth}
|
|
||||||
>
|
|
||||||
<EuiIcon type="arrowDown" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</EuiPanel>
|
|
||||||
<EuiPanel className="zoom-controls" paddingSize="none" hasShadow>
|
|
||||||
<button title="Zoom In" data-test-subj="zoom-in" onClick={handleZoomInClick}>
|
|
||||||
<EuiIcon type="plusInCircle" />
|
|
||||||
</button>
|
|
||||||
<EuiRange
|
|
||||||
className="zoom-slider"
|
|
||||||
data-test-subj="zoom-slider"
|
|
||||||
min={0}
|
|
||||||
max={1}
|
|
||||||
step={0.01}
|
|
||||||
value={scalingFactor}
|
|
||||||
onChange={handleZoomAmountChange}
|
|
||||||
/>
|
|
||||||
<button title="Zoom Out" data-test-subj="zoom-out" onClick={handleZoomOutClick}>
|
|
||||||
<EuiIcon type="minusInCircle" />
|
|
||||||
</button>
|
|
||||||
</EuiPanel>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)`
|
|
||||||
background-color: #d4d4d4;
|
|
||||||
color: #333333;
|
|
||||||
|
|
||||||
.zoom-controls {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
padding: 5px 0px;
|
|
||||||
|
|
||||||
.zoom-slider {
|
|
||||||
width: 20px;
|
|
||||||
height: 150px;
|
|
||||||
margin: 5px 0px 2px 0px;
|
|
||||||
|
|
||||||
input[type='range'] {
|
|
||||||
width: 150px;
|
|
||||||
height: 20px;
|
|
||||||
transform-origin: 75px 75px;
|
|
||||||
transform: rotate(-90deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.panning-controls {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
`;
|
|
|
@ -1,605 +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 React, { useCallback, useMemo } from 'react';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import { i18n } from '@kbn/i18n';
|
|
||||||
import {
|
|
||||||
htmlIdGenerator,
|
|
||||||
EuiI18nNumber,
|
|
||||||
EuiKeyboardAccessible,
|
|
||||||
EuiFlexGroup,
|
|
||||||
EuiFlexItem,
|
|
||||||
} from '@elastic/eui';
|
|
||||||
import { useSelector } from 'react-redux';
|
|
||||||
import { NodeSubMenu, subMenuAssets } from './submenu';
|
|
||||||
import { applyMatrix3 } from '../lib/vector2';
|
|
||||||
import {
|
|
||||||
Vector2,
|
|
||||||
Matrix3,
|
|
||||||
AdjacentProcessMap,
|
|
||||||
ResolverProcessType,
|
|
||||||
RelatedEventEntryWithStatsOrWaiting,
|
|
||||||
} from '../types';
|
|
||||||
import { SymbolIds, NamedColors } from './defs';
|
|
||||||
import { ResolverEvent } from '../../../../common/types';
|
|
||||||
import { useResolverDispatch } from './use_resolver_dispatch';
|
|
||||||
import * as eventModel from '../../../../common/models/event';
|
|
||||||
import * as processModel from '../models/process_event';
|
|
||||||
import * as selectors from '../store/selectors';
|
|
||||||
|
|
||||||
const nodeAssets = {
|
|
||||||
runningProcessCube: {
|
|
||||||
cubeSymbol: `#${SymbolIds.runningProcessCube}`,
|
|
||||||
labelBackground: NamedColors.labelBackgroundRunningProcess,
|
|
||||||
descriptionFill: NamedColors.empty,
|
|
||||||
descriptionText: i18n.translate('xpack.endpoint.resolver.runningProcess', {
|
|
||||||
defaultMessage: 'Running Process',
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
runningTriggerCube: {
|
|
||||||
cubeSymbol: `#${SymbolIds.runningTriggerCube}`,
|
|
||||||
labelBackground: NamedColors.labelBackgroundRunningTrigger,
|
|
||||||
descriptionFill: NamedColors.empty,
|
|
||||||
descriptionText: i18n.translate('xpack.endpoint.resolver.runningTrigger', {
|
|
||||||
defaultMessage: 'Running Trigger',
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
terminatedProcessCube: {
|
|
||||||
cubeSymbol: `#${SymbolIds.terminatedProcessCube}`,
|
|
||||||
labelBackground: NamedColors.labelBackgroundTerminatedProcess,
|
|
||||||
descriptionFill: NamedColors.empty,
|
|
||||||
descriptionText: i18n.translate('xpack.endpoint.resolver.terminatedProcess', {
|
|
||||||
defaultMessage: 'Terminated Process',
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
terminatedTriggerCube: {
|
|
||||||
cubeSymbol: `#${SymbolIds.terminatedTriggerCube}`,
|
|
||||||
labelBackground: NamedColors.labelBackgroundTerminatedTrigger,
|
|
||||||
descriptionFill: NamedColors.empty,
|
|
||||||
descriptionText: i18n.translate('xpack.endpoint.resolver.terminatedTrigger', {
|
|
||||||
defaultMessage: 'Terminated Trigger',
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Take a gross `schemaName` and return a beautiful translated one.
|
|
||||||
*/
|
|
||||||
const getDisplayName: (schemaName: string) => string = function nameInSchemaToDisplayName(
|
|
||||||
schemaName: string
|
|
||||||
) {
|
|
||||||
const displayNameRecord: Record<string, string> = {
|
|
||||||
application: i18n.translate('xpack.endpoint.resolver.applicationEventTypeDisplayName', {
|
|
||||||
defaultMessage: 'Application',
|
|
||||||
}),
|
|
||||||
apm: i18n.translate('xpack.endpoint.resolver.apmEventTypeDisplayName', {
|
|
||||||
defaultMessage: 'APM',
|
|
||||||
}),
|
|
||||||
audit: i18n.translate('xpack.endpoint.resolver.auditEventTypeDisplayName', {
|
|
||||||
defaultMessage: 'Audit',
|
|
||||||
}),
|
|
||||||
authentication: i18n.translate('xpack.endpoint.resolver.authenticationEventTypeDisplayName', {
|
|
||||||
defaultMessage: 'Authentication',
|
|
||||||
}),
|
|
||||||
certificate: i18n.translate('xpack.endpoint.resolver.certificateEventTypeDisplayName', {
|
|
||||||
defaultMessage: 'Certificate',
|
|
||||||
}),
|
|
||||||
cloud: i18n.translate('xpack.endpoint.resolver.cloudEventTypeDisplayName', {
|
|
||||||
defaultMessage: 'Cloud',
|
|
||||||
}),
|
|
||||||
database: i18n.translate('xpack.endpoint.resolver.databaseEventTypeDisplayName', {
|
|
||||||
defaultMessage: 'Database',
|
|
||||||
}),
|
|
||||||
driver: i18n.translate('xpack.endpoint.resolver.driverEventTypeDisplayName', {
|
|
||||||
defaultMessage: 'Driver',
|
|
||||||
}),
|
|
||||||
email: i18n.translate('xpack.endpoint.resolver.emailEventTypeDisplayName', {
|
|
||||||
defaultMessage: 'Email',
|
|
||||||
}),
|
|
||||||
file: i18n.translate('xpack.endpoint.resolver.fileEventTypeDisplayName', {
|
|
||||||
defaultMessage: 'File',
|
|
||||||
}),
|
|
||||||
host: i18n.translate('xpack.endpoint.resolver.hostEventTypeDisplayName', {
|
|
||||||
defaultMessage: 'Host',
|
|
||||||
}),
|
|
||||||
iam: i18n.translate('xpack.endpoint.resolver.iamEventTypeDisplayName', {
|
|
||||||
defaultMessage: 'IAM',
|
|
||||||
}),
|
|
||||||
iam_group: i18n.translate('xpack.endpoint.resolver.iam_groupEventTypeDisplayName', {
|
|
||||||
defaultMessage: 'IAM Group',
|
|
||||||
}),
|
|
||||||
intrusion_detection: i18n.translate(
|
|
||||||
'xpack.endpoint.resolver.intrusion_detectionEventTypeDisplayName',
|
|
||||||
{
|
|
||||||
defaultMessage: 'Intrusion Detection',
|
|
||||||
}
|
|
||||||
),
|
|
||||||
malware: i18n.translate('xpack.endpoint.resolver.malwareEventTypeDisplayName', {
|
|
||||||
defaultMessage: 'Malware',
|
|
||||||
}),
|
|
||||||
network_flow: i18n.translate('xpack.endpoint.resolver.network_flowEventTypeDisplayName', {
|
|
||||||
defaultMessage: 'Network Flow',
|
|
||||||
}),
|
|
||||||
network: i18n.translate('xpack.endpoint.resolver.networkEventTypeDisplayName', {
|
|
||||||
defaultMessage: 'Network',
|
|
||||||
}),
|
|
||||||
package: i18n.translate('xpack.endpoint.resolver.packageEventTypeDisplayName', {
|
|
||||||
defaultMessage: 'Package',
|
|
||||||
}),
|
|
||||||
process: i18n.translate('xpack.endpoint.resolver.processEventTypeDisplayName', {
|
|
||||||
defaultMessage: 'Process',
|
|
||||||
}),
|
|
||||||
registry: i18n.translate('xpack.endpoint.resolver.registryEventTypeDisplayName', {
|
|
||||||
defaultMessage: 'Registry',
|
|
||||||
}),
|
|
||||||
session: i18n.translate('xpack.endpoint.resolver.sessionEventTypeDisplayName', {
|
|
||||||
defaultMessage: 'Session',
|
|
||||||
}),
|
|
||||||
service: i18n.translate('xpack.endpoint.resolver.serviceEventTypeDisplayName', {
|
|
||||||
defaultMessage: 'Service',
|
|
||||||
}),
|
|
||||||
socket: i18n.translate('xpack.endpoint.resolver.socketEventTypeDisplayName', {
|
|
||||||
defaultMessage: 'Socket',
|
|
||||||
}),
|
|
||||||
vulnerability: i18n.translate('xpack.endpoint.resolver.vulnerabilityEventTypeDisplayName', {
|
|
||||||
defaultMessage: 'Vulnerability',
|
|
||||||
}),
|
|
||||||
web: i18n.translate('xpack.endpoint.resolver.webEventTypeDisplayName', {
|
|
||||||
defaultMessage: 'Web',
|
|
||||||
}),
|
|
||||||
alert: i18n.translate('xpack.endpoint.resolver.alertEventTypeDisplayName', {
|
|
||||||
defaultMessage: 'Alert',
|
|
||||||
}),
|
|
||||||
security: i18n.translate('xpack.endpoint.resolver.securityEventTypeDisplayName', {
|
|
||||||
defaultMessage: 'Security',
|
|
||||||
}),
|
|
||||||
dns: i18n.translate('xpack.endpoint.resolver.dnsEventTypeDisplayName', {
|
|
||||||
defaultMessage: 'DNS',
|
|
||||||
}),
|
|
||||||
clr: i18n.translate('xpack.endpoint.resolver.clrEventTypeDisplayName', {
|
|
||||||
defaultMessage: 'CLR',
|
|
||||||
}),
|
|
||||||
image_load: i18n.translate('xpack.endpoint.resolver.image_loadEventTypeDisplayName', {
|
|
||||||
defaultMessage: 'Image Load',
|
|
||||||
}),
|
|
||||||
powershell: i18n.translate('xpack.endpoint.resolver.powershellEventTypeDisplayName', {
|
|
||||||
defaultMessage: 'Powershell',
|
|
||||||
}),
|
|
||||||
wmi: i18n.translate('xpack.endpoint.resolver.wmiEventTypeDisplayName', {
|
|
||||||
defaultMessage: 'WMI',
|
|
||||||
}),
|
|
||||||
api: i18n.translate('xpack.endpoint.resolver.apiEventTypeDisplayName', {
|
|
||||||
defaultMessage: 'API',
|
|
||||||
}),
|
|
||||||
user: i18n.translate('xpack.endpoint.resolver.userEventTypeDisplayName', {
|
|
||||||
defaultMessage: 'User',
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
displayNameRecord[schemaName] ||
|
|
||||||
i18n.translate('xpack.endpoint.resolver.userEventTypeDisplayUnknown', {
|
|
||||||
defaultMessage: 'Unknown',
|
|
||||||
})
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An artifact that represents a process node and the things associated with it in the Resolver
|
|
||||||
*/
|
|
||||||
export const ProcessEventDot = styled(
|
|
||||||
React.memo(
|
|
||||||
({
|
|
||||||
className,
|
|
||||||
position,
|
|
||||||
event,
|
|
||||||
projectionMatrix,
|
|
||||||
adjacentNodeMap,
|
|
||||||
relatedEvents,
|
|
||||||
}: {
|
|
||||||
/**
|
|
||||||
* A `className` string provided by `styled`
|
|
||||||
*/
|
|
||||||
className?: string;
|
|
||||||
/**
|
|
||||||
* The positon of the process node, in 'world' coordinates.
|
|
||||||
*/
|
|
||||||
position: Vector2;
|
|
||||||
/**
|
|
||||||
* An event which contains details about the process node.
|
|
||||||
*/
|
|
||||||
event: ResolverEvent;
|
|
||||||
/**
|
|
||||||
* projectionMatrix which can be used to convert `position` to screen coordinates.
|
|
||||||
*/
|
|
||||||
projectionMatrix: Matrix3;
|
|
||||||
/**
|
|
||||||
* map of what nodes are "adjacent" to this one in "up, down, previous, next" directions
|
|
||||||
*/
|
|
||||||
adjacentNodeMap: AdjacentProcessMap;
|
|
||||||
/**
|
|
||||||
* A collection of events related to the current node and statistics (e.g. counts indexed by event type)
|
|
||||||
* to provide the user some visibility regarding the contents thereof.
|
|
||||||
*/
|
|
||||||
relatedEvents?: RelatedEventEntryWithStatsOrWaiting;
|
|
||||||
}) => {
|
|
||||||
/**
|
|
||||||
* Convert the position, which is in 'world' coordinates, to screen coordinates.
|
|
||||||
*/
|
|
||||||
const [left, top] = applyMatrix3(position, projectionMatrix);
|
|
||||||
|
|
||||||
const [magFactorX] = projectionMatrix;
|
|
||||||
|
|
||||||
const selfId = adjacentNodeMap.self;
|
|
||||||
|
|
||||||
const activeDescendantId = useSelector(selectors.uiActiveDescendantId);
|
|
||||||
const selectedDescendantId = useSelector(selectors.uiSelectedDescendantId);
|
|
||||||
|
|
||||||
const logicalProcessNodeViewWidth = 360;
|
|
||||||
const logicalProcessNodeViewHeight = 120;
|
|
||||||
/**
|
|
||||||
* The `left` and `top` values represent the 'center' point of the process node.
|
|
||||||
* Since the view has content to the left and above the 'center' point, offset the
|
|
||||||
* position to accomodate for that. This aligns the logical center of the process node
|
|
||||||
* with the correct position on the map.
|
|
||||||
*/
|
|
||||||
const processNodeViewXOffset = -0.172413 * logicalProcessNodeViewWidth * magFactorX;
|
|
||||||
const processNodeViewYOffset = -0.73684 * logicalProcessNodeViewHeight * magFactorX;
|
|
||||||
|
|
||||||
const nodeViewportStyle = useMemo(
|
|
||||||
() => ({
|
|
||||||
left: `${left + processNodeViewXOffset}px`,
|
|
||||||
top: `${top + processNodeViewYOffset}px`,
|
|
||||||
// Width of symbol viewport scaled to fit
|
|
||||||
width: `${logicalProcessNodeViewWidth * magFactorX}px`,
|
|
||||||
// Height according to symbol viewbox AR
|
|
||||||
height: `${logicalProcessNodeViewHeight * magFactorX}px`,
|
|
||||||
}),
|
|
||||||
[left, magFactorX, processNodeViewXOffset, processNodeViewYOffset, top]
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Type in non-SVG components scales as follows:
|
|
||||||
* (These values were adjusted to match the proportions in the comps provided by UX/Design)
|
|
||||||
* 18.75 : The smallest readable font size at which labels/descriptions can be read. Font size will not scale below this.
|
|
||||||
* 12.5 : A 'slope' at which the font size will scale w.r.t. to zoom level otherwise
|
|
||||||
*/
|
|
||||||
const minimumFontSize = 18.75;
|
|
||||||
const slopeOfFontScale = 12.5;
|
|
||||||
const fontSizeAdjustmentForScale = magFactorX > 1 ? slopeOfFontScale * (magFactorX - 1) : 0;
|
|
||||||
const scaledTypeSize = minimumFontSize + fontSizeAdjustmentForScale;
|
|
||||||
|
|
||||||
const markerBaseSize = 15;
|
|
||||||
const markerSize = markerBaseSize;
|
|
||||||
const markerPositionOffset = -markerBaseSize / 2;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An element that should be animated when the node is clicked.
|
|
||||||
*/
|
|
||||||
const animationTarget: {
|
|
||||||
current:
|
|
||||||
| (SVGAnimationElement & {
|
|
||||||
/**
|
|
||||||
* `beginElement` is by [w3](https://www.w3.org/TR/SVG11/animate.html#__smil__ElementTimeControl__beginElement)
|
|
||||||
* but missing in [TSJS-lib-generator](https://github.com/microsoft/TSJS-lib-generator/blob/15a4678e0ef6de308e79451503e444e9949ee849/inputfiles/addedTypes.json#L1819)
|
|
||||||
*/
|
|
||||||
beginElement: () => void;
|
|
||||||
})
|
|
||||||
| null;
|
|
||||||
} = React.createRef();
|
|
||||||
const { cubeSymbol, labelBackground, descriptionText } = nodeAssets[nodeType(event)];
|
|
||||||
const resolverNodeIdGenerator = useMemo(() => htmlIdGenerator('resolverNode'), []);
|
|
||||||
|
|
||||||
const nodeId = useMemo(() => resolverNodeIdGenerator(selfId), [
|
|
||||||
resolverNodeIdGenerator,
|
|
||||||
selfId,
|
|
||||||
]);
|
|
||||||
const labelId = useMemo(() => resolverNodeIdGenerator(), [resolverNodeIdGenerator]);
|
|
||||||
const descriptionId = useMemo(() => resolverNodeIdGenerator(), [resolverNodeIdGenerator]);
|
|
||||||
const isActiveDescendant = nodeId === activeDescendantId;
|
|
||||||
const isSelectedDescendant = nodeId === selectedDescendantId;
|
|
||||||
|
|
||||||
const dispatch = useResolverDispatch();
|
|
||||||
|
|
||||||
const handleFocus = useCallback(() => {
|
|
||||||
dispatch({
|
|
||||||
type: 'userFocusedOnResolverNode',
|
|
||||||
payload: {
|
|
||||||
nodeId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}, [dispatch, nodeId]);
|
|
||||||
|
|
||||||
const handleClick = useCallback(() => {
|
|
||||||
if (animationTarget.current !== null) {
|
|
||||||
(animationTarget.current as any).beginElement();
|
|
||||||
}
|
|
||||||
dispatch({
|
|
||||||
type: 'userSelectedResolverNode',
|
|
||||||
payload: {
|
|
||||||
nodeId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}, [animationTarget, dispatch, nodeId]);
|
|
||||||
|
|
||||||
const handleRelatedEventRequest = useCallback(() => {
|
|
||||||
dispatch({
|
|
||||||
type: 'userRequestedRelatedEventData',
|
|
||||||
payload: event,
|
|
||||||
});
|
|
||||||
}, [dispatch, event]);
|
|
||||||
|
|
||||||
const handleRelatedAlertsRequest = useCallback(() => {
|
|
||||||
dispatch({
|
|
||||||
type: 'userSelectedRelatedAlerts',
|
|
||||||
payload: event,
|
|
||||||
});
|
|
||||||
}, [dispatch, event]);
|
|
||||||
/**
|
|
||||||
* Enumerates the stats for related events to display with the node as options,
|
|
||||||
* generally in the form `number of related events in category` `category title`
|
|
||||||
* e.g. "10 DNS", "230 File"
|
|
||||||
*/
|
|
||||||
const relatedEventOptions = useMemo(() => {
|
|
||||||
if (relatedEvents === 'error') {
|
|
||||||
// Return an empty set of options if there was an error requesting them
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
const relatedStats = typeof relatedEvents === 'object' && relatedEvents.stats;
|
|
||||||
if (!relatedStats) {
|
|
||||||
// Return an empty set of options if there are no stats to report
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
// If we have entries to show, map them into options to display in the selectable list
|
|
||||||
return Object.entries(relatedStats).map(statsEntry => {
|
|
||||||
const displayName = getDisplayName(statsEntry[0]);
|
|
||||||
return {
|
|
||||||
prefix: <EuiI18nNumber value={statsEntry[1] || 0} />,
|
|
||||||
optionTitle: `${displayName}`,
|
|
||||||
action: () => {
|
|
||||||
dispatch({
|
|
||||||
type: 'userSelectedRelatedEventCategory',
|
|
||||||
payload: {
|
|
||||||
subject: event,
|
|
||||||
category: statsEntry[0],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}, [relatedEvents, dispatch, event]);
|
|
||||||
|
|
||||||
const relatedEventStatusOrOptions = (() => {
|
|
||||||
if (!relatedEvents) {
|
|
||||||
// If related events have not yet been requested
|
|
||||||
return subMenuAssets.initialMenuStatus;
|
|
||||||
}
|
|
||||||
if (relatedEvents === 'error') {
|
|
||||||
// If there was an error when we tried to request the events
|
|
||||||
return subMenuAssets.menuError;
|
|
||||||
}
|
|
||||||
if (relatedEvents === 'waitingForRelatedEventData') {
|
|
||||||
// If we're waiting for events to be returned
|
|
||||||
// Pass on the waiting symbol
|
|
||||||
return relatedEvents;
|
|
||||||
}
|
|
||||||
return relatedEventOptions;
|
|
||||||
})();
|
|
||||||
|
|
||||||
/* eslint-disable jsx-a11y/click-events-have-key-events */
|
|
||||||
/**
|
|
||||||
* Key event handling (e.g. 'Enter'/'Space') is provisioned by the `EuiKeyboardAccessible` component
|
|
||||||
*/
|
|
||||||
return (
|
|
||||||
<EuiKeyboardAccessible>
|
|
||||||
<div
|
|
||||||
data-test-subj={'resolverNode'}
|
|
||||||
className={className + ' kbn-resetFocusState'}
|
|
||||||
role="treeitem"
|
|
||||||
aria-level={adjacentNodeMap.level}
|
|
||||||
aria-flowto={
|
|
||||||
adjacentNodeMap.nextSibling === null ? undefined : adjacentNodeMap.nextSibling
|
|
||||||
}
|
|
||||||
aria-labelledby={labelId}
|
|
||||||
aria-describedby={descriptionId}
|
|
||||||
aria-haspopup={'true'}
|
|
||||||
aria-current={isActiveDescendant ? 'true' : undefined}
|
|
||||||
aria-selected={isSelectedDescendant ? 'true' : undefined}
|
|
||||||
style={nodeViewportStyle}
|
|
||||||
id={nodeId}
|
|
||||||
onClick={handleClick}
|
|
||||||
onFocus={handleFocus}
|
|
||||||
tabIndex={-1}
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
viewBox="-15 -15 90 30"
|
|
||||||
preserveAspectRatio="xMidYMid meet"
|
|
||||||
style={{
|
|
||||||
display: 'block',
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
position: 'absolute',
|
|
||||||
top: '0',
|
|
||||||
left: '0',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<g>
|
|
||||||
<use
|
|
||||||
xlinkHref={`#${SymbolIds.processCubeActiveBacking}`}
|
|
||||||
x={-11.35}
|
|
||||||
y={-11.35}
|
|
||||||
width={markerSize * 1.5}
|
|
||||||
height={markerSize * 1.5}
|
|
||||||
className="backing"
|
|
||||||
/>
|
|
||||||
<use
|
|
||||||
role="presentation"
|
|
||||||
xlinkHref={cubeSymbol}
|
|
||||||
x={markerPositionOffset}
|
|
||||||
y={markerPositionOffset}
|
|
||||||
width={markerSize}
|
|
||||||
height={markerSize}
|
|
||||||
opacity="1"
|
|
||||||
className="cube"
|
|
||||||
>
|
|
||||||
<animateTransform
|
|
||||||
attributeType="XML"
|
|
||||||
attributeName="transform"
|
|
||||||
type="scale"
|
|
||||||
values="1 1; 1 .83; 1 .8; 1 .83; 1 1"
|
|
||||||
dur="0.2s"
|
|
||||||
begin="click"
|
|
||||||
repeatCount="1"
|
|
||||||
className="squish"
|
|
||||||
ref={animationTarget}
|
|
||||||
/>
|
|
||||||
</use>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
flexFlow: 'column',
|
|
||||||
left: '25%',
|
|
||||||
top: '30%',
|
|
||||||
position: 'absolute',
|
|
||||||
width: '50%',
|
|
||||||
color: NamedColors.full,
|
|
||||||
fontSize: `${scaledTypeSize}px`,
|
|
||||||
lineHeight: '140%',
|
|
||||||
backgroundColor: NamedColors.resolverBackground,
|
|
||||||
padding: '.25rem',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
id={descriptionId}
|
|
||||||
style={{
|
|
||||||
textTransform: 'uppercase',
|
|
||||||
letterSpacing: '-0.01px',
|
|
||||||
backgroundColor: NamedColors.resolverBackground,
|
|
||||||
lineHeight: '1',
|
|
||||||
fontWeight: 'bold',
|
|
||||||
fontSize: '0.8rem',
|
|
||||||
width: '100%',
|
|
||||||
margin: '0',
|
|
||||||
textAlign: 'left',
|
|
||||||
padding: '0',
|
|
||||||
color: NamedColors.empty,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{descriptionText}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={magFactorX >= 2 ? 'euiButton' : 'euiButton euiButton--small'}
|
|
||||||
data-test-subject="nodeLabel"
|
|
||||||
id={labelId}
|
|
||||||
style={{
|
|
||||||
backgroundColor: labelBackground,
|
|
||||||
padding: '.15rem 0',
|
|
||||||
textAlign: 'center',
|
|
||||||
maxWidth: '20rem',
|
|
||||||
minWidth: '12rem',
|
|
||||||
width: '60%',
|
|
||||||
overflow: 'hidden',
|
|
||||||
whiteSpace: 'nowrap',
|
|
||||||
textOverflow: 'ellipsis',
|
|
||||||
contain: 'content',
|
|
||||||
margin: '.25rem 0 .35rem 0',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span className="euiButton__content">
|
|
||||||
<span className="euiButton__text" data-test-subj={'euiButton__text'}>
|
|
||||||
{eventModel.eventName(event)}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
{magFactorX >= 2 && (
|
|
||||||
<EuiFlexGroup justifyContent="flexStart" gutterSize="xs">
|
|
||||||
<EuiFlexItem grow={false} className="related-dropdown">
|
|
||||||
<NodeSubMenu
|
|
||||||
menuTitle={subMenuAssets.relatedEvents.title}
|
|
||||||
optionsWithActions={relatedEventStatusOrOptions}
|
|
||||||
menuAction={handleRelatedEventRequest}
|
|
||||||
/>
|
|
||||||
</EuiFlexItem>
|
|
||||||
<EuiFlexItem grow={false}>
|
|
||||||
<NodeSubMenu
|
|
||||||
menuTitle={subMenuAssets.relatedAlerts.title}
|
|
||||||
menuAction={handleRelatedAlertsRequest}
|
|
||||||
/>
|
|
||||||
</EuiFlexItem>
|
|
||||||
</EuiFlexGroup>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</EuiKeyboardAccessible>
|
|
||||||
);
|
|
||||||
/* eslint-enable jsx-a11y/click-events-have-key-events */
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)`
|
|
||||||
position: absolute;
|
|
||||||
text-align: left;
|
|
||||||
font-size: 10px;
|
|
||||||
user-select: none;
|
|
||||||
box-sizing: border-box;
|
|
||||||
border-radius: 10%;
|
|
||||||
white-space: nowrap;
|
|
||||||
will-change: left, top, width, height;
|
|
||||||
contain: layout;
|
|
||||||
min-width: 280px;
|
|
||||||
min-height: 90px;
|
|
||||||
overflow-y: visible;
|
|
||||||
|
|
||||||
//dasharray & dashoffset should be equal to "pull" the stroke back
|
|
||||||
//when it is transitioned.
|
|
||||||
//The value is tuned to look good when animated, but to preserve
|
|
||||||
//the effect, it should always be _at least_ the length of the stroke
|
|
||||||
& .backing {
|
|
||||||
stroke-dasharray: 500;
|
|
||||||
stroke-dashoffset: 500;
|
|
||||||
}
|
|
||||||
&[aria-current] .backing {
|
|
||||||
transition-property: stroke-dashoffset;
|
|
||||||
transition-duration: 1s;
|
|
||||||
stroke-dashoffset: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
& .related-dropdown {
|
|
||||||
width: 4.5em;
|
|
||||||
}
|
|
||||||
& .euiSelectableList-bordered {
|
|
||||||
border-top-right-radius: 0px;
|
|
||||||
border-top-left-radius: 0px;
|
|
||||||
}
|
|
||||||
& .euiSelectableListItem {
|
|
||||||
background-color: black;
|
|
||||||
}
|
|
||||||
& .euiSelectableListItem path {
|
|
||||||
fill: white;
|
|
||||||
}
|
|
||||||
& .euiSelectableListItem__text {
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const processTypeToCube: Record<ResolverProcessType, keyof typeof nodeAssets> = {
|
|
||||||
processCreated: 'runningProcessCube',
|
|
||||||
processRan: 'runningProcessCube',
|
|
||||||
processTerminated: 'terminatedProcessCube',
|
|
||||||
unknownProcessEvent: 'runningProcessCube',
|
|
||||||
processCausedAlert: 'runningTriggerCube',
|
|
||||||
unknownEvent: 'runningProcessCube',
|
|
||||||
};
|
|
||||||
|
|
||||||
function nodeType(processEvent: ResolverEvent): keyof typeof nodeAssets {
|
|
||||||
const processType = processModel.eventType(processEvent);
|
|
||||||
|
|
||||||
if (processType in processTypeToCube) {
|
|
||||||
return processTypeToCube[processType];
|
|
||||||
}
|
|
||||||
return 'runningProcessCube';
|
|
||||||
}
|
|
|
@ -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 { PluginInitializer } from 'kibana/public';
|
|
||||||
import {
|
|
||||||
EndpointPlugin,
|
|
||||||
EndpointPluginStart,
|
|
||||||
EndpointPluginSetup,
|
|
||||||
EndpointPluginStartDependencies,
|
|
||||||
EndpointPluginSetupDependencies,
|
|
||||||
} from './plugin';
|
|
||||||
|
|
||||||
export const plugin: PluginInitializer<
|
|
||||||
EndpointPluginSetup,
|
|
||||||
EndpointPluginStart,
|
|
||||||
EndpointPluginSetupDependencies,
|
|
||||||
EndpointPluginStartDependencies
|
|
||||||
> = () => new EndpointPlugin();
|
|
|
@ -1,71 +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 { Plugin, CoreSetup, AppMountParameters, CoreStart } from 'kibana/public';
|
|
||||||
import { EmbeddableSetup } from 'src/plugins/embeddable/public';
|
|
||||||
import { DataPublicPluginStart } from 'src/plugins/data/public';
|
|
||||||
import { i18n } from '@kbn/i18n';
|
|
||||||
import { IngestManagerStart } from '../../ingest_manager/public';
|
|
||||||
import { ResolverEmbeddableFactory } from './embeddables/resolver';
|
|
||||||
|
|
||||||
export type EndpointPluginStart = void;
|
|
||||||
export type EndpointPluginSetup = void;
|
|
||||||
export interface EndpointPluginSetupDependencies {
|
|
||||||
embeddable: EmbeddableSetup;
|
|
||||||
data: DataPublicPluginStart;
|
|
||||||
}
|
|
||||||
export interface EndpointPluginStartDependencies {
|
|
||||||
data: DataPublicPluginStart;
|
|
||||||
ingestManager: IngestManagerStart;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Functionality that the endpoint plugin uses from core.
|
|
||||||
*/
|
|
||||||
export interface EndpointPluginServices extends Partial<CoreStart> {
|
|
||||||
http: CoreStart['http'];
|
|
||||||
overlays: CoreStart['overlays'] | undefined;
|
|
||||||
notifications: CoreStart['notifications'] | undefined;
|
|
||||||
data: DataPublicPluginStart;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class EndpointPlugin
|
|
||||||
implements
|
|
||||||
Plugin<
|
|
||||||
EndpointPluginSetup,
|
|
||||||
EndpointPluginStart,
|
|
||||||
EndpointPluginSetupDependencies,
|
|
||||||
EndpointPluginStartDependencies
|
|
||||||
> {
|
|
||||||
public setup(
|
|
||||||
core: CoreSetup<EndpointPluginStartDependencies>,
|
|
||||||
plugins: EndpointPluginSetupDependencies
|
|
||||||
) {
|
|
||||||
core.application.register({
|
|
||||||
id: 'endpoint',
|
|
||||||
title: i18n.translate('xpack.endpoint.pluginTitle', {
|
|
||||||
defaultMessage: 'Endpoint',
|
|
||||||
}),
|
|
||||||
euiIconType: 'securityApp',
|
|
||||||
async mount(params: AppMountParameters) {
|
|
||||||
const [coreStart, depsStart] = await core.getStartServices();
|
|
||||||
const { renderApp } = await import('./applications/endpoint');
|
|
||||||
return renderApp(coreStart, depsStart, params);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const resolverEmbeddableFactory = new ResolverEmbeddableFactory();
|
|
||||||
|
|
||||||
plugins.embeddable.registerEmbeddableFactory(
|
|
||||||
resolverEmbeddableFactory.type,
|
|
||||||
resolverEmbeddableFactory
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public start() {}
|
|
||||||
|
|
||||||
public stop() {}
|
|
||||||
}
|
|
|
@ -1,26 +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 { PluginInitializer, PluginInitializerContext } from 'src/core/server';
|
|
||||||
import {
|
|
||||||
EndpointPlugin,
|
|
||||||
EndpointPluginStart,
|
|
||||||
EndpointPluginSetup,
|
|
||||||
EndpointPluginStartDependencies,
|
|
||||||
EndpointPluginSetupDependencies,
|
|
||||||
} from './plugin';
|
|
||||||
import { EndpointConfigSchema } from './config';
|
|
||||||
|
|
||||||
export const config = {
|
|
||||||
schema: EndpointConfigSchema,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const plugin: PluginInitializer<
|
|
||||||
EndpointPluginSetup,
|
|
||||||
EndpointPluginStart,
|
|
||||||
EndpointPluginSetupDependencies,
|
|
||||||
EndpointPluginStartDependencies
|
|
||||||
> = (initializerContext: PluginInitializerContext) => new EndpointPlugin(initializerContext);
|
|
|
@ -1,59 +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 {
|
|
||||||
EndpointPlugin,
|
|
||||||
EndpointPluginSetupDependencies,
|
|
||||||
EndpointPluginStartDependencies,
|
|
||||||
} from './plugin';
|
|
||||||
import { coreMock } from '../../../../src/core/server/mocks';
|
|
||||||
import { PluginSetupContract } from '../../features/server';
|
|
||||||
import { createMockIngestManagerStartContract } from './mocks';
|
|
||||||
|
|
||||||
describe('test endpoint plugin', () => {
|
|
||||||
let plugin: EndpointPlugin;
|
|
||||||
let mockCoreSetup: ReturnType<typeof coreMock.createSetup>;
|
|
||||||
let mockCoreStart: ReturnType<typeof coreMock.createStart>;
|
|
||||||
let mockedEndpointPluginSetupDependencies: jest.Mocked<EndpointPluginSetupDependencies>;
|
|
||||||
let mockedEndpointPluginStartDependencies: jest.Mocked<EndpointPluginStartDependencies>;
|
|
||||||
let mockedPluginSetupContract: jest.Mocked<PluginSetupContract>;
|
|
||||||
beforeEach(() => {
|
|
||||||
plugin = new EndpointPlugin(
|
|
||||||
coreMock.createPluginInitializerContext({
|
|
||||||
cookieName: 'sid',
|
|
||||||
sessionTimeout: 1500,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
mockCoreSetup = coreMock.createSetup();
|
|
||||||
mockCoreStart = coreMock.createStart();
|
|
||||||
mockedPluginSetupContract = {
|
|
||||||
registerFeature: jest.fn(),
|
|
||||||
getFeatures: jest.fn(),
|
|
||||||
getFeaturesUICapabilities: jest.fn(),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
it('test properly setup plugin', async () => {
|
|
||||||
mockedEndpointPluginSetupDependencies = {
|
|
||||||
features: mockedPluginSetupContract,
|
|
||||||
};
|
|
||||||
await plugin.setup(mockCoreSetup, mockedEndpointPluginSetupDependencies);
|
|
||||||
expect(mockedPluginSetupContract.registerFeature).toBeCalledTimes(1);
|
|
||||||
expect(mockCoreSetup.http.createRouter).toBeCalledTimes(1);
|
|
||||||
expect(() => plugin.getEndpointAppContextService().getIndexPatternRetriever()).toThrow(Error);
|
|
||||||
expect(() => plugin.getEndpointAppContextService().getAgentService()).toThrow(Error);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('test properly start plugin', async () => {
|
|
||||||
mockedEndpointPluginStartDependencies = {
|
|
||||||
ingestManager: createMockIngestManagerStartContract(''),
|
|
||||||
};
|
|
||||||
await plugin.start(mockCoreStart, mockedEndpointPluginStartDependencies);
|
|
||||||
expect(plugin.getEndpointAppContextService().getAgentService()).toBeTruthy();
|
|
||||||
expect(plugin.getEndpointAppContextService().getIndexPatternRetriever()).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,108 +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 { Plugin, CoreSetup, PluginInitializerContext, Logger, CoreStart } from 'kibana/server';
|
|
||||||
import { first } from 'rxjs/operators';
|
|
||||||
import { PluginSetupContract as FeaturesPluginSetupContract } from '../../features/server';
|
|
||||||
import { createConfig$, EndpointConfigType } from './config';
|
|
||||||
import { EndpointAppContext } from './types';
|
|
||||||
|
|
||||||
import { registerAlertRoutes } from './routes/alerts';
|
|
||||||
import { registerResolverRoutes } from './routes/resolver';
|
|
||||||
import { registerIndexPatternRoute } from './routes/index_pattern';
|
|
||||||
import { registerEndpointRoutes } from './routes/metadata';
|
|
||||||
import { IngestIndexPatternRetriever } from './index_pattern';
|
|
||||||
import { IngestManagerStartContract } from '../../ingest_manager/server';
|
|
||||||
import { EndpointAppContextService } from './endpoint_app_context_services';
|
|
||||||
import { registerPolicyRoutes } from './routes/policy';
|
|
||||||
|
|
||||||
export type EndpointPluginStart = void;
|
|
||||||
export type EndpointPluginSetup = void;
|
|
||||||
export interface EndpointPluginStartDependencies {
|
|
||||||
ingestManager: IngestManagerStartContract;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface EndpointPluginSetupDependencies {
|
|
||||||
features: FeaturesPluginSetupContract;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class EndpointPlugin
|
|
||||||
implements
|
|
||||||
Plugin<
|
|
||||||
EndpointPluginSetup,
|
|
||||||
EndpointPluginStart,
|
|
||||||
EndpointPluginSetupDependencies,
|
|
||||||
EndpointPluginStartDependencies
|
|
||||||
> {
|
|
||||||
private readonly logger: Logger;
|
|
||||||
private readonly endpointAppContextService: EndpointAppContextService = new EndpointAppContextService();
|
|
||||||
constructor(private readonly initializerContext: PluginInitializerContext) {
|
|
||||||
this.logger = this.initializerContext.logger.get('endpoint');
|
|
||||||
}
|
|
||||||
|
|
||||||
public getEndpointAppContextService(): EndpointAppContextService {
|
|
||||||
return this.endpointAppContextService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public setup(core: CoreSetup, plugins: EndpointPluginSetupDependencies) {
|
|
||||||
plugins.features.registerFeature({
|
|
||||||
id: 'endpoint',
|
|
||||||
name: 'Endpoint',
|
|
||||||
icon: 'bug',
|
|
||||||
navLinkId: 'endpoint',
|
|
||||||
app: ['endpoint', 'kibana'],
|
|
||||||
privileges: {
|
|
||||||
all: {
|
|
||||||
app: ['endpoint', 'kibana'],
|
|
||||||
api: ['resolver'],
|
|
||||||
savedObject: {
|
|
||||||
all: [],
|
|
||||||
read: [],
|
|
||||||
},
|
|
||||||
ui: ['save'],
|
|
||||||
},
|
|
||||||
read: {
|
|
||||||
app: ['endpoint', 'kibana'],
|
|
||||||
api: [],
|
|
||||||
savedObject: {
|
|
||||||
all: [],
|
|
||||||
read: [],
|
|
||||||
},
|
|
||||||
ui: [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const endpointContext = {
|
|
||||||
logFactory: this.initializerContext.logger,
|
|
||||||
service: this.endpointAppContextService,
|
|
||||||
config: (): Promise<EndpointConfigType> => {
|
|
||||||
return createConfig$(this.initializerContext)
|
|
||||||
.pipe(first())
|
|
||||||
.toPromise();
|
|
||||||
},
|
|
||||||
} as EndpointAppContext;
|
|
||||||
const router = core.http.createRouter();
|
|
||||||
registerEndpointRoutes(router, endpointContext);
|
|
||||||
registerResolverRoutes(router, endpointContext);
|
|
||||||
registerAlertRoutes(router, endpointContext);
|
|
||||||
registerIndexPatternRoute(router, endpointContext);
|
|
||||||
registerPolicyRoutes(router, endpointContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
public start(core: CoreStart, plugins: EndpointPluginStartDependencies) {
|
|
||||||
this.logger.debug('Starting plugin');
|
|
||||||
this.endpointAppContextService.start({
|
|
||||||
indexPatternRetriever: new IngestIndexPatternRetriever(
|
|
||||||
plugins.ingestManager.esIndexPatternService,
|
|
||||||
this.initializerContext.logger
|
|
||||||
),
|
|
||||||
agentService: plugins.ingestManager.agentService,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
public stop() {
|
|
||||||
this.logger.debug('Stopping plugin');
|
|
||||||
this.endpointAppContextService.stop();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 { alertDetailsReqSchema } from './schemas';
|
|
||||||
export { alertDetailsHandlerWrapper } from './handlers';
|
|
|
@ -1,7 +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 { alertListHandlerWrapper } from './handlers';
|
|
|
@ -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 { Query, Filter, TimeRange } from '../../../../../../src/plugins/data/server';
|
|
||||||
import { JsonObject } from '../../../../../../src/plugins/kibana_utils/public';
|
|
||||||
import { AlertAPIOrdering } from '../../../common/types';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sort parameters for alerts in ES.
|
|
||||||
*/
|
|
||||||
export interface AlertSortParam {
|
|
||||||
[key: string]: {
|
|
||||||
order: AlertAPIOrdering;
|
|
||||||
missing?: UndefinedResultPosition;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sort array for alerts.
|
|
||||||
*/
|
|
||||||
export type AlertSort = [AlertSortParam, AlertSortParam];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cursor-based pagination params.
|
|
||||||
*/
|
|
||||||
export type SearchCursor = [string, string];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request metadata used in searching alerts.
|
|
||||||
*/
|
|
||||||
export interface AlertSearchQuery {
|
|
||||||
pageSize: number;
|
|
||||||
pageIndex?: number;
|
|
||||||
fromIndex?: number;
|
|
||||||
query: Query;
|
|
||||||
filters: Filter[];
|
|
||||||
dateRange?: TimeRange;
|
|
||||||
sort: string;
|
|
||||||
order: AlertAPIOrdering;
|
|
||||||
searchAfter?: SearchCursor;
|
|
||||||
searchBefore?: SearchCursor;
|
|
||||||
emptyStringIsUndefined?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ES request body for alerts.
|
|
||||||
*/
|
|
||||||
export interface AlertSearchRequest {
|
|
||||||
track_total_hits: number;
|
|
||||||
query: JsonObject;
|
|
||||||
sort: AlertSort;
|
|
||||||
search_after?: SearchCursor;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request for alerts.
|
|
||||||
*/
|
|
||||||
export interface AlertSearchRequestWrapper {
|
|
||||||
index: string;
|
|
||||||
size: number;
|
|
||||||
from?: number;
|
|
||||||
body: AlertSearchRequest;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request params for alert details.
|
|
||||||
*/
|
|
||||||
export interface AlertDetailsRequestParams {
|
|
||||||
id: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request params for alert queries.
|
|
||||||
*
|
|
||||||
* Must match exactly the values that the API receives.
|
|
||||||
*/
|
|
||||||
export interface AlertListRequestQuery {
|
|
||||||
page_index?: number;
|
|
||||||
page_size: number;
|
|
||||||
query?: string;
|
|
||||||
filters?: string;
|
|
||||||
date_range: string;
|
|
||||||
sort: string;
|
|
||||||
order: AlertAPIOrdering;
|
|
||||||
after?: SearchCursor;
|
|
||||||
before?: SearchCursor;
|
|
||||||
empty_string_is_undefined?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates whether undefined results are sorted to the beginning (_first) or end (_last)
|
|
||||||
* of a result set.
|
|
||||||
*/
|
|
||||||
export enum UndefinedResultPosition {
|
|
||||||
first = '_first',
|
|
||||||
last = '_last',
|
|
||||||
}
|
|
|
@ -1,43 +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 { IRouter, Logger, RequestHandler } from 'kibana/server';
|
|
||||||
import { EndpointAppContext } from '../types';
|
|
||||||
import { IndexPatternGetParamsResult } from '../../common/types';
|
|
||||||
import { AlertConstants } from '../../common/alert_constants';
|
|
||||||
import { indexPatternGetParamsSchema } from '../../common/schema/index_pattern';
|
|
||||||
|
|
||||||
function handleIndexPattern(
|
|
||||||
log: Logger,
|
|
||||||
endpointAppContext: EndpointAppContext
|
|
||||||
): RequestHandler<IndexPatternGetParamsResult> {
|
|
||||||
return async (context, req, res) => {
|
|
||||||
try {
|
|
||||||
const indexRetriever = endpointAppContext.service.getIndexPatternRetriever();
|
|
||||||
return res.ok({
|
|
||||||
body: {
|
|
||||||
indexPattern: await indexRetriever.getIndexPattern(context, req.params.datasetPath),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
log.warn(error);
|
|
||||||
return res.notFound({ body: error });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function registerIndexPatternRoute(router: IRouter, endpointAppContext: EndpointAppContext) {
|
|
||||||
const log = endpointAppContext.logFactory.get('index_pattern');
|
|
||||||
|
|
||||||
router.get(
|
|
||||||
{
|
|
||||||
path: `${AlertConstants.INDEX_PATTERN_ROUTE}/{datasetPath}`,
|
|
||||||
validate: { params: indexPatternGetParamsSchema },
|
|
||||||
options: { authRequired: true },
|
|
||||||
},
|
|
||||||
handleIndexPattern(log, endpointAppContext)
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
../../../yarn.lock
|
|
|
@ -442,6 +442,7 @@ export class EndpointDocGenerator {
|
||||||
lineage.length === generations + 1
|
lineage.length === generations + 1
|
||||||
) {
|
) {
|
||||||
lineage.pop();
|
lineage.pop();
|
||||||
|
// eslint-disable-next-line no-continue
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Otherwise, add a child and any nodes associated with it
|
// Otherwise, add a child and any nodes associated with it
|
||||||
|
@ -738,9 +739,10 @@ export class EndpointDocGenerator {
|
||||||
}
|
}
|
||||||
|
|
||||||
private *randomNGenerator(max: number, count: number) {
|
private *randomNGenerator(max: number, count: number) {
|
||||||
while (count > 0) {
|
let iCount = count;
|
||||||
|
while (iCount > 0) {
|
||||||
yield this.randomN(max);
|
yield this.randomN(max);
|
||||||
count--;
|
iCount = iCount - 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,14 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { SearchResponse } from 'elasticsearch';
|
import { Datasource, NewDatasource } from '../../../ingest_manager/common';
|
||||||
import { TypeOf } from '@kbn/config-schema';
|
|
||||||
import { alertingIndexGetQuerySchema } from './schema/alert_index';
|
export interface AppLocation {
|
||||||
import { indexPatternGetParamsSchema } from './schema/index_pattern';
|
pathname: string;
|
||||||
import { Datasource, NewDatasource } from '../../ingest_manager/common';
|
search: string;
|
||||||
|
hash: string;
|
||||||
|
key?: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A deep readonly type that will make all children of a given object readonly recursively
|
* A deep readonly type that will make all children of a given object readonly recursively
|
||||||
|
@ -30,11 +33,6 @@ type ImmutableMap<K, V> = ReadonlyMap<Immutable<K>, Immutable<V>>;
|
||||||
type ImmutableSet<T> = ReadonlySet<Immutable<T>>;
|
type ImmutableSet<T> = ReadonlySet<Immutable<T>>;
|
||||||
type ImmutableObject<T> = { readonly [K in keyof T]: Immutable<T[K]> };
|
type ImmutableObject<T> = { readonly [K in keyof T]: Immutable<T[K]> };
|
||||||
|
|
||||||
/**
|
|
||||||
* Values for the Alert APIs 'order' and 'direction' parameters.
|
|
||||||
*/
|
|
||||||
export type AlertAPIOrdering = 'asc' | 'desc';
|
|
||||||
|
|
||||||
export interface ResolverNodeStats {
|
export interface ResolverNodeStats {
|
||||||
totalEvents: number;
|
totalEvents: number;
|
||||||
totalAlerts: number;
|
totalAlerts: number;
|
||||||
|
@ -60,46 +58,6 @@ export interface ResolverNode {
|
||||||
stats?: ResolverNodeStats;
|
stats?: ResolverNodeStats;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returned by 'api/endpoint/alerts'
|
|
||||||
*/
|
|
||||||
export interface AlertResultList {
|
|
||||||
/**
|
|
||||||
* The alerts restricted by page size.
|
|
||||||
*/
|
|
||||||
alerts: AlertData[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The total number of alerts on the page.
|
|
||||||
*/
|
|
||||||
total: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The size of the requested page.
|
|
||||||
*/
|
|
||||||
request_page_size: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The index of the requested page, starting at 0.
|
|
||||||
*/
|
|
||||||
request_page_index?: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The offset of the requested page, starting at 0.
|
|
||||||
*/
|
|
||||||
result_from_index?: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A cursor-based URL for the next page.
|
|
||||||
*/
|
|
||||||
next: string | null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A cursor-based URL for the previous page.
|
|
||||||
*/
|
|
||||||
prev: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returned by the server via /api/endpoint/metadata
|
* Returned by the server via /api/endpoint/metadata
|
||||||
*/
|
*/
|
||||||
|
@ -271,24 +229,6 @@ export type AlertEvent = Immutable<{
|
||||||
dll?: DllFields[];
|
dll?: DllFields[];
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
interface AlertMetadata {
|
|
||||||
id: string;
|
|
||||||
|
|
||||||
// Alert Details Pagination
|
|
||||||
next: string | null;
|
|
||||||
prev: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface AlertState {
|
|
||||||
state: {
|
|
||||||
host_metadata: HostMetadata;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AlertData = AlertEvent & AlertMetadata;
|
|
||||||
|
|
||||||
export type AlertDetails = AlertData & AlertState;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The status of the host
|
* The status of the host
|
||||||
*/
|
*/
|
||||||
|
@ -337,19 +277,6 @@ export type HostMetadata = Immutable<{
|
||||||
host: Host;
|
host: Host;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents `total` response from Elasticsearch after ES 7.0.
|
|
||||||
*/
|
|
||||||
export interface ESTotal {
|
|
||||||
value: number;
|
|
||||||
relation: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* `Hits` array in responses from ES search API.
|
|
||||||
*/
|
|
||||||
export type AlertHits = SearchResponse<AlertEvent>['hits']['hits'];
|
|
||||||
|
|
||||||
export interface LegacyEndpointEvent {
|
export interface LegacyEndpointEvent {
|
||||||
'@timestamp': number;
|
'@timestamp': number;
|
||||||
endgame: {
|
endgame: {
|
||||||
|
@ -427,7 +354,7 @@ export type ResolverEvent = EndpointEvent | LegacyEndpointEvent;
|
||||||
* Note that because the types coming from `@kbn/config-schema`'s schemas sometimes have deeply nested
|
* Note that because the types coming from `@kbn/config-schema`'s schemas sometimes have deeply nested
|
||||||
* `Type` types, we process the result of `TypeOf` instead, as this will be consistent.
|
* `Type` types, we process the result of `TypeOf` instead, as this will be consistent.
|
||||||
*/
|
*/
|
||||||
type KbnConfigSchemaInputTypeOf<T> = T extends Record<string, unknown>
|
export type KbnConfigSchemaInputTypeOf<T> = T extends Record<string, unknown>
|
||||||
? KbnConfigSchemaInputObjectTypeOf<
|
? KbnConfigSchemaInputObjectTypeOf<
|
||||||
T
|
T
|
||||||
> /** `schema.number()` accepts strings, so this type should accept them as well. */
|
> /** `schema.number()` accepts strings, so this type should accept them as well. */
|
||||||
|
@ -468,23 +395,6 @@ type KbnConfigSchemaNonOptionalProps<Props extends Record<string, unknown>> = Pi
|
||||||
}[keyof Props]
|
}[keyof Props]
|
||||||
>;
|
>;
|
||||||
|
|
||||||
/**
|
|
||||||
* Query params to pass to the alert API when fetching new data.
|
|
||||||
*/
|
|
||||||
export type AlertingIndexGetQueryInput = KbnConfigSchemaInputTypeOf<
|
|
||||||
TypeOf<typeof alertingIndexGetQuerySchema>
|
|
||||||
>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Result of the validated query params when handling alert index requests.
|
|
||||||
*/
|
|
||||||
export type AlertingIndexGetQueryResult = TypeOf<typeof alertingIndexGetQuerySchema>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Result of the validated params when handling an index pattern request.
|
|
||||||
*/
|
|
||||||
export type IndexPatternGetParamsResult = TypeOf<typeof indexPatternGetParamsSchema>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Endpoint Policy configuration
|
* Endpoint Policy configuration
|
||||||
*/
|
*/
|
|
@ -47,7 +47,7 @@ export const alertingIndexGetQuerySchema = schema.object(
|
||||||
try {
|
try {
|
||||||
decode(value);
|
decode(value);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return i18n.translate('xpack.endpoint.alerts.errors.bad_rison', {
|
return i18n.translate('xpack.siem.endpoint.alerts.errors.bad_rison', {
|
||||||
defaultMessage: 'must be a valid rison-encoded string',
|
defaultMessage: 'must be a valid rison-encoded string',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,7 @@ export const alertingIndexGetQuerySchema = schema.object(
|
||||||
try {
|
try {
|
||||||
decode(value);
|
decode(value);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return i18n.translate('xpack.endpoint.alerts.errors.bad_rison', {
|
return i18n.translate('xpack.siem.endpoint.alerts.errors.bad_rison', {
|
||||||
defaultMessage: 'must be a valid rison-encoded string',
|
defaultMessage: 'must be a valid rison-encoded string',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -77,7 +77,7 @@ export const alertingIndexGetQuerySchema = schema.object(
|
||||||
try {
|
try {
|
||||||
decode(value);
|
decode(value);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return i18n.translate('xpack.endpoint.alerts.errors.bad_rison', {
|
return i18n.translate('xpack.siem.endpoint.alerts.errors.bad_rison', {
|
||||||
defaultMessage: 'must be a valid rison-encoded string',
|
defaultMessage: 'must be a valid rison-encoded string',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -88,22 +88,28 @@ export const alertingIndexGetQuerySchema = schema.object(
|
||||||
{
|
{
|
||||||
validate(value) {
|
validate(value) {
|
||||||
if (value.after !== undefined && value.page_index !== undefined) {
|
if (value.after !== undefined && value.page_index !== undefined) {
|
||||||
return i18n.translate('xpack.endpoint.alerts.errors.page_index_cannot_be_used_with_after', {
|
return i18n.translate(
|
||||||
defaultMessage: '[page_index] cannot be used with [after]',
|
'xpack.siem.endpoint.alerts.errors.page_index_cannot_be_used_with_after',
|
||||||
});
|
{
|
||||||
|
defaultMessage: '[page_index] cannot be used with [after]',
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (value.before !== undefined && value.page_index !== undefined) {
|
if (value.before !== undefined && value.page_index !== undefined) {
|
||||||
return i18n.translate(
|
return i18n.translate(
|
||||||
'xpack.endpoint.alerts.errors.page_index_cannot_be_used_with_before',
|
'xpack.siem.endpoint.alerts.errors.page_index_cannot_be_used_with_before',
|
||||||
{
|
{
|
||||||
defaultMessage: '[page_index] cannot be used with [before]',
|
defaultMessage: '[page_index] cannot be used with [before]',
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (value.before !== undefined && value.after !== undefined) {
|
if (value.before !== undefined && value.after !== undefined) {
|
||||||
return i18n.translate('xpack.endpoint.alerts.errors.before_cannot_be_used_with_after', {
|
return i18n.translate(
|
||||||
defaultMessage: '[before] cannot be used with [after]',
|
'xpack.siem.endpoint.alerts.errors.before_cannot_be_used_with_after',
|
||||||
});
|
{
|
||||||
|
defaultMessage: '[before] cannot be used with [after]',
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
272
x-pack/plugins/siem/common/endpoint_alerts/types.ts
Normal file
272
x-pack/plugins/siem/common/endpoint_alerts/types.ts
Normal file
|
@ -0,0 +1,272 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under 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 { SearchResponse } from 'elasticsearch';
|
||||||
|
import { TypeOf } from '@kbn/config-schema';
|
||||||
|
import { IIndexPattern, TimeRange, Filter, Query } from 'src/plugins/data/public';
|
||||||
|
import { JsonObject } from 'src/plugins/kibana_utils/common';
|
||||||
|
import { alertingIndexGetQuerySchema } from './schema/alert_index';
|
||||||
|
import { indexPatternGetParamsSchema } from './schema/index_pattern';
|
||||||
|
import {
|
||||||
|
HostMetadata,
|
||||||
|
AlertEvent,
|
||||||
|
KbnConfigSchemaInputTypeOf,
|
||||||
|
AppLocation,
|
||||||
|
} from '../endpoint/types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A deep readonly type that will make all children of a given object readonly recursively
|
||||||
|
*/
|
||||||
|
export type Immutable<T> = T extends undefined | null | boolean | string | number
|
||||||
|
? T
|
||||||
|
: unknown extends T
|
||||||
|
? unknown
|
||||||
|
: T extends Array<infer U>
|
||||||
|
? ImmutableArray<U>
|
||||||
|
: T extends Map<infer K, infer V>
|
||||||
|
? ImmutableMap<K, V>
|
||||||
|
: T extends Set<infer M>
|
||||||
|
? ImmutableSet<M>
|
||||||
|
: ImmutableObject<T>;
|
||||||
|
|
||||||
|
type ImmutableArray<T> = ReadonlyArray<Immutable<T>>;
|
||||||
|
type ImmutableMap<K, V> = ReadonlyMap<Immutable<K>, Immutable<V>>;
|
||||||
|
type ImmutableSet<T> = ReadonlySet<Immutable<T>>;
|
||||||
|
type ImmutableObject<T> = { readonly [K in keyof T]: Immutable<T[K]> };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Values for the Alert APIs 'order' and 'direction' parameters.
|
||||||
|
*/
|
||||||
|
export type AlertAPIOrdering = 'asc' | 'desc';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returned by 'api/endpoint/alerts'
|
||||||
|
*/
|
||||||
|
export interface AlertResultList {
|
||||||
|
/**
|
||||||
|
* The alerts restricted by page size.
|
||||||
|
*/
|
||||||
|
alerts: AlertData[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The total number of alerts on the page.
|
||||||
|
*/
|
||||||
|
total: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The size of the requested page.
|
||||||
|
*/
|
||||||
|
request_page_size: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The index of the requested page, starting at 0.
|
||||||
|
*/
|
||||||
|
request_page_index?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The offset of the requested page, starting at 0.
|
||||||
|
*/
|
||||||
|
result_from_index?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A cursor-based URL for the next page.
|
||||||
|
*/
|
||||||
|
next: string | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A cursor-based URL for the previous page.
|
||||||
|
*/
|
||||||
|
prev: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AlertMetadata {
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
// Alert Details Pagination
|
||||||
|
next: string | null;
|
||||||
|
prev: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AlertState {
|
||||||
|
state: {
|
||||||
|
host_metadata: HostMetadata;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AlertData = AlertEvent & AlertMetadata;
|
||||||
|
|
||||||
|
export type AlertDetails = AlertData & AlertState;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents `total` response from Elasticsearch after ES 7.0.
|
||||||
|
*/
|
||||||
|
export interface ESTotal {
|
||||||
|
value: number;
|
||||||
|
relation: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `Hits` array in responses from ES search API.
|
||||||
|
*/
|
||||||
|
export type AlertHits = SearchResponse<AlertEvent>['hits']['hits'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query params to pass to the alert API when fetching new data.
|
||||||
|
*/
|
||||||
|
export type AlertingIndexGetQueryInput = KbnConfigSchemaInputTypeOf<
|
||||||
|
TypeOf<typeof alertingIndexGetQuerySchema>
|
||||||
|
>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Result of the validated query params when handling alert index requests.
|
||||||
|
*/
|
||||||
|
export type AlertingIndexGetQueryResult = TypeOf<typeof alertingIndexGetQuerySchema>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Result of the validated params when handling an index pattern request.
|
||||||
|
*/
|
||||||
|
export type IndexPatternGetParamsResult = TypeOf<typeof indexPatternGetParamsSchema>;
|
||||||
|
|
||||||
|
interface AlertsSearchBarState {
|
||||||
|
patterns: IIndexPattern[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AlertListData = AlertResultList;
|
||||||
|
|
||||||
|
export interface AlertListState {
|
||||||
|
/** Array of alert items. */
|
||||||
|
readonly alerts: Immutable<AlertData[]>;
|
||||||
|
|
||||||
|
/** The total number of alerts on the page. */
|
||||||
|
readonly total: number;
|
||||||
|
|
||||||
|
/** Number of alerts per page. */
|
||||||
|
readonly pageSize: number;
|
||||||
|
|
||||||
|
/** Page number, starting at 0. */
|
||||||
|
readonly pageIndex: number;
|
||||||
|
|
||||||
|
/** Current location object from React Router history. */
|
||||||
|
readonly location?: Immutable<AppLocation>;
|
||||||
|
|
||||||
|
/** Specific Alert data to be shown in the details view */
|
||||||
|
readonly alertDetails?: Immutable<AlertDetails>;
|
||||||
|
|
||||||
|
/** Search bar state including indexPatterns */
|
||||||
|
readonly searchBar: AlertsSearchBarState;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gotten by parsing the URL from the browser. Used to calculate the new URL when changing views.
|
||||||
|
*/
|
||||||
|
export interface AlertingIndexUIQueryParams {
|
||||||
|
/**
|
||||||
|
* How many items to show in list.
|
||||||
|
*/
|
||||||
|
page_size?: string;
|
||||||
|
/**
|
||||||
|
* Which page to show. If `page_index` is 1, show page 2.
|
||||||
|
*/
|
||||||
|
page_index?: string;
|
||||||
|
/**
|
||||||
|
* If any value is present, show the alert detail view for the selected alert. Should be an ID for an alert event.
|
||||||
|
*/
|
||||||
|
selected_alert?: string;
|
||||||
|
query?: string;
|
||||||
|
date_range?: string;
|
||||||
|
filters?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sort parameters for alerts in ES.
|
||||||
|
*/
|
||||||
|
export interface AlertSortParam {
|
||||||
|
[key: string]: {
|
||||||
|
order: AlertAPIOrdering;
|
||||||
|
missing?: UndefinedResultPosition;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sort array for alerts.
|
||||||
|
*/
|
||||||
|
export type AlertSort = [AlertSortParam, AlertSortParam];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cursor-based pagination params.
|
||||||
|
*/
|
||||||
|
export type SearchCursor = [string, string];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request metadata used in searching alerts.
|
||||||
|
*/
|
||||||
|
export interface AlertSearchQuery {
|
||||||
|
pageSize: number;
|
||||||
|
pageIndex?: number;
|
||||||
|
fromIndex?: number;
|
||||||
|
query: Query;
|
||||||
|
filters: Filter[];
|
||||||
|
dateRange?: TimeRange;
|
||||||
|
sort: string;
|
||||||
|
order: AlertAPIOrdering;
|
||||||
|
searchAfter?: SearchCursor;
|
||||||
|
searchBefore?: SearchCursor;
|
||||||
|
emptyStringIsUndefined?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ES request body for alerts.
|
||||||
|
*/
|
||||||
|
export interface AlertSearchRequest {
|
||||||
|
track_total_hits: number;
|
||||||
|
query: JsonObject;
|
||||||
|
sort: AlertSort;
|
||||||
|
search_after?: SearchCursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request for alerts.
|
||||||
|
*/
|
||||||
|
export interface AlertSearchRequestWrapper {
|
||||||
|
index: string;
|
||||||
|
size: number;
|
||||||
|
from?: number;
|
||||||
|
body: AlertSearchRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request params for alert details.
|
||||||
|
*/
|
||||||
|
export interface AlertDetailsRequestParams {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request params for alert queries.
|
||||||
|
*
|
||||||
|
* Must match exactly the values that the API receives.
|
||||||
|
*/
|
||||||
|
export interface AlertListRequestQuery {
|
||||||
|
page_index?: number;
|
||||||
|
page_size: number;
|
||||||
|
query?: string;
|
||||||
|
filters?: string;
|
||||||
|
date_range: string;
|
||||||
|
sort: string;
|
||||||
|
order: AlertAPIOrdering;
|
||||||
|
after?: SearchCursor;
|
||||||
|
before?: SearchCursor;
|
||||||
|
empty_string_is_undefined?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether undefined results are sorted to the beginning (_first) or end (_last)
|
||||||
|
* of a result set.
|
||||||
|
*/
|
||||||
|
export enum UndefinedResultPosition {
|
||||||
|
first = '_first',
|
||||||
|
last = '_last',
|
||||||
|
}
|
|
@ -7,9 +7,11 @@
|
||||||
"actions",
|
"actions",
|
||||||
"alerting",
|
"alerting",
|
||||||
"data",
|
"data",
|
||||||
|
"dataEnhanced",
|
||||||
"embeddable",
|
"embeddable",
|
||||||
"features",
|
"features",
|
||||||
"home",
|
"home",
|
||||||
|
"ingestManager",
|
||||||
"inspector",
|
"inspector",
|
||||||
"licensing",
|
"licensing",
|
||||||
"maps",
|
"maps",
|
||||||
|
|
|
@ -9,13 +9,17 @@
|
||||||
"build-graphql-types": "node scripts/generate_types_from_graphql.js",
|
"build-graphql-types": "node scripts/generate_types_from_graphql.js",
|
||||||
"cypress:open": "cypress open --config-file ./cypress/cypress.json",
|
"cypress:open": "cypress open --config-file ./cypress/cypress.json",
|
||||||
"cypress:run": "cypress run --browser chrome --headless --spec ./cypress/integration/**/*.spec.ts --config-file ./cypress/cypress.json --reporter ../../node_modules/cypress-multi-reporters --reporter-options configFile=./cypress/reporter_config.json; status=$?; ../../node_modules/.bin/mochawesome-merge --reportDir ../../../target/kibana-siem/cypress/results > ../../../target/kibana-siem/cypress/results/output.json; ../../../node_modules/.bin/marge ../../../target/kibana-siem/cypress/results/output.json --reportDir ../../../target/kibana-siem/cypress/results; mkdir -p ../../../target/junit && cp ../../../target/kibana-siem/cypress/results/*.xml ../../../target/junit/ && exit $status;",
|
"cypress:run": "cypress run --browser chrome --headless --spec ./cypress/integration/**/*.spec.ts --config-file ./cypress/cypress.json --reporter ../../node_modules/cypress-multi-reporters --reporter-options configFile=./cypress/reporter_config.json; status=$?; ../../node_modules/.bin/mochawesome-merge --reportDir ../../../target/kibana-siem/cypress/results > ../../../target/kibana-siem/cypress/results/output.json; ../../../node_modules/.bin/marge ../../../target/kibana-siem/cypress/results/output.json --reportDir ../../../target/kibana-siem/cypress/results; mkdir -p ../../../target/junit && cp ../../../target/kibana-siem/cypress/results/*.xml ../../../target/junit/ && exit $status;",
|
||||||
"cypress:run-as-ci": "node ../../../scripts/functional_tests --config ../../test/siem_cypress/config.ts"
|
"cypress:run-as-ci": "node ../../../scripts/functional_tests --config ../../test/siem_cypress/config.ts",
|
||||||
|
"test:generate": "ts-node --project scripts/endpoint/cli_tsconfig.json scripts/endpoint/resolver_generator.ts"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/lodash": "^4.14.110"
|
"@types/lodash": "^4.14.110"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.15",
|
||||||
"react-markdown": "^4.0.6"
|
"querystring": "^0.2.0",
|
||||||
|
"react-markdown": "^4.0.6",
|
||||||
|
"redux-devtools-extension": "^2.13.8",
|
||||||
|
"@types/seedrandom": ">=2.0.0 <4.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ import { DEFAULT_DARK_MODE } from '../../common/constants';
|
||||||
import { ErrorToastDispatcher } from '../common/components/error_toast_dispatcher';
|
import { ErrorToastDispatcher } from '../common/components/error_toast_dispatcher';
|
||||||
import { compose } from '../common/lib/compose/kibana_compose';
|
import { compose } from '../common/lib/compose/kibana_compose';
|
||||||
import { AppFrontendLibs, AppApolloClient } from '../common/lib/lib';
|
import { AppFrontendLibs, AppApolloClient } from '../common/lib/lib';
|
||||||
import { StartServices } from '../plugin';
|
import { StartServices } from '../types';
|
||||||
import { PageRouter } from './routes';
|
import { PageRouter } from './routes';
|
||||||
import { createStore, createInitialState } from '../common/store';
|
import { createStore, createInitialState } from '../common/store';
|
||||||
import { GlobalToaster, ManageGlobalToaster } from '../common/components/toasters';
|
import { GlobalToaster, ManageGlobalToaster } from '../common/components/toasters';
|
||||||
|
@ -80,7 +80,8 @@ const StartAppComponent: FC<StartAppComponent> = ({ subPlugins, ...libs }) => {
|
||||||
const store = createStore(
|
const store = createStore(
|
||||||
createInitialState(subPluginsStore.initialState),
|
createInitialState(subPluginsStore.initialState),
|
||||||
subPluginsStore.reducer,
|
subPluginsStore.reducer,
|
||||||
libs$.pipe(pluck('apolloClient'))
|
libs$.pipe(pluck('apolloClient')),
|
||||||
|
subPluginsStore.middlewares
|
||||||
);
|
);
|
||||||
|
|
||||||
const [darkMode] = useUiSetting$<boolean>(DEFAULT_DARK_MODE);
|
const [darkMode] = useUiSetting$<boolean>(DEFAULT_DARK_MODE);
|
||||||
|
|
|
@ -6,18 +6,18 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { NotificationsStart } from 'kibana/public';
|
import { NotificationsStart } from 'kibana/public';
|
||||||
import { IngestManagerStart } from '../../../../../ingest_manager/public';
|
import { IngestManagerStart } from '../../../../ingest_manager/public';
|
||||||
|
|
||||||
export const Setup: React.FunctionComponent<{
|
export const Setup: React.FunctionComponent<{
|
||||||
ingestManager: IngestManagerStart;
|
ingestManager: IngestManagerStart;
|
||||||
notifications: NotificationsStart;
|
notifications: NotificationsStart;
|
||||||
}> = ({ ingestManager, notifications }) => {
|
}> = ({ ingestManager, notifications }) => {
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const defaultText = i18n.translate('xpack.endpoint.ingestToastMessage', {
|
const defaultText = i18n.translate('xpack.siem.endpoint.ingestToastMessage', {
|
||||||
defaultMessage: 'Ingest Manager failed during its setup.',
|
defaultMessage: 'Ingest Manager failed during its setup.',
|
||||||
});
|
});
|
||||||
|
|
||||||
const title = i18n.translate('xpack.endpoint.ingestToastTitle', {
|
const title = i18n.translate('xpack.siem.endpoint.ingestToastTitle', {
|
||||||
defaultMessage: 'App failed to initialize',
|
defaultMessage: 'App failed to initialize',
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { render, unmountComponentAtNode } from 'react-dom';
|
||||||
|
|
||||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||||
import { AppMountParameters } from '../../../../../src/core/public';
|
import { AppMountParameters } from '../../../../../src/core/public';
|
||||||
import { StartServices } from '../plugin';
|
import { StartServices } from '../types';
|
||||||
import { SiemApp } from './app';
|
import { SiemApp } from './app';
|
||||||
import { SecuritySubPlugins } from './types';
|
import { SecuritySubPlugins } from './types';
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { Route, Router, Switch } from 'react-router-dom';
|
||||||
import { NotFoundPage } from './404';
|
import { NotFoundPage } from './404';
|
||||||
import { HomePage } from './home';
|
import { HomePage } from './home';
|
||||||
import { ManageRoutesSpy } from '../common/utils/route/manage_spy_routes';
|
import { ManageRoutesSpy } from '../common/utils/route/manage_spy_routes';
|
||||||
|
import { RouteCapture } from '../common/components/endpoint/route_capture';
|
||||||
|
|
||||||
interface RouterProps {
|
interface RouterProps {
|
||||||
history: History;
|
history: History;
|
||||||
|
@ -20,14 +21,16 @@ interface RouterProps {
|
||||||
const PageRouterComponent: FC<RouterProps> = ({ history, subPluginRoutes }) => (
|
const PageRouterComponent: FC<RouterProps> = ({ history, subPluginRoutes }) => (
|
||||||
<ManageRoutesSpy>
|
<ManageRoutesSpy>
|
||||||
<Router history={history}>
|
<Router history={history}>
|
||||||
<Switch>
|
<RouteCapture>
|
||||||
<Route path="/">
|
<Switch>
|
||||||
<HomePage subPlugins={subPluginRoutes} />
|
<Route path="/">
|
||||||
</Route>
|
<HomePage subPlugins={subPluginRoutes} />
|
||||||
<Route>
|
</Route>
|
||||||
<NotFoundPage />
|
<Route>
|
||||||
</Route>
|
<NotFoundPage />
|
||||||
</Switch>
|
</Route>
|
||||||
|
</Switch>
|
||||||
|
</RouteCapture>
|
||||||
</Router>
|
</Router>
|
||||||
</ManageRoutesSpy>
|
</ManageRoutesSpy>
|
||||||
);
|
);
|
||||||
|
|
|
@ -4,12 +4,18 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Reducer, AnyAction } from 'redux';
|
import { Reducer, AnyAction, Middleware, Dispatch } from 'redux';
|
||||||
|
|
||||||
import { NavTab } from '../common/components/navigation/types';
|
import { NavTab } from '../common/components/navigation/types';
|
||||||
import { HostsState } from '../hosts/store';
|
import { HostsState } from '../hosts/store';
|
||||||
import { NetworkState } from '../network/store';
|
import { NetworkState } from '../network/store';
|
||||||
import { TimelineState } from '../timelines/store/timeline/types';
|
import { TimelineState } from '../timelines/store/timeline/types';
|
||||||
|
import { ImmutableReducer, State } from '../common/store';
|
||||||
|
import { Immutable } from '../../common/endpoint/types';
|
||||||
|
import { AlertListState } from '../../common/endpoint_alerts/types';
|
||||||
|
import { AppAction } from '../common/store/actions';
|
||||||
|
import { HostState } from '../endpoint_hosts/types';
|
||||||
|
import { PolicyDetailsState, PolicyListState } from '../endpoint_policy/types';
|
||||||
|
|
||||||
export enum SiemPageName {
|
export enum SiemPageName {
|
||||||
overview = 'overview',
|
overview = 'overview',
|
||||||
|
@ -33,13 +39,21 @@ export type SiemNavTab = Record<SiemNavTabKey, NavTab>;
|
||||||
export interface SecuritySubPluginStore<K extends SecuritySubPluginKeyStore, T> {
|
export interface SecuritySubPluginStore<K extends SecuritySubPluginKeyStore, T> {
|
||||||
initialState: Record<K, T>;
|
initialState: Record<K, T>;
|
||||||
reducer: Record<K, Reducer<T, AnyAction>>;
|
reducer: Record<K, Reducer<T, AnyAction>>;
|
||||||
|
middleware?: Middleware<{}, State, Dispatch<AppAction | Immutable<AppAction>>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SecuritySubPlugin {
|
export interface SecuritySubPlugin {
|
||||||
routes: React.ReactElement[];
|
routes: React.ReactElement[];
|
||||||
}
|
}
|
||||||
|
|
||||||
type SecuritySubPluginKeyStore = 'hosts' | 'network' | 'timeline';
|
type SecuritySubPluginKeyStore =
|
||||||
|
| 'hosts'
|
||||||
|
| 'network'
|
||||||
|
| 'timeline'
|
||||||
|
| 'hostList'
|
||||||
|
| 'alertList'
|
||||||
|
| 'policyDetails'
|
||||||
|
| 'policyList';
|
||||||
export interface SecuritySubPluginWithStore<K extends SecuritySubPluginKeyStore, T>
|
export interface SecuritySubPluginWithStore<K extends SecuritySubPluginKeyStore, T>
|
||||||
extends SecuritySubPlugin {
|
extends SecuritySubPlugin {
|
||||||
store: SecuritySubPluginStore<K, T>;
|
store: SecuritySubPluginStore<K, T>;
|
||||||
|
@ -51,11 +65,20 @@ export interface SecuritySubPlugins extends SecuritySubPlugin {
|
||||||
hosts: HostsState;
|
hosts: HostsState;
|
||||||
network: NetworkState;
|
network: NetworkState;
|
||||||
timeline: TimelineState;
|
timeline: TimelineState;
|
||||||
|
alertList: Immutable<AlertListState>;
|
||||||
|
hostList: Immutable<HostState>;
|
||||||
|
policyDetails: Immutable<PolicyDetailsState>;
|
||||||
|
policyList: Immutable<PolicyListState>;
|
||||||
};
|
};
|
||||||
reducer: {
|
reducer: {
|
||||||
hosts: Reducer<HostsState, AnyAction>;
|
hosts: Reducer<HostsState, AnyAction>;
|
||||||
network: Reducer<NetworkState, AnyAction>;
|
network: Reducer<NetworkState, AnyAction>;
|
||||||
timeline: Reducer<TimelineState, AnyAction>;
|
timeline: Reducer<TimelineState, AnyAction>;
|
||||||
|
alertList: ImmutableReducer<AlertListState, AppAction>;
|
||||||
|
hostList: ImmutableReducer<HostState, AppAction>;
|
||||||
|
policyDetails: ImmutableReducer<PolicyDetailsState, AppAction>;
|
||||||
|
policyList: ImmutableReducer<PolicyListState, AppAction>;
|
||||||
};
|
};
|
||||||
|
middlewares: Array<Middleware<{}, State, Dispatch<AppAction | Immutable<AppAction>>>>;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ exports[`PageView component should display body header custom element 1`] = `
|
||||||
background: none;
|
background: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
<Memo()
|
<PageView
|
||||||
bodyHeader={
|
bodyHeader={
|
||||||
<p>
|
<p>
|
||||||
body header
|
body header
|
||||||
|
@ -88,7 +88,7 @@ exports[`PageView component should display body header custom element 1`] = `
|
||||||
</div>
|
</div>
|
||||||
</EuiPage>
|
</EuiPage>
|
||||||
</Styled(EuiPage)>
|
</Styled(EuiPage)>
|
||||||
</Memo()>
|
</PageView>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`PageView component should display body header wrapped in EuiTitle 1`] = `
|
exports[`PageView component should display body header wrapped in EuiTitle 1`] = `
|
||||||
|
@ -112,7 +112,7 @@ exports[`PageView component should display body header wrapped in EuiTitle 1`] =
|
||||||
background: none;
|
background: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
<Memo()
|
<PageView
|
||||||
bodyHeader="body header"
|
bodyHeader="body header"
|
||||||
viewType="list"
|
viewType="list"
|
||||||
>
|
>
|
||||||
|
@ -150,7 +150,7 @@ exports[`PageView component should display body header wrapped in EuiTitle 1`] =
|
||||||
className="euiPageContentHeaderSection"
|
className="euiPageContentHeaderSection"
|
||||||
data-test-subj="pageViewBodyTitleArea"
|
data-test-subj="pageViewBodyTitleArea"
|
||||||
>
|
>
|
||||||
<Memo()>
|
<PageViewBodyHeaderTitle>
|
||||||
<EuiTitle>
|
<EuiTitle>
|
||||||
<h2
|
<h2
|
||||||
className="euiTitle euiTitle--medium"
|
className="euiTitle euiTitle--medium"
|
||||||
|
@ -159,7 +159,7 @@ exports[`PageView component should display body header wrapped in EuiTitle 1`] =
|
||||||
body header
|
body header
|
||||||
</h2>
|
</h2>
|
||||||
</EuiTitle>
|
</EuiTitle>
|
||||||
</Memo()>
|
</PageViewBodyHeaderTitle>
|
||||||
</div>
|
</div>
|
||||||
</EuiPageContentHeaderSection>
|
</EuiPageContentHeaderSection>
|
||||||
</div>
|
</div>
|
||||||
|
@ -182,7 +182,7 @@ exports[`PageView component should display body header wrapped in EuiTitle 1`] =
|
||||||
</div>
|
</div>
|
||||||
</EuiPage>
|
</EuiPage>
|
||||||
</Styled(EuiPage)>
|
</Styled(EuiPage)>
|
||||||
</Memo()>
|
</PageView>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`PageView component should display header left and right 1`] = `
|
exports[`PageView component should display header left and right 1`] = `
|
||||||
|
@ -206,7 +206,7 @@ exports[`PageView component should display header left and right 1`] = `
|
||||||
background: none;
|
background: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
<Memo()
|
<PageView
|
||||||
headerLeft="page title"
|
headerLeft="page title"
|
||||||
headerRight="right side actions"
|
headerRight="right side actions"
|
||||||
viewType="list"
|
viewType="list"
|
||||||
|
@ -237,7 +237,7 @@ exports[`PageView component should display header left and right 1`] = `
|
||||||
className="euiPageHeaderSection"
|
className="euiPageHeaderSection"
|
||||||
data-test-subj="pageViewHeaderLeft"
|
data-test-subj="pageViewHeaderLeft"
|
||||||
>
|
>
|
||||||
<Memo()>
|
<PageViewHeaderTitle>
|
||||||
<EuiTitle
|
<EuiTitle
|
||||||
size="l"
|
size="l"
|
||||||
>
|
>
|
||||||
|
@ -248,7 +248,7 @@ exports[`PageView component should display header left and right 1`] = `
|
||||||
page title
|
page title
|
||||||
</h1>
|
</h1>
|
||||||
</EuiTitle>
|
</EuiTitle>
|
||||||
</Memo()>
|
</PageViewHeaderTitle>
|
||||||
</div>
|
</div>
|
||||||
</EuiPageHeaderSection>
|
</EuiPageHeaderSection>
|
||||||
<EuiPageHeaderSection
|
<EuiPageHeaderSection
|
||||||
|
@ -291,7 +291,7 @@ exports[`PageView component should display header left and right 1`] = `
|
||||||
</div>
|
</div>
|
||||||
</EuiPage>
|
</EuiPage>
|
||||||
</Styled(EuiPage)>
|
</Styled(EuiPage)>
|
||||||
</Memo()>
|
</PageView>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`PageView component should display only body if not header props used 1`] = `
|
exports[`PageView component should display only body if not header props used 1`] = `
|
||||||
|
@ -315,7 +315,7 @@ exports[`PageView component should display only body if not header props used 1`
|
||||||
background: none;
|
background: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
<Memo()
|
<PageView
|
||||||
viewType="list"
|
viewType="list"
|
||||||
>
|
>
|
||||||
<Styled(EuiPage)
|
<Styled(EuiPage)
|
||||||
|
@ -359,7 +359,7 @@ exports[`PageView component should display only body if not header props used 1`
|
||||||
</div>
|
</div>
|
||||||
</EuiPage>
|
</EuiPage>
|
||||||
</Styled(EuiPage)>
|
</Styled(EuiPage)>
|
||||||
</Memo()>
|
</PageView>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`PageView component should display only header left 1`] = `
|
exports[`PageView component should display only header left 1`] = `
|
||||||
|
@ -383,7 +383,7 @@ exports[`PageView component should display only header left 1`] = `
|
||||||
background: none;
|
background: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
<Memo()
|
<PageView
|
||||||
headerLeft="page title"
|
headerLeft="page title"
|
||||||
viewType="list"
|
viewType="list"
|
||||||
>
|
>
|
||||||
|
@ -413,7 +413,7 @@ exports[`PageView component should display only header left 1`] = `
|
||||||
className="euiPageHeaderSection"
|
className="euiPageHeaderSection"
|
||||||
data-test-subj="pageViewHeaderLeft"
|
data-test-subj="pageViewHeaderLeft"
|
||||||
>
|
>
|
||||||
<Memo()>
|
<PageViewHeaderTitle>
|
||||||
<EuiTitle
|
<EuiTitle
|
||||||
size="l"
|
size="l"
|
||||||
>
|
>
|
||||||
|
@ -424,7 +424,7 @@ exports[`PageView component should display only header left 1`] = `
|
||||||
page title
|
page title
|
||||||
</h1>
|
</h1>
|
||||||
</EuiTitle>
|
</EuiTitle>
|
||||||
</Memo()>
|
</PageViewHeaderTitle>
|
||||||
</div>
|
</div>
|
||||||
</EuiPageHeaderSection>
|
</EuiPageHeaderSection>
|
||||||
</div>
|
</div>
|
||||||
|
@ -457,7 +457,7 @@ exports[`PageView component should display only header left 1`] = `
|
||||||
</div>
|
</div>
|
||||||
</EuiPage>
|
</EuiPage>
|
||||||
</Styled(EuiPage)>
|
</Styled(EuiPage)>
|
||||||
</Memo()>
|
</PageView>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`PageView component should display only header right but include an empty left side 1`] = `
|
exports[`PageView component should display only header right but include an empty left side 1`] = `
|
||||||
|
@ -481,7 +481,7 @@ exports[`PageView component should display only header right but include an empt
|
||||||
background: none;
|
background: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
<Memo()
|
<PageView
|
||||||
headerRight="right side actions"
|
headerRight="right side actions"
|
||||||
viewType="list"
|
viewType="list"
|
||||||
>
|
>
|
||||||
|
@ -552,7 +552,7 @@ exports[`PageView component should display only header right but include an empt
|
||||||
</div>
|
</div>
|
||||||
</EuiPage>
|
</EuiPage>
|
||||||
</Styled(EuiPage)>
|
</Styled(EuiPage)>
|
||||||
</Memo()>
|
</PageView>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`PageView component should pass through EuiPage props 1`] = `
|
exports[`PageView component should pass through EuiPage props 1`] = `
|
||||||
|
@ -576,7 +576,7 @@ exports[`PageView component should pass through EuiPage props 1`] = `
|
||||||
background: none;
|
background: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
<Memo()
|
<PageView
|
||||||
aria-label="test-aria-label-here"
|
aria-label="test-aria-label-here"
|
||||||
className="test-class-name-here"
|
className="test-class-name-here"
|
||||||
data-test-subj="test-data-test-subj-here"
|
data-test-subj="test-data-test-subj-here"
|
||||||
|
@ -637,7 +637,7 @@ exports[`PageView component should pass through EuiPage props 1`] = `
|
||||||
</div>
|
</div>
|
||||||
</EuiPage>
|
</EuiPage>
|
||||||
</Styled(EuiPage)>
|
</Styled(EuiPage)>
|
||||||
</Memo()>
|
</PageView>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`PageView component should use custom element for header left and not wrap in EuiTitle 1`] = `
|
exports[`PageView component should use custom element for header left and not wrap in EuiTitle 1`] = `
|
||||||
|
@ -661,7 +661,7 @@ exports[`PageView component should use custom element for header left and not wr
|
||||||
background: none;
|
background: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
<Memo()
|
<PageView
|
||||||
headerLeft={
|
headerLeft={
|
||||||
<p>
|
<p>
|
||||||
title here
|
title here
|
||||||
|
@ -730,5 +730,5 @@ exports[`PageView component should use custom element for header left and not wr
|
||||||
</div>
|
</div>
|
||||||
</EuiPage>
|
</EuiPage>
|
||||||
</Styled(EuiPage)>
|
</Styled(EuiPage)>
|
||||||
</Memo()>
|
</PageView>
|
||||||
`;
|
`;
|
|
@ -8,8 +8,8 @@ import React from 'react';
|
||||||
import { mount } from 'enzyme';
|
import { mount } from 'enzyme';
|
||||||
import { LinkToApp } from './link_to_app';
|
import { LinkToApp } from './link_to_app';
|
||||||
import { CoreStart } from 'kibana/public';
|
import { CoreStart } from 'kibana/public';
|
||||||
import { KibanaContextProvider } from '../../../../../../../../src/plugins/kibana_react/public';
|
import { KibanaContextProvider } from '../../../../../../../src/plugins/kibana_react/public';
|
||||||
import { coreMock } from '../../../../../../../../src/core/public/mocks';
|
import { coreMock } from '../../../../../../../src/core/public/mocks';
|
||||||
|
|
||||||
type LinkToAppOnClickMock<Return = void> = jest.Mock<
|
type LinkToAppOnClickMock<Return = void> = jest.Mock<
|
||||||
Return,
|
Return,
|
||||||
|
@ -31,13 +31,13 @@ describe('LinkToApp component', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render with minimum input', () => {
|
it('should render with minimum input', () => {
|
||||||
expect(render(<LinkToApp appId="ingestManager">link</LinkToApp>)).toMatchSnapshot();
|
expect(render(<LinkToApp appId="ingestManager">{'link'}</LinkToApp>)).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
it('should render with href', () => {
|
it('should render with href', () => {
|
||||||
expect(
|
expect(
|
||||||
render(
|
render(
|
||||||
<LinkToApp appId="ingestManager" href="/app/ingest">
|
<LinkToApp appId="ingestManager" href="/app/ingest">
|
||||||
link
|
{'link'}
|
||||||
</LinkToApp>
|
</LinkToApp>
|
||||||
)
|
)
|
||||||
).toMatchSnapshot();
|
).toMatchSnapshot();
|
||||||
|
@ -47,7 +47,7 @@ describe('LinkToApp component', () => {
|
||||||
const spyOnClickHandler: LinkToAppOnClickMock = jest.fn(_event => {});
|
const spyOnClickHandler: LinkToAppOnClickMock = jest.fn(_event => {});
|
||||||
const renderResult = render(
|
const renderResult = render(
|
||||||
<LinkToApp appId="ingestManager" href="/app/ingest" onClick={spyOnClickHandler}>
|
<LinkToApp appId="ingestManager" href="/app/ingest" onClick={spyOnClickHandler}>
|
||||||
link
|
{'link'}
|
||||||
</LinkToApp>
|
</LinkToApp>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -65,7 +65,7 @@ describe('LinkToApp component', () => {
|
||||||
it('should navigate to App with specific path', () => {
|
it('should navigate to App with specific path', () => {
|
||||||
const renderResult = render(
|
const renderResult = render(
|
||||||
<LinkToApp appId="ingestManager" appPath="/some/path" href="/app/ingest">
|
<LinkToApp appId="ingestManager" appPath="/some/path" href="/app/ingest">
|
||||||
link
|
{'link'}
|
||||||
</LinkToApp>
|
</LinkToApp>
|
||||||
);
|
);
|
||||||
renderResult.find('EuiLink').simulate('click', { button: 0 });
|
renderResult.find('EuiLink').simulate('click', { button: 0 });
|
||||||
|
@ -84,7 +84,7 @@ describe('LinkToApp component', () => {
|
||||||
color="primary"
|
color="primary"
|
||||||
data-test-subj="my-test-subject"
|
data-test-subj="my-test-subject"
|
||||||
>
|
>
|
||||||
link
|
{'link'}
|
||||||
</LinkToApp>
|
</LinkToApp>
|
||||||
);
|
);
|
||||||
expect(renderResult.find('EuiLink').props()).toEqual({
|
expect(renderResult.find('EuiLink').props()).toEqual({
|
||||||
|
@ -103,7 +103,7 @@ describe('LinkToApp component', () => {
|
||||||
});
|
});
|
||||||
const renderResult = render(
|
const renderResult = render(
|
||||||
<LinkToApp appId="ingestManager" href="/app/ingest" onClick={spyOnClickHandler}>
|
<LinkToApp appId="ingestManager" href="/app/ingest" onClick={spyOnClickHandler}>
|
||||||
link
|
{'link'}
|
||||||
</LinkToApp>
|
</LinkToApp>
|
||||||
);
|
);
|
||||||
expect(() => renderResult.find('EuiLink').simulate('click')).toThrow();
|
expect(() => renderResult.find('EuiLink').simulate('click')).toThrow();
|
||||||
|
@ -116,21 +116,21 @@ describe('LinkToApp component', () => {
|
||||||
});
|
});
|
||||||
const renderResult = render(
|
const renderResult = render(
|
||||||
<LinkToApp appId="ingestManager" href="/app/ingest" onClick={spyOnClickHandler}>
|
<LinkToApp appId="ingestManager" href="/app/ingest" onClick={spyOnClickHandler}>
|
||||||
link
|
{'link'}
|
||||||
</LinkToApp>
|
</LinkToApp>
|
||||||
);
|
);
|
||||||
renderResult.find('EuiLink').simulate('click', { button: 0 });
|
renderResult.find('EuiLink').simulate('click', { button: 0 });
|
||||||
expect(fakeCoreStart.application.navigateToApp).not.toHaveBeenCalled();
|
expect(fakeCoreStart.application.navigateToApp).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
it('should not to navigate if it was not left click', () => {
|
it('should not to navigate if it was not left click', () => {
|
||||||
const renderResult = render(<LinkToApp appId="ingestManager">link</LinkToApp>);
|
const renderResult = render(<LinkToApp appId="ingestManager">{'link'}</LinkToApp>);
|
||||||
renderResult.find('EuiLink').simulate('click', { button: 1 });
|
renderResult.find('EuiLink').simulate('click', { button: 1 });
|
||||||
expect(fakeCoreStart.application.navigateToApp).not.toHaveBeenCalled();
|
expect(fakeCoreStart.application.navigateToApp).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
it('should not to navigate if it includes an anchor target', () => {
|
it('should not to navigate if it includes an anchor target', () => {
|
||||||
const renderResult = render(
|
const renderResult = render(
|
||||||
<LinkToApp appId="ingestManager" target="_blank" href="/some/path">
|
<LinkToApp appId="ingestManager" target="_blank" href="/some/path">
|
||||||
link
|
{'link'}
|
||||||
</LinkToApp>
|
</LinkToApp>
|
||||||
);
|
);
|
||||||
renderResult.find('EuiLink').simulate('click', { button: 0 });
|
renderResult.find('EuiLink').simulate('click', { button: 0 });
|
||||||
|
@ -139,7 +139,7 @@ describe('LinkToApp component', () => {
|
||||||
it('should not to navigate if if meta|alt|ctrl|shift keys are pressed', () => {
|
it('should not to navigate if if meta|alt|ctrl|shift keys are pressed', () => {
|
||||||
const renderResult = render(
|
const renderResult = render(
|
||||||
<LinkToApp appId="ingestManager" target="_blank">
|
<LinkToApp appId="ingestManager" target="_blank">
|
||||||
link
|
{'link'}
|
||||||
</LinkToApp>
|
</LinkToApp>
|
||||||
);
|
);
|
||||||
const euiLink = renderResult.find('EuiLink');
|
const euiLink = renderResult.find('EuiLink');
|
|
@ -5,15 +5,15 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { memo, MouseEventHandler } from 'react';
|
import React, { memo, MouseEventHandler } from 'react';
|
||||||
import { EuiLink } from '@elastic/eui';
|
import { EuiLink, EuiLinkProps } from '@elastic/eui';
|
||||||
import { EuiLinkProps } from '@elastic/eui';
|
import { useNavigateToAppEventHandler } from '../../hooks/endpoint/use_navigate_to_app_event_handler';
|
||||||
import { useNavigateToAppEventHandler } from '../hooks/use_navigate_to_app_event_handler';
|
|
||||||
|
|
||||||
type LinkToAppProps = EuiLinkProps & {
|
type LinkToAppProps = EuiLinkProps & {
|
||||||
/** the app id - normally the value of the `id` in that plugin's `kibana.json` */
|
/** the app id - normally the value of the `id` in that plugin's `kibana.json` */
|
||||||
appId: string;
|
appId: string;
|
||||||
/** Any app specific path (route) */
|
/** Any app specific path (route) */
|
||||||
appPath?: string;
|
appPath?: string;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
appState?: any;
|
appState?: any;
|
||||||
onClick?: MouseEventHandler<HTMLButtonElement | HTMLAnchorElement>;
|
onClick?: MouseEventHandler<HTMLButtonElement | HTMLAnchorElement>;
|
||||||
};
|
};
|
|
@ -7,20 +7,20 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { mount } from 'enzyme';
|
import { mount } from 'enzyme';
|
||||||
import { PageView } from './page_view';
|
import { PageView } from './page_view';
|
||||||
import { EuiThemeProvider } from '../../../../../../../legacy/common/eui_styled_components';
|
import { EuiThemeProvider } from '../../../../../../legacy/common/eui_styled_components';
|
||||||
|
|
||||||
describe('PageView component', () => {
|
describe('PageView component', () => {
|
||||||
const render = (ui: Parameters<typeof mount>[0]) =>
|
const render = (ui: Parameters<typeof mount>[0]) =>
|
||||||
mount(ui, { wrappingComponent: EuiThemeProvider });
|
mount(ui, { wrappingComponent: EuiThemeProvider });
|
||||||
|
|
||||||
it('should display only body if not header props used', () => {
|
it('should display only body if not header props used', () => {
|
||||||
expect(render(<PageView viewType="list">body content</PageView>)).toMatchSnapshot();
|
expect(render(<PageView viewType="list">{'body content'}</PageView>)).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
it('should display header left and right', () => {
|
it('should display header left and right', () => {
|
||||||
expect(
|
expect(
|
||||||
render(
|
render(
|
||||||
<PageView viewType="list" headerLeft="page title" headerRight="right side actions">
|
<PageView viewType="list" headerLeft="page title" headerRight="right side actions">
|
||||||
body content
|
{'body content'}
|
||||||
</PageView>
|
</PageView>
|
||||||
)
|
)
|
||||||
).toMatchSnapshot();
|
).toMatchSnapshot();
|
||||||
|
@ -29,7 +29,7 @@ describe('PageView component', () => {
|
||||||
expect(
|
expect(
|
||||||
render(
|
render(
|
||||||
<PageView viewType="list" headerLeft="page title">
|
<PageView viewType="list" headerLeft="page title">
|
||||||
body content
|
{'body content'}
|
||||||
</PageView>
|
</PageView>
|
||||||
)
|
)
|
||||||
).toMatchSnapshot();
|
).toMatchSnapshot();
|
||||||
|
@ -38,7 +38,7 @@ describe('PageView component', () => {
|
||||||
expect(
|
expect(
|
||||||
render(
|
render(
|
||||||
<PageView viewType="list" headerRight="right side actions">
|
<PageView viewType="list" headerRight="right side actions">
|
||||||
body content
|
{'body content'}
|
||||||
</PageView>
|
</PageView>
|
||||||
)
|
)
|
||||||
).toMatchSnapshot();
|
).toMatchSnapshot();
|
||||||
|
@ -46,8 +46,8 @@ describe('PageView component', () => {
|
||||||
it(`should use custom element for header left and not wrap in EuiTitle`, () => {
|
it(`should use custom element for header left and not wrap in EuiTitle`, () => {
|
||||||
expect(
|
expect(
|
||||||
render(
|
render(
|
||||||
<PageView viewType="list" headerLeft={<p>title here</p>}>
|
<PageView viewType="list" headerLeft={<p>{'title here'}</p>}>
|
||||||
body content
|
{'body content'}
|
||||||
</PageView>
|
</PageView>
|
||||||
)
|
)
|
||||||
).toMatchSnapshot();
|
).toMatchSnapshot();
|
||||||
|
@ -56,7 +56,7 @@ describe('PageView component', () => {
|
||||||
expect(
|
expect(
|
||||||
render(
|
render(
|
||||||
<PageView viewType="list" bodyHeader="body header">
|
<PageView viewType="list" bodyHeader="body header">
|
||||||
body content
|
{'body content'}
|
||||||
</PageView>
|
</PageView>
|
||||||
)
|
)
|
||||||
).toMatchSnapshot();
|
).toMatchSnapshot();
|
||||||
|
@ -64,8 +64,8 @@ describe('PageView component', () => {
|
||||||
it('should display body header custom element', () => {
|
it('should display body header custom element', () => {
|
||||||
expect(
|
expect(
|
||||||
render(
|
render(
|
||||||
<PageView viewType="list" bodyHeader={<p>body header</p>}>
|
<PageView viewType="list" bodyHeader={<p>{'body header'}</p>}>
|
||||||
body content
|
{'body content'}
|
||||||
</PageView>
|
</PageView>
|
||||||
)
|
)
|
||||||
).toMatchSnapshot();
|
).toMatchSnapshot();
|
||||||
|
@ -80,7 +80,7 @@ describe('PageView component', () => {
|
||||||
aria-label="test-aria-label-here"
|
aria-label="test-aria-label-here"
|
||||||
data-test-subj="test-data-test-subj-here"
|
data-test-subj="test-data-test-subj-here"
|
||||||
>
|
>
|
||||||
body content
|
{'body content'}
|
||||||
</PageView>
|
</PageView>
|
||||||
)
|
)
|
||||||
).toMatchSnapshot();
|
).toMatchSnapshot();
|
|
@ -56,6 +56,8 @@ export const PageViewHeaderTitle = memo<{ children: ReactNode }>(({ children })
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
PageViewHeaderTitle.displayName = 'PageViewHeaderTitle';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `PageView` component used to render `bodyHeader` when it is set as a `string`
|
* The `PageView` component used to render `bodyHeader` when it is set as a `string`
|
||||||
* Can be used when wanting to customize the `bodyHeader` value but still use the standard
|
* Can be used when wanting to customize the `bodyHeader` value but still use the standard
|
||||||
|
@ -70,6 +72,7 @@ export const PageViewBodyHeaderTitle = memo<{ children: ReactNode }>(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
PageViewBodyHeaderTitle.displayName = 'PageViewBodyHeaderTitle';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page View layout for use in Endpoint
|
* Page View layout for use in Endpoint
|
||||||
|
@ -135,3 +138,5 @@ export const PageView = memo<
|
||||||
</StyledEuiPage>
|
</StyledEuiPage>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
PageView.displayName = 'PageView';
|
|
@ -7,15 +7,18 @@
|
||||||
import React, { memo } from 'react';
|
import React, { memo } from 'react';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { EndpointAppLocation, AppAction } from '../types';
|
import { AppLocation } from '../../../../common/endpoint/types';
|
||||||
|
import { AppAction } from '../../store/actions';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component should be used above all routes, but below the Provider.
|
* This component should be used above all routes, but below the Provider.
|
||||||
* It dispatches actions when the URL is changed.
|
* It dispatches actions when the URL is changed.
|
||||||
*/
|
*/
|
||||||
export const RouteCapture = memo(({ children }) => {
|
export const RouteCapture = memo(({ children }) => {
|
||||||
const location: EndpointAppLocation = useLocation();
|
const location: AppLocation = useLocation();
|
||||||
const dispatch: (action: AppAction) => unknown = useDispatch();
|
const dispatch: (action: AppAction) => unknown = useDispatch();
|
||||||
dispatch({ type: 'userChangedUrl', payload: location });
|
dispatch({ type: 'userChangedUrl', payload: location });
|
||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
RouteCapture.displayName = 'RouteCapture';
|
|
@ -9,7 +9,7 @@ import { getOr, omit } from 'lodash/fp';
|
||||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||||
import { ChromeBreadcrumb } from '../../../../../../../../src/core/public';
|
import { ChromeBreadcrumb } from '../../../../../../../../src/core/public';
|
||||||
import { APP_NAME } from '../../../../../common/constants';
|
import { APP_NAME } from '../../../../../common/constants';
|
||||||
import { StartServices } from '../../../../plugin';
|
import { StartServices } from '../../../../types';
|
||||||
import { getBreadcrumbs as getHostDetailsBreadcrumbs } from '../../../../hosts/pages/details/utils';
|
import { getBreadcrumbs as getHostDetailsBreadcrumbs } from '../../../../hosts/pages/details/utils';
|
||||||
import { getBreadcrumbs as getIPDetailsBreadcrumbs } from '../../../../network/pages/ip_details';
|
import { getBreadcrumbs as getIPDetailsBreadcrumbs } from '../../../../network/pages/ip_details';
|
||||||
import { getBreadcrumbs as getCaseDetailsBreadcrumbs } from '../../../../cases/pages/utils';
|
import { getBreadcrumbs as getCaseDetailsBreadcrumbs } from '../../../../cases/pages/utils';
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { StartServices } from '../../../plugin';
|
import { StartServices } from '../../../types';
|
||||||
import { IndexPatternSavedObject, IndexPatternSavedObjectAttributes } from '../types';
|
import { IndexPatternSavedObject, IndexPatternSavedObjectAttributes } from '../types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { AppContextTestRender, createAppRootMockRenderer } from '../../mocks';
|
import { AppContextTestRender, createAppRootMockRenderer } from '../../mock/endpoint';
|
||||||
import { useNavigateByRouterEventHandler } from './use_navigate_by_router_event_handler';
|
import { useNavigateByRouterEventHandler } from './use_navigate_by_router_event_handler';
|
||||||
import { act, fireEvent, cleanup } from '@testing-library/react';
|
import { act, fireEvent, cleanup } from '@testing-library/react';
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ describe('useNavigateByRouterEventHandler hook', () => {
|
||||||
let renderResult: ReturnType<AppContextTestRender['render']>;
|
let renderResult: ReturnType<AppContextTestRender['render']>;
|
||||||
let linkEle: HTMLAnchorElement;
|
let linkEle: HTMLAnchorElement;
|
||||||
let clickHandlerSpy: ClickHandlerMock;
|
let clickHandlerSpy: ClickHandlerMock;
|
||||||
|
// eslint-disable-next-line react/display-name
|
||||||
const Link = React.memo<{
|
const Link = React.memo<{
|
||||||
routeTo: Parameters<typeof useNavigateByRouterEventHandler>[0];
|
routeTo: Parameters<typeof useNavigateByRouterEventHandler>[0];
|
||||||
onClick?: Parameters<typeof useNavigateByRouterEventHandler>[1];
|
onClick?: Parameters<typeof useNavigateByRouterEventHandler>[1];
|
||||||
|
@ -26,7 +27,7 @@ describe('useNavigateByRouterEventHandler hook', () => {
|
||||||
const onClickHandler = useNavigateByRouterEventHandler(routeTo, onClick);
|
const onClickHandler = useNavigateByRouterEventHandler(routeTo, onClick);
|
||||||
return (
|
return (
|
||||||
<a href="/mock/path" onClick={onClickHandler}>
|
<a href="/mock/path" onClick={onClickHandler}>
|
||||||
mock link
|
{'mock link'}
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
});
|
});
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
import { MouseEventHandler, useCallback } from 'react';
|
import { MouseEventHandler, useCallback } from 'react';
|
||||||
import { ApplicationStart } from 'kibana/public';
|
import { ApplicationStart } from 'kibana/public';
|
||||||
import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public';
|
import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
|
||||||
|
|
||||||
type NavigateToAppHandlerProps = Parameters<ApplicationStart['navigateToApp']>;
|
type NavigateToAppHandlerProps = Parameters<ApplicationStart['navigateToApp']>;
|
||||||
type EventHandlerCallback = MouseEventHandler<HTMLButtonElement | HTMLAnchorElement>;
|
type EventHandlerCallback = MouseEventHandler<HTMLButtonElement | HTMLAnchorElement>;
|
|
@ -12,7 +12,7 @@ import {
|
||||||
useUiSetting$,
|
useUiSetting$,
|
||||||
withKibana,
|
withKibana,
|
||||||
} from '../../../../../../../src/plugins/kibana_react/public';
|
} from '../../../../../../../src/plugins/kibana_react/public';
|
||||||
import { StartServices } from '../../../plugin';
|
import { StartServices } from '../../../types';
|
||||||
|
|
||||||
export type KibanaContext = KibanaReactContextValue<StartServices>;
|
export type KibanaContext = KibanaReactContextValue<StartServices>;
|
||||||
export interface WithKibanaProps {
|
export interface WithKibanaProps {
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
import { METRIC_TYPE, UiStatsMetricType } from '@kbn/analytics';
|
import { METRIC_TYPE, UiStatsMetricType } from '@kbn/analytics';
|
||||||
|
|
||||||
import { SetupPlugins } from '../../../plugin';
|
import { SetupPlugins } from '../../../types';
|
||||||
export { telemetryMiddleware } from './middleware';
|
export { telemetryMiddleware } from './middleware';
|
||||||
|
|
||||||
export { METRIC_TYPE };
|
export { METRIC_TYPE };
|
||||||
|
|
|
@ -7,12 +7,20 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { createMemoryHistory } from 'history';
|
import { createMemoryHistory } from 'history';
|
||||||
import { render as reactRender, RenderOptions, RenderResult } from '@testing-library/react';
|
import { render as reactRender, RenderOptions, RenderResult } from '@testing-library/react';
|
||||||
import { appStoreFactory } from '../store';
|
import { Store } from 'redux';
|
||||||
|
|
||||||
import { coreMock } from '../../../../../../../src/core/public/mocks';
|
import { coreMock } from '../../../../../../../src/core/public/mocks';
|
||||||
import { EndpointPluginStartDependencies } from '../../../plugin';
|
import { StartPlugins } from '../../../types';
|
||||||
import { depsStartMock } from './dependencies_start_mock';
|
import { depsStartMock } from './dependencies_start_mock';
|
||||||
import { AppRootProvider } from '../view/app_root_provider';
|
import { MiddlewareActionSpyHelper, createSpyMiddleware } from '../../store/test_utils';
|
||||||
import { createSpyMiddleware, MiddlewareActionSpyHelper } from '../store/test_utils';
|
import { apolloClientObservable } from '../test_providers';
|
||||||
|
import { createStore, State, substateMiddlewareFactory } from '../../store';
|
||||||
|
import { hostMiddlewareFactory } from '../../../endpoint_hosts/store/middleware';
|
||||||
|
import { policyListMiddlewareFactory } from '../../../endpoint_policy/store/policy_list/middleware';
|
||||||
|
import { policyDetailsMiddlewareFactory } from '../../../endpoint_policy/store/policy_details/middleware';
|
||||||
|
import { alertMiddlewareFactory } from '../../../endpoint_alerts/store/middleware';
|
||||||
|
import { AppRootProvider } from './app_root_provider';
|
||||||
|
import { SUB_PLUGINS_REDUCER, mockGlobalState } from '..';
|
||||||
|
|
||||||
type UiRender = (ui: React.ReactElement, options?: RenderOptions) => RenderResult;
|
type UiRender = (ui: React.ReactElement, options?: RenderOptions) => RenderResult;
|
||||||
|
|
||||||
|
@ -20,15 +28,16 @@ type UiRender = (ui: React.ReactElement, options?: RenderOptions) => RenderResul
|
||||||
* Mocked app root context renderer
|
* Mocked app root context renderer
|
||||||
*/
|
*/
|
||||||
export interface AppContextTestRender {
|
export interface AppContextTestRender {
|
||||||
store: ReturnType<typeof appStoreFactory>;
|
store: Store<State>;
|
||||||
history: ReturnType<typeof createMemoryHistory>;
|
history: ReturnType<typeof createMemoryHistory>;
|
||||||
coreStart: ReturnType<typeof coreMock.createStart>;
|
coreStart: ReturnType<typeof coreMock.createStart>;
|
||||||
depsStart: EndpointPluginStartDependencies;
|
depsStart: Pick<StartPlugins, 'data' | 'ingestManager'>;
|
||||||
middlewareSpy: MiddlewareActionSpyHelper;
|
middlewareSpy: MiddlewareActionSpyHelper;
|
||||||
/**
|
/**
|
||||||
* A wrapper around `AppRootContext` component. Uses the mocked modules as input to the
|
* A wrapper around `AppRootContext` component. Uses the mocked modules as input to the
|
||||||
* `AppRootContext`
|
* `AppRootContext`
|
||||||
*/
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
AppWrapper: React.FC<any>;
|
AppWrapper: React.FC<any>;
|
||||||
/**
|
/**
|
||||||
* Renders the given UI within the created `AppWrapper` providing the given UI a mocked
|
* Renders the given UI within the created `AppWrapper` providing the given UI a mocked
|
||||||
|
@ -48,12 +57,28 @@ export const createAppRootMockRenderer = (): AppContextTestRender => {
|
||||||
const coreStart = coreMock.createStart({ basePath: '/mock' });
|
const coreStart = coreMock.createStart({ basePath: '/mock' });
|
||||||
const depsStart = depsStartMock();
|
const depsStart = depsStartMock();
|
||||||
const middlewareSpy = createSpyMiddleware();
|
const middlewareSpy = createSpyMiddleware();
|
||||||
const store = appStoreFactory({
|
const state: State = mockGlobalState;
|
||||||
coreStart,
|
const store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, [
|
||||||
depsStart,
|
substateMiddlewareFactory(
|
||||||
additionalMiddleware: [middlewareSpy.actionSpyMiddleware],
|
globalState => globalState.hostList,
|
||||||
});
|
hostMiddlewareFactory(coreStart, depsStart)
|
||||||
const AppWrapper: React.FunctionComponent<{ children: React.ReactElement }> = ({ children }) => (
|
),
|
||||||
|
substateMiddlewareFactory(
|
||||||
|
globalState => globalState.policyList,
|
||||||
|
policyListMiddlewareFactory(coreStart, depsStart)
|
||||||
|
),
|
||||||
|
substateMiddlewareFactory(
|
||||||
|
globalState => globalState.policyDetails,
|
||||||
|
policyDetailsMiddlewareFactory(coreStart, depsStart)
|
||||||
|
),
|
||||||
|
substateMiddlewareFactory(
|
||||||
|
globalState => globalState.alertList,
|
||||||
|
alertMiddlewareFactory(coreStart, depsStart)
|
||||||
|
),
|
||||||
|
middlewareSpy.actionSpyMiddleware,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const AppWrapper: React.FC<{ children: React.ReactElement }> = ({ children }) => (
|
||||||
<AppRootProvider store={store} history={history} coreStart={coreStart} depsStart={depsStart}>
|
<AppRootProvider store={store} history={history} coreStart={coreStart} depsStart={depsStart}>
|
||||||
{children}
|
{children}
|
||||||
</AppRootProvider>
|
</AppRootProvider>
|
||||||
|
@ -61,7 +86,7 @@ export const createAppRootMockRenderer = (): AppContextTestRender => {
|
||||||
const render: UiRender = (ui, options) => {
|
const render: UiRender = (ui, options) => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
return reactRender(ui, {
|
return reactRender(ui, {
|
||||||
wrapper: AppWrapper,
|
wrapper: AppWrapper as React.ComponentType,
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
};
|
};
|
|
@ -9,22 +9,22 @@ import { Provider } from 'react-redux';
|
||||||
import { I18nProvider } from '@kbn/i18n/react';
|
import { I18nProvider } from '@kbn/i18n/react';
|
||||||
import { Router } from 'react-router-dom';
|
import { Router } from 'react-router-dom';
|
||||||
import { History } from 'history';
|
import { History } from 'history';
|
||||||
import { CoreStart } from 'kibana/public';
|
|
||||||
import { useObservable } from 'react-use';
|
import { useObservable } from 'react-use';
|
||||||
|
import { Store } from 'redux';
|
||||||
import { EuiThemeProvider } from '../../../../../../legacy/common/eui_styled_components';
|
import { EuiThemeProvider } from '../../../../../../legacy/common/eui_styled_components';
|
||||||
import { KibanaContextProvider } from '../../../../../../../src/plugins/kibana_react/public';
|
import { KibanaContextProvider } from '../../../../../../../src/plugins/kibana_react/public';
|
||||||
import { appStoreFactory } from '../store';
|
import { RouteCapture } from '../../components/endpoint/route_capture';
|
||||||
import { RouteCapture } from './route_capture';
|
import { StartPlugins } from '../../../types';
|
||||||
import { EndpointPluginStartDependencies } from '../../../plugin';
|
import { CoreStart } from '../../../../../../../src/core/public';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides the context for rendering the endpoint app
|
* Provides the context for rendering the endpoint app
|
||||||
*/
|
*/
|
||||||
export const AppRootProvider = memo<{
|
export const AppRootProvider = memo<{
|
||||||
store: ReturnType<typeof appStoreFactory>;
|
store: Store;
|
||||||
history: History;
|
history: History;
|
||||||
coreStart: CoreStart;
|
coreStart: CoreStart;
|
||||||
depsStart: EndpointPluginStartDependencies;
|
depsStart: Pick<StartPlugins, 'data' | 'ingestManager'>;
|
||||||
children: ReactNode | ReactNode[];
|
children: ReactNode | ReactNode[];
|
||||||
}>(
|
}>(
|
||||||
({
|
({
|
||||||
|
@ -56,3 +56,5 @@ export const AppRootProvider = memo<{
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
AppRootProvider.displayName = 'AppRootProvider';
|
|
@ -25,6 +25,15 @@ import {
|
||||||
} from '../../../common/constants';
|
} from '../../../common/constants';
|
||||||
import { networkModel } from '../../network/store';
|
import { networkModel } from '../../network/store';
|
||||||
import { TimelineType } from '../../../common/types/timeline';
|
import { TimelineType } from '../../../common/types/timeline';
|
||||||
|
import { initialPolicyListState } from '../../endpoint_policy/store/policy_list/reducer';
|
||||||
|
import { initialAlertListState } from '../../endpoint_alerts/store/reducer';
|
||||||
|
import { initialPolicyDetailsState } from '../../endpoint_policy/store/policy_details/reducer';
|
||||||
|
import { initialHostListState } from '../../endpoint_hosts/store/reducer';
|
||||||
|
|
||||||
|
const policyList = initialPolicyListState();
|
||||||
|
const alertList = initialAlertListState();
|
||||||
|
const policyDetails = initialPolicyDetailsState();
|
||||||
|
const hostList = initialHostListState();
|
||||||
|
|
||||||
export const mockGlobalState: State = {
|
export const mockGlobalState: State = {
|
||||||
app: {
|
app: {
|
||||||
|
@ -225,4 +234,8 @@ export const mockGlobalState: State = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
alertList,
|
||||||
|
hostList,
|
||||||
|
policyList,
|
||||||
|
policyDetails,
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,6 +7,10 @@
|
||||||
import { hostsReducer } from '../../hosts/store';
|
import { hostsReducer } from '../../hosts/store';
|
||||||
import { networkReducer } from '../../network/store';
|
import { networkReducer } from '../../network/store';
|
||||||
import { timelineReducer } from '../../timelines/store/timeline/reducer';
|
import { timelineReducer } from '../../timelines/store/timeline/reducer';
|
||||||
|
import { hostListReducer } from '../../endpoint_hosts/store';
|
||||||
|
import { alertListReducer } from '../../endpoint_alerts/store';
|
||||||
|
import { policyListReducer } from '../../endpoint_policy/store/policy_list';
|
||||||
|
import { policyDetailsReducer } from '../../endpoint_policy/store/policy_details';
|
||||||
|
|
||||||
interface Global extends NodeJS.Global {
|
interface Global extends NodeJS.Global {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
@ -19,4 +23,8 @@ export const SUB_PLUGINS_REDUCER = {
|
||||||
hosts: hostsReducer,
|
hosts: hostsReducer,
|
||||||
network: networkReducer,
|
network: networkReducer,
|
||||||
timeline: timelineReducer,
|
timeline: timelineReducer,
|
||||||
|
hostList: hostListReducer,
|
||||||
|
alertList: alertListReducer,
|
||||||
|
policyList: policyListReducer,
|
||||||
|
policyDetails: policyDetailsReducer,
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,6 +4,19 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { HostAction } from '../../endpoint_hosts/store/action';
|
||||||
|
import { AlertAction } from '../../endpoint_alerts/store/action';
|
||||||
|
import { PolicyListAction } from '../../endpoint_policy/store/policy_list';
|
||||||
|
import { PolicyDetailsAction } from '../../endpoint_policy/store/policy_details';
|
||||||
|
|
||||||
export { appActions } from './app';
|
export { appActions } from './app';
|
||||||
export { dragAndDropActions } from './drag_and_drop';
|
export { dragAndDropActions } from './drag_and_drop';
|
||||||
export { inputsActions } from './inputs';
|
export { inputsActions } from './inputs';
|
||||||
|
import { RoutingAction } from './routing';
|
||||||
|
|
||||||
|
export type AppAction =
|
||||||
|
| HostAction
|
||||||
|
| AlertAction
|
||||||
|
| RoutingAction
|
||||||
|
| PolicyListAction
|
||||||
|
| PolicyDetailsAction;
|
||||||
|
|
|
@ -9,5 +9,19 @@ export * from './reducer';
|
||||||
export * from './selectors';
|
export * from './selectors';
|
||||||
|
|
||||||
import { createStore, getStore } from './store';
|
import { createStore, getStore } from './store';
|
||||||
|
import { SubstateMiddlewareFactory } from './types';
|
||||||
|
|
||||||
export { createStore, getStore };
|
export { createStore, getStore };
|
||||||
|
|
||||||
|
export const substateMiddlewareFactory: SubstateMiddlewareFactory = (selector, middleware) => {
|
||||||
|
return api => {
|
||||||
|
const substateAPI = {
|
||||||
|
...api,
|
||||||
|
// Return just the substate instead of global state.
|
||||||
|
getState() {
|
||||||
|
return selector(api.getState());
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return middleware(substateAPI);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
|
@ -13,8 +13,28 @@ import { createInitialInputsState, initialInputsState, inputsReducer, InputsStat
|
||||||
import { HostsPluginState, HostsPluginReducer } from '../../hosts/store';
|
import { HostsPluginState, HostsPluginReducer } from '../../hosts/store';
|
||||||
import { NetworkPluginState, NetworkPluginReducer } from '../../network/store';
|
import { NetworkPluginState, NetworkPluginReducer } from '../../network/store';
|
||||||
import { TimelinePluginState, TimelinePluginReducer } from '../../timelines/store/timeline';
|
import { TimelinePluginState, TimelinePluginReducer } from '../../timelines/store/timeline';
|
||||||
|
import {
|
||||||
|
EndpointAlertsPluginState,
|
||||||
|
EndpointAlertsPluginReducer,
|
||||||
|
} from '../../endpoint_alerts/store';
|
||||||
|
import { EndpointHostsPluginState, EndpointHostsPluginReducer } from '../../endpoint_hosts/store';
|
||||||
|
import {
|
||||||
|
EndpointPolicyDetailsStatePluginState,
|
||||||
|
EndpointPolicyDetailsStatePluginReducer,
|
||||||
|
} from '../../endpoint_policy/store/policy_details';
|
||||||
|
import {
|
||||||
|
EndpointPolicyListStatePluginState,
|
||||||
|
EndpointPolicyListStatePluginReducer,
|
||||||
|
} from '../../endpoint_policy/store/policy_list';
|
||||||
|
|
||||||
export interface State extends HostsPluginState, NetworkPluginState, TimelinePluginState {
|
export interface State
|
||||||
|
extends HostsPluginState,
|
||||||
|
NetworkPluginState,
|
||||||
|
TimelinePluginState,
|
||||||
|
EndpointAlertsPluginState,
|
||||||
|
EndpointHostsPluginState,
|
||||||
|
EndpointPolicyDetailsStatePluginState,
|
||||||
|
EndpointPolicyListStatePluginState {
|
||||||
app: AppState;
|
app: AppState;
|
||||||
dragAndDrop: DragAndDropState;
|
dragAndDrop: DragAndDropState;
|
||||||
inputs: InputsState;
|
inputs: InputsState;
|
||||||
|
@ -26,10 +46,20 @@ export const initialState: Pick<State, 'app' | 'dragAndDrop' | 'inputs'> = {
|
||||||
inputs: initialInputsState,
|
inputs: initialInputsState,
|
||||||
};
|
};
|
||||||
|
|
||||||
type SubPluginsInitState = HostsPluginState & NetworkPluginState & TimelinePluginState;
|
type SubPluginsInitState = HostsPluginState &
|
||||||
|
NetworkPluginState &
|
||||||
|
TimelinePluginState &
|
||||||
|
EndpointAlertsPluginState &
|
||||||
|
EndpointHostsPluginState &
|
||||||
|
EndpointPolicyDetailsStatePluginState &
|
||||||
|
EndpointPolicyListStatePluginState;
|
||||||
export type SubPluginsInitReducer = HostsPluginReducer &
|
export type SubPluginsInitReducer = HostsPluginReducer &
|
||||||
NetworkPluginReducer &
|
NetworkPluginReducer &
|
||||||
TimelinePluginReducer;
|
TimelinePluginReducer &
|
||||||
|
EndpointAlertsPluginReducer &
|
||||||
|
EndpointHostsPluginReducer &
|
||||||
|
EndpointPolicyDetailsStatePluginReducer &
|
||||||
|
EndpointPolicyListStatePluginReducer;
|
||||||
|
|
||||||
export const createInitialState = (pluginsInitState: SubPluginsInitState): State => ({
|
export const createInitialState = (pluginsInitState: SubPluginsInitState): State => ({
|
||||||
...initialState,
|
...initialState,
|
||||||
|
|
|
@ -4,12 +4,11 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Immutable } from '../../../../../common/types';
|
import { AppLocation, Immutable } from '../../../../common/endpoint/types';
|
||||||
import { EndpointAppLocation } from '../../types';
|
|
||||||
|
|
||||||
interface UserChangedUrl {
|
interface UserChangedUrl {
|
||||||
readonly type: 'userChangedUrl';
|
readonly type: 'userChangedUrl';
|
||||||
readonly payload: Immutable<EndpointAppLocation>;
|
readonly payload: Immutable<AppLocation>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RoutingAction = UserChangedUrl;
|
export type RoutingAction = UserChangedUrl;
|
|
@ -4,7 +4,15 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Action, applyMiddleware, compose, createStore as createReduxStore, Store } from 'redux';
|
import {
|
||||||
|
Action,
|
||||||
|
applyMiddleware,
|
||||||
|
compose,
|
||||||
|
createStore as createReduxStore,
|
||||||
|
Store,
|
||||||
|
Middleware,
|
||||||
|
Dispatch,
|
||||||
|
} from 'redux';
|
||||||
|
|
||||||
import { createEpicMiddleware } from 'redux-observable';
|
import { createEpicMiddleware } from 'redux-observable';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
|
@ -16,6 +24,8 @@ import { inputsSelectors } from './inputs';
|
||||||
import { State, SubPluginsInitReducer, createReducer } from './reducer';
|
import { State, SubPluginsInitReducer, createReducer } from './reducer';
|
||||||
import { createRootEpic } from './epic';
|
import { createRootEpic } from './epic';
|
||||||
import { AppApolloClient } from '../lib/lib';
|
import { AppApolloClient } from '../lib/lib';
|
||||||
|
import { AppAction } from './actions';
|
||||||
|
import { Immutable } from '../../../common/endpoint/types';
|
||||||
|
|
||||||
type ComposeType = typeof compose;
|
type ComposeType = typeof compose;
|
||||||
declare global {
|
declare global {
|
||||||
|
@ -28,7 +38,8 @@ export { SubPluginsInitReducer };
|
||||||
export const createStore = (
|
export const createStore = (
|
||||||
state: State,
|
state: State,
|
||||||
pluginsReducer: SubPluginsInitReducer,
|
pluginsReducer: SubPluginsInitReducer,
|
||||||
apolloClient: Observable<AppApolloClient>
|
apolloClient: Observable<AppApolloClient>,
|
||||||
|
additionalMiddleware?: Array<Middleware<{}, State, Dispatch<AppAction | Immutable<AppAction>>>>
|
||||||
): Store<State, Action> => {
|
): Store<State, Action> => {
|
||||||
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
|
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
|
||||||
|
|
||||||
|
@ -49,7 +60,9 @@ export const createStore = (
|
||||||
store = createReduxStore(
|
store = createReduxStore(
|
||||||
createReducer(pluginsReducer),
|
createReducer(pluginsReducer),
|
||||||
state,
|
state,
|
||||||
composeEnhancers(applyMiddleware(epicMiddleware, telemetryMiddleware))
|
composeEnhancers(
|
||||||
|
applyMiddleware(epicMiddleware, telemetryMiddleware, ...(additionalMiddleware ?? []))
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
epicMiddleware.run(createRootEpic<State>());
|
epicMiddleware.run(createRootEpic<State>());
|
||||||
|
|
|
@ -5,12 +5,14 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Dispatch } from 'redux';
|
import { Dispatch } from 'redux';
|
||||||
import { AppAction, GlobalState, ImmutableMiddlewareFactory } from '../types';
|
import { State } from './reducer';
|
||||||
|
import { AppAction } from './actions';
|
||||||
|
import { ImmutableMiddlewareFactory } from './types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utilities for testing Redux middleware
|
* Utilities for testing Redux middleware
|
||||||
*/
|
*/
|
||||||
export interface MiddlewareActionSpyHelper<S = GlobalState, A extends AppAction = AppAction> {
|
export interface MiddlewareActionSpyHelper<S = State, A extends AppAction = AppAction> {
|
||||||
/**
|
/**
|
||||||
* Returns a promise that is fulfilled when the given action is dispatched or a timeout occurs.
|
* Returns a promise that is fulfilled when the given action is dispatched or a timeout occurs.
|
||||||
* The `action` will given to the promise `resolve` thus allowing for checks to be done.
|
* The `action` will given to the promise `resolve` thus allowing for checks to be done.
|
||||||
|
@ -67,7 +69,7 @@ export interface MiddlewareActionSpyHelper<S = GlobalState, A extends AppAction
|
||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
export const createSpyMiddleware = <
|
export const createSpyMiddleware = <
|
||||||
S = GlobalState,
|
S = State,
|
||||||
A extends AppAction = AppAction
|
A extends AppAction = AppAction
|
||||||
>(): MiddlewareActionSpyHelper<S, A> => {
|
>(): MiddlewareActionSpyHelper<S, A> => {
|
||||||
type ActionWatcher = (action: A) => void;
|
type ActionWatcher = (action: A) => void;
|
|
@ -4,6 +4,20 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
Dispatch,
|
||||||
|
Action as ReduxAction,
|
||||||
|
AnyAction as ReduxAnyAction,
|
||||||
|
Action,
|
||||||
|
Middleware,
|
||||||
|
} from 'redux';
|
||||||
|
|
||||||
|
import { CoreStart } from '../../../../../../src/core/public';
|
||||||
|
import { Immutable } from '../../../common/endpoint_alerts/types';
|
||||||
|
import { State } from './reducer';
|
||||||
|
import { StartPlugins } from '../../types';
|
||||||
|
import { AppAction } from './actions';
|
||||||
|
|
||||||
export type KueryFilterQueryKind = 'kuery' | 'lucene';
|
export type KueryFilterQueryKind = 'kuery' | 'lucene';
|
||||||
|
|
||||||
export interface KueryFilterQuery {
|
export interface KueryFilterQuery {
|
||||||
|
@ -15,3 +29,94 @@ export interface SerializedFilterQuery {
|
||||||
kuery: KueryFilterQuery | null;
|
kuery: KueryFilterQuery | null;
|
||||||
serializedQuery: string;
|
serializedQuery: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* like redux's `MiddlewareAPI` but `getState` returns an `Immutable` version of
|
||||||
|
* state and `dispatch` accepts `Immutable` versions of actions.
|
||||||
|
*/
|
||||||
|
export interface ImmutableMiddlewareAPI<S, A extends Action> {
|
||||||
|
dispatch: Dispatch<A | Immutable<A>>;
|
||||||
|
getState(): Immutable<S>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like redux's `Middleware` but without the ability to mutate actions or state.
|
||||||
|
* Differences:
|
||||||
|
* * `getState` returns an `Immutable` version of state
|
||||||
|
* * `dispatch` accepts `Immutable` versions of actions
|
||||||
|
* * `action`s received will be `Immutable`
|
||||||
|
*/
|
||||||
|
export type ImmutableMiddleware<S, A extends Action> = (
|
||||||
|
api: ImmutableMiddlewareAPI<S, A>
|
||||||
|
) => (next: Dispatch<A | Immutable<A>>) => (action: Immutable<A>) => unknown;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes application-standard middleware dependencies
|
||||||
|
* and returns a redux middleware.
|
||||||
|
* Middleware will be of the `ImmutableMiddleware` variety. Not able to directly
|
||||||
|
* change actions or state.
|
||||||
|
*/
|
||||||
|
export type ImmutableMiddlewareFactory<S = State> = (
|
||||||
|
coreStart: CoreStart,
|
||||||
|
depsStart: Pick<StartPlugins, 'data' | 'ingestManager'>
|
||||||
|
) => ImmutableMiddleware<S, AppAction>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple type for a redux selector.
|
||||||
|
*/
|
||||||
|
type Selector<S, R> = (state: S) => R;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes a selector and an `ImmutableMiddleware`. The
|
||||||
|
* middleware's version of `getState` will receive
|
||||||
|
* the result of the selector instead of the global state.
|
||||||
|
*
|
||||||
|
* This allows middleware to have knowledge of only a subsection of state.
|
||||||
|
*
|
||||||
|
* `selector` returns an `Immutable` version of the substate.
|
||||||
|
* `middleware` must be an `ImmutableMiddleware`.
|
||||||
|
*
|
||||||
|
* Returns a regular middleware, meant to be used with `applyMiddleware`.
|
||||||
|
*/
|
||||||
|
export type SubstateMiddlewareFactory = <Substate>(
|
||||||
|
selector: Selector<State, Immutable<Substate>>,
|
||||||
|
middleware: ImmutableMiddleware<Substate, AppAction>
|
||||||
|
) => Middleware<{}, State, Dispatch<AppAction | Immutable<AppAction>>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like `Reducer` from `redux` but it accepts immutable versions of `state` and `action`.
|
||||||
|
* Use this type for all Reducers in order to help enforce our pattern of immutable state.
|
||||||
|
*/
|
||||||
|
export type ImmutableReducer<State, Action> = (
|
||||||
|
state: Immutable<State> | undefined,
|
||||||
|
action: Immutable<Action>
|
||||||
|
) => State | Immutable<State>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A alternate interface for `redux`'s `combineReducers`. Will work with the same underlying implementation,
|
||||||
|
* but will enforce that `Immutable` versions of `state` and `action` are received.
|
||||||
|
*/
|
||||||
|
export type ImmutableCombineReducers = <S, A extends ReduxAction = ReduxAnyAction>(
|
||||||
|
reducers: ImmutableReducersMapObject<S, A>
|
||||||
|
) => ImmutableReducer<S, A>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like `redux`'s `ReducersMapObject` (which is used by `combineReducers`) but enforces that
|
||||||
|
* the `state` and `action` received are `Immutable` versions.
|
||||||
|
*/
|
||||||
|
type ImmutableReducersMapObject<S, A extends ReduxAction = ReduxAction> = {
|
||||||
|
[K in keyof S]: ImmutableReducer<S[K], A>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A better type for createStructuredSelector. This doesn't support the options object.
|
||||||
|
*/
|
||||||
|
export type CreateStructuredSelector = <
|
||||||
|
SelectorMap extends { [key: string]: (...args: never[]) => unknown }
|
||||||
|
>(
|
||||||
|
selectorMap: SelectorMap
|
||||||
|
) => (
|
||||||
|
state: SelectorMap[keyof SelectorMap] extends (state: infer State) => unknown ? State : never
|
||||||
|
) => {
|
||||||
|
[Key in keyof SelectorMap]: ReturnType<SelectorMap[Key]>;
|
||||||
|
};
|
||||||
|
|
|
@ -4,4 +4,8 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export { AlertDetailsPagination } from './pagination';
|
export interface ServerApiError {
|
||||||
|
statusCode: number;
|
||||||
|
error: string;
|
||||||
|
message: string;
|
||||||
|
}
|
|
@ -5,8 +5,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { cloneHttpFetchQuery } from './clone_http_fetch_query';
|
import { cloneHttpFetchQuery } from './clone_http_fetch_query';
|
||||||
import { Immutable } from '../../common/types';
|
import { HttpFetchQuery } from '../../../../../../src/core/public';
|
||||||
import { HttpFetchQuery } from '../../../../../src/core/public';
|
import { Immutable } from '../../../common/endpoint/types';
|
||||||
|
|
||||||
describe('cloneHttpFetchQuery', () => {
|
describe('cloneHttpFetchQuery', () => {
|
||||||
it('can clone complex queries', () => {
|
it('can clone complex queries', () => {
|
|
@ -4,9 +4,9 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Immutable } from '../../common/types';
|
import { Immutable } from '../../../common/endpoint_alerts/types';
|
||||||
|
|
||||||
import { HttpFetchQuery } from '../../../../../src/core/public';
|
import { HttpFetchQuery } from '../../../../../../src/core/public';
|
||||||
|
|
||||||
export function cloneHttpFetchQuery(query: Immutable<HttpFetchQuery>): HttpFetchQuery {
|
export function cloneHttpFetchQuery(query: Immutable<HttpFetchQuery>): HttpFetchQuery {
|
||||||
const clone: HttpFetchQuery = {};
|
const clone: HttpFetchQuery = {};
|
|
@ -20,3 +20,5 @@ export const FormattedDate = memo(({ timestamp }: { timestamp: number }) => {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
FormattedDate.displayName = 'FormattedDate';
|
39
x-pack/plugins/siem/public/endpoint_alerts/index.ts
Normal file
39
x-pack/plugins/siem/public/endpoint_alerts/index.ts
Normal file
|
@ -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 { SecuritySubPluginWithStore } from '../app/types';
|
||||||
|
import { getEndpointAlertsRoutes } from './routes';
|
||||||
|
import { Immutable } from '../../common/endpoint/types';
|
||||||
|
import { initialAlertListState, alertListReducer } from './store/reducer';
|
||||||
|
import { AlertListState } from '../../common/endpoint_alerts/types';
|
||||||
|
import { alertMiddlewareFactory } from './store/middleware';
|
||||||
|
import { substateMiddlewareFactory } from '../common/store';
|
||||||
|
import { CoreStart } from '../../../../../src/core/public';
|
||||||
|
import { StartPlugins } from '../types';
|
||||||
|
|
||||||
|
export class EndpointAlerts {
|
||||||
|
public setup() {}
|
||||||
|
|
||||||
|
public start(
|
||||||
|
core: CoreStart,
|
||||||
|
plugins: StartPlugins
|
||||||
|
): SecuritySubPluginWithStore<'alertList', Immutable<AlertListState>> {
|
||||||
|
const { data, ingestManager } = plugins;
|
||||||
|
const middleware = substateMiddlewareFactory(
|
||||||
|
globalState => globalState.alertList,
|
||||||
|
alertMiddlewareFactory(core, { data, ingestManager })
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
routes: getEndpointAlertsRoutes(),
|
||||||
|
store: {
|
||||||
|
initialState: { alertList: initialAlertListState() },
|
||||||
|
reducer: { alertList: alertListReducer },
|
||||||
|
middleware,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,8 +5,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { all } from 'deepmerge';
|
import { all } from 'deepmerge';
|
||||||
import { Immutable } from '../../../../common/types';
|
import { IIndexPattern } from 'src/plugins/data/public';
|
||||||
import { IIndexPattern } from '../../../../../../../src/plugins/data/common';
|
import { Immutable } from '../../../common/endpoint_alerts/types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Model for the `IIndexPattern` interface exported by the `data` plugin.
|
* Model for the `IIndexPattern` interface exported by the `data` plugin.
|
|
@ -4,6 +4,13 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export { policyListReducer } from './reducer';
|
import React from 'react';
|
||||||
export { PolicyListAction } from './action';
|
import { Route } from 'react-router-dom';
|
||||||
export { policyListMiddlewareFactory } from './middleware';
|
|
||||||
|
import { AlertIndex } from './view';
|
||||||
|
|
||||||
|
export const getEndpointAlertsRoutes = () => [
|
||||||
|
<Route path="/:pageName(endpoint-alerts)">
|
||||||
|
<AlertIndex />
|
||||||
|
</Route>,
|
||||||
|
];
|
|
@ -5,8 +5,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { IIndexPattern } from 'src/plugins/data/public';
|
import { IIndexPattern } from 'src/plugins/data/public';
|
||||||
import { Immutable, AlertDetails } from '../../../../../common/types';
|
// import { Immutable } from '../../../common/types';
|
||||||
import { AlertListData } from '../../types';
|
import { AlertDetails, AlertListData, Immutable } from '../../../common/endpoint_alerts/types';
|
||||||
|
|
||||||
interface ServerReturnedAlertsData {
|
interface ServerReturnedAlertsData {
|
||||||
readonly type: 'serverReturnedAlertsData';
|
readonly type: 'serverReturnedAlertsData';
|
|
@ -5,19 +5,20 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Store, createStore, applyMiddleware } from 'redux';
|
import { Store, createStore, applyMiddleware } from 'redux';
|
||||||
import { History } from 'history';
|
import { createBrowserHistory, History } from 'history';
|
||||||
|
|
||||||
|
import { coreMock } from '../../../../../../src/core/public/mocks';
|
||||||
|
import { AlertListState, Immutable } from '../../../common/endpoint_alerts/types';
|
||||||
|
import { depsStartMock, DepsStartMock } from '../../common/mock/endpoint';
|
||||||
|
|
||||||
import { alertListReducer } from './reducer';
|
import { alertListReducer } from './reducer';
|
||||||
import { AlertListState } from '../../types';
|
|
||||||
import { alertMiddlewareFactory } from './middleware';
|
import { alertMiddlewareFactory } from './middleware';
|
||||||
import { AppAction } from '../action';
|
|
||||||
import { coreMock } from 'src/core/public/mocks';
|
|
||||||
import { DepsStartMock, depsStartMock } from '../../mocks';
|
|
||||||
import { createBrowserHistory } from 'history';
|
|
||||||
import { mockAlertResultList } from './mock_alert_result_list';
|
import { mockAlertResultList } from './mock_alert_result_list';
|
||||||
import { Immutable } from '../../../../../common/types';
|
|
||||||
|
|
||||||
describe('alert details tests', () => {
|
describe('alert details tests', () => {
|
||||||
let store: Store<Immutable<AlertListState>, Immutable<AppAction>>;
|
let store: Store;
|
||||||
let coreStart: ReturnType<typeof coreMock.createStart>;
|
let coreStart: ReturnType<typeof coreMock.createStart>;
|
||||||
let depsStart: DepsStartMock;
|
let depsStart: DepsStartMock;
|
||||||
let history: History<never>;
|
let history: History<never>;
|
||||||
|
@ -56,7 +57,7 @@ describe('alert details tests', () => {
|
||||||
type: 'userChangedUrl',
|
type: 'userChangedUrl',
|
||||||
payload: {
|
payload: {
|
||||||
...history.location,
|
...history.location,
|
||||||
pathname: '/alerts',
|
pathname: '/endpoint-alerts',
|
||||||
search: '?selected_alert=q9ncfh4q9ctrmc90umcq4',
|
search: '?selected_alert=q9ncfh4q9ctrmc90umcq4',
|
||||||
},
|
},
|
||||||
});
|
});
|
|
@ -5,20 +5,17 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Store, createStore, applyMiddleware } from 'redux';
|
import { Store, createStore, applyMiddleware } from 'redux';
|
||||||
import { History } from 'history';
|
import { History, createBrowserHistory } from 'history';
|
||||||
import { alertListReducer } from './reducer';
|
import { alertListReducer } from './reducer';
|
||||||
import { AlertListState } from '../../types';
|
import { AlertListState, AlertResultList, Immutable } from '../../../common/endpoint_alerts/types';
|
||||||
import { alertMiddlewareFactory } from './middleware';
|
import { alertMiddlewareFactory } from './middleware';
|
||||||
import { AppAction } from '../action';
|
|
||||||
import { coreMock } from 'src/core/public/mocks';
|
import { coreMock } from 'src/core/public/mocks';
|
||||||
import { DepsStartMock, depsStartMock } from '../../mocks';
|
import { DepsStartMock, depsStartMock } from '../../common/mock/endpoint';
|
||||||
import { AlertResultList, Immutable } from '../../../../../common/types';
|
|
||||||
import { isOnAlertPage } from './selectors';
|
import { isOnAlertPage } from './selectors';
|
||||||
import { createBrowserHistory } from 'history';
|
|
||||||
import { mockAlertResultList } from './mock_alert_result_list';
|
import { mockAlertResultList } from './mock_alert_result_list';
|
||||||
|
|
||||||
describe('alert list tests', () => {
|
describe('alert list tests', () => {
|
||||||
let store: Store<Immutable<AlertListState>, Immutable<AppAction>>;
|
let store: Store;
|
||||||
let coreStart: ReturnType<typeof coreMock.createStart>;
|
let coreStart: ReturnType<typeof coreMock.createStart>;
|
||||||
let depsStart: DepsStartMock;
|
let depsStart: DepsStartMock;
|
||||||
let history: History<never>;
|
let history: History<never>;
|
||||||
|
@ -59,7 +56,7 @@ describe('alert list tests', () => {
|
||||||
type: 'userChangedUrl',
|
type: 'userChangedUrl',
|
||||||
payload: {
|
payload: {
|
||||||
...history.location,
|
...history.location,
|
||||||
pathname: '/alerts',
|
pathname: '/endpoint-alerts',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
|
@ -4,21 +4,28 @@
|
||||||
* you may not use this file except in compliance with 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 { Store, createStore, applyMiddleware } from 'redux';
|
import { Store, createStore, applyMiddleware } from 'redux';
|
||||||
import { History } from 'history';
|
import { History, createBrowserHistory } from 'history';
|
||||||
import { alertListReducer } from './reducer';
|
|
||||||
import { AlertListState, AlertingIndexUIQueryParams } from '../../types';
|
import { coreMock } from '../../../../../../src/core/public/mocks';
|
||||||
|
|
||||||
|
import { AlertingIndexUIQueryParams } from '../../../common/endpoint_alerts/types';
|
||||||
|
import { DepsStartMock, depsStartMock } from '../../common/mock/endpoint';
|
||||||
|
|
||||||
import { alertMiddlewareFactory } from './middleware';
|
import { alertMiddlewareFactory } from './middleware';
|
||||||
import { AppAction } from '../action';
|
|
||||||
import { coreMock } from 'src/core/public/mocks';
|
import { alertListReducer } from './reducer';
|
||||||
import { DepsStartMock, depsStartMock } from '../../mocks';
|
|
||||||
import { createBrowserHistory } from 'history';
|
|
||||||
import { uiQueryParams } from './selectors';
|
import { uiQueryParams } from './selectors';
|
||||||
import { urlFromQueryParams } from '../../view/alerts/url_from_query_params';
|
import { urlFromQueryParams } from '../view/url_from_query_params';
|
||||||
import { Immutable } from '../../../../../common/types';
|
|
||||||
|
|
||||||
describe('alert list pagination', () => {
|
describe('alert list pagination', () => {
|
||||||
let store: Store<Immutable<AlertListState>, Immutable<AppAction>>;
|
let store: Store;
|
||||||
let coreStart: ReturnType<typeof coreMock.createStart>;
|
let coreStart: ReturnType<typeof coreMock.createStart>;
|
||||||
let depsStart: DepsStartMock;
|
let depsStart: DepsStartMock;
|
||||||
let history: History<never>;
|
let history: History<never>;
|
20
x-pack/plugins/siem/public/endpoint_alerts/store/index.ts
Normal file
20
x-pack/plugins/siem/public/endpoint_alerts/store/index.ts
Normal file
|
@ -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 { AlertListState, Immutable } from '../../../common/endpoint_alerts/types';
|
||||||
|
import { ImmutableReducer } from '../../common/store';
|
||||||
|
import { AppAction } from '../../common/store/actions';
|
||||||
|
|
||||||
|
export { alertListReducer } from './reducer';
|
||||||
|
export { AlertAction } from './action';
|
||||||
|
|
||||||
|
export interface EndpointAlertsPluginState {
|
||||||
|
alertList: Immutable<AlertListState>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EndpointAlertsPluginReducer {
|
||||||
|
alertList: ImmutableReducer<AlertListState, AppAction>;
|
||||||
|
}
|
|
@ -4,14 +4,19 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { IIndexPattern } from 'src/plugins/data/public';
|
import { IIndexPattern } from '../../../../../../src/plugins/data/public';
|
||||||
import { AlertResultList, AlertDetails } from '../../../../../common/types';
|
import {
|
||||||
import { ImmutableMiddlewareFactory, AlertListState } from '../../types';
|
AlertResultList,
|
||||||
|
AlertDetails,
|
||||||
|
AlertListState,
|
||||||
|
} from '../../../common/endpoint_alerts/types';
|
||||||
|
import { AlertConstants } from '../../../common/endpoint_alerts/alert_constants';
|
||||||
|
import { ImmutableMiddlewareFactory } from '../../common/store';
|
||||||
|
import { cloneHttpFetchQuery } from '../../common/utils/clone_http_fetch_query';
|
||||||
import { isOnAlertPage, apiQueryParams, hasSelectedAlert, uiQueryParams } from './selectors';
|
import { isOnAlertPage, apiQueryParams, hasSelectedAlert, uiQueryParams } from './selectors';
|
||||||
import { cloneHttpFetchQuery } from '../../../../common/clone_http_fetch_query';
|
import { Immutable } from '../../../common/endpoint/types';
|
||||||
import { AlertConstants } from '../../../../../common/alert_constants';
|
|
||||||
|
|
||||||
export const alertMiddlewareFactory: ImmutableMiddlewareFactory<AlertListState> = (
|
export const alertMiddlewareFactory: ImmutableMiddlewareFactory<Immutable<AlertListState>> = (
|
||||||
coreStart,
|
coreStart,
|
||||||
depsStart
|
depsStart
|
||||||
) => {
|
) => {
|
|
@ -4,8 +4,8 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { AlertResultList, AlertDetails } from '../../../../../common/types';
|
import { EndpointDocGenerator } from '../../../common/endpoint/generate_data';
|
||||||
import { EndpointDocGenerator } from '../../../../../common/generate_data';
|
import { AlertResultList, AlertDetails } from '../../../common/endpoint_alerts/types';
|
||||||
|
|
||||||
export const mockAlertResultList: (options?: {
|
export const mockAlertResultList: (options?: {
|
||||||
total?: number;
|
total?: number;
|
||||||
|
@ -30,7 +30,7 @@ export const mockAlertResultList: (options?: {
|
||||||
alerts.push({
|
alerts.push({
|
||||||
...generator.generateAlert(new Date().getTime() + index * 1000),
|
...generator.generateAlert(new Date().getTime() + index * 1000),
|
||||||
...{
|
...{
|
||||||
id: 'xDUYMHABAJk0XnHd8rrd' + index,
|
id: `xDUYMHABAJk0XnHd8rrd${index}`,
|
||||||
prev: null,
|
prev: null,
|
||||||
next: null,
|
next: null,
|
||||||
},
|
},
|
|
@ -4,11 +4,11 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { AlertListState, ImmutableReducer } from '../../types';
|
import { Immutable, AlertListState } from '../../../common/endpoint_alerts/types';
|
||||||
import { AppAction } from '../action';
|
import { ImmutableReducer } from '../../common/store';
|
||||||
import { Immutable } from '../../../../../common/types';
|
import { AppAction } from '../../common/store/actions';
|
||||||
|
|
||||||
const initialState = (): Immutable<AlertListState> => {
|
export const initialAlertListState = (): Immutable<AlertListState> => {
|
||||||
return {
|
return {
|
||||||
alerts: [],
|
alerts: [],
|
||||||
alertDetails: undefined,
|
alertDetails: undefined,
|
||||||
|
@ -23,7 +23,7 @@ const initialState = (): Immutable<AlertListState> => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const alertListReducer: ImmutableReducer<AlertListState, AppAction> = (
|
export const alertListReducer: ImmutableReducer<AlertListState, AppAction> = (
|
||||||
state = initialState(),
|
state = initialAlertListState(),
|
||||||
action
|
action
|
||||||
) => {
|
) => {
|
||||||
if (action.type === 'serverReturnedAlertsData') {
|
if (action.type === 'serverReturnedAlertsData') {
|
|
@ -4,15 +4,23 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-nodejs-modules
|
||||||
import querystring from 'querystring';
|
import querystring from 'querystring';
|
||||||
import {
|
import {
|
||||||
createSelector,
|
createSelector,
|
||||||
createStructuredSelector as createStructuredSelectorWithBadType,
|
createStructuredSelector as createStructuredSelectorWithBadType,
|
||||||
} from 'reselect';
|
} from 'reselect';
|
||||||
import { encode, decode } from 'rison-node';
|
import { encode, decode } from 'rison-node';
|
||||||
import { Query, TimeRange, Filter } from 'src/plugins/data/public';
|
|
||||||
import { AlertListState, AlertingIndexUIQueryParams, CreateStructuredSelector } from '../../types';
|
import { Query, TimeRange, Filter } from '../../../../../../src/plugins/data/public';
|
||||||
import { Immutable, AlertingIndexGetQueryInput } from '../../../../../common/types';
|
|
||||||
|
import {
|
||||||
|
Immutable,
|
||||||
|
AlertingIndexGetQueryInput,
|
||||||
|
AlertListState,
|
||||||
|
AlertingIndexUIQueryParams,
|
||||||
|
} from '../../../common/endpoint_alerts/types';
|
||||||
|
import { CreateStructuredSelector } from '../../common/store';
|
||||||
|
|
||||||
const createStructuredSelector: CreateStructuredSelector = createStructuredSelectorWithBadType;
|
const createStructuredSelector: CreateStructuredSelector = createStructuredSelectorWithBadType;
|
||||||
|
|
||||||
|
@ -36,7 +44,7 @@ export const alertListPagination = createStructuredSelector({
|
||||||
* Returns a boolean based on whether or not the user is on the alerts page
|
* Returns a boolean based on whether or not the user is on the alerts page
|
||||||
*/
|
*/
|
||||||
export const isOnAlertPage = (state: Immutable<AlertListState>): boolean => {
|
export const isOnAlertPage = (state: Immutable<AlertListState>): boolean => {
|
||||||
return state.location ? state.location.pathname === '/alerts' : false;
|
return state.location ? state.location.pathname === '/endpoint-alerts' : false;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
|
@ -5,21 +5,22 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as reactTestingLibrary from '@testing-library/react';
|
import * as reactTestingLibrary from '@testing-library/react';
|
||||||
import { appStoreFactory } from '../../store';
|
|
||||||
import { fireEvent } from '@testing-library/react';
|
|
||||||
import { MemoryHistory } from 'history';
|
import { MemoryHistory } from 'history';
|
||||||
import { AppAction } from '../../types';
|
import { Store } from 'redux';
|
||||||
import { mockAlertDetailsResult } from '../../store/alerts/mock_alert_result_list';
|
|
||||||
|
import { mockAlertDetailsResult } from '../store/mock_alert_result_list';
|
||||||
import { alertPageTestRender } from './test_helpers/render_alert_page';
|
import { alertPageTestRender } from './test_helpers/render_alert_page';
|
||||||
|
import { AppAction } from '../../common/store/actions';
|
||||||
|
import { State } from '../../common/store/reducer';
|
||||||
|
|
||||||
describe('when the alert details flyout is open', () => {
|
describe('when the alert details flyout is open', () => {
|
||||||
let render: () => reactTestingLibrary.RenderResult;
|
let render: () => reactTestingLibrary.RenderResult;
|
||||||
let history: MemoryHistory<never>;
|
let history: MemoryHistory<never>;
|
||||||
let store: ReturnType<typeof appStoreFactory>;
|
let store: Store<State>;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
// Creates the render elements for the tests to use
|
// Creates the render elements for the tests to use
|
||||||
({ render, history, store } = alertPageTestRender);
|
({ render, history, store } = alertPageTestRender());
|
||||||
});
|
});
|
||||||
describe('when the alerts details flyout is open', () => {
|
describe('when the alerts details flyout is open', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
@ -50,7 +51,7 @@ describe('when the alert details flyout is open', () => {
|
||||||
'alertDetailTakeActionDropdownButton'
|
'alertDetailTakeActionDropdownButton'
|
||||||
);
|
);
|
||||||
if (takeActionButton) {
|
if (takeActionButton) {
|
||||||
fireEvent.click(takeActionButton);
|
reactTestingLibrary.fireEvent.click(takeActionButton);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
it('should display the correct fields in the dropdown', async () => {
|
it('should display the correct fields in the dropdown', async () => {
|
||||||
|
@ -64,7 +65,7 @@ describe('when the alert details flyout is open', () => {
|
||||||
renderResult = render();
|
renderResult = render();
|
||||||
const overviewTab = await renderResult.findByTestId('overviewMetadata');
|
const overviewTab = await renderResult.findByTestId('overviewMetadata');
|
||||||
if (overviewTab) {
|
if (overviewTab) {
|
||||||
fireEvent.click(overviewTab);
|
reactTestingLibrary.fireEvent.click(overviewTab);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
it('should render all accordion panels', async () => {
|
it('should render all accordion panels', async () => {
|
|
@ -6,56 +6,62 @@
|
||||||
import React, { memo, useMemo } from 'react';
|
import React, { memo, useMemo } from 'react';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { EuiAccordion, EuiDescriptionList } from '@elastic/eui';
|
import { EuiAccordion, EuiDescriptionList } from '@elastic/eui';
|
||||||
import { Immutable, AlertData } from '../../../../../../../common/types';
|
import { Immutable, AlertData } from '../../../../../common/endpoint_alerts/types';
|
||||||
import { FormattedDate } from '../../formatted_date';
|
import { FormattedDate } from '../../formatted_date';
|
||||||
|
|
||||||
export const FileAccordion = memo(({ alertData }: { alertData: Immutable<AlertData> }) => {
|
export const FileAccordion = memo(({ alertData }: { alertData: Immutable<AlertData> }) => {
|
||||||
const columns = useMemo(() => {
|
const columns = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.fileName', {
|
title: i18n.translate('xpack.siem.endpoint.application.endpoint.alertDetails.fileName', {
|
||||||
defaultMessage: 'File Name',
|
defaultMessage: 'File Name',
|
||||||
}),
|
}),
|
||||||
description: alertData.file.name,
|
description: alertData.file.name,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.filePath', {
|
title: i18n.translate('xpack.siem.endpoint.application.endpoint.alertDetails.filePath', {
|
||||||
defaultMessage: 'File Path',
|
defaultMessage: 'File Path',
|
||||||
}),
|
}),
|
||||||
description: alertData.file.path,
|
description: alertData.file.path,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.fileSize', {
|
title: i18n.translate('xpack.siem.endpoint.application.endpoint.alertDetails.fileSize', {
|
||||||
defaultMessage: 'File Size',
|
defaultMessage: 'File Size',
|
||||||
}),
|
}),
|
||||||
description: alertData.file.size,
|
description: alertData.file.size,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.fileCreated', {
|
title: i18n.translate('xpack.siem.endpoint.application.endpoint.alertDetails.fileCreated', {
|
||||||
defaultMessage: 'File Created',
|
defaultMessage: 'File Created',
|
||||||
}),
|
}),
|
||||||
description: <FormattedDate timestamp={alertData.file.created} />,
|
description: <FormattedDate timestamp={alertData.file.created} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.fileModified', {
|
title: i18n.translate(
|
||||||
defaultMessage: 'File Modified',
|
'xpack.siem.endpoint.application.endpoint.alertDetails.fileModified',
|
||||||
}),
|
{
|
||||||
|
defaultMessage: 'File Modified',
|
||||||
|
}
|
||||||
|
),
|
||||||
description: <FormattedDate timestamp={alertData.file.mtime} />,
|
description: <FormattedDate timestamp={alertData.file.mtime} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.fileAccessed', {
|
title: i18n.translate(
|
||||||
defaultMessage: 'File Accessed',
|
'xpack.siem.endpoint.application.endpoint.alertDetails.fileAccessed',
|
||||||
}),
|
{
|
||||||
|
defaultMessage: 'File Accessed',
|
||||||
|
}
|
||||||
|
),
|
||||||
description: <FormattedDate timestamp={alertData.file.accessed} />,
|
description: <FormattedDate timestamp={alertData.file.accessed} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.signer', {
|
title: i18n.translate('xpack.siem.endpoint.application.endpoint.alertDetails.signer', {
|
||||||
defaultMessage: 'Signer',
|
defaultMessage: 'Signer',
|
||||||
}),
|
}),
|
||||||
description: alertData.file.code_signature.subject_name,
|
description: alertData.file.code_signature.subject_name,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.owner', {
|
title: i18n.translate('xpack.siem.endpoint.application.endpoint.alertDetails.owner', {
|
||||||
defaultMessage: 'Owner',
|
defaultMessage: 'Owner',
|
||||||
}),
|
}),
|
||||||
description: alertData.file.owner,
|
description: alertData.file.owner,
|
||||||
|
@ -67,7 +73,7 @@ export const FileAccordion = memo(({ alertData }: { alertData: Immutable<AlertDa
|
||||||
<EuiAccordion
|
<EuiAccordion
|
||||||
id="alertDetailsFileAccordion"
|
id="alertDetailsFileAccordion"
|
||||||
buttonContent={i18n.translate(
|
buttonContent={i18n.translate(
|
||||||
'xpack.endpoint.application.endpoint.alertDetails.accordionTitles.file',
|
'xpack.siem.endpoint.application.endpoint.alertDetails.accordionTitles.file',
|
||||||
{
|
{
|
||||||
defaultMessage: 'File',
|
defaultMessage: 'File',
|
||||||
}
|
}
|
||||||
|
@ -79,3 +85,5 @@ export const FileAccordion = memo(({ alertData }: { alertData: Immutable<AlertDa
|
||||||
</EuiAccordion>
|
</EuiAccordion>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
FileAccordion.displayName = 'FileAccordion';
|
|
@ -6,44 +6,47 @@
|
||||||
import React, { memo, useMemo } from 'react';
|
import React, { memo, useMemo } from 'react';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { EuiAccordion, EuiDescriptionList } from '@elastic/eui';
|
import { EuiAccordion, EuiDescriptionList } from '@elastic/eui';
|
||||||
import { Immutable, AlertData } from '../../../../../../../common/types';
|
import { Immutable, AlertData } from '../../../../../common/endpoint_alerts/types';
|
||||||
import { FormattedDate } from '../../formatted_date';
|
import { FormattedDate } from '../../formatted_date';
|
||||||
|
|
||||||
export const GeneralAccordion = memo(({ alertData }: { alertData: Immutable<AlertData> }) => {
|
export const GeneralAccordion = memo(({ alertData }: { alertData: Immutable<AlertData> }) => {
|
||||||
const columns = useMemo(() => {
|
const columns = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.alertType', {
|
title: i18n.translate('xpack.siem.endpoint.application.endpoint.alertDetails.alertType', {
|
||||||
defaultMessage: 'Alert Type',
|
defaultMessage: 'Alert Type',
|
||||||
}),
|
}),
|
||||||
description: alertData.event.category,
|
description: alertData.event.category,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.eventType', {
|
title: i18n.translate('xpack.siem.endpoint.application.endpoint.alertDetails.eventType', {
|
||||||
defaultMessage: 'Event Type',
|
defaultMessage: 'Event Type',
|
||||||
}),
|
}),
|
||||||
description: alertData.event.kind,
|
description: alertData.event.kind,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.status', {
|
title: i18n.translate('xpack.siem.endpoint.application.endpoint.alertDetails.status', {
|
||||||
defaultMessage: 'Status',
|
defaultMessage: 'Status',
|
||||||
}),
|
}),
|
||||||
description: 'TODO',
|
description: 'TODO',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.dateCreated', {
|
title: i18n.translate('xpack.siem.endpoint.application.endpoint.alertDetails.dateCreated', {
|
||||||
defaultMessage: 'Date Created',
|
defaultMessage: 'Date Created',
|
||||||
}),
|
}),
|
||||||
description: <FormattedDate timestamp={alertData['@timestamp']} />,
|
description: <FormattedDate timestamp={alertData['@timestamp']} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.malwareScore', {
|
title: i18n.translate(
|
||||||
defaultMessage: 'MalwareScore',
|
'xpack.siem.endpoint.application.endpoint.alertDetails.malwareScore',
|
||||||
}),
|
{
|
||||||
|
defaultMessage: 'MalwareScore',
|
||||||
|
}
|
||||||
|
),
|
||||||
description: alertData.file.malware_classification.score,
|
description: alertData.file.malware_classification.score,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.fileName', {
|
title: i18n.translate('xpack.siem.endpoint.application.endpoint.alertDetails.fileName', {
|
||||||
defaultMessage: 'File Name',
|
defaultMessage: 'File Name',
|
||||||
}),
|
}),
|
||||||
description: alertData.file.name,
|
description: alertData.file.name,
|
||||||
|
@ -54,7 +57,7 @@ export const GeneralAccordion = memo(({ alertData }: { alertData: Immutable<Aler
|
||||||
<EuiAccordion
|
<EuiAccordion
|
||||||
id="alertDetailsAlertAccordion"
|
id="alertDetailsAlertAccordion"
|
||||||
buttonContent={i18n.translate(
|
buttonContent={i18n.translate(
|
||||||
'xpack.endpoint.application.endpoint.alertDetails.accordionTitles.alert',
|
'xpack.siem.endpoint.application.endpoint.alertDetails.accordionTitles.alert',
|
||||||
{
|
{
|
||||||
defaultMessage: 'Alert',
|
defaultMessage: 'Alert',
|
||||||
}
|
}
|
||||||
|
@ -67,3 +70,5 @@ export const GeneralAccordion = memo(({ alertData }: { alertData: Immutable<Aler
|
||||||
</EuiAccordion>
|
</EuiAccordion>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
GeneralAccordion.displayName = 'GeneralAccordion';
|
|
@ -6,25 +6,25 @@
|
||||||
import React, { memo, useMemo } from 'react';
|
import React, { memo, useMemo } from 'react';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { EuiAccordion, EuiDescriptionList } from '@elastic/eui';
|
import { EuiAccordion, EuiDescriptionList } from '@elastic/eui';
|
||||||
import { Immutable, AlertData } from '../../../../../../../common/types';
|
import { Immutable, AlertData } from '../../../../../common/endpoint_alerts/types';
|
||||||
|
|
||||||
export const HashAccordion = memo(({ alertData }: { alertData: Immutable<AlertData> }) => {
|
export const HashAccordion = memo(({ alertData }: { alertData: Immutable<AlertData> }) => {
|
||||||
const columns = useMemo(() => {
|
const columns = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.md5', {
|
title: i18n.translate('xpack.siem.endpoint.application.endpoint.alertDetails.md5', {
|
||||||
defaultMessage: 'MD5',
|
defaultMessage: 'MD5',
|
||||||
}),
|
}),
|
||||||
description: alertData.file.hash.md5,
|
description: alertData.file.hash.md5,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.sha1', {
|
title: i18n.translate('xpack.siem.endpoint.application.endpoint.alertDetails.sha1', {
|
||||||
defaultMessage: 'SHA1',
|
defaultMessage: 'SHA1',
|
||||||
}),
|
}),
|
||||||
description: alertData.file.hash.sha1,
|
description: alertData.file.hash.sha1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.sha256', {
|
title: i18n.translate('xpack.siem.endpoint.application.endpoint.alertDetails.sha256', {
|
||||||
defaultMessage: 'SHA256',
|
defaultMessage: 'SHA256',
|
||||||
}),
|
}),
|
||||||
description: alertData.file.hash.sha256,
|
description: alertData.file.hash.sha256,
|
||||||
|
@ -36,7 +36,7 @@ export const HashAccordion = memo(({ alertData }: { alertData: Immutable<AlertDa
|
||||||
<EuiAccordion
|
<EuiAccordion
|
||||||
id="alertDetailsHashAccordion"
|
id="alertDetailsHashAccordion"
|
||||||
buttonContent={i18n.translate(
|
buttonContent={i18n.translate(
|
||||||
'xpack.endpoint.application.endpoint.alertDetails.accordionTitles.hash',
|
'xpack.siem.endpoint.application.endpoint.alertDetails.accordionTitles.hash',
|
||||||
{
|
{
|
||||||
defaultMessage: 'Hash',
|
defaultMessage: 'Hash',
|
||||||
}
|
}
|
||||||
|
@ -48,3 +48,5 @@ export const HashAccordion = memo(({ alertData }: { alertData: Immutable<AlertDa
|
||||||
</EuiAccordion>
|
</EuiAccordion>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
HashAccordion.displayName = 'HashAccordion';
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue