foreman_remote_execution 13.1.1 → 13.2.1
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 +1 -0
- data/app/models/job_invocation.rb +23 -0
- data/app/models/job_invocation_composer.rb +2 -0
- data/app/views/api/v2/job_invocations/base.json.rabl +3 -2
- data/app/views/templates/script/package_action.erb +18 -10
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/package.json +2 -1
- data/webpack/JobInvocationDetail/JobInvocationActions.js +134 -3
- data/webpack/JobInvocationDetail/JobInvocationConstants.js +14 -0
- data/webpack/JobInvocationDetail/JobInvocationDetail.scss +5 -2
- data/webpack/JobInvocationDetail/JobInvocationSelectors.js +5 -0
- data/webpack/JobInvocationDetail/JobInvocationSystemStatusChart.js +13 -9
- data/webpack/JobInvocationDetail/JobInvocationToolbarButtons.js +268 -0
- data/webpack/JobInvocationDetail/__tests__/MainInformation.test.js +259 -0
- data/webpack/JobInvocationDetail/__tests__/fixtures.js +117 -0
- data/webpack/JobInvocationDetail/index.js +58 -38
- data/webpack/JobWizard/JobWizard.scss +14 -5
- data/webpack/JobWizard/JobWizardPageRerun.js +15 -10
- data/webpack/JobWizard/steps/HostsAndInputs/index.js +4 -1
- data/webpack/__mocks__/foremanReact/components/BreadcrumbBar/index.js +4 -0
- data/webpack/__mocks__/foremanReact/components/Head/index.js +10 -0
- data/webpack/__mocks__/foremanReact/components/HostDetails/DetailsCard/DefaultLoaderEmptyState.js +8 -0
- data/webpack/__mocks__/foremanReact/components/ToastsList/index.js +3 -0
- data/webpack/__mocks__/foremanReact/redux/API/APIActions.js +21 -0
- data/webpack/__mocks__/foremanReact/redux/API/APIConstants.js +7 -0
- data/webpack/__mocks__/foremanReact/redux/API/index.js +14 -0
- data/webpack/__mocks__/foremanReact/redux/middlewares/IntervalMiddleware/index.js +9 -0
- metadata +12 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 473900c99e054678a879651060b7596221f32c7f2713d28efb472957b7ac73ed
|
4
|
+
data.tar.gz: 14e55ba559f96def212e56aad5087b3e171fceb0cae2209b2ba1ef0566bda620
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 715de4ec99b4f17450ec02cca4db12e7778001098f49c1a733fae18c84d72855dd59e8dc86c1a6edddd72856a26c345c1397b2e2c1a66f14b0e605497f59e747
|
7
|
+
data.tar.gz: 7dd731683f6f68d5c9f32c14b9411cbde8a65e0de38de249f691cd70447b2ff936bac8f7413b4edad8bb776ae0612ccbcb487650a20a16c35e37fc2ded3b4af2
|
@@ -139,6 +139,7 @@ module Api
|
|
139
139
|
api :POST, '/job_invocations/:id/rerun', N_('Rerun job on failed hosts')
|
140
140
|
param :id, :identifier, :required => true
|
141
141
|
param :failed_only, :bool
|
142
|
+
param :succeeded_only, :bool
|
142
143
|
def rerun
|
143
144
|
composer = JobInvocationComposer.from_job_invocation(@job_invocation, params)
|
144
145
|
if composer.rerun_possible?
|
@@ -194,6 +194,10 @@ class JobInvocation < ApplicationRecord
|
|
194
194
|
failed_hosts.pluck(:id)
|
195
195
|
end
|
196
196
|
|
197
|
+
def succeeded_host_ids
|
198
|
+
succeeded_hosts.pluck(:id)
|
199
|
+
end
|
200
|
+
|
197
201
|
def failed_hosts
|
198
202
|
base = targeting.hosts
|
199
203
|
if finished?
|
@@ -203,6 +207,15 @@ class JobInvocation < ApplicationRecord
|
|
203
207
|
end
|
204
208
|
end
|
205
209
|
|
210
|
+
def succeeded_hosts
|
211
|
+
base = targeting.hosts
|
212
|
+
if finished?
|
213
|
+
base.where.not(:id => not_succeeded_template_invocations.select(:host_id))
|
214
|
+
else
|
215
|
+
base.where(:id => succeeded_template_invocations.select(:host_id))
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
206
219
|
def total_hosts_count
|
207
220
|
count = _('N/A')
|
208
221
|
|
@@ -290,4 +303,14 @@ class JobInvocation < ApplicationRecord
|
|
290
303
|
results = [:cancelled, :failed].map { |state| TemplateInvocation::TaskResultMap.status_to_task_result(state) }.flatten
|
291
304
|
template_invocations.joins(:run_host_job_task).where.not(ForemanTasks::Task.table_name => { :result => results })
|
292
305
|
end
|
306
|
+
|
307
|
+
def succeeded_template_invocations
|
308
|
+
result = TemplateInvocation::TaskResultMap.status_to_task_result(:success)
|
309
|
+
template_invocations.joins(:run_host_job_task).where(ForemanTasks::Task.table_name => { :result => result })
|
310
|
+
end
|
311
|
+
|
312
|
+
def not_succeeded_template_invocations
|
313
|
+
result = TemplateInvocation::TaskResultMap.status_to_task_result(:success)
|
314
|
+
template_invocations.joins(:run_host_job_task).where.not(ForemanTasks::Task.table_name => { :result => result })
|
315
|
+
end
|
293
316
|
end
|
@@ -25,9 +25,10 @@ end
|
|
25
25
|
if params.key?(:include_permissions)
|
26
26
|
node :permissions do |invocation|
|
27
27
|
authorizer = Authorizer.new(User.current)
|
28
|
-
edit_job_templates_permission = Permission.where(name: "edit_job_templates", resource_type: "JobTemplate").first
|
29
28
|
{
|
30
|
-
"edit_job_templates" =>
|
29
|
+
"edit_job_templates" => authorizer.can?("edit_job_templates", invocation, false),
|
30
|
+
"view_foreman_tasks" => authorizer.can?("view_foreman_tasks", invocation.task, false),
|
31
|
+
"edit_recurring_logics" => authorizer.can?("edit_recurring_logics", invocation.recurring_logic, false),
|
31
32
|
}
|
32
33
|
end
|
33
34
|
end
|
@@ -118,19 +118,27 @@ handle_zypp_res_codes () {
|
|
118
118
|
end
|
119
119
|
end
|
120
120
|
-%>
|
121
|
-
|
122
|
-
|
123
|
-
|
121
|
+
run_cmd()
|
122
|
+
{
|
123
|
+
LANG=C apt-get -o APT::Get::Upgrade-Allow-New="true" -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" -y <%= input("options") %> <%= action %> <%= input("package") %> 2>&1
|
124
|
+
}
|
124
125
|
export DEBIAN_FRONTEND=noninteractive
|
125
126
|
apt-get -y update
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
127
|
+
if [ "<%= action %>" = "remove" ]; then
|
128
|
+
OUTFILE=$(mktemp)
|
129
|
+
trap "rm -f $OUTFILE" EXIT
|
130
|
+
run_cmd > $OUTFILE
|
131
|
+
RETVAL=$?
|
132
|
+
cat $OUTFILE
|
133
|
+
if grep -q "Unable to locate" $OUTFILE; then
|
134
|
+
RETVAL=0
|
135
|
+
fi
|
130
136
|
else
|
131
|
-
|
132
|
-
|
137
|
+
run_cmd
|
138
|
+
RETVAL=$?
|
133
139
|
fi
|
140
|
+
setReturnValue() { RETVAL=$1; return $RETVAL; }
|
141
|
+
setReturnValue $RETVAL
|
134
142
|
<% elsif package_manager == 'zypper' -%>
|
135
143
|
<%-
|
136
144
|
if action == "group install"
|
@@ -141,7 +149,7 @@ handle_zypp_res_codes () {
|
|
141
149
|
-%>
|
142
150
|
zypper refresh
|
143
151
|
zypper -n <%= action %> <%= input("options") %> <%= input("package") %>
|
144
|
-
handle_zypp_res_codes $?
|
152
|
+
handle_zypp_res_codes $? "<%= action %>"
|
145
153
|
<% end -%>
|
146
154
|
RETVAL=$?
|
147
155
|
[ $RETVAL -eq 0 ] || exit_with_message "Package action failed" $RETVAL
|
data/package.json
CHANGED
@@ -1,9 +1,19 @@
|
|
1
|
-
import {
|
1
|
+
import { translate as __, sprintf } from 'foremanReact/common/I18n';
|
2
|
+
import { foremanUrl } from 'foremanReact/common/helpers';
|
3
|
+
import { addToast } from 'foremanReact/components/ToastsList';
|
4
|
+
import { APIActions, get } from 'foremanReact/redux/API';
|
2
5
|
import {
|
3
|
-
withInterval,
|
4
6
|
stopInterval,
|
7
|
+
withInterval,
|
5
8
|
} from 'foremanReact/redux/middlewares/IntervalMiddleware';
|
6
|
-
import {
|
9
|
+
import {
|
10
|
+
CANCEL_JOB,
|
11
|
+
CANCEL_RECURRING_LOGIC,
|
12
|
+
CHANGE_ENABLED_RECURRING_LOGIC,
|
13
|
+
GET_TASK,
|
14
|
+
JOB_INVOCATION_KEY,
|
15
|
+
UPDATE_JOB,
|
16
|
+
} from './JobInvocationConstants';
|
7
17
|
|
8
18
|
export const getData = url => dispatch => {
|
9
19
|
const fetchData = withInterval(
|
@@ -20,3 +30,124 @@ export const getData = url => dispatch => {
|
|
20
30
|
|
21
31
|
dispatch(fetchData);
|
22
32
|
};
|
33
|
+
|
34
|
+
export const updateJob = jobId => dispatch => {
|
35
|
+
const url = foremanUrl(`/api/job_invocations/${jobId}`);
|
36
|
+
dispatch(
|
37
|
+
APIActions.get({
|
38
|
+
url,
|
39
|
+
key: UPDATE_JOB,
|
40
|
+
})
|
41
|
+
);
|
42
|
+
};
|
43
|
+
|
44
|
+
export const cancelJob = (jobId, force) => dispatch => {
|
45
|
+
const infoToast = () =>
|
46
|
+
force
|
47
|
+
? sprintf(__('Trying to abort the job %s.'), jobId)
|
48
|
+
: sprintf(__('Trying to cancel the job %s.'), jobId);
|
49
|
+
const errorToast = response =>
|
50
|
+
force
|
51
|
+
? sprintf(__(`Could not abort the job %s: ${response}`), jobId)
|
52
|
+
: sprintf(__(`Could not cancel the job %s: ${response}`), jobId);
|
53
|
+
const url = force
|
54
|
+
? `/job_invocations/${jobId}/cancel?force=true`
|
55
|
+
: `/job_invocations/${jobId}/cancel`;
|
56
|
+
|
57
|
+
dispatch(
|
58
|
+
APIActions.post({
|
59
|
+
url,
|
60
|
+
key: CANCEL_JOB,
|
61
|
+
errorToast: ({ response }) =>
|
62
|
+
errorToast(
|
63
|
+
// eslint-disable-next-line camelcase
|
64
|
+
response?.data?.error?.full_messages ||
|
65
|
+
response?.data?.error?.message ||
|
66
|
+
'Unknown error.'
|
67
|
+
),
|
68
|
+
handleSuccess: () => {
|
69
|
+
dispatch(
|
70
|
+
addToast({
|
71
|
+
key: `cancel-job-error`,
|
72
|
+
type: 'info',
|
73
|
+
message: infoToast(),
|
74
|
+
})
|
75
|
+
);
|
76
|
+
dispatch(updateJob(jobId));
|
77
|
+
},
|
78
|
+
})
|
79
|
+
);
|
80
|
+
};
|
81
|
+
|
82
|
+
export const getTask = taskId => dispatch => {
|
83
|
+
dispatch(
|
84
|
+
get({
|
85
|
+
key: GET_TASK,
|
86
|
+
url: `/foreman_tasks/api/tasks/${taskId}`,
|
87
|
+
})
|
88
|
+
);
|
89
|
+
};
|
90
|
+
|
91
|
+
export const enableRecurringLogic = (
|
92
|
+
recurrenceId,
|
93
|
+
enabled,
|
94
|
+
jobId
|
95
|
+
) => dispatch => {
|
96
|
+
const successToast = () =>
|
97
|
+
enabled
|
98
|
+
? sprintf(__('Recurring logic %s disabled successfully.'), recurrenceId)
|
99
|
+
: sprintf(__('Recurring logic %s enabled successfully.'), recurrenceId);
|
100
|
+
const errorToast = response =>
|
101
|
+
enabled
|
102
|
+
? sprintf(
|
103
|
+
__(`Could not disable recurring logic %s: ${response}`),
|
104
|
+
recurrenceId
|
105
|
+
)
|
106
|
+
: sprintf(
|
107
|
+
__(`Could not enable recurring logic %s: ${response}`),
|
108
|
+
recurrenceId
|
109
|
+
);
|
110
|
+
const url = `/foreman_tasks/api/recurring_logics/${recurrenceId}`;
|
111
|
+
dispatch(
|
112
|
+
APIActions.put({
|
113
|
+
url,
|
114
|
+
key: CHANGE_ENABLED_RECURRING_LOGIC,
|
115
|
+
params: { recurring_logic: { enabled: !enabled } },
|
116
|
+
successToast,
|
117
|
+
errorToast: ({ response }) =>
|
118
|
+
errorToast(
|
119
|
+
// eslint-disable-next-line camelcase
|
120
|
+
response?.data?.error?.full_messages ||
|
121
|
+
response?.data?.error?.message ||
|
122
|
+
'Unknown error.'
|
123
|
+
),
|
124
|
+
handleSuccess: () => dispatch(updateJob(jobId)),
|
125
|
+
})
|
126
|
+
);
|
127
|
+
};
|
128
|
+
|
129
|
+
export const cancelRecurringLogic = (recurrenceId, jobId) => dispatch => {
|
130
|
+
const successToast = () =>
|
131
|
+
sprintf(__('Recurring logic %s cancelled successfully.'), recurrenceId);
|
132
|
+
const errorToast = response =>
|
133
|
+
sprintf(
|
134
|
+
__(`Could not cancel recurring logic %s: ${response}`),
|
135
|
+
recurrenceId
|
136
|
+
);
|
137
|
+
const url = `/foreman_tasks/recurring_logics/${recurrenceId}/cancel`;
|
138
|
+
dispatch(
|
139
|
+
APIActions.post({
|
140
|
+
url,
|
141
|
+
key: CANCEL_RECURRING_LOGIC,
|
142
|
+
successToast,
|
143
|
+
errorToast: ({ response }) =>
|
144
|
+
errorToast(
|
145
|
+
// eslint-disable-next-line camelcase
|
146
|
+
response?.data?.error?.full_messages ||
|
147
|
+
response?.data?.error?.message ||
|
148
|
+
'Unknown error.'
|
149
|
+
),
|
150
|
+
handleSuccess: () => dispatch(updateJob(jobId)),
|
151
|
+
})
|
152
|
+
);
|
153
|
+
};
|
@@ -1,9 +1,23 @@
|
|
1
|
+
import { foremanUrl } from 'foremanReact/common/helpers';
|
2
|
+
|
1
3
|
export const JOB_INVOCATION_KEY = 'JOB_INVOCATION_KEY';
|
4
|
+
export const CURRENT_PERMISSIONS = 'CURRENT_PERMISSIONS';
|
5
|
+
export const UPDATE_JOB = 'UPDATE_JOB';
|
6
|
+
export const CANCEL_JOB = 'CANCEL_JOB';
|
7
|
+
export const GET_TASK = 'GET_TASK';
|
8
|
+
export const CHANGE_ENABLED_RECURRING_LOGIC = 'CHANGE_ENABLED_RECURRING_LOGIC';
|
9
|
+
export const CANCEL_RECURRING_LOGIC = 'CANCEL_RECURRING_LOGIC';
|
10
|
+
export const GET_REPORT_TEMPLATES = 'GET_REPORT_TEMPLATES';
|
11
|
+
export const GET_REPORT_TEMPLATE_INPUTS = 'GET_REPORT_TEMPLATE_INPUTS';
|
12
|
+
export const currentPermissionsUrl = foremanUrl(
|
13
|
+
'/api/v2/permissions/current_permissions'
|
14
|
+
);
|
2
15
|
|
3
16
|
export const STATUS = {
|
4
17
|
PENDING: 'pending',
|
5
18
|
SUCCEEDED: 'succeeded',
|
6
19
|
FAILED: 'failed',
|
20
|
+
CANCELLED: 'cancelled',
|
7
21
|
};
|
8
22
|
|
9
23
|
export const DATE_OPTIONS = {
|
@@ -1,6 +1,8 @@
|
|
1
|
-
.job-invocation-detail-
|
1
|
+
.job-invocation-detail-flex {
|
2
2
|
$chart_size: 105px;
|
3
|
-
|
3
|
+
padding-top: 0px;
|
4
|
+
padding-left: 10px;
|
5
|
+
|
4
6
|
.chart-donut {
|
5
7
|
height: $chart_size;
|
6
8
|
width: $chart_size;
|
@@ -36,3 +38,4 @@
|
|
36
38
|
height: $chart_size;
|
37
39
|
}
|
38
40
|
}
|
41
|
+
|
@@ -3,3 +3,8 @@ import { JOB_INVOCATION_KEY } from './JobInvocationConstants';
|
|
3
3
|
|
4
4
|
export const selectItems = state =>
|
5
5
|
selectAPIResponse(state, JOB_INVOCATION_KEY);
|
6
|
+
|
7
|
+
export const selectTask = state => selectAPIResponse(state, 'GET_TASK');
|
8
|
+
|
9
|
+
export const selectTaskCancelable = state =>
|
10
|
+
selectTask(state).available_actions?.cancellable || false;
|
@@ -1,6 +1,7 @@
|
|
1
|
-
import React, { useEffect, useState } from 'react';
|
2
1
|
import PropTypes from 'prop-types';
|
2
|
+
import React, { useEffect, useState } from 'react';
|
3
3
|
import { translate as __, sprintf } from 'foremanReact/common/I18n';
|
4
|
+
import DefaultLoaderEmptyState from 'foremanReact/components/HostDetails/DetailsCard/DefaultLoaderEmptyState';
|
4
5
|
import {
|
5
6
|
ChartDonut,
|
6
7
|
ChartLabel,
|
@@ -9,20 +10,19 @@ import {
|
|
9
10
|
} from '@patternfly/react-charts';
|
10
11
|
import {
|
11
12
|
DescriptionList,
|
12
|
-
DescriptionListTerm,
|
13
|
-
DescriptionListGroup,
|
14
13
|
DescriptionListDescription,
|
14
|
+
DescriptionListGroup,
|
15
|
+
DescriptionListTerm,
|
15
16
|
FlexItem,
|
16
17
|
Text,
|
17
18
|
} from '@patternfly/react-core';
|
18
19
|
import {
|
19
|
-
global_palette_green_500 as successedColor,
|
20
|
-
global_palette_red_100 as failedColor,
|
21
|
-
global_palette_blue_300 as inProgressColor,
|
22
20
|
global_palette_black_600 as canceledColor,
|
23
21
|
global_palette_black_500 as emptyChartDonut,
|
22
|
+
global_palette_red_100 as failedColor,
|
23
|
+
global_palette_blue_300 as inProgressColor,
|
24
|
+
global_palette_green_500 as successedColor,
|
24
25
|
} from '@patternfly/react-tokens';
|
25
|
-
import DefaultLoaderEmptyState from 'foremanReact/components/HostDetails/DetailsCard/DefaultLoaderEmptyState';
|
26
26
|
import './JobInvocationDetail.scss';
|
27
27
|
|
28
28
|
const JobInvocationSystemStatusChart = ({
|
@@ -35,9 +35,9 @@ const JobInvocationSystemStatusChart = ({
|
|
35
35
|
failed,
|
36
36
|
pending,
|
37
37
|
cancelled,
|
38
|
-
total,
|
39
38
|
total_hosts: totalHosts, // includes scheduled
|
40
39
|
} = data;
|
40
|
+
const total = succeeded + failed + pending + cancelled;
|
41
41
|
const chartData = [
|
42
42
|
{ title: __('Succeeded:'), count: succeeded, color: successedColor.value },
|
43
43
|
{ title: __('Failed:'), count: failed, color: failedColor.value },
|
@@ -82,7 +82,11 @@ const JobInvocationSystemStatusChart = ({
|
|
82
82
|
total > 0 ? chartData.map(d => d.color) : [emptyChartDonut.value]
|
83
83
|
}
|
84
84
|
labelComponent={
|
85
|
-
<ChartTooltip
|
85
|
+
<ChartTooltip
|
86
|
+
pointerLength={0}
|
87
|
+
constrainToVisibleArea
|
88
|
+
renderInPortal={false}
|
89
|
+
/>
|
86
90
|
}
|
87
91
|
title={chartDonutTitle}
|
88
92
|
titleComponent={
|
@@ -0,0 +1,268 @@
|
|
1
|
+
import PropTypes from 'prop-types';
|
2
|
+
import React, { useEffect, useState } from 'react';
|
3
|
+
import { useDispatch, useSelector } from 'react-redux';
|
4
|
+
import {
|
5
|
+
Button,
|
6
|
+
Dropdown,
|
7
|
+
DropdownItem,
|
8
|
+
DropdownPosition,
|
9
|
+
DropdownSeparator,
|
10
|
+
DropdownToggle,
|
11
|
+
Split,
|
12
|
+
SplitItem,
|
13
|
+
} from '@patternfly/react-core';
|
14
|
+
import { translate as __ } from 'foremanReact/common/I18n';
|
15
|
+
import { foremanUrl } from 'foremanReact/common/helpers';
|
16
|
+
import { STATUS as APIStatus } from 'foremanReact/constants';
|
17
|
+
import { get } from 'foremanReact/redux/API';
|
18
|
+
import {
|
19
|
+
cancelJob,
|
20
|
+
cancelRecurringLogic,
|
21
|
+
enableRecurringLogic,
|
22
|
+
} from './JobInvocationActions';
|
23
|
+
import {
|
24
|
+
STATUS,
|
25
|
+
GET_REPORT_TEMPLATES,
|
26
|
+
GET_REPORT_TEMPLATE_INPUTS,
|
27
|
+
} from './JobInvocationConstants';
|
28
|
+
import { selectTaskCancelable } from './JobInvocationSelectors';
|
29
|
+
|
30
|
+
const JobInvocationToolbarButtons = ({
|
31
|
+
jobId,
|
32
|
+
data,
|
33
|
+
currentPermissions,
|
34
|
+
permissionsStatus,
|
35
|
+
}) => {
|
36
|
+
const { succeeded, failed, task, recurrence, permissions } = data;
|
37
|
+
const recurringEnabled = recurrence?.state === 'active';
|
38
|
+
const canViewForemanTasks = permissions
|
39
|
+
? permissions.view_foreman_tasks
|
40
|
+
: false;
|
41
|
+
const canEditRecurringLogic = permissions
|
42
|
+
? permissions.edit_recurring_logics
|
43
|
+
: false;
|
44
|
+
const isTaskCancelable = useSelector(selectTaskCancelable);
|
45
|
+
const [isActionOpen, setIsActionOpen] = useState(false);
|
46
|
+
const [reportTemplateJobId, setReportTemplateJobId] = useState(undefined);
|
47
|
+
const [templateInputId, setTemplateInputId] = useState(undefined);
|
48
|
+
const queryParams = new URLSearchParams({
|
49
|
+
[`report_template_report[input_values][${templateInputId}][value]`]: jobId,
|
50
|
+
});
|
51
|
+
const dispatch = useDispatch();
|
52
|
+
|
53
|
+
const onActionFocus = () => {
|
54
|
+
const element = document.getElementById(
|
55
|
+
`toggle-split-button-action-primary-${jobId}`
|
56
|
+
);
|
57
|
+
element.focus();
|
58
|
+
};
|
59
|
+
const onActionSelect = () => {
|
60
|
+
setIsActionOpen(false);
|
61
|
+
onActionFocus();
|
62
|
+
};
|
63
|
+
const hasPermission = permissionRequired =>
|
64
|
+
permissionsStatus === APIStatus.RESOLVED
|
65
|
+
? currentPermissions?.some(
|
66
|
+
permission => permission.name === permissionRequired
|
67
|
+
)
|
68
|
+
: false;
|
69
|
+
|
70
|
+
useEffect(() => {
|
71
|
+
dispatch(
|
72
|
+
get({
|
73
|
+
key: GET_REPORT_TEMPLATES,
|
74
|
+
url: '/api/report_templates',
|
75
|
+
handleSuccess: ({ data: { results } }) => {
|
76
|
+
setReportTemplateJobId(
|
77
|
+
results.find(result => result.name === 'Job - Invocation Report')
|
78
|
+
?.id
|
79
|
+
);
|
80
|
+
},
|
81
|
+
handleError: () => {
|
82
|
+
setReportTemplateJobId(undefined);
|
83
|
+
},
|
84
|
+
})
|
85
|
+
);
|
86
|
+
}, [dispatch]);
|
87
|
+
useEffect(() => {
|
88
|
+
if (reportTemplateJobId !== undefined) {
|
89
|
+
dispatch(
|
90
|
+
get({
|
91
|
+
key: GET_REPORT_TEMPLATE_INPUTS,
|
92
|
+
url: `/api/templates/${reportTemplateJobId}/template_inputs`,
|
93
|
+
handleSuccess: ({ data: { results } }) => {
|
94
|
+
setTemplateInputId(
|
95
|
+
results.find(result => result.name === 'job_id')?.id
|
96
|
+
);
|
97
|
+
},
|
98
|
+
handleError: () => {
|
99
|
+
setTemplateInputId(undefined);
|
100
|
+
},
|
101
|
+
})
|
102
|
+
);
|
103
|
+
}
|
104
|
+
}, [dispatch, reportTemplateJobId]);
|
105
|
+
|
106
|
+
const recurrenceDropdownItems = recurrence
|
107
|
+
? [
|
108
|
+
<DropdownSeparator ouiaId="dropdown-separator-1" key="separator-1" />,
|
109
|
+
<DropdownItem
|
110
|
+
ouiaId="change-enabled-recurring-dropdown-item"
|
111
|
+
onClick={() =>
|
112
|
+
dispatch(
|
113
|
+
enableRecurringLogic(recurrence?.id, recurringEnabled, jobId)
|
114
|
+
)
|
115
|
+
}
|
116
|
+
key="change-enabled-recurring"
|
117
|
+
component="button"
|
118
|
+
isDisabled={
|
119
|
+
recurrence?.id === undefined ||
|
120
|
+
recurrence?.state === 'cancelled' ||
|
121
|
+
!canEditRecurringLogic
|
122
|
+
}
|
123
|
+
>
|
124
|
+
{recurringEnabled ? __('Disable recurring') : __('Enable recurring')}
|
125
|
+
</DropdownItem>,
|
126
|
+
<DropdownItem
|
127
|
+
ouiaId="cancel-recurring-dropdown-item"
|
128
|
+
onClick={() => dispatch(cancelRecurringLogic(recurrence?.id, jobId))}
|
129
|
+
key="cancel-recurring"
|
130
|
+
component="button"
|
131
|
+
isDisabled={
|
132
|
+
recurrence?.id === undefined ||
|
133
|
+
recurrence?.state === 'cancelled' ||
|
134
|
+
!canEditRecurringLogic
|
135
|
+
}
|
136
|
+
>
|
137
|
+
{__('Cancel recurring')}
|
138
|
+
</DropdownItem>,
|
139
|
+
]
|
140
|
+
: [];
|
141
|
+
|
142
|
+
const dropdownItems = [
|
143
|
+
<DropdownItem
|
144
|
+
ouiaId="rerun-succeeded-dropdown-item"
|
145
|
+
href={foremanUrl(`/job_invocations/${jobId}/rerun?succeeded_only=1`)}
|
146
|
+
key="rerun-succeeded"
|
147
|
+
isDisabled={!(succeeded > 0) || !hasPermission('create_job_invocations')}
|
148
|
+
description="Rerun job on successful hosts"
|
149
|
+
>
|
150
|
+
{__('Rerun successful')}
|
151
|
+
</DropdownItem>,
|
152
|
+
<DropdownItem
|
153
|
+
ouiaId="rerun-failed-dropdown-item"
|
154
|
+
href={foremanUrl(`/job_invocations/${jobId}/rerun?failed_only=1`)}
|
155
|
+
key="rerun-failed"
|
156
|
+
isDisabled={!(failed > 0) || !hasPermission('create_job_invocations')}
|
157
|
+
description="Rerun job on failed hosts"
|
158
|
+
>
|
159
|
+
{__('Rerun failed')}
|
160
|
+
</DropdownItem>,
|
161
|
+
<DropdownItem
|
162
|
+
ouiaId="view-task-dropdown-item"
|
163
|
+
href={foremanUrl(`/foreman_tasks/tasks/${task?.id}`)}
|
164
|
+
key="view-task"
|
165
|
+
isDisabled={!canViewForemanTasks || task === undefined}
|
166
|
+
description="See details of latest task"
|
167
|
+
>
|
168
|
+
{__('View task')}
|
169
|
+
</DropdownItem>,
|
170
|
+
<DropdownSeparator ouiaId="dropdown-separator-0" key="separator-0" />,
|
171
|
+
<DropdownItem
|
172
|
+
ouiaId="cancel-dropdown-item"
|
173
|
+
onClick={() => dispatch(cancelJob(jobId, false))}
|
174
|
+
key="cancel"
|
175
|
+
component="button"
|
176
|
+
isDisabled={!isTaskCancelable || !hasPermission('cancel_job_invocations')}
|
177
|
+
description="Cancel job gracefully"
|
178
|
+
>
|
179
|
+
{__('Cancel')}
|
180
|
+
</DropdownItem>,
|
181
|
+
<DropdownItem
|
182
|
+
ouiaId="abort-dropdown-item"
|
183
|
+
onClick={() => dispatch(cancelJob(jobId, true))}
|
184
|
+
key="abort"
|
185
|
+
component="button"
|
186
|
+
isDisabled={!isTaskCancelable || !hasPermission('cancel_job_invocations')}
|
187
|
+
description="Cancel job immediately"
|
188
|
+
>
|
189
|
+
{__('Abort')}
|
190
|
+
</DropdownItem>,
|
191
|
+
...recurrenceDropdownItems,
|
192
|
+
<DropdownSeparator ouiaId="dropdown-separator-2" key="separator-2" />,
|
193
|
+
<DropdownItem
|
194
|
+
ouiaId="legacy-ui-dropdown-item"
|
195
|
+
href={`/job_invocations/${jobId}`}
|
196
|
+
key="legacy-ui"
|
197
|
+
>
|
198
|
+
{__('Legacy UI')}
|
199
|
+
</DropdownItem>,
|
200
|
+
];
|
201
|
+
|
202
|
+
return (
|
203
|
+
<>
|
204
|
+
<Split hasGutter>
|
205
|
+
<SplitItem>
|
206
|
+
<Button
|
207
|
+
component="a"
|
208
|
+
ouiaId="button-create-report"
|
209
|
+
className="button-create-report"
|
210
|
+
href={foremanUrl(
|
211
|
+
`/templates/report_templates/${reportTemplateJobId}/generate?${queryParams.toString()}`
|
212
|
+
)}
|
213
|
+
variant="secondary"
|
214
|
+
isDisabled={
|
215
|
+
task?.state === STATUS.PENDING ||
|
216
|
+
templateInputId === undefined ||
|
217
|
+
!hasPermission('generate_report_templates')
|
218
|
+
}
|
219
|
+
>
|
220
|
+
{__(`Create report`)}
|
221
|
+
</Button>
|
222
|
+
</SplitItem>
|
223
|
+
<SplitItem>
|
224
|
+
<Dropdown
|
225
|
+
ouiaId="job-invocation-global-actions-dropdown"
|
226
|
+
onSelect={onActionSelect}
|
227
|
+
position={DropdownPosition.right}
|
228
|
+
toggle={
|
229
|
+
<DropdownToggle
|
230
|
+
ouiaId="toggle-button-action-primary"
|
231
|
+
id={`toggle-split-button-action-primary-${jobId}`}
|
232
|
+
splitButtonItems={[
|
233
|
+
<Button
|
234
|
+
component="a"
|
235
|
+
ouiaId="button-rerun-all"
|
236
|
+
key="rerun"
|
237
|
+
href={foremanUrl(`/job_invocations/${jobId}/rerun`)}
|
238
|
+
variant="control"
|
239
|
+
isDisabled={!hasPermission('create_job_invocations')}
|
240
|
+
>
|
241
|
+
{__(`Rerun all`)}
|
242
|
+
</Button>,
|
243
|
+
]}
|
244
|
+
splitButtonVariant="action"
|
245
|
+
onToggle={setIsActionOpen}
|
246
|
+
/>
|
247
|
+
}
|
248
|
+
isOpen={isActionOpen}
|
249
|
+
dropdownItems={dropdownItems}
|
250
|
+
/>
|
251
|
+
</SplitItem>
|
252
|
+
</Split>
|
253
|
+
</>
|
254
|
+
);
|
255
|
+
};
|
256
|
+
|
257
|
+
JobInvocationToolbarButtons.propTypes = {
|
258
|
+
jobId: PropTypes.string.isRequired,
|
259
|
+
data: PropTypes.object.isRequired,
|
260
|
+
currentPermissions: PropTypes.array,
|
261
|
+
permissionsStatus: PropTypes.string,
|
262
|
+
};
|
263
|
+
JobInvocationToolbarButtons.defaultProps = {
|
264
|
+
currentPermissions: undefined,
|
265
|
+
permissionsStatus: undefined,
|
266
|
+
};
|
267
|
+
|
268
|
+
export default JobInvocationToolbarButtons;
|