foreman_remote_execution 17.0.0 → 17.2.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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/api/v2/job_invocations_controller.rb +24 -2
  3. data/app/controllers/job_invocations_controller.rb +1 -28
  4. data/app/models/remote_execution_provider.rb +11 -0
  5. data/app/models/script_execution_provider.rb +8 -5
  6. data/app/views/api/v2/job_invocations/hosts.json.rabl +8 -0
  7. data/config/routes.rb +0 -1
  8. data/lib/foreman_remote_execution/plugin.rb +11 -2
  9. data/lib/foreman_remote_execution/version.rb +1 -1
  10. data/test/functional/api/v2/job_invocations_controller_test.rb +46 -7
  11. data/test/unit/remote_execution_provider_test.rb +56 -0
  12. data/webpack/JobInvocationDetail/CheckboxesActions.js +1 -0
  13. data/webpack/JobInvocationDetail/DropdownFilter.js +1 -0
  14. data/webpack/JobInvocationDetail/JobInvocationActions.js +10 -4
  15. data/webpack/JobInvocationDetail/JobInvocationConstants.js +3 -2
  16. data/webpack/JobInvocationDetail/JobInvocationDetail.scss +5 -3
  17. data/webpack/JobInvocationDetail/JobInvocationHostTable.js +24 -12
  18. data/webpack/JobInvocationDetail/JobInvocationSelectors.js +0 -5
  19. data/webpack/JobInvocationDetail/JobInvocationSystemStatusChart.js +5 -2
  20. data/webpack/JobInvocationDetail/TemplateInvocation.js +7 -1
  21. data/webpack/JobInvocationDetail/TemplateInvocationComponents/OutputCodeBlock.js +2 -1
  22. data/webpack/JobInvocationDetail/TemplateInvocationComponents/TemplateActionButtons.js +17 -6
  23. data/webpack/JobInvocationDetail/TemplateInvocationComponents/index.scss +0 -1
  24. data/webpack/JobInvocationDetail/__tests__/JobInvocationHostTablePolling.test.js +33 -0
  25. data/webpack/JobInvocationDetail/__tests__/JobInvocationPolling.test.js +17 -12
  26. data/webpack/JobInvocationDetail/index.js +14 -1
  27. data/webpack/JobWizard/JobWizard.js +5 -1
  28. data/webpack/JobWizard/JobWizard.scss +21 -0
  29. data/webpack/JobWizard/JobWizardConstants.js +10 -1
  30. data/webpack/JobWizard/steps/Schedule/RepeatHour.js +4 -2
  31. data/webpack/JobWizard/steps/Schedule/RepeatWeek.js +4 -2
  32. data/webpack/JobWizard/steps/Schedule/ScheduleRecurring.js +2 -1
  33. data/webpack/JobWizard/steps/Schedule/ScheduleType.js +4 -1
  34. data/webpack/JobWizard/steps/form/DateTimePicker.js +12 -6
  35. data/webpack/JobWizard/steps/form/FormHelpers.js +5 -4
  36. data/webpack/react_app/components/FeaturesDropdown/index.scss +2 -0
  37. data/webpack/react_app/components/TargetingHosts/TargetingHostsLabelsRow.scss +6 -2
  38. data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHostsPage.test.js.snap +1 -1
  39. data/webpack/react_app/components/TargetingHosts/index.js +2 -1
  40. data/webpack/react_app/redux/actions/jobInvocations/index.js +3 -2
  41. metadata +2 -3
  42. data/webpack/__mocks__/foremanReact/constants.js +0 -25
