From 5d7d4243eecb088e8f8e3522b65caac8a6997a36 Mon Sep 17 00:00:00 2001 From: Amardeepsingh Siglani Date: Thu, 5 Oct 2023 17:27:13 -0700 Subject: [PATCH] updated log type filters; fixed search field Signed-off-by: Amardeepsingh Siglani --- .../Correlations/components/FilterGroup.tsx | 93 ++++++-- .../containers/CorrelationsContainer.tsx | 2 + public/pages/Correlations/utils/constants.tsx | 39 +++- .../pages/LogTypes/components/LogTypeForm.tsx | 4 +- public/pages/LogTypes/utils/helpers.tsx | 10 +- public/store/CorrelationsStore.ts | 3 - public/store/LogTypeStore.ts | 16 +- public/utils/constants.tsx | 202 ------------------ public/utils/helpers.tsx | 60 +++--- server/services/RuleService.ts | 6 - 10 files changed, 169 insertions(+), 266 deletions(-) delete mode 100644 public/utils/constants.tsx diff --git a/public/pages/Correlations/components/FilterGroup.tsx b/public/pages/Correlations/components/FilterGroup.tsx index b71a15eaa..40f1482c2 100644 --- a/public/pages/Correlations/components/FilterGroup.tsx +++ b/public/pages/Correlations/components/FilterGroup.tsx @@ -12,16 +12,32 @@ import { EuiPopoverTitle, EuiFieldSearch, FilterChecked, + EuiPopoverFooter, + EuiButtonEmpty, } from '@elastic/eui'; -export type FilterItem = { name: string | React.ReactNode; id: string; checked?: FilterChecked }; +export type FilterItem = { + name: string | React.ReactNode; + id: string; + visible: boolean; + childOptionIds?: Set; + checked?: FilterChecked; +}; export interface LogTypeFilterGroupProps { groupName: string; items: FilterItem[]; + hasGroupOptions?: boolean; + hasFooter?: boolean; setItems: (items: FilterItem[]) => void; } -export const FilterGroup: React.FC = ({ groupName, items, setItems }) => { +export const FilterGroup: React.FC = ({ + groupName, + items, + hasGroupOptions, + hasFooter, + setItems, +}) => { const [isPopoverOpen, setIsPopoverOpen] = useState(false); const [showActiveFilters, setShowActiveFilters] = useState(false); @@ -33,7 +49,7 @@ export const FilterGroup: React.FC = ({ groupName, item setIsPopoverOpen(false); }; - function updateItem(index: number) { + function toggleItem(index: number) { if (!items[index]) { return; } @@ -54,7 +70,35 @@ export const FilterGroup: React.FC = ({ groupName, item checked: 'on', }; } + const chilIds = newItems[index].childOptionIds; + if (chilIds) { + newItems.forEach((item) => { + if (chilIds.has(item.id)) { + item.checked = newItems[index].checked; + } + }); + } + + setItems(newItems); + setShowActiveFilters(true); + } + function toggleAll(state: 'on' | undefined) { + const newItems = items.map((item) => ({ + ...item, + checked: state, + })); + + setItems(newItems); + setShowActiveFilters(!!state); + } + + function search(term: string) { + const newItems = [...items]; + term = term.toLowerCase(); + items.forEach((item) => { + item.visible = item.id.toLowerCase().includes(term); + }); setItems(newItems); setShowActiveFilters(true); } @@ -83,20 +127,39 @@ export const FilterGroup: React.FC = ({ groupName, item panelPaddingSize="none" > - + -
- {items.map((item, index) => ( - updateItem(index)} - showIcons={true} - > - {item.name} - - ))} +
+ {items.map((item, index) => { + const itemStyle: any = {}; + itemStyle['paddingLeft'] = + hasGroupOptions && !item.childOptionIds ? 20 : itemStyle['paddingLeft']; + itemStyle['display'] = !item.visible ? 'none' : itemStyle['display']; + + return ( + toggleItem(index)} + showIcons={true} + style={itemStyle} + > + {item.name} + + ); + })}
+ {hasFooter && ( + +
+ toggleAll('on')}>Select all + toggleAll(undefined)}>Deselect all +
+
+ )} ); diff --git a/public/pages/Correlations/containers/CorrelationsContainer.tsx b/public/pages/Correlations/containers/CorrelationsContainer.tsx index 55dcbf747..aa052930c 100644 --- a/public/pages/Correlations/containers/CorrelationsContainer.tsx +++ b/public/pages/Correlations/containers/CorrelationsContainer.tsx @@ -527,6 +527,8 @@ export class Correlations extends React.Component diff --git a/public/pages/Correlations/utils/constants.tsx b/public/pages/Correlations/utils/constants.tsx index cb6e1c1b3..8c140ee62 100644 --- a/public/pages/Correlations/utils/constants.tsx +++ b/public/pages/Correlations/utils/constants.tsx @@ -7,7 +7,9 @@ import React from 'react'; import { CorrelationGraphData } from '../../../../types'; import { ruleSeverity, ruleTypes } from '../../Rules/utils/constants'; import { FilterItem } from '../components/FilterGroup'; -import { EuiIcon } from '@elastic/eui'; +import { EuiIcon, EuiTitle } from '@elastic/eui'; +import { logTypeCategories, logTypesByCategories } from '../../../utils/constants'; +import _ from 'lodash'; export const graphRenderOptions = { nodes: { @@ -47,12 +49,34 @@ export const graphRenderOptions = { }, }; -export const getDefaultLogTypeFilterItemOptions: () => FilterItem[] = () => - Object.values(ruleTypes).map((type) => ({ - name: `${type.label}`, - id: type.label.toLowerCase(), - checked: 'on', - })); +export const getDefaultLogTypeFilterItemOptions: () => FilterItem[] = () => { + const options: FilterItem[] = []; + logTypeCategories.forEach((category) => { + const logTypes = logTypesByCategories[category]; + options.push({ + name: ( + +

{category}

+
+ ), + id: category, + checked: 'on', + childOptionIds: new Set(logTypes.map(({ name }) => name)), + visible: true, + }); + + logTypes.forEach(({ name }) => { + options.push({ + name: _.capitalize(name), + id: name, + checked: 'on', + visible: true, + }); + }); + }); + + return options; +}; export const defaultSeverityFilterItemOptions: FilterItem[] = Object.values(ruleSeverity).map( (sev) => { @@ -64,6 +88,7 @@ export const defaultSeverityFilterItemOptions: FilterItem[] = Object.values(rule ), id: sev.value, checked: 'on', + visible: true, }; } ); diff --git a/public/pages/LogTypes/components/LogTypeForm.tsx b/public/pages/LogTypes/components/LogTypeForm.tsx index c2e302082..9f1cfdc90 100644 --- a/public/pages/LogTypes/components/LogTypeForm.tsx +++ b/public/pages/LogTypes/components/LogTypeForm.tsx @@ -20,7 +20,7 @@ import React from 'react'; import { LOG_TYPE_NAME_REGEX, validateName } from '../../../utils/validation'; import { NotificationsStart } from 'opensearch-dashboards/public'; import { useState } from 'react'; -import { logTypeCategoryOptions } from '../../../utils/constants'; +import { getLogTypeCategoryOptions } from '../../../utils/helpers'; export interface LogTypeFormProps { logTypeDetails: LogTypeItem; @@ -119,7 +119,7 @@ export const LogTypeForm: React.FC = ({ ({ + options={getLogTypeCategoryOptions().map((option) => ({ ...option, disabled: !isEditMode || (isEditMode && !!logTypeDetails.detectionRulesCount), }))} diff --git a/public/pages/LogTypes/utils/helpers.tsx b/public/pages/LogTypes/utils/helpers.tsx index 3c09c9803..4826ff4e1 100644 --- a/public/pages/LogTypes/utils/helpers.tsx +++ b/public/pages/LogTypes/utils/helpers.tsx @@ -9,7 +9,7 @@ import { LogType } from '../../../../types'; import { capitalize } from 'lodash'; import { Search } from '@opensearch-project/oui/src/eui_components/basic_table'; import { ruleSource } from '../../Rules/utils/constants'; -import { logTypesByCategories } from '../../../utils/constants'; +import { logTypeCategories } from '../../../utils/constants'; export const getLogTypesTableColumns = ( showDetails: (id: string) => void, @@ -71,11 +71,9 @@ export const getLogTypesTableSearchConfig = (): Search => { field: 'category', name: 'Category', multiSelect: 'or', - options: Object.keys(logTypesByCategories) - .map((category) => ({ - value: category, - })) - .sort((a, b) => (a.value < b.value ? -1 : a.value > b.value ? 1 : 0)), + options: logTypeCategories.map((category) => ({ + value: category, + })), }, { type: 'field_value_selection', diff --git a/public/store/CorrelationsStore.ts b/public/store/CorrelationsStore.ts index ba2a53240..86d9b3adf 100644 --- a/public/store/CorrelationsStore.ts +++ b/public/store/CorrelationsStore.ts @@ -226,9 +226,6 @@ export class CorrelationsStore implements ICorrelationsStore { if (findingRes.ok) { findingRes.response.findings.forEach((f) => { const rule = allRules.find((rule) => rule._id === f.queries[0].id); - if (!rule) { - console.log(f.queries[0].id); - } findings[f.id] = { ...f, id: f.id, diff --git a/public/store/LogTypeStore.ts b/public/store/LogTypeStore.ts index 2e49a5725..3528406df 100644 --- a/public/store/LogTypeStore.ts +++ b/public/store/LogTypeStore.ts @@ -9,7 +9,7 @@ import LogTypeService from '../services/LogTypeService'; import { errorNotificationToast } from '../utils/helpers'; import { DataStore } from './DataStore'; import { ruleTypes } from '../pages/Rules/utils/constants'; -import { logTypesByCategories } from '../utils/constants'; +import { logTypeCategories, logTypesByCategories } from '../utils/constants'; export class LogTypeStore { constructor(private service: LogTypeService, private notifications: NotificationsStart) {} @@ -73,6 +73,20 @@ export class LogTypeStore { logTypesByCategories[logType.category] = logTypesByCategories[logType.category] || []; logTypesByCategories[logType.category].push(logType); }); + logTypeCategories.splice( + 0, + logTypeCategories.length, + ...Object.keys(logTypesByCategories).sort((a, b) => { + if (a === 'Other') { + return 1; + } else if (b === 'Other') { + return -1; + } else { + return a < b ? -1 : a > b ? 1 : 0; + } + }) + ); + return logTypes; } diff --git a/public/utils/constants.tsx b/public/utils/constants.tsx deleted file mode 100644 index 0acfc360c..000000000 --- a/public/utils/constants.tsx +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import React from 'react'; -import { SimpleSavedObject } from 'opensearch-dashboards/public'; -import { Detector, LogType, ServerResponse } from '../../types'; -import { DetectorInput, PeriodSchedule } from '../../models/interfaces'; -import { DetectorHit } from '../../server/models/interfaces'; -import { EuiText } from '@elastic/eui'; -import _ from 'lodash'; - -export const DATE_MATH_FORMAT = 'YYYY-MM-DDTHH:mm:ss.SSSZ'; -export const MAX_RECENTLY_USED_TIME_RANGES = 5; -export const DEFAULT_DATE_RANGE = { start: 'now-24h', end: 'now' }; - -export const PLUGIN_NAME = 'opensearch_security_analytics_dashboards'; -export const OS_NOTIFICATION_PLUGIN = 'opensearch-notifications'; - -export const DEFAULT_EMPTY_DATA = '-'; - -export let isDarkMode: boolean = false; - -export function setDarkMode(isDarkModeSetting: boolean) { - isDarkMode = isDarkModeSetting; -} - -export const ROUTES = Object.freeze({ - ALERTS: '/alerts', - DETECTORS: '/detectors', - FINDINGS: '/findings', - OVERVIEW: '/overview', - RULES: '/rules', - RULES_CREATE: '/create-rule', - RULES_EDIT: '/edit-rule', - RULES_IMPORT: '/import-rule', - RULES_DUPLICATE: '/duplicate-rule', - DETECTORS_CREATE: '/create-detector', - DETECTOR_DETAILS: '/detector-details', - EDIT_DETECTOR_DETAILS: '/edit-detector-details', - EDIT_DETECTOR_RULES: '/edit-detector-rules', - EDIT_FIELD_MAPPINGS: '/edit-field-mappings', - EDIT_DETECTOR_ALERT_TRIGGERS: '/edit-alert-triggers', - CORRELATIONS: '/correlations', - CORRELATION_RULES: '/correlations/rules', - CORRELATION_RULE_CREATE: '/correlations/create-rule', - CORRELATION_RULE_EDIT: '/correlations/rule', - LOG_TYPES: '/log-types', - LOG_TYPES_CREATE: '/create-log-type', - - get LANDING_PAGE(): string { - return this.OVERVIEW; - }, -}); - -export const NOTIFICATIONS_HREF = 'notifications-dashboards#/channels'; -export const getNotificationDetailsHref = (channelId: string) => - `notifications-dashboards#/channels-details/${channelId}`; - -export const BREADCRUMBS = Object.freeze({ - SECURITY_ANALYTICS: { text: 'Security Analytics', href: '#/' }, - OVERVIEW: { text: 'Overview', href: `#${ROUTES.OVERVIEW}` }, - FINDINGS: { text: 'Findings', href: `#${ROUTES.FINDINGS}` }, - DETECTORS: { text: 'Detectors', href: `#${ROUTES.DETECTORS}` }, - DETECTORS_CREATE: { text: 'Create detector', href: `#${ROUTES.DETECTORS_CREATE}` }, - DETECTORS_DETAILS: (name: string, detectorId: string) => ({ - text: `${name}`, - href: `#${ROUTES.DETECTOR_DETAILS}/${detectorId}`, - }), - DETECTORS_EDIT_DETAILS: (name: string, detectorId: string) => ({ - text: `${name}`, - href: `#${ROUTES.EDIT_DETECTOR_DETAILS}/${detectorId}`, - }), - RULES: { text: 'Detection rules', href: `#${ROUTES.RULES}` }, - ALERTS: { text: 'Alerts', href: `#${ROUTES.ALERTS}` }, - RULES_CREATE: { text: 'Create detection rule', href: `#${ROUTES.RULES_CREATE}` }, - RULES_EDIT: { text: 'Edit rule', href: `#${ROUTES.RULES_EDIT}` }, - RULES_DUPLICATE: { text: 'Duplicate rule', href: `#${ROUTES.RULES_DUPLICATE}` }, - RULES_IMPORT: { text: 'Import rule', href: `#${ROUTES.RULES_IMPORT}` }, - CORRELATIONS: { text: 'Correlations', href: `#${ROUTES.CORRELATIONS}` }, - CORRELATION_RULES: { text: 'Correlation rules', href: `#${ROUTES.CORRELATION_RULES}` }, - CORRELATIONS_RULE_CREATE: { - text: 'Create correlation rule', - href: `#${ROUTES.CORRELATION_RULE_CREATE}`, - }, - LOG_TYPES: { text: 'Log types', href: `#${ROUTES.LOG_TYPES}` }, - LOG_TYPE_CREATE: { text: 'Create log type', href: `#${ROUTES.LOG_TYPES_CREATE}` }, -}); - -export enum SortDirection { - ASC = 'asc', - DESC = 'desc', -} - -export const FixedTimeunitOptions = [ - { value: 'ms', text: 'Millisecond(s)' }, - { value: 's', text: 'Second(s)' }, - { value: 'm', text: 'Minute(s)' }, - { value: 'h', text: 'Hour(s)' }, - { value: 'd', text: 'Day(s)' }, -]; - -export const CalendarTimeunitOptions = [ - { value: 'm', text: 'Minute' }, - { value: 'h', text: 'Hour' }, - { value: 'd', text: 'Day' }, - { value: 'w', text: 'Week' }, - { value: 'M', text: 'Month' }, - { value: 'q', text: 'Quarter' }, - { value: 'y', text: 'Year' }, -]; - -export enum IntervalType { - FIXED = 'fixed', - CALENDAR = 'calendar', -} - -export const scheduleUnitText: { [key: string]: string } = { - MINUTES: 'minute', - HOURS: 'hour', - DAYS: 'day', - WEEKS: 'week', - MONTHS: 'month', - QUARTERS: 'quarter', - YEARS: 'year', -}; - -export const EMPTY_DEFAULT_PERIOD_SCHEDULE: PeriodSchedule = { - period: { - interval: 1, - unit: 'MINUTES', - }, -}; - -export const EMPTY_DEFAULT_DETECTOR_INPUT: DetectorInput = { - detector_input: { - description: '', - indices: [], - pre_packaged_rules: [], - custom_rules: [], - }, -}; - -export const EMPTY_DEFAULT_DETECTOR: Detector = { - type: 'detector', - detector_type: 'network', - name: '', - enabled: true, - createdBy: '', - schedule: EMPTY_DEFAULT_PERIOD_SCHEDULE, - inputs: [EMPTY_DEFAULT_DETECTOR_INPUT], - triggers: [], -}; - -export const EMPTY_DEFAULT_DETECTOR_HIT: DetectorHit = { - _id: '', - _index: '', - _source: { - ...EMPTY_DEFAULT_DETECTOR, - last_update_time: 0, - enabled_time: 0, - }, -}; - -export const ALERT_STATE = Object.freeze({ - ACTIVE: 'ACTIVE', - ACKNOWLEDGED: 'ACKNOWLEDGED', - COMPLETED: 'COMPLETED', - ERROR: 'ERROR', - DELETED: 'DELETED', -}); - -export const logTypesWithDashboards = new Set(['network', 'cloudtrail', 's3']); - -export const pendingDashboardCreations: { - [detectorId: string]: undefined | Promise>>; -} = {}; - -const logTypeCategoryInfo = [ - { name: 'Access Management', description: 'User access, authentication, group management' }, - { name: 'Applications', description: 'Application lifecycle, API, and web resources activities' }, - { name: 'Cloud Services', description: 'Services managed by cloud providers' }, - { name: 'Network Activity', description: 'DNS, HTTP, Email, SSH, FTP, DHCP, RDP' }, - { name: 'System Activity', description: 'System monitoring logs' }, - { name: 'Other', description: 'Logs not covered in other categories' }, -]; - -export const logTypeCategoryOptions: any[] = logTypeCategoryInfo.map(({ name, description }) => ({ - value: name, - inputDisplay: name, - dropdownDisplay: ( - <> - {name} - -

{description}

-
- - ), -})); - -export const logTypesByCategories: { [category: string]: LogType[] } = {}; diff --git a/public/utils/helpers.tsx b/public/utils/helpers.tsx index c194c3649..ef30e0019 100644 --- a/public/utils/helpers.tsx +++ b/public/utils/helpers.tsx @@ -17,7 +17,13 @@ import { import moment from 'moment'; import { PeriodSchedule } from '../../models/interfaces'; import React from 'react'; -import { DEFAULT_EMPTY_DATA, logTypesByCategories, scheduleUnitText } from './constants'; +import { + DEFAULT_EMPTY_DATA, + logTypeCategories, + logTypeCategoryDescription, + logTypesByCategories, + scheduleUnitText, +} from './constants'; import { RuleItem, RuleItemInfo, @@ -164,7 +170,7 @@ export function renderVisualization(spec: TopLevelSpec, containerId: string) { console.error(err) ); } catch (error) { - console.log(error); + console.error(error); } function renderVegaSpec(spec: {}) { @@ -319,28 +325,19 @@ export const getSeverityBadge = (severity: string) => { }; export function formatToLogTypeOptions(logTypesByCategories: { [category: string]: LogType[] }) { - return Object.entries(logTypesByCategories) - .map(([category, logTypes]) => { - return { - label: category, - value: category, - options: logTypes - .map(({ name }) => ({ - label: name, - value: name.toLowerCase(), - })) - .sort((a, b) => (a.label < b.label ? -1 : a.label > b.label ? 1 : 0)), - }; - }) - .sort((a, b) => { - if (a.label === 'Other') { - return 1; - } else if (b.label === 'Other') { - return -1; - } else { - return a.label < b.label ? -1 : a.label > b.label ? 1 : 0; - } - }); + return logTypeCategories.map((category) => { + const logTypes = logTypesByCategories[category]; + return { + label: category, + value: category, + options: logTypes + .map(({ name }) => ({ + label: name, + value: name.toLowerCase(), + })) + .sort((a, b) => (a.label < b.label ? -1 : a.label > b.label ? 1 : 0)), + }; + }); } export async function getLogTypeOptions() { @@ -375,3 +372,18 @@ export function getLogTypeFilterOptions() { return options; } + +export function getLogTypeCategoryOptions(): any[] { + return logTypeCategoryDescription.map(({ name, description }) => ({ + value: name, + inputDisplay: name, + dropdownDisplay: ( + <> + {name} + +

{description}

+
+ + ), + })); +} diff --git a/server/services/RuleService.ts b/server/services/RuleService.ts index 1d46741d5..bee609eb9 100644 --- a/server/services/RuleService.ts +++ b/server/services/RuleService.ts @@ -80,9 +80,7 @@ export default class RulesService { if (false_positives.length > 0) { jsonPayload['falsepositives'] = false_positives.map((falsePos) => falsePos.value); } - console.log(jsonPayload); const ruleYamlPayload = safeDump(jsonPayload); - console.log(ruleYamlPayload); const params: CreateRuleParams = { body: ruleYamlPayload, @@ -226,12 +224,8 @@ export default class RulesService { if (false_positives.length > 0) { jsonPayload['falsepositives'] = false_positives.map((falsePos) => falsePos.value); } - console.log(jsonPayload); const ruleYamlPayload = safeDump(jsonPayload); - - console.log(ruleYamlPayload); - const params: UpdateRuleParams = { body: ruleYamlPayload, category, ruleId }; const { callAsCurrentUser: callWithRequest } = this.osDriver.asScoped(request); const createRuleResponse: UpdateRuleResponse = await callWithRequest(