[SECURITY_SOLUTION][ENDPOINT] Delete Endpoint Policy List code (#87063) (#87079)

* Remove Endpoint Policy List code from security_solution plugin

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Paul Tavares 2021-01-04 11:37:19 -05:00 committed by GitHub
parent 943113453a
commit 46bbbd164f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 20 additions and 1656 deletions

View file

@ -5,7 +5,6 @@
*/
import { EndpointAction } from '../../management/pages/endpoint_hosts/store/action';
import { PolicyListAction } from '../../management/pages/policy/store/policy_list';
import { PolicyDetailsAction } from '../../management/pages/policy/store/policy_details';
import { TrustedAppsPageAction } from '../../management/pages/trusted_apps/store/action';
@ -18,6 +17,5 @@ import { RoutingAction } from './routing';
export type AppAction =
| EndpointAction
| RoutingAction
| PolicyListAction
| PolicyDetailsAction
| TrustedAppsPageAction;

View file

@ -18,8 +18,6 @@ export const MANAGEMENT_ROUTING_TRUSTED_APPS_PATH = `${MANAGEMENT_ROUTING_ROOT_P
// --[ STORE ]---------------------------------------------------------------------------
/** The SIEM global store namespace where the management state will be mounted */
export const MANAGEMENT_STORE_GLOBAL_NAMESPACE: ManagementStoreGlobalNamespace = 'management';
/** Namespace within the Management state where policy list state is maintained */
export const MANAGEMENT_STORE_POLICY_LIST_NAMESPACE = 'policyList';
/** Namespace within the Management state where policy details state is maintained */
export const MANAGEMENT_STORE_POLICY_DETAILS_NAMESPACE = 'policyDetails';
/** Namespace within the Management state where endpoint-host state is maintained */

View file

@ -26,7 +26,7 @@ import {
} from '../../../../common/store/test_utils';
import { getEndpointListPath } from '../../../common/routing';
jest.mock('../../policy/store/policy_list/services/ingest', () => ({
jest.mock('../../policy/store/services/ingest', () => ({
sendGetAgentPolicyList: () => Promise.resolve({ items: [] }),
sendGetEndpointSecurityPackage: () => Promise.resolve({}),
}));

View file

@ -23,7 +23,7 @@ import { endpointListReducer } from './reducer';
import { endpointMiddlewareFactory } from './middleware';
import { getEndpointListPath } from '../../../common/routing';
jest.mock('../../policy/store/policy_list/services/ingest', () => ({
jest.mock('../../policy/store/services/ingest', () => ({
sendGetAgentConfigList: () => Promise.resolve({ items: [] }),
sendGetAgentPolicyList: () => Promise.resolve({ items: [] }),
sendGetEndpointSecurityPackage: () => Promise.resolve({}),

View file

@ -25,7 +25,7 @@ import {
sendGetEndpointSecurityPackage,
sendGetAgentPolicyList,
sendGetFleetAgentsWithEndpoint,
} from '../../policy/store/policy_list/services/ingest';
} from '../../policy/store/services/ingest';
import { AGENT_POLICY_SAVED_OBJECT_TYPE } from '../../../../../../fleet/common';
import { metadataCurrentIndexPattern } from '../../../../../common/endpoint/constants';
import { IIndexPattern, Query } from '../../../../../../../../src/plugins/data/public';

View file

@ -19,7 +19,7 @@ import {
INGEST_API_EPM_PACKAGES,
INGEST_API_PACKAGE_POLICIES,
INGEST_API_FLEET_AGENTS,
} from '../../policy/store/policy_list/services/ingest';
} from '../../policy/store/services/ingest';
import {
GetAgentPoliciesResponse,
GetAgentPoliciesResponseItem,

View file

@ -25,7 +25,7 @@ import {
} from '../../../../../common/endpoint/types';
import { EndpointDocGenerator } from '../../../../../common/endpoint/generate_data';
import { POLICY_STATUS_TO_HEALTH_COLOR, POLICY_STATUS_TO_TEXT } from './host_constants';
import { mockPolicyResultList } from '../../policy/store/policy_list/test_mock_utils';
import { mockPolicyResultList } from '../../policy/store/test_mock_utils';
// not sure why this can't be imported from '../../../../common/mock/formatted_relative';
// but sure enough it needs to be inline in this one file
@ -39,8 +39,8 @@ jest.mock('@kbn/i18n/react', () => {
};
});
jest.mock('../../../../common/components/link_to');
jest.mock('../../policy/store/policy_list/services/ingest', () => {
const originalModule = jest.requireActual('../../policy/store/policy_list/services/ingest');
jest.mock('../../policy/store/services/ingest', () => {
const originalModule = jest.requireActual('../../policy/store/services/ingest');
return {
...originalModule,
sendGetEndpointSecurityPackage: () => Promise.resolve({}),

View file

@ -18,7 +18,7 @@ import {
sendGetPackagePolicy,
sendGetFleetAgentStatusForPolicy,
sendPutPackagePolicy,
} from '../policy_list/services/ingest';
} from '../services/ingest';
import { NewPolicyData, PolicyData } from '../../../../../../common/endpoint/types';
import { ImmutableMiddlewareFactory } from '../../../../../common/store';

View file

@ -1,70 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { PolicyData } from '../../../../../../common/endpoint/types';
import { ServerApiError } from '../../../../../common/types';
import { GetAgentStatusResponse, GetPackagesResponse } from '../../../../../../../fleet/common';
interface ServerReturnedPolicyListData {
type: 'serverReturnedPolicyListData';
payload: {
policyItems: PolicyData[];
total: number;
pageSize: number;
pageIndex: number;
};
}
interface ServerFailedToReturnPolicyListData {
type: 'serverFailedToReturnPolicyListData';
payload: ServerApiError;
}
interface UserClickedPolicyListDeleteButton {
type: 'userClickedPolicyListDeleteButton';
payload: { policyId: string };
}
interface UserOpenedPolicyListDeleteModal {
type: 'userOpenedPolicyListDeleteModal';
payload: { agentPolicyId: string };
}
interface ServerDeletedPolicyFailure {
type: 'serverDeletedPolicyFailure';
payload: ServerApiError;
}
interface ServerDeletedPolicy {
type: 'serverDeletedPolicy';
payload: { id: string; success: boolean };
}
interface ServerReturnedPolicyAgentsSummaryForDeleteFailure {
type: 'serverReturnedPolicyAgentsSummaryForDeleteFailure';
payload: ServerApiError;
}
interface ServerReturnedPolicyAgentsSummaryForDelete {
type: 'serverReturnedPolicyAgentsSummaryForDelete';
payload: { agentStatusSummary: GetAgentStatusResponse['results'] };
}
interface ServerReturnedEndpointPackageInfo {
type: 'serverReturnedEndpointPackageInfo';
payload: GetPackagesResponse['response'][0];
}
export type PolicyListAction =
| ServerReturnedPolicyListData
| ServerFailedToReturnPolicyListData
| UserClickedPolicyListDeleteButton
| ServerDeletedPolicyFailure
| ServerDeletedPolicy
| UserOpenedPolicyListDeleteModal
| ServerReturnedPolicyAgentsSummaryForDeleteFailure
| ServerReturnedPolicyAgentsSummaryForDelete
| ServerReturnedEndpointPackageInfo;

View file

@ -1,277 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { PolicyListState } from '../../types';
import { Store, applyMiddleware, createStore } from 'redux';
import { coreMock } from '../../../../../../../../../src/core/public/mocks';
import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../../../../../../../fleet/common';
import { policyListReducer } from './reducer';
import { policyListMiddlewareFactory } from './middleware';
import {
isOnPolicyListPage,
selectIsLoading,
urlSearchParams,
selectIsDeleting,
endpointPackageVersion,
} from './selectors';
import { DepsStartMock, depsStartMock } from '../../../../../common/mock/endpoint';
import { setPolicyListApiMockImplementation } from './test_mock_utils';
import { INGEST_API_PACKAGE_POLICIES } from './services/ingest';
import {
createSpyMiddleware,
MiddlewareActionSpyHelper,
} from '../../../../../common/store/test_utils';
import { getPoliciesPath } from '../../../../common/routing';
describe('policy list store concerns', () => {
const policyListPathUrl = getPoliciesPath();
let fakeCoreStart: ReturnType<typeof coreMock.createStart>;
let depsStart: DepsStartMock;
let store: Store;
let waitForAction: MiddlewareActionSpyHelper['waitForAction'];
beforeEach(() => {
fakeCoreStart = coreMock.createStart({ basePath: '/mock' });
depsStart = depsStartMock();
setPolicyListApiMockImplementation(fakeCoreStart.http);
let actionSpyMiddleware;
({ actionSpyMiddleware, waitForAction } = createSpyMiddleware<PolicyListState>());
store = createStore(
policyListReducer,
undefined,
applyMiddleware(policyListMiddlewareFactory(fakeCoreStart, depsStart), actionSpyMiddleware)
);
});
it('it does nothing on `userChangedUrl` if pathname is NOT `/policy`', async () => {
const state = store.getState();
expect(isOnPolicyListPage(state)).toBe(false);
store.dispatch({
type: 'userChangedUrl',
payload: {
pathname: '/foo',
search: '',
hash: '',
},
});
expect(store.getState()).toEqual(state);
});
it('it reports `isOnPolicyListPage` correctly when router pathname is `/policy`', async () => {
store.dispatch({
type: 'userChangedUrl',
payload: {
pathname: policyListPathUrl,
search: '',
hash: '',
},
});
expect(isOnPolicyListPage(store.getState())).toBe(true);
});
it('it sets `isLoading` when `userChangedUrl`', async () => {
expect(selectIsLoading(store.getState())).toBe(false);
store.dispatch({
type: 'userChangedUrl',
payload: {
pathname: policyListPathUrl,
search: '',
hash: '',
},
});
expect(selectIsLoading(store.getState())).toBe(true);
await waitForAction('serverReturnedPolicyListData');
expect(selectIsLoading(store.getState())).toBe(false);
});
it('it sets `isDeleting` when `userClickedPolicyListDeleteButton`', async () => {
expect(selectIsDeleting(store.getState())).toBe(false);
store.dispatch({
type: 'userClickedPolicyListDeleteButton',
payload: {
policyId: '123',
},
});
expect(selectIsDeleting(store.getState())).toBe(true);
await waitForAction('serverDeletedPolicy');
expect(selectIsDeleting(store.getState())).toBe(false);
});
it('it sets refreshes policy data when `serverDeletedPolicy`', async () => {
expect(selectIsLoading(store.getState())).toBe(false);
store.dispatch({
type: 'serverDeletedPolicy',
payload: {
policyId: '',
success: true,
},
});
expect(selectIsLoading(store.getState())).toBe(true);
await waitForAction('serverReturnedPolicyListData');
expect(selectIsLoading(store.getState())).toBe(false);
});
it('it resets state on `userChangedUrl` and pathname is NOT `/policy`', async () => {
store.dispatch({
type: 'userChangedUrl',
payload: {
pathname: policyListPathUrl,
search: '',
hash: '',
},
});
await waitForAction('serverReturnedPolicyListData');
store.dispatch({
type: 'userChangedUrl',
payload: {
pathname: '/foo',
search: '',
hash: '',
},
});
expect(store.getState()).toEqual({
apiError: undefined,
location: undefined,
policyItems: [],
isLoading: false,
isDeleting: false,
deleteStatus: undefined,
endpointPackageInfo: undefined,
pageIndex: 0,
pageSize: 10,
total: 0,
agentStatusSummary: {
error: 0,
events: 0,
offline: 0,
online: 0,
total: 0,
other: 0,
},
});
});
it('uses default pagination params when not included in url', async () => {
store.dispatch({
type: 'userChangedUrl',
payload: {
pathname: policyListPathUrl,
search: '',
hash: '',
},
});
await waitForAction('serverReturnedPolicyListData');
expect(fakeCoreStart.http.get).toHaveBeenCalledWith(INGEST_API_PACKAGE_POLICIES, {
query: {
kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name: endpoint`,
page: 1,
perPage: 10,
},
});
});
describe('when url contains search params', () => {
const dispatchUserChangedUrl = (searchParams: string = '') =>
store.dispatch({
type: 'userChangedUrl',
payload: {
pathname: policyListPathUrl,
search: searchParams,
hash: '',
},
});
it('uses pagination params from url', async () => {
dispatchUserChangedUrl('?page_size=50&page_index=0');
await waitForAction('serverReturnedPolicyListData');
expect(fakeCoreStart.http.get).toHaveBeenCalledWith(INGEST_API_PACKAGE_POLICIES, {
query: {
kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name: endpoint`,
page: 1,
perPage: 50,
},
});
});
it('uses defaults for params not in url', async () => {
dispatchUserChangedUrl('?page_index=99');
expect(urlSearchParams(store.getState())).toEqual({
page_index: 99,
page_size: 10,
});
dispatchUserChangedUrl('?page_size=50');
expect(urlSearchParams(store.getState())).toEqual({
page_index: 0,
page_size: 50,
});
});
it('accepts only positive numbers for page_index and page_size', async () => {
dispatchUserChangedUrl('?page_size=-50&page_index=-99');
await waitForAction('serverReturnedPolicyListData');
expect(fakeCoreStart.http.get).toHaveBeenCalledWith(INGEST_API_PACKAGE_POLICIES, {
query: {
kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name: endpoint`,
page: 1,
perPage: 10,
},
});
});
it('it ignores non-numeric values for page_index and page_size', async () => {
dispatchUserChangedUrl('?page_size=fifty&page_index=ten');
await waitForAction('serverReturnedPolicyListData');
expect(fakeCoreStart.http.get).toHaveBeenCalledWith(INGEST_API_PACKAGE_POLICIES, {
query: {
kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name: endpoint`,
page: 1,
perPage: 10,
},
});
});
it('accepts only known values for `page_size`', async () => {
dispatchUserChangedUrl('?page_size=300&page_index=10');
await waitForAction('serverReturnedPolicyListData');
expect(fakeCoreStart.http.get).toHaveBeenCalledWith(INGEST_API_PACKAGE_POLICIES, {
query: {
kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name: endpoint`,
page: 11,
perPage: 10,
},
});
});
it(`ignores unknown url search params`, async () => {
dispatchUserChangedUrl('?page_size=20&page_index=10&foo=bar');
expect(urlSearchParams(store.getState())).toEqual({
page_index: 10,
page_size: 20,
});
});
it(`uses last param value if param is defined multiple times`, async () => {
dispatchUserChangedUrl('?page_size=20&page_size=50&page_index=20&page_index=40');
expect(urlSearchParams(store.getState())).toEqual({
page_index: 40,
page_size: 50,
});
});
it('should load package information only if not already in state', async () => {
dispatchUserChangedUrl('?page_size=10&page_index=10');
await waitForAction('serverReturnedEndpointPackageInfo');
expect(endpointPackageVersion(store.getState())).toEqual('0.5.0');
fakeCoreStart.http.get.mockClear();
dispatchUserChangedUrl('?page_size=10&page_index=11');
expect(fakeCoreStart.http.get).toHaveBeenCalledWith(INGEST_API_PACKAGE_POLICIES, {
query: {
kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name: endpoint`,
page: 12,
perPage: 10,
},
});
expect(endpointPackageVersion(store.getState())).toEqual('0.5.0');
});
});
});

View file

@ -1,21 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { PolicyListState } from '../../types';
import { ImmutableReducer } from '../../../../../common/store';
import { AppAction } from '../../../../../common/store/actions';
import { Immutable } from '../../../../../../common/endpoint/types';
export { policyListReducer } from './reducer';
export { PolicyListAction } from './action';
export { policyListMiddlewareFactory } from './middleware';
export interface EndpointPolicyListStatePluginState {
policyList: Immutable<PolicyListState>;
}
export interface EndpointPolicyListStatePluginReducer {
policyList: ImmutableReducer<PolicyListState, AppAction>;
}

View file

@ -1,124 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { GetPolicyListResponse, PolicyListState } from '../../types';
import {
sendGetEndpointSpecificPackagePolicies,
sendDeletePackagePolicy,
sendGetFleetAgentStatusForPolicy,
sendGetEndpointSecurityPackage,
} from './services/ingest';
import { endpointPackageInfo, isOnPolicyListPage, urlSearchParams } from './selectors';
import { ImmutableMiddlewareFactory } from '../../../../../common/store';
import { initialPolicyListState } from './reducer';
import {
DeletePackagePoliciesResponse,
DeletePackagePoliciesRequest,
GetAgentStatusResponse,
} from '../../../../../../../fleet/common';
export const policyListMiddlewareFactory: ImmutableMiddlewareFactory<PolicyListState> = (
coreStart
) => {
const http = coreStart.http;
return ({ getState, dispatch }) => (next) => async (action) => {
next(action);
const state = getState();
if (
(action.type === 'userChangedUrl' && isOnPolicyListPage(state)) ||
action.type === 'serverDeletedPolicy'
) {
if (!endpointPackageInfo(state)) {
// We only need the package information to retrieve the version number,
// and even if we don't have the version, the UI is still ok because we
// handle that condition. Because of this, we retrieve the package information
// in a non-blocking way here and also ignore any API failures (only log it
// to the console)
sendGetEndpointSecurityPackage(http)
.then((packageInfo) => {
dispatch({
type: 'serverReturnedEndpointPackageInfo',
payload: packageInfo,
});
})
.catch((error) => {
// eslint-disable-next-line no-console
console.error(error);
});
}
const { page_index: pageIndex, page_size: pageSize } = urlSearchParams(state);
let response: GetPolicyListResponse;
try {
response = await sendGetEndpointSpecificPackagePolicies(http, {
query: {
perPage: pageSize,
page: pageIndex + 1,
},
});
} catch (err) {
dispatch({
type: 'serverFailedToReturnPolicyListData',
payload: err.body ?? err,
});
return;
}
dispatch({
type: 'serverReturnedPolicyListData',
payload: {
policyItems: response ? response.items : initialPolicyListState().policyItems,
pageIndex,
pageSize,
total: response ? response.total : initialPolicyListState().total,
},
});
} else if (action.type === 'userClickedPolicyListDeleteButton') {
const { policyId } = action.payload;
const packagePolicyIds: DeletePackagePoliciesRequest['body']['packagePolicyIds'] = [policyId];
let apiResponse: DeletePackagePoliciesResponse;
try {
apiResponse = await sendDeletePackagePolicy(http, { body: { packagePolicyIds } });
} catch (err) {
dispatch({
type: 'serverDeletedPolicyFailure',
payload: err.body ?? err,
});
return;
}
dispatch({
type: 'serverDeletedPolicy',
payload: {
id: apiResponse ? apiResponse[0].id : '',
success: true,
},
});
} else if (action.type === 'userOpenedPolicyListDeleteModal') {
const { agentPolicyId } = action.payload;
let apiResponse: GetAgentStatusResponse;
try {
apiResponse = await sendGetFleetAgentStatusForPolicy(http, agentPolicyId);
} catch (err) {
dispatch({
type: 'serverReturnedPolicyAgentsSummaryForDeleteFailure',
payload: err.body ?? err,
});
return;
}
dispatch({
type: 'serverReturnedPolicyAgentsSummaryForDelete',
payload: {
agentStatusSummary: apiResponse.results,
},
});
}
};
};

View file

@ -1,132 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { isOnPolicyListPage } from './selectors';
import { ImmutableReducer } from '../../../../../common/store';
import { AppAction } from '../../../../../common/store/actions';
import { Immutable } from '../../../../../../common/endpoint/types';
import { PolicyListState } from '../../types';
/**
* Return the initial state.
* In case `state` was mutated, we return a fresh initial state object.
*/
export const initialPolicyListState: () => Immutable<PolicyListState> = () => ({
policyItems: [],
endpointPackageInfo: undefined,
isLoading: false,
isDeleting: false,
deleteStatus: undefined,
apiError: undefined,
pageIndex: 0,
pageSize: 10,
total: 0,
location: undefined,
agentStatusSummary: {
error: 0,
events: 0,
offline: 0,
online: 0,
total: 0,
other: 0,
},
});
export const policyListReducer: ImmutableReducer<PolicyListState, AppAction> = (
state = initialPolicyListState(),
action
) => {
if (action.type === 'serverReturnedPolicyListData') {
return {
...state,
...action.payload,
isLoading: false,
isDeleting: false,
};
}
if (action.type === 'serverFailedToReturnPolicyListData') {
return {
...state,
apiError: action.payload,
isLoading: false,
isDeleting: false,
};
}
if (action.type === 'serverDeletedPolicyFailure') {
return {
...state,
...action.payload,
isLoading: false,
isDeleting: false,
};
}
if (action.type === 'serverDeletedPolicy') {
return {
...state,
deleteStatus: action.payload.success,
isLoading: true,
isDeleting: false,
};
}
if (action.type === 'userClickedPolicyListDeleteButton') {
return {
...state,
isLoading: false,
isDeleting: true,
};
}
if (action.type === 'serverReturnedPolicyAgentsSummaryForDelete') {
return {
...state,
...action.payload,
};
}
if (action.type === 'serverReturnedPolicyAgentsSummaryForDeleteFailure') {
return {
...state,
...action.payload,
};
}
if (action.type === 'serverReturnedEndpointPackageInfo') {
return {
...state,
endpointPackageInfo: action.payload,
};
}
if (action.type === 'userChangedUrl') {
const newState: Immutable<PolicyListState> = {
...state,
location: action.payload,
};
const isCurrentlyOnListPage = isOnPolicyListPage(newState);
const wasPreviouslyOnListPage = isOnPolicyListPage(state);
// If on the current page, then return new state with location information
// Also adjust some state if user is just entering the policy list view
if (isCurrentlyOnListPage) {
if (!wasPreviouslyOnListPage) {
return {
...newState,
apiError: undefined,
isLoading: true,
isDeleting: false,
};
}
return newState;
}
return initialPolicyListState();
}
return state;
};

View file

@ -1,100 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { createSelector } from 'reselect';
import { parse } from 'query-string';
import { matchPath } from 'react-router-dom';
import { PolicyListState, PolicyListUrlSearchParams } from '../../types';
import { Immutable } from '../../../../../../common/endpoint/types';
import { MANAGEMENT_ROUTING_POLICIES_PATH } from '../../../../common/constants';
const PAGE_SIZES = Object.freeze([10, 20, 50]);
export const selectPolicyItems = (state: Immutable<PolicyListState>) => state.policyItems;
export const selectPageIndex = (state: Immutable<PolicyListState>) => state.pageIndex;
export const selectPageSize = (state: Immutable<PolicyListState>) => state.pageSize;
export const selectTotal = (state: Immutable<PolicyListState>) => state.total;
export const selectIsLoading = (state: Immutable<PolicyListState>) => state.isLoading;
export const selectApiError = (state: Immutable<PolicyListState>) => state.apiError;
export const selectIsDeleting = (state: Immutable<PolicyListState>) => state.isDeleting;
export const selectDeleteStatus = (state: Immutable<PolicyListState>) => state.deleteStatus;
export const selectAgentStatusSummary = (state: Immutable<PolicyListState>) =>
state.agentStatusSummary;
export const isOnPolicyListPage = (state: Immutable<PolicyListState>) => {
return (
matchPath(state.location?.pathname ?? '', {
path: MANAGEMENT_ROUTING_POLICIES_PATH,
exact: true,
}) !== null
);
};
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: Immutable<PolicyListState>
) => PolicyListUrlSearchParams = createSelector(routeLocation, (location) => {
const searchParams = {
page_index: 0,
page_size: 10,
};
if (!location) {
return searchParams;
}
const query = parse(location.search);
// Search params can appear multiple times in the URL, in which case the value for them,
// once parsed, would be an array. In these case, we take the last value defined
searchParams.page_index = Number(
(Array.isArray(query.page_index)
? query.page_index[query.page_index.length - 1]
: query.page_index) ?? 0
);
searchParams.page_size = Number(
(Array.isArray(query.page_size)
? query.page_size[query.page_size.length - 1]
: query.page_size) ?? 10
);
// If pageIndex is not a valid positive integer, set it to 0
if (!Number.isFinite(searchParams.page_index) || searchParams.page_index < 0) {
searchParams.page_index = 0;
}
// if pageSize is not one of the expected page sizes, reset it to 10
if (!PAGE_SIZES.includes(searchParams.page_size)) {
searchParams.page_size = 10;
}
return searchParams;
});
/**
* Returns package information for Endpoint
* @param state
*/
export const endpointPackageInfo = (state: Immutable<PolicyListState>) => state.endpointPackageInfo;
/**
* Returns the version number for the endpoint package.
*/
export const endpointPackageVersion = createSelector(
endpointPackageInfo,
(info) => info?.version ?? undefined
);

View file

@ -10,8 +10,8 @@ import {
sendGetEndpointSecurityPackage,
sendGetEndpointSpecificPackagePolicies,
} from './ingest';
import { httpServiceMock } from '../../../../../../../../../../src/core/public/mocks';
import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../../../../../../../../fleet/common';
import { httpServiceMock } from '../../../../../../../../../src/core/public/mocks';
import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../../../../../../../fleet/common';
import { policyListApiPathHandlers } from '../test_mock_utils';
describe('ingest service', () => {

View file

@ -15,9 +15,9 @@ import {
GetPackagesResponse,
GetAgentPoliciesRequest,
GetAgentPoliciesResponse,
} from '../../../../../../../../fleet/common';
import { GetPolicyListResponse, GetPolicyResponse, UpdatePolicyResponse } from '../../../types';
import { NewPolicyData } from '../../../../../../../common/endpoint/types';
} from '../../../../../../../fleet/common';
import { GetPolicyListResponse, GetPolicyResponse, UpdatePolicyResponse } from '../../types';
import { NewPolicyData } from '../../../../../../common/endpoint/types';
const INGEST_API_ROOT = `/api/fleet`;
export const INGEST_API_PACKAGE_POLICIES = `${INGEST_API_ROOT}/package_policies`;

View file

@ -4,36 +4,13 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { HttpStart } from 'kibana/public';
import { INGEST_API_EPM_PACKAGES, INGEST_API_PACKAGE_POLICIES } from './services/ingest';
import { EndpointDocGenerator } from '../../../../../../common/endpoint/generate_data';
import { GetPolicyListResponse } from '../../types';
import { GetPackagesResponse } from '../../../../../../../fleet/common';
import { EndpointDocGenerator } from '../../../../../common/endpoint/generate_data';
import { GetPolicyListResponse } from '../types';
import { GetPackagesResponse } from '../../../../../../fleet/common';
const generator = new EndpointDocGenerator('policy-list');
/**
* It sets the mock implementation on the necessary http methods to support the policy list view
* @param mockedHttpService
* @param totalPolicies
*/
export const setPolicyListApiMockImplementation = (
mockedHttpService: jest.Mocked<HttpStart>,
totalPolicies: number = 1
): void => {
const policyApiHandlers = policyListApiPathHandlers(totalPolicies);
mockedHttpService.get.mockImplementation(async (...args) => {
const [path] = args;
if (typeof path === 'string') {
if (policyApiHandlers[path]) {
return policyApiHandlers[path]();
}
}
return Promise.reject(new Error(`MOCK: unknown policy list api: ${path}`));
});
};
/**
* Returns the response body for a call to get the list of Policies
* @param options

View file

@ -4,6 +4,5 @@
* you may not use this file except in compliance with the Elastic License.
*/
export * from './policy_list';
export * from './policy_details';
export * from './policy_advanced';

View file

@ -12,7 +12,7 @@ import '../../../../common/mock/match_media.ts';
import { EndpointDocGenerator } from '../../../../../common/endpoint/generate_data';
import { AppContextTestRender, createAppRootMockRenderer } from '../../../../common/mock/endpoint';
import { getPolicyDetailPath, getEndpointListPath } from '../../../common/routing';
import { policyListApiPathHandlers } from '../store/policy_list/test_mock_utils';
import { policyListApiPathHandlers } from '../store/test_mock_utils';
import { licenseService } from '../../../../common/hooks/use_license';
jest.mock('../../../../common/components/link_to');

View file

@ -5,28 +5,13 @@
*/
import { useSelector } from 'react-redux';
import { PolicyListState, PolicyDetailsState } from '../types';
import { PolicyDetailsState } from '../types';
import { State } from '../../../../common/store';
import {
MANAGEMENT_STORE_GLOBAL_NAMESPACE,
MANAGEMENT_STORE_POLICY_DETAILS_NAMESPACE,
MANAGEMENT_STORE_POLICY_LIST_NAMESPACE,
} from '../../../common/constants';
/**
* Narrows global state down to the PolicyListState before calling the provided Policy List Selector
* @param selector
*/
export function usePolicyListSelector<TSelected>(selector: (state: PolicyListState) => TSelected) {
return useSelector((state: State) => {
return selector(
state[MANAGEMENT_STORE_GLOBAL_NAMESPACE][
MANAGEMENT_STORE_POLICY_LIST_NAMESPACE
] as PolicyListState
);
});
}
/**
* Narrows global state down to the PolicyDetailsState before calling the provided Policy Details Selector
* @param selector

View file

@ -1,77 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
import { PolicyList } from './index';
import '../../../../common/mock/match_media.ts';
import { AppContextTestRender, createAppRootMockRenderer } from '../../../../common/mock/endpoint';
import { setPolicyListApiMockImplementation } from '../store/policy_list/test_mock_utils';
jest.mock('../../../../common/components/link_to');
// Skipping these test now that the Policy List has been hidden
describe.skip('when on the policies page', () => {
let render: () => ReturnType<AppContextTestRender['render']>;
let history: AppContextTestRender['history'];
let coreStart: AppContextTestRender['coreStart'];
let middlewareSpy: AppContextTestRender['middlewareSpy'];
beforeEach(() => {
const mockedContext = createAppRootMockRenderer();
({ history, coreStart, middlewareSpy } = mockedContext);
render = () => mockedContext.render(<PolicyList />);
});
it('should NOT display timeline', async () => {
const renderResult = render();
const timelineFlyout = await renderResult.queryByTestId('flyoutOverlay');
expect(timelineFlyout).toBeNull();
});
it('should show the empty state', async () => {
const renderResult = render();
const table = await renderResult.findByTestId('emptyPolicyTable');
expect(table).not.toBeNull();
});
it('should display the instructions', async () => {
const renderResult = render();
const onboardingSteps = await renderResult.findByTestId('policyOnboardingInstructions');
expect(onboardingSteps).not.toBeNull();
});
describe('when list data loads', () => {
let firstPolicyID: string;
const renderList = async () => {
const renderResult = render();
history.push('/policy');
await Promise.all([
middlewareSpy
.waitForAction('serverReturnedPolicyListData')
.then((action) => (firstPolicyID = action.payload.policyItems[0].id)),
// middlewareSpy.waitForAction('serverReturnedAgentPolicyListData'),
]);
return renderResult;
};
beforeEach(async () => {
setPolicyListApiMockImplementation(coreStart.http, 3);
});
it('should display rows in the table', async () => {
const renderResult = await renderList();
const rows = await renderResult.findAllByRole('row');
expect(rows).toHaveLength(4);
});
it('should display policy name value as a link', async () => {
const renderResult = await renderList();
const policyNameLink = (await renderResult.findAllByTestId('policyNameLink'))[0];
expect(policyNameLink).not.toBeNull();
expect(policyNameLink.getAttribute('href')).toContain(`policy/${firstPolicyID}`);
});
});
});

View file

@ -1,524 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React, { useCallback, useEffect, useMemo, CSSProperties, useState } from 'react';
import {
EuiBasicTable,
EuiText,
EuiSpacer,
EuiFlexGroup,
EuiFlexItem,
EuiTableFieldDataColumnType,
EuiLink,
EuiPopover,
EuiContextMenuPanelProps,
EuiContextMenuItem,
EuiButtonIcon,
EuiContextMenuPanel,
EuiOverlayMask,
EuiConfirmModal,
EuiCallOut,
EuiButton,
EuiHorizontalRule,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { useDispatch } from 'react-redux';
import { useLocation, useHistory } from 'react-router-dom';
import { createStructuredSelector } from 'reselect';
import styled from 'styled-components';
import { ApplicationStart } from 'src/core/public';
import { CreateStructuredSelector } from '../../../../common/store';
import * as selectors from '../store/policy_list/selectors';
import { usePolicyListSelector } from './policy_hooks';
import { PolicyListAction } from '../store/policy_list';
import { useToasts } from '../../../../common/lib/kibana';
import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public';
import { Immutable, PolicyData } from '../../../../../common/endpoint/types';
import { useNavigateByRouterEventHandler } from '../../../../common/hooks/endpoint/use_navigate_by_router_event_handler';
import { LinkToApp } from '../../../../common/components/endpoint/link_to_app';
import { PolicyEmptyState } from '../../../components/management_empty_state';
import { FormattedDateAndTime } from '../../../../common/components/endpoint/formatted_date_time';
import { SecurityPageName } from '../../../../app/types';
import { useFormatUrl } from '../../../../common/components/link_to';
import { getPolicyDetailPath, getPoliciesPath } from '../../../common/routing';
import { useNavigateToAppEventHandler } from '../../../../common/hooks/endpoint/use_navigate_to_app_event_handler';
import { CreatePackagePolicyRouteState } from '../../../../../../fleet/public';
import { MANAGEMENT_APP_ID } from '../../../common/constants';
import { AdministrationListPage } from '../../../components/administration_list_page';
interface TableChangeCallbackArguments {
page: { index: number; size: number };
}
interface PackageData {
name: string;
title: string;
version: string;
}
const NO_WRAP_TRUNCATE_STYLE: CSSProperties = Object.freeze({
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
});
const DangerEuiContextMenuItem = styled(EuiContextMenuItem)`
color: ${(props) => props.theme.eui.euiColorDangerText};
`;
// eslint-disable-next-line react/display-name
export const TableRowActions = React.memo<{ items: EuiContextMenuPanelProps['items'] }>(
({ items }) => {
const [isOpen, setIsOpen] = useState(false);
const handleCloseMenu = useCallback(() => setIsOpen(false), [setIsOpen]);
const handleToggleMenu = useCallback(() => setIsOpen(!isOpen), [isOpen]);
return (
<EuiPopover
anchorPosition="downRight"
panelPaddingSize="none"
data-test-subj="policyActions"
button={
<EuiButtonIcon
data-test-subj="policyActionsButton"
iconType="boxesHorizontal"
onClick={handleToggleMenu}
aria-label={i18n.translate('xpack.securitySolution.endpoint.policyList.actionMenu', {
defaultMessage: 'Open',
})}
/>
}
isOpen={isOpen}
closePopover={handleCloseMenu}
repositionOnScroll
>
<EuiContextMenuPanel items={items} data-test-subj="policyActionsMenu" />
</EuiPopover>
);
}
);
const PolicyLink: React.FC<{ name: string; route: string; href: string }> = ({
name,
route,
href,
}) => {
const clickHandler = useNavigateByRouterEventHandler(route);
return (
// eslint-disable-next-line @elastic/eui/href-or-on-click
<EuiLink
href={href}
onClick={clickHandler}
data-test-subj="policyNameLink"
style={NO_WRAP_TRUNCATE_STYLE}
>
{name}
</EuiLink>
);
};
const selector = (createStructuredSelector as CreateStructuredSelector)(selectors);
export const PolicyList = React.memo(() => {
const { services } = useKibana<{ application: ApplicationStart }>();
const toasts = useToasts();
const history = useHistory();
const location = useLocation();
const { formatUrl, search } = useFormatUrl(SecurityPageName.administration);
const [showDelete, setShowDelete] = useState<boolean>(false);
const [policyIdToDelete, setPolicyIdToDelete] = useState<string>('');
const dispatch = useDispatch<(action: PolicyListAction) => void>();
const {
selectPolicyItems: policyItems,
selectPageIndex: pageIndex,
selectPageSize: pageSize,
selectTotal: totalItemCount,
selectIsLoading: loading,
selectApiError: apiError,
selectIsDeleting: isDeleting,
selectDeleteStatus: deleteStatus,
selectAgentStatusSummary: agentStatusSummary,
endpointPackageVersion,
} = usePolicyListSelector(selector);
const handleCreatePolicyClick = useNavigateToAppEventHandler<CreatePackagePolicyRouteState>(
'fleet',
{
// We redirect to Ingest's Integaration page if we can't get the package version, and
// to the Integration Endpoint Package Add Integration if we have package information.
// Also,
// We pass along soem state information so that the Ingest page can change the behaviour
// of the cancel and submit buttons and redirect the user back to endpoint policy
path: `#/integrations${
endpointPackageVersion ? `/endpoint-${endpointPackageVersion}/add-integration` : ''
}`,
state: {
onCancelNavigateTo: [MANAGEMENT_APP_ID, { path: getPoliciesPath() }],
onCancelUrl: formatUrl(getPoliciesPath()),
onSaveNavigateTo: [MANAGEMENT_APP_ID, { path: getPoliciesPath() }],
},
}
);
useEffect(() => {
if (apiError) {
toasts.addDanger({
title: apiError.error,
text: apiError.message,
});
}
}, [apiError, dispatch, toasts]);
// Handle showing update statuses
useEffect(() => {
if (deleteStatus !== undefined) {
if (deleteStatus === true) {
setPolicyIdToDelete('');
setShowDelete(false);
toasts.addSuccess({
title: i18n.translate('xpack.securitySolution.endpoint.policyList.deleteSuccessToast', {
defaultMessage: 'Success!',
}),
text: i18n.translate(
'xpack.securitySolution.endpoint.policyList.deleteSuccessToastDetails',
{
defaultMessage: 'Policy has been deleted.',
}
),
});
} else {
toasts.addDanger({
title: i18n.translate('xpack.securitySolution.endpoint.policyList.deleteFailedToast', {
defaultMessage: 'Failed!',
}),
text: i18n.translate('xpack.securitySolution.endpoint.policyList.deleteFailedToastBody', {
defaultMessage: 'Failed to delete policy',
}),
});
}
}
}, [toasts, deleteStatus]);
const paginationSetup = useMemo(() => {
return {
pageIndex,
pageSize,
totalItemCount,
pageSizeOptions: [10, 20, 50],
hidePerPageOptions: false,
};
}, [pageIndex, pageSize, totalItemCount]);
const handleTableChange = useCallback(
({ page: { index, size } }: TableChangeCallbackArguments) => {
history.push(`${location.pathname}?page_index=${index}&page_size=${size}`);
},
[history, location.pathname]
);
const handleDeleteOnClick = useCallback(
({ policyId, agentPolicyId }: { policyId: string; agentPolicyId: string }) => {
dispatch({
type: 'userOpenedPolicyListDeleteModal',
payload: {
agentPolicyId,
},
});
setPolicyIdToDelete(policyId);
setShowDelete(true);
},
[dispatch]
);
const handleDeleteConfirmation = useCallback(
({ policyId }: { policyId: string }) => {
dispatch({
type: 'userClickedPolicyListDeleteButton',
payload: {
policyId,
},
});
},
[dispatch]
);
const handleDeleteCancel = useCallback(() => {
setShowDelete(false);
}, []);
const columns: Array<EuiTableFieldDataColumnType<Immutable<PolicyData>>> = useMemo(
() => [
{
field: 'name',
name: i18n.translate('xpack.securitySolution.endpoint.policyList.nameField', {
defaultMessage: 'Policy Name',
}),
// eslint-disable-next-line react/display-name
render: (name: string, item: Immutable<PolicyData>) => {
const routePath = getPolicyDetailPath(item.id, search);
const routeUrl = formatUrl(routePath);
return (
<EuiFlexGroup gutterSize="s" alignItems="baseline" style={{ minWidth: 0 }}>
<EuiFlexItem grow={false} style={NO_WRAP_TRUNCATE_STYLE}>
<PolicyLink name={name} route={routePath} href={routeUrl} />
</EuiFlexItem>
<EuiFlexItem>
<EuiText color="subdued" size="xs" style={{ whiteSpace: 'nowrap' }}>
<FormattedMessage
id="xpack.securitySolution.endpoint.policyList.revision"
defaultMessage="rev. {revNumber}"
values={{ revNumber: item.revision }}
/>
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
);
},
},
{
field: 'created_by',
name: i18n.translate('xpack.securitySolution.endpoint.policyList.createdBy', {
defaultMessage: 'Created By',
}),
truncateText: true,
},
{
field: 'created_at',
name: i18n.translate('xpack.securitySolution.endpoint.policyList.createdAt', {
defaultMessage: 'Created Date',
}),
render(createdAt: string) {
return <FormattedDateAndTime date={new Date(createdAt)} />;
},
},
{
field: 'updated_by',
name: i18n.translate('xpack.securitySolution.endpoint.policyList.updatedBy', {
defaultMessage: 'Last Updated By',
}),
truncateText: true,
},
{
field: 'updated_at',
name: i18n.translate('xpack.securitySolution.endpoint.policyList.updatedAt', {
defaultMessage: 'Last Updated',
}),
render(updatedAt: string) {
return <FormattedDateAndTime date={new Date(updatedAt)} />;
},
},
{
field: 'package',
name: i18n.translate('xpack.securitySolution.endpoint.policyList.versionFieldLabel', {
defaultMessage: 'Version',
}),
render(pkg: Immutable<PackageData>) {
return i18n.translate('xpack.securitySolution.endpoint.policyList.versionField', {
defaultMessage: 'v{version}',
values: {
version: pkg.version,
},
});
},
},
{
field: '',
name: 'Actions',
actions: [
{
// eslint-disable-next-line react/display-name
render: (item: Immutable<PolicyData>) => {
return (
<TableRowActions
items={[
<EuiContextMenuItem icon="link" key="agentPolicyLink">
<LinkToApp
data-test-subj="agentPolicyLink"
appId="fleet"
appPath={`#/policies/${item.policy_id}`}
href={`${services.application.getUrlForApp('fleet')}#/policies/${
item.policy_id
}`}
>
<FormattedMessage
id="xpack.securitySolution.endpoint.policyList.agentPolicyAction"
defaultMessage="View Agent Policy"
/>
</LinkToApp>
</EuiContextMenuItem>,
<DangerEuiContextMenuItem
data-test-subj="policyDeleteButton"
icon="trash"
key="policyDeleteAction"
onClick={() => {
handleDeleteOnClick({ agentPolicyId: item.policy_id, policyId: item.id });
}}
>
<FormattedMessage
id="xpack.securitySolution.endpoint.policyList.policyDeleteAction"
defaultMessage="Delete Policy"
/>
</DangerEuiContextMenuItem>,
]}
/>
);
},
},
],
},
],
[services.application, handleDeleteOnClick, formatUrl, search]
);
const bodyContent = useMemo(() => {
return policyItems && policyItems.length > 0 ? (
<EuiBasicTable
items={[...policyItems]}
columns={columns}
loading={loading}
pagination={paginationSetup}
onChange={handleTableChange}
data-test-subj="policyTable"
hasActions={false}
/>
) : (
<PolicyEmptyState loading={loading} onActionClick={handleCreatePolicyClick} />
);
}, [policyItems, loading, columns, handleCreatePolicyClick, handleTableChange, paginationSetup]);
return (
<>
{showDelete && (
<ConfirmDelete
hostCount={agentStatusSummary ? agentStatusSummary.total : 0}
onCancel={handleDeleteCancel}
isDeleting={isDeleting}
onConfirm={() => {
handleDeleteConfirmation({ policyId: policyIdToDelete });
}}
/>
)}
<AdministrationListPage
data-test-subj="policyListPage"
beta={false}
title={
<FormattedMessage
id="xpack.securitySolution.policyList.pageTitle"
defaultMessage="Policies"
/>
}
subtitle={
<FormattedMessage
id="xpack.securitySolution.policyList.pageSubTitle"
defaultMessage="View and configure protections"
/>
}
actions={
<EuiButton
iconType="plusInCircle"
onClick={handleCreatePolicyClick}
data-test-subj="headerCreateNewPolicyButton"
>
<FormattedMessage
id="xpack.securitySolution.endpoint.policyList.createNewButton"
defaultMessage="Create new policy"
/>
</EuiButton>
}
>
{policyItems && policyItems.length > 0 && (
<>
<EuiText color="subdued" data-test-subj="policyTotalCount" size="xs">
<FormattedMessage
id="xpack.securitySolution.endpoint.policyList.viewTitleTotalCount"
defaultMessage="{totalItemCount, plural, one {# Policy} other {# Policies}}"
values={{ totalItemCount }}
/>
</EuiText>
<EuiHorizontalRule margin="xs" />
</>
)}
{bodyContent}
</AdministrationListPage>
</>
);
});
PolicyList.displayName = 'PolicyList';
const ConfirmDelete = React.memo<{
hostCount: number;
isDeleting: boolean;
onConfirm: () => void;
onCancel: () => void;
}>(({ hostCount, isDeleting, onCancel, onConfirm }) => {
return (
<EuiOverlayMask>
<EuiConfirmModal
data-test-subj="policyListDeleteModal"
title={i18n.translate('xpack.securitySolution.endpoint.policyList.deleteConfirm.title', {
defaultMessage: 'Delete policy and deploy changes',
})}
onCancel={onCancel}
onConfirm={onConfirm}
buttonColor="danger"
confirmButtonText={
isDeleting ? (
<FormattedMessage
id="xpack.securitySolution.endpoint.policyList.deleteConfirm.deletingButton"
defaultMessage="Deleting..."
/>
) : (
<FormattedMessage
id="xpack.securitySolution.endpoint.policyList.deleteConfirm.confirmDeleteButton"
defaultMessage="Delete Policy"
/>
)
}
confirmButtonDisabled={isDeleting}
cancelButtonText={i18n.translate(
'xpack.securitySolution.endpoint.policyList.deleteConfirm.cancelButtonTitle',
{
defaultMessage: 'Cancel',
}
)}
>
{hostCount > 0 && (
<>
<EuiCallOut
data-test-subj="policyListWarningCallout"
color="danger"
title={i18n.translate(
'xpack.securitySolution.endpoint.policyList.deleteConfirm.warningTitle',
{
defaultMessage:
'This action will delete Endpoint Security from {hostCount, plural, one {# host} other {# hosts}}',
values: { hostCount },
}
)}
>
<FormattedMessage
id="xpack.securitySolution.endpoint.policyList.deleteConfirm.warningMessage"
defaultMessage="Deleting this Policy will remove Endpoint Security from these hosts"
/>
</EuiCallOut>
<EuiSpacer size="xl" />
</>
)}
<p>
<FormattedMessage
id="xpack.securitySolution.endpoint.policyList.deleteConfirm.message"
defaultMessage="This action cannot be undone. Are you sure you wish to continue?"
/>
</p>
</EuiConfirmModal>
</EuiOverlayMask>
);
});
ConfirmDelete.displayName = 'ConfirmDelete';

