foreman_remote_execution 16.0.4 → 16.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/controllers/api/v2/job_invocations_controller.rb +33 -6
- data/app/controllers/job_invocations_controller.rb +28 -1
- data/app/controllers/template_invocations_controller.rb +1 -1
- data/app/helpers/remote_execution_helper.rb +1 -1
- data/config/routes.rb +4 -2
- data/lib/foreman_remote_execution/engine.rb +9 -256
- data/lib/foreman_remote_execution/plugin.rb +240 -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 +327 -0
- data/webpack/JobInvocationDetail/{JobInvocationHostTableToolbar.js → DropdownFilter.js} +3 -6
- data/webpack/JobInvocationDetail/JobInvocationConstants.js +8 -9
- 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 +23 -28
- 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 +2 -4
- data/webpack/JobInvocationDetail/__tests__/TableToolbarActions.test.js +204 -0
- data/webpack/JobInvocationDetail/__tests__/TemplateInvocation.test.js +34 -28
- data/webpack/JobInvocationDetail/index.js +71 -41
- data/webpack/JobWizard/steps/HostsAndInputs/buildHostQuery.js +26 -7
- data/webpack/Routes/routes.js +1 -1
- 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
@@ -52,14 +52,26 @@
|
|
52
52
|
height: $chart_size;
|
53
53
|
}
|
54
54
|
}
|
55
|
+
|
55
56
|
.job-additional-info {
|
56
57
|
padding: 0;
|
57
58
|
margin-bottom: -10px;
|
58
59
|
}
|
60
|
+
|
59
61
|
.job-details-table-section {
|
60
62
|
section:nth-child(1) {
|
61
63
|
padding: 0;
|
62
64
|
}
|
65
|
+
|
66
|
+
.pf-v5-c-toolbar__group.pf-m-button-group {
|
67
|
+
display: flex;
|
68
|
+
align-items: center;
|
69
|
+
}
|
70
|
+
|
71
|
+
.open-all-button {
|
72
|
+
margin-left: 10px;
|
73
|
+
margin-right: 15px;
|
74
|
+
}
|
63
75
|
}
|
64
76
|
|
65
77
|
.template-invocation {
|
@@ -122,3 +134,9 @@
|
|
122
134
|
margin-bottom: 10px;
|
123
135
|
}
|
124
136
|
}
|
137
|
+
|
138
|
+
.open-all-modal .pf-v5-c-modal-box__title-text {
|
139
|
+
white-space: normal;
|
140
|
+
text-overflow: unset;
|
141
|
+
word-break: break-word;
|
142
|
+
}
|
@@ -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
|
+
};
|
@@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
|
|
2
2
|
import React, { useEffect, useState } from 'react';
|
3
3
|
import { useDispatch, useSelector } from 'react-redux';
|
4
4
|
import { Button, Split, SplitItem } from '@patternfly/react-core';
|
5
|
+
import { UndoIcon } from '@patternfly/react-icons';
|
5
6
|
import {
|
6
7
|
Dropdown,
|
7
8
|
DropdownItem,
|
@@ -11,7 +12,6 @@ import {
|
|
11
12
|
} from '@patternfly/react-core/deprecated';
|
12
13
|
import { translate as __ } from 'foremanReact/common/I18n';
|
13
14
|
import { foremanUrl } from 'foremanReact/common/helpers';
|
14
|
-
import { STATUS as APIStatus } from 'foremanReact/constants';
|
15
15
|
import { get } from 'foremanReact/redux/API';
|
16
16
|
import {
|
17
17
|
cancelJob,
|
@@ -23,14 +23,12 @@ import {
|
|
23
23
|
GET_REPORT_TEMPLATES,
|
24
24
|
GET_REPORT_TEMPLATE_INPUTS,
|
25
25
|
} from './JobInvocationConstants';
|
26
|
-
import {
|
26
|
+
import {
|
27
|
+
selectTaskCancelable,
|
28
|
+
selectHasPermission,
|
29
|
+
} from './JobInvocationSelectors';
|
27
30
|
|
28
|
-
const JobInvocationToolbarButtons = ({
|
29
|
-
jobId,
|
30
|
-
data,
|
31
|
-
currentPermissions,
|
32
|
-
permissionsStatus,
|
33
|
-
}) => {
|
31
|
+
const JobInvocationToolbarButtons = ({ jobId, data }) => {
|
34
32
|
const { succeeded, failed, task, recurrence, permissions } = data;
|
35
33
|
const recurringEnabled = recurrence?.state === 'active';
|
36
34
|
const canViewForemanTasks = permissions
|
@@ -40,6 +38,8 @@ const JobInvocationToolbarButtons = ({
|
|
40
38
|
? permissions.edit_recurring_logics
|
41
39
|
: false;
|
42
40
|
const isTaskCancelable = useSelector(selectTaskCancelable);
|
41
|
+
const useHasPermission = permissionRequired =>
|
42
|
+
useSelector(selectHasPermission(permissionRequired));
|
43
43
|
const [isActionOpen, setIsActionOpen] = useState(false);
|
44
44
|
const [reportTemplateJobId, setReportTemplateJobId] = useState(undefined);
|
45
45
|
const [templateInputId, setTemplateInputId] = useState(undefined);
|
@@ -58,12 +58,6 @@ const JobInvocationToolbarButtons = ({
|
|
58
58
|
setIsActionOpen(false);
|
59
59
|
onActionFocus();
|
60
60
|
};
|
61
|
-
const hasPermission = permissionRequired =>
|
62
|
-
permissionsStatus === APIStatus.RESOLVED
|
63
|
-
? currentPermissions?.some(
|
64
|
-
permission => permission.name === permissionRequired
|
65
|
-
)
|
66
|
-
: false;
|
67
61
|
|
68
62
|
useEffect(() => {
|
69
63
|
dispatch(
|
@@ -142,7 +136,9 @@ const JobInvocationToolbarButtons = ({
|
|
142
136
|
ouiaId="rerun-succeeded-dropdown-item"
|
143
137
|
href={foremanUrl(`/job_invocations/${jobId}/rerun?succeeded_only=1`)}
|
144
138
|
key="rerun-succeeded"
|
145
|
-
isDisabled={
|
139
|
+
isDisabled={
|
140
|
+
!useHasPermission('create_job_invocations') || !(succeeded > 0)
|
141
|
+
}
|
146
142
|
description="Rerun job on successful hosts"
|
147
143
|
>
|
148
144
|
{__('Rerun successful')}
|
@@ -151,7 +147,7 @@ const JobInvocationToolbarButtons = ({
|
|
151
147
|
ouiaId="rerun-failed-dropdown-item"
|
152
148
|
href={foremanUrl(`/job_invocations/${jobId}/rerun?failed_only=1`)}
|
153
149
|
key="rerun-failed"
|
154
|
-
isDisabled={!(
|
150
|
+
isDisabled={!useHasPermission('create_job_invocations') || !(failed > 0)}
|
155
151
|
description="Rerun job on failed hosts"
|
156
152
|
>
|
157
153
|
{__('Rerun failed')}
|
@@ -171,7 +167,9 @@ const JobInvocationToolbarButtons = ({
|
|
171
167
|
onClick={() => dispatch(cancelJob(jobId, false))}
|
172
168
|
key="cancel"
|
173
169
|
component="button"
|
174
|
-
isDisabled={
|
170
|
+
isDisabled={
|
171
|
+
!useHasPermission('cancel_job_invocations') || !isTaskCancelable
|
172
|
+
}
|
175
173
|
description="Cancel job gracefully"
|
176
174
|
>
|
177
175
|
{__('Cancel')}
|
@@ -181,7 +179,9 @@ const JobInvocationToolbarButtons = ({
|
|
181
179
|
onClick={() => dispatch(cancelJob(jobId, true))}
|
182
180
|
key="abort"
|
183
181
|
component="button"
|
184
|
-
isDisabled={
|
182
|
+
isDisabled={
|
183
|
+
!useHasPermission('cancel_job_invocations') || !isTaskCancelable
|
184
|
+
}
|
185
185
|
description="Cancel job immediately"
|
186
186
|
>
|
187
187
|
{__('Abort')}
|
@@ -190,7 +190,8 @@ const JobInvocationToolbarButtons = ({
|
|
190
190
|
<DropdownSeparator ouiaId="dropdown-separator-2" key="separator-2" />,
|
191
191
|
<DropdownItem
|
192
192
|
ouiaId="legacy-ui-dropdown-item"
|
193
|
-
|
193
|
+
icon={<UndoIcon />}
|
194
|
+
href={`/legacy/job_invocations/${jobId}`}
|
194
195
|
key="legacy-ui"
|
195
196
|
>
|
196
197
|
{__('Legacy UI')}
|
@@ -210,9 +211,9 @@ const JobInvocationToolbarButtons = ({
|
|
210
211
|
)}
|
211
212
|
variant="secondary"
|
212
213
|
isDisabled={
|
214
|
+
!useHasPermission('generate_report_templates') ||
|
213
215
|
task?.state === STATUS.PENDING ||
|
214
|
-
templateInputId === undefined
|
215
|
-
!hasPermission('generate_report_templates')
|
216
|
+
templateInputId === undefined
|
216
217
|
}
|
217
218
|
>
|
218
219
|
{__(`Create report`)}
|
@@ -234,7 +235,7 @@ const JobInvocationToolbarButtons = ({
|
|
234
235
|
key="rerun"
|
235
236
|
href={foremanUrl(`/job_invocations/${jobId}/rerun`)}
|
236
237
|
variant="control"
|
237
|
-
isDisabled={!
|
238
|
+
isDisabled={!useHasPermission('create_job_invocations')}
|
238
239
|
>
|
239
240
|
{__(`Rerun all`)}
|
240
241
|
</Button>,
|
@@ -255,12 +256,6 @@ const JobInvocationToolbarButtons = ({
|
|
255
256
|
JobInvocationToolbarButtons.propTypes = {
|
256
257
|
jobId: PropTypes.string.isRequired,
|
257
258
|
data: PropTypes.object.isRequired,
|
258
|
-
currentPermissions: PropTypes.array,
|
259
|
-
permissionsStatus: PropTypes.string,
|
260
|
-
};
|
261
|
-
JobInvocationToolbarButtons.defaultProps = {
|
262
|
-
currentPermissions: undefined,
|
263
|
-
permissionsStatus: undefined,
|
264
259
|
};
|
265
260
|
|
266
261
|
export default JobInvocationToolbarButtons;
|