/* * Copyright OpenSearch Contributors * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://www.apache.org/licenses/LICENSE-2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ import React, { useState, useEffect } from 'react'; import { EuiFlexGroup, EuiText, EuiPageHeader, EuiTitle, EuiPageContent, EuiPageContentHeader, EuiPageContentHeaderSection, EuiFlexItem, EuiButton, EuiPageBody, EuiInMemoryTable, EuiBasicTableColumn, EuiButtonEmpty, EuiSearchBarProps, Query, } from '@elastic/eui'; import { difference } from 'lodash'; import { AppDependencies } from '../../types'; import { transformRoleData, requestDeleteRoles, RoleListing, fetchRole, fetchRoleMapping, buildSearchFilterOptions, } from '../utils/role-list-utils'; import { ResourceType, Action } from '../types'; import { buildHashUrl } from '../utils/url-builder'; import { ExternalLink, renderCustomization, truncatedListView, tableItemsUIProps, } from '../utils/display-utils'; import { showTableStatusMessage } from '../utils/loading-spinner-utils'; import { useDeleteConfirmState } from '../utils/delete-confirm-modal-utils'; import { useContextMenuState } from '../utils/context-menu'; import { DocLinks } from '../constants'; const columns: Array> = [ { field: 'roleName', name: 'Role', render: (text: string) => ( {text} ), sortable: true, }, { field: 'clusterPermissions', name: 'Cluster permissions', render: truncatedListView(tableItemsUIProps), truncateText: true, }, { field: 'indexPermissions', name: 'Index', render: truncatedListView(tableItemsUIProps), truncateText: true, }, { field: 'internalUsers', name: 'Internal users', render: truncatedListView(tableItemsUIProps), }, { field: 'backendRoles', name: 'Backend roles', render: truncatedListView(tableItemsUIProps), }, { field: 'tenantPermissions', name: 'Tenants', render: truncatedListView(tableItemsUIProps), }, { field: 'reserved', name: 'Customization', render: (reserved: boolean) => { return renderCustomization(reserved, tableItemsUIProps); }, }, ]; export function RoleList(props: AppDependencies) { const [roleData, setRoleData] = React.useState([]); const [errorFlag, setErrorFlag] = React.useState(false); const [selection, setSelection] = React.useState([]); const [loading, setLoading] = useState(false); React.useEffect(() => { const fetchData = async () => { try { setLoading(true); const rawRoleData = await fetchRole(props.coreStart.http); const rawRoleMappingData = await fetchRoleMapping(props.coreStart.http); const processedData = transformRoleData(rawRoleData, rawRoleMappingData); setRoleData(processedData); } catch (e) { console.log(e); setErrorFlag(true); } finally { setLoading(false); } }; fetchData(); }, [props.coreStart.http]); const handleDelete = async () => { const rolesToDelete: string[] = selection.map((r) => r.roleName); try { await requestDeleteRoles(props.coreStart.http, rolesToDelete); // Refresh from server (calling fetchData) does not work here, the server still return the roles // that had been just deleted, probably because ES takes some time to sync to all nodes. // So here remove the selected roles from local memory directly. setRoleData(difference(roleData, selection)); setSelection([]); } catch (e) { console.log(e); } finally { closeActionsMenu(); } }; const [showDeleteConfirmModal, deleteConfirmModal] = useDeleteConfirmState( handleDelete, 'role(s)' ); const actionsMenuItems = [ { window.location.href = buildHashUrl(ResourceType.roles, Action.edit, selection[0].roleName); }} disabled={selection.length !== 1 || selection[0].reserved} > Edit , // TODO: Change duplication to a popup window { window.location.href = buildHashUrl( ResourceType.roles, Action.duplicate, selection[0].roleName ); }} disabled={selection.length !== 1} > Duplicate , e.reserved)} > Delete , ]; const [actionsMenu, closeActionsMenu] = useContextMenuState('Actions', {}, actionsMenuItems); const [searchOptions, setSearchOptions] = useState({}); const [query, setQuery] = useState(null); useEffect(() => { setSearchOptions({ onChange: (arg) => { setQuery(arg.query); return true; }, filters: [ { type: 'field_value_selection', field: 'clusterPermissions', name: 'Cluster permissions', multiSelect: 'or', options: buildSearchFilterOptions(roleData, 'clusterPermissions'), }, { type: 'field_value_selection', field: 'indexPermissions', name: 'Index', multiSelect: 'or', options: buildSearchFilterOptions(roleData, 'indexPermissions'), }, { type: 'field_value_selection', field: 'internalUsers', name: 'Internal users', multiSelect: 'or', options: buildSearchFilterOptions(roleData, 'internalUsers'), }, { type: 'field_value_selection', field: 'backendRoles', name: 'Backend roles', multiSelect: 'or', options: buildSearchFilterOptions(roleData, 'backendRoles'), }, { type: 'field_value_selection', field: 'tenantPermissions', name: 'Tenants', multiSelect: 'or', options: buildSearchFilterOptions(roleData, 'tenantPermissions'), }, { type: 'field_value_selection', field: 'reserved', name: 'Customization', multiSelect: false, options: [ { value: true, view: renderCustomization(true, tableItemsUIProps), }, { value: false, view: renderCustomization(false, tableItemsUIProps), }, ], }, ], }); }, [roleData]); return ( <>

Roles

Roles {' '} ({Query.execute(query || '', roleData).length})

Roles are the core way of controlling access to your cluster. Roles contain any combination of cluster-wide permission, index-specific permissions, document- and field-level security, and tenants. Then you map users to these roles so that users gain those permissions.
{actionsMenu} Create role
{deleteConfirmModal}
); }