mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Security Solution] Display warning banner when removing a host isolation exception (#121729)
This commit is contained in:
parent
ea24d162d6
commit
9ae1ffe092
8 changed files with 114 additions and 58 deletions
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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, EuiButtonProps, PropsForButton } from '@elastic/eui';
|
||||
import React, { FC, memo, useEffect, useRef } from 'react';
|
||||
|
||||
export const AutoFocusButton: FC<PropsForButton<EuiButtonProps>> = memo((props) => {
|
||||
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||
const button = <EuiButton buttonRef={buttonRef} {...props} />;
|
||||
|
||||
useEffect(() => {
|
||||
if (buttonRef.current) {
|
||||
buttonRef.current.focus();
|
||||
}
|
||||
}, []);
|
||||
|
||||
return button;
|
||||
});
|
||||
|
||||
AutoFocusButton.displayName = 'AutoFocusButton';
|
|
@ -73,3 +73,12 @@ export function getEffectedPolicySelectionByTags(
|
|||
export function isGlobalPolicyEffected(tags?: string[]): boolean {
|
||||
return tags !== undefined && tags.find((tag) => tag === GLOBAL_POLICY_TAG) !== undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an array of an artifact tags, return the ids of policies inside
|
||||
* those tags. It will only return tags starting with `policy:` and it will
|
||||
* return them without the suffix
|
||||
*/
|
||||
export function getArtifactPoliciesIdByTag(tags: string[] = []): string[] {
|
||||
return tags.filter((tag) => tag.startsWith('policy:')).map((tag) => tag.substring(7));
|
||||
}
|
||||
|
|
|
@ -5,9 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { memo, useCallback, useEffect } from 'react';
|
||||
import {
|
||||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
EuiCallOut,
|
||||
EuiModal,
|
||||
|
@ -18,20 +16,25 @@ import {
|
|||
EuiSpacer,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import React, { memo, useCallback, useEffect } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { Dispatch } from 'redux';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useEventFiltersSelector } from '../hooks';
|
||||
import { AutoFocusButton } from '../../../../../common/components/autofocus_button/autofocus_button';
|
||||
import { useToasts } from '../../../../../common/lib/kibana';
|
||||
import { AppAction } from '../../../../../common/store/actions';
|
||||
import {
|
||||
getArtifactPoliciesIdByTag,
|
||||
isGlobalPolicyEffected,
|
||||
} from '../../../../components/effected_policy_select/utils';
|
||||
import {
|
||||
getDeleteError,
|
||||
getItemToDelete,
|
||||
isDeletionInProgress,
|
||||
wasDeletionSuccessful,
|
||||
} from '../../store/selector';
|
||||
import { AppAction } from '../../../../../common/store/actions';
|
||||
import { useToasts } from '../../../../../common/lib/kibana';
|
||||
import { isGlobalPolicyEffected } from '../../../../components/effected_policy_select/utils';
|
||||
import { useEventFiltersSelector } from '../hooks';
|
||||
|
||||
export const EventFilterDeleteModal = memo<{}>(() => {
|
||||
const dispatch = useDispatch<Dispatch<AppAction>>();
|
||||
|
@ -109,7 +112,7 @@ export const EventFilterDeleteModal = memo<{}>(() => {
|
|||
values={{
|
||||
count: isGlobalPolicyEffected(Array.from(eventFilter?.tags || []))
|
||||
? 'all'
|
||||
: eventFilter?.tags?.length || 0,
|
||||
: getArtifactPoliciesIdByTag(eventFilter?.tags as string[]).length,
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
|
@ -136,7 +139,7 @@ export const EventFilterDeleteModal = memo<{}>(() => {
|
|||
/>
|
||||
</EuiButtonEmpty>
|
||||
|
||||
<EuiButton
|
||||
<AutoFocusButton
|
||||
fill
|
||||
color="danger"
|
||||
onClick={onConfirm}
|
||||
|
@ -147,7 +150,7 @@ export const EventFilterDeleteModal = memo<{}>(() => {
|
|||
id="xpack.securitySolution.eventFilters.deletionDialog.confirmButton"
|
||||
defaultMessage="Delete"
|
||||
/>
|
||||
</EuiButton>
|
||||
</AutoFocusButton>
|
||||
</EuiModalFooter>
|
||||
</EuiModal>
|
||||
);
|
||||
|
|
|
@ -4,16 +4,18 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
|
||||
import { waitFor } from '@testing-library/dom';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
import uuid from 'uuid';
|
||||
import { getExceptionListItemSchemaMock } from '../../../../../../../lists/common/schemas/response/exception_list_item_schema.mock';
|
||||
import {
|
||||
AppContextTestRender,
|
||||
createAppRootMockRenderer,
|
||||
} from '../../../../../common/mock/endpoint';
|
||||
import { HostIsolationExceptionDeleteModal } from './delete_modal';
|
||||
import { deleteOneHostIsolationExceptionItem } from '../../service';
|
||||
import { getExceptionListItemSchemaMock } from '../../../../../../../lists/common/schemas/response/exception_list_item_schema.mock';
|
||||
import { waitFor } from '@testing-library/dom';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { HostIsolationExceptionDeleteModal } from './delete_modal';
|
||||
|
||||
jest.mock('../../service');
|
||||
const deleteOneHostIsolationExceptionItemMock = deleteOneHostIsolationExceptionItem as jest.Mock;
|
||||
|
@ -23,10 +25,11 @@ describe('When on the host isolation exceptions delete modal', () => {
|
|||
let renderResult: ReturnType<typeof render>;
|
||||
let coreStart: AppContextTestRender['coreStart'];
|
||||
let onCancel: (forceRefresh?: boolean) => void;
|
||||
let itemToDelete: ExceptionListItemSchema;
|
||||
|
||||
beforeEach(() => {
|
||||
const mockedContext = createAppRootMockRenderer();
|
||||
const itemToDelete = getExceptionListItemSchemaMock();
|
||||
itemToDelete = getExceptionListItemSchemaMock();
|
||||
deleteOneHostIsolationExceptionItemMock.mockReset();
|
||||
onCancel = jest.fn();
|
||||
render = () =>
|
||||
|
@ -44,6 +47,24 @@ describe('When on the host isolation exceptions delete modal', () => {
|
|||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it.each(['all', '0', '1', '2', '5'])(
|
||||
'should display a warning banner with how many policies (%s) will be affected. skipping non-policy-tags',
|
||||
(amount) => {
|
||||
if (amount === 'all') {
|
||||
itemToDelete.tags = ['policy:all', 'non-policy-tag'];
|
||||
} else {
|
||||
itemToDelete.tags = [
|
||||
...Array.from({ length: +amount }, (_) => `policy:${uuid.v4()}`),
|
||||
'non-policy-tag',
|
||||
];
|
||||
}
|
||||
render();
|
||||
expect(
|
||||
renderResult.getByTestId('hostIsolationExceptionsDeleteModalCalloutMessage')
|
||||
).toHaveTextContent(`Deleting this entry will remove it from ${amount} associated`);
|
||||
}
|
||||
);
|
||||
|
||||
it('should disable the buttons when confirm is pressed and show loading', async () => {
|
||||
render();
|
||||
|
||||
|
|
|
@ -5,10 +5,9 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import {
|
||||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
EuiCallOut,
|
||||
EuiModal,
|
||||
EuiModalBody,
|
||||
EuiModalFooter,
|
||||
|
@ -16,11 +15,17 @@ import {
|
|||
EuiModalHeaderTitle,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
|
||||
import React, { memo } from 'react';
|
||||
import { useMutation } from 'react-query';
|
||||
import { AutoFocusButton } from '../../../../../common/components/autofocus_button/autofocus_button';
|
||||
import { useHttp, useToasts } from '../../../../../common/lib/kibana';
|
||||
import {
|
||||
getArtifactPoliciesIdByTag,
|
||||
isGlobalPolicyEffected,
|
||||
} from '../../../../components/effected_policy_select/utils';
|
||||
import { deleteOneHostIsolationExceptionItem } from '../../service';
|
||||
|
||||
export const HostIsolationExceptionDeleteModal = memo(
|
||||
|
@ -89,13 +94,29 @@ export const HostIsolationExceptionDeleteModal = memo(
|
|||
|
||||
<EuiModalBody data-test-subj="hostIsolationExceptionsFilterDeleteModalBody">
|
||||
<EuiText>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.hostIsolationExceptions.deletionDialog.subtitle"
|
||||
defaultMessage='You are deleting exception "{name}".'
|
||||
values={{ name: <b className="eui-textBreakWord">{item?.name}</b> }}
|
||||
/>
|
||||
</p>
|
||||
<EuiCallOut
|
||||
data-test-subj="hostIsolationExceptionsDeleteModalCallout"
|
||||
title={i18n.translate(
|
||||
'xpack.securitySolution.hostIsolationExceptions.deletionDialog.calloutTitle',
|
||||
{
|
||||
defaultMessage: 'Warning',
|
||||
}
|
||||
)}
|
||||
color="danger"
|
||||
iconType="alert"
|
||||
>
|
||||
<p data-test-subj="hostIsolationExceptionsDeleteModalCalloutMessage">
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.hostIsolationExceptions.deletionDialog.calloutMessage"
|
||||
defaultMessage="Deleting this entry will remove it from {count} associated {count, plural, one {policy} other {policies}}."
|
||||
values={{
|
||||
count: isGlobalPolicyEffected(Array.from(item.tags || []))
|
||||
? 'all'
|
||||
: getArtifactPoliciesIdByTag(item.tags).length,
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</EuiCallOut>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.hostIsolationExceptions.deletionDialog.confirmation"
|
||||
|
@ -117,7 +138,7 @@ export const HostIsolationExceptionDeleteModal = memo(
|
|||
/>
|
||||
</EuiButtonEmpty>
|
||||
|
||||
<EuiButton
|
||||
<AutoFocusButton
|
||||
fill
|
||||
color="danger"
|
||||
onClick={handleConfirmButton}
|
||||
|
@ -126,9 +147,9 @@ export const HostIsolationExceptionDeleteModal = memo(
|
|||
>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.hostIsolationExceptions.deletionDialog.confirmButton"
|
||||
defaultMessage="Remove exception"
|
||||
defaultMessage="Delete"
|
||||
/>
|
||||
</EuiButton>
|
||||
</AutoFocusButton>
|
||||
</EuiModalFooter>
|
||||
</EuiModal>
|
||||
);
|
||||
|
|
|
@ -5,34 +5,31 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { FC, memo, useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { Dispatch } from 'redux';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import {
|
||||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
EuiButtonProps,
|
||||
PropsForButton,
|
||||
EuiCallOut,
|
||||
EuiSpacer,
|
||||
EuiModal,
|
||||
EuiModalBody,
|
||||
EuiModalFooter,
|
||||
EuiModalHeader,
|
||||
EuiModalHeaderTitle,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import React, { memo, useCallback, useMemo } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { Dispatch } from 'redux';
|
||||
import { AutoFocusButton } from '../../../../common/components/autofocus_button/autofocus_button';
|
||||
import { Immutable, TrustedApp } from '../../../../../common/endpoint/types';
|
||||
import { AppAction } from '../../../../common/store/actions';
|
||||
import { useTrustedAppsSelector } from './hooks';
|
||||
import { isPolicyEffectScope } from '../state/type_guards';
|
||||
import {
|
||||
getDeletionDialogEntry,
|
||||
isDeletionDialogOpen,
|
||||
isDeletionInProgress,
|
||||
} from '../store/selectors';
|
||||
import { isPolicyEffectScope } from '../state/type_guards';
|
||||
import { useTrustedAppsSelector } from './hooks';
|
||||
|
||||
const CANCEL_SUBJ = 'trustedAppDeletionCancel';
|
||||
const CONFIRM_SUBJ = 'trustedAppDeletionConfirm';
|
||||
|
@ -83,21 +80,6 @@ const getTranslations = (entry: Immutable<TrustedApp> | undefined) => ({
|
|||
),
|
||||
});
|
||||
|
||||
const AutoFocusButton: FC<PropsForButton<EuiButtonProps>> = memo((props) => {
|
||||
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||
const button = <EuiButton buttonRef={buttonRef} {...props} />;
|
||||
|
||||
useEffect(() => {
|
||||
if (buttonRef.current) {
|
||||
buttonRef.current.focus();
|
||||
}
|
||||
}, []);
|
||||
|
||||
return button;
|
||||
});
|
||||
|
||||
AutoFocusButton.displayName = 'AutoFocusButton';
|
||||
|
||||
export const TrustedAppDeletionDialog = memo(() => {
|
||||
const dispatch = useDispatch<Dispatch<AppAction>>();
|
||||
const isBusy = useTrustedAppsSelector(isDeletionInProgress);
|
||||
|
|
|
@ -23007,10 +23007,8 @@
|
|||
"xpack.securitySolution.hostIsolation.agentStatuses.empty": "-",
|
||||
"xpack.securitySolution.hostIsolationExceptions.deletionDialog.cancel": "キャンセル",
|
||||
"xpack.securitySolution.hostIsolationExceptions.deletionDialog.confirmation": "この操作は元に戻すことができません。続行していいですか?",
|
||||
"xpack.securitySolution.hostIsolationExceptions.deletionDialog.confirmButton": "例外を削除",
|
||||
"xpack.securitySolution.hostIsolationExceptions.deletionDialog.deleteFailure": "ホスト分離例外リストから\"{name}\"を削除できません。理由:{message}",
|
||||
"xpack.securitySolution.hostIsolationExceptions.deletionDialog.deleteSuccess": "\"{name}\"はホスト分離例外リストから削除されました。",
|
||||
"xpack.securitySolution.hostIsolationExceptions.deletionDialog.subtitle": "例外\"{name}\"を削除しています。",
|
||||
"xpack.securitySolution.hostIsolationExceptions.deletionDialog.title": "ホスト分離例外を削除",
|
||||
"xpack.securitySolution.hostIsolationExceptions.flyout.cancel": "キャンセル",
|
||||
"xpack.securitySolution.hostIsolationExceptions.flyout.createButton": "ホスト分離例外を追加",
|
||||
|
|
|
@ -23374,10 +23374,8 @@
|
|||
"xpack.securitySolution.hostIsolation.agentStatuses.empty": "-",
|
||||
"xpack.securitySolution.hostIsolationExceptions.deletionDialog.cancel": "取消",
|
||||
"xpack.securitySolution.hostIsolationExceptions.deletionDialog.confirmation": "此操作无法撤消。是否确定要继续?",
|
||||
"xpack.securitySolution.hostIsolationExceptions.deletionDialog.confirmButton": "移除例外",
|
||||
"xpack.securitySolution.hostIsolationExceptions.deletionDialog.deleteFailure": "无法从主机隔离例外列表中移除“{name}”。原因:{message}",
|
||||
"xpack.securitySolution.hostIsolationExceptions.deletionDialog.deleteSuccess": "已从主机隔离例外列表中移除“{name}”。",
|
||||
"xpack.securitySolution.hostIsolationExceptions.deletionDialog.subtitle": "正在删除例外“{name}”。",
|
||||
"xpack.securitySolution.hostIsolationExceptions.deletionDialog.title": "删除主机隔离例外",
|
||||
"xpack.securitySolution.hostIsolationExceptions.flyout.cancel": "取消",
|
||||
"xpack.securitySolution.hostIsolationExceptions.flyout.createButton": "添加主机隔离例外",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue