foreman_remote_execution 12.0.7 → 13.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/js_ci.yml +1 -1
- data/.github/workflows/ruby_ci.yml +16 -81
- data/.packit.yaml +8 -3
- data/app/assets/javascripts/foreman_remote_execution/locale/de/foreman_remote_execution.js +23 -14
- data/app/assets/javascripts/foreman_remote_execution/locale/en/foreman_remote_execution.js +22 -4
- data/app/assets/javascripts/foreman_remote_execution/locale/en_GB/foreman_remote_execution.js +23 -14
- data/app/assets/javascripts/foreman_remote_execution/locale/es/foreman_remote_execution.js +23 -14
- data/app/assets/javascripts/foreman_remote_execution/locale/fr/foreman_remote_execution.js +23 -14
- data/app/assets/javascripts/foreman_remote_execution/locale/ja/foreman_remote_execution.js +23 -14
- data/app/assets/javascripts/foreman_remote_execution/locale/ka/foreman_remote_execution.js +23 -14
- data/app/assets/javascripts/foreman_remote_execution/locale/ko/foreman_remote_execution.js +23 -14
- data/app/assets/javascripts/foreman_remote_execution/locale/pt_BR/foreman_remote_execution.js +23 -14
- data/app/assets/javascripts/foreman_remote_execution/locale/ru/foreman_remote_execution.js +23 -14
- data/app/assets/javascripts/foreman_remote_execution/locale/zh_CN/foreman_remote_execution.js +23 -14
- data/app/assets/javascripts/foreman_remote_execution/locale/zh_TW/foreman_remote_execution.js +23 -14
- data/app/controllers/ui_job_wizard_controller.rb +1 -1
- data/app/helpers/job_invocations_helper.rb +1 -1
- data/app/helpers/remote_execution_helper.rb +2 -2
- data/app/lib/actions/remote_execution/proxy_action.rb +1 -1
- data/app/lib/actions/remote_execution/run_host_job.rb +9 -3
- data/app/lib/actions/remote_execution/run_hosts_job.rb +0 -1
- data/app/models/host_status/execution_status.rb +2 -2
- data/app/views/job_invocations/_preview_hosts_list.html.erb +1 -1
- data/app/views/job_invocations/show.html.erb +12 -5
- data/app/views/job_invocations/show.js.erb +8 -1
- data/app/views/job_invocations/welcome.html.erb +1 -1
- data/app/views/template_invocations/_refresh.js.erb +10 -4
- data/app/views/template_invocations/show.html.erb +2 -2
- data/app/views/templates/script/convert2rhel_analyze.erb +12 -1
- data/lib/foreman_remote_execution/engine.rb +1 -1
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/locale/de/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/de/foreman_remote_execution.po +24 -6
- data/locale/en/foreman_remote_execution.po +24 -6
- data/locale/en_GB/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/en_GB/foreman_remote_execution.po +24 -6
- data/locale/es/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/es/foreman_remote_execution.po +24 -6
- data/locale/foreman_remote_execution.pot +170 -142
- data/locale/fr/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/fr/foreman_remote_execution.po +24 -6
- data/locale/ja/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/ja/foreman_remote_execution.po +24 -6
- data/locale/ka/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/ka/foreman_remote_execution.po +24 -6
- data/locale/ko/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/ko/foreman_remote_execution.po +24 -6
- data/locale/pt_BR/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/pt_BR/foreman_remote_execution.po +24 -6
- data/locale/ru/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/ru/foreman_remote_execution.po +24 -6
- data/locale/zh_CN/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/zh_CN/foreman_remote_execution.po +24 -6
- data/locale/zh_TW/LC_MESSAGES/foreman_remote_execution.mo +0 -0
- data/locale/zh_TW/foreman_remote_execution.po +24 -6
- data/package.json +1 -5
- data/test/functional/api/v2/job_invocations_controller_test.rb +7 -7
- data/test/functional/api/v2/template_invocations_controller_test.rb +3 -3
- data/test/helpers/remote_execution_helper_test.rb +8 -7
- data/test/unit/actions/run_host_job_test.rb +1 -1
- data/test/unit/actions/run_hosts_job_test.rb +11 -11
- data/test/unit/concerns/foreman_tasks_cleaner_extensions_test.rb +5 -5
- data/test/unit/concerns/host_extensions_test.rb +34 -34
- data/test/unit/concerns/nic_extensions_test.rb +1 -1
- data/test/unit/execution_task_status_mapper_test.rb +10 -10
- data/test/unit/input_template_renderer_test.rb +53 -49
- data/test/unit/job_invocation_composer_test.rb +78 -78
- data/test/unit/job_invocation_test.rb +25 -25
- data/test/unit/job_template_effective_user_test.rb +3 -3
- data/test/unit/job_template_test.rb +28 -28
- data/test/unit/remote_execution_feature_test.rb +14 -14
- data/test/unit/remote_execution_provider_test.rb +39 -39
- data/test/unit/renderer_scope_input_test.rb +6 -6
- data/test/unit/targeting_test.rb +32 -32
- data/webpack/JobWizard/JobWizardConstants.js +4 -0
- data/webpack/JobWizard/JobWizardSelectors.js +31 -3
- data/webpack/JobWizard/PermissionDenied.js +64 -0
- data/webpack/JobWizard/__tests__/fixtures.js +3 -3
- data/webpack/JobWizard/autofill.js +8 -4
- data/webpack/JobWizard/index.js +40 -1
- data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.js +26 -5
- data/webpack/JobWizard/steps/HostsAndInputs/HostPreviewModal.js +3 -3
- data/webpack/JobWizard/steps/HostsAndInputs/SelectGQL.js +1 -0
- data/webpack/JobWizard/steps/HostsAndInputs/SelectedChips.js +13 -6
- data/webpack/JobWizard/steps/HostsAndInputs/hosts.gql +1 -0
- data/webpack/JobWizard/steps/HostsAndInputs/index.js +21 -1
- data/webpack/JobWizard/steps/ReviewDetails/index.js +3 -2
- data/webpack/JobWizard/steps/form/SearchSelect.js +3 -1
- data/webpack/JobWizard/steps/form/__tests__/SelectSearch.test.js +2 -0
- metadata +7 -6
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import URI from 'urijs';
|
|
2
|
+
import { get } from 'lodash';
|
|
2
3
|
import {
|
|
3
4
|
selectAPIResponse,
|
|
4
5
|
selectAPIStatus,
|
|
@@ -42,6 +43,18 @@ export const selectJobCategoriesStatus = state =>
|
|
|
42
43
|
export const selectCategoryError = state =>
|
|
43
44
|
selectAPIErrorMessage(state, JOB_CATEGORIES);
|
|
44
45
|
|
|
46
|
+
export const selectJobCategoriesMissingPermissions = state => {
|
|
47
|
+
const jobCategoriesResponse = selectJobCategoriesResponse(state);
|
|
48
|
+
return (
|
|
49
|
+
get(jobCategoriesResponse, [
|
|
50
|
+
'response',
|
|
51
|
+
'data',
|
|
52
|
+
'error',
|
|
53
|
+
'missing_permissions',
|
|
54
|
+
]) || []
|
|
55
|
+
);
|
|
56
|
+
};
|
|
57
|
+
|
|
45
58
|
export const selectAllTemplatesError = state =>
|
|
46
59
|
selectAPIErrorMessage(state, JOB_TEMPLATES);
|
|
47
60
|
|
|
@@ -60,11 +73,26 @@ export const selectAdvancedTemplateInputs = state =>
|
|
|
60
73
|
export const selectTemplateInputs = state =>
|
|
61
74
|
selectAPIResponse(state, JOB_TEMPLATE).template_inputs || [];
|
|
62
75
|
|
|
76
|
+
export const selectHostsResponse = state => selectAPIResponse(state, HOSTS_API);
|
|
77
|
+
|
|
63
78
|
export const selectHostCount = state =>
|
|
64
|
-
|
|
79
|
+
selectHostsResponse(state).subtotal || 0;
|
|
80
|
+
|
|
81
|
+
export const selectHosts = state => {
|
|
82
|
+
const hosts = selectHostsResponse(state).results || [];
|
|
83
|
+
return hosts.map(host => ({
|
|
84
|
+
name: host.name,
|
|
85
|
+
display_name: host.display_name,
|
|
86
|
+
}));
|
|
87
|
+
};
|
|
65
88
|
|
|
66
|
-
export const
|
|
67
|
-
|
|
89
|
+
export const selectHostsMissingPermissions = state => {
|
|
90
|
+
const hostsResponse = selectHostsResponse(state);
|
|
91
|
+
return (
|
|
92
|
+
get(hostsResponse, ['response', 'data', 'error', 'missing_permissions']) ||
|
|
93
|
+
[]
|
|
94
|
+
);
|
|
95
|
+
};
|
|
68
96
|
|
|
69
97
|
export const selectIsLoadingHosts = state =>
|
|
70
98
|
!selectAPIStatus(state, HOSTS_API) ||
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { translate as __ } from 'foremanReact/common/I18n';
|
|
4
|
+
import { Icon } from 'patternfly-react';
|
|
5
|
+
import {
|
|
6
|
+
Title,
|
|
7
|
+
Button,
|
|
8
|
+
EmptyState,
|
|
9
|
+
EmptyStateVariant,
|
|
10
|
+
EmptyStateBody,
|
|
11
|
+
} from '@patternfly/react-core';
|
|
12
|
+
|
|
13
|
+
const PermissionDenied = ({ missingPermissions, setProceedAnyway }) => {
|
|
14
|
+
const description = (
|
|
15
|
+
<span>
|
|
16
|
+
{__('You are not authorized to perform this action.')}
|
|
17
|
+
<br />
|
|
18
|
+
{__(
|
|
19
|
+
'Please request the required permissions listed below from a Foreman administrator:'
|
|
20
|
+
)}
|
|
21
|
+
<br />
|
|
22
|
+
<ul className="list-unstyled">
|
|
23
|
+
{missingPermissions.map(permission => (
|
|
24
|
+
<li key={permission}>
|
|
25
|
+
<strong>{permission}</strong>
|
|
26
|
+
</li>
|
|
27
|
+
))}
|
|
28
|
+
</ul>
|
|
29
|
+
</span>
|
|
30
|
+
);
|
|
31
|
+
const handleProceedAnyway = () => {
|
|
32
|
+
setProceedAnyway(true);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<EmptyState variant={EmptyStateVariant.xl}>
|
|
37
|
+
<span className="empty-state-icon">
|
|
38
|
+
<Icon name="lock" type="fa" size="2x" />
|
|
39
|
+
</span>
|
|
40
|
+
<Title ouiaId="empty-state-header" headingLevel="h5" size="4xl">
|
|
41
|
+
{__('Permission Denied')}
|
|
42
|
+
</Title>
|
|
43
|
+
<EmptyStateBody>{description}</EmptyStateBody>
|
|
44
|
+
<Button
|
|
45
|
+
ouiaId="job-invocation-proceed-anyway-button"
|
|
46
|
+
variant="primary"
|
|
47
|
+
onClick={handleProceedAnyway}
|
|
48
|
+
>
|
|
49
|
+
{__('Proceed Anyway')}
|
|
50
|
+
</Button>
|
|
51
|
+
</EmptyState>
|
|
52
|
+
);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
PermissionDenied.propTypes = {
|
|
56
|
+
missingPermissions: PropTypes.array,
|
|
57
|
+
setProceedAnyway: PropTypes.func.isRequired,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
PermissionDenied.defaultProps = {
|
|
61
|
+
missingPermissions: ['unknown'],
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export default PermissionDenied;
|
|
@@ -227,9 +227,9 @@ export const gqlMock = [
|
|
|
227
227
|
hosts: {
|
|
228
228
|
totalCount: 3,
|
|
229
229
|
nodes: [
|
|
230
|
-
{ id: 'MDE6SG9zdC0x', name: 'host1' },
|
|
231
|
-
{ id: 'MDE6SG9zdC0y', name: 'host2' },
|
|
232
|
-
{ id: 'MDE6SG9zdC0z', name: 'host3' },
|
|
230
|
+
{ id: 'MDE6SG9zdC0x', name: 'host1', displayName: 'host1' },
|
|
231
|
+
{ id: 'MDE6SG9zdC0y', name: 'host2', displayName: 'host2' },
|
|
232
|
+
{ id: 'MDE6SG9zdC0z', name: 'host3', displayName: 'host3' },
|
|
233
233
|
],
|
|
234
234
|
},
|
|
235
235
|
},
|
|
@@ -39,10 +39,14 @@ export const useAutoFill = ({
|
|
|
39
39
|
handleSuccess: ({ data }) => {
|
|
40
40
|
setSelectedTargets(currentTargets => ({
|
|
41
41
|
...currentTargets,
|
|
42
|
-
hosts: (data.results || []).map(
|
|
43
|
-
|
|
44
|
-
name,
|
|
45
|
-
|
|
42
|
+
hosts: (data.results || []).map(
|
|
43
|
+
// eslint-disable-next-line camelcase
|
|
44
|
+
({ id, name, display_name }) => ({
|
|
45
|
+
id,
|
|
46
|
+
// eslint-disable-next-line camelcase
|
|
47
|
+
name: display_name || name,
|
|
48
|
+
})
|
|
49
|
+
),
|
|
46
50
|
}));
|
|
47
51
|
},
|
|
48
52
|
})
|
data/webpack/JobWizard/index.js
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { isEmpty } from 'lodash';
|
|
2
3
|
import PropTypes from 'prop-types';
|
|
3
4
|
import { Button } from '@patternfly/react-core';
|
|
4
5
|
import { translate as __ } from 'foremanReact/common/I18n';
|
|
6
|
+
import { STATUS } from 'foremanReact/constants';
|
|
5
7
|
import PageLayout from 'foremanReact/routes/common/PageLayout/PageLayout';
|
|
8
|
+
import { useAPI } from 'foremanReact/common/hooks/API/APIHooks';
|
|
9
|
+
import PermissionDenied from './PermissionDenied';
|
|
6
10
|
import { JobWizard } from './JobWizard';
|
|
11
|
+
import {
|
|
12
|
+
CURRENT_PERMISSIONS,
|
|
13
|
+
currentPermissionsUrl,
|
|
14
|
+
} from './JobWizardConstants';
|
|
7
15
|
|
|
8
16
|
const JobWizardPage = ({ location: { search } }) => {
|
|
9
17
|
const title = __('Run job');
|
|
@@ -13,6 +21,37 @@ const JobWizardPage = ({ location: { search } }) => {
|
|
|
13
21
|
{ caption: title },
|
|
14
22
|
],
|
|
15
23
|
};
|
|
24
|
+
const [proceedAnyway, setProceedAnyway] = useState(false);
|
|
25
|
+
|
|
26
|
+
const { response, status } = useAPI(
|
|
27
|
+
'get',
|
|
28
|
+
currentPermissionsUrl,
|
|
29
|
+
CURRENT_PERMISSIONS
|
|
30
|
+
);
|
|
31
|
+
const desiredPermissions = [
|
|
32
|
+
'view_hosts',
|
|
33
|
+
'view_smart_proxies',
|
|
34
|
+
'view_job_templates',
|
|
35
|
+
'create_job_invocations',
|
|
36
|
+
'create_template_invocations',
|
|
37
|
+
];
|
|
38
|
+
const missingPermissions =
|
|
39
|
+
status === STATUS.RESOLVED
|
|
40
|
+
? desiredPermissions.filter(
|
|
41
|
+
permission =>
|
|
42
|
+
!response.results.map(result => result.name).includes(permission)
|
|
43
|
+
)
|
|
44
|
+
: [];
|
|
45
|
+
|
|
46
|
+
if (!isEmpty(missingPermissions) && !proceedAnyway) {
|
|
47
|
+
return (
|
|
48
|
+
<PermissionDenied
|
|
49
|
+
missingPermissions={missingPermissions}
|
|
50
|
+
setProceedAnyway={setProceedAnyway}
|
|
51
|
+
/>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
16
55
|
return (
|
|
17
56
|
<PageLayout
|
|
18
57
|
header={title}
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import { isEmpty } from 'lodash';
|
|
2
3
|
import PropTypes from 'prop-types';
|
|
3
4
|
import { useSelector } from 'react-redux';
|
|
4
5
|
import { Text, TextVariants, Form, Alert } from '@patternfly/react-core';
|
|
5
6
|
import { translate as __ } from 'foremanReact/common/I18n';
|
|
7
|
+
import {
|
|
8
|
+
selectJobCategoriesMissingPermissions,
|
|
9
|
+
selectIsLoading,
|
|
10
|
+
} from '../../JobWizardSelectors';
|
|
6
11
|
import { SelectField } from '../form/SelectField';
|
|
7
12
|
import { GroupedSelectField } from '../form/GroupedSelectField';
|
|
8
13
|
import { WizardTitle } from '../form/WizardTitle';
|
|
9
14
|
import { WIZARD_TITLES, JOB_TEMPLATES } from '../../JobWizardConstants';
|
|
10
|
-
import { selectIsLoading } from '../../JobWizardSelectors';
|
|
11
15
|
|
|
12
16
|
export const CategoryAndTemplate = ({
|
|
13
17
|
jobCategories,
|
|
@@ -57,7 +61,13 @@ export const CategoryAndTemplate = ({
|
|
|
57
61
|
};
|
|
58
62
|
|
|
59
63
|
const { categoryError, allTemplatesError, templateError } = errors;
|
|
60
|
-
const
|
|
64
|
+
const missingPermissions = useSelector(selectJobCategoriesMissingPermissions);
|
|
65
|
+
const isError = !!(
|
|
66
|
+
(categoryError && isEmpty(missingPermissions)) ||
|
|
67
|
+
allTemplatesError ||
|
|
68
|
+
templateError
|
|
69
|
+
);
|
|
70
|
+
|
|
61
71
|
return (
|
|
62
72
|
<>
|
|
63
73
|
<WizardTitle title={WIZARD_TITLES.categoryAndTemplate} />
|
|
@@ -69,7 +79,7 @@ export const CategoryAndTemplate = ({
|
|
|
69
79
|
options={jobCategories}
|
|
70
80
|
setValue={onSelectCategory}
|
|
71
81
|
value={selectedCategory}
|
|
72
|
-
placeholderText={categoryError ? __('
|
|
82
|
+
placeholderText={categoryError ? __('Not available') : ''}
|
|
73
83
|
isDisabled={!!categoryError}
|
|
74
84
|
isRequired
|
|
75
85
|
/>
|
|
@@ -82,11 +92,22 @@ export const CategoryAndTemplate = ({
|
|
|
82
92
|
isDisabled={
|
|
83
93
|
!!(categoryError || allTemplatesError || isTemplatesLoading)
|
|
84
94
|
}
|
|
85
|
-
placeholderText={allTemplatesError ? __('
|
|
95
|
+
placeholderText={allTemplatesError ? __('Not available') : ''}
|
|
86
96
|
/>
|
|
97
|
+
{!isEmpty(missingPermissions) && (
|
|
98
|
+
<Alert variant="warning" title={__('Access denied')}>
|
|
99
|
+
<span>
|
|
100
|
+
{__(
|
|
101
|
+
`Missing the required permissions: ${missingPermissions.join(
|
|
102
|
+
', '
|
|
103
|
+
)}`
|
|
104
|
+
)}
|
|
105
|
+
</span>
|
|
106
|
+
</Alert>
|
|
107
|
+
)}
|
|
87
108
|
{isError && (
|
|
88
109
|
<Alert variant="danger" title={__('Errors:')}>
|
|
89
|
-
{categoryError && (
|
|
110
|
+
{categoryError && isEmpty(missingPermissions) && (
|
|
90
111
|
<span>
|
|
91
112
|
{__('Categories list failed with:')} {categoryError}
|
|
92
113
|
</span>
|
|
@@ -22,16 +22,16 @@ export const HostPreviewModal = ({ isOpen, setIsOpen, searchQuery }) => {
|
|
|
22
22
|
>
|
|
23
23
|
<List isPlain>
|
|
24
24
|
{hosts.map(host => (
|
|
25
|
-
<ListItem key={host}>
|
|
25
|
+
<ListItem key={host.name}>
|
|
26
26
|
<Button
|
|
27
27
|
component="a"
|
|
28
|
-
href={foremanUrl(`/hosts/${host}`)}
|
|
28
|
+
href={foremanUrl(`/hosts/${host.name}`)}
|
|
29
29
|
variant="link"
|
|
30
30
|
target="_blank"
|
|
31
31
|
rel="noreferrer"
|
|
32
32
|
isInline
|
|
33
33
|
>
|
|
34
|
-
{host}
|
|
34
|
+
{host.display_name}
|
|
35
35
|
</Button>
|
|
36
36
|
</ListItem>
|
|
37
37
|
))}
|
|
@@ -4,7 +4,7 @@ import { Chip, ChipGroup, Button } from '@patternfly/react-core';
|
|
|
4
4
|
import { sprintf, translate as __ } from 'foremanReact/common/I18n';
|
|
5
5
|
import { hostMethods } from '../../JobWizardConstants';
|
|
6
6
|
|
|
7
|
-
const SelectedChip = ({ selected, setSelected, categoryName }) => {
|
|
7
|
+
const SelectedChip = ({ selected, setSelected, categoryName, setLabel }) => {
|
|
8
8
|
const deleteItem = itemToRemove => {
|
|
9
9
|
setSelected(oldSelected =>
|
|
10
10
|
oldSelected.filter(({ id }) => id !== itemToRemove)
|
|
@@ -24,14 +24,14 @@ const SelectedChip = ({ selected, setSelected, categoryName }) => {
|
|
|
24
24
|
setSelected(() => []);
|
|
25
25
|
}}
|
|
26
26
|
>
|
|
27
|
-
{selected.map((
|
|
27
|
+
{selected.map((result, index) => (
|
|
28
28
|
<Chip
|
|
29
29
|
key={index}
|
|
30
|
-
id={`${categoryName}-${id}`}
|
|
31
|
-
onClick={() => deleteItem(id)}
|
|
32
|
-
closeBtnAriaLabel={`Remove ${name}`}
|
|
30
|
+
id={`${categoryName}-${result.id}`}
|
|
31
|
+
onClick={() => deleteItem(result.id)}
|
|
32
|
+
closeBtnAriaLabel={`Remove ${result.name}`}
|
|
33
33
|
>
|
|
34
|
-
{
|
|
34
|
+
{setLabel(result)}
|
|
35
35
|
</Chip>
|
|
36
36
|
))}
|
|
37
37
|
</ChipGroup>
|
|
@@ -49,6 +49,7 @@ export const SelectedChips = ({
|
|
|
49
49
|
setSelectedHostGroups,
|
|
50
50
|
hostsSearchQuery,
|
|
51
51
|
clearSearch,
|
|
52
|
+
setLabel,
|
|
52
53
|
}) => {
|
|
53
54
|
const clearAll = () => {
|
|
54
55
|
setSelectedHosts(() => []);
|
|
@@ -67,16 +68,19 @@ export const SelectedChips = ({
|
|
|
67
68
|
selected={selectedHosts}
|
|
68
69
|
categoryName={hostMethods.hosts}
|
|
69
70
|
setSelected={setSelectedHosts}
|
|
71
|
+
setLabel={setLabel}
|
|
70
72
|
/>
|
|
71
73
|
<SelectedChip
|
|
72
74
|
selected={selectedHostCollections}
|
|
73
75
|
categoryName={hostMethods.hostCollections}
|
|
74
76
|
setSelected={setSelectedHostCollections}
|
|
77
|
+
setLabel={setLabel}
|
|
75
78
|
/>
|
|
76
79
|
<SelectedChip
|
|
77
80
|
selected={selectedHostGroups}
|
|
78
81
|
categoryName={hostMethods.hostGroups}
|
|
79
82
|
setSelected={setSelectedHostGroups}
|
|
83
|
+
setLabel={setLabel}
|
|
80
84
|
/>
|
|
81
85
|
<SelectedChip
|
|
82
86
|
selected={
|
|
@@ -86,6 +90,7 @@ export const SelectedChips = ({
|
|
|
86
90
|
}
|
|
87
91
|
categoryName={hostMethods.searchQuery}
|
|
88
92
|
setSelected={clearSearch}
|
|
93
|
+
setLabel={setLabel}
|
|
89
94
|
/>
|
|
90
95
|
{showClear && (
|
|
91
96
|
<Button variant="link" className="clear-chips" onClick={clearAll}>
|
|
@@ -105,10 +110,12 @@ SelectedChips.propTypes = {
|
|
|
105
110
|
setSelectedHostGroups: PropTypes.func.isRequired,
|
|
106
111
|
hostsSearchQuery: PropTypes.string.isRequired,
|
|
107
112
|
clearSearch: PropTypes.func.isRequired,
|
|
113
|
+
setLabel: PropTypes.func.isRequired,
|
|
108
114
|
};
|
|
109
115
|
|
|
110
116
|
SelectedChip.propTypes = {
|
|
111
117
|
categoryName: PropTypes.string.isRequired,
|
|
112
118
|
selected: PropTypes.array.isRequired,
|
|
113
119
|
setSelected: PropTypes.func.isRequired,
|
|
120
|
+
setLabel: PropTypes.func.isRequired,
|
|
114
121
|
};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { isEmpty, debounce } from 'lodash';
|
|
2
3
|
import {
|
|
4
|
+
Alert,
|
|
3
5
|
Button,
|
|
4
6
|
Form,
|
|
5
7
|
FormGroup,
|
|
@@ -10,13 +12,13 @@ import {
|
|
|
10
12
|
import PropTypes from 'prop-types';
|
|
11
13
|
import { useSelector, useDispatch } from 'react-redux';
|
|
12
14
|
import { FilterIcon } from '@patternfly/react-icons';
|
|
13
|
-
import { debounce } from 'lodash';
|
|
14
15
|
import { get } from 'foremanReact/redux/API';
|
|
15
16
|
import { translate as __ } from 'foremanReact/common/I18n';
|
|
16
17
|
import {
|
|
17
18
|
selectTemplateInputs,
|
|
18
19
|
selectWithKatello,
|
|
19
20
|
selectHostCount,
|
|
21
|
+
selectHostsMissingPermissions,
|
|
20
22
|
selectIsLoadingHosts,
|
|
21
23
|
} from '../../JobWizardSelectors';
|
|
22
24
|
import { SelectField } from '../form/SelectField';
|
|
@@ -98,9 +100,11 @@ const HostsAndInputs = ({
|
|
|
98
100
|
]);
|
|
99
101
|
const withKatello = useSelector(selectWithKatello);
|
|
100
102
|
const hostCount = useSelector(selectHostCount);
|
|
103
|
+
const missingPermissions = useSelector(selectHostsMissingPermissions);
|
|
101
104
|
const dispatch = useDispatch();
|
|
102
105
|
|
|
103
106
|
const selectedHosts = selected.hosts;
|
|
107
|
+
const setLabel = result => result.displayName || result.name;
|
|
104
108
|
const setSelectedHosts = newSelected =>
|
|
105
109
|
setSelected(prevSelected => ({
|
|
106
110
|
...prevSelected,
|
|
@@ -126,6 +130,7 @@ const HostsAndInputs = ({
|
|
|
126
130
|
const [errorText, setErrorText] = useState(
|
|
127
131
|
__('Please select at least one host')
|
|
128
132
|
);
|
|
133
|
+
|
|
129
134
|
return (
|
|
130
135
|
<div className="target-hosts-and-inputs">
|
|
131
136
|
<WizardTitle title={WIZARD_TITLES.hostsAndInputs} />
|
|
@@ -187,6 +192,7 @@ const HostsAndInputs = ({
|
|
|
187
192
|
apiKey={HOSTS}
|
|
188
193
|
name="hosts"
|
|
189
194
|
placeholderText={__('Filter by hosts')}
|
|
195
|
+
setLabel={setLabel}
|
|
190
196
|
/>
|
|
191
197
|
)}
|
|
192
198
|
{hostMethod === hostMethods.hostCollections && (
|
|
@@ -197,6 +203,7 @@ const HostsAndInputs = ({
|
|
|
197
203
|
name="host collections"
|
|
198
204
|
url="/katello/api/host_collections?per_page=100"
|
|
199
205
|
placeholderText={__('Filter by host collections')}
|
|
206
|
+
setLabel={setLabel}
|
|
200
207
|
/>
|
|
201
208
|
)}
|
|
202
209
|
{hostMethod === hostMethods.hostGroups && (
|
|
@@ -206,6 +213,7 @@ const HostsAndInputs = ({
|
|
|
206
213
|
apiKey={HOST_GROUPS}
|
|
207
214
|
name="host groups"
|
|
208
215
|
placeholderText={__('Filter by host groups')}
|
|
216
|
+
setLabel={setLabel}
|
|
209
217
|
/>
|
|
210
218
|
)}
|
|
211
219
|
</InputGroup>
|
|
@@ -219,6 +227,7 @@ const HostsAndInputs = ({
|
|
|
219
227
|
setSelectedHostGroups={setSelectedHostGroups}
|
|
220
228
|
hostsSearchQuery={hostsSearchQuery}
|
|
221
229
|
clearSearch={clearSearch}
|
|
230
|
+
setLabel={setLabel}
|
|
222
231
|
/>
|
|
223
232
|
<Text>
|
|
224
233
|
{__('Apply to')}{' '}
|
|
@@ -237,6 +246,17 @@ const HostsAndInputs = ({
|
|
|
237
246
|
value={templateValues}
|
|
238
247
|
setValue={setTemplateValues}
|
|
239
248
|
/>
|
|
249
|
+
{!isEmpty(missingPermissions) && (
|
|
250
|
+
<Alert variant="warning" title={__('Access denied')}>
|
|
251
|
+
<span>
|
|
252
|
+
{__(
|
|
253
|
+
`Missing the required permissions: ${missingPermissions.join(
|
|
254
|
+
', '
|
|
255
|
+
)}`
|
|
256
|
+
)}
|
|
257
|
+
</span>
|
|
258
|
+
</Alert>
|
|
259
|
+
)}
|
|
240
260
|
</Form>
|
|
241
261
|
</div>
|
|
242
262
|
);
|
|
@@ -79,12 +79,13 @@ const ReviewDetails = ({
|
|
|
79
79
|
|
|
80
80
|
const hostsCount = useSelector(selectHostCount);
|
|
81
81
|
const [hostPreviewOpen, setHostPreviewOpen] = useState(false);
|
|
82
|
+
const NUM_CHIPS = 3;
|
|
82
83
|
const stringHosts = () => {
|
|
83
84
|
if (hosts.length === 0) {
|
|
84
85
|
return __('No Target Hosts');
|
|
85
86
|
}
|
|
86
|
-
if (hosts.length
|
|
87
|
-
return hosts.join(', ');
|
|
87
|
+
if (hosts.length < NUM_CHIPS) {
|
|
88
|
+
return hosts.map(host => host.display_name).join(', ');
|
|
88
89
|
}
|
|
89
90
|
return (
|
|
90
91
|
<div>
|
|
@@ -16,6 +16,7 @@ export const SearchSelect = ({
|
|
|
16
16
|
apiKey,
|
|
17
17
|
url,
|
|
18
18
|
variant,
|
|
19
|
+
setLabel,
|
|
19
20
|
}) => {
|
|
20
21
|
const [onSearch, response, isLoading] = useNameSearch(apiKey, url);
|
|
21
22
|
const [isOpen, setIsOpen] = useState(false);
|
|
@@ -48,7 +49,7 @@ export const SearchSelect = ({
|
|
|
48
49
|
...selectOptions,
|
|
49
50
|
...Immutable.asMutable(response?.results || [])?.map((result, index) => (
|
|
50
51
|
<SelectOption key={index + 1} value={result.id}>
|
|
51
|
-
{result
|
|
52
|
+
{setLabel(result)}
|
|
52
53
|
</SelectOption>
|
|
53
54
|
)),
|
|
54
55
|
];
|
|
@@ -104,6 +105,7 @@ SearchSelect.propTypes = {
|
|
|
104
105
|
name: PropTypes.string,
|
|
105
106
|
selected: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
|
|
106
107
|
setSelected: PropTypes.func.isRequired,
|
|
108
|
+
setLabel: PropTypes.func.isRequired,
|
|
107
109
|
placeholderText: PropTypes.string,
|
|
108
110
|
apiKey: PropTypes.string.isRequired,
|
|
109
111
|
url: PropTypes.string,
|
|
@@ -7,6 +7,7 @@ const apiKey = 'HOSTS_KEY';
|
|
|
7
7
|
describe('SearchSelect', () => {
|
|
8
8
|
it('too many', () => {
|
|
9
9
|
const onSearch = jest.fn();
|
|
10
|
+
const setLabel = jest.fn();
|
|
10
11
|
render(
|
|
11
12
|
<SearchSelect
|
|
12
13
|
selected={['hosts1,host2']}
|
|
@@ -19,6 +20,7 @@ describe('SearchSelect', () => {
|
|
|
19
20
|
{ results: ['host1', 'host2', 'host3'], subtotal: 101 },
|
|
20
21
|
false,
|
|
21
22
|
]}
|
|
23
|
+
setLabel={setLabel}
|
|
22
24
|
/>
|
|
23
25
|
);
|
|
24
26
|
const openSelectbutton = screen.getByRole('button', {
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: foreman_remote_execution
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 13.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Foreman Remote Execution team
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2024-
|
|
11
|
+
date: 2024-03-12 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: deface
|
|
@@ -446,6 +446,7 @@ files:
|
|
|
446
446
|
- webpack/JobWizard/JobWizardHelpers.js
|
|
447
447
|
- webpack/JobWizard/JobWizardPageRerun.js
|
|
448
448
|
- webpack/JobWizard/JobWizardSelectors.js
|
|
449
|
+
- webpack/JobWizard/PermissionDenied.js
|
|
449
450
|
- webpack/JobWizard/StartsBeforeErrorAlert.js
|
|
450
451
|
- webpack/JobWizard/__tests__/JobWizardPageRerun.test.js
|
|
451
452
|
- webpack/JobWizard/__tests__/__snapshots__/integration.test.js.snap
|
|
@@ -575,7 +576,7 @@ homepage: https://github.com/theforeman/foreman_remote_execution
|
|
|
575
576
|
licenses:
|
|
576
577
|
- GPL-3.0
|
|
577
578
|
metadata: {}
|
|
578
|
-
post_install_message:
|
|
579
|
+
post_install_message:
|
|
579
580
|
rdoc_options: []
|
|
580
581
|
require_paths:
|
|
581
582
|
- lib
|
|
@@ -593,8 +594,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
593
594
|
- !ruby/object:Gem::Version
|
|
594
595
|
version: '0'
|
|
595
596
|
requirements: []
|
|
596
|
-
rubygems_version: 3.3.
|
|
597
|
-
signing_key:
|
|
597
|
+
rubygems_version: 3.3.26
|
|
598
|
+
signing_key:
|
|
598
599
|
specification_version: 4
|
|
599
600
|
summary: A plugin bringing remote execution to the Foreman, completing the config
|
|
600
601
|
management functionality with remote management functionality.
|