diff --git a/opensearch_dashboards.json b/opensearch_dashboards.json index d6286eed..3770e6ae 100644 --- a/opensearch_dashboards.json +++ b/opensearch_dashboards.json @@ -8,5 +8,5 @@ "server": true, "ui": true, "supportedOSDataSourceVersions": ">=2.16.0", - "requiredOSDataSourcePlugins": ["opensearch_security_analytics"] + "requiredOSDataSourcePlugins": ["opensearch-security-analytics"] } diff --git a/public/components/PageHeader/PageHeader.tsx b/public/components/PageHeader/PageHeader.tsx index bf177012..ab0d603a 100644 --- a/public/components/PageHeader/PageHeader.tsx +++ b/public/components/PageHeader/PageHeader.tsx @@ -25,7 +25,7 @@ export const PageHeader: React.FC = ({ }) => { const { HeaderControl } = getNavigationUI(); const { setAppBadgeControls, setAppRightControls, setAppDescriptionControls } = getApplication(); - + return getUseUpdatedUx() ? ( <> diff --git a/public/pages/Main/Main.tsx b/public/pages/Main/Main.tsx index 08944885..040150ac 100644 --- a/public/pages/Main/Main.tsx +++ b/public/pages/Main/Main.tsx @@ -20,7 +20,7 @@ import { import { Toast } from '@opensearch-project/oui/src/eui_components/toast/global_toast_list'; import { AppMountParameters, CoreStart, SavedObject } from 'opensearch-dashboards/public'; import { SaContextConsumer } from '../../services'; -import { DEFAULT_DATE_RANGE, DATE_TIME_FILTER_KEY, ROUTES } 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'; @@ -64,6 +64,7 @@ import { ThreatIntelSource } from '../ThreatIntel/containers/ThreatIntelSource/T 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"; enum Navigation { SecurityAnalytics = 'Security Analytics', @@ -136,19 +137,40 @@ export default class Main extends Component { const defaultDateTimeFilter = cachedDateTimeFilter ? JSON.parse(cachedDateTimeFilter) : { - startTime: DEFAULT_DATE_RANGE.start, - endTime: DEFAULT_DATE_RANGE.end, - }; + 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 { + dataSourceId: string; + dataSourceLabel: string; + }; + dataSourceId = parsedDataSourceId; + dataSourceLabel = parsedDataSourceLabel || ""; + + if (dataSourceId) { + dataSourceObservable.next({ id: dataSourceId, label: dataSourceLabel }); + } + } + this.state = { getStartedDismissedOnce: false, selectedNavItemId: Navigation.Overview, dateTimeFilter: defaultDateTimeFilter, showFlyoutData: null, - dataSourceLoading: props.multiDataSourceEnabled, - selectedDataSource: { id: '' }, + /** + * 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, }; - DataStore.detectors.setHandlers(this.showCallout, this.showToast); DataStore.findings.setFlyoutCallback(this.showFlyout); } @@ -237,7 +259,7 @@ export default class Main extends Component { selectedDataSource: { ...sources[0] }, }); } - + dataSourceObservable.next({ id: this.state.selectedDataSource.id, label: this.state.selectedDataSource.label }); if (dataSourceLoading) { this.setState({ dataSourceLoading: false }); } @@ -403,7 +425,6 @@ export default class Main extends Component { dataSourceMenuReadOnly, } = this.state; const sideNav: EuiSideNavItemType<{ style: any }>[] = this.getSideNavItems(); - const dataSourceContextValue: DataSourceContextType = { dataSource: selectedDataSource, setDataSource: this.onDataSourceSelected, diff --git a/public/pages/Overview/components/Widgets/DetectorsWidget.tsx b/public/pages/Overview/components/Widgets/DetectorsWidget.tsx index 624b8869..ef92bc89 100644 --- a/public/pages/Overview/components/Widgets/DetectorsWidget.tsx +++ b/public/pages/Overview/components/Widgets/DetectorsWidget.tsx @@ -97,7 +97,7 @@ export const DetectorsWidget: React.FC = ({ ); return ( - + ) {} + private updateDefaultRouteOfManagementApplications: AppUpdater = () => { + const hash = `#/?dataSourceId=${dataSourceObservable.value?.id || ""}`; + return { + defaultPath: hash, + }; + }; + + private appStateUpdater = new BehaviorSubject(this.updateDefaultRouteOfManagementApplications); + public setup( core: CoreSetup, { dataSourceManagement }: SecurityAnalyticsPluginSetupDeps @@ -93,6 +105,7 @@ export class SecurityAnalyticsPlugin id: OVERVIEW_NAV_ID, title: 'Overview', order: 0, + updater$: this.appStateUpdater, mount: async (params: AppMountParameters) => { return mountWrapper(params, ROUTES.LANDING_PAGE); }, @@ -103,6 +116,7 @@ export class SecurityAnalyticsPlugin title: 'Threat alerts', order: 9070, category: DEFAULT_APP_CATEGORIES.investigate, + updater$: this.appStateUpdater, mount: async (params: AppMountParameters) => { return mountWrapper(params, ROUTES.ALERTS); }, @@ -113,6 +127,7 @@ export class SecurityAnalyticsPlugin title: 'Findings', order: 9080, category: DEFAULT_APP_CATEGORIES.investigate, + updater$: this.appStateUpdater, mount: async (params: AppMountParameters) => { return mountWrapper(params, ROUTES.FINDINGS); }, @@ -123,6 +138,7 @@ export class SecurityAnalyticsPlugin title: 'Correlations', order: 9080, category: DEFAULT_APP_CATEGORIES.investigate, + updater$: this.appStateUpdater, mount: async (params: AppMountParameters) => { return mountWrapper(params, ROUTES.CORRELATIONS); }, @@ -133,6 +149,7 @@ export class SecurityAnalyticsPlugin title: 'Threat detectors', order: 9080, category: DEFAULT_APP_CATEGORIES.configure, + updater$: this.appStateUpdater, mount: async (params: AppMountParameters) => { return mountWrapper(params, ROUTES.DETECTORS); }, @@ -143,6 +160,7 @@ export class SecurityAnalyticsPlugin title: 'Detection rules', order: 9080, category: DEFAULT_APP_CATEGORIES.configure, + updater$: this.appStateUpdater, mount: async (params: AppMountParameters) => { return mountWrapper(params, ROUTES.RULES); }, @@ -153,6 +171,7 @@ export class SecurityAnalyticsPlugin title: 'Correlation rules', order: 9080, category: DEFAULT_APP_CATEGORIES.configure, + updater$: this.appStateUpdater, mount: async (params: AppMountParameters) => { return mountWrapper(params, ROUTES.CORRELATION_RULES); }, @@ -163,6 +182,7 @@ export class SecurityAnalyticsPlugin title: 'Threat intelligence', order: 9080, category: DEFAULT_APP_CATEGORIES.configure, + updater$: this.appStateUpdater, mount: async (params: AppMountParameters) => { return mountWrapper(params, ROUTES.THREAT_INTEL_OVERVIEW); }, @@ -173,11 +193,18 @@ export class SecurityAnalyticsPlugin title: 'Log types', order: 9080, category: DEFAULT_APP_CATEGORIES.configure, + updater$: this.appStateUpdater, mount: async (params: AppMountParameters) => { return mountWrapper(params, ROUTES.LOG_TYPES); }, }); + dataSourceObservable.subscribe((dataSourceOption) => { + if (dataSourceOption) { + this.appStateUpdater.next(this.updateDefaultRouteOfManagementApplications); + } + }); + const navlinks = [ { id: OVERVIEW_NAV_ID, showInAllNavGroup: true }, { id: THREAT_ALERTS_NAV_ID, showInAllNavGroup: true }, diff --git a/public/utils/constants.ts b/public/utils/constants.ts index dc9ec37f..a18eee26 100644 --- a/public/utils/constants.ts +++ b/public/utils/constants.ts @@ -9,6 +9,9 @@ import { DetectorInput, PeriodSchedule } from '../../models/interfaces'; import { DetectorHit } from '../../server/models/interfaces'; import _ from 'lodash'; import { euiPaletteColorBlind } from '@elastic/eui'; +import { DataSourceOption } from 'src/plugins/data_source_management/public'; +import { BehaviorSubject } from 'rxjs'; +import { i18n } from "@osd/i18n"; export const DATE_MATH_FORMAT = 'YYYY-MM-DDTHH:mm:ss.SSSZ'; export const MAX_RECENTLY_USED_TIME_RANGES = 5; @@ -77,7 +80,7 @@ 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: { text: 'Threat detectors', href: `#${ROUTES.DETECTORS}` }, DETECTORS_CREATE: { text: 'Create detector', href: `#${ROUTES.DETECTORS_CREATE}` }, EDIT_DETECTOR_DETAILS: { text: 'Edit detector details' }, DETECTORS_DETAILS: (name: string, detectorId: string) => ({ @@ -89,7 +92,7 @@ export const BREADCRUMBS = Object.freeze({ href: `#${ROUTES.EDIT_DETECTOR_DETAILS}/${detectorId}`, }), RULES: { text: 'Detection rules', href: `#${ROUTES.RULES}` }, - ALERTS: { text: 'Alerts', href: `#${ROUTES.ALERTS}` }, + ALERTS: { text: 'Threat 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}` }, @@ -241,3 +244,12 @@ export enum AlertTabId { ThreatIntel = 'threat-intel', Correlations = 'correlations', } + +const LocalCluster: DataSourceOption = { + label: i18n.translate("dataSource.localCluster", { + defaultMessage: "Local cluster", + }), + id: "", +}; + +export const dataSourceObservable = new BehaviorSubject(LocalCluster); diff --git a/public/utils/helpers.tsx b/public/utils/helpers.tsx index 6bfb1b99..1ad55987 100644 --- a/public/utils/helpers.tsx +++ b/public/utils/helpers.tsx @@ -17,7 +17,7 @@ import { } from '@elastic/eui'; import moment from 'moment'; import { PeriodSchedule } from '../../models/interfaces'; -import React from 'react'; +import React, { useContext, useEffect } from 'react'; import { BREADCRUMBS, DEFAULT_EMPTY_DATA, @@ -48,6 +48,9 @@ import { compile } from 'vega-lite'; import { Handler } from 'vega-tooltip'; import { expressionInterpreter as vegaExpressionInterpreter } from 'vega-interpreter/build/vega-interpreter'; import { getBreadCrumbsSetter, getUseUpdatedUx } from '../services/utils/constants'; +import { useHistory } from 'react-router-dom'; +import queryString from "query-string"; +import { DataSourceContext } from '../services/DataSourceContext'; export const parseStringsToOptions = (strings: string[]) => { return strings.map((str) => ({ id: str, label: str })); diff --git a/types/Overview.ts b/types/Overview.ts index a0035f4c..b26705e1 100644 --- a/types/Overview.ts +++ b/types/Overview.ts @@ -85,4 +85,4 @@ export interface DetectorItem { detectorName: string; status: string; logTypes: string; -} +} \ No newline at end of file