[Security Solution][Endpoint] Refactor Host Isolation component used in Isolate use case (#100159)
* EndpointHostIsolateForm component * Refactor Detections Host isolation flyout to use isolateform
This commit is contained in:
parent
8d85d72fef
commit
532a33b051
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export * from './isolate_success';
|
||||
export * from './isolate_form';
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* 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, { ChangeEventHandler, memo, ReactNode, useCallback } from 'react';
|
||||
import {
|
||||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
EuiTextArea,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { CANCEL, COMMENT, COMMENT_PLACEHOLDER, CONFIRM } from './translations';
|
||||
|
||||
export interface EndpointIsolatedFormProps {
|
||||
hostName: string;
|
||||
onCancel: () => void;
|
||||
onConfirm: () => void;
|
||||
onChange: (changes: { comment: string }) => void;
|
||||
comment?: string;
|
||||
/** Any additional message to be appended to the default one */
|
||||
messageAppend?: ReactNode;
|
||||
/** If true, then `Confirm` and `Cancel` buttons will be disabled, and `Confirm` button will loading loading style */
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
export const EndpointIsolateForm = memo<EndpointIsolatedFormProps>(
|
||||
({ hostName, onCancel, onConfirm, onChange, comment = '', messageAppend, isLoading = false }) => {
|
||||
const handleCommentChange: ChangeEventHandler<HTMLTextAreaElement> = useCallback(
|
||||
(event) => {
|
||||
onChange({ comment: event.target.value });
|
||||
},
|
||||
[onChange]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiText size="s">
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.hostIsolation.isolateThisHost"
|
||||
defaultMessage="Isolate host {hostName} from network."
|
||||
values={{ hostName: <b>{hostName}</b> }}
|
||||
/>{' '}
|
||||
{messageAppend}
|
||||
</p>
|
||||
</EuiText>
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
<EuiTitle size="xs">
|
||||
<h4>{COMMENT}</h4>
|
||||
</EuiTitle>
|
||||
<EuiTextArea
|
||||
data-test-subj="host_isolation_comment"
|
||||
fullWidth
|
||||
placeholder={COMMENT_PLACEHOLDER}
|
||||
value={comment}
|
||||
onChange={handleCommentChange}
|
||||
/>
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
<EuiFlexGroup justifyContent="flexEnd">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty onClick={onCancel} disabled={isLoading}>
|
||||
{CANCEL}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton fill onClick={onConfirm} disabled={isLoading} isLoading={isLoading}>
|
||||
{CONFIRM}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
EndpointIsolateForm.displayName = 'EndpointIsolateForm';
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* 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, { memo, ReactNode } from 'react';
|
||||
import { EuiButtonEmpty, EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
|
||||
import { GET_SUCCESS_MESSAGE } from './translations';
|
||||
|
||||
export interface EndpointIsolateSuccessProps {
|
||||
hostName: string;
|
||||
completeButtonLabel: string;
|
||||
onComplete: () => void;
|
||||
additionalInfo?: ReactNode;
|
||||
}
|
||||
|
||||
export const EndpointIsolateSuccess = memo<EndpointIsolateSuccessProps>(
|
||||
({ hostName, onComplete, completeButtonLabel, additionalInfo }) => {
|
||||
return (
|
||||
<>
|
||||
<EuiCallOut iconType="check" color="success" title={GET_SUCCESS_MESSAGE(hostName)}>
|
||||
{additionalInfo}
|
||||
</EuiCallOut>
|
||||
|
||||
<EuiFlexGroup gutterSize="none" justifyContent="flexEnd">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty flush="right" onClick={onComplete}>
|
||||
<EuiText size="s">
|
||||
<p>{completeButtonLabel}</p>
|
||||
</EuiText>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
EndpointIsolateSuccess.displayName = 'EndpointIsolateSuccess';
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
|
||||
export const CANCEL = i18n.translate('xpack.securitySolution.endpoint.hostIsolation.cancel', {
|
||||
defaultMessage: 'Cancel',
|
||||
});
|
||||
|
||||
export const CONFIRM = i18n.translate('xpack.securitySolution.endpoint.hostIsolation.confirm', {
|
||||
defaultMessage: 'Confirm',
|
||||
});
|
||||
|
||||
export const COMMENT = i18n.translate('xpack.securitySolution.endpoint.hostIsolation.comment', {
|
||||
defaultMessage: 'Comment',
|
||||
});
|
||||
|
||||
export const COMMENT_PLACEHOLDER = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.hostIsolation.comment.placeholder',
|
||||
{ defaultMessage: 'You may leave an optional note here.' }
|
||||
);
|
||||
|
||||
export const GET_SUCCESS_MESSAGE = (hostName: string) =>
|
||||
i18n.translate('xpack.securitySolution.endpoint.hostIsolation.successfulMessage', {
|
||||
defaultMessage: 'Host Isolation on {hostName} successfully submitted',
|
||||
values: { hostName },
|
||||
});
|
|
@ -7,32 +7,19 @@
|
|||
|
||||
import React, { useMemo, useState, useCallback } from 'react';
|
||||
import { find } from 'lodash/fp';
|
||||
import {
|
||||
EuiCallOut,
|
||||
EuiTitle,
|
||||
EuiText,
|
||||
EuiTextArea,
|
||||
EuiSpacer,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiText, EuiSpacer } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { useHostIsolation } from '../../containers/detection_engine/alerts/use_host_isolation';
|
||||
import {
|
||||
CANCEL,
|
||||
CASES_ASSOCIATED_WITH_ALERT,
|
||||
COMMENT,
|
||||
COMMENT_PLACEHOLDER,
|
||||
CONFIRM,
|
||||
RETURN_TO_ALERT_DETAILS,
|
||||
} from './translations';
|
||||
import { CASES_ASSOCIATED_WITH_ALERT, RETURN_TO_ALERT_DETAILS } from './translations';
|
||||
import { Maybe } from '../../../../../observability/common/typings';
|
||||
import { useCasesFromAlerts } from '../../containers/detection_engine/alerts/use_cases_from_alerts';
|
||||
import { CaseDetailsLink } from '../../../common/components/links';
|
||||
import { TimelineEventsDetailsItem } from '../../../../common/search_strategy';
|
||||
import {
|
||||
EndpointIsolatedFormProps,
|
||||
EndpointIsolateForm,
|
||||
EndpointIsolateSuccess,
|
||||
} from '../../../common/components/endpoint/host_isolation';
|
||||
|
||||
export const HostIsolationPanel = React.memo(
|
||||
({
|
||||
|
@ -76,6 +63,11 @@ export const HostIsolationPanel = React.memo(
|
|||
|
||||
const backToAlertDetails = useCallback(() => cancelCallback(), [cancelCallback]);
|
||||
|
||||
const handleIsolateFormChange: EndpointIsolatedFormProps['onChange'] = useCallback(
|
||||
({ comment: newComment }) => setComment(newComment),
|
||||
[]
|
||||
);
|
||||
|
||||
const casesList = useMemo(
|
||||
() =>
|
||||
caseIds.map((id, index) => {
|
||||
|
@ -100,18 +92,12 @@ export const HostIsolationPanel = React.memo(
|
|||
return (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiCallOut
|
||||
iconType="check"
|
||||
color="success"
|
||||
title={i18n.translate(
|
||||
'xpack.securitySolution.endpoint.hostIsolation.successfulIsolation.title',
|
||||
{
|
||||
defaultMessage: 'Host Isolation on {hostname} successfully submitted',
|
||||
values: { hostname: hostName },
|
||||
}
|
||||
)}
|
||||
>
|
||||
{caseCount > 0 && (
|
||||
<EndpointIsolateSuccess
|
||||
hostName={hostName}
|
||||
completeButtonLabel={RETURN_TO_ALERT_DETAILS}
|
||||
onComplete={backToAlertDetails}
|
||||
additionalInfo={
|
||||
caseCount > 0 && (
|
||||
<>
|
||||
<EuiText size="s">
|
||||
<p>
|
||||
|
@ -126,17 +112,9 @@ export const HostIsolationPanel = React.memo(
|
|||
<ul>{casesList}</ul>
|
||||
</EuiText>
|
||||
</>
|
||||
)}
|
||||
</EuiCallOut>
|
||||
<EuiFlexGroup gutterSize="none" justifyContent="flexEnd">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty flush="right" onClick={backToAlertDetails}>
|
||||
<EuiText size="s">
|
||||
<p>{RETURN_TO_ALERT_DETAILS}</p>
|
||||
</EuiText>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}, [backToAlertDetails, hostName, caseCount, casesList]);
|
||||
|
@ -145,13 +123,18 @@ export const HostIsolationPanel = React.memo(
|
|||
return (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiText size="s">
|
||||
<p>
|
||||
<EndpointIsolateForm
|
||||
hostName={hostName}
|
||||
onCancel={backToAlertDetails}
|
||||
onConfirm={confirmHostIsolation}
|
||||
onChange={handleIsolateFormChange}
|
||||
comment={comment}
|
||||
isLoading={loading}
|
||||
messageAppend={
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.hostIsolation.isolateThisHost"
|
||||
defaultMessage="Isolate host {hostname} from network. This action will be added to the {cases}."
|
||||
id="xpack.securitySolution.detections.hostIsolation.impactedCases"
|
||||
defaultMessage="This action will be added to the {cases}."
|
||||
values={{
|
||||
hostname: <b>{hostName}</b>,
|
||||
cases: (
|
||||
<b>
|
||||
{caseCount}
|
||||
|
@ -161,42 +144,19 @@ export const HostIsolationPanel = React.memo(
|
|||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiTitle size="xs">
|
||||
<h4>{COMMENT}</h4>
|
||||
</EuiTitle>
|
||||
<EuiTextArea
|
||||
data-test-subj="host_isolation_comment"
|
||||
fullWidth={true}
|
||||
placeholder={COMMENT_PLACEHOLDER}
|
||||
value={comment}
|
||||
onChange={(event: React.ChangeEvent<HTMLTextAreaElement>) =>
|
||||
setComment(event.target.value)
|
||||
}
|
||||
/>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFlexGroup justifyContent="flexEnd">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty onClick={backToAlertDetails}>{CANCEL}</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton fill onClick={confirmHostIsolation} isLoading={loading}>
|
||||
{CONFIRM}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
);
|
||||
}, [
|
||||
alertRule,
|
||||
backToAlertDetails,
|
||||
comment,
|
||||
confirmHostIsolation,
|
||||
hostName,
|
||||
backToAlertDetails,
|
||||
confirmHostIsolation,
|
||||
handleIsolateFormChange,
|
||||
comment,
|
||||
loading,
|
||||
caseCount,
|
||||
alertRule,
|
||||
]);
|
||||
|
||||
return isIsolated ? hostIsolated : hostNotIsolated;
|
||||
|
|
|
@ -14,23 +14,6 @@ export const ISOLATE_HOST = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const COMMENT = i18n.translate('xpack.securitySolution.endpoint.hostIsolation.comment', {
|
||||
defaultMessage: 'Comment',
|
||||
});
|
||||
|
||||
export const COMMENT_PLACEHOLDER = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.hostIsolation.comment.placeholder',
|
||||
{ defaultMessage: 'You may leave an optional note here.' }
|
||||
);
|
||||
|
||||
export const CANCEL = i18n.translate('xpack.securitySolution.endpoint.hostIsolation.cancel', {
|
||||
defaultMessage: 'Cancel',
|
||||
});
|
||||
|
||||
export const CONFIRM = i18n.translate('xpack.securitySolution.endpoint.hostIsolation.confirm', {
|
||||
defaultMessage: 'Confirm',
|
||||
});
|
||||
|
||||
export const CASES_ASSOCIATED_WITH_ALERT = (caseCount: number): string =>
|
||||
i18n.translate(
|
||||
'xpack.securitySolution.endpoint.hostIsolation.isolateHost.casesAssociatedWithAlert',
|
||||
|
|
Loading…
Reference in a new issue