+
+

+ Manage existing data connections +

+
+
+
+ Manage already created data connections. +
+
+
+
+
+
+
+
+
+ +
+ + + +
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + Name + + + + + + Actions + + +
+
+ Name +
+
+
+ +
+
+
+ Actions +
+
+ +
+
+
+ Name +
+
+
+ +
+
+
+ Actions +
+
+ +
+
+
+ Name +
+
+
+ +
+
+
+ Actions +
+
+ +
+
+
+ Name +
+
+
+ +
+
+
+ Actions +
+
+ +
+
+
+
+
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+
+
+
+
+`; diff --git a/public/components/data_connections/components/__tests__/data_connection.test.tsx b/public/components/data_connections/components/__tests__/data_connection.test.tsx new file mode 100644 index 000000000..995719810 --- /dev/null +++ b/public/components/data_connections/components/__tests__/data_connection.test.tsx @@ -0,0 +1,38 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { configure } from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; +import { act } from '@testing-library/react'; +import React from 'react'; +import { describeDataConnection, mockRoleData } from '../../../../../test/datasources'; +import { DataConnection } from '../data_connection'; +import ReactDOM from 'react-dom'; + +jest.mock('../../../../../public/framework/core_refs', () => ({ + coreRefs: { + chrome: { + setBreadcrumbs: jest.fn(), + }, + http: { + get: jest.fn().mockResolvedValueOnce(mockRoleData).mockResolvedValue(describeDataConnection), + }, + }, +})); + +describe('Data Connection Page test', () => { + configure({ adapter: new Adapter() }); + + it('Renders data connection page with data', async () => { + const pplService = { + fetch: jest.fn(), + }; + const container = document.createElement('div'); + await act(() => { + ReactDOM.render(, container); + }); + expect(container).toMatchSnapshot(); + }); +}); diff --git a/public/components/data_connections/components/__tests__/manage_data_connections_description.test.tsx b/public/components/data_connections/components/__tests__/manage_data_connections_description.test.tsx new file mode 100644 index 000000000..686230683 --- /dev/null +++ b/public/components/data_connections/components/__tests__/manage_data_connections_description.test.tsx @@ -0,0 +1,22 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { configure, mount } from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; +import { waitFor } from '@testing-library/react'; +import React from 'react'; +import { DataConnectionsDescription } from '../manage_data_connections_description'; + +describe('Manage Data Connections Description test', () => { + configure({ adapter: new Adapter() }); + + it('Renders manage data connections description', async () => { + const wrapper = mount(); + + await waitFor(() => { + expect(wrapper).toMatchSnapshot(); + }); + }); +}); diff --git a/public/components/data_connections/components/__tests__/manage_data_connections_table.test.tsx b/public/components/data_connections/components/__tests__/manage_data_connections_table.test.tsx new file mode 100644 index 000000000..cd5509f1b --- /dev/null +++ b/public/components/data_connections/components/__tests__/manage_data_connections_table.test.tsx @@ -0,0 +1,36 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { configure } from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; +import { act } from '@testing-library/react'; +import React from 'react'; +import { ManageDataConnectionsTable } from '../manage_data_connections_table'; +import { showDataConnectionsData } from '../../../../../test/datasources'; +import ReactDOM from 'react-dom'; + +describe('Manage Data Connections Table test', () => { + configure({ adapter: new Adapter() }); + + it('Renders manage data connections table with data', async () => { + const http = { + get: jest.fn().mockResolvedValue(showDataConnectionsData), + }; + const pplService = { + fetch: jest.fn().mockResolvedValue(showDataConnectionsData), + }; + const mockChrome = { + setBreadcrumbs: jest.fn(), + }; + const container = document.createElement('div'); + await act(() => { + ReactDOM.render( + , + container + ); + }); + expect(container).toMatchSnapshot(); + }); +}); diff --git a/public/components/data_connections/components/access_control_tab.tsx b/public/components/data_connections/components/access_control_tab.tsx new file mode 100644 index 000000000..58fdffde3 --- /dev/null +++ b/public/components/data_connections/components/access_control_tab.tsx @@ -0,0 +1,132 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiText, + EuiHorizontalRule, +} from '@elastic/eui'; +import React, { useEffect, useState } from 'react'; +import { EuiPanel } from '@elastic/eui'; +import { ConnectionManagementCallout } from './connection_management_callout'; +import { coreRefs } from '../../../../public/framework/core_refs'; +import { QueryPermissionsConfiguration } from './query_permissions'; +import { DATACONNECTIONS_BASE } from '../../../../common/constants/shared'; +import { SaveOrCancel } from './save_or_cancel'; + +interface AccessControlTabProps { + dataConnection: string; + connector: string; + properties: unknown; +} + +export const AccessControlTab = (props: AccessControlTabProps) => { + const [mode, setMode] = useState<'view' | 'edit'>('view'); + const [roles, setRoles] = useState>([]); + const [selectedQueryPermissionRoles, setSelectedQueryPermissionRoles] = useState< + Array<{ label: string }> + >([]); + const { http } = coreRefs; + + useEffect(() => { + http!.get('/api/v1/configuration/roles').then((data) => + setRoles( + Object.keys(data.data).map((key) => { + return { label: key }; + }) + ) + ); + }, []); + + const AccessControlDetails = () => { + return ( + + + + + Query access + + {[].length ? `Restricted` : '-'} + + + + + + ); + }; + + const EditAccessControlDetails = () => { + return ( + + + + ); + }; + + const saveChanges = () => { + http!.put(`${DATACONNECTIONS_BASE}`, { + body: JSON.stringify({ + name: props.dataConnection, + allowedRoles: selectedQueryPermissionRoles.map((role) => role.label), + connector: props.connector, + properties: props.properties, + }), + }); + setMode('view'); + }; + + const AccessControlHeader = () => { + return ( + + + +

Access Control

+ Control which OpenSearch users have access to this data source. +
+
+ + + setMode(mode === 'view' ? 'edit' : 'view')} + fill={mode === 'view' ? true : false} + > + {mode === 'view' ? 'Edit' : 'Cancel'} + + +
+ ); + }; + + return ( + <> + + + + + + + {mode === 'view' ? : } + + + {mode === 'edit' && ( + { + setMode('view'); + }} + onSave={saveChanges} + /> + )} + + + ); +}; diff --git a/public/components/data_connections/components/connection_configuration.tsx b/public/components/data_connections/components/connection_configuration.tsx new file mode 100644 index 000000000..8ce2bd7be --- /dev/null +++ b/public/components/data_connections/components/connection_configuration.tsx @@ -0,0 +1,139 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + EuiFieldPassword, + EuiFieldText, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiSelect, + EuiSpacer, + EuiText, + EuiTextArea, +} from '@elastic/eui'; +import { EuiSelectOption } from '@elastic/eui/src/components/form/select'; +import React, { useState } from 'react'; + +interface ConnectionConfigurationProps { + connectionName: string; + connectionDetails: string; + onConnectionDetailsChange: (e: any) => void; + authenticationOptions: EuiSelectOption[]; + setSelectedAuthenticationMethod: (authenticationMethod: EuiSelectOption) => void; + selectedAuthenticationMethod: string; +} + +export const ConnectionConfiguration = (props: ConnectionConfigurationProps) => { + const { + connectionName, + connectionDetails, + onConnectionDetailsChange, + authenticationOptions, + selectedAuthenticationMethod, + setSelectedAuthenticationMethod, + } = props; + const [details, setDetails] = useState(connectionDetails); + + const [password, setPassword] = useState(''); + const [dual, setDual] = useState(true); + + const NameRow = () => { + return ( + + + Data source name + + This is the name of the data source and how it will be referenced in OpenSearch + Dashboards. + + + + + + + + + ); + }; + + const SparkEndpointRow = () => { + return ( + + + Spark endpoint URL + + { + "The URL for your Spark cluster and where your data is. This is what OpenSearch will connect to. The endpoint URL can't be changed. If you'd like to use another endpoint create a new data source." + } + + + + + + + + + ); + }; + + return ( + + + + + + Description - optional + + Text that can help identify the data source or share additional details + + + + + { + setDetails(e.target.value); + }} + onBlur={onConnectionDetailsChange} + /> + + + + + + + + + Authentication details + + This is information used to authenticate and create a data source with Spark. + + + + + setSelectedAuthenticationMethod(e)} + /> + + + + + + setPassword(e.target.value)} + /> + + + + + ); +}; diff --git a/public/components/data_connections/components/connection_details.tsx b/public/components/data_connections/components/connection_details.tsx new file mode 100644 index 000000000..44cd206f4 --- /dev/null +++ b/public/components/data_connections/components/connection_details.tsx @@ -0,0 +1,159 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiText, + EuiHorizontalRule, +} from '@elastic/eui'; +import React, { useState } from 'react'; +import { EuiPanel } from '@elastic/eui'; +import { ConnectionManagementCallout } from './connection_management_callout'; +import { coreRefs } from '../../../../public/framework/core_refs'; +import { DATACONNECTIONS_BASE } from '../../../../common/constants/shared'; +import { SaveOrCancel } from './save_or_cancel'; +import { ConnectionConfiguration } from './connection_configuration'; + +interface ConnectionDetailProps { + dataConnection: string; + connector: string; + allowedRoles: string[]; + properties: unknown; +} + +export const ConnectionDetails = (props: ConnectionDetailProps) => { + const [mode, setMode] = useState<'view' | 'edit'>('view'); + const { http } = coreRefs; + + const { dataConnection, connector, allowedRoles, properties } = props; + const [connectionDetails, setConnectionDetails] = useState(''); + const onChange = (e) => { + setConnectionDetails(e.target.value); + }; + const authenticationOptions = [{ value: 'option_one', text: 'Username & Password' }]; + + const [selectedAuthenticationMethod, setSelectedAuthenticationMethod] = useState( + authenticationOptions[0].value + ); + + const onAuthenticationMethodChange = (e) => { + setSelectedAuthenticationMethod(e.target.value); + }; + + const ConnectionConfigurationView = () => { + return ( + + + + + Data source name + + {dataConnection} + + + + Spark endpoint URL + + {'-'} + + + + + + + + Description + + {'-'} + + + + Authentication method + + {'-'} + + + + + + ); + }; + + const EditConnectionConfiguration = () => { + return ( + + + + ); + }; + + const saveChanges = () => { + http!.put(`${DATACONNECTIONS_BASE}`, { + body: JSON.stringify({ + name: props.dataConnection, + allowedRoles: props.allowedRoles, + connector: props.connector, + properties: props.properties, + }), + }); + setMode('view'); + }; + + const ConnectionConfigurationHeader = () => { + return ( + + + +

Data source configurations

+ Control configurations for your data source. +
+
+ + + setMode(mode === 'view' ? 'edit' : 'view')} + fill={mode === 'view' ? true : false} + > + {mode === 'view' ? 'Edit' : 'Cancel'} + + +
+ ); + }; + + return ( + <> + + + + + + + {mode === 'view' ? : } + + + {mode === 'edit' && ( + { + setMode('view'); + }} + onSave={saveChanges} + /> + )} + + + ); +}; diff --git a/public/components/data_connections/components/connection_management_callout.tsx b/public/components/data_connections/components/connection_management_callout.tsx new file mode 100644 index 000000000..b78872287 --- /dev/null +++ b/public/components/data_connections/components/connection_management_callout.tsx @@ -0,0 +1,16 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { EuiCallOut } from '@elastic/eui'; +import React from 'react'; + +export const ConnectionManagementCallout = () => { + return ( + + Access to data can be managed in other systems outside of OpenSearch. Check with your + administrator for additional configurations. + + ); +}; diff --git a/public/components/data_connections/components/data_connection.tsx b/public/components/data_connections/components/data_connection.tsx new file mode 100644 index 000000000..d4c87ef49 --- /dev/null +++ b/public/components/data_connections/components/data_connection.tsx @@ -0,0 +1,209 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + EuiFlexGroup, + EuiFlexItem, + EuiPage, + EuiPageBody, + EuiSpacer, + EuiTitle, + EuiText, + EuiPanel, + EuiPageHeader, + EuiPageHeaderSection, + EuiAccordion, + EuiIcon, + EuiCard, + EuiTabbedContent, +} from '@elastic/eui'; +import React, { useEffect, useState } from 'react'; +import { AccessControlTab } from './access_control_tab'; +import { NoAccess } from './no_access'; +import { DATACONNECTIONS_BASE } from '../../../../common/constants/shared'; +import { coreRefs } from '../../../../public/framework/core_refs'; +import { ConnectionDetails } from './connection_details'; + +interface DatasourceDetails { + allowedRoles: string[]; + name: string; + cluster: string; + connector: string; + properties: unknown; +} + +export const DataConnection = (props: any) => { + const { dataSource } = props; + const [datasourceDetails, setDatasourceDetails] = useState({ + allowedRoles: [], + name: '', + cluster: '', + connector: '', + properties: {}, + }); + const [hasAccess, setHasAccess] = useState(true); + const { http, chrome } = coreRefs; + + useEffect(() => { + chrome!.setBreadcrumbs([ + { + text: 'Data Connections', + href: '#/', + }, + { + text: `${dataSource}`, + href: `#/manage/${dataSource}`, + }, + ]); + http! + .get(`${DATACONNECTIONS_BASE}/${dataSource}`) + .then((data) => + setDatasourceDetails({ + allowedRoles: data.allowedRoles, + name: data.name, + cluster: data.properties['emr.cluster'], + connector: data.connector, + properties: data.properties, + }) + ) + .catch((err) => { + setHasAccess(false); + }); + }, [chrome, http]); + + const tabs = [ + { + id: 'access_control', + name: 'Access control', + disabled: false, + content: ( + + ), + }, + { + id: 'connection_configuration', + name: 'Connection configuration', + disabled: false, + content: ( + + ), + }, + ]; + + const DatasourceOverview = () => { + return ( + + + + + + Connection title + + {datasourceDetails.name || '-'} + + + + Authentication method + + {'-'} + + + + + + + + Data source description + + {'-'} + + + + Query permissions + + {datasourceDetails.allowedRoles && datasourceDetails.allowedRoles.length + ? 'Restricted' + : 'Everyone'} + + + + + + + + Spark data location + + {'-'} + + + + + + + + ); + }; + + if (!hasAccess) { + return ; + } + + return ( + + + + + + + +

