Skip to content
Snippets Groups Projects
options_editor.js 5.73 KiB
import React from 'react';
import {
  EuiForm,
  EuiFormRow,
  EuiTitle,
  EuiSpacer,
  EuiFieldText,
  EuiFieldNumber,
  EuiSelect,
  EuiFlexGroup,
  EuiFlexItem,
  EuiButton,
  EuiButtonIcon,
} from '@elastic/eui';

// Default data types in The Hive
const DEFAULT_THE_HIVE_TYPES = [
  '',
  'autonomous-system',
  'domain',
  'file',
  'filename',
  'fqdn',
  'hash',
  'ip',
  'mail',
  'mail_subject',
  'regexp',
  'registry',
  'uri_path',
  'url',
  'user-agent',
  'other',	
];

// Options for EuiSelect for selection of field's data type in TheHive
const typesOptions = DEFAULT_THE_HIVE_TYPES.map( dt => ({value: dt, text: dt}) );

export function optionsEditor(props) {
  //console.log("editor render(), props:", props);
  const { stateParams, setValue, setValidity, vis } = props;
  
  // onClick/onChange handlers
  const obsAddNew = () => {
    const newObsFields = [...stateParams.obsFields, {name: "", type: "", cnt: 100}];
    // For some reason, first click on the button after editor is loaded does
    // nothing. Calling setValue twice here fixes it.  
    setValue("obsFields", newObsFields);
    setValue("obsFields", newObsFields);
//     setValidity(false); // since new row is empty, form is always invalid
  };
  const obsRemove = (ix) => {
    let newArray = [...stateParams.obsFields];
    newArray.splice(ix, 1);
    setValue("obsFields", newArray);
//     validate();
  }
  const obsSetName = (ix, name) => {
    let newArray = [...stateParams.obsFields];
    newArray[ix].name = name;
    setValue("obsFields", newArray);
//     validate();
  } 
  const obsSetType = (ix, type) => {
    let newArray = [...stateParams.obsFields];
    newArray[ix].type = type;
    setValue("obsFields", newArray);
//     validate();
  }
  const obsSetCnt = (ix, cnt) => {
    let newArray = [...stateParams.obsFields];
    newArray[ix].cnt = parseInt(cnt);
    setValue("obsFields", newArray);
//     validate();
  }
//   const validate = () => {
//     let valid = true;
//     for (let field of stateParams.obsFields) {
//       if (field.name == "" || field.type == "" || field.cnt == "") {
//         valid = false;
//         break;
//       }
//     }
//     // TODO check for duplicate fields
//     setValidity(valid);
//   }
  
  // Get list of all fields in index (except those beginning with "_" or "@")
  // and create "options" parameter for EuiSelect.
  // Also, fields with "aggregatable=false" are removed, as they can't be used
  // with "terms" aggregation we need.
  // See this for details: https://www.elastic.co/guide/en/elasticsearch/reference/7.x/fielddata.html
  // Empty field is added at the beginning, meaning "no selection yet".
  const fieldOptions = [{value: "", text: ""}].concat(
    vis.indexPattern.fields.raw.filter( f => (f.name[0] != "_" && f.name[0] != "@" && f.aggregatable) ).map( f => ({value: f.name, text: `${f.name} (${f.type})`}) )
  );

  return <EuiForm>
    <EuiFormRow fullWidth={true} label="Base URL of The Hive">
      <EuiFieldText
        fullWidth={true}
        value={stateParams.url}
        onChange={e => setValue('url', e.target.value)}
        isInvalid={stateParams.url == ""}
      />
    </EuiFormRow>
    <EuiFlexGroup>
      <EuiFlexItem grow={1}>
        <EuiFormRow label="API key to access The Hive" helpText="API key of a user with permission to create cases (not admin).">
          <EuiFieldText
            fullWidth={true}
            value={stateParams.apikey}
            onChange={e => setValue('apikey', e.target.value)}
            isInvalid={stateParams.apikey == ""}
          />
        </EuiFormRow>
      </EuiFlexItem>
      <EuiFlexItem grow={1}>
        <EuiFormRow label="Assignee" helpText="Assign created cases to this user instead of the owner of the API key (optional, if set, it must be a valid username from The Hive instance). **Note: This currently doesn't work due to a bug in The Hive.**">
          <EuiFieldText
            value={stateParams.owner}
            onChange={e => setValue('owner', e.target.value)}
          />
        </EuiFormRow>
      </EuiFlexItem>
    </EuiFlexGroup>
    <EuiTitle size="s"><h3>Fields to get potential observables from ...</h3></EuiTitle>
    <EuiSpacer size="s" />
    {stateParams.obsFields.map( (field, ix) => (
      <EuiFlexGroup key={ix} gutterSize="s">
        <EuiFlexItem grow={3}>
          <EuiFormRow label="Field name">
            <EuiSelect
              options={fieldOptions}
              value={field.name}
              onChange={ e => obsSetName(ix, e.target.value) }
              isInvalid={field.name == ""}
            />
          </EuiFormRow>
        </EuiFlexItem>
        <EuiFlexItem grow={2}>
          <EuiFormRow label="Data type in The Hive">
            <EuiSelect
              options={typesOptions}
              value={field.type}
              onChange={ e => obsSetType(ix, e.target.value) }
              isInvalid={field.type == ""}
            />
          </EuiFormRow>
        </EuiFlexItem>
        <EuiFlexItem grow={1}>
          <EuiFormRow label="Max items shown">
            <EuiFieldNumber
              min={1}
              max={1000}
              value={parseInt(field.cnt)}
              onChange={ e => obsSetCnt(ix, e.target.value) }
              isInvalid={!(field.cnt > 0)}
            />
          </EuiFormRow>
        </EuiFlexItem>
        <EuiFlexItem grow={false}>
          <EuiFormRow hasEmptyLabelSpace>
            <EuiButtonIcon iconType="trash" iconSize="m" color="danger" aria-label="Remove field" onClick={ e => obsRemove(ix) } />
          </EuiFormRow>
        </EuiFlexItem>
      </EuiFlexGroup>
    ))}
    <EuiFlexGroup>
      <EuiFlexItem grow={false}>
        <EuiButton iconType="plusInCircleFilled" color="primary" onClick={obsAddNew}>Add new field ...</EuiButton>
      </EuiFlexItem>
    </EuiFlexGroup>
  </EuiForm>
}