parent
51d96e52ec
commit
e9319360e2
|
@ -8,7 +8,6 @@
|
|||
|
||||
import {
|
||||
EuiBadge,
|
||||
EuiHealth,
|
||||
EuiIconTip,
|
||||
EuiLink,
|
||||
EuiTextColor,
|
||||
|
@ -17,7 +16,6 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import * as H from 'history';
|
||||
import React from 'react';
|
||||
import euiLightVars from '@elastic/eui/dist/eui_theme_light.json';
|
||||
import { getEmptyTagValue } from '../../../../components/empty_value';
|
||||
import {
|
||||
deleteRulesAction,
|
||||
|
@ -32,6 +30,7 @@ import { TableData } from '../types';
|
|||
import * as i18n from '../translations';
|
||||
import { PreferenceFormattedDate } from '../../../../components/formatted_date';
|
||||
import { RuleSwitch } from '../components/rule_switch';
|
||||
import { SeverityBadge } from '../components/severity_badge';
|
||||
|
||||
const getActions = (dispatch: React.Dispatch<Action>, history: H.History) => [
|
||||
{
|
||||
|
@ -92,21 +91,7 @@ export const getColumns = (
|
|||
{
|
||||
field: 'severity',
|
||||
name: i18n.COLUMN_SEVERITY,
|
||||
render: (value: TableData['severity']) => (
|
||||
<EuiHealth
|
||||
color={
|
||||
value === 'low'
|
||||
? euiLightVars.euiColorVis0
|
||||
: value === 'medium'
|
||||
? euiLightVars.euiColorVis5
|
||||
: value === 'high'
|
||||
? euiLightVars.euiColorVis7
|
||||
: euiLightVars.euiColorVis9
|
||||
}
|
||||
>
|
||||
{value}
|
||||
</EuiHealth>
|
||||
),
|
||||
render: (value: TableData['severity']) => <SeverityBadge value={value} />,
|
||||
truncateText: true,
|
||||
},
|
||||
{
|
||||
|
|
|
@ -37,6 +37,25 @@ const MyEuiFormRow = styled(EuiFormRow)`
|
|||
}
|
||||
`;
|
||||
|
||||
export const MyAddItemButton = styled(EuiButtonEmpty)`
|
||||
margin-top: 4px;
|
||||
|
||||
&.euiButtonEmpty--xSmall {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.euiIcon {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
`;
|
||||
|
||||
MyAddItemButton.defaultProps = {
|
||||
flush: 'left',
|
||||
iconType: 'plusInCircle',
|
||||
size: 'xs',
|
||||
};
|
||||
|
||||
export const AddItem = ({
|
||||
addText,
|
||||
dataTestSubj,
|
||||
|
@ -160,9 +179,9 @@ export const AddItem = ({
|
|||
);
|
||||
})}
|
||||
|
||||
<EuiButtonEmpty size="xs" onClick={addItem} isDisabled={isDisabled} iconType="plusInCircle">
|
||||
<MyAddItemButton onClick={addItem} isDisabled={isDisabled}>
|
||||
{addText}
|
||||
</EuiButtonEmpty>
|
||||
</MyAddItemButton>
|
||||
</>
|
||||
</MyEuiFormRow>
|
||||
);
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<svg width="8" height="8" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M1 0H0v8h8V7H1V0z" fill="#D3DAE6"/></svg>
|
After Width: | Height: | Size: 163 B |
|
@ -9,12 +9,10 @@ import {
|
|||
EuiLoadingSpinner,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiHealth,
|
||||
EuiLink,
|
||||
EuiText,
|
||||
EuiListGroup,
|
||||
EuiButtonEmpty,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
import euiLightVars from '@elastic/eui/dist/eui_theme_light.json';
|
||||
|
||||
import { isEmpty } from 'lodash/fp';
|
||||
import React from 'react';
|
||||
|
@ -27,6 +25,11 @@ import { tacticsOptions, techniquesOptions } from '../../../mitre/mitre_tactics_
|
|||
import { FilterLabel } from './filter_label';
|
||||
import * as i18n from './translations';
|
||||
import { BuildQueryBarDescription, BuildThreatsDescription, ListItems } from './types';
|
||||
import { SeverityBadge } from '../severity_badge';
|
||||
import ListTreeIcon from './assets/list_tree_icon.svg';
|
||||
|
||||
const isNotEmptyArray = (values: string[]) =>
|
||||
!isEmpty(values) && values.filter(val => !isEmpty(val)).length > 0;
|
||||
|
||||
const EuiBadgeWrap = styled(EuiBadge)`
|
||||
.euiBadge__text {
|
||||
|
@ -97,10 +100,17 @@ const ThreatsEuiFlexGroup = styled(EuiFlexGroup)`
|
|||
}
|
||||
`;
|
||||
|
||||
const MyEuiListGroup = styled(EuiListGroup)`
|
||||
padding: 0px;
|
||||
.euiListGroupItem__button {
|
||||
padding: 0px;
|
||||
const TechniqueLinkItem = styled(EuiButtonEmpty)`
|
||||
.euiIcon {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
`;
|
||||
|
||||
const ReferenceLinkItem = styled(EuiButtonEmpty)`
|
||||
.euiIcon {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
`;
|
||||
|
||||
|
@ -118,28 +128,31 @@ export const buildThreatsDescription = ({
|
|||
const tactic = tacticsOptions.find(t => t.name === threat.tactic.name);
|
||||
return (
|
||||
<EuiFlexItem key={`${threat.tactic.name}-${index}`}>
|
||||
<EuiText grow={false} size="s">
|
||||
<h5>
|
||||
<EuiLink href={threat.tactic.reference} target="_blank">
|
||||
{tactic != null ? tactic.text : ''}
|
||||
</EuiLink>
|
||||
</h5>
|
||||
<MyEuiListGroup
|
||||
flush={false}
|
||||
bordered={false}
|
||||
listItems={threat.techniques.map(technique => {
|
||||
const myTechnique = techniquesOptions.find(t => t.name === technique.name);
|
||||
return {
|
||||
label: myTechnique != null ? myTechnique.label : '',
|
||||
href: technique.reference,
|
||||
target: '_blank',
|
||||
};
|
||||
})}
|
||||
/>
|
||||
</EuiText>
|
||||
<EuiLink href={threat.tactic.reference} target="_blank">
|
||||
{tactic != null ? tactic.text : ''}
|
||||
</EuiLink>
|
||||
<EuiFlexGroup gutterSize="none" alignItems="flexStart" direction="column">
|
||||
{threat.techniques.map(technique => {
|
||||
const myTechnique = techniquesOptions.find(t => t.name === technique.name);
|
||||
return (
|
||||
<EuiFlexItem>
|
||||
<TechniqueLinkItem
|
||||
href={technique.reference}
|
||||
target="_blank"
|
||||
iconType={ListTreeIcon}
|
||||
size="xs"
|
||||
flush="left"
|
||||
>
|
||||
{myTechnique != null ? myTechnique.label : ''}
|
||||
</TechniqueLinkItem>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
})}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
})}
|
||||
<EuiSpacer />
|
||||
</ThreatsEuiFlexGroup>
|
||||
),
|
||||
},
|
||||
|
@ -148,12 +161,34 @@ export const buildThreatsDescription = ({
|
|||
return [];
|
||||
};
|
||||
|
||||
export const buildUnorderedListArrayDescription = (
|
||||
label: string,
|
||||
field: string,
|
||||
values: string[]
|
||||
): ListItems[] => {
|
||||
if (isNotEmptyArray(values)) {
|
||||
return [
|
||||
{
|
||||
title: label,
|
||||
description: (
|
||||
<ul>
|
||||
{values.map((val: string) =>
|
||||
isEmpty(val) ? null : <li key={`${field}-${val}`}>{val}</li>
|
||||
)}
|
||||
</ul>
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
export const buildStringArrayDescription = (
|
||||
label: string,
|
||||
field: string,
|
||||
values: string[]
|
||||
): ListItems[] => {
|
||||
if (!isEmpty(values) && values.filter(val => !isEmpty(val)).length > 0) {
|
||||
if (isNotEmptyArray(values)) {
|
||||
return [
|
||||
{
|
||||
title: label,
|
||||
|
@ -174,46 +209,34 @@ export const buildStringArrayDescription = (
|
|||
return [];
|
||||
};
|
||||
|
||||
export const buildSeverityDescription = (label: string, value: string): ListItems[] => {
|
||||
return [
|
||||
{
|
||||
title: label,
|
||||
description: (
|
||||
<EuiHealth
|
||||
color={
|
||||
value === 'low'
|
||||
? euiLightVars.euiColorVis0
|
||||
: value === 'medium'
|
||||
? euiLightVars.euiColorVis5
|
||||
: value === 'high'
|
||||
? euiLightVars.euiColorVis7
|
||||
: euiLightVars.euiColorVis9
|
||||
}
|
||||
>
|
||||
{value}
|
||||
</EuiHealth>
|
||||
),
|
||||
},
|
||||
];
|
||||
};
|
||||
export const buildSeverityDescription = (label: string, value: string): ListItems[] => [
|
||||
{
|
||||
title: label,
|
||||
description: <SeverityBadge value={value} />,
|
||||
},
|
||||
];
|
||||
|
||||
export const buildUrlsDescription = (label: string, values: string[]): ListItems[] => {
|
||||
if (!isEmpty(values) && values.filter(val => !isEmpty(val)).length > 0) {
|
||||
if (isNotEmptyArray(values)) {
|
||||
return [
|
||||
{
|
||||
title: label,
|
||||
description: (
|
||||
<EuiListGroup
|
||||
flush={true}
|
||||
bordered={false}
|
||||
listItems={values.map((val: string) => ({
|
||||
label: val,
|
||||
href: val,
|
||||
iconType: 'link',
|
||||
size: 'xs',
|
||||
target: '_blank',
|
||||
}))}
|
||||
/>
|
||||
<EuiFlexGroup gutterSize="none" alignItems="flexStart" direction="column">
|
||||
{values.map((val: string) => (
|
||||
<EuiFlexItem>
|
||||
<ReferenceLinkItem
|
||||
href={val}
|
||||
target="_blank"
|
||||
iconType="link"
|
||||
size="xs"
|
||||
flush="left"
|
||||
>
|
||||
{val}
|
||||
</ReferenceLinkItem>
|
||||
</EuiFlexItem>
|
||||
))}
|
||||
</EuiFlexGroup>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
|
|
@ -4,10 +4,9 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EuiDescriptionList, EuiFlexGroup, EuiFlexItem, EuiTextArea } from '@elastic/eui';
|
||||
import { EuiDescriptionList, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { isEmpty, chunk, get, pick } from 'lodash/fp';
|
||||
import React, { memo, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import {
|
||||
IIndexPattern,
|
||||
|
@ -26,6 +25,7 @@ import {
|
|||
buildSeverityDescription,
|
||||
buildStringArrayDescription,
|
||||
buildThreatsDescription,
|
||||
buildUnorderedListArrayDescription,
|
||||
buildUrlsDescription,
|
||||
} from './helpers';
|
||||
|
||||
|
@ -36,15 +36,6 @@ interface StepRuleDescriptionProps {
|
|||
schema: FormSchema;
|
||||
}
|
||||
|
||||
const EuiFlexItemWidth = styled(EuiFlexItem)<{ direction: string }>`
|
||||
${props => (props.direction === 'row' ? 'width : 50%;' : 'width: 100%;')};
|
||||
`;
|
||||
|
||||
const MyEuiTextArea = styled(EuiTextArea)`
|
||||
max-width: 100%;
|
||||
height: 80px;
|
||||
`;
|
||||
|
||||
const StepRuleDescriptionComponent: React.FC<StepRuleDescriptionProps> = ({
|
||||
data,
|
||||
direction = 'row',
|
||||
|
@ -62,13 +53,24 @@ const StepRuleDescriptionComponent: React.FC<StepRuleDescriptionProps> = ({
|
|||
],
|
||||
[]
|
||||
);
|
||||
|
||||
if (direction === 'row') {
|
||||
return (
|
||||
<EuiFlexGroup>
|
||||
{chunk(Math.ceil(listItems.length / 2), listItems).map((chunkListItems, index) => (
|
||||
<EuiFlexItem key={`description-step-rule-${index}`}>
|
||||
<EuiDescriptionList listItems={chunkListItems} />
|
||||
</EuiFlexItem>
|
||||
))}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="none" direction={direction} justifyContent="spaceAround">
|
||||
{chunk(Math.ceil(listItems.length / 2), listItems).map((chunkListItems, index) => (
|
||||
<EuiFlexItemWidth direction={direction} key={`description-step-rule-${index}`} grow={false}>
|
||||
<EuiDescriptionList listItems={chunkListItems} />
|
||||
</EuiFlexItemWidth>
|
||||
))}
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem key={`description-step-rule`}>
|
||||
<EuiDescriptionList listItems={listItems} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
|
@ -123,18 +125,28 @@ const getDescriptionItem = (
|
|||
return [
|
||||
{
|
||||
title: label,
|
||||
description: <MyEuiTextArea value={get(field, value)} readOnly={true} />,
|
||||
description: get(field, value),
|
||||
},
|
||||
];
|
||||
} else if (field === 'references') {
|
||||
const urls: string[] = get(field, value);
|
||||
return buildUrlsDescription(label, urls);
|
||||
} else if (field === 'falsePositives') {
|
||||
const values: string[] = get(field, value);
|
||||
return buildUnorderedListArrayDescription(label, field, values);
|
||||
} else if (Array.isArray(get(field, value))) {
|
||||
const values: string[] = get(field, value);
|
||||
return buildStringArrayDescription(label, field, values);
|
||||
} else if (field === 'severity') {
|
||||
const val: string = get(field, value);
|
||||
return buildSeverityDescription(label, val);
|
||||
} else if (field === 'riskScore') {
|
||||
return [
|
||||
{
|
||||
title: label,
|
||||
description: get(field, value),
|
||||
},
|
||||
];
|
||||
} else if (field === 'timeline') {
|
||||
const timeline = get(field, value) as FieldValueTimeline;
|
||||
return [
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
*/
|
||||
|
||||
import {
|
||||
EuiButtonEmpty,
|
||||
EuiButtonIcon,
|
||||
EuiFormRow,
|
||||
EuiSuperSelect,
|
||||
|
@ -24,6 +23,7 @@ import * as Rulei18n from '../../translations';
|
|||
import { FieldHook, getFieldValidityAndErrorMessage } from '../shared_imports';
|
||||
import { threatsDefault } from '../step_about_rule/default_value';
|
||||
import { IMitreEnterpriseAttack } from '../../types';
|
||||
import { MyAddItemButton } from '../add_item_form';
|
||||
import { isMitreAttackInvalid } from './helpers';
|
||||
import * as i18n from './translations';
|
||||
|
||||
|
@ -134,13 +134,19 @@ export const AddMitreThreat = ({ dataTestSubj, field, idAria, isDisabled }: AddI
|
|||
|
||||
const getSelectTechniques = (item: IMitreEnterpriseAttack, index: number, disabled: boolean) => {
|
||||
const invalid = isMitreAttackInvalid(item.tactic.name, item.techniques);
|
||||
const options = techniquesOptions.filter(t => t.tactics.includes(kebabCase(item.tactic.name)));
|
||||
const selectedOptions = item.techniques.map(technic => ({
|
||||
...technic,
|
||||
label: `${technic.name} (${technic.id})`, // API doesn't allow for label field
|
||||
}));
|
||||
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="s" alignItems="center">
|
||||
<EuiFlexItem grow>
|
||||
<EuiComboBox
|
||||
placeholder={item.tactic.name === 'none' ? '' : i18n.TECHNIQUES_PLACEHOLDER}
|
||||
options={techniquesOptions.filter(t => t.tactics.includes(kebabCase(item.tactic.name)))}
|
||||
selectedOptions={item.techniques}
|
||||
options={options}
|
||||
selectedOptions={selectedOptions}
|
||||
onChange={updateTechniques.bind(null, index)}
|
||||
isDisabled={disabled || item.tactic.name === 'none'}
|
||||
fullWidth={true}
|
||||
|
@ -202,9 +208,9 @@ export const AddMitreThreat = ({ dataTestSubj, field, idAria, isDisabled }: AddI
|
|||
{values.length - 1 !== index && <EuiSpacer size="s" />}
|
||||
</div>
|
||||
))}
|
||||
<EuiButtonEmpty size="xs" onClick={addItem} isDisabled={isDisabled} iconType="plusInCircle">
|
||||
<MyAddItemButton onClick={addItem} isDisabled={isDisabled}>
|
||||
{i18n.ADD_MITRE_ATTACK}
|
||||
</EuiButtonEmpty>
|
||||
</MyAddItemButton>
|
||||
</MitreContainer>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* 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 { EuiText } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
|
||||
import * as RuleI18n from '../../translations';
|
||||
|
||||
export const OptionalFieldLabel = (
|
||||
<EuiText color="subdued" size="xs">
|
||||
{RuleI18n.OPTIONAL_FIELD}
|
||||
</EuiText>
|
||||
);
|
|
@ -51,7 +51,7 @@ interface QueryBarDefineRuleProps {
|
|||
|
||||
const StyledEuiFormRow = styled(EuiFormRow)`
|
||||
.kbnTypeahead__items {
|
||||
max-height: 14vh !important;
|
||||
max-height: 45vh !important;
|
||||
}
|
||||
.globalQueryBar {
|
||||
padding: 4px 0px 0px 0px;
|
||||
|
|
|
@ -4,7 +4,14 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiFieldNumber, EuiFormRow, EuiSelect } from '@elastic/eui';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiFieldNumber,
|
||||
EuiFormRow,
|
||||
EuiSelect,
|
||||
EuiFormControlLayout,
|
||||
} from '@elastic/eui';
|
||||
import { isEmpty } from 'lodash/fp';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
@ -26,10 +33,28 @@ const timeTypeOptions = [
|
|||
{ value: 'h', text: I18n.HOURS },
|
||||
];
|
||||
|
||||
// move optional label to the end of input
|
||||
const StyledLabelAppend = styled(EuiFlexItem)`
|
||||
&.euiFlexItem.euiFlexItem--flexGrowZero {
|
||||
margin-left: 31px;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledEuiFormRow = styled(EuiFormRow)`
|
||||
max-width: none;
|
||||
|
||||
.euiFormControlLayout {
|
||||
max-width: 200px !important;
|
||||
}
|
||||
|
||||
.euiFormControlLayout__childrenWrapper > *:first-child {
|
||||
box-shadow: none;
|
||||
height: 38px;
|
||||
}
|
||||
|
||||
.euiFormControlLayout:not(:first-child) {
|
||||
border-left: 1px solid ${({ theme }) => theme.eui.euiColorLightShade};
|
||||
}
|
||||
`;
|
||||
|
||||
const MyEuiSelect = styled(EuiSelect)`
|
||||
|
@ -89,9 +114,9 @@ export const ScheduleItem = ({ dataTestSubj, field, idAria, isDisabled }: Schedu
|
|||
<EuiFlexItem grow={false} component="span">
|
||||
{field.label}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false} component="span">
|
||||
<StyledLabelAppend grow={false} component="span">
|
||||
{field.labelAppend}
|
||||
</EuiFlexItem>
|
||||
</StyledLabelAppend>
|
||||
</EuiFlexGroup>
|
||||
),
|
||||
[field.label, field.labelAppend]
|
||||
|
@ -107,7 +132,7 @@ export const ScheduleItem = ({ dataTestSubj, field, idAria, isDisabled }: Schedu
|
|||
data-test-subj={dataTestSubj}
|
||||
describedByIds={idAria ? [idAria] : undefined}
|
||||
>
|
||||
<EuiFieldNumber
|
||||
<EuiFormControlLayout
|
||||
append={
|
||||
<MyEuiSelect
|
||||
fullWidth={false}
|
||||
|
@ -117,12 +142,9 @@ export const ScheduleItem = ({ dataTestSubj, field, idAria, isDisabled }: Schedu
|
|||
{...rest}
|
||||
/>
|
||||
}
|
||||
fullWidth
|
||||
min={0}
|
||||
onChange={onChangeTimeVal}
|
||||
value={timeVal}
|
||||
{...rest}
|
||||
/>
|
||||
>
|
||||
<EuiFieldNumber fullWidth min={0} onChange={onChangeTimeVal} value={timeVal} {...rest} />
|
||||
</EuiFormControlLayout>
|
||||
</StyledEuiFormRow>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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 { upperFirst } from 'lodash/fp';
|
||||
import React from 'react';
|
||||
import { EuiHealth } from '@elastic/eui';
|
||||
import euiLightVars from '@elastic/eui/dist/eui_theme_light.json';
|
||||
|
||||
interface Props {
|
||||
value: string;
|
||||
}
|
||||
|
||||
const SeverityBadgeComponent: React.FC<Props> = ({ value }) => (
|
||||
<EuiHealth
|
||||
color={
|
||||
value === 'low'
|
||||
? euiLightVars.euiColorVis0
|
||||
: value === 'medium'
|
||||
? euiLightVars.euiColorVis5
|
||||
: value === 'high'
|
||||
? euiLightVars.euiColorVis7
|
||||
: euiLightVars.euiColorVis9
|
||||
}
|
||||
>
|
||||
{upperFirst(value)}
|
||||
</EuiHealth>
|
||||
);
|
||||
|
||||
export const SeverityBadge = React.memo(SeverityBadgeComponent);
|
|
@ -4,6 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import styled from 'styled-components';
|
||||
import { EuiHealth } from '@elastic/eui';
|
||||
import euiLightVars from '@elastic/eui/dist/eui_theme_light.json';
|
||||
import React from 'react';
|
||||
|
@ -16,22 +17,30 @@ interface SeverityOptionItem {
|
|||
inputDisplay: React.ReactElement;
|
||||
}
|
||||
|
||||
const StyledEuiHealth = styled(EuiHealth)`
|
||||
line-height: inherit;
|
||||
`;
|
||||
|
||||
export const severityOptions: SeverityOptionItem[] = [
|
||||
{
|
||||
value: 'low',
|
||||
inputDisplay: <EuiHealth color={euiLightVars.euiColorVis0}>{I18n.LOW}</EuiHealth>,
|
||||
inputDisplay: <StyledEuiHealth color={euiLightVars.euiColorVis0}>{I18n.LOW}</StyledEuiHealth>,
|
||||
},
|
||||
{
|
||||
value: 'medium',
|
||||
inputDisplay: <EuiHealth color={euiLightVars.euiColorVis5}>{I18n.MEDIUM} </EuiHealth>,
|
||||
inputDisplay: (
|
||||
<StyledEuiHealth color={euiLightVars.euiColorVis5}>{I18n.MEDIUM}</StyledEuiHealth>
|
||||
),
|
||||
},
|
||||
{
|
||||
value: 'high',
|
||||
inputDisplay: <EuiHealth color={euiLightVars.euiColorVis7}>{I18n.HIGH} </EuiHealth>,
|
||||
inputDisplay: <StyledEuiHealth color={euiLightVars.euiColorVis7}>{I18n.HIGH}</StyledEuiHealth>,
|
||||
},
|
||||
{
|
||||
value: 'critical',
|
||||
inputDisplay: <EuiHealth color={euiLightVars.euiColorVis9}>{I18n.CRITICAL} </EuiHealth>,
|
||||
inputDisplay: (
|
||||
<StyledEuiHealth color={euiLightVars.euiColorVis9}>{I18n.CRITICAL}</StyledEuiHealth>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import { EuiButton, EuiHorizontalRule, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { isEqual, get } from 'lodash/fp';
|
||||
import React, { memo, useCallback, useEffect, useState } from 'react';
|
||||
import React, { FC, memo, useCallback, useEffect, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { RuleStepProps, RuleStep, AboutStepRule } from '../../types';
|
||||
|
@ -22,6 +22,7 @@ import { isUrlInvalid } from './helpers';
|
|||
import { schema } from './schema';
|
||||
import * as I18n from './translations';
|
||||
import { PickTimeline } from '../pick_timeline';
|
||||
import { StepContentWrapper } from '../step_content_wrapper';
|
||||
|
||||
const CommonUseField = getUseField({ component: Field });
|
||||
|
||||
|
@ -33,64 +34,67 @@ const TagContainer = styled.div`
|
|||
margin-top: 16px;
|
||||
`;
|
||||
|
||||
export const StepAboutRule = memo<StepAboutRuleProps>(
|
||||
({
|
||||
defaultValues,
|
||||
descriptionDirection = 'row',
|
||||
isReadOnlyView,
|
||||
isUpdateView = false,
|
||||
isLoading,
|
||||
setForm,
|
||||
setStepData,
|
||||
}) => {
|
||||
const [myStepData, setMyStepData] = useState<AboutStepRule>(stepAboutDefaultValue);
|
||||
const StepAboutRuleComponent: FC<StepAboutRuleProps> = ({
|
||||
addPadding = false,
|
||||
defaultValues,
|
||||
descriptionDirection = 'row',
|
||||
isReadOnlyView,
|
||||
isUpdateView = false,
|
||||
isLoading,
|
||||
setForm,
|
||||
setStepData,
|
||||
}) => {
|
||||
const [myStepData, setMyStepData] = useState<AboutStepRule>(stepAboutDefaultValue);
|
||||
|
||||
const { form } = useForm({
|
||||
defaultValue: myStepData,
|
||||
options: { stripEmptyFields: false },
|
||||
schema,
|
||||
});
|
||||
const { form } = useForm({
|
||||
defaultValue: myStepData,
|
||||
options: { stripEmptyFields: false },
|
||||
schema,
|
||||
});
|
||||
|
||||
const onSubmit = useCallback(async () => {
|
||||
if (setStepData) {
|
||||
setStepData(RuleStep.aboutRule, null, false);
|
||||
const { isValid, data } = await form.submit();
|
||||
if (isValid) {
|
||||
setStepData(RuleStep.aboutRule, data, isValid);
|
||||
setMyStepData({ ...data, isNew: false } as AboutStepRule);
|
||||
}
|
||||
const onSubmit = useCallback(async () => {
|
||||
if (setStepData) {
|
||||
setStepData(RuleStep.aboutRule, null, false);
|
||||
const { isValid, data } = await form.submit();
|
||||
if (isValid) {
|
||||
setStepData(RuleStep.aboutRule, data, isValid);
|
||||
setMyStepData({ ...data, isNew: false } as AboutStepRule);
|
||||
}
|
||||
}, [form]);
|
||||
}
|
||||
}, [form]);
|
||||
|
||||
useEffect(() => {
|
||||
const { isNew, ...initDefaultValue } = myStepData;
|
||||
if (defaultValues != null && !isEqual(initDefaultValue, defaultValues)) {
|
||||
const myDefaultValues = {
|
||||
...defaultValues,
|
||||
isNew: false,
|
||||
};
|
||||
setMyStepData(myDefaultValues);
|
||||
if (!isReadOnlyView) {
|
||||
Object.keys(schema).forEach(key => {
|
||||
const val = get(key, myDefaultValues);
|
||||
if (val != null) {
|
||||
form.setFieldValue(key, val);
|
||||
}
|
||||
});
|
||||
}
|
||||
useEffect(() => {
|
||||
const { isNew, ...initDefaultValue } = myStepData;
|
||||
if (defaultValues != null && !isEqual(initDefaultValue, defaultValues)) {
|
||||
const myDefaultValues = {
|
||||
...defaultValues,
|
||||
isNew: false,
|
||||
};
|
||||
setMyStepData(myDefaultValues);
|
||||
if (!isReadOnlyView) {
|
||||
Object.keys(schema).forEach(key => {
|
||||
const val = get(key, myDefaultValues);
|
||||
if (val != null) {
|
||||
form.setFieldValue(key, val);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [defaultValues]);
|
||||
}
|
||||
}, [defaultValues]);
|
||||
|
||||
useEffect(() => {
|
||||
if (setForm != null) {
|
||||
setForm(RuleStep.aboutRule, form);
|
||||
}
|
||||
}, [form]);
|
||||
useEffect(() => {
|
||||
if (setForm != null) {
|
||||
setForm(RuleStep.aboutRule, form);
|
||||
}
|
||||
}, [form]);
|
||||
|
||||
return isReadOnlyView && myStepData != null ? (
|
||||
return isReadOnlyView && myStepData != null ? (
|
||||
<StepContentWrapper addPadding={addPadding}>
|
||||
<StepRuleDescription direction={descriptionDirection} schema={schema} data={myStepData} />
|
||||
) : (
|
||||
<>
|
||||
</StepContentWrapper>
|
||||
) : (
|
||||
<>
|
||||
<StepContentWrapper addPadding={!isUpdateView}>
|
||||
<Form form={form} data-test-subj="stepAboutRule">
|
||||
<CommonUseField
|
||||
path="name"
|
||||
|
@ -205,24 +209,26 @@ export const StepAboutRule = memo<StepAboutRuleProps>(
|
|||
}}
|
||||
</FormDataProvider>
|
||||
</Form>
|
||||
{!isUpdateView && (
|
||||
<>
|
||||
<EuiHorizontalRule margin="m" />
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
justifyContent="flexEnd"
|
||||
gutterSize="xs"
|
||||
responsive={false}
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton fill onClick={onSubmit} isDisabled={isLoading}>
|
||||
{RuleI18n.CONTINUE}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
</StepContentWrapper>
|
||||
{!isUpdateView && (
|
||||
<>
|
||||
<EuiHorizontalRule margin="m" />
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
justifyContent="flexEnd"
|
||||
gutterSize="xs"
|
||||
responsive={false}
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton fill onClick={onSubmit} isDisabled={isLoading}>
|
||||
{RuleI18n.CONTINUE}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const StepAboutRule = memo(StepAboutRuleComponent);
|
||||
|
|
|
@ -4,11 +4,8 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EuiText } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
|
||||
import * as RuleI18n from '../../translations';
|
||||
import { IMitreEnterpriseAttack } from '../../types';
|
||||
import {
|
||||
FIELD_TYPES,
|
||||
|
@ -18,6 +15,7 @@ import {
|
|||
ERROR_CODE,
|
||||
} from '../shared_imports';
|
||||
import { isMitreAttackInvalid } from '../mitre/helpers';
|
||||
import { OptionalFieldLabel } from '../optional_field_label';
|
||||
import { isUrlInvalid } from './helpers';
|
||||
import * as I18n from './translations';
|
||||
|
||||
|
@ -108,7 +106,7 @@ export const schema: FormSchema = {
|
|||
defaultMessage: 'Reference URLs',
|
||||
}
|
||||
),
|
||||
labelAppend: <EuiText size="xs">{RuleI18n.OPTIONAL_FIELD}</EuiText>,
|
||||
labelAppend: OptionalFieldLabel,
|
||||
validations: [
|
||||
{
|
||||
validator: (
|
||||
|
@ -136,10 +134,10 @@ export const schema: FormSchema = {
|
|||
label: i18n.translate(
|
||||
'xpack.siem.detectionEngine.createRule.stepAboutRule.fieldFalsePositiveLabel',
|
||||
{
|
||||
defaultMessage: 'False positives examples',
|
||||
defaultMessage: 'False positive examples',
|
||||
}
|
||||
),
|
||||
labelAppend: <EuiText size="xs">{RuleI18n.OPTIONAL_FIELD}</EuiText>,
|
||||
labelAppend: OptionalFieldLabel,
|
||||
},
|
||||
threats: {
|
||||
label: i18n.translate(
|
||||
|
@ -148,7 +146,7 @@ export const schema: FormSchema = {
|
|||
defaultMessage: 'MITRE ATT&CK\\u2122',
|
||||
}
|
||||
),
|
||||
labelAppend: <EuiText size="xs">{RuleI18n.OPTIONAL_FIELD}</EuiText>,
|
||||
labelAppend: OptionalFieldLabel,
|
||||
validations: [
|
||||
{
|
||||
validator: (
|
||||
|
@ -184,6 +182,6 @@ export const schema: FormSchema = {
|
|||
'Type one or more custom identifying tags for this rule. Press enter after each tag to begin a new one.',
|
||||
}
|
||||
),
|
||||
labelAppend: <EuiText size="xs">{RuleI18n.OPTIONAL_FIELD}</EuiText>,
|
||||
labelAppend: OptionalFieldLabel,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* 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 styled from 'styled-components';
|
||||
|
||||
const StyledDiv = styled.div<{ addPadding: boolean }>`
|
||||
padding-left: ${({ addPadding }) => addPadding && '53px'}; /* to align with the step title */
|
||||
`;
|
||||
|
||||
StyledDiv.defaultProps = {
|
||||
addPadding: false,
|
||||
};
|
||||
|
||||
export const StepContentWrapper = React.memo(StyledDiv);
|
|
@ -12,7 +12,8 @@ import {
|
|||
EuiButton,
|
||||
} from '@elastic/eui';
|
||||
import { isEmpty, isEqual, get } from 'lodash/fp';
|
||||
import React, { memo, useCallback, useState, useEffect } from 'react';
|
||||
import React, { FC, memo, useCallback, useState, useEffect } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { IIndexPattern } from '../../../../../../../../../../src/plugins/data/public';
|
||||
import { useFetchIndexPatterns } from '../../../../../containers/detection_engine/rules';
|
||||
|
@ -22,6 +23,7 @@ import * as RuleI18n from '../../translations';
|
|||
import { DefineStepRule, RuleStep, RuleStepProps } from '../../types';
|
||||
import { StepRuleDescription } from '../description_step';
|
||||
import { QueryBarDefineRule } from '../query_bar';
|
||||
import { StepContentWrapper } from '../step_content_wrapper';
|
||||
import { Field, Form, FormDataProvider, getUseField, UseField, useForm } from '../shared_imports';
|
||||
import { schema } from './schema';
|
||||
import * as i18n from './translations';
|
||||
|
@ -42,6 +44,20 @@ const stepDefineDefaultValue = {
|
|||
},
|
||||
};
|
||||
|
||||
const MyLabelButton = styled(EuiButtonEmpty)`
|
||||
height: 18px;
|
||||
font-size: 12px;
|
||||
|
||||
.euiIcon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
`;
|
||||
|
||||
MyLabelButton.defaultProps = {
|
||||
flush: 'right',
|
||||
};
|
||||
|
||||
const getStepDefaultValue = (
|
||||
indicesConfig: string[],
|
||||
defaultValues: DefineStepRule | null
|
||||
|
@ -59,106 +75,104 @@ const getStepDefaultValue = (
|
|||
}
|
||||
};
|
||||
|
||||
export const StepDefineRule = memo<StepDefineRuleProps>(
|
||||
({
|
||||
defaultValues,
|
||||
descriptionDirection = 'row',
|
||||
isReadOnlyView,
|
||||
isLoading,
|
||||
isUpdateView = false,
|
||||
resizeParentContainer,
|
||||
setForm,
|
||||
setStepData,
|
||||
}) => {
|
||||
const [openTimelineSearch, setOpenTimelineSearch] = useState(false);
|
||||
const [localUseIndicesConfig, setLocalUseIndicesConfig] = useState(false);
|
||||
const [indicesConfig] = useUiSetting$<string[]>(DEFAULT_INDEX_KEY);
|
||||
const [mylocalIndicesConfig, setMyLocalIndicesConfig] = useState(
|
||||
defaultValues != null ? defaultValues.index : indicesConfig ?? []
|
||||
);
|
||||
const [
|
||||
{
|
||||
browserFields,
|
||||
indexPatterns: indexPatternQueryBar,
|
||||
isLoading: indexPatternLoadingQueryBar,
|
||||
},
|
||||
] = useFetchIndexPatterns(mylocalIndicesConfig);
|
||||
const [myStepData, setMyStepData] = useState<DefineStepRule>(
|
||||
getStepDefaultValue(indicesConfig, null)
|
||||
);
|
||||
const StepDefineRuleComponent: FC<StepDefineRuleProps> = ({
|
||||
addPadding = false,
|
||||
defaultValues,
|
||||
descriptionDirection = 'row',
|
||||
isReadOnlyView,
|
||||
isLoading,
|
||||
isUpdateView = false,
|
||||
setForm,
|
||||
setStepData,
|
||||
}) => {
|
||||
const [openTimelineSearch, setOpenTimelineSearch] = useState(false);
|
||||
const [localUseIndicesConfig, setLocalUseIndicesConfig] = useState(false);
|
||||
const [indicesConfig] = useUiSetting$<string[]>(DEFAULT_INDEX_KEY);
|
||||
const [mylocalIndicesConfig, setMyLocalIndicesConfig] = useState(
|
||||
defaultValues != null ? defaultValues.index : indicesConfig ?? []
|
||||
);
|
||||
const [
|
||||
{ browserFields, indexPatterns: indexPatternQueryBar, isLoading: indexPatternLoadingQueryBar },
|
||||
] = useFetchIndexPatterns(mylocalIndicesConfig);
|
||||
const [myStepData, setMyStepData] = useState<DefineStepRule>(
|
||||
getStepDefaultValue(indicesConfig, null)
|
||||
);
|
||||
|
||||
const { form } = useForm({
|
||||
defaultValue: myStepData,
|
||||
options: { stripEmptyFields: false },
|
||||
schema,
|
||||
});
|
||||
const { form } = useForm({
|
||||
defaultValue: myStepData,
|
||||
options: { stripEmptyFields: false },
|
||||
schema,
|
||||
});
|
||||
|
||||
const onSubmit = useCallback(async () => {
|
||||
if (setStepData) {
|
||||
setStepData(RuleStep.defineRule, null, false);
|
||||
const { isValid, data } = await form.submit();
|
||||
if (isValid && setStepData) {
|
||||
setStepData(RuleStep.defineRule, data, isValid);
|
||||
setMyStepData({ ...data, isNew: false } as DefineStepRule);
|
||||
const onSubmit = useCallback(async () => {
|
||||
if (setStepData) {
|
||||
setStepData(RuleStep.defineRule, null, false);
|
||||
const { isValid, data } = await form.submit();
|
||||
if (isValid && setStepData) {
|
||||
setStepData(RuleStep.defineRule, data, isValid);
|
||||
setMyStepData({ ...data, isNew: false } as DefineStepRule);
|
||||
}
|
||||
}
|
||||
}, [form]);
|
||||
|
||||
useEffect(() => {
|
||||
if (indicesConfig != null && defaultValues != null) {
|
||||
const myDefaultValues = getStepDefaultValue(indicesConfig, defaultValues);
|
||||
if (!isEqual(myDefaultValues, myStepData)) {
|
||||
setMyStepData(myDefaultValues);
|
||||
setLocalUseIndicesConfig(isEqual(myDefaultValues.index, indicesConfig));
|
||||
if (!isReadOnlyView) {
|
||||
Object.keys(schema).forEach(key => {
|
||||
const val = get(key, myDefaultValues);
|
||||
if (val != null) {
|
||||
form.setFieldValue(key, val);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [form]);
|
||||
}
|
||||
}, [defaultValues, indicesConfig]);
|
||||
|
||||
useEffect(() => {
|
||||
if (indicesConfig != null && defaultValues != null) {
|
||||
const myDefaultValues = getStepDefaultValue(indicesConfig, defaultValues);
|
||||
if (!isEqual(myDefaultValues, myStepData)) {
|
||||
setMyStepData(myDefaultValues);
|
||||
setLocalUseIndicesConfig(isEqual(myDefaultValues.index, indicesConfig));
|
||||
if (!isReadOnlyView) {
|
||||
Object.keys(schema).forEach(key => {
|
||||
const val = get(key, myDefaultValues);
|
||||
if (val != null) {
|
||||
form.setFieldValue(key, val);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [defaultValues, indicesConfig]);
|
||||
useEffect(() => {
|
||||
if (setForm != null) {
|
||||
setForm(RuleStep.defineRule, form);
|
||||
}
|
||||
}, [form]);
|
||||
|
||||
useEffect(() => {
|
||||
if (setForm != null) {
|
||||
setForm(RuleStep.defineRule, form);
|
||||
}
|
||||
}, [form]);
|
||||
const handleResetIndices = useCallback(() => {
|
||||
const indexField = form.getFields().index;
|
||||
indexField.setValue(indicesConfig);
|
||||
}, [form, indicesConfig]);
|
||||
|
||||
const handleResetIndices = useCallback(() => {
|
||||
const indexField = form.getFields().index;
|
||||
indexField.setValue(indicesConfig);
|
||||
}, [form, indicesConfig]);
|
||||
const handleOpenTimelineSearch = useCallback(() => {
|
||||
setOpenTimelineSearch(true);
|
||||
}, []);
|
||||
|
||||
const handleOpenTimelineSearch = useCallback(() => {
|
||||
setOpenTimelineSearch(true);
|
||||
}, []);
|
||||
const handleCloseTimelineSearch = useCallback(() => {
|
||||
setOpenTimelineSearch(false);
|
||||
}, []);
|
||||
|
||||
const handleCloseTimelineSearch = useCallback(() => {
|
||||
setOpenTimelineSearch(false);
|
||||
}, []);
|
||||
|
||||
return isReadOnlyView && myStepData != null ? (
|
||||
return isReadOnlyView && myStepData != null ? (
|
||||
<StepContentWrapper addPadding={addPadding}>
|
||||
<StepRuleDescription
|
||||
direction={descriptionDirection}
|
||||
indexPatterns={indexPatternQueryBar as IIndexPattern}
|
||||
schema={schema}
|
||||
data={myStepData}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
</StepContentWrapper>
|
||||
) : (
|
||||
<>
|
||||
<StepContentWrapper addPadding={!isUpdateView}>
|
||||
<Form form={form} data-test-subj="stepDefineRule">
|
||||
<CommonUseField
|
||||
path="index"
|
||||
config={{
|
||||
...schema.index,
|
||||
labelAppend: !localUseIndicesConfig ? (
|
||||
<EuiButtonEmpty size="xs" onClick={handleResetIndices}>
|
||||
<small>{i18n.RESET_DEFAULT_INDEX}</small>
|
||||
</EuiButtonEmpty>
|
||||
<MyLabelButton onClick={handleResetIndices} iconType="refresh">
|
||||
{i18n.RESET_DEFAULT_INDEX}
|
||||
</MyLabelButton>
|
||||
) : null,
|
||||
}}
|
||||
componentProps={{
|
||||
|
@ -176,9 +190,9 @@ export const StepDefineRule = memo<StepDefineRuleProps>(
|
|||
config={{
|
||||
...schema.queryBar,
|
||||
labelAppend: (
|
||||
<EuiButtonEmpty size="xs" onClick={handleOpenTimelineSearch}>
|
||||
<small>{i18n.IMPORT_TIMELINE_QUERY}</small>
|
||||
</EuiButtonEmpty>
|
||||
<MyLabelButton onClick={handleOpenTimelineSearch}>
|
||||
{i18n.IMPORT_TIMELINE_QUERY}
|
||||
</MyLabelButton>
|
||||
),
|
||||
}}
|
||||
component={QueryBarDefineRule}
|
||||
|
@ -192,7 +206,6 @@ export const StepDefineRule = memo<StepDefineRuleProps>(
|
|||
dataTestSubj: 'detectionEngineStepDefineRuleQueryBar',
|
||||
openTimelineSearch,
|
||||
onCloseTimelineSearch: handleCloseTimelineSearch,
|
||||
resizeParentContainer,
|
||||
}}
|
||||
/>
|
||||
<FormDataProvider pathsToWatch="index">
|
||||
|
@ -212,24 +225,26 @@ export const StepDefineRule = memo<StepDefineRuleProps>(
|
|||
}}
|
||||
</FormDataProvider>
|
||||
</Form>
|
||||
{!isUpdateView && (
|
||||
<>
|
||||
<EuiHorizontalRule margin="m" />
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
justifyContent="flexEnd"
|
||||
gutterSize="xs"
|
||||
responsive={false}
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton fill onClick={onSubmit} isDisabled={isLoading}>
|
||||
{RuleI18n.CONTINUE}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
</StepContentWrapper>
|
||||
{!isUpdateView && (
|
||||
<>
|
||||
<EuiHorizontalRule margin="m" />
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
justifyContent="flexEnd"
|
||||
gutterSize="xs"
|
||||
responsive={false}
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton fill onClick={onSubmit} isDisabled={isLoading}>
|
||||
{RuleI18n.CONTINUE}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const StepDefineRule = memo(StepDefineRuleComponent);
|
||||
|
|
|
@ -6,12 +6,13 @@
|
|||
|
||||
import { EuiHorizontalRule, EuiFlexGroup, EuiFlexItem, EuiButton } from '@elastic/eui';
|
||||
import { isEqual, get } from 'lodash/fp';
|
||||
import React, { memo, useCallback, useEffect, useState } from 'react';
|
||||
import React, { FC, memo, useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { RuleStep, RuleStepProps, ScheduleStepRule } from '../../types';
|
||||
import { StepRuleDescription } from '../description_step';
|
||||
import { ScheduleItem } from '../schedule_item_form';
|
||||
import { Form, UseField, useForm } from '../shared_imports';
|
||||
import { StepContentWrapper } from '../step_content_wrapper';
|
||||
import { schema } from './schema';
|
||||
import * as I18n from './translations';
|
||||
|
||||
|
@ -26,67 +27,70 @@ const stepScheduleDefaultValue = {
|
|||
from: '0m',
|
||||
};
|
||||
|
||||
export const StepScheduleRule = memo<StepScheduleRuleProps>(
|
||||
({
|
||||
defaultValues,
|
||||
descriptionDirection = 'row',
|
||||
isReadOnlyView,
|
||||
isLoading,
|
||||
isUpdateView = false,
|
||||
setStepData,
|
||||
setForm,
|
||||
}) => {
|
||||
const [myStepData, setMyStepData] = useState<ScheduleStepRule>(stepScheduleDefaultValue);
|
||||
const StepScheduleRuleComponent: FC<StepScheduleRuleProps> = ({
|
||||
addPadding = false,
|
||||
defaultValues,
|
||||
descriptionDirection = 'row',
|
||||
isReadOnlyView,
|
||||
isLoading,
|
||||
isUpdateView = false,
|
||||
setStepData,
|
||||
setForm,
|
||||
}) => {
|
||||
const [myStepData, setMyStepData] = useState<ScheduleStepRule>(stepScheduleDefaultValue);
|
||||
|
||||
const { form } = useForm({
|
||||
defaultValue: myStepData,
|
||||
options: { stripEmptyFields: false },
|
||||
schema,
|
||||
});
|
||||
const { form } = useForm({
|
||||
defaultValue: myStepData,
|
||||
options: { stripEmptyFields: false },
|
||||
schema,
|
||||
});
|
||||
|
||||
const onSubmit = useCallback(
|
||||
async (enabled: boolean) => {
|
||||
if (setStepData) {
|
||||
setStepData(RuleStep.scheduleRule, null, false);
|
||||
const { isValid: newIsValid, data } = await form.submit();
|
||||
if (newIsValid) {
|
||||
setStepData(RuleStep.scheduleRule, { ...data, enabled }, newIsValid);
|
||||
setMyStepData({ ...data, isNew: false } as ScheduleStepRule);
|
||||
const onSubmit = useCallback(
|
||||
async (enabled: boolean) => {
|
||||
if (setStepData) {
|
||||
setStepData(RuleStep.scheduleRule, null, false);
|
||||
const { isValid: newIsValid, data } = await form.submit();
|
||||
if (newIsValid) {
|
||||
setStepData(RuleStep.scheduleRule, { ...data, enabled }, newIsValid);
|
||||
setMyStepData({ ...data, isNew: false } as ScheduleStepRule);
|
||||
}
|
||||
}
|
||||
},
|
||||
[form]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const { isNew, ...initDefaultValue } = myStepData;
|
||||
if (defaultValues != null && !isEqual(initDefaultValue, defaultValues)) {
|
||||
const myDefaultValues = {
|
||||
...defaultValues,
|
||||
isNew: false,
|
||||
};
|
||||
setMyStepData(myDefaultValues);
|
||||
if (!isReadOnlyView) {
|
||||
Object.keys(schema).forEach(key => {
|
||||
const val = get(key, myDefaultValues);
|
||||
if (val != null) {
|
||||
form.setFieldValue(key, val);
|
||||
}
|
||||
}
|
||||
},
|
||||
[form]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const { isNew, ...initDefaultValue } = myStepData;
|
||||
if (defaultValues != null && !isEqual(initDefaultValue, defaultValues)) {
|
||||
const myDefaultValues = {
|
||||
...defaultValues,
|
||||
isNew: false,
|
||||
};
|
||||
setMyStepData(myDefaultValues);
|
||||
if (!isReadOnlyView) {
|
||||
Object.keys(schema).forEach(key => {
|
||||
const val = get(key, myDefaultValues);
|
||||
if (val != null) {
|
||||
form.setFieldValue(key, val);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [defaultValues]);
|
||||
}
|
||||
}, [defaultValues]);
|
||||
|
||||
useEffect(() => {
|
||||
if (setForm != null) {
|
||||
setForm(RuleStep.scheduleRule, form);
|
||||
}
|
||||
}, [form]);
|
||||
useEffect(() => {
|
||||
if (setForm != null) {
|
||||
setForm(RuleStep.scheduleRule, form);
|
||||
}
|
||||
}, [form]);
|
||||
|
||||
return isReadOnlyView && myStepData != null ? (
|
||||
return isReadOnlyView && myStepData != null ? (
|
||||
<StepContentWrapper addPadding={addPadding}>
|
||||
<StepRuleDescription direction={descriptionDirection} schema={schema} data={myStepData} />
|
||||
) : (
|
||||
<>
|
||||
</StepContentWrapper>
|
||||
) : (
|
||||
<>
|
||||
<StepContentWrapper addPadding={!isUpdateView}>
|
||||
<Form form={form} data-test-subj="stepScheduleRule">
|
||||
<UseField
|
||||
path="interval"
|
||||
|
@ -107,40 +111,42 @@ export const StepScheduleRule = memo<StepScheduleRuleProps>(
|
|||
}}
|
||||
/>
|
||||
</Form>
|
||||
</StepContentWrapper>
|
||||
|
||||
{!isUpdateView && (
|
||||
<>
|
||||
<EuiHorizontalRule margin="s" />
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
justifyContent="flexEnd"
|
||||
gutterSize="xs"
|
||||
responsive={false}
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
fill={false}
|
||||
isDisabled={isLoading}
|
||||
isLoading={isLoading}
|
||||
onClick={onSubmit.bind(null, false)}
|
||||
>
|
||||
{I18n.COMPLETE_WITHOUT_ACTIVATING}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
fill
|
||||
isDisabled={isLoading}
|
||||
isLoading={isLoading}
|
||||
onClick={onSubmit.bind(null, true)}
|
||||
>
|
||||
{I18n.COMPLETE_WITH_ACTIVATING}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
{!isUpdateView && (
|
||||
<>
|
||||
<EuiHorizontalRule margin="m" />
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
justifyContent="flexEnd"
|
||||
gutterSize="xs"
|
||||
responsive={false}
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
fill={false}
|
||||
isDisabled={isLoading}
|
||||
isLoading={isLoading}
|
||||
onClick={onSubmit.bind(null, false)}
|
||||
>
|
||||
{I18n.COMPLETE_WITHOUT_ACTIVATING}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
fill
|
||||
isDisabled={isLoading}
|
||||
isLoading={isLoading}
|
||||
onClick={onSubmit.bind(null, true)}
|
||||
>
|
||||
{I18n.COMPLETE_WITH_ACTIVATING}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const StepScheduleRule = memo(StepScheduleRuleComponent);
|
||||
|
|
|
@ -4,11 +4,9 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EuiText } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import * as RuleI18n from '../../translations';
|
||||
import { OptionalFieldLabel } from '../optional_field_label';
|
||||
import { FormSchema } from '../shared_imports';
|
||||
|
||||
export const schema: FormSchema = {
|
||||
|
@ -33,7 +31,7 @@ export const schema: FormSchema = {
|
|||
defaultMessage: 'Additional look-back',
|
||||
}
|
||||
),
|
||||
labelAppend: <EuiText size="xs">{RuleI18n.OPTIONAL_FIELD}</EuiText>,
|
||||
labelAppend: OptionalFieldLabel,
|
||||
helpText: i18n.translate(
|
||||
'xpack.siem.detectionEngine.createRule.stepScheduleRule.fieldAdditionalLookBackHelpText',
|
||||
{
|
||||
|
|
|
@ -27,26 +27,17 @@ import * as i18n from './translations';
|
|||
|
||||
const stepsRuleOrder = [RuleStep.defineRule, RuleStep.aboutRule, RuleStep.scheduleRule];
|
||||
|
||||
const ResizeEuiPanel = styled(EuiPanel)<{
|
||||
height?: number;
|
||||
const MyEuiPanel = styled(EuiPanel)<{
|
||||
zIndex?: number;
|
||||
}>`
|
||||
position: relative;
|
||||
z-index: ${props => props.zIndex}; /* ugly fix to allow searchBar to overflow the EuiPanel */
|
||||
|
||||
.euiAccordion__iconWrapper {
|
||||
display: none;
|
||||
}
|
||||
.euiAccordion__childWrapper {
|
||||
height: ${props => (props.height !== -1 ? `${props.height}px !important` : 'auto')};
|
||||
}
|
||||
.euiAccordion__button {
|
||||
cursor: default !important;
|
||||
&:hover {
|
||||
text-decoration: none !important;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const MyEuiPanel = styled(EuiPanel)`
|
||||
.euiAccordion__iconWrapper {
|
||||
display: none;
|
||||
overflow: visible;
|
||||
}
|
||||
.euiAccordion__button {
|
||||
cursor: default !important;
|
||||
|
@ -64,7 +55,6 @@ export const CreateRuleComponent = React.memo(() => {
|
|||
canUserCRUD,
|
||||
hasManageApiKey,
|
||||
} = useUserInfo();
|
||||
const [heightAccordion, setHeightAccordion] = useState(-1);
|
||||
const [openAccordionId, setOpenAccordionId] = useState<RuleStep>(RuleStep.defineRule);
|
||||
const defineRuleRef = useRef<EuiAccordion | null>(null);
|
||||
const aboutRuleRef = useRef<EuiAccordion | null>(null);
|
||||
|
@ -239,7 +229,7 @@ export const CreateRuleComponent = React.memo(() => {
|
|||
isLoading={isLoading || loading}
|
||||
title={i18n.PAGE_TITLE}
|
||||
/>
|
||||
<ResizeEuiPanel height={heightAccordion}>
|
||||
<MyEuiPanel zIndex={3}>
|
||||
<EuiAccordion
|
||||
initialIsOpen={true}
|
||||
id={RuleStep.defineRule}
|
||||
|
@ -259,18 +249,19 @@ export const CreateRuleComponent = React.memo(() => {
|
|||
)
|
||||
}
|
||||
>
|
||||
<EuiHorizontalRule margin="xs" />
|
||||
<EuiHorizontalRule margin="m" />
|
||||
<StepDefineRule
|
||||
addPadding={true}
|
||||
isReadOnlyView={isStepRuleInReadOnlyView[RuleStep.defineRule]}
|
||||
isLoading={isLoading || loading}
|
||||
setForm={setStepsForm}
|
||||
setStepData={setStepData}
|
||||
resizeParentContainer={height => setHeightAccordion(height)}
|
||||
descriptionDirection="row"
|
||||
/>
|
||||
</EuiAccordion>
|
||||
</ResizeEuiPanel>
|
||||
<EuiSpacer size="s" />
|
||||
<MyEuiPanel>
|
||||
</MyEuiPanel>
|
||||
<EuiSpacer size="l" />
|
||||
<MyEuiPanel zIndex={2}>
|
||||
<EuiAccordion
|
||||
initialIsOpen={false}
|
||||
id={RuleStep.aboutRule}
|
||||
|
@ -290,8 +281,10 @@ export const CreateRuleComponent = React.memo(() => {
|
|||
)
|
||||
}
|
||||
>
|
||||
<EuiHorizontalRule margin="xs" />
|
||||
<EuiHorizontalRule margin="m" />
|
||||
<StepAboutRule
|
||||
addPadding={true}
|
||||
descriptionDirection="row"
|
||||
isReadOnlyView={isStepRuleInReadOnlyView[RuleStep.aboutRule]}
|
||||
isLoading={isLoading || loading}
|
||||
setForm={setStepsForm}
|
||||
|
@ -299,8 +292,8 @@ export const CreateRuleComponent = React.memo(() => {
|
|||
/>
|
||||
</EuiAccordion>
|
||||
</MyEuiPanel>
|
||||
<EuiSpacer size="s" />
|
||||
<MyEuiPanel>
|
||||
<EuiSpacer size="l" />
|
||||
<MyEuiPanel zIndex={1}>
|
||||
<EuiAccordion
|
||||
initialIsOpen={false}
|
||||
id={RuleStep.scheduleRule}
|
||||
|
@ -320,8 +313,10 @@ export const CreateRuleComponent = React.memo(() => {
|
|||
)
|
||||
}
|
||||
>
|
||||
<EuiHorizontalRule margin="xs" />
|
||||
<EuiHorizontalRule margin="m" />
|
||||
<StepScheduleRule
|
||||
addPadding={true}
|
||||
descriptionDirection="row"
|
||||
isReadOnlyView={isStepRuleInReadOnlyView[RuleStep.scheduleRule]}
|
||||
isLoading={isLoading || loading}
|
||||
setForm={setStepsForm}
|
||||
|
|
|
@ -237,7 +237,7 @@ const RuleDetailsComponent = memo<RuleDetailsComponentProps>(
|
|||
<StepPanel loading={isLoading} title={ruleI18n.ABOUT}>
|
||||
{aboutRuleData != null && (
|
||||
<StepAboutRule
|
||||
descriptionDirection="column"
|
||||
descriptionDirection="row"
|
||||
isReadOnlyView={true}
|
||||
isLoading={false}
|
||||
defaultValues={aboutRuleData}
|
||||
|
|
|
@ -57,6 +57,7 @@ export interface RuleStepData {
|
|||
}
|
||||
|
||||
export interface RuleStepProps {
|
||||
addPadding?: boolean;
|
||||
descriptionDirection?: 'row' | 'column';
|
||||
setStepData?: (step: RuleStep, data: unknown, isValid: boolean) => void;
|
||||
isReadOnlyView: boolean;
|
||||
|
|
Loading…
Reference in a new issue