import React, { useState, useCallback } from 'react'; import { FormattedMessage } from 'react-intl'; import { Skeleton, Split, SplitItem, ActionList, ActionListItem, Dropdown, DropdownItem, DropdownToggle, DropdownToggleAction, Alert, } from '@patternfly/react-core'; import { translate as __ } from 'foremanReact/common/I18n'; import { TableVariant, Thead, Tbody, Tr, Th, Td } from '@patternfly/react-table'; import { ExclamationTriangleIcon } from '@patternfly/react-icons'; import { useSelector } from 'react-redux'; import { selectAPIResponse } from 'foremanReact/redux/API/APISelectors'; import { useBulkSelect, useUrlParams } from 'foremanReact/components/PF4/TableIndexPage/Table/TableHooks'; import { useTableSort } from 'foremanReact/components/PF4/Helpers/useTableSort'; import TracesEnabler from './TracesEnabler'; import TableWrapper from '../../../../Table/TableWrapper'; import { getHostTraces } from './HostTracesActions'; import { resolveTraces } from '../RemoteExecutionActions'; import { selectHostTracesStatus } from './HostTracesSelectors'; import { resolveTraceUrl } from '../customizedRexUrlHelpers'; import './TracesTab.scss'; import hostIdNotReady from '../../HostDetailsActions'; import SortableColumnHeaders from '../../../../Table/components/SortableColumnHeaders'; import { useRexJobPolling } from '../RemoteExecutionHooks'; import { hasRequiredPermissions as can, missingRequiredPermissions as cannot, userPermissionsFromHostDetails } from '../../hostDetailsHelpers'; import { HOST_TRACES_KEY } from './HostTracesConstants'; const invokeRexJobs = ['create_job_invocations']; const createBookmarks = ['create_bookmarks']; const containsStaticType = (results = []) => results.some(result => result.app_type === 'static'); const TracesTab = () => { const hostDetails = useSelector(state => selectAPIResponse(state, 'HOST_DETAILS')); const { id: hostId, name: hostname, content_facet_attributes: contentFacetAttributes, } = hostDetails; const showActions = can(invokeRexJobs, userPermissionsFromHostDetails({ hostDetails })); const showEnableTracer = (contentFacetAttributes?.katello_tracer_installed === false); const tracerRpmAvailable = contentFacetAttributes?.katello_tracer_rpm_available; const emptyContentTitle = showActions ? __('No applications to restart') : __('Traces not available'); const tracesNotAvailBody = showEnableTracer ? __('Traces may be enabled by a user with the appropriate permissions.') : __('Traces will be shown here to a user with the appropriate permissions.'); const emptyContentBody = showActions ? ({__('installing or updating packages')}, }} defaultMessage={__('Traces may be listed here after {pkgLink}.')} />) : tracesNotAvailBody; const emptySearchTitle = __('No matching traces found'); const emptySearchBody = __('Try changing your search settings.'); const errorSearchTitle = __('Problem searching traces'); const columnHeaders = [ __('Application'), __('Type'), __('Helper'), ]; const COLUMNS_TO_SORT_PARAMS = { [columnHeaders[0]]: 'application', [columnHeaders[1]]: 'app_type', [columnHeaders[2]]: 'helper', }; const { pfSortParams, apiSortParams, activeSortColumn, activeSortDirection, } = useTableSort({ allColumns: columnHeaders, columnsToSortParams: COLUMNS_TO_SORT_PARAMS, initialSortColumnName: 'Application', }); const { searchParam } = useUrlParams(); const [isBulkActionOpen, setIsBulkActionOpen] = useState(false); const toggleBulkAction = () => setIsBulkActionOpen(prev => !prev); const response = useSelector(state => selectAPIResponse(state, 'HOST_TRACES')); const { results, ...meta } = response; const { error: errorSearchBody } = meta; const tracesSearchQuery = id => `id = ${id}`; const { selectOne, isSelected, searchQuery, selectedCount, isSelectable, updateSearchQuery, selectNone, fetchBulkParams, selectedResults, selectAllMode, ...selectAll } = useBulkSelect({ results, metadata: meta, isSelectable: result => !!result.restart_command, initialSearchQuery: searchParam || '', }); const willRestartHost = containsStaticType(selectedResults) || (selectAllMode && containsStaticType(results)); const BulkRestartTracesAction = () => resolveTraces({ hostname, search: fetchBulkParams(), }); const { triggerJobStart: triggerBulkRestart, lastCompletedJob: lastCompletedBulkRestart, isPolling: isBulkRestartInProgress, } = useRexJobPolling(BulkRestartTracesAction); const restartTraceAction = id => resolveTraces({ hostname, search: tracesSearchQuery(id), }); const { triggerJobStart: triggerAppRestart, lastCompletedJob: lastCompletedAppRestart, isPolling: isAppRestartInProgress, } = useRexJobPolling(restartTraceAction); const actionInProgress = (isBulkRestartInProgress || isAppRestartInProgress); const fetchItems = useCallback( params => (hostId ? getHostTraces(hostId, { ...apiSortParams, ...params }) : hostIdNotReady), [hostId, apiSortParams], ); const onBulkRestartApp = () => { triggerBulkRestart(); selectNone(); }; const onRestartApp = id => triggerAppRestart(id); const bulkCustomizedRexUrl = () => resolveTraceUrl({ hostname, search: (selectedCount > 0) ? fetchBulkParams() : '', }); const readOnlyBookmarks = cannot(createBookmarks, userPermissionsFromHostDetails({ hostDetails })); const dropdownItems = [ {__('Restart via remote execution')} , {__('Restart via customized remote execution')} , ]; const actionButtons = showActions ? ( {willRestartHost ? __('Reboot host') : __('Restart app')} , ]} isDisabled={selectedCount === 0} splitButtonVariant="action" toggleVariant="primary" onToggle={toggleBulkAction} /> } isOpen={isBulkActionOpen} dropdownItems={dropdownItems} /> ) : null; const status = useSelector(state => selectHostTracesStatus(state)); if (showEnableTracer && showActions) { return ; } if (!hostId) return ; /* eslint-disable max-len */ return (

{__('Tracer helps administrators identify applications that need to be restarted after a system is patched.')}

{willRestartHost && ( )} {results?.map((result, rowIndex) => { const { id, application, helper, app_type: appType, } = result; const resolveDisabled = !isSelectable(id); let disabledReason; if (resolveDisabled) disabledReason = __('Traces that require logout cannot be restarted remotely'); if (actionInProgress) disabledReason = __('A remote execution job is in progress'); let rowDropdownItems = [ { title: 'Restart via remote execution', onClick: () => onRestartApp(id), isDisabled: actionInProgress }, { component: 'a', href: resolveTraceUrl({ hostname, search: tracesSearchQuery(id) }), title: 'Restart via customized remote execution', }, ]; if (resolveDisabled) { rowDropdownItems = [ { isDisabled: true, title: __('Traces that require logout cannot be restarted remotely') }, ]; } return ( {showActions ? ( selectOne(selected, id, result), rowIndex, variant: 'checkbox', }} title={disabledReason} /> ) :   } {application} {appType} {appType === 'static' ? : null} {helper} {showActions && ( )} ); }) }
); }; /* eslint-enable max-len */ export default TracesTab;