[SECURITY_SOLUTION][ENDPOINT] Delete Endpoint Policy List code (#87063)
* Remove Endpoint Policy List code from security_solution plugin
This commit is contained in:
parent
e3486648b9
commit
e067f557de
|
@ -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;
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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({}),
|
||||
}));
|
||||
|
|
|
@ -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({}),
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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({}),
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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;
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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>;
|
||||
}
|
|
@ -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,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
|
@ -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;
|
||||
};
|
|
@ -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
|
||||
);
|
|
@ -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', () => {
|
|
@ -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`;
|
|
@ -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
|
|
@ -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';
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}`);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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';
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -17110,36 +17110,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": "以下のビジュアライゼーションとイベントリストの一部のプロセスイベントを表示できませんでした。データの上限に達しました。",
|
||||
|
@ -17777,8 +17753,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": "警告",
|
||||
|
|
|
@ -17128,36 +17128,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": "下面可视化和事件列表中的一些进程事件无法显示,因为已达到数据限制。",
|
||||
|
@ -17795,8 +17771,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": "警告",
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -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');
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue