foreman_remote_execution 16.0.3 → 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/api/v2/job_templates_controller.rb +2 -1
- data/app/controllers/cockpit_controller.rb +2 -2
- data/app/controllers/job_invocations_controller.rb +28 -1
- data/app/controllers/template_invocations_controller.rb +1 -1
- data/app/helpers/hosts_extensions_helper.rb +1 -1
- data/app/lib/actions/remote_execution/run_host_job.rb +5 -28
- data/app/models/concerns/foreman_remote_execution/host_extensions.rb +7 -1
- data/app/models/job_template.rb +5 -0
- data/app/models/{ssh_execution_provider.rb → script_execution_provider.rb} +2 -4
- data/config/initializers/inflections.rb +0 -1
- data/config/routes.rb +1 -0
- data/lib/foreman_remote_execution/engine.rb +10 -257
- 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/functional/api/v2/job_templates_controller_test.rb +29 -0
- data/test/unit/actions/run_host_job_test.rb +1 -1
- data/test/unit/job_invocation_report_template_test.rb +6 -6
- data/test/unit/remote_execution_provider_test.rb +19 -19
- data/webpack/JobInvocationDetail/CheckboxesActions.js +196 -0
- data/webpack/JobInvocationDetail/{JobInvocationHostTableToolbar.js → DropdownFilter.js} +3 -6
- data/webpack/JobInvocationDetail/JobInvocationConstants.js +7 -7
- data/webpack/JobInvocationDetail/JobInvocationDetail.scss +32 -0
- data/webpack/JobInvocationDetail/JobInvocationHostTable.js +221 -91
- data/webpack/JobInvocationDetail/JobInvocationSelectors.js +30 -3
- data/webpack/JobInvocationDetail/JobInvocationSystemStatusChart.js +8 -22
- 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 +64 -31
- data/webpack/JobWizard/steps/HostsAndInputs/buildHostQuery.js +26 -7
- data/webpack/react_app/components/TargetingHosts/TargetingHostsLabelsRow.scss +1 -1
- metadata +9 -7
- data/webpack/JobInvocationDetail/OpenAlInvocations.js +0 -111
- data/webpack/JobInvocationDetail/__tests__/OpenAlInvocations.test.js +0 -110
@@ -1,51 +1,73 @@
|
|
1
|
+
/* eslint-disable max-lines */
|
1
2
|
/* eslint-disable camelcase */
|
2
|
-
import PropTypes from 'prop-types';
|
3
|
-
import React, { useMemo, useEffect, useState } from 'react';
|
4
|
-
import { Icon } from 'patternfly-react';
|
5
|
-
import { translate as __ } from 'foremanReact/common/I18n';
|
6
|
-
import { FormattedMessage } from 'react-intl';
|
7
|
-
import { Tr, Td, Tbody, ExpandableRowContent } from '@patternfly/react-table';
|
8
3
|
import {
|
9
4
|
EmptyState,
|
10
|
-
EmptyStateVariant,
|
11
5
|
EmptyStateBody,
|
12
6
|
EmptyStateHeader,
|
7
|
+
EmptyStateVariant,
|
8
|
+
ToolbarItem,
|
13
9
|
} from '@patternfly/react-core';
|
10
|
+
import { ExpandableRowContent, Tbody, Td, Tr } from '@patternfly/react-table';
|
11
|
+
import { translate as __ } from 'foremanReact/common/I18n';
|
14
12
|
import { foremanUrl } from 'foremanReact/common/helpers';
|
15
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';
|
16
16
|
import { Table } from 'foremanReact/components/PF4/TableIndexPage/Table/Table';
|
17
|
-
import TableIndexPage from 'foremanReact/components/PF4/TableIndexPage/TableIndexPage';
|
18
|
-
import { useSetParamsAndApiAndSearch } from 'foremanReact/components/PF4/TableIndexPage/Table/TableIndexHooks';
|
19
17
|
import {
|
20
18
|
useBulkSelect,
|
21
19
|
useUrlParams,
|
22
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';
|
23
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';
|
24
32
|
import Columns, {
|
25
33
|
JOB_INVOCATION_HOSTS,
|
34
|
+
MAX_HOSTS_API_SIZE,
|
26
35
|
STATUS_UPPERCASE,
|
36
|
+
LIST_TEMPLATE_INVOCATIONS,
|
37
|
+
ALL_JOB_HOSTS,
|
27
38
|
} from './JobInvocationConstants';
|
39
|
+
import { PopupAlert } from './OpenAllInvocationsModal';
|
28
40
|
import { TemplateInvocation } from './TemplateInvocation';
|
29
|
-
import { OpenAlInvocations, PopupAlert } from './OpenAlInvocations';
|
30
41
|
import { RowActions } from './TemplateInvocationComponents/TemplateActionButtons';
|
31
|
-
import JobInvocationHostTableToolbar from './JobInvocationHostTableToolbar';
|
32
42
|
|
33
43
|
const JobInvocationHostTable = ({
|
44
|
+
failedCount,
|
34
45
|
id,
|
35
|
-
targeting,
|
36
|
-
finished,
|
37
|
-
autoRefresh,
|
38
46
|
initialFilter,
|
47
|
+
onFilterUpdate,
|
48
|
+
targeting,
|
39
49
|
}) => {
|
40
50
|
const columns = Columns();
|
41
51
|
const columnNamesKeys = Object.keys(columns);
|
42
52
|
const apiOptions = { key: JOB_INVOCATION_HOSTS };
|
43
|
-
const
|
53
|
+
const history = useHistory();
|
54
|
+
const [selectedFilter, setSelectedFilter] = useState(initialFilter);
|
55
|
+
const [expandedHost, setExpandedHost] = useState([]);
|
56
|
+
|
57
|
+
useEffect(() => {
|
58
|
+
if (initialFilter !== selectedFilter) {
|
59
|
+
wrapSetSelectedFilter(initialFilter);
|
60
|
+
}
|
61
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
62
|
+
}, [initialFilter]);
|
63
|
+
|
44
64
|
const {
|
45
65
|
searchParam: urlSearchQuery = '',
|
46
66
|
page: urlPage,
|
47
67
|
per_page: urlPerPage,
|
68
|
+
order: urlOrder,
|
48
69
|
} = useUrlParams();
|
70
|
+
|
49
71
|
const constructFilter = (
|
50
72
|
filter = selectedFilter,
|
51
73
|
search = urlSearchQuery
|
@@ -61,11 +83,21 @@ const JobInvocationHostTable = ({
|
|
61
83
|
.join(' AND ');
|
62
84
|
};
|
63
85
|
|
64
|
-
const
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
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
|
+
});
|
69
101
|
const { response, status, setAPIOptions } = useAPI(
|
70
102
|
'get',
|
71
103
|
`/api/job_invocations/${id}/hosts`,
|
@@ -74,18 +106,93 @@ const JobInvocationHostTable = ({
|
|
74
106
|
}
|
75
107
|
);
|
76
108
|
|
77
|
-
const
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
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
|
+
};
|
115
|
+
|
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]);
|
82
134
|
|
83
|
-
|
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
|
+
},
|
84
154
|
initialSearchQuery: urlSearchQuery,
|
85
155
|
});
|
86
|
-
|
87
|
-
|
88
|
-
|
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
|
+
);
|
89
196
|
|
90
197
|
const controller = 'hosts';
|
91
198
|
const memoDefaultSearchProps = useMemo(
|
@@ -96,49 +203,53 @@ const JobInvocationHostTable = ({
|
|
96
203
|
`/${controller}/auto_complete_search`
|
97
204
|
);
|
98
205
|
|
99
|
-
const wrapSetSelectedFilter =
|
100
|
-
|
101
|
-
|
102
|
-
if (prevOptions.params.search !== filterSearch) {
|
103
|
-
return {
|
104
|
-
...prevOptions,
|
105
|
-
params: {
|
106
|
-
...prevOptions.params,
|
107
|
-
search: filterSearch,
|
108
|
-
},
|
109
|
-
};
|
110
|
-
}
|
111
|
-
return prevOptions;
|
112
|
-
});
|
113
|
-
setSelectedFilter(filter);
|
114
|
-
};
|
206
|
+
const wrapSetSelectedFilter = newFilter => {
|
207
|
+
setSelectedFilter(newFilter);
|
208
|
+
onFilterUpdate(newFilter);
|
115
209
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
params: {
|
122
|
-
...prevOptions.params,
|
123
|
-
},
|
124
|
-
}));
|
125
|
-
}
|
126
|
-
}, 5000);
|
127
|
-
|
128
|
-
return () => {
|
129
|
-
clearInterval(intervalId);
|
210
|
+
const filterSearch = constructFilter(newFilter, urlSearchQuery);
|
211
|
+
|
212
|
+
const newParams = {
|
213
|
+
...defaultParams,
|
214
|
+
page: 1,
|
130
215
|
};
|
131
|
-
|
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
|
+
};
|
132
227
|
|
133
228
|
const wrapSetAPIOptions = newAPIOptions => {
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
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() });
|
142
253
|
};
|
143
254
|
|
144
255
|
const combinedResponse = {
|
@@ -147,8 +258,8 @@ const JobInvocationHostTable = ({
|
|
147
258
|
can_create: false,
|
148
259
|
results: response?.results || [],
|
149
260
|
total: response?.total || 0,
|
150
|
-
per_page:
|
151
|
-
page:
|
261
|
+
per_page: defaultParams?.perPage,
|
262
|
+
page: defaultParams?.page,
|
152
263
|
subtotal: response?.subtotal || 0,
|
153
264
|
message: response?.message || 'error',
|
154
265
|
},
|
@@ -202,6 +313,7 @@ const JobInvocationHostTable = ({
|
|
202
313
|
return isExpanding ? [...otherExpandedHosts, host] : otherExpandedHosts;
|
203
314
|
});
|
204
315
|
const [showAlert, setShowAlert] = useState(false);
|
316
|
+
|
205
317
|
return (
|
206
318
|
<>
|
207
319
|
{showAlert && <PopupAlert setShowAlert={setShowAlert} />}
|
@@ -212,34 +324,44 @@ const JobInvocationHostTable = ({
|
|
212
324
|
controller="hosts"
|
213
325
|
creatable={false}
|
214
326
|
replacementResponse={combinedResponse}
|
215
|
-
updateSearchQuery={
|
327
|
+
updateSearchQuery={updateSearchQueryBulk}
|
216
328
|
customToolbarItems={[
|
217
|
-
<
|
218
|
-
|
219
|
-
results={results}
|
220
|
-
id={id}
|
221
|
-
key="OpenAlInvocations"
|
222
|
-
/>,
|
223
|
-
<JobInvocationHostTableToolbar
|
329
|
+
<DropdownFilter
|
330
|
+
key="dropdown-filter"
|
224
331
|
dropdownFilter={selectedFilter}
|
225
332
|
setDropdownFilter={wrapSetSelectedFilter}
|
226
|
-
|
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}
|
227
341
|
/>,
|
228
342
|
]}
|
343
|
+
selectionToolbar={selectionToolbar}
|
229
344
|
>
|
230
345
|
<Table
|
231
346
|
ouiaId="job-invocation-hosts-table"
|
232
347
|
columns={columns}
|
233
348
|
customEmptyState={
|
234
|
-
status === STATUS_UPPERCASE.RESOLVED && !
|
349
|
+
status === STATUS_UPPERCASE.RESOLVED && !results.length
|
235
350
|
? customEmptyState
|
236
351
|
: null
|
237
352
|
}
|
238
|
-
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)}
|
239
360
|
setParams={wrapSetAPIOptions}
|
240
361
|
itemCount={response?.subtotal}
|
241
|
-
results={
|
362
|
+
results={results}
|
242
363
|
url=""
|
364
|
+
showCheckboxes
|
243
365
|
refreshData={() => {}}
|
244
366
|
errorMessage={
|
245
367
|
status === STATUS_UPPERCASE.ERROR && response?.message
|
@@ -250,9 +372,9 @@ const JobInvocationHostTable = ({
|
|
250
372
|
isDeleteable={false}
|
251
373
|
childrenOutsideTbody
|
252
374
|
>
|
253
|
-
{results
|
254
|
-
<Tbody key={
|
255
|
-
<Tr ouiaId={`table-row-${
|
375
|
+
{results.map((result, rowIndex) => (
|
376
|
+
<Tbody key={result.id}>
|
377
|
+
<Tr ouiaId={`table-row-${result.id}`}>
|
256
378
|
<Td
|
257
379
|
expand={{
|
258
380
|
rowIndex,
|
@@ -262,7 +384,8 @@ const JobInvocationHostTable = ({
|
|
262
384
|
expandId: 'host-expandable',
|
263
385
|
}}
|
264
386
|
/>
|
265
|
-
{
|
387
|
+
<RowSelectTd rowData={result} {...{ selectOne, isSelected }} />
|
388
|
+
{columnNamesKeys.map(k => (
|
266
389
|
<Td key={k}>{columns[k].wrapper(result)}</Td>
|
267
390
|
))}
|
268
391
|
<Td isActionCell>
|
@@ -275,7 +398,7 @@ const JobInvocationHostTable = ({
|
|
275
398
|
>
|
276
399
|
<Td
|
277
400
|
dataLabel={`${result.id}-expandable-content`}
|
278
|
-
colSpan={columnNamesKeys.length +
|
401
|
+
colSpan={columnNamesKeys.length + 3}
|
279
402
|
>
|
280
403
|
<ExpandableRowContent>
|
281
404
|
{result.job_status === 'cancelled' ||
|
@@ -284,7 +407,12 @@ const JobInvocationHostTable = ({
|
|
284
407
|
{__('A task for this host has not been started')}
|
285
408
|
</div>
|
286
409
|
) : (
|
287
|
-
<TemplateInvocation
|
410
|
+
<TemplateInvocation
|
411
|
+
hostID={result.id}
|
412
|
+
jobID={id}
|
413
|
+
isInTableView
|
414
|
+
isExpanded={isHostExpanded(result.id)}
|
415
|
+
/>
|
288
416
|
)}
|
289
417
|
</ExpandableRowContent>
|
290
418
|
</Td>
|
@@ -300,11 +428,13 @@ const JobInvocationHostTable = ({
|
|
300
428
|
JobInvocationHostTable.propTypes = {
|
301
429
|
id: PropTypes.string.isRequired,
|
302
430
|
targeting: PropTypes.object.isRequired,
|
303
|
-
|
304
|
-
autoRefresh: PropTypes.bool.isRequired,
|
431
|
+
failedCount: PropTypes.number.isRequired,
|
305
432
|
initialFilter: PropTypes.string.isRequired,
|
433
|
+
onFilterUpdate: PropTypes.func,
|
306
434
|
};
|
307
435
|
|
308
|
-
JobInvocationHostTable.defaultProps = {
|
436
|
+
JobInvocationHostTable.defaultProps = {
|
437
|
+
onFilterUpdate: () => {},
|
438
|
+
};
|
309
439
|
|
310
440
|
export default JobInvocationHostTable;
|
@@ -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
|
+
};
|
@@ -53,7 +53,6 @@ const JobInvocationSystemStatusChart = ({
|
|
53
53
|
};
|
54
54
|
const chartSize = 105;
|
55
55
|
const [legendWidth, setLegendWidth] = useState(270);
|
56
|
-
const [cursor, setCursor] = useState('default');
|
57
56
|
|
58
57
|
// Calculates chart legend width based on its content
|
59
58
|
useEffect(() => {
|
@@ -79,7 +78,7 @@ const JobInvocationSystemStatusChart = ({
|
|
79
78
|
|
80
79
|
return (
|
81
80
|
<>
|
82
|
-
<FlexItem className="chart-donut"
|
81
|
+
<FlexItem className="chart-donut">
|
83
82
|
<ChartDonut
|
84
83
|
allowTooltip
|
85
84
|
constrainToVisibleArea
|
@@ -96,12 +95,6 @@ const JobInvocationSystemStatusChart = ({
|
|
96
95
|
target: 'data',
|
97
96
|
eventHandlers: {
|
98
97
|
onClick: onChartClick,
|
99
|
-
onMouseOver: () => {
|
100
|
-
setCursor('pointer');
|
101
|
-
},
|
102
|
-
onMouseOut: () => {
|
103
|
-
setCursor('default');
|
104
|
-
},
|
105
98
|
},
|
106
99
|
},
|
107
100
|
]}
|
@@ -109,7 +102,12 @@ const JobInvocationSystemStatusChart = ({
|
|
109
102
|
total > 0 ? chartData.map(d => d.color) : [emptyChartDonut.value]
|
110
103
|
}
|
111
104
|
labelComponent={
|
112
|
-
<ChartTooltip
|
105
|
+
<ChartTooltip
|
106
|
+
pointerLength={0}
|
107
|
+
renderInPortal={false}
|
108
|
+
constrainToVisibleArea
|
109
|
+
center={{ x: 15, y: 0 }}
|
110
|
+
/>
|
113
111
|
}
|
114
112
|
title={chartDonutTitle}
|
115
113
|
titleComponent={
|
@@ -133,7 +131,7 @@ const JobInvocationSystemStatusChart = ({
|
|
133
131
|
height={chartSize}
|
134
132
|
/>
|
135
133
|
</FlexItem>
|
136
|
-
<FlexItem className="chart-legend"
|
134
|
+
<FlexItem className="chart-legend">
|
137
135
|
<Text ouiaId="legend-title" className="legend-title">
|
138
136
|
{__('System status')}
|
139
137
|
</Text>
|
@@ -156,24 +154,12 @@ const JobInvocationSystemStatusChart = ({
|
|
156
154
|
target: 'data',
|
157
155
|
eventHandlers: {
|
158
156
|
onClick: onChartClick,
|
159
|
-
onMouseOver: () => {
|
160
|
-
setCursor('pointer');
|
161
|
-
},
|
162
|
-
onMouseOut: () => {
|
163
|
-
setCursor('default');
|
164
|
-
},
|
165
157
|
},
|
166
158
|
},
|
167
159
|
{
|
168
160
|
target: 'labels',
|
169
161
|
eventHandlers: {
|
170
162
|
onClick: onChartClick,
|
171
|
-
onMouseOver: () => {
|
172
|
-
setCursor('pointer');
|
173
|
-
},
|
174
|
-
onMouseOut: () => {
|
175
|
-
setCursor('default');
|
176
|
-
},
|
177
163
|
},
|
178
164
|
},
|
179
165
|
]}
|
@@ -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;
|