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 (
+
+ );
+};
+
+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);
});
});