diff --git a/public/app.scss b/public/app.scss index 01c242a5d..c879d2fbc 100644 --- a/public/app.scss +++ b/public/app.scss @@ -18,6 +18,7 @@ $euiTextColor: $euiColorDarkestShade !default; @import "./pages/Correlations/Correlations.scss"; @import "./pages/Correlations/components/FindingCard.scss"; @import "./pages/Findings/components/CorrelationsTable/CorrelationsTable.scss"; +@import "./pages/Rules/components/RuleEditor/DetectionVisualEditor.scss"; .selected-radio-panel { background-color: tintOrShade($euiColorPrimary, 90%, 70%); diff --git a/public/pages/Rules/components/RuleEditor/DetectionVisualEditor.scss b/public/pages/Rules/components/RuleEditor/DetectionVisualEditor.scss new file mode 100644 index 000000000..04d5f3dd6 --- /dev/null +++ b/public/pages/Rules/components/RuleEditor/DetectionVisualEditor.scss @@ -0,0 +1,45 @@ + +.detection-visual-editor { + .euiAccordionForm:nth-of-type(1) { + border-top: 1px solid #D3DAE6; + } + + .euiAccordionForm { + border-top: 0 !important; + } + + .detection-visual-editor-accordion-wrapper { + width: 100%; + .detection-visual-editor-form-row { + max-width: 100%; + .detection-visual-editor-textarea { + max-width: 100%; + padding: 0; + min-height: 100px; + } + } + + .detection-visual-editor-textarea-clear-btn { + align-items: flex-end; + } + } + + .detection-visual-editor-name { + box-shadow: none; + background-color: transparent; + padding: 0; + } + + .detection-visual-editor-delete-selection { + margin-top: 0 !important; + } + + .euiButtonIcon--danger { + color: $ouiTextSubduedColor !important; + + &:hover { + color: $ouiColorDanger !important; + background-color: transparent !important; + } + } +} diff --git a/public/pages/Rules/components/RuleEditor/DetectionVisualEditor.tsx b/public/pages/Rules/components/RuleEditor/DetectionVisualEditor.tsx index 644b845de..871e95218 100644 --- a/public/pages/Rules/components/RuleEditor/DetectionVisualEditor.tsx +++ b/public/pages/Rules/components/RuleEditor/DetectionVisualEditor.tsx @@ -29,6 +29,7 @@ import { EuiModalFooter, EuiFilePicker, EuiCodeEditor, + EuiButtonEmpty, } from '@elastic/eui'; import _ from 'lodash'; import { validateCondition, validateDetectionFieldName } from '../../../../utils/validation'; @@ -275,6 +276,10 @@ export class DetectionVisualEditor extends React.Component< const { errors } = this.state; const selection = selections[selectionIdx]; + if (!selection.name) { + selection.name = `Selection_${selectionIdx + 1}`; + } + delete errors.fields['name']; if (!selection.name) { errors.fields['name'] = 'Selection name is required'; @@ -426,23 +431,38 @@ export class DetectionVisualEditor extends React.Component< } = this.state; return ( - + {selections.map((selection, selectionIdx) => { return ( -
+
- -

{selection.name || `Selection_${selectionIdx + 1}`}

-
+ + this.updateSelection(selectionIdx, { name: e.target.value })} + onBlur={(e) => this.updateSelection(selectionIdx, { name: e.target.value })} + value={selection.name || `Selection_${selectionIdx + 1}`} + /> +

Define the search identifier in your data the rule will be applied to.

- + {selections.length > 1 && ( { @@ -466,183 +486,190 @@ export class DetectionVisualEditor extends React.Component< - Name} - > - this.updateSelection(selectionIdx, { name: e.target.value })} - onBlur={(e) => this.updateSelection(selectionIdx, { name: e.target.value })} - value={selection.name} - /> - - - - {selection.data.map((datum, idx) => { const radioGroupOptions = this.createRadioGroupOptions(selectionIdx, idx); const fieldName = `field_${selectionIdx}_${idx}`; const valueId = `value_${selectionIdx}_${idx}`; return ( - 1 ? ( - - { - const newData = [...selection.data]; - newData.splice(idx, 1); - this.updateSelection(selectionIdx, { data: newData }); - }} - /> - - ) : null - } - style={{ maxWidth: '500px' }} - > - - - - - Key} - > - + Map {idx + 1}} + extraAction={ + selection.data.length > 1 ? ( + + { + const newData = [...selection.data]; + newData.splice(idx, 1); + this.updateSelection(selectionIdx, { data: newData }); + }} + /> + + ) : null + } + style={{ maxWidth: '70%' }} + > + + + - this.updateDatumInState(selectionIdx, idx, { - field: e.target.value, - }) - } - onBlur={(e) => - this.updateDatumInState(selectionIdx, idx, { - field: e.target.value, - }) - } - value={datum.field} - /> - - - - Modifier}> - { - this.updateDatumInState(selectionIdx, idx, { - modifier: e[0].value, - }); - }} - onBlur={(e) => {}} - selectedOptions={ - datum.modifier - ? [{ value: datum.modifier, label: datum.modifier }] - : [detectionModifierOptions[0]] - } - /> - - - - - - { - this.updateDatumInState(selectionIdx, idx, { - selectedRadioId: id as SelectionMapValueRadioId, - }); - }} - /> - - - {datum.selectedRadioId?.includes('list') ? ( - <> - { - this.setState({ - fileUploadModalState: { - selectionIdx, - dataIdx: idx, - }, - }); - }} - > - Upload file - - + error={errors.fields[fieldName]} + label={Key} + > + + this.updateDatumInState(selectionIdx, idx, { + field: e.target.value, + }) + } + onBlur={(e) => + this.updateDatumInState(selectionIdx, idx, { + field: e.target.value, + }) + } + value={datum.field} + /> + + + + Modifier}> + { + this.updateDatumInState(selectionIdx, idx, { + modifier: e[0].value, + }); + }} + onBlur={(e) => {}} + selectedOptions={ + datum.modifier + ? [{ value: datum.modifier, label: datum.modifier }] + : [detectionModifierOptions[0]] + } + /> + + + + + + { + this.updateDatumInState(selectionIdx, idx, { + selectedRadioId: id as SelectionMapValueRadioId, + }); + }} + /> + + + + {datum.selectedRadioId?.includes('list') ? ( + <> + + + { + this.setState({ + fileUploadModalState: { + selectionIdx, + dataIdx: idx, + }, + }); + }} + > + Upload file + + + + + { + this.updateDatumInState(selectionIdx, idx, { + values: [], + }); + }} + > + Clear list + + + + + + { + const values = e.target.value.split('\n'); + this.updateDatumInState(selectionIdx, idx, { + values, + }); + }} + onBlur={(e) => { + const values = e.target.value.split('\n'); + this.updateDatumInState(selectionIdx, idx, { + values, + }); + }} + value={datum.values.join('\n')} + compressed={true} + isInvalid={errors.touched[valueId] && !!errors.fields[valueId]} + /> + + + ) : ( - { - const values = e.target.value.split('\n'); - console.log(values); this.updateDatumInState(selectionIdx, idx, { - values, + values: [e.target.value, ...datum.values.slice(1)], }); }} onBlur={(e) => { - const values = e.target.value.split('\n'); - console.log(values); this.updateDatumInState(selectionIdx, idx, { - values, + values: [e.target.value, ...datum.values.slice(1)], }); }} - value={datum.values.join('\n')} - compressed={true} - isInvalid={errors.touched[valueId] && !!errors.fields[valueId]} + value={datum.values[0]} /> - - ) : ( - - { - this.updateDatumInState(selectionIdx, idx, { - values: [e.target.value, ...datum.values.slice(1)], - }); - }} - onBlur={(e) => { - this.updateDatumInState(selectionIdx, idx, { - values: [e.target.value, ...datum.values.slice(1)], - }); - }} - value={datum.values[0]} - /> - - )} - - - - + )} + +
); })} + + { @@ -674,6 +701,7 @@ export class DetectionVisualEditor extends React.Component< ...selections, { ...defaultDetectionObj.selections[0], + name: `Selection_${selections.length + 1}`, }, ], }, diff --git a/public/pages/Rules/components/RuleEditor/RuleEditorForm.tsx b/public/pages/Rules/components/RuleEditor/RuleEditorForm.tsx index 2c611865c..2ec6a6c72 100644 --- a/public/pages/Rules/components/RuleEditor/RuleEditorForm.tsx +++ b/public/pages/Rules/components/RuleEditor/RuleEditorForm.tsx @@ -400,7 +400,7 @@ export const RuleEditorForm: React.FC = ({ selectedOptions={ props.values.status ? [{ value: props.values.status, label: props.values.status }] - : [] + : [{ value: ruleStatus[0], label: ruleStatus[0] }] } /> diff --git a/public/pages/Rules/containers/CreateRule/CreateRule.tsx b/public/pages/Rules/containers/CreateRule/CreateRule.tsx index c44fd2504..118b15b5b 100644 --- a/public/pages/Rules/containers/CreateRule/CreateRule.tsx +++ b/public/pages/Rules/containers/CreateRule/CreateRule.tsx @@ -5,7 +5,7 @@ import { BrowserServices } from '../../../../models/interfaces'; import { RuleEditorContainer } from '../../components/RuleEditor/RuleEditorContainer'; -import React, { useContext } from 'react'; +import React, { useContext, useEffect } from 'react'; import { RouteComponentProps } from 'react-router-dom'; import { BREADCRUMBS } from '../../../../utils/constants'; import { CoreServicesContext } from '../../../../components/core_services'; @@ -20,7 +20,10 @@ export interface CreateRuleProps { export const CreateRule: React.FC = ({ history, services, notifications }) => { const context = useContext(CoreServicesContext); - setBreadCrumb(BREADCRUMBS.RULES_CREATE, context?.chrome.setBreadcrumbs); + + useEffect(() => { + setBreadCrumb(BREADCRUMBS.RULES_CREATE, context?.chrome.setBreadcrumbs); + }); return (