Skip to content

Commit

Permalink
[Threat intel platform][Part 1] UX to support threat intel platform (#…
Browse files Browse the repository at this point in the history
…1050)

* overview and connection pages UI

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

* all producer flows for threat intel UI with dummy data

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

* updated enum name

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

* separated dummy data

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

* updated snapshots

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

* apis integrated; UX added, updated

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

* refactored to use source type; integrated source refresh and delete

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

* if edit scan reloads and no monitor go to create

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

* using async imports for vega

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

* Threat intel findings integrated

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

* fixed refresh button visibility; updated snapshots

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

* addressed PR comments

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>

---------

Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>
(cherry picked from commit 03c076f)
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
github-actions[bot] committed Jun 28, 2024
1 parent 3d361b6 commit 12e7817
Show file tree
Hide file tree
Showing 70 changed files with 5,731 additions and 710 deletions.
12 changes: 12 additions & 0 deletions common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,15 @@
*/

export const DEFAULT_RULE_UUID = '25b9c01c-350d-4b95-bed1-836d04a4f324';

export enum ThreatIntelIocType {
IPAddress = 'ip',
Domain = 'domain',
FileHash = 'hash',
}

export const IocLabel: { [k in ThreatIntelIocType]: string } = {
[ThreatIntelIocType.IPAddress]: 'IP-Address',
[ThreatIntelIocType.Domain]: 'Domains',
[ThreatIntelIocType.FileHash]: 'File hash',
};
2 changes: 2 additions & 0 deletions cypress/integration/3_alerts.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,10 @@ describe('Alerts', () => {
});

