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.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby_ci.yml +2 -0
  3. data/app/controllers/api/v2/job_invocations_controller.rb +1 -0
  4. data/app/helpers/hosts_extensions_helper.rb +62 -0
  5. data/app/lib/actions/remote_execution/run_host_job.rb +4 -0
  6. data/app/lib/actions/remote_execution/run_hosts_job.rb +4 -0
  7. data/app/models/concerns/foreman_remote_execution/foreman_tasks_task_extensions.rb +6 -0
  8. data/app/models/host_status/execution_status.rb +2 -1
  9. data/app/models/job_invocation.rb +1 -1
  10. data/app/models/job_invocation_composer.rb +7 -3
  11. data/app/models/job_template.rb +6 -1
  12. data/app/models/remote_execution_provider.rb +4 -0
  13. data/app/models/ssh_execution_provider.rb +3 -3
  14. data/app/models/template_invocation.rb +2 -0
  15. data/app/services/remote_execution_proxy_selector.rb +1 -1
  16. data/app/views/api/v2/job_invocations/base.json.rabl +1 -1
  17. data/app/views/job_invocations/_card_target_hosts.html.erb +8 -0
  18. data/app/views/job_invocations/_form.html.erb +2 -0
  19. data/app/views/overrides/subnets/_rex_tab_pane.html.erb +1 -1
  20. data/db/migrate/20220331112719_add_ssh_user_to_job_invocation.rb +5 -0
  21. data/lib/foreman_remote_execution/engine.rb +6 -3
  22. data/lib/foreman_remote_execution/version.rb +1 -1
  23. data/locale/action_names.rb +3 -3
  24. data/locale/de/foreman_remote_execution.po +23 -23
  25. data/locale/en/foreman_remote_execution.po +23 -23
  26. data/locale/en_GB/foreman_remote_execution.po +23 -23
  27. data/locale/es/foreman_remote_execution.po +23 -23
  28. data/locale/foreman_remote_execution.pot +64 -66
  29. data/locale/fr/foreman_remote_execution.po +23 -23
  30. data/locale/ja/foreman_remote_execution.po +23 -23
  31. data/locale/ko/foreman_remote_execution.po +23 -23
  32. data/locale/pt_BR/foreman_remote_execution.po +23 -23
  33. data/locale/ru/foreman_remote_execution.po +23 -23
  34. data/locale/zh_CN/foreman_remote_execution.po +23 -23
  35. data/locale/zh_TW/foreman_remote_execution.po +23 -23
  36. data/test/unit/api_params_test.rb +33 -0
  37. data/test/unit/remote_execution_provider_test.rb +26 -0
  38. data/webpack/JobWizard/JobWizardConstants.js +2 -2
  39. data/webpack/JobWizard/__tests__/fixtures.js +8 -4
  40. data/webpack/JobWizard/steps/AdvancedFields/AdvancedFields.js +9 -0
  41. data/webpack/JobWizard/steps/AdvancedFields/Fields.js +21 -0
  42. data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +7 -2
  43. data/webpack/JobWizard/steps/HostsAndInputs/SelectGQL.js +2 -1
  44. data/webpack/JobWizard/steps/HostsAndInputs/SelectedChips.js +1 -1
  45. data/webpack/JobWizard/steps/HostsAndInputs/buildHostQuery.js +1 -1
  46. data/webpack/JobWizard/steps/HostsAndInputs/hostgroups.gql +1 -0
  47. data/webpack/JobWizard/steps/HostsAndInputs/hosts.gql +1 -0
  48. data/webpack/JobWizard/steps/ReviewDetails/index.js +2 -1
  49. data/webpack/JobWizard/steps/Schedule/__tests__/Schedule.test.js +15 -15
  50. data/webpack/JobWizard/steps/form/SearchSelect.js +0 -1
  51. data/webpack/JobWizard/submit.js +2 -0
  52. data/webpack/__mocks__/foremanReact/common/globalIdHelpers.js +1 -0
  53. data/webpack/global_index.js +2 -8
  54. data/webpack/react_app/components/FeaturesDropdown/index.js +1 -1
  55. data/webpack/react_app/components/HostKebab/KebabItems.js +6 -1
  56. data/webpack/react_app/components/RecentJobsCard/RecentJobsCard.js +4 -4
  57. data/webpack/react_app/components/TargetingHosts/TargetingHostsConsts.js +1 -0
  58. data/webpack/react_app/components/TargetingHosts/TargetingHostsPage.js +8 -3
  59. data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHostsPage.test.js.snap +9 -1
  60. data/webpack/react_app/components/TargetingHosts/__tests__/fixtures.js +1 -4
  61. data/webpack/react_app/components/TargetingHosts/index.js +1 -0
  62. data/webpack/react_app/extend/Fills.js +48 -0
  63. metadata +9 -8
  64. data/app/helpers/concerns/foreman_remote_execution/hosts_helper_extensions.rb +0 -62
  65. data/webpack/react_app/extend/fillKebabItems.js +0 -11
  66. data/webpack/react_app/extend/fillRecentJobsCard.js +0 -11
  67. data/webpack/react_app/extend/fillRexFeaturesDropdown.js +0 -11
  68. 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 Details"
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 All Jobs"
1221
+ msgid "View all jobs"
1213
1222
  msgstr ""
