foreman_remote_execution 6.2.0 → 7.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ruby_ci.yml +2 -0
- data/app/controllers/api/v2/job_invocations_controller.rb +1 -0
- data/app/helpers/hosts_extensions_helper.rb +62 -0
- data/app/lib/actions/remote_execution/run_host_job.rb +4 -0
- data/app/lib/actions/remote_execution/run_hosts_job.rb +4 -0
- data/app/models/concerns/foreman_remote_execution/foreman_tasks_task_extensions.rb +6 -0
- data/app/models/host_status/execution_status.rb +2 -1
- data/app/models/job_invocation.rb +1 -1
- data/app/models/job_invocation_composer.rb +7 -3
- data/app/models/job_template.rb +6 -1
- data/app/models/remote_execution_provider.rb +4 -0
- data/app/models/ssh_execution_provider.rb +3 -3
- data/app/models/template_invocation.rb +2 -0
- data/app/services/remote_execution_proxy_selector.rb +1 -1
- data/app/views/api/v2/job_invocations/base.json.rabl +1 -1
- data/app/views/job_invocations/_card_target_hosts.html.erb +8 -0
- data/app/views/job_invocations/_form.html.erb +2 -0
- data/app/views/overrides/subnets/_rex_tab_pane.html.erb +1 -1
- data/db/migrate/20220331112719_add_ssh_user_to_job_invocation.rb +5 -0
- data/lib/foreman_remote_execution/engine.rb +6 -3
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/locale/action_names.rb +3 -3
- data/locale/de/foreman_remote_execution.po +23 -23
- data/locale/en/foreman_remote_execution.po +23 -23
- data/locale/en_GB/foreman_remote_execution.po +23 -23
- data/locale/es/foreman_remote_execution.po +23 -23
- data/locale/foreman_remote_execution.pot +64 -66
- data/locale/fr/foreman_remote_execution.po +23 -23
- data/locale/ja/foreman_remote_execution.po +23 -23
- data/locale/ko/foreman_remote_execution.po +23 -23
- data/locale/pt_BR/foreman_remote_execution.po +23 -23
- data/locale/ru/foreman_remote_execution.po +23 -23
- data/locale/zh_CN/foreman_remote_execution.po +23 -23
- data/locale/zh_TW/foreman_remote_execution.po +23 -23
- data/test/unit/api_params_test.rb +33 -0
- data/test/unit/remote_execution_provider_test.rb +26 -0
- data/webpack/JobWizard/JobWizardConstants.js +2 -2
- data/webpack/JobWizard/__tests__/fixtures.js +8 -4
- data/webpack/JobWizard/steps/AdvancedFields/AdvancedFields.js +9 -0
- data/webpack/JobWizard/steps/AdvancedFields/Fields.js +21 -0
- data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +7 -2
- data/webpack/JobWizard/steps/HostsAndInputs/SelectGQL.js +2 -1
- data/webpack/JobWizard/steps/HostsAndInputs/SelectedChips.js +1 -1
- data/webpack/JobWizard/steps/HostsAndInputs/buildHostQuery.js +1 -1
- data/webpack/JobWizard/steps/HostsAndInputs/hostgroups.gql +1 -0
- data/webpack/JobWizard/steps/HostsAndInputs/hosts.gql +1 -0
- data/webpack/JobWizard/steps/ReviewDetails/index.js +2 -1
- data/webpack/JobWizard/steps/Schedule/__tests__/Schedule.test.js +15 -15
- data/webpack/JobWizard/steps/form/SearchSelect.js +0 -1
- data/webpack/JobWizard/submit.js +2 -0
- data/webpack/__mocks__/foremanReact/common/globalIdHelpers.js +1 -0
- data/webpack/global_index.js +2 -8
- data/webpack/react_app/components/FeaturesDropdown/index.js +1 -1
- data/webpack/react_app/components/HostKebab/KebabItems.js +6 -1
- data/webpack/react_app/components/RecentJobsCard/RecentJobsCard.js +4 -4
- data/webpack/react_app/components/TargetingHosts/TargetingHostsConsts.js +1 -0
- data/webpack/react_app/components/TargetingHosts/TargetingHostsPage.js +8 -3
- data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHostsPage.test.js.snap +9 -1
- data/webpack/react_app/components/TargetingHosts/__tests__/fixtures.js +1 -4
- data/webpack/react_app/components/TargetingHosts/index.js +1 -0
- data/webpack/react_app/extend/Fills.js +48 -0
- metadata +9 -8
- data/app/helpers/concerns/foreman_remote_execution/hosts_helper_extensions.rb +0 -62
- data/webpack/react_app/extend/fillKebabItems.js +0 -11
- data/webpack/react_app/extend/fillRecentJobsCard.js +0 -11
- data/webpack/react_app/extend/fillRexFeaturesDropdown.js +0 -11
- data/webpack/react_app/extend/fillregistrationAdvanced.js +0 -11
@@ -23,6 +23,9 @@ msgstr "%s"
|
|
23
23
|
msgid "%s ago"
|
24
24
|
msgstr "%s 之前"
|
25
25
|
|
26
|
+
msgid "%s job has been invoked"
|
27
|
+
msgstr ""
|
28
|
+
|
26
29
|
msgid "%{description} on %{host}"
|
27
30
|
msgstr "%{description} 於 %{host} 之上"
|
28
31
|
|
@@ -67,15 +70,15 @@ msgstr "要使用執行這 script 的使用者。如果使用者與 SSH 使用
|
|
67
70
|
msgid "Abort Job"
|
68
71
|
msgstr ""
|
69
72
|
|
73
|
+
msgid "Action with sub plans"
|
74
|
+
msgstr ""
|
75
|
+
|
70
76
|
msgid "Actions"
|
71
77
|
msgstr "動作"
|
72
78
|
|
73
79
|
msgid "Add Foreign Input Set"
|
74
80
|
msgstr "新增外部輸入集"
|
75
81
|
|
76
|
-
msgid "Advanced Fields"
|
77
|
-
msgstr ""
|
78
|
-
|
79
82
|
msgid "Advanced fields"
|
80
83
|
msgstr ""
|
81
84
|
|
@@ -466,9 +469,15 @@ msgstr ""
|
|
466
469
|
msgid "Import"
|
467
470
|
msgstr "匯入"
|
468
471
|
|
472
|
+
msgid "Import Puppet classes"
|
473
|
+
msgstr ""
|
474
|
+
|
469
475
|
msgid "Import a job template from ERB"
|
470
476
|
msgstr ""
|
471
477
|
|
478
|
+
msgid "Import facts"
|
479
|
+
msgstr ""
|
480
|
+
|
472
481
|
msgid "Include all inputs from the foreign template"
|
473
482
|
msgstr ""
|
474
483
|
|
@@ -502,9 +511,6 @@ msgstr "祈願類型,%s 之一"
|
|
502
511
|
msgid "Job"
|
503
512
|
msgstr "工作"
|
504
513
|
|
505
|
-
msgid "Job Category"
|
506
|
-
msgstr ""
|
507
|
-
|
508
514
|
msgid "Job Details"
|
509
515
|
msgstr ""
|
510
516
|
|
@@ -826,7 +832,7 @@ msgstr "解析至"
|
|
826
832
|
msgid "Results"
|
827
833
|
msgstr ""
|
828
834
|
|
829
|
-
msgid "Review
|
835
|
+
msgid "Review details"
|
830
836
|
msgstr ""
|
831
837
|
|
832
838
|
msgid "Run"
|
@@ -850,9 +856,6 @@ msgstr ""
|
|
850
856
|
msgid "Running"
|
851
857
|
msgstr ""
|
852
858
|
|
853
|
-
msgid "SSH"
|
854
|
-
msgstr "SSH"
|
855
|
-
|
856
859
|
msgid "SSH Port"
|
857
860
|
msgstr ""
|
858
861
|
|
@@ -868,6 +871,9 @@ msgstr "排程"
|
|
868
871
|
msgid "Schedule Remote Job"
|
869
872
|
msgstr ""
|
870
873
|
|
874
|
+
msgid "Schedule a job"
|
875
|
+
msgstr ""
|
876
|
+
|
871
877
|
msgid "Schedule for future execution"
|
872
878
|
msgstr ""
|
873
879
|
|
@@ -889,6 +895,9 @@ msgstr ""
|
|
889
895
|
msgid "Scheduled to start before"
|
890
896
|
msgstr ""
|
891
897
|
|
898
|
+
msgid "Script"
|
899
|
+
msgstr ""
|
900
|
+
|
892
901
|
msgid "Scroll to bottom"
|
893
902
|
msgstr "捲動至底端"
|
894
903
|
|
@@ -1209,16 +1218,16 @@ msgstr "使用者輸入"
|
|
1209
1218
|
msgid "Value"
|
1210
1219
|
msgstr "值"
|
1211
1220
|
|
1212
|
-
msgid "View
|
1221
|
+
msgid "View all jobs"
|
1213
1222
|
msgstr ""
|
1214
1223
|
|
1215
|
-
msgid "View
|
1224
|
+
msgid "View finished jobs"
|
1216
1225
|
msgstr ""
|
1217
1226
|
|
1218
|
-
msgid "View
|
1227
|
+
msgid "View running jobs"
|
1219
1228
|
msgstr ""
|
1220
1229
|
|
1221
|
-
msgid "View
|
1230
|
+
msgid "View scheduled jobs"
|
1222
1231
|
msgstr ""
|
1223
1232
|
|
1224
1233
|
msgid "Web Console"
|
@@ -1364,12 +1373,3 @@ msgstr ""
|
|
1364
1373
|
|
1365
1374
|
msgid "using Smart Proxy"
|
1366
1375
|
msgstr ""
|
1367
|
-
|
1368
|
-
msgid "»Action with sub plans«"
|
1369
|
-
msgstr ""
|
1370
|
-
|
1371
|
-
msgid "»Import Puppet classes«"
|
1372
|
-
msgstr ""
|
1373
|
-
|
1374
|
-
msgid "»Import facts«"
|
1375
|
-
msgstr ""
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'test_plugin_helper'
|
4
|
+
|
5
|
+
class ApiParamsTest < ActiveSupport::TestCase
|
6
|
+
describe '#format_datetime' do
|
7
|
+
let(:params) { JobInvocationComposer::ApiParams.allocate }
|
8
|
+
|
9
|
+
it 'leaves empty string as is' do
|
10
|
+
assert_equal params.send(:format_datetime, ''), ''
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'honors explicitly supplied time zone' do
|
14
|
+
in_time_zone(ActiveSupport::TimeZone['America/New_York']) do
|
15
|
+
assert_equal '2022-07-08 08:53', params.send(:format_datetime, '2022-07-08 12:53:20 UTC')
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'implicitly honors current user\'s time zone' do
|
20
|
+
in_time_zone(ActiveSupport::TimeZone['America/New_York']) do
|
21
|
+
assert_equal '2022-07-08 12:53', params.send(:format_datetime, '2022-07-08 12:53:20')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def in_time_zone(zone)
|
27
|
+
old_tz = Time.zone
|
28
|
+
Time.zone = zone
|
29
|
+
yield
|
30
|
+
ensure
|
31
|
+
Time.zone = old_tz
|
32
|
+
end
|
33
|
+
end
|
@@ -33,6 +33,10 @@ class RemoteExecutionProviderTest < ActiveSupport::TestCase
|
|
33
33
|
it 'accepts strings' do
|
34
34
|
RemoteExecutionProvider.provider_for('SSH').must_equal SSHExecutionProvider
|
35
35
|
end
|
36
|
+
|
37
|
+
it 'returns a default one if unknown value is provided' do
|
38
|
+
RemoteExecutionProvider.provider_for('WinRM').must_equal ScriptExecutionProvider
|
39
|
+
end
|
36
40
|
end
|
37
41
|
|
38
42
|
describe '.provider_names' do
|
@@ -52,6 +56,28 @@ class RemoteExecutionProviderTest < ActiveSupport::TestCase
|
|
52
56
|
end
|
53
57
|
end
|
54
58
|
|
59
|
+
describe '.provider_proxy_features' do
|
60
|
+
it 'returns correct values' do
|
61
|
+
RemoteExecutionProvider.stubs(:providers).returns(
|
62
|
+
:SSH => SSHExecutionProvider,
|
63
|
+
:script => ScriptExecutionProvider
|
64
|
+
)
|
65
|
+
|
66
|
+
features = RemoteExecutionProvider.provider_proxy_features
|
67
|
+
_(features).must_include 'SSH'
|
68
|
+
_(features).must_include 'Script'
|
69
|
+
RemoteExecutionProvider.unstub(:providers)
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'can deal with non-arrays' do
|
73
|
+
provider = OpenStruct.new(proxy_feature: 'Testing')
|
74
|
+
RemoteExecutionProvider.stubs(:providers).returns(:testing => provider)
|
75
|
+
features = RemoteExecutionProvider.provider_proxy_features
|
76
|
+
_(features).must_include 'Testing'
|
77
|
+
RemoteExecutionProvider.unstub(:providers)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
55
81
|
describe '.host_setting' do
|
56
82
|
let(:host) { FactoryBot.create(:host) }
|
57
83
|
|
@@ -19,9 +19,9 @@ export const repeatTypes = {
|
|
19
19
|
export const WIZARD_TITLES = {
|
20
20
|
categoryAndTemplate: __('Category and Template'),
|
21
21
|
hostsAndInputs: __('Target hosts and inputs'),
|
22
|
-
advanced: __('Advanced
|
22
|
+
advanced: __('Advanced fields'),
|
23
23
|
schedule: __('Schedule'),
|
24
|
-
review: __('Review
|
24
|
+
review: __('Review details'),
|
25
25
|
};
|
26
26
|
|
27
27
|
export const initialScheduleState = {
|
@@ -193,7 +193,11 @@ export const gqlMock = [
|
|
193
193
|
data: {
|
194
194
|
hosts: {
|
195
195
|
totalCount: 3,
|
196
|
-
nodes: [
|
196
|
+
nodes: [
|
197
|
+
{ id: 'MDE6SG9zdC0x', name: 'host1' },
|
198
|
+
{ id: 'MDE6SG9zdC0y', name: 'host2' },
|
199
|
+
{ id: 'MDE6SG9zdC0z', name: 'host3' },
|
200
|
+
],
|
197
201
|
},
|
198
202
|
},
|
199
203
|
},
|
@@ -211,9 +215,9 @@ export const gqlMock = [
|
|
211
215
|
hostgroups: {
|
212
216
|
totalCount: 3,
|
213
217
|
nodes: [
|
214
|
-
{ name: 'host_group1' },
|
215
|
-
{ name: 'host_group2' },
|
216
|
-
{ name: 'host_group3' },
|
218
|
+
{ id: 'MDE6SG9zdGdyb3VwLTE=', name: 'host_group1' },
|
219
|
+
{ id: 'MDE6SG9zdGdyb3VwLTI=', name: 'host_group2' },
|
220
|
+
{ id: 'MDE6SG9zdGdyb3VwLTM=', name: 'host_group3' },
|
217
221
|
],
|
218
222
|
},
|
219
223
|
},
|
@@ -16,6 +16,7 @@ import {
|
|
16
16
|
TimeSpanLevelField,
|
17
17
|
TemplateInputsFields,
|
18
18
|
ExecutionOrderingField,
|
19
|
+
SSHUserField,
|
19
20
|
} from './Fields';
|
20
21
|
import { DescriptionField } from './DescriptionField';
|
21
22
|
import { WIZARD_TITLES } from '../../JobWizardConstants';
|
@@ -40,6 +41,14 @@ export const AdvancedFields = ({
|
|
40
41
|
value={advancedValues.templateValues}
|
41
42
|
setValue={newValue => setAdvancedValues({ templateValues: newValue })}
|
42
43
|
/>
|
44
|
+
<SSHUserField
|
45
|
+
value={advancedValues.sshUser}
|
46
|
+
setValue={newValue =>
|
47
|
+
setAdvancedValues({
|
48
|
+
sshUser: newValue,
|
49
|
+
})
|
50
|
+
}
|
51
|
+
/>
|
43
52
|
{effectiveUser?.overridable && (
|
44
53
|
<EffectiveUserField
|
45
54
|
value={advancedValues.effectiveUserValue}
|
@@ -203,6 +203,24 @@ export const ExecutionOrderingField = ({ isRandomizedOrdering, setValue }) => (
|
|
203
203
|
export const TemplateInputsFields = ({ inputs, value, setValue }) => (
|
204
204
|
<>{inputs?.map(input => formatter(input, value, setValue))}</>
|
205
205
|
);
|
206
|
+
|
207
|
+
export const SSHUserField = ({ value, setValue }) => (
|
208
|
+
<FormGroup
|
209
|
+
label={__('SSH user')}
|
210
|
+
labelIcon={helpLabel(__('A user to be used for SSH.'), 'ssh-user')}
|
211
|
+
fieldId="ssh-user"
|
212
|
+
>
|
213
|
+
<TextInput
|
214
|
+
aria-label="ssh user"
|
215
|
+
autoComplete="ssh-user"
|
216
|
+
id="ssh-user"
|
217
|
+
type="text"
|
218
|
+
value={value}
|
219
|
+
onChange={newValue => setValue(newValue)}
|
220
|
+
/>
|
221
|
+
</FormGroup>
|
222
|
+
);
|
223
|
+
|
206
224
|
EffectiveUserField.propTypes = {
|
207
225
|
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
208
226
|
setValue: PropTypes.func.isRequired,
|
@@ -240,3 +258,6 @@ TemplateInputsFields.propTypes = {
|
|
240
258
|
TemplateInputsFields.defaultProps = {
|
241
259
|
value: {},
|
242
260
|
};
|
261
|
+
|
262
|
+
SSHUserField.propTypes = EffectiveUserField.propTypes;
|
263
|
+
SSHUserField.defaultProps = EffectiveUserField.defaultProps;
|
@@ -143,7 +143,7 @@ describe('AdvancedFields', () => {
|
|
143
143
|
);
|
144
144
|
|
145
145
|
await act(async () => {
|
146
|
-
fireEvent.click(screen.getByText('Advanced
|
146
|
+
fireEvent.click(screen.getByText('Advanced fields'));
|
147
147
|
});
|
148
148
|
expect(textField.value).toBe(textValue);
|
149
149
|
expect(searchField.value).toBe(searchValue);
|
@@ -162,9 +162,14 @@ describe('AdvancedFields', () => {
|
|
162
162
|
</MockedProvider>
|
163
163
|
);
|
164
164
|
await act(async () => {
|
165
|
-
fireEvent.click(screen.getByText('Advanced
|
165
|
+
fireEvent.click(screen.getByText('Advanced fields'));
|
166
166
|
});
|
167
167
|
|
168
|
+
expect(
|
169
|
+
screen.getByLabelText('ssh user', {
|
170
|
+
selector: 'input',
|
171
|
+
}).value
|
172
|
+
).toBe('');
|
168
173
|
expect(
|
169
174
|
screen.getByLabelText('effective user', {
|
170
175
|
selector: 'input',
|
@@ -5,6 +5,7 @@ import {
|
|
5
5
|
useForemanOrganization,
|
6
6
|
useForemanLocation,
|
7
7
|
} from 'foremanReact/Root/Context/ForemanContext';
|
8
|
+
import { decodeId } from 'foremanReact/common/globalIdHelpers';
|
8
9
|
import { HOSTS, HOST_GROUPS, dataName } from '../../JobWizardConstants';
|
9
10
|
import { SearchSelect } from '../form/SearchSelect';
|
10
11
|
import hostsQuery from './hosts.gql';
|
@@ -35,7 +36,7 @@ export const useNameSearchGQL = apiKey => {
|
|
35
36
|
subtotal: data?.[dataName[apiKey]]?.totalCount,
|
36
37
|
results:
|
37
38
|
data?.[dataName[apiKey]]?.nodes.map(node => ({
|
38
|
-
id: node.
|
39
|
+
id: decodeId(node.id),
|
39
40
|
name: node.name,
|
40
41
|
})) || [],
|
41
42
|
},
|
@@ -1,6 +1,6 @@
|
|
1
1
|
export const buildHostQuery = (selected, search) => {
|
2
2
|
const { hosts, hostCollections, hostGroups } = selected;
|
3
|
-
const hostsSearch = `(
|
3
|
+
const hostsSearch = `(id ^ (${hosts.map(({ id }) => id).join(',')}))`;
|
4
4
|
const hostCollectionsSearch = `(host_collection_id ^ (${hostCollections
|
5
5
|
.map(({ id }) => id)
|
6
6
|
.join(',')}))`;
|
@@ -72,7 +72,7 @@ const ReviewDetails = ({
|
|
72
72
|
};
|
73
73
|
const [isAdvancedShown, setIsAdvancedShown] = useState(false);
|
74
74
|
const detailsFirstHalf = [
|
75
|
-
{ label: __('Job
|
75
|
+
{ label: __('Job category'), value: jobCategory },
|
76
76
|
{ label: __('Job template'), value: jobTemplate },
|
77
77
|
{ label: __('Target hosts'), value: stringHosts() },
|
78
78
|
...templateInputs.map(({ name }) => ({
|
@@ -125,6 +125,7 @@ const ReviewDetails = ({
|
|
125
125
|
].filter(d => d);
|
126
126
|
|
127
127
|
const advancedFields = [
|
128
|
+
{ label: __('SSH user'), value: advancedValues.sshUser },
|
128
129
|
{ label: __('Effective user'), value: advancedValues.effectiveUserValue },
|
129
130
|
{ label: __('Description Template'), value: advancedValues.description },
|
130
131
|
{ label: __('Timeout to kill'), value: advancedValues.timeoutToKill },
|
@@ -186,7 +186,7 @@ describe('Schedule', () => {
|
|
186
186
|
expect(
|
187
187
|
screen.getByPlaceholderText('Repeat N times').hasAttribute('disabled')
|
188
188
|
).toBeTruthy();
|
189
|
-
expect(screen.getByText('Review
|
189
|
+
expect(screen.getByText('Review details').disabled).toBeFalsy();
|
190
190
|
await act(async () => {
|
191
191
|
fireEvent.click(
|
192
192
|
screen.getByLabelText('Does not repeat', { selector: 'button' })
|
@@ -196,7 +196,7 @@ describe('Schedule', () => {
|
|
196
196
|
await act(async () => {
|
197
197
|
fireEvent.click(screen.getByText('Cronline'));
|
198
198
|
});
|
199
|
-
expect(screen.getByText('Review
|
199
|
+
expect(screen.getByText('Review details').disabled).toBeTruthy();
|
200
200
|
const newRepeatTimes = '3';
|
201
201
|
const repeatNTimes = screen.getByPlaceholderText('Repeat N times');
|
202
202
|
expect(repeatNTimes.value).toBe('');
|
@@ -216,7 +216,7 @@ describe('Schedule', () => {
|
|
216
216
|
});
|
217
217
|
});
|
218
218
|
expect(cronline.value).toBe(newCronline);
|
219
|
-
expect(screen.getByText('Review
|
219
|
+
expect(screen.getByText('Review details').disabled).toBeFalsy();
|
220
220
|
|
221
221
|
await act(async () => {
|
222
222
|
fireEvent.click(screen.getByText('Category and Template'));
|
@@ -236,7 +236,7 @@ describe('Schedule', () => {
|
|
236
236
|
fireEvent.click(screen.getByText('Monthly'));
|
237
237
|
});
|
238
238
|
|
239
|
-
expect(screen.getByText('Review
|
239
|
+
expect(screen.getByText('Review details').disabled).toBeTruthy();
|
240
240
|
const newDays = '1,2,3';
|
241
241
|
const days = screen.getByLabelText('days');
|
242
242
|
expect(days.value).toBe('');
|
@@ -248,7 +248,7 @@ describe('Schedule', () => {
|
|
248
248
|
});
|
249
249
|
expect(days.value).toBe(newDays);
|
250
250
|
|
251
|
-
expect(screen.getByText('Review
|
251
|
+
expect(screen.getByText('Review details').disabled).toBeTruthy();
|
252
252
|
const newAtMonthly = '13:07';
|
253
253
|
const at = () => screen.getByLabelText('repeat-at');
|
254
254
|
expect(at().value).toBe('');
|
@@ -259,13 +259,13 @@ describe('Schedule', () => {
|
|
259
259
|
});
|
260
260
|
expect(at().value).toBe(newAtMonthly);
|
261
261
|
|
262
|
-
expect(screen.getByText('Review
|
262
|
+
expect(screen.getByText('Review details').disabled).toBeFalsy();
|
263
263
|
fireEvent.click(screen.getByText('Monthly'));
|
264
264
|
await act(async () => {
|
265
265
|
fireEvent.click(screen.getByText('Weekly'));
|
266
266
|
});
|
267
267
|
|
268
|
-
expect(screen.getByText('Review
|
268
|
+
expect(screen.getByText('Review details').disabled).toBeTruthy();
|
269
269
|
const dayTue = screen.getByLabelText('Tue checkbox');
|
270
270
|
const daySat = screen.getByLabelText('Sat checkbox');
|
271
271
|
expect(dayTue.checked).toBe(false);
|
@@ -293,19 +293,19 @@ describe('Schedule', () => {
|
|
293
293
|
});
|
294
294
|
expect(at().value).toBe(newAtWeekly);
|
295
295
|
|
296
|
-
expect(screen.getByText('Review
|
296
|
+
expect(screen.getByText('Review details').disabled).toBeFalsy();
|
297
297
|
fireEvent.click(screen.getByText('Weekly'));
|
298
298
|
await act(async () => {
|
299
299
|
fireEvent.click(screen.getByText('Daily'));
|
300
300
|
});
|
301
301
|
|
302
|
-
expect(screen.getByText('Review
|
302
|
+
expect(screen.getByText('Review details').disabled).toBeFalsy();
|
303
303
|
await act(async () => {
|
304
304
|
fireEvent.change(at(), {
|
305
305
|
target: { value: '' },
|
306
306
|
});
|
307
307
|
});
|
308
|
-
expect(screen.getByText('Review
|
308
|
+
expect(screen.getByText('Review details').disabled).toBeTruthy();
|
309
309
|
const newAtDaily = '17:07';
|
310
310
|
expect(at().value).toBe('');
|
311
311
|
await act(async () => {
|
@@ -314,14 +314,14 @@ describe('Schedule', () => {
|
|
314
314
|
});
|
315
315
|
});
|
316
316
|
expect(at().value).toBe(newAtDaily);
|
317
|
-
expect(screen.getByText('Review
|
317
|
+
expect(screen.getByText('Review details').disabled).toBeFalsy();
|
318
318
|
|
319
319
|
fireEvent.click(screen.getByText('Daily'));
|
320
320
|
await act(async () => {
|
321
321
|
fireEvent.click(screen.getByText('Hourly'));
|
322
322
|
});
|
323
323
|
|
324
|
-
expect(screen.getByText('Review
|
324
|
+
expect(screen.getByText('Review details').disabled).toBeTruthy();
|
325
325
|
const newMinutes = '6';
|
326
326
|
const atHourly = screen.getByLabelText('repeat-at-minute-typeahead');
|
327
327
|
expect(atHourly.value).toBe('');
|
@@ -331,7 +331,7 @@ describe('Schedule', () => {
|
|
331
331
|
await act(async () => {
|
332
332
|
fireEvent.click(screen.getByText(newMinutes));
|
333
333
|
});
|
334
|
-
expect(screen.getByText('Review
|
334
|
+
expect(screen.getByText('Review details').disabled).toBeFalsy();
|
335
335
|
expect(atHourly.value).toBe(newMinutes);
|
336
336
|
});
|
337
337
|
it('should show invalid error on start date after end', async () => {
|
@@ -353,7 +353,7 @@ describe('Schedule', () => {
|
|
353
353
|
expect(
|
354
354
|
screen.queryAllByText('End time needs to be after start time')
|
355
355
|
).toHaveLength(0);
|
356
|
-
expect(screen.getByText('Review
|
356
|
+
expect(screen.getByText('Review details').disabled).toBeFalsy();
|
357
357
|
await act(async () => {
|
358
358
|
await fireEvent.change(startsDateField, {
|
359
359
|
target: { value: '2020/10/15' },
|
@@ -368,7 +368,7 @@ describe('Schedule', () => {
|
|
368
368
|
screen.queryAllByText('End time needs to be after start time')
|
369
369
|
).toHaveLength(1);
|
370
370
|
|
371
|
-
expect(screen.getByText('Review
|
371
|
+
expect(screen.getByText('Review details').disabled).toBeTruthy();
|
372
372
|
});
|
373
373
|
it('purpose and ends should be disabled when no reaccurence ', async () => {
|
374
374
|
render(
|
@@ -93,7 +93,6 @@ export const SearchSelect = ({
|
|
93
93
|
autoSearch(value || '');
|
94
94
|
}}
|
95
95
|
placeholderText={placeholderText}
|
96
|
-
onFilter={() => null} // https://github.com/patternfly/patternfly-react/issues/6321
|
97
96
|
typeAheadAriaLabel={`${name} typeahead input`}
|
98
97
|
>
|
99
98
|
{selectOptions}
|
data/webpack/JobWizard/submit.js
CHANGED
@@ -23,6 +23,7 @@ export const submit = ({
|
|
23
23
|
purpose,
|
24
24
|
} = scheduleValue;
|
25
25
|
const {
|
26
|
+
sshUser,
|
26
27
|
effectiveUserValue,
|
27
28
|
effectiveUserPassword,
|
28
29
|
description,
|
@@ -64,6 +65,7 @@ export const submit = ({
|
|
64
65
|
: 'dynamic_query',
|
65
66
|
randomized_ordering: isRandomizedOrdering,
|
66
67
|
inputs: { ...templateValues, ...advancedTemplateValues },
|
68
|
+
ssh_user: sshUser,
|
67
69
|
ssh: {
|
68
70
|
effective_user: effectiveUserValue,
|
69
71
|
effective_user_password: effectiveUserPassword,
|
@@ -0,0 +1 @@
|
|
1
|
+
export const decodeId = whatever => whatever;
|
data/webpack/global_index.js
CHANGED
@@ -1,14 +1,8 @@
|
|
1
1
|
import { registerRoutes } from 'foremanReact/routes/RoutingService';
|
2
2
|
import routes from './Routes/routes';
|
3
|
-
import fillregistrationAdvanced from './react_app/extend/fillregistrationAdvanced';
|
4
|
-
import fillRecentJobsCard from './react_app/extend/fillRecentJobsCard';
|
5
|
-
import fillFeaturesDropdown from './react_app/extend/fillRexFeaturesDropdown';
|
6
|
-
import fillKebabItems from './react_app/extend/fillKebabItems';
|
7
3
|
import registerReducers from './react_app/extend/reducers';
|
4
|
+
import registerFills from './react_app/extend/Fills';
|
8
5
|
|
9
6
|
registerReducers();
|
10
7
|
registerRoutes('foreman_remote_execution', routes);
|
11
|
-
|
12
|
-
fillRecentJobsCard();
|
13
|
-
fillregistrationAdvanced();
|
14
|
-
fillKebabItems();
|
8
|
+
registerFills();
|
@@ -52,7 +52,7 @@ const FeaturesDropdown = ({ hostId }) => {
|
|
52
52
|
toggle={
|
53
53
|
<DropdownToggle
|
54
54
|
splitButtonItems={scheduleJob}
|
55
|
-
toggleVariant="
|
55
|
+
toggleVariant="secondary"
|
56
56
|
onToggle={() => setIsOpen(prev => !prev)}
|
57
57
|
isDisabled={status === STATUS.PENDING}
|
58
58
|
splitButtonVariant="action"
|
@@ -13,7 +13,12 @@ const HostKebabItems = () => {
|
|
13
13
|
|
14
14
|
if (!consoleUrl) return null;
|
15
15
|
return (
|
16
|
-
<DropdownItem
|
16
|
+
<DropdownItem
|
17
|
+
icon={<CodeIcon />}
|
18
|
+
href={consoleUrl}
|
19
|
+
target="_blank"
|
20
|
+
rel="noreferrer"
|
21
|
+
>
|
17
22
|
{__('Web Console')}
|
18
23
|
</DropdownItem>
|
19
24
|
);
|
@@ -28,7 +28,7 @@ const RecentJobsCard = ({ hostDetails: { name, id } }) => {
|
|
28
28
|
href={foremanUrl(`${JOB_BASE_URL}${name}`)}
|
29
29
|
key="link-to-all"
|
30
30
|
>
|
31
|
-
{__('View
|
31
|
+
{__('View all jobs')}
|
32
32
|
</DropdownItem>,
|
33
33
|
<DropdownItem
|
34
34
|
href={foremanUrl(
|
@@ -36,19 +36,19 @@ const RecentJobsCard = ({ hostDetails: { name, id } }) => {
|
|
36
36
|
)}
|
37
37
|
key="link-to-finished"
|
38
38
|
>
|
39
|
-
{__('View
|
39
|
+
{__('View finished jobs')}
|
40
40
|
</DropdownItem>,
|
41
41
|
<DropdownItem
|
42
42
|
href={foremanUrl(`${JOB_BASE_URL}${name}+and+status+%3D+running`)}
|
43
43
|
key="link-to-running"
|
44
44
|
>
|
45
|
-
{__('View
|
45
|
+
{__('View running jobs')}
|
46
46
|
</DropdownItem>,
|
47
47
|
<DropdownItem
|
48
48
|
href={foremanUrl(`${JOB_BASE_URL}${name}+and+status+%3D+queued`)}
|
49
49
|
key="link-to-scheduled"
|
50
50
|
>
|
51
|
-
{__('View
|
51
|
+
{__('View scheduled jobs')}
|
52
52
|
</DropdownItem>,
|
53
53
|
]}
|
54
54
|
>
|