@@ -1,5 +1,4 @@
1
1
  .jobs-table-action-item a {
2
-
3
2
  color: inherit;
4
3
  text-decoration: none;
5
4
 
@@ -311,6 +311,39 @@ describe('JobInvocationHostTable polling', () => {
311
311
  expect(hostsCalls.length).toBeGreaterThan(callsAfterFilterChange);
312
312
  });
313
313
 
314
+ it('sends include_permissions only on the first request', () => {
315
+ renderTable();
316
+
317
+ expect(hostsCalls).toHaveLength(1);
318
+ expect(hostsCalls[0].params.include_permissions).toBe(true);
319
+
320
+ act(() => {
321
+ flushPendingCallbacks();
322
+ });
323
+
324
+ act(() => {
325
+ jest.advanceTimersByTime(5000);
326
+ });
327
+
328
+ act(() => {
329
+ flushPendingCallbacks();
330
+ });
331
+
332
+ expect(hostsCalls).toHaveLength(2);
333
+ expect(hostsCalls[1].params.include_permissions).toBeUndefined();
334
+
335
+ act(() => {
336
+ jest.advanceTimersByTime(5000);
337
+ });
338
+
339
+ act(() => {
340
+ flushPendingCallbacks();
341
+ });
342
+
343
+ expect(hostsCalls).toHaveLength(3);
344
+ expect(hostsCalls[2].params.include_permissions).toBeUndefined();
345
+ });
346
+
314
347
  it('stops polling on API error', () => {
315
348
  apiGetSpy.mockRestore();
316
349
  setupErrorMock();
@@ -5,7 +5,10 @@ import {
5
5
  getJobInvocation,
6
6
  stopJobInvocationPolling,
7
7
  } from '../JobInvocationActions';
8
- import { JOB_INVOCATION_KEY } from '../JobInvocationConstants';
8
+ import {
9
+ JOB_INVOCATION_KEY,
10
+ AUTO_REFRESH_INTERVAL_MS,
11
+ } from '../JobInvocationConstants';
9
12
 
10
13
  jest.useFakeTimers();
11
14
 
@@ -66,22 +69,24 @@ describe('job invocation polling', () => {
66
69
  store.dispatch(getJobInvocation(url, ref));
67
70
  expect(apiGetSpy).toHaveBeenCalledTimes(1);
68
71
 
69
- jest.advanceTimersByTime(5000);
72
+ jest.advanceTimersByTime(AUTO_REFRESH_INTERVAL_MS);
70
73
  expect(apiGetSpy).toHaveBeenCalledTimes(2);
71
74
  });
72
75
 
73
- it('includes include_permissions=true in every poll call', () => {
76
+ it('does not include include_permissions on subsequent poll calls', () => {
74
77
  setupGetMock(runningData);
75
78
  const store = makeStore();
76
79
 
77
80
  store.dispatch(getJobInvocation(url, ref));
78
- jest.advanceTimersByTime(5000);
81
+ jest.advanceTimersByTime(AUTO_REFRESH_INTERVAL_MS);
79
82
 
80
83
  expect(apiGetSpy).toHaveBeenCalledTimes(2);
81
84
  expect(apiGetSpy.mock.calls[1][0].params).toMatchObject({
82
85
  include_hosts: false,
83
- include_permissions: true,
84
86
  });
87
+ expect(apiGetSpy.mock.calls[1][0].params).not.toHaveProperty(
88
+ 'include_permissions'
89
+ );
85
90
  });
86
91
 
87
92
  it('stops polling when job succeeds', () => {
@@ -91,7 +96,7 @@ describe('job invocation polling', () => {
91
96
  store.dispatch(getJobInvocation(url, ref));
92
97
  expect(apiGetSpy).toHaveBeenCalledTimes(1);
93
98
 
94
- jest.advanceTimersByTime(5000);
99
+ jest.advanceTimersByTime(AUTO_REFRESH_INTERVAL_MS);
95
100
  expect(apiGetSpy).toHaveBeenCalledTimes(1);
96
101
  });
97
102
 
@@ -100,7 +105,7 @@ describe('job invocation polling', () => {
100
105
  const store = makeStore();
101
106
 
102
107
  store.dispatch(getJobInvocation(url, ref));
103
- jest.advanceTimersByTime(5000);
108
+ jest.advanceTimersByTime(AUTO_REFRESH_INTERVAL_MS);
104
109
 
105
110
  expect(apiGetSpy).toHaveBeenCalledTimes(1);
106
111
  });
@@ -110,7 +115,7 @@ describe('job invocation polling', () => {
110
115
  const store = makeStore();
111
116
 
112
117
  store.dispatch(getJobInvocation(url, ref));
113
- jest.advanceTimersByTime(5000);
118
+ jest.advanceTimersByTime(AUTO_REFRESH_INTERVAL_MS);
114
119
 
115
120
  expect(apiGetSpy).toHaveBeenCalledTimes(1);
116
121
  });
@@ -125,7 +130,7 @@ describe('job invocation polling', () => {
125
130
  const store = makeStore();
126
131
 
127
132
  store.dispatch(getJobInvocation(url, ref));
128
- jest.advanceTimersByTime(5000);
133
+ jest.advanceTimersByTime(AUTO_REFRESH_INTERVAL_MS);
129
134
 
130
135
  expect(apiGetSpy).toHaveBeenCalledTimes(1);
131
136
  });
@@ -138,7 +143,7 @@ describe('job invocation polling', () => {
138
143
  expect(apiGetSpy).toHaveBeenCalledTimes(1);
139
144
 
140
145
  stopJobInvocationPolling(ref);
141
- jest.advanceTimersByTime(5000);
146
+ jest.advanceTimersByTime(AUTO_REFRESH_INTERVAL_MS);
142
147
 
143
148
  expect(apiGetSpy).toHaveBeenCalledTimes(1);
144
149
  });
@@ -154,7 +159,7 @@ describe('job invocation polling', () => {
154
159
  store.dispatch(getJobInvocation(url, ref));
155
160
  expect(apiGetSpy).toHaveBeenCalledTimes(2);
156
161
 
157
- jest.advanceTimersByTime(5000);
162
+ jest.advanceTimersByTime(AUTO_REFRESH_INTERVAL_MS);
158
163
 
159
164
  // Only the second poll chain fires (first was cancelled)
160
165
  expect(apiGetSpy).toHaveBeenCalledTimes(3);
@@ -165,7 +170,7 @@ describe('job invocation polling', () => {
165
170
  const store = makeStore();
166
171
 
167
172
  store.dispatch(getJobInvocation(url, ref));
168
- jest.advanceTimersByTime(15000);
173
+ jest.advanceTimersByTime(AUTO_REFRESH_INTERVAL_MS * 3);
169
174
 
170
175
  expect(apiGetSpy).toHaveBeenCalledTimes(4);
171
176
  });
@@ -54,6 +54,11 @@ const JobInvocationDetailPage = ({
54
54
  } = items;
55
55
  const finished = isJobFinished(statusLabel);
56
56
  const pollTimeoutRef = useRef({ timeoutId: null, cancel: () => {} });
57
+ const permissionsRef = useRef(null);
58
+ if (items.permissions && !permissionsRef.current) {
59
+ permissionsRef.current = items.permissions;
60
+ }
61
+ const permissions = items.permissions || permissionsRef.current;
57
62
  const jobInvocationApiStatus = useSelector(state =>
58
63
  selectAPIStatus(state, JOB_INVOCATION_KEY)
59
64
  );
@@ -87,6 +92,11 @@ const JobInvocationDetailPage = ({
87
92
  };
88
93
  }, [dispatch, id]);
89
94
 
95
+ const dataWithPermissions = useMemo(() => ({ ...items, permissions }), [
96
+ items,
97
+ permissions,
98
+ ]);
99
+
90
100
  const apiFailed = jobInvocationApiStatus === API_STATUS.ERROR;
91
101
 
92
102
  const backendErrorMessage = useMemo(() => {
@@ -143,7 +153,10 @@ const JobInvocationDetailPage = ({
143
153
  breadcrumbOptions={breadcrumbOptions}
144
154
  toolbarButtons={
145
155
  items.id !== undefined && (
146
- <JobInvocationToolbarButtons jobId={id} data={items} />
156
+ <JobInvocationToolbarButtons
157
+ jobId={id}
158
+ data={dataWithPermissions}
159
+ />
147
160
  )
148
161
  }
149
162
  searchable={false}
@@ -18,6 +18,7 @@ import {
18
18
  WIZARD_TITLES,
19
19
  SCHEDULE_TYPES,
20
20
  initialScheduleState,
21
+ STARTS_ERROR_CHECK_INTERVAL_MS,
21
22
  } from './JobWizardConstants';
22
23
  import {
23
24
  selectTemplateError,
@@ -237,7 +238,10 @@ export const JobWizard = ({ rerunData }) => {
237
238
  }
238
239
  };
239
240
  updateStartsError();
240
- const interval = setInterval(updateStartsError, 5000);
241
+ const interval = setInterval(
242
+ updateStartsError,
243
+ STARTS_ERROR_CHECK_INTERVAL_MS
244
+ );
241
245
 
242
246
  return () => {
243
247
  interval && clearInterval(interval);
@@ -1,5 +1,6 @@
1
1
  .job-wizard {
2
2
  font-size: var(--pf-v5-global--FontSize--md);
3
+
3
4
  .wizard-title {
4
5
  margin-bottom: 25px;
5
6
  }
@@ -15,6 +16,7 @@
15
16
  var(--pf-v5-cwizard__toggle--ZIndex) + 1
16
17
  ); // So the select box can be shown above the wizard footer and navigation toggle
17
18
  }
19
+
18
20
  .pf-v5-c-wizard__main-body {
19
21
  @media (max-width: 600px) {
20
22
  max-width: 100%;
@@ -41,6 +43,7 @@
41
43
  .pf-v5-c-chip-group.pf-m-category {
42
44
  margin-bottom: 10px;
43
45
  }
46
+
44
47
  .pf-v5-c-select__toggle-typeahead {
45
48
  border: 0px;
46
49
  }
@@ -50,62 +53,76 @@
50
53
  flex-wrap: nowrap;
51
54
  }
52
55
  }
56
+
53
57
  .foreman-search-field {
54
58
  width: 100%;
55
59
  }
56
60
  }
61
+
57
62
  input[type='radio'],
58
63
  input[type='checkbox'] {
59
64
  margin: 0;
60
65
  }
66
+
61
67
  .schedule-tab {
62
68
  #repeat-on-weekly {
63
69
  display: grid;
64
70
  grid-template-columns: repeat(7, 1fr);
65
71
  }
72
+
66
73
  .pf-v5-l-grid {
67
74
  gap: var(--pf-v5-cform--GridGap);
68
75
  }
76
+
69
77
  #repeat-on-hourly {
70
78
  max-height: 300px;
71
79
  overflow: scroll;
72
80
  }
81
+
73
82
  .schedule-radio label {
74
83
  width: 100%;
75
84
  }
85
+
76
86
  .schedule-radio input {
77
87
  align-self: center;
78
88
  }
89
+
79
90
  .schedule-radio-repeat-text {
80
91
  width: 100px;
81
92
  display: inline-block;
82
93
  margin-right: 5px;
83
94
  align-self: center;
84
95
  }
96
+
85
97
  .schedule-radio-title {
86
98
  width: 80px;
87
99
  display: inline-block;
88
100
  align-self: center;
89
101
  }
102
+
90
103
  .schedule-radio-occurences {
91
104
  display: inline-block;
92
105
  align-self: center;
93
106
  }
107
+
94
108
  .schedule-radio-wrapper {
95
109
  display: flex;
96
110
  }
97
111
  }
112
+
98
113
  .future-schedule-tab {
99
114
  .clear-datetime-button {
100
115
  margin-left: 10px;
101
116
  align-self: center;
102
117
  font-size: var(--pf-v5-global--FontSize--md);
103
118
  }
119
+
104
120
  .pf-v5-c-form__group-control {
105
121
  display: flex;
106
122
  flex-wrap: wrap;
107
123
  }
108
124
  }
125
+
109
126
  .pf-v5-c-date-picker {
110
127
  vertical-align: top;
111
128
  }
@@ -119,10 +136,12 @@
119
136
  // overwriting bootstrap/_forms.scss margin: 4px 0 0;
120
137
  margin: 0;
121
138
  }
139
+
122
140
  textarea {
123
141
  min-height: 40px;
124
142
  min-width: 100px;
125
143
  }
144
+
126
145
  .pf-v5-c-modal-box {
127
146
  width: auto;
128
147
  }
@@ -132,9 +151,11 @@
132
151
  margin-left: 10px;
133
152
  }
134
153
  }
154
+
135
155
  .pf-v5-c-radio__body {
136
156
  font-size: var(--pf-v5-cradio__label--FontSize);
137
157
  }
158
+
138
159
  .reset-default {
139
160
  padding-bottom: 0;
140
161
  }
@@ -1,6 +1,9 @@
1
1
  import { translate as __ } from 'foremanReact/common/I18n';
2
2
  import { foremanUrl } from 'foremanReact/common/helpers';
3
- import { getControllerSearchProps } from 'foremanReact/constants';
3
+ import {
4
+ getControllerSearchProps,
5
+ MS_PER_SECOND,
6
+ } from 'foremanReact/constants';
4
7
 
5
8
  export const JOB_TEMPLATES = 'JOB_TEMPLATES';
6
9
  export const JOB_CATEGORIES = 'JOB_CATEGORIES';
@@ -78,3 +81,9 @@ export const HOST_IDS = 'HOST_IDS';
78
81
  export const REX_FEATURE = 'REX_FEATURE';
79
82
 
80
83
  export const JOB_API_KEY = 'JOB_API_KEY';
84
+
85
+ export const SUNDAY_BASE_YEAR = 2017;
86
+ export const DEFAULT_MINUTE_OPTIONS = [0, 15, 30, 45];
87
+ export const STARTS_ERROR_CHECK_INTERVAL_MS = 5000;
88
+ export const DATE_PADDING_SLICE = -2;
89
+ export const DEBOUNCE_INPUT_MS = MS_PER_SECOND;
@@ -12,13 +12,15 @@ import {
12
12
  SelectVariant,
13
13
  } from '@patternfly/react-core/deprecated';
14
14
  import { translate as __ } from 'foremanReact/common/I18n';
15
+ import { MINUTES_PER_HOUR } from 'foremanReact/constants';
15
16
  import { helpLabel } from '../form/FormHelpers';
17
+ import { DEFAULT_MINUTE_OPTIONS } from '../../JobWizardConstants';
16
18
 
17
19
  export const RepeatHour = ({ repeatData, setRepeatData }) => {
18
20
  const isValidMinute = newMinute =>
19
21
  Number.isInteger(parseInt(newMinute, 10)) &&
20
22
  newMinute >= 0 &&
21
- newMinute < 60;
23
+ newMinute < MINUTES_PER_HOUR;
22
24
 
23
25
  const { minute } = repeatData;
24
26
  useEffect(() => {
@@ -27,7 +29,7 @@ export const RepeatHour = ({ repeatData, setRepeatData }) => {
27
29
  }
28
30
  }, [minute, setRepeatData]);
29
31
  const [minuteOpen, setMinuteOpen] = useState(false);
30
- const [options, setOptions] = useState([0, 15, 30, 45]);
32
+ const [options, setOptions] = useState(DEFAULT_MINUTE_OPTIONS);
31
33
  const [isAlertOpen, setIsAlertOpen] = useState(false);
32
34
  return (
33
35
  <FormGroup
@@ -2,15 +2,17 @@ import React, { useEffect } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import { FormGroup, Checkbox } from '@patternfly/react-core';
4
4
  import { translate as __, documentLocale } from 'foremanReact/common/I18n';
5
+ import { DAYS_PER_WEEK } from 'foremanReact/constants';
5
6
  import { RepeatDaily } from './RepeatDaily';
6
7
  import { noop } from '../../../helpers';
8
+ import { SUNDAY_BASE_YEAR } from '../../JobWizardConstants';
7
9
 
8
10
  export const getWeekDays = () => {
9
11
  const locale = documentLocale().replace(/-/g, '_');
10
- const baseDate = new Date(Date.UTC(2017, 0, 1)); // just a Sunday
12
+ const baseDate = new Date(Date.UTC(SUNDAY_BASE_YEAR, 0, 1)); // just a Sunday
11
13
  const weekDays = [];
12
14
  const formatOptions = { weekday: 'short', timeZone: 'UTC' };
13
- for (let i = 0; i < 7; i++) {
15
+ for (let i = 0; i < DAYS_PER_WEEK; i++) {
14
16
  try {
15
17
  weekDays.push(baseDate.toLocaleDateString(locale, formatOptions));
16
18
  } catch {
@@ -12,6 +12,7 @@ import {
12
12
  } from '@patternfly/react-core';
13
13
  import { ExclamationCircleIcon } from '@patternfly/react-icons';
14
14
  import { translate as __ } from 'foremanReact/common/I18n';
15
+ import { MS_PER_MINUTE } from 'foremanReact/constants';
15
16
  import { RepeatOn } from './RepeatOn';
16
17
  import { SCHEDULE_TYPES } from '../../JobWizardConstants';
17
18
  import { PurposeField } from './PurposeField';
@@ -114,7 +115,7 @@ export const ScheduleRecurring = ({
114
115
  setScheduleValue(current => ({
115
116
  ...current,
116
117
  startsAt: new Date(
117
- new Date().getTime() + 60000
118
+ new Date().getTime() + MS_PER_MINUTE
118
119
  ).toISOString(), // 1 minute in the future
119
120
  isFuture: true,
120
121
  }))
@@ -2,6 +2,7 @@ import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import { Form, FormGroup, Radio, Divider } from '@patternfly/react-core';
4
4
  import { translate as __ } from 'foremanReact/common/I18n';
5
+ import { MS_PER_MINUTE } from 'foremanReact/constants';
5
6
  import {
6
7
  WIZARD_TITLES,
7
8
  SCHEDULE_TYPES,
@@ -49,7 +50,9 @@ export const ScheduleType = ({
49
50
  onChange={() => {
50
51
  setScheduleValue(current => ({
51
52
  ...current,
52
- startsAt: new Date(new Date().getTime() + 60000).toISOString(), // 1 minute in the future
53
+ startsAt: new Date(
54
+ new Date().getTime() + MS_PER_MINUTE
55
+ ).toISOString(), // 1 minute in the future
53
56
  scheduleType: SCHEDULE_TYPES.FUTURE,
54
57
  repeatType: repeatTypes.noRepeat,
55
58
  }));
@@ -7,13 +7,19 @@ import {
7
7
  } from '@patternfly/react-core';
8
8
  import { debounce } from 'lodash';
9
9
  import { translate as __, documentLocale } from 'foremanReact/common/I18n';
10
+ import {
11
+ DATE_PADDING_SLICE,
12
+ DEBOUNCE_INPUT_MS,
13
+ } from '../../JobWizardConstants';
10
14
 
11
15
  const formatDateTime = d =>
12
16
  `${d.getFullYear()}-${`0${d.getMonth() + 1}`.slice(
13
- -2
14
- )}-${`0${d.getDate()}`.slice(-2)} ${`0${d.getHours()}`.slice(
15
- -2
16
- )}:${`0${d.getMinutes()}`.slice(-2)}:${`0${d.getSeconds()}`.slice(-2)}`;
17
+ DATE_PADDING_SLICE
18
+ )}-${`0${d.getDate()}`.slice(DATE_PADDING_SLICE)} ${`0${d.getHours()}`.slice(
19
+ DATE_PADDING_SLICE
20
+ )}:${`0${d.getMinutes()}`.slice(
21
+ DATE_PADDING_SLICE
22
+ )}:${`0${d.getSeconds()}`.slice(DATE_PADDING_SLICE)}`;
17
23
 
18
24
  export const DateTimePicker = ({
19
25
  dateTime,
@@ -103,7 +109,7 @@ export const DateTimePicker = ({
103
109
  aria-label={`${ariaLabel} datepicker`}
104
110
  value={formattedDate}
105
111
  placeholder="yyyy/mm/dd"
106
- onChange={debounce(onDateChange, 1000, {
112
+ onChange={debounce(onDateChange, DEBOUNCE_INPUT_MS, {
107
113
  leading: false,
108
114
  trailing: true,
109
115
  })}
@@ -123,7 +129,7 @@ export const DateTimePicker = ({
123
129
  time={dateTime ? dateObject.toString() : ''}
124
130
  inputProps={dateTime ? {} : { value: '' }}
125
131
  placeholder={includeSeconds ? 'hh:mm:ss' : 'hh:mm'}
126
- onChange={debounce(onTimeChange, 1000, {
132
+ onChange={debounce(onTimeChange, DEBOUNCE_INPUT_MS, {
127
133
  leading: false,
128
134
  trailing: true,
129
135
  })}
@@ -8,16 +8,17 @@ export const helpLabel = (text, id) => {
8
8
  if (!text) return null;
9
9
  return (
10
10
  <Popover id={`${id}-help`} bodyContent={text} aria-label="help-text">
11
- <button
12
- type="button"
11
+ <Button
12
+ ouiaId={`${id}-help-button`}
13
+ variant="plain"
13
14
  aria-label={__('open-help-tooltip-button')}
14
15
  onClick={e => e.preventDefault()}
15
- className="pf-v5-c-form__group-label-help"
16
+ isInline
16
17
  >
17
18
  <Icon isInline>
18
19
  <HelpIcon />
19
20
  </Icon>
20
- </button>
21
+ </Button>
21
22
  </Popover>
22
23
  );
23
24
  };
@@ -1,7 +1,9 @@
1
1
  #schedule-a-job-dropdown ul.pf-v5-c-dropdown__menu {
2
2
  padding-left: 0;
3
+
3
4
  li {
4
5
  display: unset;
6
+
5
7
  a {
6
8
  font-size: 16px;
7
9
  color: var(--pf-v5-cdropdown__menu-item--Color);
@@ -3,24 +3,28 @@
3
3
  .tasks-labels-row {
4
4
  margin: 0;
5
5
  padding: 10px;
6
+
6
7
  .title {
7
8
  font-weight: 600;
8
9
  font-size: 13px;
9
10
  }
11
+
10
12
  .label {
11
13
  font-size: 100%;
12
-
13
14
  margin-left: 5px;
14
15
  margin-right: 5px;
16
+
15
17
  a {
16
18
  padding-left: 10px;
17
19
  }
18
20
  }
21
+
19
22
  .pficon-close {
20
23
  color: $color-pf-white;
21
24
  }
25
+
22
26
  .compound-label-pf {
23
- margin-left: 0;
24
27
  margin: 10px;
28
+ margin-left: 0;
25
29
  }
26
30
  }
@@ -25,7 +25,7 @@ exports[`TargetingHostsPage renders 1`] = `
25
25
  },
26
26
  "bookmarks": Object {
27
27
  "canCreate": true,
28
- "documentationUrl": "4.1.5Searching",
28
+ "documentationUrl": "/links/manual/4.1.5Searching",
29
29
  "id": "targeting_hosts_search",
30
30
  "url": "/api/bookmarks",
31
31
  },
@@ -7,6 +7,7 @@ import {
7
7
  withInterval,
8
8
  stopInterval,
9
9
  } from 'foremanReact/redux/middlewares/IntervalMiddleware';
10
+ import { MS_PER_SECOND } from 'foremanReact/constants';
10
11
 
11
12
  import {
12
13
  selectItems,
@@ -100,7 +101,7 @@ const WrappedTargetingHosts = () => {
100
101
  dispatch(stopInterval(TARGETING_HOSTS));
101
102
  },
102
103
  }),
103
- 1000
104
+ MS_PER_SECOND
104
105
  ),
105
106
  [dispatch]
106
107
  );
@@ -1,5 +1,6 @@
1
1
  // eslint-disable-next-line import/no-extraneous-dependencies
2
2
  import API from 'foremanReact/API';
3
+ import { HTTP_STATUS_CODES, MS_PER_SECOND } from 'foremanReact/constants';
3
4
 
4
5
  import {
5
6
  JOB_INVOCATIONS_GET_JOB_INVOCATIONS,
@@ -7,7 +8,7 @@ import {
7
8
  JOB_INVOCATIONS_JOB_FINISHED,
8
9
  } from '../../consts';
9
10
 
10
- const defaultJobInvocationsPollingInterval = 1000;
11
+ const defaultJobInvocationsPollingInterval = MS_PER_SECOND;
11
12
  const jobInvocationsInterval =
12
13
  process.env.JOB_INVOCATIONS_POLLING || defaultJobInvocationsPollingInterval;
13
14
 
@@ -32,7 +33,7 @@ const getJobInvocations = url => async (dispatch, getState) => {
32
33
  }
33
34
 
34
35
  function onGetJobInvocationsFailed(error) {
35
- if (error.response.status === 401) {
36
+ if (error.response.status === HTTP_STATUS_CODES.UNAUTHORIZED) {
36
37
  window.location.replace('/users/login');
37
38
  }
38
39
  }
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foreman_remote_execution
3
3
  version: !ruby/object:Gem::Version
4
- version: 17.0.0
4
+ version: 17.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Foreman Remote Execution team
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-08-03 00:00:00.000000000 Z
10
+ date: 2026-08-24 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: deface
@@ -485,7 +485,6 @@ files:
485
485
  - webpack/__mocks__/foremanReact/components/Pagination.js
486
486
  - webpack/__mocks__/foremanReact/components/ToastsList/index.js
487
487
  - webpack/__mocks__/foremanReact/components/common/ActionButtons/ActionButtons.js
488
- - webpack/__mocks__/foremanReact/constants.js
489
488
  - webpack/__mocks__/foremanReact/history.js
490
489
  - webpack/__mocks__/foremanReact/redux/API/APIActions.js
491
490
  - webpack/__mocks__/foremanReact/redux/API/APIConstants.js
@@ -1,25 +0,0 @@
1
- export const STATUS = {
2
- PENDING: 'PENDING',
3
- RESOLVED: 'RESOLVED',
4
- ERROR: 'ERROR',
5
- };
6
-
7
- export const getControllerSearchProps = (
8
- controller,
9
- id = 'searchBar',
10
- canCreate = true
11
- ) => ({
12
- controller,
13
- autocomplete: {
14
- id,
15
- searchQuery: '',
16
- url: `${controller}/auto_complete_search`,
17
- useKeyShortcuts: true,
18
- },
19
- bookmarks: {
20
- id,
21
- url: '/api/bookmarks',
22
- canCreate,
23
- documentationUrl: `4.1.5Searching`,
24
- },
25
- });