diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/create_trusted_app_form.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/create_trusted_app_form.tsx index 51d02105bdaac..c89a5ba3325a9 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/create_trusted_app_form.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/create_trusted_app_form.tsx @@ -23,7 +23,7 @@ import { TrustedApp, } from '../../../../../../common/endpoint/types'; import { LogicalConditionBuilderProps } from './logical_condition/logical_condition_builder'; -import { OS_TITLES } from '../constants'; +import { OS_TITLES } from '../translations'; import { isMacosLinuxTrustedAppCondition, isTrustedAppSupportedOs, diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/constants.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/constants.ts deleted file mode 100644 index d5df8c528511a..0000000000000 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/constants.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { i18n } from '@kbn/i18n'; -import { TrustedApp } from '../../../../../common/endpoint/types'; - -export const OS_TITLES: Readonly<{ [K in TrustedApp['os']]: string }> = { - windows: i18n.translate('xpack.securitySolution.trustedapps.os.windows', { - defaultMessage: 'Windows', - }), - macos: i18n.translate('xpack.securitySolution.trustedapps.os.macos', { - defaultMessage: 'Mac OS', - }), - linux: i18n.translate('xpack.securitySolution.trustedapps.os.linux', { - defaultMessage: 'Linux', - }), -}; diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/translations.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/translations.ts new file mode 100644 index 0000000000000..e16155df6d2db --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/translations.ts @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { + TrustedApp, + MacosLinuxConditionEntry, + WindowsConditionEntry, +} from '../../../../../common/endpoint/types'; + +export const OS_TITLES: Readonly<{ [K in TrustedApp['os']]: string }> = { + windows: i18n.translate('xpack.securitySolution.trustedapps.os.windows', { + defaultMessage: 'Windows', + }), + macos: i18n.translate('xpack.securitySolution.trustedapps.os.macos', { + defaultMessage: 'Mac OS', + }), + linux: i18n.translate('xpack.securitySolution.trustedapps.os.linux', { + defaultMessage: 'Linux', + }), +}; + +export const PROPERTY_TITLES: Readonly< + { [K in keyof Omit]: string } +> = { + name: i18n.translate('xpack.securitySolution.trustedapps.trustedapp.name', { + defaultMessage: 'Name', + }), + os: i18n.translate('xpack.securitySolution.trustedapps.trustedapp.os', { + defaultMessage: 'OS', + }), + created_at: i18n.translate('xpack.securitySolution.trustedapps.trustedapp.createdAt', { + defaultMessage: 'Date Created', + }), + created_by: i18n.translate('xpack.securitySolution.trustedapps.trustedapp.createdBy', { + defaultMessage: 'Created By', + }), +}; + +export const ENTRY_PROPERTY_TITLES: Readonly< + { [K in keyof Omit]: string } +> = { + field: i18n.translate('xpack.securitySolution.trustedapps.trustedapp.entry.field', { + defaultMessage: 'Field', + }), + operator: i18n.translate('xpack.securitySolution.trustedapps.trustedapp.entry.operator', { + defaultMessage: 'Operator', + }), + value: i18n.translate('xpack.securitySolution.trustedapps.trustedapp.entry.value', { + defaultMessage: 'Value', + }), +}; + +export const ACTIONS_COLUMN_TITLE = i18n.translate( + 'xpack.securitySolution.trustedapps.list.columns.actions', + { + defaultMessage: 'Actions', + } +); + +export const LIST_ACTIONS = { + delete: { + name: i18n.translate('xpack.securitySolution.trustedapps.list.actions.delete', { + defaultMessage: 'Remove', + }), + description: i18n.translate( + 'xpack.securitySolution.trustedapps.list.actions.delete.description', + { + defaultMessage: 'Remove this entry', + } + ), + }, +}; diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_list.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_list.tsx index c91512d477510..80e735798567a 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_list.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_list.tsx @@ -5,11 +5,16 @@ */ import { Dispatch } from 'redux'; -import React, { memo, useCallback, useMemo } from 'react'; +import React, { memo, ReactNode, useCallback, useMemo, useState } from 'react'; import { useDispatch } from 'react-redux'; import { useHistory } from 'react-router-dom'; -import { EuiBasicTable, EuiBasicTableColumn, EuiTableActionsColumnType } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; +import { + EuiBasicTable, + EuiBasicTableColumn, + EuiButtonIcon, + EuiTableActionsColumnType, + RIGHT_ALIGNMENT, +} from '@elastic/eui'; import { Immutable } from '../../../../../common/endpoint/types'; import { AppAction } from '../../../../common/store/actions'; @@ -29,41 +34,51 @@ import { import { useTrustedAppsSelector } from './hooks'; import { FormattedDate } from '../../../../common/components/formatted_date'; -import { OS_TITLES } from './constants'; - -const COLUMN_TITLES: Readonly< - { [K in keyof Omit | 'actions']: string } -> = { - name: i18n.translate('xpack.securitySolution.trustedapps.list.columns.name', { - defaultMessage: 'Name', - }), - os: i18n.translate('xpack.securitySolution.trustedapps.list.columns.os', { - defaultMessage: 'OS', - }), - created_at: i18n.translate('xpack.securitySolution.trustedapps.list.columns.createdAt', { - defaultMessage: 'Date Created', - }), - created_by: i18n.translate('xpack.securitySolution.trustedapps.list.columns.createdBy', { - defaultMessage: 'Created By', - }), - actions: i18n.translate('xpack.securitySolution.trustedapps.list.columns.actions', { - defaultMessage: 'Actions', - }), -}; +import { ACTIONS_COLUMN_TITLE, LIST_ACTIONS, OS_TITLES, PROPERTY_TITLES } from './translations'; +import { TrustedAppCard } from './components/trusted_app_card'; + +interface DetailsMap { + [K: string]: ReactNode; +} + +interface TrustedAppsListContext { + dispatch: Dispatch>; + detailsMapState: [DetailsMap, (value: DetailsMap) => void]; +} +type ColumnsList = Array>>; type ActionsList = EuiTableActionsColumnType>['actions']; -const getActionDefinitions = (dispatch: Dispatch>): ActionsList => [ +const toggleItemDetailsInMap = ( + map: DetailsMap, + item: Immutable, + { dispatch }: TrustedAppsListContext +): DetailsMap => { + const changedMap = { ...map }; + + if (changedMap[item.id]) { + delete changedMap[item.id]; + } else { + changedMap[item.id] = ( + { + dispatch({ + type: 'trustedAppDeletionDialogStarted', + payload: { entry: item }, + }); + }} + /> + ); + } + + return changedMap; +}; + +const getActionDefinitions = ({ dispatch }: TrustedAppsListContext): ActionsList => [ { - name: i18n.translate('xpack.securitySolution.trustedapps.list.actions.delete', { - defaultMessage: 'Delete', - }), - description: i18n.translate( - 'xpack.securitySolution.trustedapps.list.actions.delete.description', - { - defaultMessage: 'Delete this entry', - } - ), + name: LIST_ACTIONS.delete.name, + description: LIST_ACTIONS.delete.description, 'data-test-subj': 'trustedAppDeleteAction', isPrimary: true, icon: 'trash', @@ -78,44 +93,61 @@ const getActionDefinitions = (dispatch: Dispatch>): Actions }, ]; -type ColumnsList = Array>>; +const getColumnDefinitions = (context: TrustedAppsListContext): ColumnsList => { + const [itemDetailsMap, setItemDetailsMap] = context.detailsMapState; -const getColumnDefinitions = (dispatch: Dispatch>): ColumnsList => [ - { - field: 'name', - name: COLUMN_TITLES.name, - }, - { - field: 'os', - name: COLUMN_TITLES.os, - render(value: TrustedApp['os'], record: Immutable) { - return OS_TITLES[value]; + return [ + { + field: 'name', + name: PROPERTY_TITLES.name, }, - }, - { - field: 'created_at', - name: COLUMN_TITLES.created_at, - render(value: TrustedApp['created_at'], record: Immutable) { - return ( - - ); + { + field: 'os', + name: PROPERTY_TITLES.os, + render(value: TrustedApp['os'], record: Immutable) { + return OS_TITLES[value]; + }, }, - }, - { - field: 'created_by', - name: COLUMN_TITLES.created_by, - }, - { - name: COLUMN_TITLES.actions, - actions: getActionDefinitions(dispatch), - }, -]; + { + field: 'created_at', + name: PROPERTY_TITLES.created_at, + render(value: TrustedApp['created_at'], record: Immutable) { + return ( + + ); + }, + }, + { + field: 'created_by', + name: PROPERTY_TITLES.created_by, + }, + { + name: ACTIONS_COLUMN_TITLE, + actions: getActionDefinitions(context), + }, + { + align: RIGHT_ALIGNMENT, + width: '40px', + isExpander: true, + render(item: Immutable) { + return ( + setItemDetailsMap(toggleItemDetailsInMap(itemDetailsMap, item, context))} + aria-label={itemDetailsMap[item.id] ? 'Collapse' : 'Expand'} + iconType={itemDetailsMap[item.id] ? 'arrowUp' : 'arrowDown'} + /> + ); + }, + }, + ]; +}; export const TrustedAppsList = memo(() => { + const [detailsMap, setDetailsMap] = useState({}); const pageIndex = useTrustedAppsSelector(getListCurrentPageIndex); const pageSize = useTrustedAppsSelector(getListCurrentPageSize); const totalItemCount = useTrustedAppsSelector(getListTotalItemsCount); @@ -125,10 +157,16 @@ export const TrustedAppsList = memo(() => { return ( getColumnDefinitions(dispatch), [dispatch])} + columns={useMemo( + () => getColumnDefinitions({ dispatch, detailsMapState: [detailsMap, setDetailsMap] }), + [dispatch, detailsMap, setDetailsMap] + )} items={useMemo(() => [...listItems], [listItems])} error={useTrustedAppsSelector(getListErrorMessage)} loading={useTrustedAppsSelector(isListLoading)} + itemId="id" + itemIdToExpandedRowMap={detailsMap} + isExpandable={true} pagination={useMemo( () => ({ pageIndex,