diff --git a/app/controllers/foreman_puppet/api/v2/hosts_bulk_actions_controller.rb b/app/controllers/foreman_puppet/api/v2/hosts_bulk_actions_controller.rb new file mode 100644 index 00000000..52795942 --- /dev/null +++ b/app/controllers/foreman_puppet/api/v2/hosts_bulk_actions_controller.rb @@ -0,0 +1,59 @@ +module ForemanPuppet + module Api + module V2 + class HostsBulkActionsController < ::ForemanPuppet::Api::V2::PuppetBaseController + include ::Api::V2::BulkHostsExtension + before_action :find_editable_hosts, only: %i[change_puppet_proxy remove_puppet_proxy] + before_action :find_smart_proxy, only: %i[change_puppet_proxy] + + def_param_group :bulk_params do + param :organization_id, :number, required: true, desc: N_('ID of the organization') + param :included, Hash, required: true, action_aware: true do + param :search, String, required: false, desc: N_('Search string for hosts to perform an action on') + param :ids, Array, required: false, desc: N_('List of host ids to perform an action on') + end + param :excluded, Hash, required: true, action_aware: true do + param :ids, Array, required: false, desc: N_('List of host ids to exclude and not run an action on') + end + end + + api :PUT, '/hosts/bulk/change_puppet_proxy', N_('Change Puppet (CA) Proxy') + param_group :bulk_params + param :proxy_id, :number, required: true, desc: N_('ID of the Puppet proxy to reassign the hosts to') + param :ca_proxy, :bool, required: true, desc: N_('True, if Puppet CA proxy should be changed instead of the Puppet proxy') + def change_puppet_proxy + ::BulkHostsManager.new(hosts: @hosts).change_puppet_proxy(@proxy, params[:ca_proxy]) + process_response(true, { message: n_('Updated host: changed Puppet proxy', 'Updated hosts: changed Puppet proxy', @hosts.count) }) + end + + api :PUT, '/hosts/bulk/remove_puppet_proxy', N_('Remove Puppet (CA) Proxy') + param_group :bulk_params + param :ca_proxy, :bool, required: true, desc: N_('True, if Puppet CA proxy should be removed instead of the Puppet proxy') + def remove_puppet_proxy + ::BulkHostsManager.new(hosts: @hosts).change_puppet_proxy(nil, params[:ca_proxy]) + process_response(true, { message: n_('Updated host: removed Puppet proxy', 'Updated hosts: removedPuppet proxy', @hosts.count) }) + end + + private + + def find_editable_hosts + find_bulk_hosts(:edit_hosts, params) + end + + def find_smart_proxy + @proxy = SmartProxy.find_by(id: params[:smart_proxy_id]) + if @proxy.nil? + render json: { + error: { + message: _('The Puppet proxy you have provided cannot be found.'), + }, + }, status: :unprocessable_entity + false + else + true + end + end + end + end + end +end diff --git a/app/services/concerns/foreman_puppet/extensions/bulk_hosts_manager.rb b/app/services/concerns/foreman_puppet/extensions/bulk_hosts_manager.rb new file mode 100644 index 00000000..def4d784 --- /dev/null +++ b/app/services/concerns/foreman_puppet/extensions/bulk_hosts_manager.rb @@ -0,0 +1,21 @@ +module ForemanPuppet + module Extensions + module BulkHostsManager + extend ActiveSupport::Concern + + def change_puppet_proxy(proxy, ca_proxy) + @hosts.each do |host| + if ca_proxy + host.puppet_ca_proxy = proxy + else + host.puppet_proxy = proxy + end + host.save(validate: false) + rescue StandardError => e + message = format(_('Failed to set %{proxy_type} proxy for %{host}.'), host: host, proxy_type: proxy_type) + Foreman::Logging.exception(message, e) + end + end + end + end +end diff --git a/config/api_routes.rb b/config/api_routes.rb index cda02fed..a7f6d713 100644 --- a/config/api_routes.rb +++ b/config/api_routes.rb @@ -2,6 +2,9 @@ namespace :api, defaults: { format: 'json' } do scope '(:apiv)', module: :v2, defaults: { apiv: 'v2' }, apiv: /v1|v2/, constraints: ApiConstraints.new(version: 2, default: true) do constraints(id: %r{[^/]+}) do + match 'hosts/bulk/change_puppet_proxy', to: 'hosts_bulk_actions#change_puppet_proxy', via: [:put] + match 'hosts/bulk/remove_puppet_proxy', to: 'hosts_bulk_actions#remove_puppet_proxy', via: [:put] + resources :config_groups, except: %i[new edit] resources :hosts, only: [] do diff --git a/lib/foreman_puppet/engine.rb b/lib/foreman_puppet/engine.rb index 63424e87..8f7d7396 100644 --- a/lib/foreman_puppet/engine.rb +++ b/lib/foreman_puppet/engine.rb @@ -49,6 +49,7 @@ class Engine < ::Rails::Engine ::ProvisioningTemplate.include ForemanPuppet::Extensions::ProvisioningTemplate ::HostCounter.prepend ForemanPuppet::Extensions::HostCounter + ::BulkHostsManager.include ForemanPuppet::Extensions::BulkHostsManager ::Api::V2::BaseController.include ForemanPuppet::Extensions::ApiBaseController ::Api::V2::HostsController.include ForemanPuppet::Extensions::ApiV2HostsController diff --git a/webpack/global_index.js b/webpack/global_index.js index 2ee8f404..51e88141 100644 --- a/webpack/global_index.js +++ b/webpack/global_index.js @@ -1,9 +1,16 @@ +import React from 'react'; +import { addGlobalFill } from 'foremanReact/components/common/Fill/GlobalFill'; import { registerReducer } from 'foremanReact/common/MountingService'; import { registerColumns } from 'foremanReact/components/HostsIndex/Columns/core'; import { translate as __ } from 'foremanReact/common/I18n'; import reducers from './src/reducers'; import { registerFills } from './src/Extends/Fills'; import { registerLegacy } from './legacy'; +import HostsIndexActionsBar from './src/Extends/Hosts/ActionsBar'; +import BulkChangePuppetProxy from './src/Extends/Hosts/BulkActions/BulkChangePuppetProxy'; +import BulkChangePuppetCAProxy from './src/Extends/Hosts/BulkActions/BulkChangePuppetCAProxy'; +import BulkRemovePuppetProxy from './src/Extends/Hosts/BulkActions/BulkRemovePuppetProxy'; +import BulkRemovePuppetCAProxy from './src/Extends/Hosts/BulkActions/BulkRemovePuppetCAProxy'; // register reducers registerReducer('puppet', reducers); @@ -29,4 +36,39 @@ puppetHostsIndexColumns.forEach(column => { column.categoryKey = 'puppet'; }); +addGlobalFill( + 'hosts-index-kebab', + 'puppet-hosts-index-kebab', + , + 100 +); + +addGlobalFill( + '_all-hosts-modals', + 'BulkChangePuppetProxy', + , + 100 +); + +addGlobalFill( + '_all-hosts-modals', + 'BulkChangePuppetCAProxy', + , + 100 +); + +addGlobalFill( + '_all-hosts-modals', + 'BulkRemovePuppetCAProxy', + , + 100 +); + +addGlobalFill( + '_all-hosts-modals', + 'BulkRemovePuppetProxy', + , + 100 +); + registerColumns(puppetHostsIndexColumns); diff --git a/webpack/src/Extends/Hosts/ActionsBar/ActionsBar.scss b/webpack/src/Extends/Hosts/ActionsBar/ActionsBar.scss new file mode 100644 index 00000000..926931cc --- /dev/null +++ b/webpack/src/Extends/Hosts/ActionsBar/ActionsBar.scss @@ -0,0 +1,14 @@ +.disabled-menu-item-span { + width: 25em; + display: flex; + flex-direction: row; +} + +.disabled-menu-item-p { + margin-left: 0.6em; + word-break: normal; +} + +.disabled-menu-item-icon { + font-size: small; +} diff --git a/webpack/src/Extends/Hosts/ActionsBar/index.js b/webpack/src/Extends/Hosts/ActionsBar/index.js new file mode 100644 index 00000000..45f64d6f --- /dev/null +++ b/webpack/src/Extends/Hosts/ActionsBar/index.js @@ -0,0 +1,105 @@ +import React, { useContext, useEffect } from 'react'; +import PropTypes from 'prop-types'; +import { useDispatch } from 'react-redux'; +import { Menu, MenuItem, MenuContent, MenuList } from '@patternfly/react-core'; +import { BanIcon } from '@patternfly/react-icons'; +import { translate as __ } from 'foremanReact/common/I18n'; +import { ForemanHostsIndexActionsBarContext } from 'foremanReact/components/HostsIndex'; +import { useForemanModal } from 'foremanReact/components/ForemanModal/ForemanModalHooks'; +import { addModal } from 'foremanReact/components/ForemanModal/ForemanModalActions'; +import './ActionsBar.scss'; + +const DisabledMenuItemDescription = ({ disabledReason }) => ( + + + + +

