foreman_remote_execution 4.5.5 → 4.8.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/ruby_ci.yml +7 -0
- data/.rubocop_todo.yml +1 -0
- data/app/controllers/api/v2/job_invocations_controller.rb +7 -1
- data/app/graphql/types/job_invocation.rb +16 -0
- data/app/lib/actions/remote_execution/run_host_job.rb +2 -1
- data/app/lib/actions/remote_execution/run_hosts_job.rb +57 -3
- data/app/mailers/rex_job_mailer.rb +15 -0
- data/app/models/job_invocation.rb +4 -0
- data/app/models/job_invocation_composer.rb +21 -13
- data/app/models/job_template.rb +1 -1
- data/app/models/remote_execution_provider.rb +17 -2
- data/app/models/rex_mail_notification.rb +13 -0
- data/app/models/setting/remote_execution.rb +7 -1
- data/app/services/ui_notifications/remote_execution_jobs/base_job_finish.rb +2 -1
- data/app/views/dashboard/_latest-jobs.html.erb +21 -0
- data/app/views/rex_job_mailer/job_finished.html.erb +24 -0
- data/app/views/rex_job_mailer/job_finished.text.erb +9 -0
- data/app/views/template_invocations/show.html.erb +2 -1
- data/db/seeds.d/50-notification_blueprints.rb +14 -0
- data/db/seeds.d/95-mail_notifications.rb +24 -0
- data/foreman_remote_execution.gemspec +2 -4
- data/lib/foreman_remote_execution/engine.rb +4 -0
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/package.json +6 -6
- data/test/functional/api/v2/job_invocations_controller_test.rb +10 -0
- data/test/graphql/queries/job_invocation_query_test.rb +31 -0
- data/test/graphql/queries/job_invocations_query_test.rb +35 -0
- data/test/unit/actions/run_hosts_job_test.rb +99 -4
- data/test/unit/concerns/host_extensions_test.rb +4 -4
- data/test/unit/input_template_renderer_test.rb +1 -89
- data/test/unit/job_invocation_composer_test.rb +1 -12
- data/test/unit/job_invocation_report_template_test.rb +15 -12
- data/test/unit/remote_execution_provider_test.rb +34 -0
- data/webpack/JobWizard/JobWizard.js +53 -20
- data/webpack/JobWizard/JobWizard.scss +33 -4
- data/webpack/JobWizard/JobWizardConstants.js +17 -0
- data/webpack/JobWizard/__tests__/fixtures.js +8 -0
- data/webpack/JobWizard/__tests__/integration.test.js +3 -7
- data/webpack/JobWizard/steps/AdvancedFields/AdvancedFields.js +16 -5
- data/webpack/JobWizard/steps/AdvancedFields/Fields.js +48 -1
- data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +29 -14
- data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.js +4 -2
- data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.test.js +3 -2
- data/webpack/JobWizard/steps/HostsAndInputs/SelectedChips.js +25 -0
- data/webpack/JobWizard/steps/HostsAndInputs/TemplateInputs.js +23 -0
- data/webpack/JobWizard/steps/HostsAndInputs/__tests__/SelectedChips.test.js +37 -0
- data/webpack/JobWizard/steps/HostsAndInputs/__tests__/TemplateInputs.test.js +50 -0
- data/webpack/JobWizard/steps/HostsAndInputs/index.js +66 -0
- data/webpack/JobWizard/steps/Schedule/ScheduleType.js +24 -21
- data/webpack/JobWizard/steps/Schedule/StartEndDates.js +36 -21
- data/webpack/JobWizard/steps/Schedule/__tests__/Schedule.test.js +155 -0
- data/webpack/JobWizard/steps/Schedule/__tests__/StartEndDates.test.js +9 -8
- data/webpack/JobWizard/steps/Schedule/index.js +89 -28
- data/webpack/JobWizard/steps/form/DateTimePicker.js +93 -0
- data/webpack/JobWizard/steps/form/Formatter.js +10 -9
- data/webpack/JobWizard/steps/form/NumberInput.js +2 -0
- data/webpack/JobWizard/steps/form/WizardTitle.js +14 -0
- data/webpack/react_app/components/RecentJobsCard/JobStatusIcon.js +43 -0
- data/webpack/react_app/components/RecentJobsCard/RecentJobsCard.js +73 -66
- data/webpack/react_app/components/RecentJobsCard/RecentJobsTable.js +98 -0
- data/webpack/react_app/components/RecentJobsCard/constants.js +11 -0
- data/webpack/react_app/components/RecentJobsCard/styles.scss +11 -0
- data/webpack/react_app/extend/fillRecentJobsCard.js +1 -1
- metadata +26 -19
- data/webpack/react_app/components/RecentJobsCard/styles.css +0 -15
@@ -350,12 +350,6 @@ class JobInvocationComposerTest < ActiveSupport::TestCase
|
|
350
350
|
end
|
351
351
|
|
352
352
|
describe '#available_bookmarks' do
|
353
|
-
it 'obeys authorization' do
|
354
|
-
composer
|
355
|
-
Bookmark.expects(:authorized).with(:view_bookmarks).returns(Bookmark.where({}))
|
356
|
-
composer.available_bookmarks
|
357
|
-
end
|
358
|
-
|
359
353
|
context 'there are hostgroups and hosts bookmark' do
|
360
354
|
let(:hostgroups) { Bookmark.create(:name => 'hostgroups', :query => 'name = x', :controller => 'hostgroups') }
|
361
355
|
let(:hosts) { Bookmark.create(:name => 'hosts', :query => 'name = x', :controller => 'hosts') }
|
@@ -931,12 +925,7 @@ class JobInvocationComposerTest < ActiveSupport::TestCase
|
|
931
925
|
|
932
926
|
context 'with template in setting present' do
|
933
927
|
before do
|
934
|
-
|
935
|
-
:setting,
|
936
|
-
:name => 'remote_execution_form_job_template',
|
937
|
-
:category => 'Setting::RemoteExecution',
|
938
|
-
:value => setting_template.name
|
939
|
-
)
|
928
|
+
Setting[:remote_execution_form_job_template] = setting_template.name
|
940
929
|
end
|
941
930
|
|
942
931
|
it 'should resolve category to the setting value' do
|
@@ -28,15 +28,16 @@ class JobReportTemplateTest < ActiveSupport::TestCase
|
|
28
28
|
describe 'task reporting' do
|
29
29
|
let(:fake_outputs) do
|
30
30
|
[
|
31
|
-
{ 'output_type' => 'stderr', 'output' => "error"
|
32
|
-
{ 'output_type' => 'stdout', 'output' => "output"
|
33
|
-
{ 'output_type' => '
|
31
|
+
{ 'output_type' => 'stderr', 'output' => "error" },
|
32
|
+
{ 'output_type' => 'stdout', 'output' => "output" },
|
33
|
+
{ 'output_type' => 'debug', 'output' => "debug" },
|
34
34
|
]
|
35
35
|
end
|
36
|
-
let(:fake_task) { FakeTask.new(result: 'success', action_continuous_output: fake_outputs) }
|
36
|
+
let(:fake_task) { FakeTask.new(result: 'success', action_continuous_output: fake_outputs, :ended_at => Time.new(2020, 12, 1, 0, 0, 0).utc) }
|
37
|
+
let(:job_invocation) { FactoryBot.create(:job_invocation, :with_task) }
|
38
|
+
let(:host) { job_invocation.template_invocations.first.host }
|
37
39
|
|
38
40
|
it 'should render task outputs' do
|
39
|
-
job_invocation = FactoryBot.create(:job_invocation, :with_task)
|
40
41
|
JobInvocation.any_instance.expects(:sub_task_for_host).returns(fake_task)
|
41
42
|
|
42
43
|
input = job_invocation_template.template_inputs.first
|
@@ -44,13 +45,15 @@ class JobReportTemplateTest < ActiveSupport::TestCase
|
|
44
45
|
result = ReportComposer.new(composer_params).render
|
45
46
|
|
46
47
|
# parsing the CSV result
|
47
|
-
CSV.parse(result.strip, headers: true)
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
48
|
+
rows = CSV.parse(result.strip, headers: true)
|
49
|
+
assert_equal 1, rows.count
|
50
|
+
row = rows.first
|
51
|
+
assert_equal host.name, row['Host']
|
52
|
+
assert_equal 'success', row['Result']
|
53
|
+
assert_equal 'error', row['stderr']
|
54
|
+
assert_equal 'output', row['stdout']
|
55
|
+
assert_equal 'debug', row['debug']
|
56
|
+
assert_kind_of Time, Time.zone.parse(row['Finished']), 'Parsing of time column failed'
|
54
57
|
end
|
55
58
|
end
|
56
59
|
end
|
@@ -212,6 +212,40 @@ class RemoteExecutionProviderTest < ActiveSupport::TestCase
|
|
212
212
|
host.interfaces.each(&:save)
|
213
213
|
host.reload
|
214
214
|
SSHExecutionProvider.find_ip_or_hostname(host).must_equal execution_interface.ip
|
215
|
+
|
216
|
+
# there is an execution interface with both IPv6 and IPv4: IPv4 is being preferred over IPv6 by default
|
217
|
+
execution_interface = FactoryBot.build(:nic_managed,
|
218
|
+
flags.merge(:execution => true, :ip => '10.0.0.4', :ip6 => 'fd00::4'))
|
219
|
+
host.interfaces = [execution_interface]
|
220
|
+
host.interfaces.each(&:save)
|
221
|
+
host.reload
|
222
|
+
SSHExecutionProvider.find_ip_or_hostname(host).must_equal execution_interface.ip
|
223
|
+
end
|
224
|
+
|
225
|
+
it 'gets ipv6 from flagged interfaces with IPv6 preference' do
|
226
|
+
host.host_params['remote_execution_connect_by_ip_prefer_ipv6'] = true
|
227
|
+
host.host_params['remote_execution_connect_by_ip'] = true
|
228
|
+
|
229
|
+
# there is an execution interface with both IPv6 and IPv4: IPv6 is being preferred over IPv4 by host parameter configuration
|
230
|
+
execution_interface = FactoryBot.build(:nic_managed,
|
231
|
+
flags.merge(:execution => true, :ip => '10.0.0.4', :ip6 => 'fd00::4'))
|
232
|
+
host.interfaces = [execution_interface]
|
233
|
+
host.interfaces.each(&:save)
|
234
|
+
host.reload
|
235
|
+
SSHExecutionProvider.find_ip_or_hostname(host).must_equal execution_interface.ip6
|
236
|
+
end
|
237
|
+
|
238
|
+
it 'gets ipv6 from flagged interfaces with IPv4 preference but without IPv4 address' do
|
239
|
+
host.host_params['remote_execution_connect_by_ip_prefer_ipv6'] = false
|
240
|
+
host.host_params['remote_execution_connect_by_ip'] = true
|
241
|
+
|
242
|
+
# there is an execution interface with both IPv6 and IPv4: IPv6 is being preferred over IPv4 by host parameter configuration
|
243
|
+
execution_interface = FactoryBot.build(:nic_managed,
|
244
|
+
flags.merge(:execution => true, :ip => nil, :ip6 => 'fd00::4'))
|
245
|
+
host.interfaces = [execution_interface]
|
246
|
+
host.interfaces.each(&:save)
|
247
|
+
host.reload
|
248
|
+
SSHExecutionProvider.find_ip_or_hostname(host).must_equal execution_interface.ip6
|
215
249
|
end
|
216
250
|
end
|
217
251
|
end
|
@@ -3,43 +3,64 @@ import React, { useState, useEffect, useCallback } from 'react';
|
|
3
3
|
import { useDispatch, useSelector } from 'react-redux';
|
4
4
|
import { Wizard } from '@patternfly/react-core';
|
5
5
|
import { get } from 'foremanReact/redux/API';
|
6
|
-
import { translate as __ } from 'foremanReact/common/I18n';
|
7
6
|
import history from 'foremanReact/history';
|
8
7
|
import CategoryAndTemplate from './steps/CategoryAndTemplate/';
|
9
8
|
import { AdvancedFields } from './steps/AdvancedFields/AdvancedFields';
|
10
|
-
import {
|
9
|
+
import {
|
10
|
+
JOB_TEMPLATE,
|
11
|
+
WIZARD_TITLES,
|
12
|
+
initialScheduleState,
|
13
|
+
} from './JobWizardConstants';
|
11
14
|
import { selectTemplateError } from './JobWizardSelectors';
|
12
15
|
import Schedule from './steps/Schedule/';
|
16
|
+
import HostsAndInputs from './steps/HostsAndInputs/';
|
13
17
|
import './JobWizard.scss';
|
14
18
|
|
15
19
|
export const JobWizard = () => {
|
16
20
|
const [jobTemplateID, setJobTemplateID] = useState(null);
|
17
21
|
const [category, setCategory] = useState('');
|
18
22
|
const [advancedValues, setAdvancedValues] = useState({});
|
23
|
+
const [templateValues, setTemplateValues] = useState({}); // TODO use templateValues in advanced fields - description https://github.com/theforeman/foreman_remote_execution/pull/605
|
24
|
+
const [selectedHosts, setSelectedHosts] = useState(['host1', 'host2']);
|
25
|
+
const [scheduleValue, setScheduleValue] = useState(initialScheduleState);
|
19
26
|
const dispatch = useDispatch();
|
20
27
|
|
21
28
|
const setDefaults = useCallback(
|
22
29
|
({
|
23
30
|
data: {
|
31
|
+
template_inputs,
|
24
32
|
advanced_template_inputs,
|
25
33
|
effective_user,
|
26
|
-
job_template: {
|
34
|
+
job_template: { execution_timeout_interval, description_format },
|
27
35
|
},
|
28
36
|
}) => {
|
29
37
|
const advancedTemplateValues = {};
|
38
|
+
const defaultTemplateValues = {};
|
39
|
+
const inputs = template_inputs;
|
30
40
|
const advancedInputs = advanced_template_inputs;
|
31
|
-
if (
|
32
|
-
|
33
|
-
|
41
|
+
if (inputs) {
|
42
|
+
setTemplateValues(() => {
|
43
|
+
inputs.forEach(input => {
|
44
|
+
defaultTemplateValues[input.name] = input?.default || '';
|
45
|
+
});
|
46
|
+
return defaultTemplateValues;
|
34
47
|
});
|
35
48
|
}
|
36
|
-
setAdvancedValues(currentAdvancedValues =>
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
49
|
+
setAdvancedValues(currentAdvancedValues => {
|
50
|
+
if (advancedInputs) {
|
51
|
+
advancedInputs.forEach(input => {
|
52
|
+
advancedTemplateValues[input.name] = input?.default || '';
|
53
|
+
});
|
54
|
+
}
|
55
|
+
return {
|
56
|
+
...currentAdvancedValues,
|
57
|
+
effectiveUserValue: effective_user?.value || '',
|
58
|
+
timeoutToKill: execution_timeout_interval || '',
|
59
|
+
templateValues: advancedTemplateValues,
|
60
|
+
description: description_format || '',
|
61
|
+
isRandomizedOrdering: false,
|
62
|
+
};
|
63
|
+
});
|
43
64
|
},
|
44
65
|
[]
|
45
66
|
);
|
@@ -59,7 +80,7 @@ export const JobWizard = () => {
|
|
59
80
|
const isTemplate = !templateError && !!jobTemplateID;
|
60
81
|
const steps = [
|
61
82
|
{
|
62
|
-
name:
|
83
|
+
name: WIZARD_TITLES.categoryAndTemplate,
|
63
84
|
component: (
|
64
85
|
<CategoryAndTemplate
|
65
86
|
jobTemplate={jobTemplateID}
|
@@ -70,12 +91,19 @@ export const JobWizard = () => {
|
|
70
91
|
),
|
71
92
|
},
|
72
93
|
{
|
73
|
-
name:
|
74
|
-
component:
|
94
|
+
name: WIZARD_TITLES.hostsAndInputs,
|
95
|
+
component: (
|
96
|
+
<HostsAndInputs
|
97
|
+
templateValues={templateValues}
|
98
|
+
setTemplateValues={setTemplateValues}
|
99
|
+
selectedHosts={selectedHosts}
|
100
|
+
setSelectedHosts={setSelectedHosts}
|
101
|
+
/>
|
102
|
+
),
|
75
103
|
canJumpTo: isTemplate,
|
76
104
|
},
|
77
105
|
{
|
78
|
-
name:
|
106
|
+
name: WIZARD_TITLES.advanced,
|
79
107
|
component: (
|
80
108
|
<AdvancedFields
|
81
109
|
advancedValues={advancedValues}
|
@@ -91,12 +119,17 @@ export const JobWizard = () => {
|
|
91
119
|
canJumpTo: isTemplate,
|
92
120
|
},
|
93
121
|
{
|
94
|
-
name:
|
95
|
-
component:
|
122
|
+
name: WIZARD_TITLES.schedule,
|
123
|
+
component: (
|
124
|
+
<Schedule
|
125
|
+
scheduleValue={scheduleValue}
|
126
|
+
setScheduleValue={setScheduleValue}
|
127
|
+
/>
|
128
|
+
),
|
96
129
|
canJumpTo: isTemplate,
|
97
130
|
},
|
98
131
|
{
|
99
|
-
name:
|
132
|
+
name: WIZARD_TITLES.review,
|
100
133
|
component: <p>Review Details</p>,
|
101
134
|
nextButtonText: 'Run',
|
102
135
|
canJumpTo: isTemplate,
|
@@ -1,5 +1,10 @@
|
|
1
1
|
.job-wizard {
|
2
|
+
.wizard-title {
|
3
|
+
margin-bottom: 25px;
|
4
|
+
}
|
5
|
+
|
2
6
|
.pf-c-wizard__main {
|
7
|
+
overflow: visible;
|
3
8
|
z-index: calc(
|
4
9
|
var(--pf-c-wizard__footer--ZIndex) + 1
|
5
10
|
); // So the select box can be shown above the wizard footer
|
@@ -12,6 +17,9 @@
|
|
12
17
|
}
|
13
18
|
#advanced-fields-job-template {
|
14
19
|
.foreman-search-field {
|
20
|
+
.rbt-input-hint input{
|
21
|
+
display: none;
|
22
|
+
}
|
15
23
|
// Giving pf3 search bar a pf4 look
|
16
24
|
.search-bar {
|
17
25
|
display: block;
|
@@ -41,13 +49,34 @@
|
|
41
49
|
}
|
42
50
|
}
|
43
51
|
|
52
|
+
.hosts-chip-group {
|
53
|
+
margin-top: 8px;
|
54
|
+
}
|
55
|
+
input[type='radio'],
|
56
|
+
input[type='checkbox'] {
|
57
|
+
margin: 0;
|
58
|
+
}
|
44
59
|
.schedule-tab {
|
45
|
-
input[type='radio'],
|
46
|
-
input[type='checkbox'] {
|
47
|
-
margin: 0;
|
48
|
-
}
|
49
60
|
.advanced-scheduling-button {
|
50
61
|
text-align: start;
|
51
62
|
}
|
52
63
|
}
|
64
|
+
|
65
|
+
.pf-c-date-picker {
|
66
|
+
vertical-align: top;
|
67
|
+
}
|
68
|
+
|
69
|
+
.time-picker {
|
70
|
+
width: 150px;
|
71
|
+
}
|
72
|
+
|
73
|
+
input[type='radio'],
|
74
|
+
input[type='checkbox'] {
|
75
|
+
// overwriting bootstrap/_forms.scss margin: 4px 0 0;
|
76
|
+
margin: 0;
|
77
|
+
}
|
78
|
+
textarea {
|
79
|
+
min-height: 40px;
|
80
|
+
min-width: 100px;
|
81
|
+
}
|
53
82
|
}
|
@@ -14,3 +14,20 @@ export const repeatTypes = {
|
|
14
14
|
daily: __('Daily'),
|
15
15
|
hourly: __('Hourly'),
|
16
16
|
};
|
17
|
+
|
18
|
+
export const WIZARD_TITLES = {
|
19
|
+
categoryAndTemplate: __('Category and Template'),
|
20
|
+
hostsAndInputs: __('Target hosts and inputs'),
|
21
|
+
advanced: __('Advanced Fields'),
|
22
|
+
schedule: __('Schedule'),
|
23
|
+
review: __('Review Details'),
|
24
|
+
};
|
25
|
+
|
26
|
+
export const initialScheduleState = {
|
27
|
+
repeatType: repeatTypes.noRepeat,
|
28
|
+
repeatAmount: '',
|
29
|
+
starts: '',
|
30
|
+
ends: '',
|
31
|
+
isFuture: false,
|
32
|
+
isNeverEnds: false,
|
33
|
+
};
|
@@ -93,6 +93,14 @@ export const testSetup = (selectors, api) => {
|
|
93
93
|
jest.spyOn(selectors, 'selectJobCategories');
|
94
94
|
jest.spyOn(selectors, 'selectJobCategoriesStatus');
|
95
95
|
|
96
|
+
jest.spyOn(selectors, 'selectTemplateInputs');
|
97
|
+
jest.spyOn(selectors, 'selectAdvancedTemplateInputs');
|
98
|
+
selectors.selectTemplateInputs.mockImplementation(
|
99
|
+
() => jobTemplateResponse.template_inputs
|
100
|
+
);
|
101
|
+
selectors.selectAdvancedTemplateInputs.mockImplementation(
|
102
|
+
() => jobTemplateResponse.advanced_template_inputs
|
103
|
+
);
|
96
104
|
selectors.selectJobCategories.mockImplementation(() => jobCategories);
|
97
105
|
selectors.selectJobTemplates.mockImplementation(() => [
|
98
106
|
jobTemplate,
|
@@ -5,6 +5,7 @@ import { render, fireEvent, screen, act } from '@testing-library/react';
|
|
5
5
|
import * as api from 'foremanReact/redux/API';
|
6
6
|
import { JobWizard } from '../JobWizard';
|
7
7
|
import * as selectors from '../JobWizardSelectors';
|
8
|
+
import { WIZARD_TITLES } from '../JobWizardConstants';
|
8
9
|
import {
|
9
10
|
testSetup,
|
10
11
|
mockApi,
|
@@ -62,13 +63,8 @@ describe('Job wizard fill', () => {
|
|
62
63
|
<JobWizard />
|
63
64
|
</Provider>
|
64
65
|
);
|
65
|
-
const
|
66
|
-
|
67
|
-
'Advanced Fields',
|
68
|
-
'Schedule',
|
69
|
-
'Review Details',
|
70
|
-
'Category and Template',
|
71
|
-
];
|
66
|
+
const titles = Object.values(WIZARD_TITLES);
|
67
|
+
const steps = [titles[1], titles[0], ...titles.slice(2)]; // the first title is selected at the beggining
|
72
68
|
// eslint-disable-next-line no-unused-vars
|
73
69
|
for await (const step of steps) {
|
74
70
|
const stepSelector = screen.getByText(step);
|
@@ -1,8 +1,7 @@
|
|
1
1
|
import React from 'react';
|
2
2
|
import PropTypes from 'prop-types';
|
3
3
|
import { useSelector } from 'react-redux';
|
4
|
-
import {
|
5
|
-
import { translate as __ } from 'foremanReact/common/I18n';
|
4
|
+
import { Form } from '@patternfly/react-core';
|
6
5
|
import {
|
7
6
|
selectEffectiveUser,
|
8
7
|
selectAdvancedTemplateInputs,
|
@@ -17,8 +16,11 @@ import {
|
|
17
16
|
ConcurrencyLevelField,
|
18
17
|
TimeSpanLevelField,
|
19
18
|
TemplateInputsFields,
|
19
|
+
ExecutionOrderingField,
|
20
20
|
} from './Fields';
|
21
21
|
import { DescriptionField } from './DescriptionField';
|
22
|
+
import { WIZARD_TITLES } from '../../JobWizardConstants';
|
23
|
+
import { WizardTitle } from '../form/WizardTitle';
|
22
24
|
|
23
25
|
export const AdvancedFields = ({ advancedValues, setAdvancedValues }) => {
|
24
26
|
const effectiveUser = useSelector(selectEffectiveUser);
|
@@ -26,9 +28,10 @@ export const AdvancedFields = ({ advancedValues, setAdvancedValues }) => {
|
|
26
28
|
const templateInputs = useSelector(selectTemplateInputs);
|
27
29
|
return (
|
28
30
|
<>
|
29
|
-
<
|
30
|
-
{
|
31
|
-
|
31
|
+
<WizardTitle
|
32
|
+
title={WIZARD_TITLES.advanced}
|
33
|
+
className="advanced-fields-title"
|
34
|
+
/>
|
32
35
|
<Form id="advanced-fields-job-template" autoComplete="off">
|
33
36
|
<TemplateInputsFields
|
34
37
|
inputs={advancedTemplateInputs}
|
@@ -98,6 +101,14 @@ export const AdvancedFields = ({ advancedValues, setAdvancedValues }) => {
|
|
98
101
|
})
|
99
102
|
}
|
100
103
|
/>
|
104
|
+
<ExecutionOrderingField
|
105
|
+
isRandomizedOrdering={advancedValues.isRandomizedOrdering}
|
106
|
+
setValue={newValue =>
|
107
|
+
setAdvancedValues({
|
108
|
+
isRandomizedOrdering: newValue,
|
109
|
+
})
|
110
|
+
}
|
111
|
+
/>
|
101
112
|
</Form>
|
102
113
|
</>
|
103
114
|
);
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import React from 'react';
|
2
2
|
import PropTypes from 'prop-types';
|
3
|
-
import { FormGroup, TextInput } from '@patternfly/react-core';
|
3
|
+
import { FormGroup, TextInput, Radio } from '@patternfly/react-core';
|
4
4
|
import { translate as __ } from 'foremanReact/common/I18n';
|
5
5
|
import { helpLabel } from '../form/FormHelpers';
|
6
6
|
import { formatter } from '../form/Formatter';
|
@@ -18,6 +18,7 @@ export const EffectiveUserField = ({ value, setValue }) => (
|
|
18
18
|
fieldId="effective-user"
|
19
19
|
>
|
20
20
|
<TextInput
|
21
|
+
aria-label="effective user"
|
21
22
|
autoComplete="effective-user"
|
22
23
|
id="effective-user"
|
23
24
|
type="text"
|
@@ -61,6 +62,7 @@ export const PasswordField = ({ value, setValue }) => (
|
|
61
62
|
fieldId="job-password"
|
62
63
|
>
|
63
64
|
<TextInput
|
65
|
+
aria-label="job password"
|
64
66
|
autoComplete="new-password" // to prevent firefox from autofilling the user password
|
65
67
|
id="job-password"
|
66
68
|
type="password"
|
@@ -83,6 +85,7 @@ export const KeyPassphraseField = ({ value, setValue }) => (
|
|
83
85
|
fieldId="key-passphrase"
|
84
86
|
>
|
85
87
|
<TextInput
|
88
|
+
aria-label="key passphrase"
|
86
89
|
autoComplete="key-passphrase"
|
87
90
|
id="key-passphrase"
|
88
91
|
type="password"
|
@@ -105,6 +108,7 @@ export const EffectiveUserPasswordField = ({ value, setValue }) => (
|
|
105
108
|
fieldId="effective-user-password"
|
106
109
|
>
|
107
110
|
<TextInput
|
111
|
+
aria-label="effective userpassword"
|
108
112
|
autoComplete="effective-user-password"
|
109
113
|
id="effective-user-password"
|
110
114
|
type="password"
|
@@ -161,6 +165,41 @@ export const TimeSpanLevelField = ({ value, setValue }) => (
|
|
161
165
|
/>
|
162
166
|
);
|
163
167
|
|
168
|
+
export const ExecutionOrderingField = ({ isRandomizedOrdering, setValue }) => (
|
169
|
+
<FormGroup
|
170
|
+
label={__('Execution ordering')}
|
171
|
+
fieldId="schedule-type"
|
172
|
+
labelIcon={helpLabel(
|
173
|
+
<div
|
174
|
+
dangerouslySetInnerHTML={{
|
175
|
+
__html: __(
|
176
|
+
'Execution ordering determines whether the jobs should be executed on hosts in alphabetical order or in randomized order.<br><ul><li><b>Ordered</b> - executes the jobs on hosts in alphabetical order</li><li><b>Randomized</b> - randomizes the order in which jobs are executed on hosts</li></ul>'
|
177
|
+
),
|
178
|
+
}}
|
179
|
+
/>,
|
180
|
+
'effective-user-password'
|
181
|
+
)}
|
182
|
+
isInline
|
183
|
+
>
|
184
|
+
<Radio
|
185
|
+
aria-label="execution order alphabetical"
|
186
|
+
isChecked={!isRandomizedOrdering}
|
187
|
+
name="execution-order"
|
188
|
+
onChange={() => setValue(false)}
|
189
|
+
id="execution-order-alphabetical"
|
190
|
+
label={__('Alphabetical')}
|
191
|
+
/>
|
192
|
+
<Radio
|
193
|
+
aria-label="execution order randomized"
|
194
|
+
isChecked={isRandomizedOrdering}
|
195
|
+
name="execution-order"
|
196
|
+
onChange={() => setValue(true)}
|
197
|
+
id="execution-order-randomized"
|
198
|
+
label={__('Randomized')}
|
199
|
+
/>
|
200
|
+
</FormGroup>
|
201
|
+
);
|
202
|
+
|
164
203
|
export const TemplateInputsFields = ({ inputs, value, setValue }) => (
|
165
204
|
<>{inputs?.map(input => formatter(input, value, setValue))}</>
|
166
205
|
);
|
@@ -184,6 +223,14 @@ ConcurrencyLevelField.propTypes = EffectiveUserField.propTypes;
|
|
184
223
|
ConcurrencyLevelField.defaultProps = EffectiveUserField.defaultProps;
|
185
224
|
TimeSpanLevelField.propTypes = EffectiveUserField.propTypes;
|
186
225
|
TimeSpanLevelField.defaultProps = EffectiveUserField.defaultProps;
|
226
|
+
ExecutionOrderingField.propTypes = {
|
227
|
+
isRandomizedOrdering: PropTypes.bool,
|
228
|
+
setValue: PropTypes.func.isRequired,
|
229
|
+
};
|
230
|
+
ExecutionOrderingField.defaultProps = {
|
231
|
+
isRandomizedOrdering: false,
|
232
|
+
};
|
233
|
+
|
187
234
|
TemplateInputsFields.propTypes = {
|
188
235
|
inputs: PropTypes.array.isRequired,
|
189
236
|
value: PropTypes.object,
|