View file

@ -13,10 +13,8 @@ import {
MANAGEMENT_STORE_ENDPOINTS_NAMESPACE,
MANAGEMENT_STORE_GLOBAL_NAMESPACE,
MANAGEMENT_STORE_POLICY_DETAILS_NAMESPACE,
MANAGEMENT_STORE_POLICY_LIST_NAMESPACE,
MANAGEMENT_STORE_TRUSTED_APPS_NAMESPACE,
} from '../common/constants';
import { policyListMiddlewareFactory } from '../pages/policy/store/policy_list';
import { policyDetailsMiddlewareFactory } from '../pages/policy/store/policy_details';
import { endpointMiddlewareFactory } from '../pages/endpoint_hosts/store/middleware';
import { trustedAppsPageMiddlewareFactory } from '../pages/trusted_apps/store/middleware';
@ -31,10 +29,6 @@ export const managementMiddlewareFactory: SecuritySubPluginMiddlewareFactory = (
depsStart
) => {
return [
substateMiddlewareFactory(
createSubStateSelector(MANAGEMENT_STORE_POLICY_LIST_NAMESPACE),
policyListMiddlewareFactory(coreStart, depsStart)
),
substateMiddlewareFactory(
createSubStateSelector(MANAGEMENT_STORE_POLICY_DETAILS_NAMESPACE),
policyDetailsMiddlewareFactory(coreStart, depsStart)

View file

@ -9,14 +9,9 @@ import {
policyDetailsReducer,
initialPolicyDetailsState,
} from '../pages/policy/store/policy_details/reducer';
import {
policyListReducer,
initialPolicyListState,
} from '../pages/policy/store/policy_list/reducer';
import {
MANAGEMENT_STORE_ENDPOINTS_NAMESPACE,
MANAGEMENT_STORE_POLICY_DETAILS_NAMESPACE,
MANAGEMENT_STORE_POLICY_LIST_NAMESPACE,
MANAGEMENT_STORE_TRUSTED_APPS_NAMESPACE,
} from '../common/constants';
import { ImmutableCombineReducers } from '../../common/store';
@ -35,7 +30,6 @@ const immutableCombineReducers: ImmutableCombineReducers = combineReducers;
* Returns the initial state of the store for the SIEM Management section
*/
export const mockManagementState: Immutable<ManagementState> = {
[MANAGEMENT_STORE_POLICY_LIST_NAMESPACE]: initialPolicyListState(),
[MANAGEMENT_STORE_POLICY_DETAILS_NAMESPACE]: initialPolicyDetailsState(),
[MANAGEMENT_STORE_ENDPOINTS_NAMESPACE]: initialEndpointListState,
[MANAGEMENT_STORE_TRUSTED_APPS_NAMESPACE]: initialTrustedAppsPageState(),
@ -45,7 +39,6 @@ export const mockManagementState: Immutable<ManagementState> = {
* Redux store reducer for the SIEM Management section
*/
export const managementReducer = immutableCombineReducers({
[MANAGEMENT_STORE_POLICY_LIST_NAMESPACE]: policyListReducer,
[MANAGEMENT_STORE_POLICY_DETAILS_NAMESPACE]: policyDetailsReducer,
[MANAGEMENT_STORE_ENDPOINTS_NAMESPACE]: endpointListReducer,
[MANAGEMENT_STORE_TRUSTED_APPS_NAMESPACE]: trustedAppsPageReducer,

View file

@ -6,9 +6,9 @@
import { CombinedState } from 'redux';
import { SecurityPageName } from '../app/types';
import { PolicyListState, PolicyDetailsState } from './pages/policy/types';
import { PolicyDetailsState } from './pages/policy/types';
import { EndpointState } from './pages/endpoint_hosts/types';
import { TrustedAppsListPageState } from './pages/trusted_apps/state/trusted_apps_list_page_state';
import { TrustedAppsListPageState } from './pages/trusted_apps/state';
/**
* The type for the management store global namespace. Used mostly internally to reference
@ -17,7 +17,6 @@ import { TrustedAppsListPageState } from './pages/trusted_apps/state/trusted_app
export type ManagementStoreGlobalNamespace = 'management';
export type ManagementState = CombinedState<{
policyList: PolicyListState;
policyDetails: PolicyDetailsState;
endpoints: EndpointState;
trustedApps: TrustedAppsListPageState;

View file

@ -17120,36 +17120,12 @@
"xpack.securitySolution.endpoint.policyDetailsConfig.windows.events.security": "セキュリティ",
"xpack.securitySolution.endpoint.policyDetailType": "タイプ",
"xpack.securitySolution.endpoint.policyList.actionButtonText": "Endpoint Securityを追加",
"xpack.securitySolution.endpoint.policyList.actionMenu": "開く",
"xpack.securitySolution.endpoint.policyList.agentPolicyAction": "エージェントポリシーを表示",
"xpack.securitySolution.endpoint.policyList.createdAt": "作成日時",
"xpack.securitySolution.endpoint.policyList.createdBy": "作成者",
"xpack.securitySolution.endpoint.policyList.createNewButton": "新しいポリシーを作成",
"xpack.securitySolution.endpoint.policyList.deleteConfirm.cancelButtonTitle": "キャンセル",
"xpack.securitySolution.endpoint.policyList.deleteConfirm.confirmDeleteButton": "ポリシーを削除",
"xpack.securitySolution.endpoint.policyList.deleteConfirm.deletingButton": "削除中…",
"xpack.securitySolution.endpoint.policyList.deleteConfirm.message": "この操作は元に戻すことができません。続行していいですか?",
"xpack.securitySolution.endpoint.policyList.deleteConfirm.title": "ポリシーを削除し、変更をデプロイ",
"xpack.securitySolution.endpoint.policyList.deleteConfirm.warningMessage": "このポリシーを削除すると、これらのホストからEndpoint Securityが削除されます",
"xpack.securitySolution.endpoint.policyList.deleteConfirm.warningTitle": "このアクションにより、{hostCount, plural, one {個のホスト} other {個のホスト}}からEndpoint Securityが削除されます",
"xpack.securitySolution.endpoint.policyList.deleteFailedToast": "失敗しました。",
"xpack.securitySolution.endpoint.policyList.deleteFailedToastBody": "ポリシーを削除できませんでした",
"xpack.securitySolution.endpoint.policyList.deleteSuccessToast": "成功!",
"xpack.securitySolution.endpoint.policyList.deleteSuccessToastDetails": "ポリシーが削除されました。",
"xpack.securitySolution.endpoint.policyList.emptyCreateNewButton": "エージェントの登録",
"xpack.securitySolution.endpoint.policyList.nameField": "ポリシー名",
"xpack.securitySolution.endpoint.policyList.onboardingDocsLink": "セキュリティアプリドキュメントを表示",
"xpack.securitySolution.endpoint.policyList.onboardingSectionOne": "Endpoint Securityでは、脅威防御、検出、深いセキュリティデータの可視化を実現し、ホストを保護します。",
"xpack.securitySolution.endpoint.policyList.onboardingSectionThree": "開始するには、Endpoint Security統合をエージェントに追加します。詳細については、 ",
"xpack.securitySolution.endpoint.policyList.onboardingSectionTwo": "このページでは、Endpoint Securityを実行している環境でホストを表示して管理できます。",
"xpack.securitySolution.endpoint.policyList.onboardingTitle": "Endpoint Securityの基本",
"xpack.securitySolution.endpoint.policyList.policyDeleteAction": "ポリシーを削除",
"xpack.securitySolution.endpoint.policyList.revision": "rev. {revNumber}",
"xpack.securitySolution.endpoint.policyList.updatedAt": "最終更新",
"xpack.securitySolution.endpoint.policyList.updatedBy": "最終更新者",
"xpack.securitySolution.endpoint.policyList.versionField": "v{version}",
"xpack.securitySolution.endpoint.policyList.versionFieldLabel": "バージョン",
"xpack.securitySolution.endpoint.policyList.viewTitleTotalCount": "{totalItemCount, plural, one {# ポリシー} other {# ポリシー}}",
"xpack.securitySolution.endpoint.policyResponse.backLinkTitle": "エンドポイント詳細",
"xpack.securitySolution.endpoint.policyResponse.title": "ポリシー応答",
"xpack.securitySolution.endpoint.resolver.eitherLineageLimitExceeded": "以下のビジュアライゼーションとイベントリストの一部のプロセスイベントを表示できませんでした。データの上限に達しました。",
@ -17787,8 +17763,6 @@
"xpack.securitySolution.paginatedTable.tooManyResultsToastText": "クエリ範囲を縮めて結果をさらにフィルタリングしてください",
"xpack.securitySolution.paginatedTable.tooManyResultsToastTitle": " - 結果が多すぎます",
"xpack.securitySolution.policiesTab": "ポリシー",
"xpack.securitySolution.policyList.pageSubTitle": "保護の表示と構成",
"xpack.securitySolution.policyList.pageTitle": "ポリシー",
"xpack.securitySolution.policyStatusText.failure": "失敗",
"xpack.securitySolution.policyStatusText.success": "成功",
"xpack.securitySolution.policyStatusText.warning": "警告",

View file

@ -17138,36 +17138,12 @@
"xpack.securitySolution.endpoint.policyDetailsConfig.windows.events.security": "安全",
"xpack.securitySolution.endpoint.policyDetailType": "类型",
"xpack.securitySolution.endpoint.policyList.actionButtonText": "添加 Endpoint Security",
"xpack.securitySolution.endpoint.policyList.actionMenu": "打开",
"xpack.securitySolution.endpoint.policyList.agentPolicyAction": "查看代理策略",
"xpack.securitySolution.endpoint.policyList.createdAt": "创建日期",
"xpack.securitySolution.endpoint.policyList.createdBy": "创建者",
"xpack.securitySolution.endpoint.policyList.createNewButton": "创建新策略",
"xpack.securitySolution.endpoint.policyList.deleteConfirm.cancelButtonTitle": "取消",
"xpack.securitySolution.endpoint.policyList.deleteConfirm.confirmDeleteButton": "删除策略",
"xpack.securitySolution.endpoint.policyList.deleteConfirm.deletingButton": "正在删除……",
"xpack.securitySolution.endpoint.policyList.deleteConfirm.message": "此操作无法撤消。是否确定要继续?",
"xpack.securitySolution.endpoint.policyList.deleteConfirm.title": "删除策略并部署更改",
"xpack.securitySolution.endpoint.policyList.deleteConfirm.warningMessage": "删除此策略将从这些主机上移除 Endpoint Security",
"xpack.securitySolution.endpoint.policyList.deleteConfirm.warningTitle": "此操作将从 {hostCount, plural, one {# 个主机} other {# 个主机}}上删除 Endpoint Security",
"xpack.securitySolution.endpoint.policyList.deleteFailedToast": "失败!",
"xpack.securitySolution.endpoint.policyList.deleteFailedToastBody": "无法删除策略",
"xpack.securitySolution.endpoint.policyList.deleteSuccessToast": "成功!",
"xpack.securitySolution.endpoint.policyList.deleteSuccessToastDetails": "策略已删除。",
"xpack.securitySolution.endpoint.policyList.emptyCreateNewButton": "注册代理",
"xpack.securitySolution.endpoint.policyList.nameField": "策略名称",
"xpack.securitySolution.endpoint.policyList.onboardingDocsLink": "查看 Security 应用文档",
"xpack.securitySolution.endpoint.policyList.onboardingSectionOne": "Endpoint Security 使用威胁防御、检测和深度安全数据可见性来保护您的主机。",
"xpack.securitySolution.endpoint.policyList.onboardingSectionThree": "首先,将 Endpoint Security 集成添加到您的代理。有关更多信息, ",
"xpack.securitySolution.endpoint.policyList.onboardingSectionTwo": "从此页面,您将可以查看和管理环境中运行 Endpoint Security 的主机。",
"xpack.securitySolution.endpoint.policyList.onboardingTitle": "开始使用 Endpoint Security",
"xpack.securitySolution.endpoint.policyList.policyDeleteAction": "删除策略",
"xpack.securitySolution.endpoint.policyList.revision": "修订 {revNumber}",
"xpack.securitySolution.endpoint.policyList.updatedAt": "最后更新时间",
"xpack.securitySolution.endpoint.policyList.updatedBy": "最后更新者",
"xpack.securitySolution.endpoint.policyList.versionField": "v{version}",
"xpack.securitySolution.endpoint.policyList.versionFieldLabel": "版本",
"xpack.securitySolution.endpoint.policyList.viewTitleTotalCount": "{totalItemCount, plural, one {# 个策略} other {# 个策略}}",
"xpack.securitySolution.endpoint.policyResponse.backLinkTitle": "终端详情",
"xpack.securitySolution.endpoint.policyResponse.title": "策略响应",
"xpack.securitySolution.endpoint.resolver.eitherLineageLimitExceeded": "下面可视化和事件列表中的一些进程事件无法显示,因为已达到数据限制。",
@ -17805,8 +17781,6 @@
"xpack.securitySolution.paginatedTable.tooManyResultsToastText": "缩减您的查询范围,以更好地筛选结果",
"xpack.securitySolution.paginatedTable.tooManyResultsToastTitle": " - 结果过多",
"xpack.securitySolution.policiesTab": "策略",
"xpack.securitySolution.policyList.pageSubTitle": "查看并配置防护",
"xpack.securitySolution.policyList.pageTitle": "策略",
"xpack.securitySolution.policyStatusText.failure": "失败",
"xpack.securitySolution.policyStatusText.success": "成功",
"xpack.securitySolution.policyStatusText.warning": "警告",

View file

@ -1,158 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../ftr_provider_context';
import { PolicyTestResourceInfo } from '../../services/endpoint_policy';
export default function ({ getPageObjects, getService }: FtrProviderContext) {
const pageObjects = getPageObjects([
'common',
'endpoint',
'policy',
'endpointPageUtils',
'ingestManagerCreatePackagePolicy',
]);
const testSubjects = getService('testSubjects');
const policyTestResources = getService('policyTestResources');
const RELATIVE_DATE_FORMAT = /\d (?:seconds|minutes) ago/i;
describe('When on the Endpoint Policy List', function () {
this.tags(['ciGroup7']);
before(async () => {
await pageObjects.policy.navigateToPolicyList();
});
it('loads the Policy List Page', async () => {
await testSubjects.existOrFail('policyListPage');
});
it('displays page title', async () => {
const policyTitle = await testSubjects.getVisibleText('header-page-title');
expect(policyTitle).to.equal('Policies');
});
it('shows header create policy button', async () => {
const createButtonTitle = await testSubjects.getVisibleText('headerCreateNewPolicyButton');
expect(createButtonTitle).to.equal('Create new policy');
});
it('shows empty state', async () => {
await testSubjects.existOrFail('emptyPolicyTable');
});
describe('and policies exists', () => {
let policyInfo: PolicyTestResourceInfo;
before(async () => {
// load/create a policy and then navigate back to the policy view so that the list is refreshed
policyInfo = await policyTestResources.createPolicy();
await pageObjects.policy.navigateToPolicyList();
await pageObjects.endpoint.waitForTableToHaveData('policyTable');
});
after(async () => {
if (policyInfo) {
await policyInfo.cleanup();
}
});
it('has correct table headers', async () => {
const allHeaderCells = await pageObjects.endpointPageUtils.tableHeaderVisibleText(
'policyTable'
);
expect(allHeaderCells).to.eql([
'Policy Name',
'Created By',
'Created Date',
'Last Updated By',
'Last Updated',
'Version',
'Actions',
]);
});
it('should show policy on the list', async () => {
const [, policyRow] = await pageObjects.endpointPageUtils.tableData('policyTable');
// Validate row data with the exception of the Date columns - since those are initially
// shown as relative.
expect([policyRow[0], policyRow[1], policyRow[3], policyRow[5], policyRow[6]]).to.eql([
'Protect East Coastrev. 1',
'elastic',
'elastic',
`v${policyInfo.packagePolicy.package?.version}`,
'',
]);
[policyRow[2], policyRow[4]].forEach((relativeDate) => {
expect(relativeDate).to.match(RELATIVE_DATE_FORMAT);
});
});
it('should show agent policy action as a link', async () => {
await (await pageObjects.policy.findFirstActionsButton()).click();
const agentPolicyLink = await testSubjects.find('agentPolicyLink');
expect(await agentPolicyLink.getAttribute('href')).to.match(
new RegExp(`\/ingestManager#\/policies\/${policyInfo.agentPolicy.id}$`)
);
// Close action menu
await (await pageObjects.policy.findFirstActionsButton()).click();
});
it('should delete a policy', async () => {
await pageObjects.policy.launchAndFindDeleteModal();
await testSubjects.existOrFail('policyListDeleteModal');
await pageObjects.common.clickConfirmOnModal();
const emptyPolicyTable = await testSubjects.find('emptyPolicyTable');
expect(emptyPolicyTable).not.to.be(null);
});
});
describe('and user clicks on page header create button', () => {
beforeEach(async () => {
await pageObjects.policy.navigateToPolicyList();
await (await pageObjects.policy.findHeaderCreateNewButton()).click();
});
it('should redirect to ingest management integrations add package policy', async () => {
await pageObjects.ingestManagerCreatePackagePolicy.ensureOnCreatePageOrFail();
});
it('should redirect user back to Policy List if Cancel button is clicked', async () => {
await (await pageObjects.ingestManagerCreatePackagePolicy.findCancelButton()).click();
await pageObjects.policy.ensureIsOnPolicyPage();
});
it('should redirect user back to Policy List if Back link is clicked', async () => {
await (await pageObjects.ingestManagerCreatePackagePolicy.findBackLink()).click();
await pageObjects.policy.ensureIsOnPolicyPage();
});
it('should display custom endpoint configuration message', async () => {
await pageObjects.ingestManagerCreatePackagePolicy.selectAgentPolicy();
const endpointConfig = await pageObjects.policy.findPackagePolicyEndpointCustomConfiguration();
expect(endpointConfig).not.to.be(undefined);
});
it('should have empty value for package policy name', async () => {
await pageObjects.ingestManagerCreatePackagePolicy.selectAgentPolicy();
expect(await pageObjects.ingestManagerCreatePackagePolicy.getPackagePolicyName()).to.be('');
});
it('should redirect user back to Policy List after a successful save', async () => {
const newPolicyName = `endpoint policy ${Date.now()}`;
await pageObjects.ingestManagerCreatePackagePolicy.selectAgentPolicy();
await pageObjects.ingestManagerCreatePackagePolicy.setPackagePolicyName(newPolicyName);
await (await pageObjects.ingestManagerCreatePackagePolicy.findSaveButton()).click();
await pageObjects.ingestManagerCreatePackagePolicy.waitForSaveSuccessNotification();
await pageObjects.policy.ensureIsOnPolicyPage();
await policyTestResources.deletePolicyByName(newPolicyName);
});
});
describe('and user clicks on page header create button', () => {
it('should direct users to the ingest management integrations add package policy', async () => {
await pageObjects.policy.navigateToPolicyList();
await (await pageObjects.policy.findOnboardingStartButton()).click();
await pageObjects.ingestManagerCreatePackagePolicy.ensureOnCreatePageOrFail();
});
});
});
}

View file

@ -12,42 +12,6 @@ export function EndpointPolicyPageProvider({ getService, getPageObjects }: FtrPr
const browser = getService('browser');
return {
/**
* Navigates to the Endpoint Policy List
*/
async navigateToPolicyList() {
await pageObjects.common.navigateToUrlWithBrowserHistory(
'securitySolutionManagement',
'/policy'
);
await pageObjects.header.waitUntilLoadingHasFinished();
},
/**
* Finds and returns the Policy Details Page Save button
*/
async findFirstActionsButton() {
await this.ensureIsOnPolicyPage();
return await testSubjects.find('policyActionsButton');
},
/**
* Finds and returns the Policy Details Page Save button
*/
async launchAndFindDeleteModal() {
const actionsButton = await this.findFirstActionsButton();
await actionsButton.click();
await testSubjects.click('policyDeleteButton');
return await testSubjects.find('policyListDeleteModal');
},
/**
* ensures that the Policy Page is the currently display view
*/
async ensureIsOnPolicyPage() {
await testSubjects.existOrFail('policyListPage');
},
/**
* Navigates to the Endpoint Policy Details page
*
@ -130,13 +94,5 @@ export function EndpointPolicyPageProvider({ getService, getPageObjects }: FtrPr
async findPackagePolicyEndpointCustomConfiguration(onEditPage: boolean = false) {
return await testSubjects.find(`endpointPackagePolicy_${onEditPage ? 'edit' : 'create'}`);
},
/**
* Finds and returns the onboarding button displayed in empty List pages
*/
async findOnboardingStartButton() {
await testSubjects.waitForEnabled('onboardingStartButton');
return await testSubjects.find('onboardingStartButton');
},
};
}