1214
1223
 
1215
- msgid "View Finished Jobs"
1224
+ msgid "View finished jobs"
1216
1225
  msgstr ""
1217
1226
 
1218
- msgid "View Running Jobs"
1227
+ msgid "View running jobs"
1219
1228
  msgstr ""
1220
1229
 
1221
- msgid "View Scheduled Jobs"
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 Fields'),
22
+ advanced: __('Advanced fields'),
23
23
  schedule: __('Schedule'),
24
- review: __('Review Details'),
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: [{ name: 'host1' }, { name: 'host2' }, { name: 'host3' }],
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 Fields'));
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 Fields'));
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.name,
39
+ id: decodeId(node.id),
39
40
  name: node.name,
40
41
  })) || [],
41
42
  },
@@ -15,7 +15,7 @@ const SelectedChip = ({ selected, setSelected, categoryName }) => {
15
15
  {selected.map(({ name, id }, index) => (
16
16
  <Chip
17
17
  key={index}
18
- id={id}
18
+ id={`${categoryName}-${id}`}
19
19
  onClick={() => deleteItem(id)}
20
20
  closeBtnAriaLabel={`Close ${name}`}
21
21
  >
@@ -1,6 +1,6 @@
1
1
  export const buildHostQuery = (selected, search) => {
2
2
  const { hosts, hostCollections, hostGroups } = selected;
3
- const hostsSearch = `(name ^ (${hosts.map(({ id }) => id).join(',')}))`;
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(',')}))`;
@@ -2,6 +2,7 @@ query($search: String!) {
2
2
  hostgroups(first: 100, last: 100, search: $search) {
3
3
  totalCount
4
4
  nodes {
5
+ id
5
6
  name
6
7
  }
7
8
  }
@@ -2,6 +2,7 @@ query($search: String!) {
2
2
  hosts(first: 100, last: 100, search: $search) {
3
3
  totalCount
4
4
  nodes {
5
+ id
5
6
  name
6
7
  }
7
8
  }
@@ -72,7 +72,7 @@ const ReviewDetails = ({
72
72
  };
73
73
  const [isAdvancedShown, setIsAdvancedShown] = useState(false);
74
74
  const detailsFirstHalf = [
75
- { label: __('Job Category'), value: jobCategory },
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 Details').disabled).toBeFalsy();
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 Details').disabled).toBeTruthy();
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 Details').disabled).toBeFalsy();
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 Details').disabled).toBeTruthy();
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 Details').disabled).toBeTruthy();
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 Details').disabled).toBeFalsy();
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 Details').disabled).toBeTruthy();
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 Details').disabled).toBeFalsy();
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 Details').disabled).toBeFalsy();
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 Details').disabled).toBeTruthy();
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 Details').disabled).toBeFalsy();
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 Details').disabled).toBeTruthy();
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 Details').disabled).toBeFalsy();
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 Details').disabled).toBeFalsy();
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 Details').disabled).toBeTruthy();
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}
@@ -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;
@@ -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
- fillFeaturesDropdown();
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="primary"
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 icon={<CodeIcon />} href={consoleUrl}>
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 All Jobs')}
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 Finished Jobs')}
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 Running Jobs')}
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 Scheduled Jobs')}
51
+ {__('View scheduled jobs')}
52
52
  </DropdownItem>,
53
53
  ]}
54
54
  >
@@ -1 +1,2 @@
1
1
  export const TARGETING_HOSTS = 'TARGETING_HOSTS';
2
+ export const TARGETING_HOSTS_AUTOCOMPLETE = 'targeting_hosts_search';