{disabledReason}

+
+); + +DisabledMenuItemDescription.propTypes = { + disabledReason: PropTypes.string.isRequired, +}; + +const HostActionsBar = () => { + const { selectedCount, setMenuOpen } = useContext( + ForemanHostsIndexActionsBarContext + ); + + const dispatch = useDispatch(); + useEffect(() => { + [ + 'bulk-change-puppet-proxy', + 'bulk-change-puppet-ca-proxy', + 'bulk-remove-puppet-proxy', + 'bulk-remove-puppet-ca-proxy', + ].forEach(id => { + dispatch(addModal({ id })); + }); + }, [dispatch]); + const { setModalOpen: openBulkChangePuppetProxy } = useForemanModal({ + id: 'bulk-change-puppet-proxy', + }); + const { setModalOpen: openBulkChangePuppetCAProxy } = useForemanModal({ + id: 'bulk-change-puppet-ca-proxy', + }); + const { setModalOpen: openBulkRemovePuppetProxy } = useForemanModal({ + id: 'bulk-remove-puppet-proxy', + }); + const { setModalOpen: openBulkRemovePuppetCAProxy } = useForemanModal({ + id: 'bulk-remove-puppet-ca-proxy', + }); + + return ( + setMenuOpen(false)}> + + + + {__('Change Puppet Proxy')} + + + {__('Remove Puppet Proxy')} + + + {__('Change Puppet CA Proxy')} + + + {__('Remove Puppet CA Proxy')} + + + + + } + > + {__('Puppet')} + + ); +}; + +export default HostActionsBar; diff --git a/webpack/src/Extends/Hosts/BulkActions/BulkChangeProxyCommon/actions.js b/webpack/src/Extends/Hosts/BulkActions/BulkChangeProxyCommon/actions.js new file mode 100644 index 00000000..a618ac76 --- /dev/null +++ b/webpack/src/Extends/Hosts/BulkActions/BulkChangeProxyCommon/actions.js @@ -0,0 +1,37 @@ +import { APIActions } from 'foremanReact/redux/API'; +import { foremanUrl } from 'foremanReact/common/helpers'; + +export const SMART_PROXY_KEY = 'SMART_PROXY_KEY'; +export const BULK_CHANGE_PUPPET_CA_PROXY_KEY = 'BULK_CHANGE_PUPPET_CA_PROXY'; +export const BULK_CHANGE_PUPPET_PROXY_KEY = 'BULK_CHANGE_PUPPET_PROXY'; + +export const fetchSmartProxies = () => { + const url = foremanUrl('/api/smart_proxies'); + return APIActions.get({ + key: SMART_PROXY_KEY, + url, + params: { + per_page: 'all', + }, + }); +}; + +export const bulkChangePuppetProxy = ( + params, + handleSuccess, + handleError, + key +) => { + const url = foremanUrl( + `/foreman_puppet/api/v2/hosts/bulk/change_puppet_proxy` + ); + return APIActions.put({ + key, + url, + handleSuccess, + handleError, + params, + }); +}; + +export default fetchSmartProxies; diff --git a/webpack/src/Extends/Hosts/BulkActions/BulkChangeProxyCommon/index.js b/webpack/src/Extends/Hosts/BulkActions/BulkChangeProxyCommon/index.js new file mode 100644 index 00000000..4d8436ab --- /dev/null +++ b/webpack/src/Extends/Hosts/BulkActions/BulkChangeProxyCommon/index.js @@ -0,0 +1,237 @@ +import React, { useState, useEffect } from 'react'; +import PropTypes from 'prop-types'; +import { FormattedMessage } from 'react-intl'; +import { useDispatch, useSelector } from 'react-redux'; +import { + Modal, + Button, + TextContent, + Text, + Select, + SelectOption, + SelectList, + MenuToggle, +} from '@patternfly/react-core'; +import { addToast } from 'foremanReact/components/ToastsList/slice'; +import { translate as __ } from 'foremanReact/common/I18n'; +import { foremanUrl } from 'foremanReact/common/helpers'; +import { APIActions } from 'foremanReact/redux/API'; +import { STATUS } from 'foremanReact/constants'; +import { + selectAPIStatus, + selectAPIResponse, +} from 'foremanReact/redux/API/APISelectors'; +import { + HOSTS_API_PATH, + API_REQUEST_KEY, +} from 'foremanReact/routes/Hosts/constants'; +import { + fetchSmartProxies, + SMART_PROXY_KEY, + bulkChangePuppetProxy, + BULK_CHANGE_PUPPET_PROXY_KEY, + BULK_CHANGE_PUPPET_CA_PROXY_KEY, +} from './actions'; + +const BulkChangeProxyCommon = ({ + isOpen, + closeModal, + selectAllHostsMode, + selectedCount, + fetchBulkParams, + selectMessage, + handleErrorMessage, + changeMessage, + allHostsMessage, + someHostsMessage, + isCAProxy, +}) => { + const dispatch = useDispatch(); + const [smartProxyId, setSmartProxyId] = useState(''); + const [smartProxySelectOpen, setSmartProxySelectOpen] = useState(false); + + const actionKey = isCAProxy + ? BULK_CHANGE_PUPPET_CA_PROXY_KEY + : BULK_CHANGE_PUPPET_PROXY_KEY; + + useEffect(() => { + dispatch(fetchSmartProxies()); + }, [dispatch]); + + const smartProxies = useSelector(state => + selectAPIResponse(state, SMART_PROXY_KEY) + ); + const smartProxyStatus = useSelector(state => + selectAPIStatus(state, SMART_PROXY_KEY) + ); + + const onToggleClick = () => { + setSmartProxySelectOpen(!smartProxySelectOpen); + }; + + const handleSmartProxySelect = (event, selection) => { + setSmartProxyId(selection); + setSmartProxySelectOpen(false); + }; + + const getSmartProxyLabel = id => id.substring(id.indexOf('-') + 1); + + const toggle = toggleRef => ( + + {smartProxyId ? getSmartProxyLabel(smartProxyId) : selectMessage} + + ); + + const handleModalClose = () => { + setSmartProxyId(''); + closeModal(); + }; + + const handleError = response => { + handleModalClose(); + dispatch( + addToast({ + type: 'danger', + key: actionKey, + message: handleErrorMessage, + }) + ); + }; + + const handleSuccess = response => { + dispatch( + addToast({ + type: 'success', + message: response.data.message, + }) + ); + dispatch( + APIActions.get({ + key: API_REQUEST_KEY, + url: foremanUrl(HOSTS_API_PATH), + }) + ); + handleModalClose(); + }; + + const handleConfirm = () => { + const requestBody = { + included: { + search: fetchBulkParams(), + }, + smart_proxy_id: smartProxyId.split('-')[0], + ca_proxy: isCAProxy, + }; + + dispatch( + bulkChangePuppetProxy(requestBody, handleSuccess, handleError, actionKey) + ); + }; + + const modalActions = [ + , + , + ]; + + return ( + + + + {selectAllHostsMode ? ( + {__('All')}, + }} + /> + ) : ( + {selectedCount}, + }} + /> + )} + + + {smartProxyStatus === STATUS.RESOLVED && ( + + )} + + ); +}; + +BulkChangeProxyCommon.propTypes = { + isOpen: PropTypes.bool, + closeModal: PropTypes.func, + fetchBulkParams: PropTypes.func.isRequired, + selectedCount: PropTypes.number.isRequired, + selectAllHostsMode: PropTypes.bool.isRequired, + selectMessage: PropTypes.string.isRequired, + handleErrorMessage: PropTypes.string.isRequired, + changeMessage: PropTypes.string.isRequired, + allHostsMessage: PropTypes.string.isRequired, + someHostsMessage: PropTypes.string.isRequired, + isCAProxy: PropTypes.bool.isRequired, +}; + +BulkChangeProxyCommon.defaultProps = { + isOpen: false, + closeModal: () => {}, +}; + +export default BulkChangeProxyCommon; diff --git a/webpack/src/Extends/Hosts/BulkActions/BulkChangePuppetCAProxy/index.js b/webpack/src/Extends/Hosts/BulkActions/BulkChangePuppetCAProxy/index.js new file mode 100644 index 00000000..65dfed94 --- /dev/null +++ b/webpack/src/Extends/Hosts/BulkActions/BulkChangePuppetCAProxy/index.js @@ -0,0 +1,41 @@ +import React, { useContext } from 'react'; +import { ForemanActionsBarContext } from 'foremanReact/components/HostDetails/ActionsBar'; +import { useForemanModal } from 'foremanReact/components/ForemanModal/ForemanModalHooks'; +import { translate as __ } from 'foremanReact/common/I18n'; + +import BulkChangeProxyCommom from '../BulkChangeProxyCommon'; + +const BulkChangePuppetCAProxyScene = () => { + const { + selectAllHostsMode, + selectedCount, + selectedResults, + fetchBulkParams, + } = useContext(ForemanActionsBarContext); + const { modalOpen, setModalClosed } = useForemanModal({ + id: 'bulk-change-puppet-ca-proxy', + }); + return ( + + ); +}; + +export default BulkChangePuppetCAProxyScene; diff --git a/webpack/src/Extends/Hosts/BulkActions/BulkChangePuppetProxy/index.js b/webpack/src/Extends/Hosts/BulkActions/BulkChangePuppetProxy/index.js new file mode 100644 index 00000000..71482a28 --- /dev/null +++ b/webpack/src/Extends/Hosts/BulkActions/BulkChangePuppetProxy/index.js @@ -0,0 +1,41 @@ +import React, { useContext } from 'react'; +import { ForemanActionsBarContext } from 'foremanReact/components/HostDetails/ActionsBar'; +import { useForemanModal } from 'foremanReact/components/ForemanModal/ForemanModalHooks'; +import { translate as __ } from 'foremanReact/common/I18n'; + +import BulkChangeProxyCommon from '../BulkChangeProxyCommon'; + +const BulkChangePuppetProxyScene = () => { + const { + selectAllHostsMode, + selectedCount, + selectedResults, + fetchBulkParams, + } = useContext(ForemanActionsBarContext); + const { modalOpen, setModalClosed } = useForemanModal({ + id: 'bulk-change-puppet-proxy', + }); + return ( + + ); +}; + +export default BulkChangePuppetProxyScene; diff --git a/webpack/src/Extends/Hosts/BulkActions/BulkRemoveProxyCommon/actions.js b/webpack/src/Extends/Hosts/BulkActions/BulkRemoveProxyCommon/actions.js new file mode 100644 index 00000000..c4ea1a3e --- /dev/null +++ b/webpack/src/Extends/Hosts/BulkActions/BulkRemoveProxyCommon/actions.js @@ -0,0 +1,25 @@ +import { APIActions } from 'foremanReact/redux/API'; +import { foremanUrl } from 'foremanReact/common/helpers'; + +export const BULK_REMOVE_PUPPET_PROXY_KEY = 'BULK_REMOVE_PUPPET_PROXY_KEY'; +export const BULK_REMOVE_PUPPET_CA_PROXY_KEY = + 'BULK_REMOVE_PUPPET_CA_PROXY_KEY'; + +export const bulkRemovePuppetProxyAction = ( + params, + handleSuccess, + handleError +) => { + const url = foremanUrl( + `/foreman_puppet/api/v2/hosts/bulk/remove_puppet_proxy` + ); + return APIActions.put({ + key: BULK_REMOVE_PUPPET_PROXY_KEY, + url, + handleSuccess, + handleError, + params, + }); +}; + +export default bulkRemovePuppetProxyAction; diff --git a/webpack/src/Extends/Hosts/BulkActions/BulkRemoveProxyCommon/index.js b/webpack/src/Extends/Hosts/BulkActions/BulkRemoveProxyCommon/index.js new file mode 100644 index 00000000..a0b43ba2 --- /dev/null +++ b/webpack/src/Extends/Hosts/BulkActions/BulkRemoveProxyCommon/index.js @@ -0,0 +1,160 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { FormattedMessage } from 'react-intl'; +import { useDispatch } from 'react-redux'; +import { Modal, Button, TextContent, Text } from '@patternfly/react-core'; +import { addToast } from 'foremanReact/components/ToastsList/slice'; +import { translate as __ } from 'foremanReact/common/I18n'; +import { foremanUrl } from 'foremanReact/common/helpers'; +import { APIActions } from 'foremanReact/redux/API'; +import { + HOSTS_API_PATH, + API_REQUEST_KEY, +} from 'foremanReact/routes/Hosts/constants'; + +import { + BULK_REMOVE_PUPPET_PROXY_KEY, + BULK_REMOVE_PUPPET_CA_PROXY_KEY, + bulkRemovePuppetProxyAction, +} from './actions'; + +const BulkRemoveProxyCommon = ({ + isCaProxy, + isOpen, + closeModal, + selectAllHostsMode, + selectedCount, + fetchBulkParams, + handleErrorMessage, + removeMessage, + allHostsMessage, + someHostsMessage, +}) => { + const actionKey = isCaProxy + ? BULK_REMOVE_PUPPET_CA_PROXY_KEY + : BULK_REMOVE_PUPPET_PROXY_KEY; + + const handleModalClose = () => { + closeModal(); + }; + + const dispatch = useDispatch(); + + const handleError = response => { + handleModalClose(); + dispatch( + addToast({ + type: 'danger', + key: actionKey, + message: handleErrorMessage, + }) + ); + }; + + const handleSuccess = response => { + dispatch( + addToast({ + type: 'success', + message: response.data.message, + }) + ); + dispatch( + APIActions.get({ + key: API_REQUEST_KEY, + url: foremanUrl(HOSTS_API_PATH), + }) + ); + handleModalClose(); + }; + + const handleConfirm = () => { + const requestBody = { + included: { + search: fetchBulkParams(), + ca_proxy: isCaProxy, + }, + }; + + dispatch( + bulkRemovePuppetProxyAction(requestBody, handleSuccess, handleError) + ); + }; + + const modalActions = [ + , + , + ]; + + return ( + + + + {selectAllHostsMode ? ( + {__('All')}, + }} + /> + ) : ( + {selectedCount}, + }} + /> + )} + + + + ); +}; + +BulkRemoveProxyCommon.propTypes = { + isCaProxy: PropTypes.bool.isRequired, + isOpen: PropTypes.bool, + closeModal: PropTypes.func, + fetchBulkParams: PropTypes.func.isRequired, + selectedCount: PropTypes.number.isRequired, + selectAllHostsMode: PropTypes.bool.isRequired, + handleErrorMessage: PropTypes.string.isRequired, + removeMessage: PropTypes.string, + allHostsMessage: PropTypes.string.isRequired, + someHostsMessage: PropTypes.string.isRequired, +}; + +BulkRemoveProxyCommon.defaultProps = { + isOpen: false, + closeModal: () => {}, + removeMessage: 'Remove Puppet (CA) Proxy', +}; + +export default BulkRemoveProxyCommon; diff --git a/webpack/src/Extends/Hosts/BulkActions/BulkRemovePuppetCAProxy/index.js b/webpack/src/Extends/Hosts/BulkActions/BulkRemovePuppetCAProxy/index.js new file mode 100644 index 00000000..3fa1b442 --- /dev/null +++ b/webpack/src/Extends/Hosts/BulkActions/BulkRemovePuppetCAProxy/index.js @@ -0,0 +1,39 @@ +import React, { useContext } from 'react'; +import { ForemanActionsBarContext } from 'foremanReact/components/HostDetails/ActionsBar'; +import { useForemanModal } from 'foremanReact/components/ForemanModal/ForemanModalHooks'; +import { translate as __ } from 'foremanReact/common/I18n'; +import BulkRemoveProxyCommon from '../BulkRemoveProxyCommon'; + +const BulkRemovePuppetCAProxyScene = () => { + const { + selectAllHostsMode, + selectedCount, + selectedResults, + fetchBulkParams, + } = useContext(ForemanActionsBarContext); + const { modalOpen, setModalClosed } = useForemanModal({ + id: 'bulk-remove-puppet-ca-proxy', + }); + return ( + + ); +}; + +export default BulkRemovePuppetCAProxyScene; diff --git a/webpack/src/Extends/Hosts/BulkActions/BulkRemovePuppetProxy/index.js b/webpack/src/Extends/Hosts/BulkActions/BulkRemovePuppetProxy/index.js new file mode 100644 index 00000000..3167376b --- /dev/null +++ b/webpack/src/Extends/Hosts/BulkActions/BulkRemovePuppetProxy/index.js @@ -0,0 +1,39 @@ +import React, { useContext } from 'react'; +import { ForemanActionsBarContext } from 'foremanReact/components/HostDetails/ActionsBar'; +import { useForemanModal } from 'foremanReact/components/ForemanModal/ForemanModalHooks'; +import { translate as __ } from 'foremanReact/common/I18n'; +import BulkRemoveProxyCommon from '../BulkRemoveProxyCommon'; + +const BulkRemovePuppetProxyScene = () => { + const { + selectAllHostsMode, + selectedCount, + selectedResults, + fetchBulkParams, + } = useContext(ForemanActionsBarContext); + const { modalOpen, setModalClosed } = useForemanModal({ + id: 'bulk-remove-puppet-proxy', + }); + return ( + + ); +}; + +export default BulkRemovePuppetProxyScene; diff --git a/webpack/src/foreman_puppet_host_form.test.js b/webpack/src/foreman_puppet_host_form.test.js index f7415fe4..240e0bf3 100644 --- a/webpack/src/foreman_puppet_host_form.test.js +++ b/webpack/src/foreman_puppet_host_form.test.js @@ -37,7 +37,7 @@ describe('checkForUnavailablePuppetclasses', () => { ); checkForUnavailablePuppetclasses(); - expect($('#puppetclasses_unavailable_warning').length).toBe(1); + expect($('#puppetclasses_unavailable_warning')).toHaveLength(1); }); it('does not add a warning if no unavailable classes are found', () => { @@ -48,8 +48,8 @@ describe('checkForUnavailablePuppetclasses', () => { expect( $('#hostgroup .help-block') .first() - .children().length - ).toBe(0); + .children() + ).toHaveLength(0); }); it('adds a warning sign to the tab if unavailable classes are found', () => { @@ -58,7 +58,7 @@ describe('checkForUnavailablePuppetclasses', () => { ); checkForUnavailablePuppetclasses(); setTimeout(() => { - expect($('a .pficon').length).toBe(1); + expect($('a .pficon')).toHaveLength(1); }, 100); }); });