{dataSource}

+
+
+
+
+
+ + + + + + + } + title={'Query data'} + description="Query your data in Data Explorer or Observability Logs." + onClick={() => {}} + /> + + + } + title={'Accelerate performance'} + description="Accelerate performance through OpenSearch indexing." + onClick={() => {}} + /> + + + + + + +
+
+ ); +}; diff --git a/public/components/data_connections/components/data_connections_header.tsx b/public/components/data_connections/components/data_connections_header.tsx new file mode 100644 index 000000000..7f071683b --- /dev/null +++ b/public/components/data_connections/components/data_connections_header.tsx @@ -0,0 +1,38 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + EuiLink, + EuiPageHeader, + EuiPageHeaderSection, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import _ from 'lodash'; +import React from 'react'; +import { OPENSEARCH_DOCUMENTATION_URL } from '../../../../common/constants/data_connections'; + +export const DataConnectionsHeader = () => { + return ( +
+ + + +

Data connections

+
+
+
+ + + Connect and manage compatible OpenSearch and OpenSearch Dashboard data connections.{' '} + + Learn more + + + +
+ ); +}; diff --git a/public/components/data_connections/components/manage_data_connections_description.tsx b/public/components/data_connections/components/manage_data_connections_description.tsx new file mode 100644 index 000000000..26cfa90cc --- /dev/null +++ b/public/components/data_connections/components/manage_data_connections_description.tsx @@ -0,0 +1,23 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { EuiSpacer, EuiText, EuiTitle, EuiHorizontalRule } from '@elastic/eui'; +import React from 'react'; + +export const DataConnectionsDescription = () => { + return ( +
+ +

Manage existing data connections

+
+ + + + Manage already created data connections. + + +
+ ); +}; diff --git a/public/components/data_connections/components/manage_data_connections_table.tsx b/public/components/data_connections/components/manage_data_connections_table.tsx new file mode 100644 index 000000000..e1950e704 --- /dev/null +++ b/public/components/data_connections/components/manage_data_connections_table.tsx @@ -0,0 +1,189 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiInMemoryTable, + EuiLink, + EuiOverlayMask, + EuiPage, + EuiPageBody, + EuiPageContent, + EuiTableFieldDataColumnType, +} from '@elastic/eui'; +import _ from 'lodash'; +import React, { useEffect, useState } from 'react'; +import { DataConnectionsHeader } from './data_connections_header'; +import { HomeProps } from '../home'; +import { DataConnectionsDescription } from './manage_data_connections_description'; +import { DATACONNECTIONS_BASE } from '../../../../common/constants/shared'; +import { useToast } from '../../../../public/components/common/toast'; +import { DeleteModal } from '../../../../public/components/common/helpers/delete_modal'; + +interface DataConnection { + connectionType: 'OPENSEARCH' | 'SPARK'; + name: string; +} + +export const ManageDataConnectionsTable = (props: HomeProps) => { + const { http, chrome, pplService } = props; + + const { setToast } = useToast(); + + const [data, setData] = useState([]); + const [isModalVisible, setIsModalVisible] = useState(false); + const [modalLayout, setModalLayout] = useState(); + + const deleteConnection = (connectionName: string) => { + http! + .delete(`${DATACONNECTIONS_BASE}/${connectionName}`) + .then(() => { + setToast(`Data connection ${connectionName} deleted successfully`); + setData( + data.filter((connection) => { + return !(connection.name === connectionName); + }) + ); + }) + .catch((err) => { + setToast(`Data connection $${connectionName} not deleted. See output for more details.`); + }); + }; + + useEffect(() => { + chrome.setBreadcrumbs([ + { + text: 'Data Connections', + href: '#/', + }, + ]); + handleDataRequest(); + }, [chrome]); + + async function handleDataRequest() { + pplService!.fetch({ query: 'show datasources', format: 'jdbc' }).then((dataconnections) => + setData( + dataconnections.jsonData.map((x: any) => { + return { name: x.DATASOURCE_NAME, connectionType: x.CONNECTOR_TYPE }; + }) + ) + ); + } + + const displayDeleteModal = (connectionName: string) => { + setModalLayout( + { + setIsModalVisible(false); + deleteConnection(connectionName); + }} + onCancel={() => { + setIsModalVisible(false); + }} + title={`Delete ${connectionName}`} + message={`Are you sure you want to delete ${connectionName}?`} + /> + ); + setIsModalVisible(true); + }; + + const icon = (record: DataConnection) => { + switch (record.connectionType) { + case 'OPENSEARCH': + return ; + default: + return <>; + } + }; + + const tableColumns = [ + { + field: 'name', + name: 'Name', + sortable: true, + truncateText: true, + render: (value, record: DataConnection) => ( + + {icon(record)} + + + {_.truncate(record.name, { length: 100 })} + + + + ), + }, + { + field: 'actions', + name: 'Actions', + sortable: true, + truncateText: true, + render: (value, record) => ( + { + displayDeleteModal(record.name); + }} + /> + ), + }, + ] as Array>; + + const search = { + box: { + incremental: true, + }, + filters: [ + { + type: 'field_value_selection', + field: 'templateName', + name: 'Type', + multiSelect: false, + options: [].map((i) => ({ + value: i, + name: i, + view: i, + })), + }, + ], + }; + + const entries = data.map((dataconnection: DataConnection) => { + const name = dataconnection.name; + const connectionType = dataconnection.connectionType; + return { connectionType, name, data: { name, connectionType } }; + }); + + return ( + + + + + + + + + {isModalVisible && modalLayout} + + + ); +}; diff --git a/public/components/data_connections/components/no_access.tsx b/public/components/data_connections/components/no_access.tsx new file mode 100644 index 000000000..1766f7fe1 --- /dev/null +++ b/public/components/data_connections/components/no_access.tsx @@ -0,0 +1,30 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { EuiButton, EuiEmptyPrompt, EuiPage, EuiText } from '@elastic/eui'; +import React from 'react'; + +export const NoAccess = () => { + return ( + + {'No permissions to access'}} + body={ + + { + 'You are missing permissions to view connection details. Contact your administrator for permissions.' + } + + } + actions={ + (window.location.hash = '')}> + Return to data connections + + } + /> + + ); +}; diff --git a/public/components/data_connections/components/query_permissions.tsx b/public/components/data_connections/components/query_permissions.tsx new file mode 100644 index 000000000..eee8312a8 --- /dev/null +++ b/public/components/data_connections/components/query_permissions.tsx @@ -0,0 +1,87 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + EuiComboBox, + EuiFlexGroup, + EuiFlexItem, + EuiLink, + EuiRadioGroup, + EuiSpacer, + EuiText, +} from '@elastic/eui'; +import React, { useState } from 'react'; +import { + OPENSEARCH_DOCUMENTATION_URL, + QUERY_ALL, + QUERY_RESTRICTED, +} from '../../../../common/constants/data_connections'; +import { PermissionsConfigurationProps } from '../../../../common/types/data_connections'; + +export const QueryPermissionsConfiguration = (props: PermissionsConfigurationProps) => { + const { roles, selectedRoles, setSelectedRoles } = props; + + const [selectedAccessLevel, setSelectedAccessLevel] = useState( + selectedRoles.length ? QUERY_RESTRICTED : QUERY_ALL + ); + const accessLevelOptions = [ + { + id: QUERY_RESTRICTED, + label: 'Restricted - accessible by users with specific OpenSearch roles', + }, + { + id: QUERY_ALL, + label: 'Everyone - accessible by all users on this cluster', + }, + ]; + + const ConfigureRoles = () => { + return ( +
+ + OpenSearch Roles + + Select one or more OpenSearch roles that can query this data connection. + + +
+ ); + }; + + return ( + + + + Query Permissions + + Control which OpenSearch roles have query permissions on this data source.{' '} + + Learn more + + + + + setSelectedAccessLevel(id)} + name="query-radio-group" + legend={{ + children: Access level, + }} + /> + {selectedAccessLevel === QUERY_RESTRICTED && } + + + + ); +}; diff --git a/public/components/data_connections/components/save_or_cancel.tsx b/public/components/data_connections/components/save_or_cancel.tsx new file mode 100644 index 000000000..ccb966210 --- /dev/null +++ b/public/components/data_connections/components/save_or_cancel.tsx @@ -0,0 +1,32 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { EuiBottomBar, EuiButton, EuiButtonEmpty, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import React from 'react'; + +interface SaveOrCancelProps { + onSave: () => void; + onCancel: () => void; +} + +export const SaveOrCancel = (props: SaveOrCancelProps) => { + const { onSave, onCancel } = props; + return ( + + + + + Discard change(s) + + + + + Save + + + + + ); +}; diff --git a/public/components/data_connections/home.tsx b/public/components/data_connections/home.tsx new file mode 100644 index 000000000..29d5e83bd --- /dev/null +++ b/public/components/data_connections/home.tsx @@ -0,0 +1,50 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { HashRouter, Route, RouteComponentProps, Switch } from 'react-router-dom'; +import { ChromeBreadcrumb, ChromeStart, HttpStart } from '../../../../../src/core/public'; +import { DataConnection } from './components/data_connection'; +import { ManageDataConnectionsTable } from './components/manage_data_connections_table'; + +export interface HomeProps extends RouteComponentProps { + pplService: any; + parentBreadcrumb: ChromeBreadcrumb; + http: HttpStart; + chrome: ChromeStart; +} + +export const Home = (props: HomeProps) => { + const { http, chrome, pplService } = props; + + const commonProps = { + http, + chrome, + pplService, + }; + + return ( + + + ( + + )} + /> + + } + /> + + + ); +}; diff --git a/public/components/integrations/components/available_integration_overview_page.tsx b/public/components/integrations/components/available_integration_overview_page.tsx index e858daa54..360bf0db4 100644 --- a/public/components/integrations/components/available_integration_overview_page.tsx +++ b/public/components/integrations/components/available_integration_overview_page.tsx @@ -117,8 +117,7 @@ export function AvailableIntegrationOverviewPage(props: AvailableIntegrationOver http.get(`${INTEGRATIONS_BASE}/repository`).then((exists) => { setData(exists.data); - let newItems = exists.data.hits - .flatMap((hit: { labels?: string[] }) => hit.labels ?? []); + let newItems = exists.data.hits.flatMap((hit: { labels?: string[] }) => hit.labels ?? []); newItems = [...new Set(newItems)].sort().map((newItem) => { return { name: newItem, diff --git a/public/framework/core_refs.ts b/public/framework/core_refs.ts index 68cf51547..5b3791f95 100644 --- a/public/framework/core_refs.ts +++ b/public/framework/core_refs.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { HttpStart, IToasts } from '../../../../src/core/public'; +import { ChromeStart, HttpStart, IToasts } from '../../../../src/core/public'; import { SavedObjectsClientContract } from '../../../../src/core/public'; import PPLService from '../services/requests/ppl'; @@ -14,6 +14,7 @@ class CoreRefs { public savedObjectsClient?: SavedObjectsClientContract; public pplService?: PPLService; public toasts?: IToasts; + public chrome?: ChromeStart; private constructor() { // ... } diff --git a/public/plugin.ts b/public/plugin.ts index 021a9f56a..e3215ce02 100644 --- a/public/plugin.ts +++ b/public/plugin.ts @@ -39,6 +39,9 @@ import { observabilityIntegrationsTitle, observabilityIntegrationsPluginOrder, observabilityPluginOrder, + observabilityDataConnectionsID, + observabilityDataConnectionsPluginOrder, + observabilityDataConnectionsTitle, } from '../common/constants/shared'; import { QueryManager } from '../common/query_manager'; import { VISUALIZATION_SAVED_OBJECT } from '../common/types/observability_saved_object_attributes'; @@ -197,6 +200,23 @@ export class ObservabilityPlugin mount: appMountWithStartPage('integrations'), }); + core.application.register({ + id: observabilityDataConnectionsID, + title: observabilityDataConnectionsTitle, + category: DEFAULT_APP_CATEGORIES.management, + order: observabilityDataConnectionsPluginOrder, + mount: appMountWithStartPage('dataconnections'), + }); + + setupDeps.managementOverview?.register({ + id: observabilityDataConnectionsID, + title: observabilityDataConnectionsTitle, + order: 9070, + description: i18n.translate('observability.dataconnectionsDescription', { + defaultMessage: 'Manage compatible data connections with OpenSearch Dashboards.', + }), + }); + const embeddableFactory = new ObservabilityEmbeddableFactoryDefinition(async () => ({ getAttributeService: (await core.getStartServices())[1].dashboard.getAttributeService, savedObjectsClient: (await core.getStartServices())[0].savedObjects.client, @@ -242,6 +262,7 @@ export class ObservabilityPlugin coreRefs.savedObjectsClient = core.savedObjects.client; coreRefs.pplService = pplService; coreRefs.toasts = core.notifications.toasts; + coreRefs.chrome = core.chrome; return {}; } diff --git a/public/types.ts b/public/types.ts index 48f0ad9de..75afb133b 100644 --- a/public/types.ts +++ b/public/types.ts @@ -7,6 +7,7 @@ import { SavedObjectsClient } from '../../../src/core/server'; import { DashboardStart } from '../../../src/plugins/dashboard/public'; import { DataPublicPluginSetup } from '../../../src/plugins/data/public'; import { EmbeddableSetup, EmbeddableStart } from '../../../src/plugins/embeddable/public'; +import { ManagementOverViewPluginSetup } from '../../../src/plugins/management_overview/public'; import { NavigationPublicPluginStart } from '../../../src/plugins/navigation/public'; import { UiActionsStart } from '../../../src/plugins/ui_actions/public'; import { VisualizationsSetup } from '../../../src/plugins/visualizations/public'; @@ -23,6 +24,7 @@ export interface SetupDependencies { visualizations: VisualizationsSetup; data: DataPublicPluginSetup; uiActions: UiActionsStart; + managementOverview?: ManagementOverViewPluginSetup; } // eslint-disable-next-line @typescript-eslint/no-empty-interface diff --git a/server/adaptors/opensearch_observability_plugin.ts b/server/adaptors/opensearch_observability_plugin.ts index fbdbac72b..10137b33c 100644 --- a/server/adaptors/opensearch_observability_plugin.ts +++ b/server/adaptors/opensearch_observability_plugin.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { OPENSEARCH_PANELS_API } from '../../common/constants/shared'; +import { OPENSEARCH_DATASOURCES_API, OPENSEARCH_PANELS_API } from '../../common/constants/shared'; export function OpenSearchObservabilityPlugin(Client: any, config: any, components: any) { const clientAction = components.clientAction.factory; diff --git a/server/adaptors/ppl_plugin.ts b/server/adaptors/ppl_plugin.ts index 304d196e3..563c43672 100644 --- a/server/adaptors/ppl_plugin.ts +++ b/server/adaptors/ppl_plugin.ts @@ -3,7 +3,11 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { PPL_ENDPOINT, SQL_ENDPOINT } from '../../common/constants/shared'; +import { + OPENSEARCH_DATACONNECTIONS_API, + PPL_ENDPOINT, + SQL_ENDPOINT, +} from '../../common/constants/shared'; export const PPLPlugin = function (Client, config, components) { const ca = components.clientAction.factory; @@ -37,4 +41,45 @@ export const PPLPlugin = function (Client, config, components) { needBody: true, method: 'POST', }); + + ppl.getDataConnectionById = ca({ + url: { + fmt: `${OPENSEARCH_DATACONNECTIONS_API.DATACONNECTION}/<%=dataconnection%>`, + req: { + dataconnection: { + type: 'string', + required: true, + }, + }, + }, + method: 'GET', + }); + + ppl.deleteDataConnection = ca({ + url: { + fmt: `${OPENSEARCH_DATACONNECTIONS_API.DATACONNECTION}/<%=dataconnection%>`, + req: { + dataconnection: { + type: 'string', + required: true, + }, + }, + }, + method: 'DELETE', + }); + + ppl.modifyDataConnection = ca({ + url: { + fmt: `${OPENSEARCH_DATACONNECTIONS_API.DATACONNECTION}`, + }, + needBody: true, + method: 'PUT', + }); + + ppl.getDataConnections = ca({ + url: { + fmt: `${OPENSEARCH_DATACONNECTIONS_API.DATACONNECTION}`, + }, + method: 'GET', + }); }; diff --git a/server/routes/data_connections/data_connections_router.ts b/server/routes/data_connections/data_connections_router.ts new file mode 100644 index 000000000..a65660ba4 --- /dev/null +++ b/server/routes/data_connections/data_connections_router.ts @@ -0,0 +1,128 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { schema } from '@osd/config-schema'; +import { IRouter } from '../../../../../src/core/server'; +import { DATACONNECTIONS_BASE } from '../../../common/constants/shared'; + +export function registerDataConnectionsRoute(router: IRouter) { + router.get( + { + path: `${DATACONNECTIONS_BASE}/{name}`, + validate: { + params: schema.object({ + name: schema.string(), + }), + }, + }, + async (context, request, response): Promise => { + try { + const dataConnectionsresponse = await context.observability_plugin.observabilityClient + .asScoped(request) + .callAsCurrentUser('ppl.getDataConnectionById', { + dataconnection: request.params.name, + }); + return response.ok({ + body: dataConnectionsresponse, + }); + } catch (error: any) { + console.error('Issue in fetching data connection:', error); + return response.custom({ + statusCode: error.statusCode || 500, + body: error.message, + }); + } + } + ); + + router.delete( + { + path: `${DATACONNECTIONS_BASE}/{name}`, + validate: { + params: schema.object({ + name: schema.string(), + }), + }, + }, + async (context, request, response): Promise => { + try { + const dataConnectionsresponse = await context.observability_plugin.observabilityClient + .asScoped(request) + .callAsCurrentUser('ppl.deleteDataConnection', { + dataconnection: request.params.name, + }); + return response.ok({ + body: dataConnectionsresponse, + }); + } catch (error: any) { + console.error('Issue in deleting data connection:', error); + return response.custom({ + statusCode: error.statusCode || 500, + body: error.message, + }); + } + } + ); + + router.put( + { + path: `${DATACONNECTIONS_BASE}`, + validate: { + body: schema.object({ + name: schema.string(), + connector: schema.string(), + allowedRoles: schema.arrayOf(schema.string()), + properties: schema.any(), + }), + }, + }, + async (context, request, response): Promise => { + try { + const dataConnectionsresponse = await context.observability_plugin.observabilityClient + .asScoped(request) + .callAsCurrentUser('ppl.modifyDataConnection', { + body: { + name: request.body.name, + connector: request.body.connector, + allowedRoles: request.body.allowedRoles, + properties: request.body.properties, + }, + }); + return response.ok({ + body: dataConnectionsresponse, + }); + } catch (error: any) { + console.error('Issue in modifying data connection:', error); + return response.custom({ + statusCode: error.statusCode || 500, + body: error.message, + }); + } + } + ); + + router.get( + { + path: `${DATACONNECTIONS_BASE}`, + validate: false, + }, + async (context, request, response): Promise => { + try { + const dataConnectionsresponse = await context.observability_plugin.observabilityClient + .asScoped(request) + .callAsCurrentUser('ppl.getDataConnections'); + return response.ok({ + body: dataConnectionsresponse, + }); + } catch (error: any) { + console.error('Issue in fetching data connections:', error); + return response.custom({ + statusCode: error.statusCode || 500, + body: error.message, + }); + } + } + ); +} diff --git a/server/routes/index.ts b/server/routes/index.ts index 11912d813..4c278fa45 100644 --- a/server/routes/index.ts +++ b/server/routes/index.ts @@ -21,6 +21,7 @@ import { registerEventAnalyticsRouter } from './event_analytics/event_analytics_ import { registerAppAnalyticsRouter } from './application_analytics/app_analytics_router'; import { registerMetricsRoute } from './metrics/metrics_rounter'; import { registerIntegrationsRoute } from './integrations/integrations_router'; +import { registerDataConnectionsRoute } from './data_connections/data_connections_router'; export function setupRoutes({ router, client }: { router: IRouter; client: ILegacyClusterClient }) { PanelsRouter(router); @@ -42,4 +43,5 @@ export function setupRoutes({ router, client }: { router: IRouter; client: ILega registerMetricsRoute(router); registerIntegrationsRoute(router); + registerDataConnectionsRoute(router); } diff --git a/test/datasources.ts b/test/datasources.ts new file mode 100644 index 000000000..1bdf6e25b --- /dev/null +++ b/test/datasources.ts @@ -0,0 +1,781 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export const showDataConnectionsData = { + schema: [ + { + name: 'DATASOURCE_NAME', + type: 'string', + }, + { + name: 'CONNECTOR_TYPE', + type: 'string', + }, + ], + datarows: [ + ['my_spark_actual', 'SPARK'], + ['@opensearch', 'OPENSEARCH'], + ['my_spark', 'SPARK'], + ], + total: 3, + size: 3, + jsonData: [ + { + DATASOURCE_NAME: 'my_spark3', + CONNECTOR_TYPE: 'SPARK', + }, + { + DATASOURCE_NAME: 'my_spark4', + CONNECTOR_TYPE: 'SPARK', + }, + { + DATASOURCE_NAME: 'my_spark', + CONNECTOR_TYPE: 'SPARK', + }, + { + DATASOURCE_NAME: 'my_spark2', + CONNECTOR_TYPE: 'SPARK', + }, + ], +}; + +export const describeDataConnection = { + name: 'my_spark3', + connector: 'SPARK', + allowedRoles: [], + properties: { + 'spark.connector': 'emr', + 'spark.datasource.flint.host': '0.0.0.0', + 'spark.datasource.flint.integration': + 'https://aws.oss.sonatype.org/content/repositories/snapshots/org/opensearch/opensearch-spark-standalone_2.12/0.1.0-SNAPSHOT/opensearch-spark-standalone_2.12-0.1.0-20230731.182705-3.jar', + 'spark.datasource.flint.port': '9200', + 'spark.datasource.flint.scheme': 'http', + 'emr.cluster': 'j-3UNQLT1MPBGLG', + }, +}; + +export const mockRoleData = { + total: 44, + data: { + security_analytics_ack_alerts: { + reserved: true, + hidden: false, + cluster_permissions: ['cluster:admin/opensearch/securityanalytics/alerts/*'], + index_permissions: [], + tenant_permissions: [], + static: false, + }, + observability_read_access: { + reserved: true, + hidden: false, + cluster_permissions: ['cluster:admin/opensearch/observability/get'], + index_permissions: [], + tenant_permissions: [], + static: false, + }, + kibana_user: { + reserved: true, + hidden: false, + description: 'Provide the minimum permissions for a kibana user', + cluster_permissions: ['cluster_composite_ops'], + index_permissions: [ + { + index_patterns: [ + '.kibana', + '.kibana-6', + '.kibana_*', + '.opensearch_dashboards', + '.opensearch_dashboards-6', + '.opensearch_dashboards_*', + ], + fls: [], + masked_fields: [], + allowed_actions: ['delete', 'index', 'manage', 'read'], + }, + { + index_patterns: ['.tasks', '.management-beats', '*:.tasks', '*:.management-beats'], + fls: [], + masked_fields: [], + allowed_actions: ['indices_all'], + }, + ], + tenant_permissions: [], + static: true, + }, + own_index: { + reserved: true, + hidden: false, + description: 'Allow all for indices named like the current user', + cluster_permissions: ['cluster_composite_ops'], + index_permissions: [ + { + index_patterns: ['${user_name}'], + fls: [], + masked_fields: [], + allowed_actions: ['indices_all'], + }, + ], + tenant_permissions: [], + static: true, + }, + alerting_full_access: { + reserved: true, + hidden: false, + cluster_permissions: [ + 'cluster:admin/opendistro/alerting/*', + 'cluster:admin/opensearch/alerting/*', + 'cluster:admin/opensearch/notifications/feature/publish', + 'cluster_monitor', + ], + index_permissions: [ + { + index_patterns: ['*'], + fls: [], + masked_fields: [], + allowed_actions: [ + 'indices:admin/aliases/get', + 'indices:admin/mappings/get', + 'indices_monitor', + ], + }, + ], + tenant_permissions: [], + static: false, + }, + snapshot_management_read_access: { + reserved: true, + hidden: false, + cluster_permissions: [ + 'cluster:admin/opensearch/snapshot_management/policy/explain', + 'cluster:admin/opensearch/snapshot_management/policy/get', + 'cluster:admin/opensearch/snapshot_management/policy/search', + 'cluster:admin/repository/get', + 'cluster:admin/snapshot/get', + ], + index_permissions: [], + tenant_permissions: [], + static: false, + }, + all_access: { + reserved: true, + hidden: false, + description: 'Allow full access to all indices and all cluster APIs', + cluster_permissions: ['*'], + index_permissions: [ + { + index_patterns: ['*'], + fls: [], + masked_fields: [], + allowed_actions: ['*'], + }, + ], + tenant_permissions: [ + { + tenant_patterns: ['*'], + allowed_actions: ['kibana_all_write'], + }, + ], + static: true, + }, + alerting_read_access: { + reserved: true, + hidden: false, + cluster_permissions: [ + 'cluster:admin/opendistro/alerting/alerts/get', + 'cluster:admin/opendistro/alerting/destination/get', + 'cluster:admin/opendistro/alerting/monitor/get', + 'cluster:admin/opendistro/alerting/monitor/search', + 'cluster:admin/opensearch/alerting/findings/get', + 'cluster:admin/opensearch/alerting/workflow/get', + 'cluster:admin/opensearch/alerting/workflow_alerts/get', + ], + index_permissions: [], + tenant_permissions: [], + static: false, + }, + cross_cluster_replication_follower_full_access: { + reserved: true, + hidden: false, + cluster_permissions: ['cluster:admin/plugins/replication/autofollow/update'], + index_permissions: [ + { + index_patterns: ['*'], + fls: [], + masked_fields: [], + allowed_actions: [ + 'indices:admin/plugins/replication/index/pause', + 'indices:admin/plugins/replication/index/resume', + 'indices:admin/plugins/replication/index/setup/validate', + 'indices:admin/plugins/replication/index/start', + 'indices:admin/plugins/replication/index/status_check', + 'indices:admin/plugins/replication/index/stop', + 'indices:admin/plugins/replication/index/update', + 'indices:data/write/plugins/replication/changes', + ], + }, + ], + tenant_permissions: [], + static: false, + }, + manage_snapshots: { + reserved: true, + hidden: false, + description: 'Provide the minimum permissions for managing snapshots', + cluster_permissions: ['manage_snapshots'], + index_permissions: [ + { + index_patterns: ['*'], + fls: [], + masked_fields: [], + allowed_actions: ['indices:admin/create', 'indices:data/write/index'], + }, + ], + tenant_permissions: [], + static: true, + }, + logstash: { + reserved: true, + hidden: false, + description: 'Provide the minimum permissions for logstash and beats', + cluster_permissions: [ + 'cluster:admin/ingest/pipeline/get', + 'cluster:admin/ingest/pipeline/put', + 'cluster_composite_ops', + 'cluster_monitor', + 'indices:admin/template/get', + 'indices:admin/template/put', + ], + index_permissions: [ + { + index_patterns: ['logstash-*'], + fls: [], + masked_fields: [], + allowed_actions: ['create_index', 'crud'], + }, + { + index_patterns: ['*beat*'], + fls: [], + masked_fields: [], + allowed_actions: ['crud', 'create_index'], + }, + ], + tenant_permissions: [], + static: true, + }, + observability_full_access: { + reserved: true, + hidden: false, + cluster_permissions: [ + 'cluster:admin/opensearch/observability/create', + 'cluster:admin/opensearch/observability/delete', + 'cluster:admin/opensearch/observability/get', + 'cluster:admin/opensearch/observability/update', + ], + index_permissions: [], + tenant_permissions: [], + static: false, + }, + point_in_time_full_access: { + reserved: true, + hidden: false, + cluster_permissions: [], + index_permissions: [ + { + index_patterns: ['*'], + fls: [], + masked_fields: [], + allowed_actions: ['manage_point_in_time'], + }, + ], + tenant_permissions: [], + static: false, + }, + notifications_full_access: { + reserved: true, + hidden: false, + cluster_permissions: ['cluster:admin/opensearch/notifications/*'], + index_permissions: [], + tenant_permissions: [], + static: false, + }, + notifications_read_access: { + reserved: true, + hidden: false, + cluster_permissions: [ + 'cluster:admin/opensearch/notifications/channels/get', + 'cluster:admin/opensearch/notifications/configs/get', + 'cluster:admin/opensearch/notifications/features', + ], + index_permissions: [], + tenant_permissions: [], + static: false, + }, + cross_cluster_replication_leader_full_access: { + reserved: true, + hidden: false, + cluster_permissions: [], + index_permissions: [ + { + index_patterns: ['*'], + fls: [], + masked_fields: [], + allowed_actions: [ + 'indices:admin/plugins/replication/index/setup/validate', + 'indices:data/read/plugins/replication/changes', + 'indices:data/read/plugins/replication/file_chunk', + ], + }, + ], + tenant_permissions: [], + static: false, + }, + knn_read_access: { + reserved: true, + hidden: false, + cluster_permissions: [ + 'cluster:admin/knn_get_model_action', + 'cluster:admin/knn_search_model_action', + 'cluster:admin/knn_stats_action', + ], + index_permissions: [], + tenant_permissions: [], + static: false, + }, + ppl_full_access: { + reserved: true, + hidden: false, + cluster_permissions: ['cluster:admin/opensearch/ppl'], + index_permissions: [ + { + index_patterns: ['*'], + fls: [], + masked_fields: [], + allowed_actions: [ + 'indices:admin/mappings/get', + 'indices:data/read/search*', + 'indices:monitor/settings/get', + ], + }, + ], + tenant_permissions: [], + static: false, + }, + security_analytics_read_access: { + reserved: true, + hidden: false, + cluster_permissions: [ + 'cluster:admin/opensearch/securityanalytics/alerts/get', + 'cluster:admin/opensearch/securityanalytics/correlations/findings', + 'cluster:admin/opensearch/securityanalytics/correlations/list', + 'cluster:admin/opensearch/securityanalytics/detector/get', + 'cluster:admin/opensearch/securityanalytics/detector/search', + 'cluster:admin/opensearch/securityanalytics/findings/get', + 'cluster:admin/opensearch/securityanalytics/mapping/get', + 'cluster:admin/opensearch/securityanalytics/mapping/view/get', + 'cluster:admin/opensearch/securityanalytics/rule/get', + 'cluster:admin/opensearch/securityanalytics/rule/search', + ], + index_permissions: [], + tenant_permissions: [], + static: false, + }, + security_analytics_full_access: { + reserved: true, + hidden: false, + cluster_permissions: [ + 'cluster:admin/opensearch/securityanalytics/alerts/*', + 'cluster:admin/opensearch/securityanalytics/correlations/*', + 'cluster:admin/opensearch/securityanalytics/detector/*', + 'cluster:admin/opensearch/securityanalytics/findings/*', + 'cluster:admin/opensearch/securityanalytics/mapping/*', + 'cluster:admin/opensearch/securityanalytics/rule/*', + ], + index_permissions: [ + { + index_patterns: ['*'], + fls: [], + masked_fields: [], + allowed_actions: ['indices:admin/mapping/put', 'indices:admin/mappings/get'], + }, + ], + tenant_permissions: [], + static: false, + }, + knn_full_access: { + reserved: true, + hidden: false, + cluster_permissions: [ + 'cluster:admin/knn_delete_model_action', + 'cluster:admin/knn_get_model_action', + 'cluster:admin/knn_remove_model_from_cache_action', + 'cluster:admin/knn_search_model_action', + 'cluster:admin/knn_stats_action', + 'cluster:admin/knn_training_job_route_decision_info_action', + 'cluster:admin/knn_training_job_router_action', + 'cluster:admin/knn_training_model_action', + 'cluster:admin/knn_update_model_graveyard_action', + 'cluster:admin/knn_warmup_action', + ], + index_permissions: [], + tenant_permissions: [], + static: false, + }, + asynchronous_search_read_access: { + reserved: true, + hidden: false, + cluster_permissions: ['cluster:admin/opendistro/asynchronous_search/get'], + index_permissions: [], + tenant_permissions: [], + static: false, + }, + index_management_full_access: { + reserved: true, + hidden: false, + cluster_permissions: [ + 'cluster:admin/opendistro/ism/*', + 'cluster:admin/opendistro/rollup/*', + 'cluster:admin/opendistro/transform/*', + 'cluster:admin/opensearch/controlcenter/lron/*', + 'cluster:admin/opensearch/notifications/channels/get', + 'cluster:admin/opensearch/notifications/feature/publish', + ], + index_permissions: [ + { + index_patterns: ['*'], + fls: [], + masked_fields: [], + allowed_actions: ['indices:admin/opensearch/ism/*'], + }, + ], + tenant_permissions: [], + static: false, + }, + readall_and_monitor: { + reserved: true, + hidden: false, + description: 'Provide the minimum permissions for to readall indices and monitor the cluster', + cluster_permissions: ['cluster_composite_ops_ro', 'cluster_monitor'], + index_permissions: [ + { + index_patterns: ['*'], + fls: [], + masked_fields: [], + allowed_actions: ['read'], + }, + ], + tenant_permissions: [], + static: true, + }, + ml_read_access: { + reserved: true, + hidden: false, + cluster_permissions: [ + 'cluster:admin/opensearch/ml/connectors/get', + 'cluster:admin/opensearch/ml/connectors/search', + 'cluster:admin/opensearch/ml/model_groups/search', + 'cluster:admin/opensearch/ml/models/get', + 'cluster:admin/opensearch/ml/models/search', + 'cluster:admin/opensearch/ml/tasks/get', + 'cluster:admin/opensearch/ml/tasks/search', + ], + index_permissions: [], + tenant_permissions: [], + static: false, + }, + security_rest_api_full_access: { + reserved: true, + hidden: false, + cluster_permissions: [ + 'restapi:admin/actiongroups', + 'restapi:admin/allowlist', + 'restapi:admin/internalusers', + 'restapi:admin/nodesdn', + 'restapi:admin/roles', + 'restapi:admin/rolesmapping', + 'restapi:admin/ssl/certs/info', + 'restapi:admin/ssl/certs/reload', + 'restapi:admin/tenants', + ], + index_permissions: [], + tenant_permissions: [], + static: false, + }, + kibana_read_only: { + reserved: true, + hidden: false, + cluster_permissions: [], + index_permissions: [], + tenant_permissions: [], + static: false, + }, + cross_cluster_search_remote_full_access: { + reserved: true, + hidden: false, + cluster_permissions: [], + index_permissions: [ + { + index_patterns: ['*'], + fls: [], + masked_fields: [], + allowed_actions: ['indices:admin/shards/search_shards', 'indices:data/read/search'], + }, + ], + tenant_permissions: [], + static: false, + }, + reports_read_access: { + reserved: true, + hidden: false, + cluster_permissions: [ + 'cluster:admin/opendistro/reports/definition/get', + 'cluster:admin/opendistro/reports/definition/list', + 'cluster:admin/opendistro/reports/instance/get', + 'cluster:admin/opendistro/reports/instance/list', + 'cluster:admin/opendistro/reports/menu/download', + ], + index_permissions: [], + tenant_permissions: [], + static: false, + }, + anomaly_read_access: { + reserved: true, + hidden: false, + cluster_permissions: [ + 'cluster:admin/opendistro/ad/detector/info', + 'cluster:admin/opendistro/ad/detector/search', + 'cluster:admin/opendistro/ad/detector/validate', + 'cluster:admin/opendistro/ad/detectors/get', + 'cluster:admin/opendistro/ad/result/search', + 'cluster:admin/opendistro/ad/result/topAnomalies', + 'cluster:admin/opendistro/ad/tasks/search', + ], + index_permissions: [], + tenant_permissions: [], + static: false, + }, + anomaly_full_access: { + reserved: true, + hidden: false, + cluster_permissions: ['cluster:admin/opendistro/ad/*', 'cluster_monitor'], + index_permissions: [ + { + index_patterns: ['*'], + fls: [], + masked_fields: [], + allowed_actions: [ + 'indices:admin/aliases/get', + 'indices:admin/mappings/get', + 'indices_monitor', + ], + }, + ], + tenant_permissions: [], + static: false, + }, + reports_instances_read_access: { + reserved: true, + hidden: false, + cluster_permissions: [ + 'cluster:admin/opendistro/reports/instance/get', + 'cluster:admin/opendistro/reports/instance/list', + 'cluster:admin/opendistro/reports/menu/download', + ], + index_permissions: [], + tenant_permissions: [], + static: false, + }, + snapshot_management_full_access: { + reserved: true, + hidden: false, + cluster_permissions: [ + 'cluster:admin/opensearch/notifications/feature/publish', + 'cluster:admin/opensearch/snapshot_management/*', + 'cluster:admin/repository/*', + 'cluster:admin/snapshot/*', + ], + index_permissions: [], + tenant_permissions: [], + static: false, + }, + readall: { + reserved: true, + hidden: false, + description: 'Provide the minimum permissions for to readall indices', + cluster_permissions: ['cluster_composite_ops_ro'], + index_permissions: [ + { + index_patterns: ['*'], + fls: [], + masked_fields: [], + allowed_actions: ['read'], + }, + ], + tenant_permissions: [], + static: true, + }, + asynchronous_search_full_access: { + reserved: true, + hidden: false, + cluster_permissions: ['cluster:admin/opendistro/asynchronous_search/*'], + index_permissions: [ + { + index_patterns: ['*'], + fls: [], + masked_fields: [], + allowed_actions: ['indices:data/read/search*'], + }, + ], + tenant_permissions: [], + static: false, + }, + ml_full_access: { + reserved: true, + hidden: false, + cluster_permissions: ['cluster:admin/opensearch/ml/*', 'cluster_monitor'], + index_permissions: [ + { + index_patterns: ['*'], + fls: [], + masked_fields: [], + allowed_actions: ['indices_monitor'], + }, + ], + tenant_permissions: [], + static: false, + }, + reports_full_access: { + reserved: true, + hidden: false, + cluster_permissions: [ + 'cluster:admin/opendistro/reports/definition/create', + 'cluster:admin/opendistro/reports/definition/delete', + 'cluster:admin/opendistro/reports/definition/get', + 'cluster:admin/opendistro/reports/definition/list', + 'cluster:admin/opendistro/reports/definition/on_demand', + 'cluster:admin/opendistro/reports/definition/update', + 'cluster:admin/opendistro/reports/instance/get', + 'cluster:admin/opendistro/reports/instance/list', + 'cluster:admin/opendistro/reports/menu/download', + ], + index_permissions: [], + tenant_permissions: [], + static: false, + }, + security_rest_api_access: { + reserved: true, + hidden: false, + cluster_permissions: [], + index_permissions: [], + tenant_permissions: [], + static: false, + }, + ip2geo_datasource_read_access: { + reserved: true, + hidden: false, + cluster_permissions: ['cluster:admin/geospatial/datasource/get'], + index_permissions: [], + tenant_permissions: [], + static: false, + }, + alerting_ack_alerts: { + reserved: true, + hidden: false, + cluster_permissions: [ + 'cluster:admin/opendistro/alerting/alerts/*', + 'cluster:admin/opendistro/alerting/chained_alerts/*', + 'cluster:admin/opendistro/alerting/workflow_alerts/*', + ], + index_permissions: [], + tenant_permissions: [], + static: false, + }, + ip2geo_datasource_full_access: { + reserved: true, + hidden: false, + cluster_permissions: ['cluster:admin/geospatial/datasource/*'], + index_permissions: [], + tenant_permissions: [], + static: false, + }, + kibana_server: { + reserved: true, + hidden: false, + description: 'Provide the minimum permissions for the Kibana server', + cluster_permissions: [ + 'cluster_composite_ops', + 'cluster_monitor', + 'indices:admin/index_template*', + 'indices:admin/template*', + 'indices:data/read/scroll*', + 'manage_point_in_time', + ], + index_permissions: [ + { + index_patterns: ['.kibana', '.opensearch_dashboards'], + fls: [], + masked_fields: [], + allowed_actions: ['indices_all'], + }, + { + index_patterns: ['.kibana-6', '.opensearch_dashboards-6'], + fls: [], + masked_fields: [], + allowed_actions: ['indices_all'], + }, + { + index_patterns: ['.kibana_*', '.opensearch_dashboards_*'], + fls: [], + masked_fields: [], + allowed_actions: ['indices_all'], + }, + { + index_patterns: ['.tasks'], + fls: [], + masked_fields: [], + allowed_actions: ['indices_all'], + }, + { + index_patterns: ['.management-beats*'], + fls: [], + masked_fields: [], + allowed_actions: ['indices_all'], + }, + { + index_patterns: ['*'], + fls: [], + masked_fields: [], + allowed_actions: ['indices:admin/aliases*'], + }, + ], + tenant_permissions: [], + static: true, + }, + notebooks_read_access: { + reserved: true, + hidden: false, + cluster_permissions: [ + 'cluster:admin/opendistro/notebooks/get', + 'cluster:admin/opendistro/notebooks/list', + ], + index_permissions: [], + tenant_permissions: [], + static: false, + }, + notebooks_full_access: { + reserved: true, + hidden: false, + cluster_permissions: [ + 'cluster:admin/opendistro/notebooks/create', + 'cluster:admin/opendistro/notebooks/delete', + 'cluster:admin/opendistro/notebooks/get', + 'cluster:admin/opendistro/notebooks/list', + 'cluster:admin/opendistro/notebooks/update', + ], + index_permissions: [], + tenant_permissions: [], + static: false, + }, + }, +};