it('are generated', () => {
setupIntercept(cy, '/_security_analytics/alerts', 'getAlerts', 'GET');
// Refresh the table
cy.get('[data-test-subj="superDatePickerApplyTimeButton"]').click({ force: true });
cy.wait('@getAlerts').should('have.property', 'state', 'Complete');

cy.wait(10000);

Expand Down
185 changes: 185 additions & 0 deletions public/components/Notifications/NotificationForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import {
EuiAccordion,
EuiButton,
EuiComboBox,
EuiComboBoxOptionOption,
EuiFieldText,
EuiFlexGroup,
EuiFlexItem,
EuiFormRow,
EuiSpacer,
EuiSwitch,
EuiText,
EuiTextArea,
} from '@elastic/eui';
import React, { useState } from 'react';
import { NOTIFICATIONS_HREF } from '../../utils/constants';
import { NotificationsCallOut } from '../NotificationsCallOut';
import {
NotificationChannelOption,
NotificationChannelTypeOptions,
TriggerAction,
} from '../../../types';
import { getIsNotificationPluginInstalled } from '../../utils/helpers';

export interface NotificationFormProps {
allNotificationChannels: NotificationChannelTypeOptions[];
loadingNotifications: boolean;
action: TriggerAction;
prepareMessage: (updateMessage?: boolean, onMount?: boolean) => void;
refreshNotificationChannels: () => void;
onChannelsChange: (selectedOptions: EuiComboBoxOptionOption<string>[]) => void;
onMessageBodyChange: (message: string) => void;
onMessageSubjectChange: (subject: string) => void;
}

export const NotificationForm: React.FC<NotificationFormProps> = ({
action,
allNotificationChannels,
loadingNotifications,
prepareMessage,
refreshNotificationChannels,
onChannelsChange,
onMessageBodyChange,
onMessageSubjectChange,
}) => {
const hasNotificationPlugin = getIsNotificationPluginInstalled();
const [showNotificationDetails, setShowNotificationDetails] = useState(true);
const selectedNotificationChannelOption: NotificationChannelOption[] = [];
if (action.destination_id) {
allNotificationChannels.forEach((typeOption) => {
const matchingChannel = typeOption.options.find(
(option) => option.value === action.destination_id
);
if (matchingChannel) selectedNotificationChannelOption.push(matchingChannel);
});
}

return (
<>
<EuiSwitch
label="Send notification"
checked={showNotificationDetails}
onChange={(e) => setShowNotificationDetails(e.target.checked)}
/>
<EuiSpacer />
{showNotificationDetails && (
<>
<EuiFlexGroup alignItems={'flexEnd'}>
<EuiFlexItem style={{ maxWidth: 400 }}>
<EuiFormRow
label={
<EuiText size="m">
<p>Notification channel</p>
</EuiText>
}
>
<EuiComboBox
placeholder={'Select notification channel.'}
async={true}
isLoading={loadingNotifications}
options={allNotificationChannels as EuiComboBoxOptionOption<string>[]}
selectedOptions={
selectedNotificationChannelOption as EuiComboBoxOptionOption<string>[]
}
onChange={onChannelsChange}
singleSelection={{ asPlainText: true }}
onFocus={refreshNotificationChannels}
isDisabled={!hasNotificationPlugin}
/>
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton
href={NOTIFICATIONS_HREF}
iconType={'popout'}
target={'_blank'}
isDisabled={!hasNotificationPlugin}
>
Manage channels
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>

{!hasNotificationPlugin && (
<>
<EuiSpacer size="m" />
<NotificationsCallOut />
</>
)}

<EuiSpacer size={'l'} />

<EuiAccordion
id={`alert-condition-notify-msg-${action.id ?? 'draft'}`}
buttonContent={
<EuiText size="m">
<p>Notification message</p>
</EuiText>
}
paddingSize={'l'}
initialIsOpen={false}
>
<EuiFlexGroup direction={'column'} style={{ width: '75%' }}>
<EuiFlexItem>
<EuiFormRow
label={
<EuiText size={'s'}>
<p>Message subject</p>
</EuiText>
}
fullWidth={true}
>
<EuiFieldText
placeholder={'Enter a subject for the notification message.'}
value={action?.subject_template.source}
onChange={(e) => onMessageSubjectChange(e.target.value)}
required={true}
fullWidth={true}
/>
</EuiFormRow>
</EuiFlexItem>

<EuiFlexItem>
<EuiFormRow
label={
<EuiText size="s">
<p>Message body</p>
</EuiText>
}
fullWidth={true}
>
<EuiTextArea
placeholder={'Enter the content of the notification message.'}
value={action?.message_template.source}
onChange={(e) => onMessageBodyChange(e.target.value)}
required={true}
fullWidth={true}
/>
</EuiFormRow>
</EuiFlexItem>

<EuiFlexItem>
<EuiFormRow>
<EuiButton
fullWidth={false}
onClick={() => prepareMessage(true /* updateMessage */)}
>
Generate message
</EuiButton>
</EuiFormRow>
</EuiFlexItem>
</EuiFlexGroup>
</EuiAccordion>

<EuiSpacer size="xl" />
</>
)}
</>
);
};
35 changes: 35 additions & 0 deletions public/components/Utility/DescriptionGroup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import {
EuiDescriptionList,
EuiFlexGroup,
EuiFlexGroupProps,
EuiFlexItem,
EuiFlexItemProps,
} from '@elastic/eui';
import React from 'react';

export interface DescriptionGroupProps {
listItems: { title: NonNullable<React.ReactNode>; description: NonNullable<React.ReactNode> }[];
itemProps?: Pick<EuiFlexItemProps, 'grow'>;
groupProps?: Pick<EuiFlexGroupProps, 'justifyContent'>;
}

export const DescriptionGroup: React.FC<DescriptionGroupProps> = ({
listItems,
itemProps,
groupProps,
}) => {
return (
<EuiFlexGroup gutterSize="s" {...groupProps}>
{listItems.map((item, idx) => (
<EuiFlexItem {...itemProps} key={`item-${idx}`}>
<EuiDescriptionList listItems={[item]} />
</EuiFlexItem>
))}
</EuiFlexGroup>
);
};
23 changes: 23 additions & 0 deletions public/components/Utility/StatusWithIndicator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { EuiIcon } from '@elastic/eui';
import React from 'react';

export interface StatusWithIndicatorProps {
text: string;
indicatorColor: 'success' | 'text';
}

export const StatusWithIndicator: React.FC<StatusWithIndicatorProps> = ({
text,
indicatorColor,
}) => {
return (
<span>
<EuiIcon type={'dot'} color={indicatorColor} style={{ marginBottom: 4 }} /> {text}
</span>
);
};
2 changes: 2 additions & 0 deletions public/models/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
} from '../services';
import CorrelationService from '../services/CorrelationService';
import MetricsService from '../services/MetricsService';
import ThreatIntelService from '../services/ThreatIntelService';

export interface BrowserServices {
detectorsService: DetectorsService;
Expand All @@ -33,6 +34,7 @@ export interface BrowserServices {
indexPatternsService: IndexPatternsService;
logTypeService: LogTypeService;
metricsService: MetricsService;
threatIntelService: ThreatIntelService;
}

export interface RuleOptions {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ exports[`<Alerts /> spec renders the component 1`] = `
findingService={
FindingsService {
"getFindings": [Function],
"getThreatIntelFindings": [Function],
"httpClient": [MockFunction],
}
}
Expand Down Expand Up @@ -124,6 +125,7 @@ exports[`<Alerts /> spec renders the component 1`] = `
findingService={
FindingsService {
"getFindings": [Function],
"getThreatIntelFindings": [Function],
"httpClient": [MockFunction],
}
}
Expand Down
21 changes: 2 additions & 19 deletions public/pages/Correlations/containers/CreateCorrelationRule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ import { CoreServicesContext } from '../../../components/core_services';
import { RouteComponentProps, useParams } from 'react-router-dom';
import { validateName } from '../../../utils/validation';
import { FieldMappingService, IndexService, OpenSearchService, NotificationsService } from '../../../services';
import { errorNotificationToast, getDataSources, getLogTypeOptions, getPlugins } from '../../../utils/helpers';
import { errorNotificationToast, getDataSources, getFieldsForIndex, getLogTypeOptions, getPlugins } from '../../../utils/helpers';
import { severityOptions } from '../../../pages/Alerts/utils/constants';
import _ from 'lodash';
import { NotificationChannelOption, NotificationChannelTypeOptions } from '../../CreateDetector/components/ConfigureAlerts/models/interfaces';
Expand Down Expand Up @@ -402,24 +402,7 @@ export const CreateCorrelationRule: React.FC<CreateCorrelationRuleProps> = (

const getLogFields = useCallback(
async (indexName: string) => {
let fields: {
label: string;
value: string;
}[] = [];

if (indexName) {
const result = await props.indexService.getIndexFields(indexName);
if (result?.ok) {
fields = result.response?.map((field) => ({
label: field,
value: field,
}));
}

return fields;
}

return fields;
return getFieldsForIndex(props.indexService, indexName);
},
[props.indexService.getIndexFields]
);
Expand Down
Loading

0 comments on commit 12e7817

Please sign in to comment.