mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[Security Solution] host isolation exceptions listing under policy integration details tab (#120361)
This commit is contained in:
parent
b0442e396b
commit
b6753241ed
19 changed files with 796 additions and 56 deletions
|
@ -9,9 +9,11 @@ import type { FoundExceptionListItemSchema } from '@kbn/securitysolution-io-ts-l
|
|||
|
||||
import { getExceptionListItemSchemaMock } from './exception_list_item_schema.mock';
|
||||
|
||||
export const getFoundExceptionListItemSchemaMock = (): FoundExceptionListItemSchema => ({
|
||||
data: [getExceptionListItemSchemaMock()],
|
||||
export const getFoundExceptionListItemSchemaMock = (
|
||||
count: number = 1
|
||||
): FoundExceptionListItemSchema => ({
|
||||
data: Array.from({ length: count }, getExceptionListItemSchemaMock),
|
||||
page: 1,
|
||||
per_page: 1,
|
||||
total: 1,
|
||||
total: count,
|
||||
});
|
||||
|
|
|
@ -14,6 +14,7 @@ export const MANAGEMENT_ROUTING_POLICIES_PATH = `${MANAGEMENT_PATH}/:tabName(${A
|
|||
export const MANAGEMENT_ROUTING_POLICY_DETAILS_FORM_PATH = `${MANAGEMENT_PATH}/:tabName(${AdministrationSubTab.policies})/:policyId/settings`;
|
||||
export const MANAGEMENT_ROUTING_POLICY_DETAILS_TRUSTED_APPS_PATH = `${MANAGEMENT_PATH}/:tabName(${AdministrationSubTab.policies})/:policyId/trustedApps`;
|
||||
export const MANAGEMENT_ROUTING_POLICY_DETAILS_EVENT_FILTERS_PATH = `${MANAGEMENT_PATH}/:tabName(${AdministrationSubTab.policies})/:policyId/eventFilters`;
|
||||
export const MANAGEMENT_ROUTING_POLICY_DETAILS_HOST_ISOLATION_EXCEPTIONS_PATH = `${MANAGEMENT_PATH}/:tabName(${AdministrationSubTab.policies})/:policyId/hostIsolationExceptions`;
|
||||
/** @deprecated use the paths defined above instead */
|
||||
export const MANAGEMENT_ROUTING_POLICY_DETAILS_PATH_OLD = `${MANAGEMENT_PATH}/:tabName(${AdministrationSubTab.policies})/:policyId`;
|
||||
export const MANAGEMENT_ROUTING_TRUSTED_APPS_PATH = `${MANAGEMENT_PATH}/:tabName(${AdministrationSubTab.trustedApps})`;
|
||||
|
|
|
@ -6,10 +6,16 @@
|
|||
*/
|
||||
|
||||
import { isEmpty } from 'lodash/fp';
|
||||
import { generatePath } from 'react-router-dom';
|
||||
// eslint-disable-next-line import/no-nodejs-modules
|
||||
import querystring from 'querystring';
|
||||
|
||||
import { generatePath } from 'react-router-dom';
|
||||
import { appendSearch } from '../../common/components/link_to/helpers';
|
||||
import { EndpointIndexUIQueryParams } from '../pages/endpoint_hosts/types';
|
||||
import { EventFiltersPageLocation } from '../pages/event_filters/types';
|
||||
import { HostIsolationExceptionsPageLocation } from '../pages/host_isolation_exceptions/types';
|
||||
import { PolicyDetailsArtifactsPageLocation } from '../pages/policy/types';
|
||||
import { TrustedAppsListPageLocation } from '../pages/trusted_apps/state';
|
||||
import { AdministrationSubTab } from '../types';
|
||||
import {
|
||||
MANAGEMENT_DEFAULT_PAGE,
|
||||
MANAGEMENT_DEFAULT_PAGE_SIZE,
|
||||
|
@ -19,17 +25,11 @@ import {
|
|||
MANAGEMENT_ROUTING_HOST_ISOLATION_EXCEPTIONS_PATH,
|
||||
MANAGEMENT_ROUTING_POLICIES_PATH,
|
||||
MANAGEMENT_ROUTING_POLICY_DETAILS_FORM_PATH,
|
||||
MANAGEMENT_ROUTING_POLICY_DETAILS_HOST_ISOLATION_EXCEPTIONS_PATH,
|
||||
MANAGEMENT_ROUTING_POLICY_DETAILS_TRUSTED_APPS_PATH,
|
||||
MANAGEMENT_ROUTING_POLICY_DETAILS_EVENT_FILTERS_PATH,
|
||||
MANAGEMENT_ROUTING_TRUSTED_APPS_PATH,
|
||||
} from './constants';
|
||||
import { AdministrationSubTab } from '../types';
|
||||
import { appendSearch } from '../../common/components/link_to/helpers';
|
||||
import { EndpointIndexUIQueryParams } from '../pages/endpoint_hosts/types';
|
||||
import { TrustedAppsListPageLocation } from '../pages/trusted_apps/state';
|
||||
import { EventFiltersPageLocation } from '../pages/event_filters/types';
|
||||
import { HostIsolationExceptionsPageLocation } from '../pages/host_isolation_exceptions/types';
|
||||
import { PolicyDetailsArtifactsPageLocation } from '../pages/policy/types';
|
||||
|
||||
// Taken from: https://github.com/microsoft/TypeScript/issues/12936#issuecomment-559034150
|
||||
type ExactKeys<T1, T2> = Exclude<keyof T1, keyof T2> extends never ? T1 : never;
|
||||
|
@ -390,3 +390,16 @@ export const getHostIsolationExceptionsListPath = (
|
|||
querystring.stringify(normalizeHostIsolationExceptionsPageLocation(location))
|
||||
)}`;
|
||||
};
|
||||
|
||||
export const getPolicyHostIsolationExceptionsPath = (
|
||||
policyId: string,
|
||||
location?: Partial<PolicyDetailsArtifactsPageLocation>
|
||||
) => {
|
||||
const path = generatePath(MANAGEMENT_ROUTING_POLICY_DETAILS_HOST_ISOLATION_EXCEPTIONS_PATH, {
|
||||
tabName: AdministrationSubTab.policies,
|
||||
policyId,
|
||||
});
|
||||
return `${path}${appendSearch(
|
||||
querystring.stringify(normalizePolicyDetailsArtifactsListPageLocation(location))
|
||||
)}`;
|
||||
};
|
||||
|
|
|
@ -49,3 +49,24 @@ export const parsePoliciesToKQL = (includedPolicies: string, excludedPolicies: s
|
|||
|
||||
return `(${kuery.join(' AND ')})`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Takes a list of policies (string[]) and an existing kuery
|
||||
* (string) and returns an unified KQL with and AND
|
||||
* @param policies string[] a list of policies ids
|
||||
* @param kuery string an existing KQL.
|
||||
*/
|
||||
export const parsePoliciesAndFilterToKql = ({
|
||||
policies,
|
||||
kuery,
|
||||
}: {
|
||||
policies?: string[];
|
||||
kuery?: string;
|
||||
}): string | undefined => {
|
||||
if (!policies || !policies.length) {
|
||||
return kuery;
|
||||
}
|
||||
|
||||
const policiesKQL = parsePoliciesToKQL(policies.join(','), '');
|
||||
return `(${policiesKQL})${kuery ? ` AND (${kuery})` : ''}`;
|
||||
};
|
||||
|
|
|
@ -22,7 +22,7 @@ import {
|
|||
MANAGEMENT_STORE_HOST_ISOLATION_EXCEPTIONS_NAMESPACE,
|
||||
} from '../../../common/constants';
|
||||
import { getHostIsolationExceptionsListPath } from '../../../common/routing';
|
||||
import { parseQueryFilterToKQL } from '../../../common/utils';
|
||||
import { parsePoliciesAndFilterToKql, parseQueryFilterToKQL } from '../../../common/utils';
|
||||
import {
|
||||
getHostIsolationExceptionItems,
|
||||
getHostIsolationExceptionSummary,
|
||||
|
@ -87,23 +87,37 @@ export function useCanSeeHostIsolationExceptionsMenu(): boolean {
|
|||
|
||||
const SEARCHABLE_FIELDS: Readonly<string[]> = [`name`, `description`, `entries.value`];
|
||||
|
||||
export function useFetchHostIsolationExceptionsList(): QueryObserverResult<
|
||||
FoundExceptionListItemSchema,
|
||||
ServerApiError
|
||||
> {
|
||||
export function useFetchHostIsolationExceptionsList({
|
||||
filter,
|
||||
page,
|
||||
perPage,
|
||||
policies,
|
||||
enabled = true,
|
||||
}: {
|
||||
filter?: string;
|
||||
page: number;
|
||||
perPage: number;
|
||||
policies?: string[];
|
||||
enabled?: boolean;
|
||||
}): QueryObserverResult<FoundExceptionListItemSchema, ServerApiError> {
|
||||
const http = useHttp();
|
||||
const location = useHostIsolationExceptionsSelector(getCurrentLocation);
|
||||
|
||||
return useQuery<FoundExceptionListItemSchema, ServerApiError>(
|
||||
['hostIsolationExceptions', 'list', location.filter, location.page_size, location.page_index],
|
||||
['hostIsolationExceptions', 'list', filter, perPage, page, policies],
|
||||
() => {
|
||||
const kql = parsePoliciesAndFilterToKql({
|
||||
policies,
|
||||
kuery: filter ? parseQueryFilterToKQL(filter, SEARCHABLE_FIELDS) : undefined,
|
||||
});
|
||||
|
||||
return getHostIsolationExceptionItems({
|
||||
http,
|
||||
page: location.page_index + 1,
|
||||
perPage: location.page_size,
|
||||
filter: parseQueryFilterToKQL(location.filter, SEARCHABLE_FIELDS) || undefined,
|
||||
page: page + 1,
|
||||
perPage,
|
||||
filter: kql,
|
||||
});
|
||||
}
|
||||
},
|
||||
{ enabled }
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -114,7 +128,7 @@ export function useGetHostIsolationExceptionFormEntry({
|
|||
}: {
|
||||
id?: string;
|
||||
onSuccess: (data: CreateExceptionListItemSchema | UpdateExceptionListItemSchema) => void;
|
||||
onError: (error: ServerApiError) => void;
|
||||
onError?: (error: ServerApiError) => void;
|
||||
}): QueryObserverResult {
|
||||
const http = useHttp();
|
||||
return useQuery<UpdateExceptionListItemSchema | CreateExceptionListItemSchema, ServerApiError>(
|
||||
|
|
|
@ -91,7 +91,9 @@ describe('When on the host isolation exceptions page', () => {
|
|||
|
||||
describe('And data exists', () => {
|
||||
beforeEach(async () => {
|
||||
getHostIsolationExceptionItemsMock.mockImplementation(getFoundExceptionListItemSchemaMock);
|
||||
getHostIsolationExceptionItemsMock.mockImplementation(() =>
|
||||
getFoundExceptionListItemSchemaMock(1)
|
||||
);
|
||||
});
|
||||
|
||||
it('should show loading indicator while retrieving data and hide it when it gets it', async () => {
|
||||
|
@ -185,7 +187,9 @@ describe('When on the host isolation exceptions page', () => {
|
|||
describe('has canIsolateHost privileges', () => {
|
||||
beforeEach(async () => {
|
||||
setEndpointPrivileges({ canIsolateHost: true });
|
||||
getHostIsolationExceptionItemsMock.mockImplementation(getFoundExceptionListItemSchemaMock);
|
||||
getHostIsolationExceptionItemsMock.mockImplementation(() =>
|
||||
getFoundExceptionListItemSchemaMock(1)
|
||||
);
|
||||
});
|
||||
|
||||
it('should show the create flyout when the add button is pressed', async () => {
|
||||
|
|
|
@ -55,7 +55,12 @@ export const HostIsolationExceptionsList = () => {
|
|||
|
||||
const [itemToDelete, setItemToDelete] = useState<ExceptionListItemSchema | null>(null);
|
||||
|
||||
const { isLoading, data, error, refetch } = useFetchHostIsolationExceptionsList();
|
||||
const { isLoading, data, error, refetch } = useFetchHostIsolationExceptionsList({
|
||||
filter: location.filter,
|
||||
page: location.page_index,
|
||||
perPage: location.page_size,
|
||||
});
|
||||
|
||||
const toasts = useToasts();
|
||||
|
||||
// load the list of policies>
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
MANAGEMENT_ROUTING_POLICY_DETAILS_TRUSTED_APPS_PATH,
|
||||
MANAGEMENT_ROUTING_POLICY_DETAILS_EVENT_FILTERS_PATH,
|
||||
MANAGEMENT_ROUTING_POLICY_DETAILS_PATH_OLD,
|
||||
MANAGEMENT_ROUTING_POLICY_DETAILS_HOST_ISOLATION_EXCEPTIONS_PATH,
|
||||
} from '../../common/constants';
|
||||
import { NotFoundPage } from '../../../app/404';
|
||||
import { getPolicyDetailPath } from '../../common/routing';
|
||||
|
@ -25,6 +26,7 @@ export const PolicyContainer = memo(() => {
|
|||
MANAGEMENT_ROUTING_POLICY_DETAILS_FORM_PATH,
|
||||
MANAGEMENT_ROUTING_POLICY_DETAILS_TRUSTED_APPS_PATH,
|
||||
MANAGEMENT_ROUTING_POLICY_DETAILS_EVENT_FILTERS_PATH,
|
||||
MANAGEMENT_ROUTING_POLICY_DETAILS_HOST_ISOLATION_EXCEPTIONS_PATH,
|
||||
]}
|
||||
exact
|
||||
component={PolicyDetails}
|
||||
|
|
|
@ -7,12 +7,13 @@
|
|||
|
||||
import { matchPath } from 'react-router-dom';
|
||||
import { createSelector } from 'reselect';
|
||||
import { PolicyDetailsSelector, PolicyDetailsState } from '../../../types';
|
||||
import {
|
||||
MANAGEMENT_ROUTING_POLICY_DETAILS_FORM_PATH,
|
||||
MANAGEMENT_ROUTING_POLICY_DETAILS_HOST_ISOLATION_EXCEPTIONS_PATH,
|
||||
MANAGEMENT_ROUTING_POLICY_DETAILS_TRUSTED_APPS_PATH,
|
||||
MANAGEMENT_ROUTING_POLICY_DETAILS_EVENT_FILTERS_PATH,
|
||||
} from '../../../../../common/constants';
|
||||
import { PolicyDetailsSelector, PolicyDetailsState } from '../../../types';
|
||||
|
||||
/**
|
||||
* Returns current artifacts location
|
||||
|
@ -37,7 +38,7 @@ export const isOnPolicyFormView: PolicyDetailsSelector<boolean> = createSelector
|
|||
}
|
||||
);
|
||||
|
||||
/** Returns a boolean of whether the user is on the policy trusted app page or not */
|
||||
/** Returns a boolean of whether the user is on the policy trusted apps page or not */
|
||||
export const isOnPolicyTrustedAppsView: PolicyDetailsSelector<boolean> = createSelector(
|
||||
getUrlLocationPathname,
|
||||
(pathname) => {
|
||||
|
@ -62,3 +63,16 @@ export const isOnPolicyEventFiltersView: PolicyDetailsSelector<boolean> = create
|
|||
);
|
||||
}
|
||||
);
|
||||
|
||||
/** Returns a boolean of whether the user is on the host isolation exceptions page or not */
|
||||
export const isOnHostIsolationExceptionsView: PolicyDetailsSelector<boolean> = createSelector(
|
||||
getUrlLocationPathname,
|
||||
(pathname) => {
|
||||
return (
|
||||
matchPath(pathname ?? '', {
|
||||
path: MANAGEMENT_ROUTING_POLICY_DETAILS_HOST_ISOLATION_EXCEPTIONS_PATH,
|
||||
exact: true,
|
||||
}) !== null
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { createSelector } from 'reselect';
|
||||
import { matchPath } from 'react-router-dom';
|
||||
import { createSelector } from 'reselect';
|
||||
import { ILicense } from '../../../../../../../../licensing/common/types';
|
||||
import { unsetPolicyFeaturesAccordingToLicenseLevel } from '../../../../../../../common/license/policy_config';
|
||||
import { PolicyDetailsState } from '../../../types';
|
||||
|
@ -20,6 +20,7 @@ import {
|
|||
import { policyFactory as policyConfigFactory } from '../../../../../../../common/endpoint/models/policy_config';
|
||||
import {
|
||||
MANAGEMENT_ROUTING_POLICY_DETAILS_FORM_PATH,
|
||||
MANAGEMENT_ROUTING_POLICY_DETAILS_HOST_ISOLATION_EXCEPTIONS_PATH,
|
||||
MANAGEMENT_ROUTING_POLICY_DETAILS_TRUSTED_APPS_PATH,
|
||||
MANAGEMENT_ROUTING_POLICY_DETAILS_EVENT_FILTERS_PATH,
|
||||
} from '../../../../../common/constants';
|
||||
|
@ -28,6 +29,7 @@ import { getPolicyDataForUpdate } from '../../../../../../../common/endpoint/ser
|
|||
import {
|
||||
isOnPolicyTrustedAppsView,
|
||||
isOnPolicyEventFiltersView,
|
||||
isOnHostIsolationExceptionsView,
|
||||
isOnPolicyFormView,
|
||||
} from './policy_common_selectors';
|
||||
|
||||
|
@ -90,7 +92,8 @@ export const needsToRefresh = (state: Immutable<PolicyDetailsState>): boolean =>
|
|||
export const isOnPolicyDetailsPage = (state: Immutable<PolicyDetailsState>) =>
|
||||
isOnPolicyFormView(state) ||
|
||||
isOnPolicyTrustedAppsView(state) ||
|
||||
isOnPolicyEventFiltersView(state);
|
||||
isOnPolicyEventFiltersView(state) ||
|
||||
isOnHostIsolationExceptionsView(state);
|
||||
|
||||
/** Returns the license info fetched from the license service */
|
||||
export const license = (state: Immutable<PolicyDetailsState>) => {
|
||||
|
@ -107,6 +110,7 @@ export const policyIdFromParams: (state: Immutable<PolicyDetailsState>) => strin
|
|||
MANAGEMENT_ROUTING_POLICY_DETAILS_FORM_PATH,
|
||||
MANAGEMENT_ROUTING_POLICY_DETAILS_TRUSTED_APPS_PATH,
|
||||
MANAGEMENT_ROUTING_POLICY_DETAILS_EVENT_FILTERS_PATH,
|
||||
MANAGEMENT_ROUTING_POLICY_DETAILS_HOST_ISOLATION_EXCEPTIONS_PATH,
|
||||
],
|
||||
exact: true,
|
||||
})?.params?.policyId ?? ''
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiEmptyPrompt, EuiLink, EuiPageTemplate } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import React from 'react';
|
||||
|
||||
export const PolicyHostIsolationExceptionsEmptyUnassigned = ({
|
||||
policyName,
|
||||
toHostIsolationList,
|
||||
}: {
|
||||
policyName: string;
|
||||
toHostIsolationList: string;
|
||||
}) => {
|
||||
return (
|
||||
<EuiPageTemplate template="centeredContent">
|
||||
<EuiEmptyPrompt
|
||||
iconType="plusInCircle"
|
||||
data-test-subj="policy-host-isolation-exceptions-empty-unassigned"
|
||||
title={
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policy.hostIsolationExceptions.empty.unassigned.title"
|
||||
defaultMessage="No assigned host isolation exceptions"
|
||||
/>
|
||||
</h2>
|
||||
}
|
||||
body={
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policy.hostIsolationExceptions.empty.unassigned.content"
|
||||
defaultMessage="There are currently no host isolation exceptions assigned to {policyName}. Assign exceptions now or add and manage them on the host isolation exceptions page."
|
||||
values={{ policyName }}
|
||||
/>
|
||||
}
|
||||
actions={[
|
||||
<EuiLink href={toHostIsolationList}>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policy.hostIsolationExceptions.empty.unassigned.secondaryAction"
|
||||
defaultMessage="Manage host isolation exceptions"
|
||||
/>
|
||||
</EuiLink>,
|
||||
]}
|
||||
/>
|
||||
</EuiPageTemplate>
|
||||
);
|
||||
};
|
||||
|
||||
PolicyHostIsolationExceptionsEmptyUnassigned.displayName =
|
||||
'PolicyHostIsolationExceptionsEmptyUnassigned';
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiButton, EuiEmptyPrompt, EuiPageTemplate } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import React from 'react';
|
||||
|
||||
export const PolicyHostIsolationExceptionsEmptyUnexisting = ({
|
||||
toHostIsolationList,
|
||||
}: {
|
||||
toHostIsolationList: string;
|
||||
}) => {
|
||||
return (
|
||||
<EuiPageTemplate template="centeredContent">
|
||||
<EuiEmptyPrompt
|
||||
iconType="plusInCircle"
|
||||
data-test-subj="policy-host-isolation-exceptions-empty-unexisting"
|
||||
title={
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policy.hostIsolationExceptions.empty.unexisting.title"
|
||||
defaultMessage="No host isolation exceptions exist"
|
||||
/>
|
||||
</h2>
|
||||
}
|
||||
body={
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policy.hostIsolationExceptions.empty.unexisting.content"
|
||||
defaultMessage="There are currently no host isolation exceptions applied to your endpoints."
|
||||
/>
|
||||
}
|
||||
actions={
|
||||
<EuiButton color="primary" fill href={toHostIsolationList}>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policy.hostIsolationExceptions.empty.unexisting.action"
|
||||
defaultMessage="Add host isolation exceptions"
|
||||
/>
|
||||
</EuiButton>
|
||||
}
|
||||
/>
|
||||
</EuiPageTemplate>
|
||||
);
|
||||
};
|
||||
|
||||
PolicyHostIsolationExceptionsEmptyUnexisting.displayName =
|
||||
'PolicyHostIsolationExceptionsEmptyUnexisting';
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { FoundExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
|
||||
import { act } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import uuid from 'uuid';
|
||||
import { getPolicyHostIsolationExceptionsPath } from '../../../../../common/routing';
|
||||
import { getFoundExceptionListItemSchemaMock } from '../../../../../../../../lists/common/schemas/response/found_exception_list_item_schema.mock';
|
||||
import {
|
||||
AppContextTestRender,
|
||||
createAppRootMockRenderer,
|
||||
} from '../../../../../../common/mock/endpoint';
|
||||
import { PolicyHostIsolationExceptionsList } from './list';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
const emptyList = {
|
||||
data: [],
|
||||
page: 1,
|
||||
per_page: 10,
|
||||
total: 0,
|
||||
};
|
||||
|
||||
describe('Policy details host isolation exceptions tab', () => {
|
||||
let policyId: string;
|
||||
let render: (
|
||||
exceptions: FoundExceptionListItemSchema
|
||||
) => ReturnType<AppContextTestRender['render']>;
|
||||
let renderResult: ReturnType<typeof render>;
|
||||
let history: AppContextTestRender['history'];
|
||||
let mockedContext: AppContextTestRender;
|
||||
|
||||
beforeEach(() => {
|
||||
policyId = uuid.v4();
|
||||
mockedContext = createAppRootMockRenderer();
|
||||
({ history } = mockedContext);
|
||||
render = (exceptions: FoundExceptionListItemSchema) =>
|
||||
(renderResult = mockedContext.render(
|
||||
<PolicyHostIsolationExceptionsList policyId={policyId} exceptions={exceptions} />
|
||||
));
|
||||
|
||||
act(() => {
|
||||
history.push(getPolicyHostIsolationExceptionsPath(policyId));
|
||||
});
|
||||
});
|
||||
|
||||
it('should display a searchbar and count even with no exceptions', () => {
|
||||
render(emptyList);
|
||||
expect(
|
||||
renderResult.getByTestId('policyDetailsHostIsolationExceptionsSearchCount')
|
||||
).toHaveTextContent('Showing 0 exceptions');
|
||||
expect(renderResult.getByTestId('searchField')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should render the list of exceptions collapsed and expand it when clicked', () => {
|
||||
// render 3
|
||||
render(getFoundExceptionListItemSchemaMock(3));
|
||||
expect(renderResult.getAllByTestId('hostIsolationExceptions-collapsed-list-card')).toHaveLength(
|
||||
3
|
||||
);
|
||||
expect(
|
||||
renderResult.queryAllByTestId(
|
||||
'hostIsolationExceptions-collapsed-list-card-criteriaConditions'
|
||||
)
|
||||
).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should expand an item when expand is clicked', () => {
|
||||
render(getFoundExceptionListItemSchemaMock(1));
|
||||
expect(renderResult.getAllByTestId('hostIsolationExceptions-collapsed-list-card')).toHaveLength(
|
||||
1
|
||||
);
|
||||
|
||||
userEvent.click(
|
||||
renderResult.getByTestId('hostIsolationExceptions-collapsed-list-card-header-expandCollapse')
|
||||
);
|
||||
|
||||
expect(
|
||||
renderResult.queryAllByTestId(
|
||||
'hostIsolationExceptions-collapsed-list-card-criteriaConditions'
|
||||
)
|
||||
).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should change the address location when a filter is applied', () => {
|
||||
render(getFoundExceptionListItemSchemaMock(1));
|
||||
userEvent.type(renderResult.getByTestId('searchField'), 'search me{enter}');
|
||||
expect(history.location.search).toBe('?filter=search%20me');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiSpacer, EuiText } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FoundExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import {
|
||||
MANAGEMENT_DEFAULT_PAGE_SIZE,
|
||||
MANAGEMENT_PAGE_SIZE_OPTIONS,
|
||||
} from '../../../../../common/constants';
|
||||
import { getPolicyHostIsolationExceptionsPath } from '../../../../../common/routing';
|
||||
import {
|
||||
ArtifactCardGrid,
|
||||
ArtifactCardGridProps,
|
||||
} from '../../../../../components/artifact_card_grid';
|
||||
import { useEndpointPoliciesToArtifactPolicies } from '../../../../../components/artifact_entry_card/hooks/use_endpoint_policies_to_artifact_policies';
|
||||
import { SearchExceptions } from '../../../../../components/search_exceptions';
|
||||
import { useGetEndpointSpecificPolicies } from '../../../../../services/policies/hooks';
|
||||
import { getCurrentArtifactsLocation } from '../../../store/policy_details/selectors';
|
||||
import { usePolicyDetailsSelector } from '../../policy_hooks';
|
||||
|
||||
export const PolicyHostIsolationExceptionsList = ({
|
||||
exceptions,
|
||||
policyId,
|
||||
}: {
|
||||
exceptions: FoundExceptionListItemSchema;
|
||||
policyId: string;
|
||||
}) => {
|
||||
const history = useHistory();
|
||||
// load the list of policies>
|
||||
const policiesRequest = useGetEndpointSpecificPolicies();
|
||||
const urlParams = usePolicyDetailsSelector(getCurrentArtifactsLocation);
|
||||
|
||||
const [expandedItemsMap, setExpandedItemsMap] = useState<Map<string, boolean>>(new Map());
|
||||
|
||||
const pagination = {
|
||||
totalItemCount: exceptions?.total ?? 0,
|
||||
pageSize: exceptions?.per_page ?? MANAGEMENT_DEFAULT_PAGE_SIZE,
|
||||
pageSizeOptions: [...MANAGEMENT_PAGE_SIZE_OPTIONS],
|
||||
pageIndex: (exceptions?.page ?? 1) - 1,
|
||||
};
|
||||
|
||||
const handlePageChange = useCallback<ArtifactCardGridProps['onPageChange']>(
|
||||
({ pageIndex, pageSize }) => {
|
||||
history.push(
|
||||
getPolicyHostIsolationExceptionsPath(policyId, {
|
||||
...urlParams,
|
||||
// If user changed page size, then reset page index back to the first page
|
||||
page_index: pageIndex,
|
||||
page_size: pageSize,
|
||||
})
|
||||
);
|
||||
},
|
||||
[history, policyId, urlParams]
|
||||
);
|
||||
|
||||
const handleSearchInput = useCallback(
|
||||
(filter: string) => {
|
||||
history.push(
|
||||
getPolicyHostIsolationExceptionsPath(policyId, {
|
||||
...urlParams,
|
||||
filter,
|
||||
})
|
||||
);
|
||||
},
|
||||
[history, policyId, urlParams]
|
||||
);
|
||||
|
||||
const artifactCardPolicies = useEndpointPoliciesToArtifactPolicies(policiesRequest.data?.items);
|
||||
|
||||
const provideCardProps: ArtifactCardGridProps['cardComponentProps'] = (item) => {
|
||||
return {
|
||||
expanded: expandedItemsMap.get(item.id) || false,
|
||||
actions: [],
|
||||
policies: artifactCardPolicies,
|
||||
};
|
||||
};
|
||||
|
||||
const handleExpandCollapse: ArtifactCardGridProps['onExpandCollapse'] = ({
|
||||
expanded,
|
||||
collapsed,
|
||||
}) => {
|
||||
const newExpandedMap = new Map(expandedItemsMap);
|
||||
for (const item of expanded) {
|
||||
newExpandedMap.set(item.id, true);
|
||||
}
|
||||
for (const item of collapsed) {
|
||||
newExpandedMap.set(item.id, false);
|
||||
}
|
||||
setExpandedItemsMap(newExpandedMap);
|
||||
};
|
||||
|
||||
const totalItemsCountLabel = useMemo<string>(() => {
|
||||
return i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.hostIsolationExceptions.list.totalItemCount',
|
||||
{
|
||||
defaultMessage: 'Showing {totalItemsCount, plural, one {# exception} other {# exceptions}}',
|
||||
values: { totalItemsCount: pagination.totalItemCount },
|
||||
}
|
||||
);
|
||||
}, [pagination.totalItemCount]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SearchExceptions
|
||||
placeholder={i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.hostIsolationExceptions.list.search.placeholder',
|
||||
{
|
||||
defaultMessage: 'Search on the fields below: name, description, value, ip',
|
||||
}
|
||||
)}
|
||||
defaultValue={urlParams.filter}
|
||||
hideRefreshButton
|
||||
onSearch={handleSearchInput}
|
||||
/>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiText
|
||||
color="subdued"
|
||||
size="xs"
|
||||
data-test-subj="policyDetailsHostIsolationExceptionsSearchCount"
|
||||
>
|
||||
{totalItemsCountLabel}
|
||||
</EuiText>
|
||||
<EuiSpacer size="m" />
|
||||
<ArtifactCardGrid
|
||||
items={exceptions.data}
|
||||
onPageChange={handlePageChange}
|
||||
onExpandCollapse={handleExpandCollapse}
|
||||
cardComponentProps={provideCardProps}
|
||||
pagination={pagination}
|
||||
loading={policiesRequest.isLoading}
|
||||
data-test-subj={'hostIsolationExceptions-collapsed-list'}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
PolicyHostIsolationExceptionsList.displayName = 'PolicyHostIsolationExceptionsList';
|
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { getFoundExceptionListItemSchemaMock } from '../../../../../../../lists/common/schemas/response/found_exception_list_item_schema.mock';
|
||||
import { EndpointDocGenerator } from '../../../../../../common/endpoint/generate_data';
|
||||
import { PolicyData } from '../../../../../../common/endpoint/types';
|
||||
import {
|
||||
AppContextTestRender,
|
||||
createAppRootMockRenderer,
|
||||
} from '../../../../../common/mock/endpoint';
|
||||
import { getPolicyHostIsolationExceptionsPath } from '../../../../common/routing';
|
||||
import { getHostIsolationExceptionItems } from '../../../host_isolation_exceptions/service';
|
||||
import { PolicyHostIsolationExceptionsTab } from './host_isolation_exceptions_tab';
|
||||
|
||||
jest.mock('../../../host_isolation_exceptions/service');
|
||||
|
||||
const getHostIsolationExceptionItemsMock = getHostIsolationExceptionItems as jest.Mock;
|
||||
|
||||
const endpointGenerator = new EndpointDocGenerator('seed');
|
||||
|
||||
const emptyList = {
|
||||
data: [],
|
||||
page: 1,
|
||||
per_page: 10,
|
||||
total: 0,
|
||||
};
|
||||
|
||||
describe('Policy details host isolation exceptions tab', () => {
|
||||
let policyId: string;
|
||||
let policy: PolicyData;
|
||||
let render: () => ReturnType<AppContextTestRender['render']>;
|
||||
let renderResult: ReturnType<typeof render>;
|
||||
let history: AppContextTestRender['history'];
|
||||
let mockedContext: AppContextTestRender;
|
||||
|
||||
beforeEach(() => {
|
||||
getHostIsolationExceptionItemsMock.mockClear();
|
||||
policy = endpointGenerator.generatePolicyPackagePolicy();
|
||||
policyId = policy.id;
|
||||
mockedContext = createAppRootMockRenderer();
|
||||
({ history } = mockedContext);
|
||||
render = () =>
|
||||
(renderResult = mockedContext.render(<PolicyHostIsolationExceptionsTab policy={policy} />));
|
||||
|
||||
history.push(getPolicyHostIsolationExceptionsPath(policyId));
|
||||
});
|
||||
|
||||
it('should display display a "loading" state while requests happen', async () => {
|
||||
const promises: Array<() => void> = [];
|
||||
getHostIsolationExceptionItemsMock.mockImplementation(() => {
|
||||
return new Promise<void>((resolve) => promises.push(resolve));
|
||||
});
|
||||
render();
|
||||
expect(await renderResult.findByTestId('policyHostIsolationExceptionsTabLoading')).toBeTruthy();
|
||||
// prevent memory leaks
|
||||
promises.forEach((resolve) => resolve());
|
||||
});
|
||||
|
||||
it("should display an 'unexistent' empty state if there are no host isolation exceptions at all", async () => {
|
||||
// mock no data for all requests
|
||||
getHostIsolationExceptionItemsMock.mockResolvedValue({
|
||||
...emptyList,
|
||||
});
|
||||
render();
|
||||
expect(
|
||||
await renderResult.findByTestId('policy-host-isolation-exceptions-empty-unexisting')
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should display an 'unassigned' empty state if there are no host isolation exceptions assigned", async () => {
|
||||
// mock no data for all requests
|
||||
getHostIsolationExceptionItemsMock.mockImplementation((params) => {
|
||||
// no filter = fetch all exceptions
|
||||
if (!params.filter) {
|
||||
return {
|
||||
...emptyList,
|
||||
total: 1,
|
||||
};
|
||||
}
|
||||
return {
|
||||
...emptyList,
|
||||
};
|
||||
});
|
||||
render();
|
||||
expect(
|
||||
await renderResult.findByTestId('policy-host-isolation-exceptions-empty-unassigned')
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it('Should display the count of total assigned policies', async () => {
|
||||
getHostIsolationExceptionItemsMock.mockImplementation(() => {
|
||||
return getFoundExceptionListItemSchemaMock(4);
|
||||
});
|
||||
render();
|
||||
expect(
|
||||
await renderResult.findByTestId('policyHostIsolationExceptionsTabSubtitle')
|
||||
).toHaveTextContent('There are 4 exceptions associated with this policy');
|
||||
});
|
||||
|
||||
it('should apply a filter when requested from location search params', async () => {
|
||||
history.push(getPolicyHostIsolationExceptionsPath(policyId, { filter: 'my filter' }));
|
||||
getHostIsolationExceptionItemsMock.mockImplementation(() => {
|
||||
return getFoundExceptionListItemSchemaMock(4);
|
||||
});
|
||||
render();
|
||||
expect(getHostIsolationExceptionItemsMock).toHaveBeenLastCalledWith({
|
||||
filter: `((exception-list-agnostic.attributes.tags:"policy:${policyId}" OR exception-list-agnostic.attributes.tags:"policy:all")) AND ((exception-list-agnostic.attributes.name:(*my*filter*) OR exception-list-agnostic.attributes.description:(*my*filter*) OR exception-list-agnostic.attributes.entries.value:(*my*filter*)))`,
|
||||
http: mockedContext.coreStart.http,
|
||||
page: 1,
|
||||
perPage: 10,
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,164 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import {
|
||||
EuiLink,
|
||||
EuiPageContent,
|
||||
EuiPageHeader,
|
||||
EuiPageHeaderSection,
|
||||
EuiProgress,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { APP_UI_ID } from '../../../../../../common/constants';
|
||||
import { PolicyData } from '../../../../../../common/endpoint/types';
|
||||
import { useAppUrl } from '../../../../../common/lib/kibana';
|
||||
import {
|
||||
MANAGEMENT_DEFAULT_PAGE,
|
||||
MANAGEMENT_DEFAULT_PAGE_SIZE,
|
||||
} from '../../../../common/constants';
|
||||
import { getHostIsolationExceptionsListPath } from '../../../../common/routing';
|
||||
import { useFetchHostIsolationExceptionsList } from '../../../host_isolation_exceptions/view/hooks';
|
||||
import { getCurrentArtifactsLocation } from '../../store/policy_details/selectors';
|
||||
import { usePolicyDetailsSelector } from '../policy_hooks';
|
||||
import { PolicyHostIsolationExceptionsEmptyUnexisting } from './components/empty_unexisting';
|
||||
import { PolicyHostIsolationExceptionsEmptyUnassigned } from './components/empty_unassigned';
|
||||
import { PolicyHostIsolationExceptionsList } from './components/list';
|
||||
|
||||
export const PolicyHostIsolationExceptionsTab = ({ policy }: { policy: PolicyData }) => {
|
||||
const { getAppUrl } = useAppUrl();
|
||||
|
||||
const policyId = policy.id;
|
||||
|
||||
const location = usePolicyDetailsSelector(getCurrentArtifactsLocation);
|
||||
const toHostIsolationList = getAppUrl({
|
||||
appId: APP_UI_ID,
|
||||
path: getHostIsolationExceptionsListPath(),
|
||||
});
|
||||
|
||||
const allPolicyExceptionsListRequest = useFetchHostIsolationExceptionsList({
|
||||
page: MANAGEMENT_DEFAULT_PAGE,
|
||||
perPage: MANAGEMENT_DEFAULT_PAGE_SIZE,
|
||||
policies: [policyId, 'all'],
|
||||
});
|
||||
|
||||
const policySearchedExceptionsListRequest = useFetchHostIsolationExceptionsList({
|
||||
filter: location.filter,
|
||||
page: location.page_index,
|
||||
perPage: location.page_size,
|
||||
policies: [policyId, 'all'],
|
||||
});
|
||||
|
||||
const allExceptionsListRequest = useFetchHostIsolationExceptionsList({
|
||||
page: MANAGEMENT_DEFAULT_PAGE,
|
||||
perPage: MANAGEMENT_DEFAULT_PAGE_SIZE,
|
||||
// only do this request if no assigned policies found
|
||||
enabled: allPolicyExceptionsListRequest.data?.total === 0,
|
||||
});
|
||||
|
||||
const hasNoAssignedOrExistingExceptions = allPolicyExceptionsListRequest.data?.total === 0;
|
||||
const hasNoExistingExceptions = allExceptionsListRequest.data?.total === 0;
|
||||
|
||||
const subTitle = useMemo(() => {
|
||||
const link = (
|
||||
<EuiLink href={getAppUrl({ appId: APP_UI_ID, path: toHostIsolationList })} target="_blank">
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policy.hostIsolationExceptions.list.viewAllLinkLabel"
|
||||
defaultMessage="view all host isolation exceptions"
|
||||
/>
|
||||
</EuiLink>
|
||||
);
|
||||
|
||||
return policySearchedExceptionsListRequest.data ? (
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policy.hostIsolationExceptions.list.about"
|
||||
defaultMessage="There {count, plural, one {is} other {are}} {count} {count, plural, =1 {exception} other {exceptions}} associated with this policy. Click here to {link}"
|
||||
values={{
|
||||
count: allPolicyExceptionsListRequest.data?.total,
|
||||
link,
|
||||
}}
|
||||
/>
|
||||
) : null;
|
||||
}, [
|
||||
allPolicyExceptionsListRequest.data?.total,
|
||||
getAppUrl,
|
||||
policySearchedExceptionsListRequest.data,
|
||||
toHostIsolationList,
|
||||
]);
|
||||
|
||||
const isLoading =
|
||||
policySearchedExceptionsListRequest.isLoading ||
|
||||
allPolicyExceptionsListRequest.isLoading ||
|
||||
allExceptionsListRequest.isLoading ||
|
||||
!policy;
|
||||
|
||||
// render non-existent or non-assigned messages
|
||||
if (!isLoading && (hasNoAssignedOrExistingExceptions || hasNoExistingExceptions)) {
|
||||
if (hasNoExistingExceptions) {
|
||||
return (
|
||||
<PolicyHostIsolationExceptionsEmptyUnexisting toHostIsolationList={toHostIsolationList} />
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<PolicyHostIsolationExceptionsEmptyUnassigned
|
||||
policyName={policy.name}
|
||||
toHostIsolationList={toHostIsolationList}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// render header and list
|
||||
return !isLoading && policySearchedExceptionsListRequest.data ? (
|
||||
<div data-test-subj={'policyHostIsolationExceptionsTab'}>
|
||||
<EuiPageHeader alignItems="center">
|
||||
<EuiPageHeaderSection>
|
||||
<EuiTitle size="m">
|
||||
<h2>
|
||||
{i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.hostIsolationExceptions.list.title',
|
||||
{
|
||||
defaultMessage: 'Assigned host isolation exceptions',
|
||||
}
|
||||
)}
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
<EuiText size="xs" data-test-subj="policyHostIsolationExceptionsTabSubtitle">
|
||||
<p>{subTitle}</p>
|
||||
</EuiText>
|
||||
</EuiPageHeaderSection>
|
||||
</EuiPageHeader>
|
||||
|
||||
<EuiSpacer size="l" />
|
||||
<EuiPageContent
|
||||
hasBorder={false}
|
||||
hasShadow={false}
|
||||
paddingSize="none"
|
||||
color="transparent"
|
||||
borderRadius="none"
|
||||
>
|
||||
<PolicyHostIsolationExceptionsList
|
||||
exceptions={policySearchedExceptionsListRequest.data}
|
||||
policyId={policyId}
|
||||
/>
|
||||
</EuiPageContent>
|
||||
</div>
|
||||
) : (
|
||||
<EuiProgress
|
||||
size="xs"
|
||||
color="primary"
|
||||
data-test-subj="policyHostIsolationExceptionsTabLoading"
|
||||
/>
|
||||
);
|
||||
};
|
||||
PolicyHostIsolationExceptionsTab.displayName = 'PolicyHostIsolationExceptionsTab';
|
|
@ -127,6 +127,7 @@ describe('Policy Details', () => {
|
|||
expect(pageTitle).toHaveLength(1);
|
||||
expect(pageTitle.text()).toEqual(policyPackagePolicy.name);
|
||||
});
|
||||
|
||||
it('should navigate to list if back to link is clicked', async () => {
|
||||
policyView.update();
|
||||
|
||||
|
@ -135,6 +136,7 @@ describe('Policy Details', () => {
|
|||
backToListLink.simulate('click', { button: 0 });
|
||||
expect(history.location.pathname).toEqual(endpointListPath);
|
||||
});
|
||||
|
||||
it('should display agent stats', async () => {
|
||||
await asyncActions;
|
||||
policyView.update();
|
||||
|
@ -143,6 +145,7 @@ describe('Policy Details', () => {
|
|||
expect(agentsSummary).toHaveLength(1);
|
||||
expect(agentsSummary.text()).toBe('Total agents5Healthy3Unhealthy1Offline1');
|
||||
});
|
||||
|
||||
it('should display event filters tab', async () => {
|
||||
await asyncActions;
|
||||
policyView.update();
|
||||
|
@ -151,5 +154,11 @@ describe('Policy Details', () => {
|
|||
expect(eventFiltersTab).toHaveLength(1);
|
||||
expect(eventFiltersTab.text()).toBe('Event filters');
|
||||
});
|
||||
|
||||
it('should display the host isolation exceptions tab', async () => {
|
||||
await asyncActions;
|
||||
policyView.update();
|
||||
expect(policyView.find('#hostIsolationExceptions')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,34 +5,37 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiSpacer, EuiTabbedContent, EuiTabbedContentTab } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiTabbedContent, EuiSpacer, EuiTabbedContentTab } from '@elastic/eui';
|
||||
|
||||
import { usePolicyDetailsSelector } from '../policy_hooks';
|
||||
import {
|
||||
isOnPolicyFormView,
|
||||
isOnPolicyTrustedAppsView,
|
||||
isOnPolicyEventFiltersView,
|
||||
policyIdFromParams,
|
||||
policyDetails,
|
||||
} from '../../store/policy_details/selectors';
|
||||
|
||||
import { PolicyTrustedAppsLayout } from '../trusted_apps/layout';
|
||||
import { PolicyEventFiltersLayout } from '../event_filters/layout';
|
||||
import { PolicyFormLayout } from '../policy_forms/components';
|
||||
import { PolicyData } from '../../../../../../common/endpoint/types';
|
||||
import {
|
||||
getPolicyDetailPath,
|
||||
getPolicyTrustedAppsPath,
|
||||
getPolicyEventFiltersPath,
|
||||
getPolicyHostIsolationExceptionsPath,
|
||||
getPolicyTrustedAppsPath,
|
||||
} from '../../../../common/routing';
|
||||
import {
|
||||
isOnHostIsolationExceptionsView,
|
||||
isOnPolicyEventFiltersView,
|
||||
isOnPolicyFormView,
|
||||
isOnPolicyTrustedAppsView,
|
||||
policyDetails,
|
||||
policyIdFromParams,
|
||||
} from '../../store/policy_details/selectors';
|
||||
import { PolicyEventFiltersLayout } from '../event_filters/layout';
|
||||
import { PolicyHostIsolationExceptionsTab } from '../host_isolation_exceptions/host_isolation_exceptions_tab';
|
||||
import { PolicyFormLayout } from '../policy_forms/components';
|
||||
import { usePolicyDetailsSelector } from '../policy_hooks';
|
||||
import { PolicyTrustedAppsLayout } from '../trusted_apps/layout';
|
||||
|
||||
export const PolicyTabs = React.memo(() => {
|
||||
const history = useHistory();
|
||||
const isInSettingsTab = usePolicyDetailsSelector(isOnPolicyFormView);
|
||||
const isInTrustedAppsTab = usePolicyDetailsSelector(isOnPolicyTrustedAppsView);
|
||||
const isInEventFilters = usePolicyDetailsSelector(isOnPolicyEventFiltersView);
|
||||
const isInHostIsolationExceptionsTab = usePolicyDetailsSelector(isOnHostIsolationExceptionsView);
|
||||
const policyId = usePolicyDetailsSelector(policyIdFromParams);
|
||||
const policyItem = usePolicyDetailsSelector(policyDetails);
|
||||
|
||||
|
@ -74,6 +77,21 @@ export const PolicyTabs = React.memo(() => {
|
|||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'hostIsolationExceptions',
|
||||
name: i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.details.tabs.isInHostIsolationExceptions',
|
||||
{
|
||||
defaultMessage: 'Host isolation exceptions',
|
||||
}
|
||||
),
|
||||
content: (
|
||||
<>
|
||||
<EuiSpacer />
|
||||
<PolicyHostIsolationExceptionsTab policy={policyItem as PolicyData} />
|
||||
</>
|
||||
),
|
||||
},
|
||||
],
|
||||
[policyItem]
|
||||
);
|
||||
|
@ -87,19 +105,30 @@ export const PolicyTabs = React.memo(() => {
|
|||
initialTab = tabs[1];
|
||||
} else if (isInEventFilters) {
|
||||
initialTab = tabs[2];
|
||||
} else if (isInHostIsolationExceptionsTab) {
|
||||
initialTab = tabs[3];
|
||||
}
|
||||
|
||||
return initialTab;
|
||||
}, [isInSettingsTab, isInTrustedAppsTab, isInEventFilters, tabs]);
|
||||
}, [isInSettingsTab, isInTrustedAppsTab, isInEventFilters, isInHostIsolationExceptionsTab, tabs]);
|
||||
|
||||
const onTabClickHandler = useCallback(
|
||||
(selectedTab: EuiTabbedContentTab) => {
|
||||
const path =
|
||||
selectedTab.id === 'settings'
|
||||
? getPolicyDetailPath(policyId)
|
||||
: selectedTab.id === 'trustedApps'
|
||||
? getPolicyTrustedAppsPath(policyId)
|
||||
: getPolicyEventFiltersPath(policyId);
|
||||
let path: string = '';
|
||||
switch (selectedTab.id) {
|
||||
case 'settings':
|
||||
path = getPolicyDetailPath(policyId);
|
||||
break;
|
||||
case 'trustedApps':
|
||||
path = getPolicyTrustedAppsPath(policyId);
|
||||
break;
|
||||
case 'hostIsolationExceptions':
|
||||
path = getPolicyHostIsolationExceptionsPath(policyId);
|
||||
break;
|
||||
case 'eventFilters':
|
||||
path = getPolicyEventFiltersPath(policyId);
|
||||
break;
|
||||
}
|
||||
history.push(path);
|
||||
},
|
||||
[history, policyId]
|
||||
|
|
|
@ -13,8 +13,8 @@ import { sendGetEndpointSpecificPackagePolicies } from './policies';
|
|||
export function useGetEndpointSpecificPolicies({
|
||||
onError,
|
||||
}: {
|
||||
onError: (error: ServerApiError) => void;
|
||||
}): QueryObserverResult<GetPolicyListResponse> {
|
||||
onError?: (error: ServerApiError) => void;
|
||||
} = {}): QueryObserverResult<GetPolicyListResponse> {
|
||||
const http = useHttp();
|
||||
return useQuery<GetPolicyListResponse, ServerApiError>(
|
||||
['endpointSpecificPolicies'],
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue