foreman_remote_execution 16.0.4 → 16.0.5
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/app/controllers/job_invocations_controller.rb +28 -1
- data/app/controllers/template_invocations_controller.rb +1 -1
- data/config/routes.rb +1 -0
- data/lib/foreman_remote_execution/engine.rb +9 -256
- data/lib/foreman_remote_execution/plugin.rb +246 -0
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/test/functional/api/v2/job_invocations_controller_test.rb +1 -1
- data/test/unit/job_invocation_report_template_test.rb +6 -6
- data/webpack/JobInvocationDetail/CheckboxesActions.js +196 -0
- data/webpack/JobInvocationDetail/{JobInvocationHostTableToolbar.js → DropdownFilter.js} +3 -6
- data/webpack/JobInvocationDetail/JobInvocationConstants.js +6 -6
- data/webpack/JobInvocationDetail/JobInvocationDetail.scss +18 -0
- data/webpack/JobInvocationDetail/JobInvocationHostTable.js +205 -89
- data/webpack/JobInvocationDetail/JobInvocationSelectors.js +30 -3
- data/webpack/JobInvocationDetail/JobInvocationToolbarButtons.js +20 -27
- data/webpack/JobInvocationDetail/OpenAllInvocationsModal.js +118 -0
- data/webpack/JobInvocationDetail/TemplateInvocation.js +54 -24
- data/webpack/JobInvocationDetail/TemplateInvocationComponents/TemplateActionButtons.js +8 -10
- data/webpack/JobInvocationDetail/TemplateInvocationPage.js +1 -1
- data/webpack/JobInvocationDetail/__tests__/MainInformation.test.js +1 -1
- data/webpack/JobInvocationDetail/__tests__/TableToolbarActions.test.js +202 -0
- data/webpack/JobInvocationDetail/__tests__/TemplateInvocation.test.js +34 -28
- data/webpack/JobInvocationDetail/index.js +61 -30
- data/webpack/JobWizard/steps/HostsAndInputs/buildHostQuery.js +26 -7
- data/webpack/react_app/components/TargetingHosts/TargetingHostsLabelsRow.scss +1 -1
- metadata +8 -6
- data/webpack/JobInvocationDetail/OpenAlInvocations.js +0 -111
- data/webpack/JobInvocationDetail/__tests__/OpenAlInvocations.test.js +0 -110
@@ -1,48 +1,58 @@
|
|
1
1
|
/* eslint-disable max-lines */
|
2
2
|
/* eslint-disable camelcase */
|
3
|
-
import PropTypes from 'prop-types';
|
4
|
-
import React, { useMemo, useEffect, useState } from 'react';
|
5
|
-
import { Icon } from 'patternfly-react';
|
6
|
-
import { translate as __ } from 'foremanReact/common/I18n';
|
7
|
-
import { FormattedMessage } from 'react-intl';
|
8
|
-
import { Tr, Td, Tbody, ExpandableRowContent } from '@patternfly/react-table';
|
9
3
|
import {
|
10
4
|
EmptyState,
|
11
|
-
EmptyStateVariant,
|
12
5
|
EmptyStateBody,
|
13
6
|
EmptyStateHeader,
|
7
|
+
EmptyStateVariant,
|
8
|
+
ToolbarItem,
|
14
9
|
} from '@patternfly/react-core';
|
10
|
+
import { ExpandableRowContent, Tbody, Td, Tr } from '@patternfly/react-table';
|
11
|
+
import { translate as __ } from 'foremanReact/common/I18n';
|
15
12
|
import { foremanUrl } from 'foremanReact/common/helpers';
|
16
13
|
import { useAPI } from 'foremanReact/common/hooks/API/APIHooks';
|
14
|
+
import { RowSelectTd } from 'foremanReact/components/HostsIndex/RowSelectTd';
|
15
|
+
import SelectAllCheckbox from 'foremanReact/components/PF4/TableIndexPage/Table/SelectAllCheckbox';
|
17
16
|
import { Table } from 'foremanReact/components/PF4/TableIndexPage/Table/Table';
|
18
|
-
import TableIndexPage from 'foremanReact/components/PF4/TableIndexPage/TableIndexPage';
|
19
|
-
import { useSetParamsAndApiAndSearch } from 'foremanReact/components/PF4/TableIndexPage/Table/TableIndexHooks';
|
20
17
|
import {
|
21
18
|
useBulkSelect,
|
22
19
|
useUrlParams,
|
23
20
|
} from 'foremanReact/components/PF4/TableIndexPage/Table/TableHooks';
|
21
|
+
import { getPageStats } from 'foremanReact/components/PF4/TableIndexPage/Table/helpers';
|
22
|
+
import TableIndexPage from 'foremanReact/components/PF4/TableIndexPage/TableIndexPage';
|
24
23
|
import { getControllerSearchProps } from 'foremanReact/constants';
|
24
|
+
import { Icon } from 'patternfly-react';
|
25
|
+
import PropTypes from 'prop-types';
|
26
|
+
import React, { useEffect, useMemo, useState } from 'react';
|
27
|
+
import { FormattedMessage } from 'react-intl';
|
28
|
+
import { useHistory } from 'react-router-dom';
|
29
|
+
import URI from 'urijs';
|
30
|
+
import { CheckboxesActions } from './CheckboxesActions';
|
31
|
+
import DropdownFilter from './DropdownFilter';
|
25
32
|
import Columns, {
|
26
33
|
JOB_INVOCATION_HOSTS,
|
34
|
+
MAX_HOSTS_API_SIZE,
|
27
35
|
STATUS_UPPERCASE,
|
36
|
+
LIST_TEMPLATE_INVOCATIONS,
|
37
|
+
ALL_JOB_HOSTS,
|
28
38
|
} from './JobInvocationConstants';
|
39
|
+
import { PopupAlert } from './OpenAllInvocationsModal';
|
29
40
|
import { TemplateInvocation } from './TemplateInvocation';
|
30
|
-
import { OpenAlInvocations, PopupAlert } from './OpenAlInvocations';
|
31
41
|
import { RowActions } from './TemplateInvocationComponents/TemplateActionButtons';
|
32
|
-
import JobInvocationHostTableToolbar from './JobInvocationHostTableToolbar';
|
33
42
|
|
34
43
|
const JobInvocationHostTable = ({
|
44
|
+
failedCount,
|
35
45
|
id,
|
36
|
-
targeting,
|
37
|
-
finished,
|
38
|
-
autoRefresh,
|
39
46
|
initialFilter,
|
40
47
|
onFilterUpdate,
|
48
|
+
targeting,
|
41
49
|
}) => {
|
42
50
|
const columns = Columns();
|
43
51
|
const columnNamesKeys = Object.keys(columns);
|
44
52
|
const apiOptions = { key: JOB_INVOCATION_HOSTS };
|
53
|
+
const history = useHistory();
|
45
54
|
const [selectedFilter, setSelectedFilter] = useState(initialFilter);
|
55
|
+
const [expandedHost, setExpandedHost] = useState([]);
|
46
56
|
|
47
57
|
useEffect(() => {
|
48
58
|
if (initialFilter !== selectedFilter) {
|
@@ -55,7 +65,9 @@ const JobInvocationHostTable = ({
|
|
55
65
|
searchParam: urlSearchQuery = '',
|
56
66
|
page: urlPage,
|
57
67
|
per_page: urlPerPage,
|
68
|
+
order: urlOrder,
|
58
69
|
} = useUrlParams();
|
70
|
+
|
59
71
|
const constructFilter = (
|
60
72
|
filter = selectedFilter,
|
61
73
|
search = urlSearchQuery
|
@@ -71,11 +83,21 @@ const JobInvocationHostTable = ({
|
|
71
83
|
.join(' AND ');
|
72
84
|
};
|
73
85
|
|
74
|
-
const
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
86
|
+
const defaultParams = useMemo(
|
87
|
+
() => ({
|
88
|
+
...(urlPage ? { page: Number(urlPage) } : {}),
|
89
|
+
...(urlPerPage ? { per_page: Number(urlPerPage) } : {}),
|
90
|
+
...(urlOrder ? { order: urlOrder } : {}),
|
91
|
+
}),
|
92
|
+
[urlPage, urlPerPage, urlOrder]
|
93
|
+
);
|
94
|
+
|
95
|
+
useAPI('get', `/job_invocations/${id}/hosts`, {
|
96
|
+
params: {
|
97
|
+
search: defaultParams.search,
|
98
|
+
},
|
99
|
+
key: LIST_TEMPLATE_INVOCATIONS,
|
100
|
+
});
|
79
101
|
const { response, status, setAPIOptions } = useAPI(
|
80
102
|
'get',
|
81
103
|
`/api/job_invocations/${id}/hosts`,
|
@@ -84,18 +106,93 @@ const JobInvocationHostTable = ({
|
|
84
106
|
}
|
85
107
|
);
|
86
108
|
|
87
|
-
const
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
109
|
+
const [allPagesResponse, setAllPagesResponse] = useState([]);
|
110
|
+
const apiAllParams = {
|
111
|
+
page: 1,
|
112
|
+
per_page: Math.min(response?.subtotal || 1, MAX_HOSTS_API_SIZE),
|
113
|
+
search: constructFilter(selectedFilter, urlSearchQuery),
|
114
|
+
};
|
92
115
|
|
93
|
-
const {
|
116
|
+
const { response: allResponse, setAPIOptions: setAllAPIOptions } = useAPI(
|
117
|
+
'get',
|
118
|
+
`/api/job_invocations/${id}/hosts`,
|
119
|
+
{
|
120
|
+
params: apiAllParams,
|
121
|
+
key: ALL_JOB_HOSTS,
|
122
|
+
}
|
123
|
+
);
|
124
|
+
|
125
|
+
useEffect(() => {
|
126
|
+
if (response?.subtotal) {
|
127
|
+
setAllAPIOptions(prevOptions => ({
|
128
|
+
...prevOptions,
|
129
|
+
params: apiAllParams,
|
130
|
+
}));
|
131
|
+
}
|
132
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
133
|
+
}, [response?.subtotal, selectedFilter, urlSearchQuery]);
|
134
|
+
|
135
|
+
useEffect(() => {
|
136
|
+
if (allResponse?.results) {
|
137
|
+
setAllPagesResponse(allResponse.results);
|
138
|
+
}
|
139
|
+
}, [allResponse]);
|
140
|
+
|
141
|
+
const {
|
142
|
+
updateSearchQuery: updateSearchQueryBulk,
|
143
|
+
fetchBulkParams,
|
144
|
+
inclusionSet,
|
145
|
+
exclusionSet,
|
146
|
+
...selectAllOptions
|
147
|
+
} = useBulkSelect({
|
148
|
+
results: response?.results,
|
149
|
+
metadata: {
|
150
|
+
total: response?.total,
|
151
|
+
page: response?.page,
|
152
|
+
selectable: response?.subtotal,
|
153
|
+
},
|
94
154
|
initialSearchQuery: urlSearchQuery,
|
95
155
|
});
|
96
|
-
|
97
|
-
|
98
|
-
|
156
|
+
|
157
|
+
const {
|
158
|
+
selectAll,
|
159
|
+
selectPage,
|
160
|
+
selectNone,
|
161
|
+
selectedCount,
|
162
|
+
selectOne,
|
163
|
+
areAllRowsOnPageSelected,
|
164
|
+
areAllRowsSelected,
|
165
|
+
isSelected,
|
166
|
+
} = selectAllOptions;
|
167
|
+
|
168
|
+
const allHostIds = allPagesResponse?.map(item => item.id) || [];
|
169
|
+
const selectedIds =
|
170
|
+
areAllRowsSelected() || exclusionSet.size > 0
|
171
|
+
? allHostIds.filter(hostId => !exclusionSet.has(hostId))
|
172
|
+
: Array.from(inclusionSet);
|
173
|
+
|
174
|
+
const { pageRowCount } = getPageStats({
|
175
|
+
total: response?.total || 0,
|
176
|
+
page: response?.page || urlPage || 1,
|
177
|
+
perPage: response?.per_page || urlPerPage || 0,
|
178
|
+
});
|
179
|
+
|
180
|
+
const selectionToolbar = (
|
181
|
+
<ToolbarItem key="selectAll">
|
182
|
+
<SelectAllCheckbox
|
183
|
+
{...{
|
184
|
+
selectAll,
|
185
|
+
selectPage,
|
186
|
+
selectNone,
|
187
|
+
selectedCount,
|
188
|
+
pageRowCount,
|
189
|
+
}}
|
190
|
+
totalCount={response?.total}
|
191
|
+
areAllRowsOnPageSelected={areAllRowsOnPageSelected()}
|
192
|
+
areAllRowsSelected={areAllRowsSelected()}
|
193
|
+
/>
|
194
|
+
</ToolbarItem>
|
195
|
+
);
|
99
196
|
|
100
197
|
const controller = 'hosts';
|
101
198
|
const memoDefaultSearchProps = useMemo(
|
@@ -106,50 +203,53 @@ const JobInvocationHostTable = ({
|
|
106
203
|
`/${controller}/auto_complete_search`
|
107
204
|
);
|
108
205
|
|
109
|
-
const wrapSetSelectedFilter =
|
110
|
-
|
111
|
-
|
112
|
-
if (prevOptions.params.search !== filterSearch) {
|
113
|
-
return {
|
114
|
-
...prevOptions,
|
115
|
-
params: {
|
116
|
-
...prevOptions.params,
|
117
|
-
search: filterSearch,
|
118
|
-
},
|
119
|
-
};
|
120
|
-
}
|
121
|
-
return prevOptions;
|
122
|
-
});
|
123
|
-
setSelectedFilter(filter);
|
124
|
-
onFilterUpdate(filter);
|
125
|
-
};
|
206
|
+
const wrapSetSelectedFilter = newFilter => {
|
207
|
+
setSelectedFilter(newFilter);
|
208
|
+
onFilterUpdate(newFilter);
|
126
209
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
params: {
|
133
|
-
...prevOptions.params,
|
134
|
-
},
|
135
|
-
}));
|
136
|
-
}
|
137
|
-
}, 5000);
|
138
|
-
|
139
|
-
return () => {
|
140
|
-
clearInterval(intervalId);
|
210
|
+
const filterSearch = constructFilter(newFilter, urlSearchQuery);
|
211
|
+
|
212
|
+
const newParams = {
|
213
|
+
...defaultParams,
|
214
|
+
page: 1,
|
141
215
|
};
|
142
|
-
|
216
|
+
|
217
|
+
if (filterSearch !== '') {
|
218
|
+
newParams.search = filterSearch;
|
219
|
+
}
|
220
|
+
|
221
|
+
setAPIOptions(prev => ({ ...prev, params: newParams }));
|
222
|
+
|
223
|
+
const urlSearchParams = new URLSearchParams(window.location.search);
|
224
|
+
urlSearchParams.set('page', '1');
|
225
|
+
history.push({ search: urlSearchParams.toString() });
|
226
|
+
};
|
143
227
|
|
144
228
|
const wrapSetAPIOptions = newAPIOptions => {
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
229
|
+
const newParams = newAPIOptions?.params ?? newAPIOptions ?? {};
|
230
|
+
|
231
|
+
const filterSearch = constructFilter(
|
232
|
+
selectedFilter,
|
233
|
+
newParams.search ?? urlSearchQuery
|
234
|
+
);
|
235
|
+
|
236
|
+
const mergedParams = {
|
237
|
+
...defaultParams,
|
238
|
+
...newParams,
|
239
|
+
};
|
240
|
+
|
241
|
+
if (filterSearch !== '') {
|
242
|
+
mergedParams.search = filterSearch;
|
243
|
+
} else if ('search' in mergedParams) {
|
244
|
+
delete mergedParams.search;
|
245
|
+
}
|
246
|
+
|
247
|
+
setAPIOptions(prev => ({ ...prev, params: mergedParams }));
|
248
|
+
|
249
|
+
const { search: _search, ...paramsForUrl } = mergedParams;
|
250
|
+
const uri = new URI();
|
251
|
+
uri.setSearch(paramsForUrl);
|
252
|
+
history.push({ search: uri.search() });
|
153
253
|
};
|
154
254
|
|
155
255
|
const combinedResponse = {
|
@@ -158,8 +258,8 @@ const JobInvocationHostTable = ({
|
|
158
258
|
can_create: false,
|
159
259
|
results: response?.results || [],
|
160
260
|
total: response?.total || 0,
|
161
|
-
per_page:
|
162
|
-
page:
|
261
|
+
per_page: defaultParams?.perPage,
|
262
|
+
page: defaultParams?.page,
|
163
263
|
subtotal: response?.subtotal || 0,
|
164
264
|
message: response?.message || 'error',
|
165
265
|
},
|
@@ -213,6 +313,7 @@ const JobInvocationHostTable = ({
|
|
213
313
|
return isExpanding ? [...otherExpandedHosts, host] : otherExpandedHosts;
|
214
314
|
});
|
215
315
|
const [showAlert, setShowAlert] = useState(false);
|
316
|
+
|
216
317
|
return (
|
217
318
|
<>
|
218
319
|
{showAlert && <PopupAlert setShowAlert={setShowAlert} />}
|
@@ -223,34 +324,44 @@ const JobInvocationHostTable = ({
|
|
223
324
|
controller="hosts"
|
224
325
|
creatable={false}
|
225
326
|
replacementResponse={combinedResponse}
|
226
|
-
updateSearchQuery={
|
327
|
+
updateSearchQuery={updateSearchQueryBulk}
|
227
328
|
customToolbarItems={[
|
228
|
-
<
|
229
|
-
|
230
|
-
results={results}
|
231
|
-
id={id}
|
232
|
-
key="OpenAlInvocations"
|
233
|
-
/>,
|
234
|
-
<JobInvocationHostTableToolbar
|
329
|
+
<DropdownFilter
|
330
|
+
key="dropdown-filter"
|
235
331
|
dropdownFilter={selectedFilter}
|
236
332
|
setDropdownFilter={wrapSetSelectedFilter}
|
237
|
-
|
333
|
+
/>,
|
334
|
+
<CheckboxesActions
|
335
|
+
bulkParams={selectedCount > 0 ? fetchBulkParams() : null}
|
336
|
+
selectedIds={selectedIds}
|
337
|
+
failedCount={failedCount}
|
338
|
+
jobID={id}
|
339
|
+
key="checkboxes-actions"
|
340
|
+
filter={selectedFilter}
|
238
341
|
/>,
|
239
342
|
]}
|
343
|
+
selectionToolbar={selectionToolbar}
|
240
344
|
>
|
241
345
|
<Table
|
242
346
|
ouiaId="job-invocation-hosts-table"
|
243
347
|
columns={columns}
|
244
348
|
customEmptyState={
|
245
|
-
status === STATUS_UPPERCASE.RESOLVED && !
|
349
|
+
status === STATUS_UPPERCASE.RESOLVED && !results.length
|
246
350
|
? customEmptyState
|
247
351
|
: null
|
248
352
|
}
|
249
|
-
params={
|
353
|
+
params={{
|
354
|
+
page: response?.page || Number(urlPage),
|
355
|
+
per_page: response?.per_page || Number(urlPerPage),
|
356
|
+
order: urlOrder,
|
357
|
+
}}
|
358
|
+
page={response?.page || Number(urlPage)}
|
359
|
+
perPage={response?.per_page || Number(urlPerPage)}
|
250
360
|
setParams={wrapSetAPIOptions}
|
251
361
|
itemCount={response?.subtotal}
|
252
|
-
results={
|
362
|
+
results={results}
|
253
363
|
url=""
|
364
|
+
showCheckboxes
|
254
365
|
refreshData={() => {}}
|
255
366
|
errorMessage={
|
256
367
|
status === STATUS_UPPERCASE.ERROR && response?.message
|
@@ -261,9 +372,9 @@ const JobInvocationHostTable = ({
|
|
261
372
|
isDeleteable={false}
|
262
373
|
childrenOutsideTbody
|
263
374
|
>
|
264
|
-
{results
|
265
|
-
<Tbody key={
|
266
|
-
<Tr ouiaId={`table-row-${
|
375
|
+
{results.map((result, rowIndex) => (
|
376
|
+
<Tbody key={result.id}>
|
377
|
+
<Tr ouiaId={`table-row-${result.id}`}>
|
267
378
|
<Td
|
268
379
|
expand={{
|
269
380
|
rowIndex,
|
@@ -273,6 +384,7 @@ const JobInvocationHostTable = ({
|
|
273
384
|
expandId: 'host-expandable',
|
274
385
|
}}
|
275
386
|
/>
|
387
|
+
<RowSelectTd rowData={result} {...{ selectOne, isSelected }} />
|
276
388
|
{columnNamesKeys.map(k => (
|
277
389
|
<Td key={k}>{columns[k].wrapper(result)}</Td>
|
278
390
|
))}
|
@@ -286,7 +398,7 @@ const JobInvocationHostTable = ({
|
|
286
398
|
>
|
287
399
|
<Td
|
288
400
|
dataLabel={`${result.id}-expandable-content`}
|
289
|
-
colSpan={columnNamesKeys.length}
|
401
|
+
colSpan={columnNamesKeys.length + 3}
|
290
402
|
>
|
291
403
|
<ExpandableRowContent>
|
292
404
|
{result.job_status === 'cancelled' ||
|
@@ -295,7 +407,12 @@ const JobInvocationHostTable = ({
|
|
295
407
|
{__('A task for this host has not been started')}
|
296
408
|
</div>
|
297
409
|
) : (
|
298
|
-
<TemplateInvocation
|
410
|
+
<TemplateInvocation
|
411
|
+
hostID={result.id}
|
412
|
+
jobID={id}
|
413
|
+
isInTableView
|
414
|
+
isExpanded={isHostExpanded(result.id)}
|
415
|
+
/>
|
299
416
|
)}
|
300
417
|
</ExpandableRowContent>
|
301
418
|
</Td>
|
@@ -311,8 +428,7 @@ const JobInvocationHostTable = ({
|
|
311
428
|
JobInvocationHostTable.propTypes = {
|
312
429
|
id: PropTypes.string.isRequired,
|
313
430
|
targeting: PropTypes.object.isRequired,
|
314
|
-
|
315
|
-
autoRefresh: PropTypes.bool.isRequired,
|
431
|
+
failedCount: PropTypes.number.isRequired,
|
316
432
|
initialFilter: PropTypes.string.isRequired,
|
317
433
|
onFilterUpdate: PropTypes.func,
|
318
434
|
};
|
@@ -1,8 +1,15 @@
|
|
1
|
-
|
1
|
+
/* eslint-disable camelcase */
|
2
|
+
import {
|
3
|
+
selectAPIResponse,
|
4
|
+
selectAPIStatus,
|
5
|
+
} from 'foremanReact/redux/API/APISelectors';
|
6
|
+
import { STATUS as APIStatus } from 'foremanReact/constants';
|
2
7
|
import {
|
3
8
|
JOB_INVOCATION_KEY,
|
4
9
|
GET_TASK,
|
5
10
|
GET_TEMPLATE_INVOCATION,
|
11
|
+
LIST_TEMPLATE_INVOCATIONS,
|
12
|
+
CURRENT_PERMISSIONS,
|
6
13
|
} from './JobInvocationConstants';
|
7
14
|
|
8
15
|
export const selectItems = state =>
|
@@ -13,5 +20,25 @@ export const selectTask = state => selectAPIResponse(state, GET_TASK);
|
|
13
20
|
export const selectTaskCancelable = state =>
|
14
21
|
selectTask(state).available_actions?.cancellable || false;
|
15
22
|
|
16
|
-
export const selectTemplateInvocation = state =>
|
17
|
-
selectAPIResponse(state, GET_TEMPLATE_INVOCATION);
|
23
|
+
export const selectTemplateInvocation = hostID => state =>
|
24
|
+
selectAPIResponse(state, `${GET_TEMPLATE_INVOCATION}_${hostID}`);
|
25
|
+
|
26
|
+
export const selectTemplateInvocationStatus = hostID => state =>
|
27
|
+
selectAPIStatus(state, `${GET_TEMPLATE_INVOCATION}_${hostID}`);
|
28
|
+
|
29
|
+
export const selectTemplateInvocationList = state =>
|
30
|
+
selectAPIResponse(state, LIST_TEMPLATE_INVOCATIONS)
|
31
|
+
?.template_invocations_task_by_hosts;
|
32
|
+
|
33
|
+
export const selectCurrentPermisions = state =>
|
34
|
+
selectAPIResponse(state, CURRENT_PERMISSIONS);
|
35
|
+
|
36
|
+
export const selectHasPermission = permissionRequired => state => {
|
37
|
+
const status = selectAPIStatus(state, CURRENT_PERMISSIONS);
|
38
|
+
const selectCurrentPermissions = selectCurrentPermisions(state)?.results;
|
39
|
+
return status === APIStatus.RESOLVED
|
40
|
+
? selectCurrentPermissions?.some(
|
41
|
+
permission => permission.name === permissionRequired
|
42
|
+
)
|
43
|
+
: false;
|
44
|
+
};
|
@@ -11,7 +11,6 @@ import {
|
|
11
11
|
} from '@patternfly/react-core/deprecated';
|
12
12
|
import { translate as __ } from 'foremanReact/common/I18n';
|
13
13
|
import { foremanUrl } from 'foremanReact/common/helpers';
|
14
|
-
import { STATUS as APIStatus } from 'foremanReact/constants';
|
15
14
|
import { get } from 'foremanReact/redux/API';
|
16
15
|
import {
|
17
16
|
cancelJob,
|
@@ -23,14 +22,12 @@ import {
|
|
23
22
|
GET_REPORT_TEMPLATES,
|
24
23
|
GET_REPORT_TEMPLATE_INPUTS,
|
25
24
|
} from './JobInvocationConstants';
|
26
|
-
import {
|
25
|
+
import {
|
26
|
+
selectTaskCancelable,
|
27
|
+
selectHasPermission,
|
28
|
+
} from './JobInvocationSelectors';
|
27
29
|
|
28
|
-
const JobInvocationToolbarButtons = ({
|
29
|
-
jobId,
|
30
|
-
data,
|
31
|
-
currentPermissions,
|
32
|
-
permissionsStatus,
|
33
|
-
}) => {
|
30
|
+
const JobInvocationToolbarButtons = ({ jobId, data }) => {
|
34
31
|
const { succeeded, failed, task, recurrence, permissions } = data;
|
35
32
|
const recurringEnabled = recurrence?.state === 'active';
|
36
33
|
const canViewForemanTasks = permissions
|
@@ -40,6 +37,8 @@ const JobInvocationToolbarButtons = ({
|
|
40
37
|
? permissions.edit_recurring_logics
|
41
38
|
: false;
|
42
39
|
const isTaskCancelable = useSelector(selectTaskCancelable);
|
40
|
+
const useHasPermission = permissionRequired =>
|
41
|
+
useSelector(selectHasPermission(permissionRequired));
|
43
42
|
const [isActionOpen, setIsActionOpen] = useState(false);
|
44
43
|
const [reportTemplateJobId, setReportTemplateJobId] = useState(undefined);
|
45
44
|
const [templateInputId, setTemplateInputId] = useState(undefined);
|
@@ -58,12 +57,6 @@ const JobInvocationToolbarButtons = ({
|
|
58
57
|
setIsActionOpen(false);
|
59
58
|
onActionFocus();
|
60
59
|
};
|
61
|
-
const hasPermission = permissionRequired =>
|
62
|
-
permissionsStatus === APIStatus.RESOLVED
|
63
|
-
? currentPermissions?.some(
|
64
|
-
permission => permission.name === permissionRequired
|
65
|
-
)
|
66
|
-
: false;
|
67
60
|
|
68
61
|
useEffect(() => {
|
69
62
|
dispatch(
|
@@ -142,7 +135,9 @@ const JobInvocationToolbarButtons = ({
|
|
142
135
|
ouiaId="rerun-succeeded-dropdown-item"
|
143
136
|
href={foremanUrl(`/job_invocations/${jobId}/rerun?succeeded_only=1`)}
|
144
137
|
key="rerun-succeeded"
|
145
|
-
isDisabled={
|
138
|
+
isDisabled={
|
139
|
+
!useHasPermission('create_job_invocations') || !(succeeded > 0)
|
140
|
+
}
|
146
141
|
description="Rerun job on successful hosts"
|
147
142
|
>
|
148
143
|
{__('Rerun successful')}
|
@@ -151,7 +146,7 @@ const JobInvocationToolbarButtons = ({
|
|
151
146
|
ouiaId="rerun-failed-dropdown-item"
|
152
147
|
href={foremanUrl(`/job_invocations/${jobId}/rerun?failed_only=1`)}
|
153
148
|
key="rerun-failed"
|
154
|
-
isDisabled={!(
|
149
|
+
isDisabled={!useHasPermission('create_job_invocations') || !(failed > 0)}
|
155
150
|
description="Rerun job on failed hosts"
|
156
151
|
>
|
157
152
|
{__('Rerun failed')}
|
@@ -171,7 +166,9 @@ const JobInvocationToolbarButtons = ({
|
|
171
166
|
onClick={() => dispatch(cancelJob(jobId, false))}
|
172
167
|
key="cancel"
|
173
168
|
component="button"
|
174
|
-
isDisabled={
|
169
|
+
isDisabled={
|
170
|
+
!useHasPermission('cancel_job_invocations') || !isTaskCancelable
|
171
|
+
}
|
175
172
|
description="Cancel job gracefully"
|
176
173
|
>
|
177
174
|
{__('Cancel')}
|
@@ -181,7 +178,9 @@ const JobInvocationToolbarButtons = ({
|
|
181
178
|
onClick={() => dispatch(cancelJob(jobId, true))}
|
182
179
|
key="abort"
|
183
180
|
component="button"
|
184
|
-
isDisabled={
|
181
|
+
isDisabled={
|
182
|
+
!useHasPermission('cancel_job_invocations') || !isTaskCancelable
|
183
|
+
}
|
185
184
|
description="Cancel job immediately"
|
186
185
|
>
|
187
186
|
{__('Abort')}
|
@@ -210,9 +209,9 @@ const JobInvocationToolbarButtons = ({
|
|
210
209
|
)}
|
211
210
|
variant="secondary"
|
212
211
|
isDisabled={
|
212
|
+
!useHasPermission('generate_report_templates') ||
|
213
213
|
task?.state === STATUS.PENDING ||
|
214
|
-
templateInputId === undefined
|
215
|
-
!hasPermission('generate_report_templates')
|
214
|
+
templateInputId === undefined
|
216
215
|
}
|
217
216
|
>
|
218
217
|
{__(`Create report`)}
|
@@ -234,7 +233,7 @@ const JobInvocationToolbarButtons = ({
|
|
234
233
|
key="rerun"
|
235
234
|
href={foremanUrl(`/job_invocations/${jobId}/rerun`)}
|
236
235
|
variant="control"
|
237
|
-
isDisabled={!
|
236
|
+
isDisabled={!useHasPermission('create_job_invocations')}
|
238
237
|
>
|
239
238
|
{__(`Rerun all`)}
|
240
239
|
</Button>,
|
@@ -255,12 +254,6 @@ const JobInvocationToolbarButtons = ({
|
|
255
254
|
JobInvocationToolbarButtons.propTypes = {
|
256
255
|
jobId: PropTypes.string.isRequired,
|
257
256
|
data: PropTypes.object.isRequired,
|
258
|
-
currentPermissions: PropTypes.array,
|
259
|
-
permissionsStatus: PropTypes.string,
|
260
|
-
};
|
261
|
-
JobInvocationToolbarButtons.defaultProps = {
|
262
|
-
currentPermissions: undefined,
|
263
|
-
permissionsStatus: undefined,
|
264
257
|
};
|
265
258
|
|
266
259
|
export default JobInvocationToolbarButtons;
|