Endpoint: Redux types (#63413)
## Summary Changes some types around so modifying redux state or actions will cause type errors * the state and action types aren't using `Immutable` directly. This means that you can instantiate them and mutate them * the `state` and `action` params received by all reducers will be automatically cast to `Immutable` * reducers can return either `Immutable` or regular versions of `state` * some code may be mutating redux state directly, this is being ignored (at least for now)
This commit is contained in:
parent
bb0d03fe11
commit
08aa214190
|
@ -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(),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -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: {
|
|
@ -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> = 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>
|
||||
|
@ -442,3 +445,125 @@ export type AlertingIndexGetQueryInput = KbnConfigSchemaInputTypeOf<
|
|||
* Result of the validated query params when handling alert index requests.
|
||||
*/
|
||||
export type AlertingIndexGetQueryResult = TypeOf<typeof alertingIndexGetQuerySchema>;
|
||||
|
||||
/**
|
||||
* 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<PolicyConfig['windows'], 'events' | 'malware'>;
|
||||
|
||||
/**
|
||||
* Mac-specific policy configuration that is supported via the UI
|
||||
*/
|
||||
type MacPolicyConfig = Pick<PolicyConfig['mac'], 'malware' | 'events'>;
|
||||
|
||||
/**
|
||||
* Linux-specific policy configuration that is supported via the UI
|
||||
*/
|
||||
type LinuxPolicyConfig = Pick<PolicyConfig['linux'], 'events'>;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
};
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
|
|
|
@ -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>): IIndexPattern {
|
||||
return all([value]) as IIndexPattern;
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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<AlertListState, AppAction>;
|
||||
let store: Store<Immutable<AlertListState>, Immutable<AppAction>>;
|
||||
let coreStart: ReturnType<typeof coreMock.createStart>;
|
||||
let depsStart: DepsStartMock;
|
||||
let history: History<never>;
|
||||
/**
|
||||
* A function that waits until a selector returns true.
|
||||
*/
|
||||
let selectorIsTrue: (selector: (state: AlertListState) => boolean) => Promise<void>;
|
||||
let selectorIsTrue: (selector: (state: Immutable<AlertListState>) => boolean) => Promise<void>;
|
||||
beforeEach(() => {
|
||||
coreStart = coreMock.createStart();
|
||||
depsStart = depsStartMock();
|
||||
|
|
|
@ -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<AlertListState, AppAction>;
|
||||
let store: Store<Immutable<AlertListState>, Immutable<AppAction>>;
|
||||
let coreStart: ReturnType<typeof coreMock.createStart>;
|
||||
let depsStart: DepsStartMock;
|
||||
let history: History<never>;
|
||||
/**
|
||||
* A function that waits until a selector returns true.
|
||||
*/
|
||||
let selectorIsTrue: (selector: (state: AlertListState) => boolean) => Promise<void>;
|
||||
let selectorIsTrue: (selector: (state: Immutable<AlertListState>) => boolean) => Promise<void>;
|
||||
beforeEach(() => {
|
||||
coreStart = coreMock.createStart();
|
||||
depsStart = depsStartMock();
|
||||
|
|
|
@ -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<AlertListState, AppAction>;
|
||||
let store: Store<Immutable<AlertListState>, Immutable<AppAction>>;
|
||||
let coreStart: ReturnType<typeof coreMock.createStart>;
|
||||
let depsStart: DepsStartMock;
|
||||
let history: History<never>;
|
||||
|
|
|
@ -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<AlertListState> => {
|
||||
return {
|
||||
alerts: [],
|
||||
alertDetails: undefined,
|
||||
|
@ -22,7 +22,7 @@ const initialState = (): AlertListState => {
|
|||
};
|
||||
};
|
||||
|
||||
export const alertListReducer: Reducer<AlertListState, AppAction> = (
|
||||
export const alertListReducer: ImmutableReducer<AlertListState, AppAction> = (
|
||||
state = initialState(),
|
||||
action
|
||||
) => {
|
||||
|
|
|
@ -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<AlertListState>) => state.alerts;
|
||||
|
||||
export const selectedAlertDetailsData = (state: AlertListState) => state.alertDetails;
|
||||
export const selectedAlertDetailsData = (state: Immutable<AlertListState>) => 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<AlertListState>) => state.pageIndex,
|
||||
pageSize: (state: Immutable<AlertListState>) => state.pageSize,
|
||||
total: (state: Immutable<AlertListState>) => 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<AlertListState>): 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<AlertListState>
|
||||
) => Immutable<AlertingIndexUIQueryParams> = createSelector(
|
||||
(state: AlertListState) => state.location,
|
||||
(location: AlertListState['location']) => {
|
||||
state => state.location,
|
||||
(location: Immutable<AlertListState>['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<AlertListState>) => 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<AlertListState>
|
||||
) => 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<AlertListState>) => 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<AlertListState>) => 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<AlertListState>) =>
|
||||
state.searchBar.patterns;
|
||||
|
||||
/**
|
||||
* query params to use when requesting alert data.
|
||||
*/
|
||||
export const apiQueryParams: (
|
||||
state: AlertListState
|
||||
state: Immutable<AlertListState>
|
||||
) => Immutable<AlertingIndexGetQueryInput> = 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<AlertListState>) => boolean = createSelector(
|
||||
uiQueryParams,
|
||||
({ selected_alert: selectedAlert }) => selectedAlert !== undefined
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<CoreStart>;
|
||||
let depsStart: DepsStartMock;
|
||||
let fakeHttpServices: jest.Mocked<HttpSetup>;
|
||||
let store: Store<HostListState>;
|
||||
let getState: typeof store['getState'];
|
||||
let dispatch: Dispatch<AppAction>;
|
||||
type HostListStore = Store<Immutable<HostListState>, Immutable<AppAction>>;
|
||||
let store: HostListStore;
|
||||
let getState: HostListStore['getState'];
|
||||
let dispatch: HostListStore['dispatch'];
|
||||
|
||||
let history: History<never>;
|
||||
const getEndpointListApiResponse = (): HostResultList => {
|
||||
|
|
|
@ -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<HostListState, AppAction> = (
|
||||
export const hostListReducer: ImmutableReducer<HostListState, AppAction> = (
|
||||
state = initialState(),
|
||||
action
|
||||
) => {
|
||||
|
|
|
@ -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<HostListState>) => state.hosts;
|
||||
|
||||
export const pageIndex = (state: HostListState) => state.pageIndex;
|
||||
export const pageIndex = (state: Immutable<HostListState>) => state.pageIndex;
|
||||
|
||||
export const pageSize = (state: HostListState) => state.pageSize;
|
||||
export const pageSize = (state: Immutable<HostListState>) => state.pageSize;
|
||||
|
||||
export const totalHits = (state: HostListState) => state.total;
|
||||
export const totalHits = (state: Immutable<HostListState>) => state.total;
|
||||
|
||||
export const isLoading = (state: HostListState) => state.loading;
|
||||
export const isLoading = (state: Immutable<HostListState>) => state.loading;
|
||||
|
||||
export const detailsError = (state: HostListState) => state.detailsError;
|
||||
export const detailsError = (state: Immutable<HostListState>) => state.detailsError;
|
||||
|
||||
export const detailsData = (state: HostListState) => {
|
||||
export const detailsData = (state: Immutable<HostListState>) => {
|
||||
return state.details;
|
||||
};
|
||||
|
||||
export const isOnHostPage = (state: HostListState) =>
|
||||
export const isOnHostPage = (state: Immutable<HostListState>) =>
|
||||
state.location ? state.location.pathname === '/hosts' : false;
|
||||
|
||||
export const uiQueryParams: (
|
||||
state: HostListState
|
||||
state: Immutable<HostListState>
|
||||
) => Immutable<HostIndexUIQueryParams> = createSelector(
|
||||
(state: HostListState) => state.location,
|
||||
(location: HostListState['location']) => {
|
||||
(state: Immutable<HostListState>) => state.location,
|
||||
(location: Immutable<HostListState>['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<HostListState>) => boolean = createSelector(
|
||||
uiQueryParams,
|
||||
({ selected_host: selectedHost }) => {
|
||||
return selectedHost !== undefined;
|
||||
|
|
|
@ -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;
|
|
@ -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';
|
||||
|
|
|
@ -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<PolicyDetailsState>;
|
||||
|
@ -38,7 +38,7 @@ describe('policy details: ', () => {
|
|||
streams: [],
|
||||
config: {
|
||||
policy: {
|
||||
value: generatePolicy(),
|
||||
value: policyConfigFactory(),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -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<PolicyDetailsState> = coreStart => {
|
||||
const http = coreStart.http;
|
||||
|
@ -49,7 +45,7 @@ export const policyDetailsMiddlewareFactory: MiddlewareFactory<PolicyDetailsStat
|
|||
streams: [],
|
||||
config: {
|
||||
policy: {
|
||||
value: generatePolicy(),
|
||||
value: policyConfigFactory(),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -4,10 +4,10 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { Reducer } from 'redux';
|
||||
import { PolicyDetailsState, UIPolicyConfig } from '../../types';
|
||||
import { AppAction } from '../action';
|
||||
import { fullPolicy, isOnPolicyDetailsPage } from './selectors';
|
||||
import { PolicyDetailsState, ImmutableReducer } from '../../types';
|
||||
import { Immutable, PolicyConfig, UIPolicyConfig } from '../../../../../common/types';
|
||||
|
||||
const initialPolicyDetailsState = (): PolicyDetailsState => {
|
||||
return {
|
||||
|
@ -23,7 +23,7 @@ const initialPolicyDetailsState = (): PolicyDetailsState => {
|
|||
};
|
||||
};
|
||||
|
||||
export const policyDetailsReducer: Reducer<PolicyDetailsState, AppAction> = (
|
||||
export const policyDetailsReducer: ImmutableReducer<PolicyDetailsState, AppAction> = (
|
||||
state = initialPolicyDetailsState(),
|
||||
action
|
||||
) => {
|
||||
|
@ -70,7 +70,7 @@ export const policyDetailsReducer: Reducer<PolicyDetailsState, AppAction> = (
|
|||
}
|
||||
|
||||
if (action.type === 'userChangedUrl') {
|
||||
const newState = {
|
||||
const newState: Immutable<PolicyDetailsState> = {
|
||||
...state,
|
||||
location: action.payload,
|
||||
};
|
||||
|
@ -79,8 +79,10 @@ export const policyDetailsReducer: Reducer<PolicyDetailsState, AppAction> = (
|
|||
|
||||
// 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<PolicyDetailsState, AppAction> = (
|
|||
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,
|
||||
|
|
|
@ -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<PolicyDetailsState>) => 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<PolicyDetailsState>) => {
|
||||
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<PolicyConfig> = 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<PolicyDetailsState>) => PolicyConfig = createSelector(
|
||||
policyDetails,
|
||||
policyData => {
|
||||
return policyData?.inputs[0]?.config?.policy?.value ?? generatePolicy();
|
||||
return policyData?.inputs[0]?.config?.policy?.value ?? defaultFullPolicy;
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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<typeof coreMock.createStart>;
|
||||
let depsStart: DepsStartMock;
|
||||
let store: Store<PolicyListState>;
|
||||
let getState: typeof store['getState'];
|
||||
let dispatch: Dispatch<AppAction>;
|
||||
type PolicyListStore = Store<Immutable<PolicyListState>, Immutable<AppAction>>;
|
||||
let store: PolicyListStore;
|
||||
let getState: PolicyListStore['getState'];
|
||||
let dispatch: PolicyListStore['dispatch'];
|
||||
let waitForAction: MiddlewareActionSpyHelper['waitForAction'];
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -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<PolicyListState, AppAction> = (
|
||||
export const policyListReducer: ImmutableReducer<PolicyListState, AppAction> = (
|
||||
state = initialPolicyListState(),
|
||||
action
|
||||
) => {
|
||||
|
@ -42,7 +42,7 @@ export const policyListReducer: Reducer<PolicyListState, AppAction> = (
|
|||
}
|
||||
|
||||
if (action.type === 'userChangedUrl') {
|
||||
const newState = {
|
||||
const newState: Immutable<PolicyListState> = {
|
||||
...state,
|
||||
location: action.payload,
|
||||
};
|
||||
|
@ -53,14 +53,15 @@ export const policyListReducer: Reducer<PolicyListState, AppAction> = (
|
|||
// 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;
|
||||
|
|
|
@ -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<PolicyListState>) => state.policyItems;
|
||||
|
||||
export const selectPageIndex = (state: PolicyListState) => state.pageIndex;
|
||||
export const selectPageIndex = (state: Immutable<PolicyListState>) => state.pageIndex;
|
||||
|
||||
export const selectPageSize = (state: PolicyListState) => state.pageSize;
|
||||
export const selectPageSize = (state: Immutable<PolicyListState>) => state.pageSize;
|
||||
|
||||
export const selectTotal = (state: PolicyListState) => state.total;
|
||||
export const selectTotal = (state: Immutable<PolicyListState>) => state.total;
|
||||
|
||||
export const selectIsLoading = (state: PolicyListState) => state.isLoading;
|
||||
export const selectIsLoading = (state: Immutable<PolicyListState>) => state.isLoading;
|
||||
|
||||
export const selectApiError = (state: PolicyListState) => state.apiError;
|
||||
export const selectApiError = (state: Immutable<PolicyListState>) => state.apiError;
|
||||
|
||||
export const isOnPolicyListPage = (state: PolicyListState) => {
|
||||
export const isOnPolicyListPage = (state: Immutable<PolicyListState>) => {
|
||||
return state.location?.pathname === '/policy';
|
||||
};
|
||||
|
||||
const routeLocation = (state: PolicyListState) => state.location;
|
||||
const routeLocation = (state: Immutable<PolicyListState>) => 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<PolicyListState>
|
||||
) => PolicyListUrlSearchParams = createSelector(routeLocation, location => {
|
||||
const searchParams = {
|
||||
page_index: 0,
|
||||
|
|
|
@ -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`;
|
||||
|
|
|
@ -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<GlobalState, AppAction> = combineReducers({
|
||||
export const appReducer: ImmutableReducer<GlobalState, AppAction> = immutableCombineReducers({
|
||||
hostList: hostListReducer,
|
||||
alertList: alertListReducer,
|
||||
policyList: policyListReducer,
|
||||
|
|
|
@ -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<PolicyConfig['windows'], 'events' | 'malware'>;
|
||||
|
||||
/**
|
||||
* Mac-specific policy configuration that is supported via the UI
|
||||
*/
|
||||
type MacPolicyConfig = Pick<PolicyConfig['mac'], 'malware' | 'events'>;
|
||||
|
||||
/**
|
||||
* Linux-specific policy configuration that is supported via the UI
|
||||
*/
|
||||
type LinuxPolicyConfig = Pick<PolicyConfig['linux'], 'events'>;
|
||||
|
||||
/**
|
||||
* 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<O, Criteria> = {
|
|||
}[keyof O];
|
||||
|
||||
/** Returns an array of the policy OSes that have a malware protection field */
|
||||
|
||||
export type MalwareProtectionOSes = KeysByValueCriteria<UIPolicyConfig, { malware: MalwareFields }>;
|
||||
/** 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, 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>;
|
||||
};
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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<EuiBasicTableColumn<Immutable<HostMetadata>>> = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
field: '',
|
||||
|
@ -174,7 +176,7 @@ export const HostList = () => {
|
|||
<EuiHorizontalRule margin="xs" />
|
||||
<EuiBasicTable
|
||||
data-test-subj="hostListTable"
|
||||
items={listData}
|
||||
items={useMemo(() => [...listData], [listData])}
|
||||
columns={columns}
|
||||
loading={isLoading}
|
||||
pagination={paginationSetup}
|
||||
|
|
|
@ -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 (
|
||||
<EuiFlexGroup direction="row" gutterSize="none" alignItems="center">
|
||||
<EuiFlexGroup direction="column" gutterSize="none">
|
||||
|
@ -59,28 +72,25 @@ export const ConfigForm: React.FC<{
|
|||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem className="policyDetailTitleFlexItem">
|
||||
<EuiText>{supportedOss.join(', ')}</EuiText>
|
||||
<EuiText>{supportedOss}</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexItem grow={false}>{rightCorner}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
}, [rightCorner, supportedOss, type]);
|
||||
|
||||
return (
|
||||
<PolicyDetailCard>
|
||||
<EuiCard
|
||||
data-test-subj={id}
|
||||
description={description}
|
||||
data-test-subj={dataTestSubj}
|
||||
textAlign="left"
|
||||
title={typeTitle()}
|
||||
description=""
|
||||
children={
|
||||
<>
|
||||
<EuiHorizontalRule margin="m" />
|
||||
{children}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
title={typeTitle}
|
||||
>
|
||||
<EuiHorizontalRule margin="m" />
|
||||
{children}
|
||||
</EuiCard>
|
||||
</PolicyDetailCard>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 (
|
||||
<>
|
||||
<EuiTitle size="xxs">
|
||||
|
@ -63,7 +59,7 @@ export const LinuxEvents = React.memo(() => {
|
|||
</h5>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
{checkboxes.map((item, index) => {
|
||||
{items.map((item, index) => {
|
||||
return (
|
||||
<EventsCheckbox
|
||||
name={item.name}
|
||||
|
@ -77,7 +73,7 @@ export const LinuxEvents = React.memo(() => {
|
|||
})}
|
||||
</>
|
||||
);
|
||||
}, [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}
|
||||
</ConfigForm>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -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 (
|
||||
<>
|
||||
<EuiTitle size="xxs">
|
||||
|
@ -63,7 +59,7 @@ export const MacEvents = React.memo(() => {
|
|||
</h5>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
{checkboxes.map((item, index) => {
|
||||
{items.map((item, index) => {
|
||||
return (
|
||||
<EventsCheckbox
|
||||
name={item.name}
|
||||
|
@ -77,7 +73,7 @@ export const MacEvents = React.memo(() => {
|
|||
})}
|
||||
</>
|
||||
);
|
||||
}, [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}
|
||||
</ConfigForm>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -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 (
|
||||
<>
|
||||
<EuiTitle size="xxs">
|
||||
|
@ -94,7 +90,7 @@ export const WindowsEvents = React.memo(() => {
|
|||
</h5>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
{checkboxes.map((item, index) => {
|
||||
{items.map((item, index) => {
|
||||
return (
|
||||
<EventsCheckbox
|
||||
name={item.name}
|
||||
|
@ -108,7 +104,7 @@ export const WindowsEvents = React.memo(() => {
|
|||
})}
|
||||
</>
|
||||
);
|
||||
}, [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}
|
||||
</ConfigForm>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -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 (
|
||||
<>
|
||||
<EuiTitle size="xxxs">
|
||||
|
@ -148,9 +148,9 @@ export const MalwareProtections = React.memo(() => {
|
|||
</ProtectionRadioGroup>
|
||||
</>
|
||||
);
|
||||
};
|
||||
}, [radios]);
|
||||
|
||||
const ProtectionSwitch = () => {
|
||||
const protectionSwitch = useMemo(() => {
|
||||
return (
|
||||
<EuiSwitch
|
||||
label={i18n.translate('xpack.endpoint.policy.details.malwareProtectionsEnabled', {
|
||||
|
@ -163,18 +163,21 @@ export const MalwareProtections = React.memo(() => {
|
|||
onChange={handleSwitchChange}
|
||||
/>
|
||||
);
|
||||
};
|
||||
}, [handleSwitchChange, selected]);
|
||||
|
||||
return (
|
||||
<ConfigForm
|
||||
type={i18n.translate('xpack.endpoint.policy.details.malware', { defaultMessage: 'Malware' })}
|
||||
supportedOss={[
|
||||
i18n.translate('xpack.endpoint.policy.details.windows', { defaultMessage: 'Windows' }),
|
||||
i18n.translate('xpack.endpoint.policy.details.mac', { defaultMessage: 'Mac' }),
|
||||
]}
|
||||
id="malwareProtectionsForm"
|
||||
rightCorner={ProtectionSwitch()}
|
||||
children={RadioButtons()}
|
||||
/>
|
||||
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}
|
||||
</ConfigForm>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -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 <PolicyLink name={value} route={`/policy/${_item.id}`} />;
|
||||
const renderPolicyNameLink = (value: string, item: Immutable<PolicyData>) => {
|
||||
return <PolicyLink name={value} route={`/policy/${item.id}`} />;
|
||||
};
|
||||
|
||||
export const PolicyList = React.memo(() => {
|
||||
|
@ -88,7 +88,7 @@ export const PolicyList = React.memo(() => {
|
|||
[history, location.pathname]
|
||||
);
|
||||
|
||||
const columns: Array<EuiTableFieldDataColumnType<PolicyData>> = useMemo(
|
||||
const columns: Array<EuiTableFieldDataColumnType<Immutable<PolicyData>>> = useMemo(
|
||||
() => [
|
||||
{
|
||||
field: 'name',
|
||||
|
@ -160,7 +160,7 @@ export const PolicyList = React.memo(() => {
|
|||
}
|
||||
>
|
||||
<EuiBasicTable
|
||||
items={policyItems}
|
||||
items={useMemo(() => [...policyItems], [policyItems])}
|
||||
columns={columns}
|
||||
loading={loading}
|
||||
pagination={paginationSetup}
|
||||
|
|
Loading…
Reference in a new issue