diff --git a/x-pack/plugins/endpoint/common/generate_data.ts b/x-pack/plugins/endpoint/common/generate_data.ts index 3f783d90e577d..1978d780f54f5 100644 --- a/x-pack/plugins/endpoint/common/generate_data.ts +++ b/x-pack/plugins/endpoint/common/generate_data.ts @@ -6,11 +6,8 @@ import uuid from 'uuid'; import seedrandom from 'seedrandom'; -import { AlertEvent, EndpointEvent, HostMetadata, OSFields, HostFields } from './types'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { PolicyData } from '../public/applications/endpoint/types'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { generatePolicy } from '../public/applications/endpoint/models/policy'; +import { AlertEvent, EndpointEvent, HostMetadata, OSFields, HostFields, PolicyData } from './types'; +import { factory as policyFactory } from './models/policy_config'; export type Event = AlertEvent | EndpointEvent; @@ -474,7 +471,7 @@ export class EndpointDocGenerator { streams: [], config: { policy: { - value: generatePolicy(), + value: policyFactory(), }, }, }, diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/models/policy.ts b/x-pack/plugins/endpoint/common/models/policy_config.ts similarity index 89% rename from x-pack/plugins/endpoint/public/applications/endpoint/models/policy.ts rename to x-pack/plugins/endpoint/common/models/policy_config.ts index 5269ee72f4039..199b8a91e4307 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/models/policy.ts +++ b/x-pack/plugins/endpoint/common/models/policy_config.ts @@ -7,11 +7,9 @@ import { PolicyConfig, ProtectionModes } from '../types'; /** - * Generate a new Policy model. - * NOTE: in the near future, this will likely be removed and an API call to EPM will be used to retrieve - * the latest from the Endpoint package + * Return a new default `PolicyConfig`. */ -export const generatePolicy = (): PolicyConfig => { +export const factory = (): PolicyConfig => { return { windows: { events: { diff --git a/x-pack/plugins/endpoint/common/types.ts b/x-pack/plugins/endpoint/common/types.ts index 403ca9832e191..7143f07d8c702 100644 --- a/x-pack/plugins/endpoint/common/types.ts +++ b/x-pack/plugins/endpoint/common/types.ts @@ -7,12 +7,15 @@ import { SearchResponse } from 'elasticsearch'; import { TypeOf } from '@kbn/config-schema'; import { alertingIndexGetQuerySchema } from './schema/alert_index'; +import { Datasource, NewDatasource } from '../../ingest_manager/common'; /** * A deep readonly type that will make all children of a given object readonly recursively */ export type Immutable = T extends undefined | null | boolean | string | number ? T + : unknown extends T + ? unknown : T extends Array ? ImmutableArray : T extends Map @@ -442,3 +445,125 @@ export type AlertingIndexGetQueryInput = KbnConfigSchemaInputTypeOf< * Result of the validated query params when handling alert index requests. */ export type AlertingIndexGetQueryResult = TypeOf; + +/** + * 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; + }; +} + +/** + * Windows-specific policy configuration that is supported via the UI + */ +type WindowsPolicyConfig = Pick; + +/** + * Mac-specific policy configuration that is supported via the UI + */ +type MacPolicyConfig = Pick; + +/** + * Linux-specific policy configuration that is supported via the UI + */ +type LinuxPolicyConfig = Pick; + +/** + * The set of Policy configuration settings that are show/edited via the UI + */ +export interface UIPolicyConfig { + windows: WindowsPolicyConfig; + mac: MacPolicyConfig; + linux: LinuxPolicyConfig; +} + +interface PolicyConfigAdvancedOptions { + elasticsearch: { + indices: { + control: string; + event: string; + logging: string; + }; + kernel: { + connect: boolean; + process: boolean; + }; + }; +} + +/** Policy: Malware protection fields */ +export interface MalwareFields { + mode: ProtectionModes; +} + +/** Policy protection mode options */ +export enum ProtectionModes { + detect = 'detect', + prevent = 'prevent', + preventNotify = 'preventNotify', + off = 'off', +} + +/** + * Endpoint Policy data, which extends Ingest's `Datasource` type + */ +export type PolicyData = Datasource & NewPolicyData; + +/** + * New policy data. Used when updating the policy record via ingest APIs + */ +export type NewPolicyData = NewDatasource & { + inputs: [ + { + type: 'endpoint'; + enabled: boolean; + streams: []; + config: { + policy: { + value: PolicyConfig; + }; + }; + } + ]; +}; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/models/index_pattern.ts b/x-pack/plugins/endpoint/public/applications/endpoint/models/index_pattern.ts new file mode 100644 index 0000000000000..0cae054432f96 --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/models/index_pattern.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { all } from 'deepmerge'; +import { Immutable } from '../../../../common/types'; +import { IIndexPattern } from '../../../../../../../src/plugins/data/common'; + +/** + * Model for the `IIndexPattern` interface exported by the `data` plugin. + */ +export function clone(value: IIndexPattern | Immutable): IIndexPattern { + return all([value]) as IIndexPattern; +} diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/models/policy_details_config.ts b/x-pack/plugins/endpoint/public/applications/endpoint/models/policy_details_config.ts index bf96942e83a91..3e56b1ff14d65 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/models/policy_details_config.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/models/policy_details_config.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { UIPolicyConfig } from '../types'; +import { UIPolicyConfig } from '../../../../common/types'; /** * A typed Object.entries() function where the keys and values are typed based on the given object diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_details.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_details.test.ts index 79e9de9c67352..feac8944f476b 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_details.test.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_details.test.ts @@ -14,16 +14,17 @@ import { coreMock } from 'src/core/public/mocks'; import { DepsStartMock, depsStartMock } from '../../mocks'; import { createBrowserHistory } from 'history'; import { mockAlertResultList } from './mock_alert_result_list'; +import { Immutable } from '../../../../../common/types'; describe('alert details tests', () => { - let store: Store; + let store: Store, Immutable>; let coreStart: ReturnType; let depsStart: DepsStartMock; let history: History; /** * A function that waits until a selector returns true. */ - let selectorIsTrue: (selector: (state: AlertListState) => boolean) => Promise; + let selectorIsTrue: (selector: (state: Immutable) => boolean) => Promise; beforeEach(() => { coreStart = coreMock.createStart(); depsStart = depsStartMock(); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_list.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_list.test.ts index b1cc2d46f614a..84281813312e0 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_list.test.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_list.test.ts @@ -12,20 +12,20 @@ import { alertMiddlewareFactory } from './middleware'; import { AppAction } from '../action'; import { coreMock } from 'src/core/public/mocks'; import { DepsStartMock, depsStartMock } from '../../mocks'; -import { AlertResultList } from '../../../../../common/types'; +import { AlertResultList, Immutable } from '../../../../../common/types'; import { isOnAlertPage } from './selectors'; import { createBrowserHistory } from 'history'; import { mockAlertResultList } from './mock_alert_result_list'; describe('alert list tests', () => { - let store: Store; + let store: Store, Immutable>; let coreStart: ReturnType; let depsStart: DepsStartMock; let history: History; /** * A function that waits until a selector returns true. */ - let selectorIsTrue: (selector: (state: AlertListState) => boolean) => Promise; + let selectorIsTrue: (selector: (state: Immutable) => boolean) => Promise; beforeEach(() => { coreStart = coreMock.createStart(); depsStart = depsStartMock(); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_list_pagination.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_list_pagination.test.ts index bb5893f14287b..4cc86e9c0449c 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_list_pagination.test.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_list_pagination.test.ts @@ -15,9 +15,10 @@ import { DepsStartMock, depsStartMock } from '../../mocks'; import { createBrowserHistory } from 'history'; import { uiQueryParams } from './selectors'; import { urlFromQueryParams } from '../../view/alerts/url_from_query_params'; +import { Immutable } from '../../../../../common/types'; describe('alert list pagination', () => { - let store: Store; + let store: Store, Immutable>; let coreStart: ReturnType; let depsStart: DepsStartMock; let history: History; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/reducer.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/reducer.ts index 4430a4d39cf4a..52b91dcae7d70 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/reducer.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/reducer.ts @@ -4,11 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Reducer } from 'redux'; -import { AlertListState } from '../../types'; +import { AlertListState, ImmutableReducer } from '../../types'; import { AppAction } from '../action'; +import { Immutable } from '../../../../../common/types'; -const initialState = (): AlertListState => { +const initialState = (): Immutable => { return { alerts: [], alertDetails: undefined, @@ -22,7 +22,7 @@ const initialState = (): AlertListState => { }; }; -export const alertListReducer: Reducer = ( +export const alertListReducer: ImmutableReducer = ( state = initialState(), action ) => { diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/selectors.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/selectors.ts index 5e9b08c09c2c7..cc362c3701956 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/selectors.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/selectors.ts @@ -19,23 +19,23 @@ const createStructuredSelector: CreateStructuredSelector = createStructuredSelec /** * Returns the Alert Data array from state */ -export const alertListData = (state: AlertListState) => state.alerts; +export const alertListData = (state: Immutable) => state.alerts; -export const selectedAlertDetailsData = (state: AlertListState) => state.alertDetails; +export const selectedAlertDetailsData = (state: Immutable) => state.alertDetails; /** * Returns the alert list pagination data from state */ export const alertListPagination = createStructuredSelector({ - pageIndex: (state: AlertListState) => state.pageIndex, - pageSize: (state: AlertListState) => state.pageSize, - total: (state: AlertListState) => state.total, + pageIndex: (state: Immutable) => state.pageIndex, + pageSize: (state: Immutable) => state.pageSize, + total: (state: Immutable) => state.total, }); /** * Returns a boolean based on whether or not the user is on the alerts page */ -export const isOnAlertPage = (state: AlertListState): boolean => { +export const isOnAlertPage = (state: Immutable): boolean => { return state.location ? state.location.pathname === '/alerts' : false; }; @@ -44,10 +44,10 @@ export const isOnAlertPage = (state: AlertListState): boolean => { * Used to calculate urls for links and such. */ export const uiQueryParams: ( - state: AlertListState + state: Immutable ) => Immutable = createSelector( - (state: AlertListState) => state.location, - (location: AlertListState['location']) => { + state => state.location, + (location: Immutable['location']) => { const data: AlertingIndexUIQueryParams = {}; if (location) { // Removes the `?` from the beginning of query string if it exists @@ -82,7 +82,7 @@ export const uiQueryParams: ( * Parses the ui query params and returns a object that represents the query used by the SearchBar component. * If the query url param is undefined, a default is returned. */ -export const searchBarQuery: (state: AlertListState) => Query = createSelector( +export const searchBarQuery: (state: Immutable) => Query = createSelector( uiQueryParams, ({ query }) => { if (query !== undefined) { @@ -97,21 +97,20 @@ export const searchBarQuery: (state: AlertListState) => Query = createSelector( * Parses the ui query params and returns a rison encoded string that represents the search bar's date range. * A default is provided if 'date_range' is not present in the url params. */ -export const encodedSearchBarDateRange: (state: AlertListState) => string = createSelector( - uiQueryParams, - ({ date_range: dateRange }) => { - if (dateRange === undefined) { - return encode({ from: 'now-24h', to: 'now' }); - } else { - return dateRange; - } +export const encodedSearchBarDateRange: ( + state: Immutable +) => string = createSelector(uiQueryParams, ({ date_range: dateRange }) => { + if (dateRange === undefined) { + return encode({ from: 'now-24h', to: 'now' }); + } else { + return dateRange; } -); +}); /** * Parses the ui query params and returns a object that represents the dateRange used by the SearchBar component. */ -export const searchBarDateRange: (state: AlertListState) => TimeRange = createSelector( +export const searchBarDateRange: (state: Immutable) => TimeRange = createSelector( encodedSearchBarDateRange, encodedDateRange => { return (decode(encodedDateRange) as unknown) as TimeRange; @@ -122,7 +121,7 @@ export const searchBarDateRange: (state: AlertListState) => TimeRange = createSe * Parses the ui query params and returns an array of filters used by the SearchBar component. * If the 'filters' param is not present, a default is returned. */ -export const searchBarFilters: (state: AlertListState) => Filter[] = createSelector( +export const searchBarFilters: (state: Immutable) => Filter[] = createSelector( uiQueryParams, ({ filters }) => { if (filters !== undefined) { @@ -136,13 +135,14 @@ export const searchBarFilters: (state: AlertListState) => Filter[] = createSelec /** * Returns the indexPatterns used by the SearchBar component */ -export const searchBarIndexPatterns = (state: AlertListState) => state.searchBar.patterns; +export const searchBarIndexPatterns = (state: Immutable) => + state.searchBar.patterns; /** * query params to use when requesting alert data. */ export const apiQueryParams: ( - state: AlertListState + state: Immutable ) => Immutable = createSelector( uiQueryParams, encodedSearchBarDateRange, @@ -161,7 +161,7 @@ export const apiQueryParams: ( * True if the user has selected an alert to see details about. * Populated via the browsers query params. */ -export const hasSelectedAlert: (state: AlertListState) => boolean = createSelector( +export const hasSelectedAlert: (state: Immutable) => boolean = createSelector( uiQueryParams, ({ selected_alert: selectedAlert }) => selectedAlert !== undefined ); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/action.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/action.ts index 4dafa68ddb647..21871ec8ca849 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/action.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/action.ts @@ -27,14 +27,8 @@ interface UserPaginatedHostList { payload: HostListPagination; } -// Why is FakeActionWithNoPayload here, see: https://github.com/elastic/endpoint-app-team/issues/273 -interface FakeActionWithNoPayload { - type: 'fakeActionWithNoPayLoad'; -} - export type HostAction = | ServerReturnedHostList | ServerReturnedHostDetails | ServerFailedToReturnHostDetails - | UserPaginatedHostList - | FakeActionWithNoPayload; + | UserPaginatedHostList; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.test.ts index 8c8578426aa29..8f39baddda00e 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.test.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.test.ts @@ -4,11 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ import { CoreStart, HttpSetup } from 'kibana/public'; -import { applyMiddleware, createStore, Dispatch, Store } from 'redux'; +import { applyMiddleware, createStore, Store } from 'redux'; import { coreMock } from '../../../../../../../../src/core/public/mocks'; import { History, createBrowserHistory } from 'history'; import { hostListReducer, hostMiddlewareFactory } from './index'; -import { HostResultList } from '../../../../../common/types'; +import { HostResultList, Immutable } from '../../../../../common/types'; import { HostListState } from '../../types'; import { AppAction } from '../action'; import { listData } from './selectors'; @@ -20,9 +20,10 @@ describe('host list middleware', () => { let fakeCoreStart: jest.Mocked; let depsStart: DepsStartMock; let fakeHttpServices: jest.Mocked; - let store: Store; - let getState: typeof store['getState']; - let dispatch: Dispatch; + type HostListStore = Store, Immutable>; + let store: HostListStore; + let getState: HostListStore['getState']; + let dispatch: HostListStore['dispatch']; let history: History; const getEndpointListApiResponse = (): HostResultList => { diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/reducer.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/reducer.ts index ad6741dab7be7..298e819645dbe 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/reducer.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/reducer.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Reducer } from 'redux'; -import { HostListState } from '../../types'; +import { HostListState, ImmutableReducer } from '../../types'; import { AppAction } from '../action'; const initialState = (): HostListState => { @@ -21,7 +20,7 @@ const initialState = (): HostListState => { }; }; -export const hostListReducer: Reducer = ( +export const hostListReducer: ImmutableReducer = ( state = initialState(), action ) => { diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/selectors.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/selectors.ts index ebe310cb51190..35bf5d0616878 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/selectors.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/selectors.ts @@ -8,30 +8,30 @@ import { createSelector } from 'reselect'; import { Immutable } from '../../../../../common/types'; import { HostListState, HostIndexUIQueryParams } from '../../types'; -export const listData = (state: HostListState) => state.hosts; +export const listData = (state: Immutable) => state.hosts; -export const pageIndex = (state: HostListState) => state.pageIndex; +export const pageIndex = (state: Immutable) => state.pageIndex; -export const pageSize = (state: HostListState) => state.pageSize; +export const pageSize = (state: Immutable) => state.pageSize; -export const totalHits = (state: HostListState) => state.total; +export const totalHits = (state: Immutable) => state.total; -export const isLoading = (state: HostListState) => state.loading; +export const isLoading = (state: Immutable) => state.loading; -export const detailsError = (state: HostListState) => state.detailsError; +export const detailsError = (state: Immutable) => state.detailsError; -export const detailsData = (state: HostListState) => { +export const detailsData = (state: Immutable) => { return state.details; }; -export const isOnHostPage = (state: HostListState) => +export const isOnHostPage = (state: Immutable) => state.location ? state.location.pathname === '/hosts' : false; export const uiQueryParams: ( - state: HostListState + state: Immutable ) => Immutable = createSelector( - (state: HostListState) => state.location, - (location: HostListState['location']) => { + (state: Immutable) => state.location, + (location: Immutable['location']) => { const data: HostIndexUIQueryParams = {}; if (location) { // Removes the `?` from the beginning of query string if it exists @@ -52,7 +52,7 @@ export const uiQueryParams: ( } ); -export const hasSelectedHost: (state: HostListState) => boolean = createSelector( +export const hasSelectedHost: (state: Immutable) => boolean = createSelector( uiQueryParams, ({ selected_host: selectedHost }) => { return selectedHost !== undefined; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/immutable_combine_reducers.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/immutable_combine_reducers.ts new file mode 100644 index 0000000000000..6895f0106fb5d --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/immutable_combine_reducers.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +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; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/action.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/action.ts index 9905145048a8a..4de3dac02a8ec 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/action.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/action.ts @@ -4,8 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PolicyData, PolicyDetailsState, ServerApiError, UIPolicyConfig } from '../../types'; +import { PolicyDetailsState, ServerApiError } from '../../types'; import { GetAgentStatusResponse } from '../../../../../../ingest_manager/common/types/rest_spec'; +import { PolicyData, UIPolicyConfig } from '../../../../../common/types'; interface ServerReturnedPolicyDetailsData { type: 'serverReturnedPolicyDetailsData'; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/index.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/index.test.ts index f81852d6a074a..a24687ebbcbbc 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/index.test.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/index.test.ts @@ -9,7 +9,7 @@ import { createStore, Dispatch, Store } from 'redux'; import { policyDetailsReducer, PolicyDetailsAction } from './index'; import { policyConfig } from './selectors'; import { clone } from '../../models/policy_details_config'; -import { generatePolicy } from '../../models/policy'; +import { factory as policyConfigFactory } from '../../../../../common/models/policy_config'; describe('policy details: ', () => { let store: Store; @@ -38,7 +38,7 @@ describe('policy details: ', () => { streams: [], config: { policy: { - value: generatePolicy(), + value: policyConfigFactory(), }, }, }, diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/middleware.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/middleware.ts index 2581ab37f5677..7a3fbe7f23b9b 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/middleware.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/middleware.ts @@ -4,19 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - MiddlewareFactory, - PolicyData, - PolicyDetailsState, - UpdatePolicyResponse, -} from '../../types'; +import { MiddlewareFactory, PolicyDetailsState, UpdatePolicyResponse } from '../../types'; import { policyIdFromParams, isOnPolicyDetailsPage, policyDetails } from './selectors'; -import { generatePolicy } from '../../models/policy'; import { sendGetDatasource, sendGetFleetAgentStatusForConfig, sendPutDatasource, } from '../policy_list/services/ingest'; +import { PolicyData } from '../../../../../common/types'; +import { factory as policyConfigFactory } from '../../../../../common/models/policy_config'; export const policyDetailsMiddlewareFactory: MiddlewareFactory = coreStart => { const http = coreStart.http; @@ -49,7 +45,7 @@ export const policyDetailsMiddlewareFactory: MiddlewareFactory { return { @@ -23,7 +23,7 @@ const initialPolicyDetailsState = (): PolicyDetailsState => { }; }; -export const policyDetailsReducer: Reducer = ( +export const policyDetailsReducer: ImmutableReducer = ( state = initialPolicyDetailsState(), action ) => { @@ -70,7 +70,7 @@ export const policyDetailsReducer: Reducer = ( } if (action.type === 'userChangedUrl') { - const newState = { + const newState: Immutable = { ...state, location: action.payload, }; @@ -79,8 +79,10 @@ export const policyDetailsReducer: Reducer = ( // Did user just enter the Detail page? if so, then set the loading indicator and return new state if (isCurrentlyOnDetailsPage && !wasPreviouslyOnDetailsPage) { - newState.isLoading = true; - return newState; + return { + ...newState, + isLoading: true, + }; } return { ...initialPolicyDetailsState(), @@ -93,10 +95,19 @@ export const policyDetailsReducer: Reducer = ( return state; } const newState = { ...state, policyItem: { ...state.policyItem } }; - const newPolicy: any = { ...fullPolicy(state) }; + const newPolicy: PolicyConfig = { ...fullPolicy(state) }; + + /** + * This is directly changing redux state because `policyItem.inputs` was copied over and not cloned. + */ + // @ts-ignore newState.policyItem.inputs[0].config.policy.value = newPolicy; Object.entries(action.payload.policyConfig).forEach(([section, newSettings]) => { + /** + * this is not safe because `action.payload.policyConfig` may have excess keys + */ + // @ts-ignore newPolicy[section as keyof UIPolicyConfig] = { ...newPolicy[section as keyof UIPolicyConfig], ...newSettings, diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/selectors.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/selectors.ts index a37a06bafcf05..98e129132e1cb 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/selectors.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/selectors.ts @@ -5,14 +5,15 @@ */ import { createSelector } from 'reselect'; -import { PolicyConfig, PolicyDetailsState, UIPolicyConfig } from '../../types'; -import { generatePolicy } from '../../models/policy'; +import { PolicyDetailsState } from '../../types'; +import { Immutable, PolicyConfig, UIPolicyConfig } from '../../../../../common/types'; +import { factory as policyConfigFactory } from '../../../../../common/models/policy_config'; /** Returns the policy details */ -export const policyDetails = (state: PolicyDetailsState) => state.policyItem; +export const policyDetails = (state: Immutable) => state.policyItem; /** Returns a boolean of whether the user is on the policy details page or not */ -export const isOnPolicyDetailsPage = (state: PolicyDetailsState) => { +export const isOnPolicyDetailsPage = (state: Immutable) => { if (state.location) { const pathnameParts = state.location.pathname.split('/'); return pathnameParts[1] === 'policy' && pathnameParts[2]; @@ -32,14 +33,16 @@ export const policyIdFromParams: (state: PolicyDetailsState) => string = createS } ); +const defaultFullPolicy: Immutable = policyConfigFactory(); + /** * Returns the full Endpoint Policy, which will include private settings not shown on the UI. * Note: this will return a default full policy if the `policyItem` is `undefined` */ -export const fullPolicy: (s: PolicyDetailsState) => PolicyConfig = createSelector( +export const fullPolicy: (s: Immutable) => PolicyConfig = createSelector( policyDetails, policyData => { - return policyData?.inputs[0]?.config?.policy?.value ?? generatePolicy(); + return policyData?.inputs[0]?.config?.policy?.value ?? defaultFullPolicy; } ); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/action.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/action.ts index 3db224f049c05..4c379b7426461 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/action.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/action.ts @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PolicyData, ServerApiError } from '../../types'; +import { ServerApiError } from '../../types'; +import { PolicyData } from '../../../../../common/types'; interface ServerReturnedPolicyListData { type: 'serverReturnedPolicyListData'; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/index.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/index.test.ts index 4d153b5e03cd2..97a2b65fb65f8 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/index.test.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/index.test.ts @@ -5,7 +5,7 @@ */ import { EndpointAppLocation, PolicyListState } from '../../types'; -import { applyMiddleware, createStore, Dispatch, Store } from 'redux'; +import { applyMiddleware, createStore, Store } from 'redux'; import { AppAction } from '../action'; import { policyListReducer } from './reducer'; import { policyListMiddlewareFactory } from './middleware'; @@ -18,13 +18,15 @@ import { setPolicyListApiMockImplementation, } from './test_mock_utils'; import { INGEST_API_DATASOURCES } from './services/ingest'; +import { Immutable } from '../../../../../common/types'; describe('policy list store concerns', () => { let fakeCoreStart: ReturnType; let depsStart: DepsStartMock; - let store: Store; - let getState: typeof store['getState']; - let dispatch: Dispatch; + type PolicyListStore = Store, Immutable>; + let store: PolicyListStore; + let getState: PolicyListStore['getState']; + let dispatch: PolicyListStore['dispatch']; let waitForAction: MiddlewareActionSpyHelper['waitForAction']; beforeEach(() => { diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/reducer.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/reducer.ts index 30c1deac7f5e1..ccd3f84dd060c 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/reducer.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/reducer.ts @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Reducer } from 'redux'; -import { PolicyListState } from '../../types'; +import { PolicyListState, ImmutableReducer } from '../../types'; import { AppAction } from '../action'; import { isOnPolicyListPage } from './selectors'; +import { Immutable } from '../../../../../common/types'; const initialPolicyListState = (): PolicyListState => { return { @@ -21,7 +21,7 @@ const initialPolicyListState = (): PolicyListState => { }; }; -export const policyListReducer: Reducer = ( +export const policyListReducer: ImmutableReducer = ( state = initialPolicyListState(), action ) => { @@ -42,7 +42,7 @@ export const policyListReducer: Reducer = ( } if (action.type === 'userChangedUrl') { - const newState = { + const newState: Immutable = { ...state, location: action.payload, }; @@ -53,14 +53,15 @@ export const policyListReducer: Reducer = ( // Also adjust some state if user is just entering the policy list view if (isCurrentlyOnListPage) { if (!wasPreviouslyOnListPage) { - newState.apiError = undefined; - newState.isLoading = true; + return { + ...newState, + apiError: undefined, + isLoading: true, + }; } return newState; } - return { - ...initialPolicyListState(), - }; + return initialPolicyListState(); } return state; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/selectors.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/selectors.ts index ce13d89b2b8c2..6d2e952fa07bb 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/selectors.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/selectors.ts @@ -7,32 +7,33 @@ import { createSelector } from 'reselect'; import { parse } from 'query-string'; import { PolicyListState, PolicyListUrlSearchParams } from '../../types'; +import { Immutable } from '../../../../../common/types'; const PAGE_SIZES = Object.freeze([10, 20, 50]); -export const selectPolicyItems = (state: PolicyListState) => state.policyItems; +export const selectPolicyItems = (state: Immutable) => state.policyItems; -export const selectPageIndex = (state: PolicyListState) => state.pageIndex; +export const selectPageIndex = (state: Immutable) => state.pageIndex; -export const selectPageSize = (state: PolicyListState) => state.pageSize; +export const selectPageSize = (state: Immutable) => state.pageSize; -export const selectTotal = (state: PolicyListState) => state.total; +export const selectTotal = (state: Immutable) => state.total; -export const selectIsLoading = (state: PolicyListState) => state.isLoading; +export const selectIsLoading = (state: Immutable) => state.isLoading; -export const selectApiError = (state: PolicyListState) => state.apiError; +export const selectApiError = (state: Immutable) => state.apiError; -export const isOnPolicyListPage = (state: PolicyListState) => { +export const isOnPolicyListPage = (state: Immutable) => { return state.location?.pathname === '/policy'; }; -const routeLocation = (state: PolicyListState) => state.location; +const routeLocation = (state: Immutable) => state.location; /** * Returns the supported URL search params, populated with defaults if none where present in the URL */ export const urlSearchParams: ( - state: PolicyListState + state: Immutable ) => PolicyListUrlSearchParams = createSelector(routeLocation, location => { const searchParams = { page_index: 0, diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/services/ingest.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/services/ingest.ts index 5fccb01d1ad35..4356517e43c2c 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/services/ingest.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_list/services/ingest.ts @@ -9,12 +9,8 @@ import { GetDatasourcesRequest, GetAgentStatusResponse, } from '../../../../../../../ingest_manager/common'; -import { - NewPolicyData, - GetPolicyListResponse, - GetPolicyResponse, - UpdatePolicyResponse, -} from '../../../types'; +import { GetPolicyListResponse, GetPolicyResponse, UpdatePolicyResponse } from '../../../types'; +import { NewPolicyData } from '../../../../../../common/types'; const INGEST_API_ROOT = `/api/ingest_manager`; export const INGEST_API_DATASOURCES = `${INGEST_API_ROOT}/datasources`; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/reducer.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/reducer.ts index c8b2d08676724..2f77c380d9387 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/reducer.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/reducer.ts @@ -3,15 +3,16 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { combineReducers, Reducer } from 'redux'; + import { hostListReducer } from './hosts'; import { AppAction } from './action'; import { alertListReducer } from './alerts'; -import { GlobalState } from '../types'; +import { GlobalState, ImmutableReducer } from '../types'; import { policyListReducer } from './policy_list'; import { policyDetailsReducer } from './policy_details'; +import { immutableCombineReducers } from './immutable_combine_reducers'; -export const appReducer: Reducer = combineReducers({ +export const appReducer: ImmutableReducer = immutableCombineReducers({ hostList: hostListReducer, alertList: alertListReducer, policyList: policyListReducer, diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/types.ts b/x-pack/plugins/endpoint/public/applications/endpoint/types.ts index 015468f84e740..7aca94d3e9c7c 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/types.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/types.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Dispatch, MiddlewareAPI } from 'redux'; +import { Dispatch, MiddlewareAPI, Action as ReduxAction, AnyAction as ReduxAnyAction } from 'redux'; import { IIndexPattern } from 'src/plugins/data/public'; import { HostMetadata, @@ -13,13 +13,14 @@ import { Immutable, ImmutableArray, AlertDetails, + MalwareFields, + UIPolicyConfig, + PolicyData, } from '../../../common/types'; import { EndpointPluginStartDependencies } from '../../plugin'; import { AppAction } from './store/action'; import { CoreStart } from '../../../../../../src/core/public'; import { - Datasource, - NewDatasource, GetAgentStatusResponse, GetDatasourcesResponse, GetOneDatasourceResponse, @@ -59,29 +60,6 @@ export interface ServerApiError { message: string; } -/** - * New policy data. Used when updating the policy record via ingest APIs - */ -export type NewPolicyData = NewDatasource & { - inputs: [ - { - type: 'endpoint'; - enabled: boolean; - streams: []; - config: { - policy: { - value: PolicyConfig; - }; - }; - } - ]; -}; - -/** - * Endpoint Policy data, which extends Ingest's `Datasource` type - */ -export type PolicyData = Datasource & NewPolicyData; - /** * Policy list store state */ @@ -192,30 +170,6 @@ interface PolicyConfigAdvancedOptions { }; } -/** - * Windows-specific policy configuration that is supported via the UI - */ -type WindowsPolicyConfig = Pick; - -/** - * Mac-specific policy configuration that is supported via the UI - */ -type MacPolicyConfig = Pick; - -/** - * Linux-specific policy configuration that is supported via the UI - */ -type LinuxPolicyConfig = Pick; - -/** - * The set of Policy configuration settings that are show/edited via the UI - */ -export interface UIPolicyConfig { - windows: WindowsPolicyConfig; - mac: MacPolicyConfig; - linux: LinuxPolicyConfig; -} - /** OS used in Policy */ export enum OS { windows = 'windows', @@ -246,20 +200,7 @@ export type KeysByValueCriteria = { }[keyof O]; /** Returns an array of the policy OSes that have a malware protection field */ - export type MalwareProtectionOSes = KeysByValueCriteria; -/** Policy: Malware protection fields */ -export interface MalwareFields { - mode: ProtectionModes; -} - -/** Policy protection mode options */ -export enum ProtectionModes { - detect = 'detect', - prevent = 'prevent', - preventNotify = 'preventNotify', - off = 'off', -} export interface GlobalState { readonly hostList: HostListState; @@ -349,3 +290,28 @@ export interface GetPolicyResponse extends GetOneDatasourceResponse { 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: Immutable | undefined, + action: Immutable +) => State | Immutable; + +/** + * 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 = ( + reducers: ImmutableReducersMapObject +) => ImmutableReducer; + +/** + * Like `redux`'s `ReducersMapObject` (which is used by `combineReducers`) but enforces that + * the `state` and `action` received are `Immutable` versions. + */ +type ImmutableReducersMapObject = { + [K in keyof S]: ImmutableReducer; +}; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index_search_bar.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index_search_bar.tsx index 5b872962a5dc0..1ede06c086517 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index_search_bar.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index_search_bar.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { useMemo } from 'react'; import { memo, useEffect, useCallback } from 'react'; import { useHistory } from 'react-router-dom'; import { encode, RisonValue } from 'rison-node'; @@ -14,11 +14,18 @@ import { urlFromQueryParams } from './url_from_query_params'; import { useAlertListSelector } from './hooks/use_alerts_selector'; import * as selectors from '../../store/alerts/selectors'; import { EndpointPluginServices } from '../../../../plugin'; +import { clone } from '../../models/index_pattern'; export const AlertIndexSearchBar = memo(() => { const history = useHistory(); const queryParams = useAlertListSelector(selectors.uiQueryParams); const searchBarIndexPatterns = useAlertListSelector(selectors.searchBarIndexPatterns); + + // Deeply clone the search bar index patterns as the receiving component may mutate them + const clonedSearchBarIndexPatterns = useMemo( + () => searchBarIndexPatterns.map(pattern => clone(pattern)), + [searchBarIndexPatterns] + ); const searchBarQuery = useAlertListSelector(selectors.searchBarQuery); const searchBarDateRange = useAlertListSelector(selectors.searchBarDateRange); const searchBarFilters = useAlertListSelector(selectors.searchBarFilters); @@ -68,7 +75,7 @@ export const AlertIndexSearchBar = memo(() => { dataTestSubj="alertsSearchBar" appName="endpoint" isLoading={false} - indexPatterns={searchBarIndexPatterns} + indexPatterns={clonedSearchBarIndexPatterns} query={searchBarQuery} dateRangeFrom={searchBarDateRange.from} dateRangeTo={searchBarDateRange.to} diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.tsx index 94625b8c66191..1d81d6e8a16db 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.tsx @@ -23,12 +23,14 @@ import { i18n } from '@kbn/i18n'; import styled from 'styled-components'; import { FormattedMessage } from '@kbn/i18n/react'; import { createStructuredSelector } from 'reselect'; +import { EuiBasicTableColumn } from '@elastic/eui'; import { HostDetailsFlyout } from './details'; import * as selectors from '../../store/hosts/selectors'; import { HostAction } from '../../store/hosts/action'; import { useHostListSelector } from './hooks'; import { CreateStructuredSelector } from '../../types'; import { urlFromQueryParams } from './url_from_query_params'; +import { HostMetadata, Immutable } from '../../../../../common/types'; const selector = (createStructuredSelector as CreateStructuredSelector)(selectors); export const HostList = () => { @@ -65,7 +67,7 @@ export const HostList = () => { [dispatch] ); - const columns = useMemo(() => { + const columns: Array>> = useMemo(() => { return [ { field: '', @@ -174,7 +176,7 @@ export const HostList = () => { [...listData], [listData])} columns={columns} loading={isLoading} pagination={paginationSetup} diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/config_form.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/config_form.tsx index 8b6c32c3277ed..341086c7cf75c 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/config_form.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/config_form.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { useMemo } from 'react'; import { EuiCard, EuiFlexGroup, @@ -25,14 +25,27 @@ const PolicyDetailCard = styled.div` } `; export const ConfigForm: React.FC<{ + /** + * A subtitle for this component. + **/ type: string; - supportedOss: string[]; + /** + * Types of supported operating systems. + */ + supportedOss: React.ReactNode; children: React.ReactNode; - id: string; - /** Takes a react component to be put on the right corner of the card */ + /** + * A description for the component. + */ + description: string; + /** + * The `data-test-subj` attribute to append to a certain child element. + */ + dataTestSubj: string; + /** React Node to be put on the right corner of the card */ rightCorner: React.ReactNode; -}> = React.memo(({ type, supportedOss, children, id, rightCorner }) => { - const typeTitle = () => { +}> = React.memo(({ type, supportedOss, children, dataTestSubj, rightCorner, description }) => { + const typeTitle = useMemo(() => { return ( @@ -59,28 +72,25 @@ export const ConfigForm: React.FC<{ - {supportedOss.join(', ')} + {supportedOss} {rightCorner} ); - }; + }, [rightCorner, supportedOss, type]); return ( - - {children} - - } - /> + title={typeTitle} + > + + {children} + ); }); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/checkbox.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/checkbox.tsx index bec6b33b85c7f..74322ac8b993b 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/checkbox.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/checkbox.tsx @@ -11,7 +11,7 @@ import { htmlIdGenerator } from '@elastic/eui'; import { usePolicyDetailsSelector } from '../../policy_hooks'; import { policyConfig } from '../../../../store/policy_details/selectors'; import { PolicyDetailsAction } from '../../../../store/policy_details'; -import { UIPolicyConfig } from '../../../../types'; +import { UIPolicyConfig } from '../../../../../../../common/types'; export const EventsCheckbox = React.memo(function({ name, diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/linux.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/linux.tsx index ca2215d3d0a59..c3d6bdba7c852 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/linux.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/linux.tsx @@ -8,24 +8,24 @@ import React, { useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiTitle, EuiText, EuiSpacer } from '@elastic/eui'; -import { ImmutableArray } from '../../../../../../../common/types'; -import { getIn, setIn } from '../../../../models/policy_details_config'; import { EventsCheckbox } from './checkbox'; -import { OS, UIPolicyConfig } from '../../../../types'; +import { OS } from '../../../../types'; import { usePolicyDetailsSelector } from '../../policy_hooks'; import { selectedLinuxEvents, totalLinuxEvents } from '../../../../store/policy_details/selectors'; import { ConfigForm } from '../config_form'; +import { getIn, setIn } from '../../../../models/policy_details_config'; +import { UIPolicyConfig } from '../../../../../../../common/types'; export const LinuxEvents = React.memo(() => { const selected = usePolicyDetailsSelector(selectedLinuxEvents); const total = usePolicyDetailsSelector(totalLinuxEvents); - const checkboxes: ImmutableArray<{ - name: string; - os: 'linux'; - protectionField: keyof UIPolicyConfig['linux']['events']; - }> = useMemo( - () => [ + const checkboxes = useMemo(() => { + const items: Array<{ + name: string; + os: 'linux'; + protectionField: keyof UIPolicyConfig['linux']['events']; + }> = [ { name: i18n.translate('xpack.endpoint.policyDetailsConfig.linux.events.file', { defaultMessage: 'File', @@ -47,11 +47,7 @@ export const LinuxEvents = React.memo(() => { os: OS.linux, protectionField: 'network', }, - ], - [] - ); - - const renderCheckboxes = useMemo(() => { + ]; return ( <> @@ -63,7 +59,7 @@ export const LinuxEvents = React.memo(() => { - {checkboxes.map((item, index) => { + {items.map((item, index) => { return ( { })} ); - }, [checkboxes]); + }, []); const collectionsEnabled = useMemo(() => { return ( @@ -96,13 +92,16 @@ export const LinuxEvents = React.memo(() => { type={i18n.translate('xpack.endpoint.policy.details.eventCollection', { defaultMessage: 'Event Collection', })} - supportedOss={useMemo( - () => [i18n.translate('xpack.endpoint.policy.details.linux', { defaultMessage: 'Linux' })], - [] - )} - id="linuxEventsForm" + description={i18n.translate('xpack.endpoint.policy.details.eventCollectionLabel', { + defaultMessage: 'Event Collection', + })} + supportedOss={i18n.translate('xpack.endpoint.policy.details.linux', { + defaultMessage: 'Linux', + })} + dataTestSubj="linuxEventingForm" rightCorner={collectionsEnabled} - children={renderCheckboxes} - /> + > + {checkboxes} + ); }); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/mac.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/mac.tsx index 5024d02603d77..40b80b9af0f65 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/mac.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/mac.tsx @@ -8,24 +8,24 @@ import React, { useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiTitle, EuiText, EuiSpacer } from '@elastic/eui'; -import { ImmutableArray } from '../../../../../../../common/types'; -import { getIn, setIn } from '../../../../models/policy_details_config'; import { EventsCheckbox } from './checkbox'; -import { OS, UIPolicyConfig } from '../../../../types'; +import { OS } from '../../../../types'; import { usePolicyDetailsSelector } from '../../policy_hooks'; import { selectedMacEvents, totalMacEvents } from '../../../../store/policy_details/selectors'; import { ConfigForm } from '../config_form'; +import { getIn, setIn } from '../../../../models/policy_details_config'; +import { UIPolicyConfig } from '../../../../../../../common/types'; export const MacEvents = React.memo(() => { const selected = usePolicyDetailsSelector(selectedMacEvents); const total = usePolicyDetailsSelector(totalMacEvents); - const checkboxes: ImmutableArray<{ - name: string; - os: 'mac'; - protectionField: keyof UIPolicyConfig['mac']['events']; - }> = useMemo( - () => [ + const checkboxes = useMemo(() => { + const items: Array<{ + name: string; + os: 'mac'; + protectionField: keyof UIPolicyConfig['mac']['events']; + }> = [ { name: i18n.translate('xpack.endpoint.policyDetailsConfig.mac.events.file', { defaultMessage: 'File', @@ -47,11 +47,7 @@ export const MacEvents = React.memo(() => { os: OS.mac, protectionField: 'network', }, - ], - [] - ); - - const renderCheckboxes = useMemo(() => { + ]; return ( <> @@ -63,7 +59,7 @@ export const MacEvents = React.memo(() => { - {checkboxes.map((item, index) => { + {items.map((item, index) => { return ( { })} ); - }, [checkboxes]); + }, []); const collectionsEnabled = useMemo(() => { return ( @@ -96,13 +92,14 @@ export const MacEvents = React.memo(() => { type={i18n.translate('xpack.endpoint.policy.details.eventCollection', { defaultMessage: 'Event Collection', })} - supportedOss={useMemo( - () => [i18n.translate('xpack.endpoint.policy.details.mac', { defaultMessage: 'Mac' })], - [] - )} - id="macEventsForm" + description={i18n.translate('xpack.endpoint.policy.details.eventCollectionLabel', { + defaultMessage: 'Event Collection', + })} + supportedOss={i18n.translate('xpack.endpoint.policy.details.mac', { defaultMessage: 'Mac' })} + dataTestSubj="macEventingForm" rightCorner={collectionsEnabled} - children={renderCheckboxes} - /> + > + {checkboxes} + ); }); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/windows.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/windows.tsx index 5b347ec387f48..7f946de9614ca 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/windows.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/events/windows.tsx @@ -8,27 +8,27 @@ import React, { useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiTitle, EuiText, EuiSpacer } from '@elastic/eui'; -import { ImmutableArray } from '../../../../../../../common/types'; -import { setIn, getIn } from '../../../../models/policy_details_config'; import { EventsCheckbox } from './checkbox'; -import { OS, UIPolicyConfig } from '../../../../types'; +import { OS } from '../../../../types'; import { usePolicyDetailsSelector } from '../../policy_hooks'; import { selectedWindowsEvents, totalWindowsEvents, } from '../../../../store/policy_details/selectors'; import { ConfigForm } from '../config_form'; +import { setIn, getIn } from '../../../../models/policy_details_config'; +import { UIPolicyConfig, ImmutableArray } from '../../../../../../../common/types'; export const WindowsEvents = React.memo(() => { const selected = usePolicyDetailsSelector(selectedWindowsEvents); const total = usePolicyDetailsSelector(totalWindowsEvents); - const checkboxes: ImmutableArray<{ - name: string; - os: 'windows'; - protectionField: keyof UIPolicyConfig['windows']['events']; - }> = useMemo( - () => [ + const checkboxes = useMemo(() => { + const items: ImmutableArray<{ + name: string; + os: 'windows'; + protectionField: keyof UIPolicyConfig['windows']['events']; + }> = [ { name: i18n.translate('xpack.endpoint.policyDetailsConfig.windows.events.dllDriverLoad', { defaultMessage: 'DLL and Driver Load', @@ -78,11 +78,7 @@ export const WindowsEvents = React.memo(() => { os: OS.windows, protectionField: 'security', }, - ], - [] - ); - - const renderCheckboxes = useMemo(() => { + ]; return ( <> @@ -94,7 +90,7 @@ export const WindowsEvents = React.memo(() => { - {checkboxes.map((item, index) => { + {items.map((item, index) => { return ( { })} ); - }, [checkboxes]); + }, []); const collectionsEnabled = useMemo(() => { return ( @@ -127,15 +123,16 @@ export const WindowsEvents = React.memo(() => { type={i18n.translate('xpack.endpoint.policy.details.eventCollection', { defaultMessage: 'Event Collection', })} - supportedOss={useMemo( - () => [ - i18n.translate('xpack.endpoint.policy.details.windows', { defaultMessage: 'Windows' }), - ], - [] - )} - id="windowsEventsForm" + description={i18n.translate('xpack.endpoint.policy.details.windowsLabel', { + defaultMessage: 'Windows', + })} + supportedOss={i18n.translate('xpack.endpoint.policy.details.windows', { + defaultMessage: 'Windows', + })} + dataTestSubj="windowsEventingForm" rightCorner={collectionsEnabled} - children={renderCheckboxes} - /> + > + {checkboxes} + ); }); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/protections/malware.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/protections/malware.tsx index 66b22178607b9..14871c71ec038 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/protections/malware.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_forms/protections/malware.tsx @@ -11,8 +11,8 @@ import { EuiRadio, EuiSwitch, EuiTitle, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { htmlIdGenerator } from '@elastic/eui'; -import { Immutable } from '../../../../../../../common/types'; -import { OS, ProtectionModes, MalwareProtectionOSes } from '../../../../types'; +import { Immutable, ProtectionModes, ImmutableArray } from '../../../../../../../common/types'; +import { OS, MalwareProtectionOSes } from '../../../../types'; import { ConfigForm } from '../config_form'; import { policyConfig } from '../../../../store/policy_details/selectors'; import { usePolicyDetailsSelector } from '../../policy_hooks'; @@ -73,7 +73,7 @@ export const MalwareProtections = React.memo(() => { // currently just taking windows.malware, but both windows.malware and mac.malware should be the same value const selected = policyDetailsConfig && policyDetailsConfig.windows.malware.mode; - const radios: Array<{ + const radios: ImmutableArray<{ id: ProtectionModes; label: string; protection: 'malware'; @@ -123,7 +123,7 @@ export const MalwareProtections = React.memo(() => { [dispatch, policyDetailsConfig] ); - const RadioButtons = () => { + const radioButtons = useMemo(() => { return ( <> @@ -148,9 +148,9 @@ export const MalwareProtections = React.memo(() => { ); - }; + }, [radios]); - const ProtectionSwitch = () => { + const protectionSwitch = useMemo(() => { return ( { onChange={handleSwitchChange} /> ); - }; + }, [handleSwitchChange, selected]); return ( + supportedOss={i18n.translate('xpack.endpoint.policy.details.windowsAndMac', { + defaultMessage: 'Windows, Mac', + })} + dataTestSubj="malwareProtectionsForm" + description={i18n.translate('xpack.endpoint.policy.details.malwareLabel', { + defaultMessage: 'Malware', + })} + rightCorner={protectionSwitch} + > + {radioButtons} + ); }); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_list.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_list.tsx index 295312fff01dd..062c7afb6706d 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_list.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_list.tsx @@ -20,10 +20,10 @@ import { } from '../../store/policy_list/selectors'; import { usePolicyListSelector } from './policy_hooks'; import { PolicyListAction } from '../../store/policy_list'; -import { PolicyData } from '../../types'; import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; import { PageView } from '../components/page_view'; import { LinkToApp } from '../components/link_to_app'; +import { Immutable, PolicyData } from '../../../../../common/types'; interface TableChangeCallbackArguments { page: { index: number; size: number }; @@ -44,8 +44,8 @@ const PolicyLink: React.FC<{ name: string; route: string }> = ({ name, route }) ); }; -const renderPolicyNameLink = (value: string, _item: PolicyData) => { - return ; +const renderPolicyNameLink = (value: string, item: Immutable) => { + return ; }; export const PolicyList = React.memo(() => { @@ -88,7 +88,7 @@ export const PolicyList = React.memo(() => { [history, location.pathname] ); - const columns: Array> = useMemo( + const columns: Array>> = useMemo( () => [ { field: 'name', @@ -160,7 +160,7 @@ export const PolicyList = React.memo(() => { } > [...policyItems], [policyItems])} columns={columns} loading={loading} pagination={paginationSetup}