diff --git a/public/components/Charts/ChartContainer.scss b/public/components/Charts/ChartContainer.scss
index 0cfaa2fa6..2eeb2a340 100644
--- a/public/components/Charts/ChartContainer.scss
+++ b/public/components/Charts/ChartContainer.scss
@@ -1,6 +1,6 @@
.chart-view-container {
position: relative;
- width: 100%;
+ width: 90%;
height: 100%;
.chart-view-container-mask {
diff --git a/public/components/DataSourceThreatAlertsCard/DataSourceThreatAlertsCard.tsx b/public/components/DataSourceThreatAlertsCard/DataSourceThreatAlertsCard.tsx
index 32891aceb..5a2cd5cca 100644
--- a/public/components/DataSourceThreatAlertsCard/DataSourceThreatAlertsCard.tsx
+++ b/public/components/DataSourceThreatAlertsCard/DataSourceThreatAlertsCard.tsx
@@ -5,7 +5,6 @@
import React, { useCallback, useEffect, useState } from 'react';
import {
- EuiBadge,
EuiBasicTable,
EuiBasicTableColumn,
EuiFlexGroup,
@@ -23,10 +22,9 @@ import {
import {
dataSourceFilterFn,
errorNotificationToast,
- getBadgeText,
- getSeverityColor,
getTruncatedText,
renderTime,
+ getAlertSeverityBadge,
} from '../../utils/helpers';
import { THREAT_ALERTS_NAV_ID } from '../../utils/constants';
import {
@@ -137,17 +135,7 @@ export const DataSourceThreatAlertsCard: React.FC = (
name: 'Alert severity',
sortable: true,
align: 'left',
- render: (severity: string) => {
- const severityColor = getSeverityColor(severity);
- return (
-
- {getBadgeText(severity)}
-
- );
- },
+ render: getAlertSeverityBadge,
},
];
diff --git a/public/components/MDS/DataSourceMenuWrapper.tsx b/public/components/MDS/DataSourceMenuWrapper.tsx
index 9a5c1049e..69ddc8419 100644
--- a/public/components/MDS/DataSourceMenuWrapper.tsx
+++ b/public/components/MDS/DataSourceMenuWrapper.tsx
@@ -159,6 +159,7 @@ export const DataSourceMenuWrapper: React.FC = ({
ROUTES.RULES_DUPLICATE,
ROUTES.LOG_TYPES_CREATE,
ROUTES.CORRELATION_RULE_CREATE,
+ ROUTES.GETTING_STARTED,
ROUTES.ROOT,
]}
render={() => {
diff --git a/public/components/PageHeader/PageHeader.tsx b/public/components/PageHeader/PageHeader.tsx
index bf1770129..548967cdf 100644
--- a/public/components/PageHeader/PageHeader.tsx
+++ b/public/components/PageHeader/PageHeader.tsx
@@ -7,14 +7,13 @@ import React from 'react';
import {
TopNavControlData,
TopNavControlDescriptionData,
- TopNavControlLinkData,
} from '../../../../../src/plugins/navigation/public';
import { getApplication, getNavigationUI, getUseUpdatedUx } from '../../services/utils/constants';
export interface PageHeaderProps {
appRightControls?: TopNavControlData[];
appBadgeControls?: TopNavControlData[];
- appDescriptionControls?: (TopNavControlDescriptionData | TopNavControlLinkData)[];
+ appDescriptionControls?: TopNavControlDescriptionData[];
}
export const PageHeader: React.FC = ({
diff --git a/public/pages/CreateDetector/components/ConfigureAlerts/components/AlertCondition/__snapshots__/AlertConditionPanel.test.tsx.snap b/public/pages/CreateDetector/components/ConfigureAlerts/components/AlertCondition/__snapshots__/AlertConditionPanel.test.tsx.snap
index 6fe293be6..c28c89fc3 100644
--- a/public/pages/CreateDetector/components/ConfigureAlerts/components/AlertCondition/__snapshots__/AlertConditionPanel.test.tsx.snap
+++ b/public/pages/CreateDetector/components/ConfigureAlerts/components/AlertCondition/__snapshots__/AlertConditionPanel.test.tsx.snap
@@ -476,7 +476,6 @@ Object {
tabindex="-1"
>
=
name: 'Threat intel source',
field: 'ioc_feed_ids',
render: (ioc_feed_ids: ThreatIntelFinding['ioc_feed_ids']) => {
- return {ioc_feed_ids[0]?.feed_id ?? DEFAULT_EMPTY_DATA};
+ return (
+ {ioc_feed_ids.map((ids) => ids.feed_name).join(', ') || DEFAULT_EMPTY_DATA}
+ );
},
},
{
diff --git a/public/pages/Main/Main.tsx b/public/pages/Main/Main.tsx
index 78fe978f8..463aa64f5 100644
--- a/public/pages/Main/Main.tsx
+++ b/public/pages/Main/Main.tsx
@@ -18,9 +18,14 @@ import {
EuiFlexItem,
} from '@elastic/eui';
import { Toast } from '@opensearch-project/oui/src/eui_components/toast/global_toast_list';
-import { AppMountParameters, CoreStart, SavedObject } from 'opensearch-dashboards/public';
+import { AppMountParameters, CoreStart } from 'opensearch-dashboards/public';
import { SaContextConsumer } from '../../services';
-import { DEFAULT_DATE_RANGE, DATE_TIME_FILTER_KEY, ROUTES, dataSourceObservable } from '../../utils/constants';
+import {
+ DEFAULT_DATE_RANGE,
+ DATE_TIME_FILTER_KEY,
+ ROUTES,
+ dataSourceObservable,
+} from '../../utils/constants';
import { CoreServicesConsumer } from '../../components/core_services';
import Findings from '../Findings';
import Detectors from '../Detectors';
@@ -56,15 +61,14 @@ import { DataSourceManagementPluginSetup } from '../../../../../src/plugins/data
import { DataSourceMenuWrapper } from '../../components/MDS/DataSourceMenuWrapper';
import { DataSourceOption } from 'src/plugins/data_source_management/public/components/data_source_menu/types';
import { DataSourceContext, DataSourceContextConsumer } from '../../services/DataSourceContext';
-import { dataSourceInfo } from '../../services/utils/constants';
+import { dataSourceInfo, getUseUpdatedUx } from '../../services/utils/constants';
import { ThreatIntelOverview } from '../ThreatIntel/containers/Overview/ThreatIntelOverview';
import { AddThreatIntelSource } from '../ThreatIntel/containers/AddThreatIntelSource/AddThreatIntelSource';
import { ThreatIntelScanConfigForm } from '../ThreatIntel/containers/ScanConfiguration/ThreatIntelScanConfigForm';
import { ThreatIntelSource } from '../ThreatIntel/containers/ThreatIntelSource/ThreatIntelSource';
-import * as pluginManifest from '../../../opensearch_dashboards.json';
-import { DataSourceAttributes } from '../../../../../src/plugins/data_source/common/data_sources';
-import semver from 'semver';
import queryString from 'query-string';
+import { dataSourceFilterFn } from '../../utils/helpers';
+import { GettingStartedContent } from '../Overview/components/GettingStarted/GettingStartedContent';
enum Navigation {
SecurityAnalytics = 'Security Analytics',
@@ -137,20 +141,21 @@ export default class Main extends Component {
const defaultDateTimeFilter = cachedDateTimeFilter
? JSON.parse(cachedDateTimeFilter)
: {
- startTime: DEFAULT_DATE_RANGE.start,
- endTime: DEFAULT_DATE_RANGE.end,
- };
- let dataSourceId = "";
- let dataSourceLabel = "";
+ startTime: DEFAULT_DATE_RANGE.start,
+ endTime: DEFAULT_DATE_RANGE.end,
+ };
+ let dataSourceId = '';
+ let dataSourceLabel = '';
if (props.multiDataSourceEnabled) {
- const { dataSourceId: parsedDataSourceId, dataSourceLabel: parsedDataSourceLabel } = queryString.parse(
- this.props.location.search
- ) as {
+ const {
+ dataSourceId: parsedDataSourceId,
+ dataSourceLabel: parsedDataSourceLabel,
+ } = queryString.parse(this.props.location.search) as {
dataSourceId: string;
dataSourceLabel: string;
};
dataSourceId = parsedDataSourceId;
- dataSourceLabel = parsedDataSourceLabel || "";
+ dataSourceLabel = parsedDataSourceLabel || '';
if (dataSourceId) {
dataSourceObservable.next({ id: dataSourceId, label: dataSourceLabel });
@@ -163,10 +168,10 @@ export default class Main extends Component {
dateTimeFilter: defaultDateTimeFilter,
showFlyoutData: null,
/**
- * undefined: need data source picker to help to determine which data source to use.
- * empty string: using the local cluster.
- * string: using the selected data source.
- */
+ * undefined: need data source picker to help to determine which data source to use.
+ * empty string: using the local cluster.
+ * string: using the selected data source.
+ */
dataSourceLoading: dataSourceId === undefined ? props.multiDataSourceEnabled : false,
selectedDataSource: { id: dataSourceId },
dataSourceMenuReadOnly: false,
@@ -267,7 +272,10 @@ export default class Main extends Component {
selectedDataSource: { ...sources[0] },
});
}
- dataSourceObservable.next({ id: this.state.selectedDataSource.id, label: this.state.selectedDataSource.label });
+ dataSourceObservable.next({
+ id: this.state.selectedDataSource.id,
+ label: this.state.selectedDataSource.label,
+ });
if (dataSourceLoading) {
this.setState({ dataSourceLoading: false });
}
@@ -406,17 +414,6 @@ export default class Main extends Component {
];
};
- dataSourceFilterFn = (dataSource: SavedObject) => {
- const dataSourceVersion = dataSource?.attributes?.dataSourceVersion || '';
- const installedPlugins = dataSource?.attributes?.installedPlugins || [];
- return (
- semver.satisfies(dataSourceVersion, pluginManifest.supportedOSDataSourceVersions) &&
- pluginManifest.requiredOSDataSourcePlugins.every((plugin) =>
- installedPlugins.includes(plugin)
- )
- );
- };
-
render() {
const {
landingPage,
@@ -463,7 +460,7 @@ export default class Main extends Component {
dataSourceLoading={this.state.dataSourceLoading}
dataSourceMenuReadOnly={dataSourceMenuReadOnly}
setHeaderActionMenu={setActionMenu}
- dataSourceFilterFn={this.dataSourceFilterFn}
+ dataSourceFilterFn={dataSourceFilterFn}
/>
)}
{!dataSourceLoading && services && (
@@ -620,6 +617,17 @@ export default class Main extends Component {
/>
)}
/>
+ {getUseUpdatedUx() && (
+ (
+ {}}
+ />
+ )}
+ />
+ )}
(
diff --git a/public/pages/Overview/components/GettingStarted/GettingStartedContent.tsx b/public/pages/Overview/components/GettingStarted/GettingStartedContent.tsx
new file mode 100644
index 000000000..14a913bba
--- /dev/null
+++ b/public/pages/Overview/components/GettingStarted/GettingStartedContent.tsx
@@ -0,0 +1,210 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import React, { useMemo } from 'react';
+import {
+ EuiHorizontalRule,
+ EuiLink,
+ EuiPanel,
+ EuiSpacer,
+ EuiSteps,
+ EuiText,
+ EuiTitle,
+} from '@elastic/eui';
+import { EuiContainedStepProps } from '@elastic/eui/src/components/steps/steps';
+import {
+ BREADCRUMBS,
+ DETECTORS_NAV_ID,
+ DETECTION_RULE_NAV_ID,
+ FINDINGS_NAV_ID,
+ ROUTES,
+ THREAT_ALERTS_NAV_ID,
+ THREAT_INTEL_NAV_ID,
+ CORRELATIONS_RULE_NAV_ID,
+} from '../../../../utils/constants';
+import { RouteComponentProps } from 'react-router-dom';
+import { GetStartedStep } from './GetStartedStep';
+import { moreLink } from '../../utils/constants';
+import { setBreadcrumbs } from '../../../../utils/helpers';
+import { getApplication, getUseUpdatedUx } from '../../../../services/utils/constants';
+import { PageHeader } from '../../../../components/PageHeader/PageHeader';
+
+export interface GettingStartedPopupProps {
+ onStepClicked: () => void;
+ history: RouteComponentProps['history'];
+}
+
+export const GettingStartedContent: React.FC = ({
+ onStepClicked,
+ history,
+}) => {
+ const useUpdatedUx = getUseUpdatedUx();
+ if (useUpdatedUx) {
+ setBreadcrumbs([BREADCRUMBS.GETTING_STARTED]);
+ }
+ const onActionClick = (appId: string, route: string) => {
+ if (useUpdatedUx) {
+ const url = getApplication().getUrlForApp(appId, { path: `#${route}` });
+ getApplication().navigateToUrl(url);
+ } else {
+ history.push(route);
+ }
+ };
+
+ const steps: EuiContainedStepProps[] = useMemo(
+ () => [
+ {
+ title: 'Create security detector',
+ children: (
+ {
+ onStepClicked();
+ onActionClick(DETECTORS_NAV_ID, ROUTES.DETECTORS_CREATE);
+ },
+ opts: {
+ fill: true,
+ },
+ },
+ ]}
+ />
+ ),
+ },
+ {
+ title: 'Set up threat intelligence analytics',
+ children: (
+ {
+ onStepClicked();
+ onActionClick(THREAT_INTEL_NAV_ID, ROUTES.THREAT_INTEL_OVERVIEW);
+ },
+ },
+ ]}
+ />
+ ),
+ },
+ {
+ title: 'Discover security findings',
+ children: (
+ {
+ onStepClicked();
+ onActionClick(FINDINGS_NAV_ID, ROUTES.FINDINGS);
+ },
+ },
+ ]}
+ />
+ ),
+ },
+ {
+ title: 'View security alerts',
+ children: (
+ {
+ onStepClicked();
+ onActionClick(THREAT_ALERTS_NAV_ID, ROUTES.ALERTS);
+ },
+ },
+ ]}
+ />
+ ),
+ },
+ {
+ title: 'Create custom rules for detectors',
+ children: (
+ {
+ onStepClicked();
+ onActionClick(DETECTION_RULE_NAV_ID, ROUTES.RULES);
+ },
+ },
+ ]}
+ />
+ ),
+ },
+ {
+ title: 'Set up correlation rules',
+ children: (
+ {
+ onStepClicked();
+ onActionClick(CORRELATIONS_RULE_NAV_ID, ROUTES.CORRELATION_RULES);
+ },
+ },
+ ]}
+ />
+ ),
+ },
+ ],
+ [onStepClicked]
+ );
+
+ const stepsComponent = ;
+
+ return (
+ <>
+
+
+ Get started with Security analytics
+
+
+
+
+ Generates critical security insights from your event logs.
+
+ Learn more
+
+
+
+
+
+ {useUpdatedUx ? {stepsComponent} : stepsComponent}
+ >
+ );
+};
diff --git a/public/pages/Overview/components/GettingStarted/GettingStartedPopup.tsx b/public/pages/Overview/components/GettingStarted/GettingStartedPopup.tsx
deleted file mode 100644
index 62a086f7b..000000000
--- a/public/pages/Overview/components/GettingStarted/GettingStartedPopup.tsx
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright OpenSearch Contributors
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import React, { useMemo } from 'react';
-import { EuiHorizontalRule, EuiLink, EuiSpacer, EuiSteps, EuiText, EuiTitle } from '@elastic/eui';
-import { EuiContainedStepProps } from '@elastic/eui/src/components/steps/steps';
-import { ROUTES } from '../../../../utils/constants';
-import { RouteComponentProps } from 'react-router-dom';
-import { GetStartedStep } from './GetStartedStep';
-import { moreLink } from '../../utils/constants';
-
-export interface GettingStartedPopupProps {
- dismissPopup: () => void;
- history: RouteComponentProps['history'];
-}
-
-export const GettingStartedPopup: React.FC = ({
- dismissPopup,
- history,
-}) => {
- const steps: EuiContainedStepProps[] = useMemo(
- () => [
- {
- title: 'Create security detector',
- children: (
- {
- dismissPopup();
- history.push(ROUTES.DETECTORS_CREATE);
- },
- opts: {
- fill: true,
- },
- },
- ]}
- />
- ),
- },
- {
- title: 'Discover security findings',
- children: (
- {
- dismissPopup();
- history.push(ROUTES.FINDINGS);
- },
- },
- ]}
- />
- ),
- },
- {
- title: 'View security alerts',
- children: (
- {
- dismissPopup();
- history.push(ROUTES.ALERTS);
- },
- },
- ]}
- />
- ),
- },
- {
- title: 'Create custom rules for detectors',
- children: (
- {
- dismissPopup();
- history.push(ROUTES.RULES);
- },
- },
- ]}
- />
- ),
- },
- ],
- [dismissPopup]
- );
-
- return (
- <>
-
- Get started with Security analytics
-
-
-
-
- Generates critical security insights from your event logs.
-
- Learn more
-
-
-
-
-
- >
- );
-};
diff --git a/public/pages/Overview/components/Widgets/RecentAlertsWidget.tsx b/public/pages/Overview/components/Widgets/RecentAlertsWidget.tsx
index 14bedd4f5..b719ee682 100644
--- a/public/pages/Overview/components/Widgets/RecentAlertsWidget.tsx
+++ b/public/pages/Overview/components/Widgets/RecentAlertsWidget.tsx
@@ -4,12 +4,11 @@
*/
import { EuiBasicTableColumn, EuiSmallButton, EuiEmptyPrompt } from '@elastic/eui';
-import { DEFAULT_EMPTY_DATA, ROUTES, SortDirection } from '../../../../utils/constants';
+import { ROUTES, SortDirection } from '../../../../utils/constants';
import React, { useEffect, useState } from 'react';
import { TableWidget } from './TableWidget';
import { WidgetContainer } from './WidgetContainer';
-import { parseAlertSeverityToOption } from '../../../CreateDetector/components/ConfigureAlerts/utils/helpers';
-import { renderTime } from '../../../../utils/helpers';
+import { getAlertSeverityBadge, renderTime } from '../../../../utils/helpers';
import { OverviewAlertItem } from '../../../../../types';
const columns: EuiBasicTableColumn[] = [
@@ -31,7 +30,7 @@ const columns: EuiBasicTableColumn[] = [
name: 'Alert severity',
sortable: true,
align: 'left',
- render: (severity: string) => parseAlertSeverityToOption(severity)?.label || DEFAULT_EMPTY_DATA,
+ render: (severity: string) => getAlertSeverityBadge(severity),
},
];
@@ -76,7 +75,7 @@ export const RecentAlertsWidget: React.FC = ({
);
return (
-
+
[] = [
{
@@ -32,7 +33,7 @@ const columns: EuiBasicTableColumn[] = [
sortable: false,
align: 'left',
width: '20%',
- render: (ruleSeverity: string) => capitalizeFirstLetter(ruleSeverity),
+ render: (ruleSeverity: string) => getSeverityBadge(ruleSeverity), // capitalizeFirstLetter(ruleSeverity),
},
{
field: 'detector',
@@ -75,13 +76,13 @@ export const RecentFindingsWidget: React.FC = ({
);
}, [items]);
- const actions = React.useMemo(
- () => [View all findings],
- []
- );
+ const actions = React.useMemo(() => {
+ const baseUrl = getUseUpdatedUx() ? getApplication().getUrlForApp(FINDINGS_NAV_ID) : '';
+ return [View all];
+ }, []);
return (
-
+
[] = [
+ {
+ name: 'Time',
+ field: 'timestamp',
+ render: (timestamp: number) => renderTime(timestamp),
+ },
+ {
+ name: 'Indicator of compromise',
+ field: 'ioc_value',
+ },
+ {
+ name: 'Indicator type',
+ field: 'ioc_type',
+ render: (iocType: ThreatIntelIocType) => IocLabel[iocType],
+ },
+ {
+ name: 'Threat intel source',
+ field: 'ioc_feed_ids',
+ render: (ioc_feed_ids: ThreatIntelFinding['ioc_feed_ids']) => {
+ return (
+ {ioc_feed_ids.map((ids) => ids.feed_name).join(', ') || DEFAULT_EMPTY_DATA}
+ );
+ },
+ },
+];
+
+export interface RecentThreatIntelFindingsWidgetProps {
+ items: ThreatIntelFinding[];
+ loading?: boolean;
+}
+
+export const RecentThreatIntelFindingsWidget: React.FC = ({
+ items,
+ loading = false,
+}) => {
+ const [findingItems, setFindingItems] = useState([]);
+ const [widgetEmptyMessage, setWidgetEmptyMessage] = useState(
+ undefined
+ );
+
+ useEffect(() => {
+ items.sort((a, b) => {
+ return b.timestamp - a.timestamp;
+ });
+ setFindingItems(items.slice(0, 20));
+ setWidgetEmptyMessage(
+ items.length > 0 ? undefined : (
+
+ No recent findings.Adjust the time range to
+ see more results.
+
+ }
+ />
+ )
+ );
+ }, [items]);
+
+ const threatIntelFindingsUrl = `${getApplication().getUrlForApp(FINDINGS_NAV_ID, {
+ path: `#${ROUTES.FINDINGS}`,
+ })}?detectionType=${FindingTabId.ThreatIntel}`;
+ const actions = React.useMemo(
+ () => [
+ getApplication().navigateToUrl(threatIntelFindingsUrl)}>
+ View all
+ ,
+ ],
+ []
+ );
+
+ return (
+
+
+
+ );
+};
diff --git a/public/pages/Overview/components/Widgets/Summary.tsx b/public/pages/Overview/components/Widgets/Summary.tsx
index 58f09264e..2e2a50f60 100644
--- a/public/pages/Overview/components/Widgets/Summary.tsx
+++ b/public/pages/Overview/components/Widgets/Summary.tsx
@@ -27,6 +27,7 @@ import { ROUTES } from '../../../../utils/constants';
import { ChartContainer } from '../../../../components/Charts/ChartContainer';
import { getLogTypeLabel } from '../../../LogTypes/utils/helpers';
import { OverviewAlertItem, OverviewFindingItem } from '../../../../../types';
+import { getUseUpdatedUx } from '../../../../services/utils/constants';
export interface SummaryProps {
findings: OverviewFindingItem[];
@@ -147,22 +148,24 @@ export const Summary: React.FC = ({
return (
-
- {activeAlerts === 0 && totalFindings === 0 ? null : (
-
- {createStatComponent(
- 'Total active alerts',
- { url: ROUTES.ALERTS, color: 'danger' },
- activeAlerts
- )}
- {createStatComponent(
- 'Total findings',
- { url: ROUTES.FINDINGS, color: 'primary' },
- totalFindings
- )}
-
- )}
-
+ {!getUseUpdatedUx() && (
+
+ {activeAlerts === 0 && totalFindings === 0 ? null : (
+
+ {createStatComponent(
+ 'Total active alerts',
+ { url: ROUTES.ALERTS, color: 'danger' },
+ activeAlerts
+ )}
+ {createStatComponent(
+ 'Total findings',
+ { url: ROUTES.FINDINGS, color: 'primary' },
+ totalFindings
+ )}
+
+ )}
+
+ )}
{activeAlerts === 0 && totalFindings === 0 ? (
{
return (
-
+
{children}
diff --git a/public/pages/Overview/containers/Overview/Overview.tsx b/public/pages/Overview/containers/Overview/Overview.tsx
index 09745a79a..cf88ead9e 100644
--- a/public/pages/Overview/containers/Overview/Overview.tsx
+++ b/public/pages/Overview/containers/Overview/Overview.tsx
@@ -13,6 +13,9 @@ import {
EuiTitle,
EuiSpacer,
EuiSmallButton,
+ EuiCard,
+ EuiPanel,
+ EuiStat,
} from '@elastic/eui';
import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react';
import {
@@ -24,16 +27,18 @@ import {
import { CoreServicesContext } from '../../../../../public/components/core_services';
import { RecentAlertsWidget } from '../../components/Widgets/RecentAlertsWidget';
import { RecentFindingsWidget } from '../../components/Widgets/RecentFindingsWidget';
-import { DetectorsWidget } from '../../components/Widgets/DetectorsWidget';
import { OverviewViewModelActor } from '../../models/OverviewViewModel';
import { SecurityAnalyticsContext } from '../../../../services';
import { Summary } from '../../components/Widgets/Summary';
import { TopRulesWidget } from '../../components/Widgets/TopRulesWidget';
-import { GettingStartedPopup } from '../../components/GettingStarted/GettingStartedPopup';
+import { GettingStartedContent } from '../../components/GettingStarted/GettingStartedContent';
import { getChartTimeUnit, TimeUnit } from '../../utils/helpers';
import { OverviewProps, OverviewState, OverviewViewModel } from '../../../../../types';
import { setBreadcrumbs } from '../../../../utils/helpers';
import { PageHeader } from '../../../../components/PageHeader/PageHeader';
+import { getOverviewStatsProps, getOverviewsCardsProps } from '../../utils/constants';
+import { getUseUpdatedUx } from '../../../../services/utils/constants';
+import { RecentThreatIntelFindingsWidget } from '../../components/Widgets/RecentThreatIntelFindingsWidget';
export const Overview: React.FC = (props) => {
const {
@@ -51,6 +56,8 @@ export const Overview: React.FC = (props) => {
detectors: [],
findings: [],
alerts: [],
+ threatIntelFindings: [],
+ correlations: 0,
},
});
@@ -74,7 +81,7 @@ export const Overview: React.FC = (props) => {
return fireAbortSignals;
}, [fireAbortSignals]);
- const updateState = (overviewViewModel: OverviewViewModel, modelLoadingComplete: boolean) => {
+ const updateState = (overviewViewModel: OverviewViewModel, _modelLoadingComplete: boolean) => {
setState({
...state,
overviewViewModel: { ...overviewViewModel },
@@ -219,10 +226,17 @@ export const Overview: React.FC = (props) => {
anchorPosition="downRight"
closePopover={closePopover}
>
-
+
);
+ const overviewStats = {
+ alerts: state.overviewViewModel.alerts.filter((a) => !a.acknowledged).length,
+ correlations: state.overviewViewModel.correlations,
+ ruleFindings: state.overviewViewModel.findings.length,
+ threatIntelFindings: state.overviewViewModel.threatIntelFindings.length,
+ };
+
return (
= (props) => {
{ renderComponent: datePicker },
{ renderComponent: createDetectorAction },
]}
- appBadgeControls={[{ renderComponent: gettingStartedBadgeControl }]}
>
-
+
Overview
@@ -246,6 +259,30 @@ export const Overview: React.FC = (props) => {
+ {getUseUpdatedUx() && (
+ <>
+
+
+ {getOverviewsCardsProps().map((p, idx) => (
+
+
+
+ ))}
+
+
+
+
+ {getOverviewStatsProps(overviewStats).map((p, idx) => (
+
+
+
+
+
+ ))}
+
+
+ >
+ )}
= (props) => {
-
diff --git a/public/pages/Overview/models/OverviewViewModel.ts b/public/pages/Overview/models/OverviewViewModel.ts
index 8f507cd94..d9d8397ae 100644
--- a/public/pages/Overview/models/OverviewViewModel.ts
+++ b/public/pages/Overview/models/OverviewViewModel.ts
@@ -18,6 +18,7 @@ import {
OverviewFindingItem,
OverviewViewModel,
OverviewViewModelRefreshHandler,
+ ThreatIntelFinding,
} from '../../../../types';
export class OverviewViewModelActor {
@@ -25,6 +26,8 @@ export class OverviewViewModelActor {
detectors: [],
findings: [],
alerts: [],
+ threatIntelFindings: [],
+ correlations: 0,
};
private partialUpdateHandlers: OverviewViewModelRefreshHandler[] = [];
private fullUpdateHandlers: OverviewViewModelRefreshHandler[] = [];
@@ -65,12 +68,15 @@ export class OverviewViewModelActor {
}
private async updateFindings(signal: AbortSignal) {
- const detectorInfo = new Map();
+ const detectorInfo = new Map<
+ string,
+ { logType: string; name: string; detectorHit: DetectorHit }
+ >();
this.overviewViewModel.detectors.forEach((detectorHit) => {
detectorInfo.set(detectorHit._id, {
logType: detectorHit._source.detector_type,
name: detectorHit._source.name,
- detectorHit
+ detectorHit,
});
});
const detectorIds = detectorInfo.keys();
@@ -78,8 +84,8 @@ export class OverviewViewModelActor {
const ruleIds = new Set();
const duration = getDuration({
startTime: this.startTime,
- endTime: this.endTime
- })
+ endTime: this.endTime,
+ });
try {
for (let id of detectorIds) {
@@ -139,8 +145,8 @@ export class OverviewViewModelActor {
let alertItems: OverviewAlertItem[] = [];
const duration = getDuration({
startTime: this.startTime,
- endTime: this.endTime
- })
+ endTime: this.endTime,
+ });
try {
for (let detector of this.overviewViewModel.detectors) {
@@ -168,12 +174,55 @@ export class OverviewViewModelActor {
this.overviewViewModel.alerts = this.filterChartDataByTime(alertItems);
}
+ private async updateThreatIntelFindings(signal: AbortSignal) {
+ let tIFindings: ThreatIntelFinding[] = [];
+ const duration = getDuration({
+ startTime: this.startTime,
+ endTime: this.endTime,
+ });
+
+ try {
+ tIFindings = await DataStore.threatIntel.getAllThreatIntelFindings(signal, duration);
+ } catch (e: any) {
+ errorNotificationToast(this.notifications, 'retrieve', 'threat intel findings', e);
+ }
+
+ this.overviewViewModel.threatIntelFindings = this.filterChartDataByTime(
+ tIFindings,
+ 'timestamp'
+ );
+ }
+
+ private async updateCorrelationsCount() {
+ let count = 0;
+ const duration = getDuration({
+ startTime: this.startTime,
+ endTime: this.endTime,
+ });
+
+ try {
+ count = await DataStore.correlations.getCorrelationsCountInWindow(
+ duration.startTime.toString(),
+ duration.endTime.toString()
+ );
+ } catch (e: any) {
+ errorNotificationToast(this.notifications, 'retrieve', 'correlation count', e);
+ }
+
+ this.overviewViewModel.correlations = count;
+ }
+
public getOverviewViewModel() {
return this.overviewViewModel;
}
- public registerRefreshHandler(handler: OverviewViewModelRefreshHandler, allowPartialResults: boolean) {
- allowPartialResults ? this.partialUpdateHandlers.push(handler) : this.fullUpdateHandlers.push(handler);
+ public registerRefreshHandler(
+ handler: OverviewViewModelRefreshHandler,
+ allowPartialResults: boolean
+ ) {
+ allowPartialResults
+ ? this.partialUpdateHandlers.push(handler)
+ : this.fullUpdateHandlers.push(handler);
}
startTime = DEFAULT_DATE_RANGE.start;
@@ -189,34 +238,48 @@ export class OverviewViewModelActor {
this.refreshState = 'InProgress';
- await this.runSteps([
- async () => {
- await this.updateDetectors();
- this.updateResults(this.partialUpdateHandlers, false);
- },
- async () => {
- await this.updateFindings(signal);
- this.updateResults(this.partialUpdateHandlers, false);
- },
- async (signal: AbortSignal) => {
- await this.updateAlerts(signal);
- this.updateResults(this.partialUpdateHandlers, false);
- }
- ], signal);
+ await this.runSteps(
+ [
+ async () => {
+ await this.updateDetectors();
+ this.updateResults(this.partialUpdateHandlers, false);
+ },
+ async (signal: AbortSignal) => {
+ await this.updateFindings(signal);
+ this.updateResults(this.partialUpdateHandlers, false);
+ },
+ async (signal: AbortSignal) => {
+ await this.updateAlerts(signal);
+ this.updateResults(this.partialUpdateHandlers, false);
+ },
+ async (signal: AbortSignal) => {
+ await this.updateThreatIntelFindings(signal);
+ this.updateResults(this.partialUpdateHandlers, false);
+ },
+ async (_signal: AbortSignal) => {
+ await this.updateCorrelationsCount();
+ this.updateResults(this.partialUpdateHandlers, false);
+ },
+ ],
+ signal
+ );
this.updateResults(this.fullUpdateHandlers, true);
this.refreshState = 'Complete';
}
- private filterChartDataByTime = (chartData: any) => {
+ private filterChartDataByTime = (chartData: any, timeField: string = 'time') => {
const startMoment = dateMath.parse(this.startTime);
const endMoment = dateMath.parse(this.endTime);
return chartData.filter((dataItem: any) => {
- return moment(dataItem.time).isBetween(moment(startMoment), moment(endMoment));
+ return moment(dataItem[timeField]).isBetween(moment(startMoment), moment(endMoment));
});
};
- private updateResults(handlers: OverviewViewModelRefreshHandler[], modelLoadingComplete: boolean) {
+ private updateResults(
+ handlers: OverviewViewModelRefreshHandler[],
+ modelLoadingComplete: boolean
+ ) {
handlers.forEach((handler) => {
handler(this.overviewViewModel, modelLoadingComplete);
});
@@ -227,7 +290,7 @@ export class OverviewViewModelActor {
if (signal.aborted) {
break;
}
-
+
await step(signal);
if (signal.aborted) {
diff --git a/public/pages/Overview/utils/constants.ts b/public/pages/Overview/utils/constants.ts
index d8438e667..f3e51317e 100644
--- a/public/pages/Overview/utils/constants.ts
+++ b/public/pages/Overview/utils/constants.ts
@@ -3,9 +3,114 @@
* SPDX-License-Identifier: Apache-2.0
*/
+import { EuiCardProps, EuiStatProps } from '@elastic/eui';
+import {
+ CORRELATIONS_RULE_NAV_ID,
+ DETECTORS_NAV_ID,
+ GETTING_STARTED_NAV_ID,
+ THREAT_ALERTS_NAV_ID,
+ THREAT_INTEL_NAV_ID,
+} from '../../../utils/constants';
+import { getApplication } from '../../../services/utils/constants';
+
export const summaryGroupByOptions = [
{ text: 'All findings', value: 'finding' },
{ text: 'Log type', value: 'logType' },
];
export const moreLink = 'https://opensearch.org/docs/latest/security-analytics/';
+
+export const getOverviewsCardsProps = (): EuiCardProps[] => [
+ {
+ title: 'Configure Security Analytics',
+ description: 'Set up tools and components to get started.',
+ selectable: {
+ onClick: () => {
+ getApplication().navigateToApp(GETTING_STARTED_NAV_ID);
+ },
+ children: 'Getting started guide',
+ isDisabled: false,
+ },
+ },
+ {
+ title: 'Uncover security findings',
+ description: 'Identify security threats in your log data with detection rules.',
+ selectable: {
+ onClick: () => {
+ getApplication().navigateToApp(DETECTORS_NAV_ID);
+ },
+ children: 'Threat detectors',
+ isDisabled: false,
+ },
+ },
+ {
+ title: 'Discover insights',
+ description: 'Explore data to uncover insights.',
+ selectable: {
+ onClick: () => {
+ getApplication().navigateToApp('discover');
+ },
+ children: 'Discover',
+ isDisabled: false,
+ },
+ },
+ {
+ title: 'Get notified',
+ description: 'Receive timely notifications with detector-driven alerts.',
+ selectable: {
+ onClick: () => {
+ getApplication().navigateToApp(THREAT_ALERTS_NAV_ID);
+ },
+ children: 'Threat alerts',
+ isDisabled: false,
+ },
+ },
+ {
+ title: 'Correlate events',
+ description: 'Detect multi-system threats with correlation rule builder',
+ selectable: {
+ onClick: () => {
+ getApplication().navigateToApp(CORRELATIONS_RULE_NAV_ID);
+ },
+ children: 'Correlation rules',
+ isDisabled: false,
+ },
+ },
+ {
+ title: 'Scan your logs',
+ description: 'Identify malicious actors from known indicators of compromise.',
+ selectable: {
+ onClick: () => {
+ getApplication().navigateToApp(THREAT_INTEL_NAV_ID);
+ },
+ children: ' Threat intelligence',
+ isDisabled: false,
+ },
+ },
+];
+
+export const getOverviewStatsProps = ({
+ alerts,
+ correlations,
+ ruleFindings,
+ threatIntelFindings,
+}: any): EuiStatProps[] => {
+ return [
+ {
+ title: alerts,
+ description: 'Total active alerts',
+ },
+ {
+ title: correlations,
+ description: 'Correlations',
+ },
+ {
+ title: ruleFindings,
+ description: 'Detection rule findings',
+ },
+ {
+ title: threatIntelFindings,
+ description: 'Threat intel findings',
+ },
+ ];
+};
diff --git a/public/pages/Rules/components/RuleEditor/RuleEditorForm.tsx b/public/pages/Rules/components/RuleEditor/RuleEditorForm.tsx
index d5976b299..25f59618e 100644
--- a/public/pages/Rules/components/RuleEditor/RuleEditorForm.tsx
+++ b/public/pages/Rules/components/RuleEditor/RuleEditorForm.tsx
@@ -44,6 +44,7 @@ import { getLogTypeLabel } from '../../../LogTypes/utils/helpers';
import { getSeverityLabel } from '../../../Correlations/utils/constants';
import { DataSourceContext } from '../../../../services/DataSourceContext';
import { PageHeader } from '../../../../components/PageHeader/PageHeader';
+import { TopNavControlLinkData } from '../../../../../../../src/plugins/navigation/public';
export interface VisualRuleEditorProps {
initialValue: RuleEditorFormModel;
@@ -53,7 +54,7 @@ export interface VisualRuleEditorProps {
cancel: () => void;
mode: 'create' | 'edit';
title: string;
- subtitleData?: { text: string; href?: string }[];
+ subtitleData?: { description: string; links?: TopNavControlLinkData };
}
const editorTypes = [
@@ -195,45 +196,22 @@ export const RuleEditorForm: React.FC = ({
return (