foreman_remote_execution 16.6.5 → 17.0.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 +22 -13
- data/app/views/api/v2/job_invocations/main.json.rabl +16 -11
- data/app/views/job_templates/_alerts.html.erb +4 -0
- data/lib/foreman_remote_execution/plugin.rb +1 -1
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/package.json +3 -3
- data/test/functional/api/v2/job_invocations_controller_test.rb +68 -3
- data/webpack/JobInvocationDetail/CheckboxesActions.js +4 -10
- data/webpack/JobInvocationDetail/JobInvocationActions.js +80 -90
- data/webpack/JobInvocationDetail/JobInvocationConstants.js +7 -6
- data/webpack/JobInvocationDetail/JobInvocationEmptyState.js +52 -0
- data/webpack/JobInvocationDetail/JobInvocationHostTable.js +76 -42
- data/webpack/JobInvocationDetail/JobInvocationSelectors.js +1 -19
- data/webpack/JobInvocationDetail/JobInvocationToolbarButtons.js +202 -153
- data/webpack/JobInvocationDetail/TemplateInvocation.js +31 -25
- data/webpack/JobInvocationDetail/__tests__/JobInvocationDetailEmptyState.test.js +53 -0
- data/webpack/JobInvocationDetail/__tests__/JobInvocationHostTablePolling.test.js +336 -0
- data/webpack/JobInvocationDetail/__tests__/JobInvocationPolling.test.js +223 -0
- data/webpack/JobInvocationDetail/__tests__/MainInformation.test.js +260 -111
- data/webpack/JobInvocationDetail/__tests__/TableToolbarActions.test.js +3 -1
- data/webpack/JobInvocationDetail/__tests__/TemplateInvocationPolling.test.js +254 -0
- data/webpack/JobInvocationDetail/__tests__/fixtures.js +2 -0
- data/webpack/JobInvocationDetail/__tests__/foremanTestHelpers.js +30 -0
- data/webpack/JobInvocationDetail/index.js +50 -30
- data/webpack/__mocks__/foremanReact/Root/Context/ForemanContext/index.js +15 -0
- data/webpack/__mocks__/foremanReact/common/hooks/Permissions/permissionHooks.js +1 -0
- data/webpack/__mocks__/foremanReact/redux/API/APISelectors.js +5 -0
- data/webpack/test_setup.js +1 -13
- metadata +11 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d0c5f9e975897ee24ccf2c91fafe81e998742ec7453ca0e7cf3aea4846962b23
|
|
4
|
+
data.tar.gz: 20f9ca73d78bd41ce758b3ac39e1e880be6630230eb64d735850a88ae7b9cbec
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 57609a9b217ab4e7e5e60536a6a4de788b26d46d09f68330ca385fbd4df59d65cabc33581026ba15795aacbfc96b1fe6a97b735e006dfcf0a51188032ea525b9
|
|
7
|
+
data.tar.gz: 23d742ef78d0f9dc5cc205d541c74a8c3a3472618c4e67a6cb1c5c94c552a2ea6aa601ce38b498f9fae2403227f0efdf44055fefe8cc90df1de86e3c7b6fc96f
|
|
@@ -19,13 +19,16 @@ module Api
|
|
|
19
19
|
|
|
20
20
|
api :GET, '/job_invocations/:id', N_('Show job invocation')
|
|
21
21
|
param :id, :identifier, :required => true
|
|
22
|
-
param :
|
|
22
|
+
param :include_hosts, :bool, :required => false, :default_value => true, :desc => N_('Include hosts and template invocations in the response. Defaults to true for backwards compatibility. Pass false to skip serializing all hosts.')
|
|
23
|
+
param :host_status, :bool, :required => false, :default_value => false, :desc => N_('Show job status for each host, only applicable when include_hosts is true')
|
|
23
24
|
def show
|
|
24
|
-
|
|
25
|
+
@pattern_template_invocations = @job_invocation.pattern_template_invocations.includes(:input_values)
|
|
25
26
|
@job_organization = Taxonomy.find_by(id: @job_invocation.task.input[:current_organization_id])
|
|
26
27
|
@job_location = Taxonomy.find_by(id: @job_invocation.task.input[:current_location_id])
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
|
|
29
|
+
if include_hosts?
|
|
30
|
+
set_hosts_and_template_invocations
|
|
31
|
+
set_statuses_and_smart_proxies if Foreman::Cast.to_bool(params[:host_status])
|
|
29
32
|
end
|
|
30
33
|
end
|
|
31
34
|
|
|
@@ -84,13 +87,14 @@ module Api
|
|
|
84
87
|
|
|
85
88
|
api :POST, '/job_invocations/', N_('Create a job invocation')
|
|
86
89
|
param_group :job_invocation, :as => :create
|
|
90
|
+
param :include_hosts, :bool, :required => false, :default_value => true, :desc => N_('Include hosts and template invocations in the response. Defaults to true for backwards compatibility. Pass false to skip serializing all hosts.')
|
|
87
91
|
def create
|
|
88
92
|
composer = JobInvocationComposer.from_api_params(
|
|
89
93
|
job_invocation_params
|
|
90
94
|
)
|
|
91
95
|
composer.trigger!
|
|
92
96
|
@job_invocation = composer.job_invocation
|
|
93
|
-
|
|
97
|
+
set_hosts_and_template_invocations if include_hosts?
|
|
94
98
|
process_response @job_invocation
|
|
95
99
|
rescue JobInvocationComposer::JobTemplateNotFound, JobInvocationComposer::FeatureNotFound => e
|
|
96
100
|
not_found(error: { message: e.message })
|
|
@@ -115,10 +119,10 @@ module Api
|
|
|
115
119
|
param :id, :identifier, :required => true
|
|
116
120
|
def hosts
|
|
117
121
|
set_hosts_and_template_invocations
|
|
118
|
-
set_statuses_and_smart_proxies
|
|
119
122
|
@total = @hosts.size
|
|
120
123
|
@hosts = @hosts.search_for(params[:search], :order => params[:order]).paginate(:page => params[:page], :per_page => params[:per_page])
|
|
121
124
|
@subtotal = @hosts.total_entries
|
|
125
|
+
set_statuses_and_smart_proxies
|
|
122
126
|
if params[:awaiting]
|
|
123
127
|
@hosts = @hosts.select { |host| @host_statuses[host.id] == 'N/A' }
|
|
124
128
|
end
|
|
@@ -298,7 +302,6 @@ module Api
|
|
|
298
302
|
end
|
|
299
303
|
|
|
300
304
|
def set_hosts_and_template_invocations
|
|
301
|
-
@pattern_template_invocations = @job_invocation.pattern_template_invocations.includes(:input_values)
|
|
302
305
|
@hosts = @job_invocation.targeting.hosts.authorized(:view_hosts, Host)
|
|
303
306
|
|
|
304
307
|
if params[:search].present?
|
|
@@ -310,16 +313,22 @@ module Api
|
|
|
310
313
|
.includes(:input_values)
|
|
311
314
|
end
|
|
312
315
|
|
|
316
|
+
def include_hosts?
|
|
317
|
+
params[:include_hosts].nil? || Foreman::Cast.to_bool(params[:include_hosts])
|
|
318
|
+
end
|
|
319
|
+
|
|
313
320
|
def set_statuses_and_smart_proxies
|
|
314
|
-
template_invocations = @template_invocations.
|
|
321
|
+
template_invocations = @template_invocations.where(host_id: @hosts.select(:id))
|
|
322
|
+
.includes(:run_host_job_task).to_a
|
|
315
323
|
hosts = @hosts.to_a
|
|
316
|
-
|
|
317
|
-
|
|
324
|
+
template_invocations_by_host_id = template_invocations.index_by(&:host_id)
|
|
325
|
+
@host_statuses = hosts.to_h do |host|
|
|
326
|
+
template_invocation = template_invocations_by_host_id[host.id]
|
|
318
327
|
task = template_invocation.try(:run_host_job_task)
|
|
319
328
|
[host.id, template_invocation_status(task, @job_invocation.task)]
|
|
320
|
-
end
|
|
321
|
-
@smart_proxy_id =
|
|
322
|
-
@smart_proxy_name =
|
|
329
|
+
end
|
|
330
|
+
@smart_proxy_id = template_invocations.to_h { |ti| [ti.host_id, ti.smart_proxy_id] }
|
|
331
|
+
@smart_proxy_name = template_invocations.to_h { |ti| [ti.host_id, ti.smart_proxy_name] }
|
|
323
332
|
end
|
|
324
333
|
end
|
|
325
334
|
end
|
|
@@ -19,12 +19,14 @@ child :targeting do
|
|
|
19
19
|
attributes :bookmark_id, :bookmark_name, :search_query, :targeting_type, :user_id, :status, :status_label,
|
|
20
20
|
:randomized_ordering
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
if @hosts
|
|
23
|
+
child @hosts => :hosts do
|
|
24
|
+
extends 'api/v2/hosts/base'
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
if @host_statuses
|
|
27
|
+
node :job_status do |host|
|
|
28
|
+
@host_statuses[host.id]
|
|
29
|
+
end
|
|
28
30
|
end
|
|
29
31
|
end
|
|
30
32
|
end
|
|
@@ -32,14 +34,17 @@ end
|
|
|
32
34
|
|
|
33
35
|
child :task do
|
|
34
36
|
attributes :id, :state, :started_at
|
|
37
|
+
node(:cancellable) { |task| task.try(:cancellable?) }
|
|
35
38
|
end
|
|
36
39
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
if @template_invocations
|
|
41
|
+
child @template_invocations => :template_invocations do
|
|
42
|
+
attributes :template_id, :template_name, :host_id
|
|
43
|
+
child :input_values do
|
|
44
|
+
attributes :template_input_name, :template_input_id
|
|
45
|
+
node :value do |iv|
|
|
46
|
+
iv.template_input.respond_to?(:hidden_value) && iv.template_input.hidden_value? ? '*' * 5 : iv.value
|
|
47
|
+
end
|
|
43
48
|
end
|
|
44
49
|
end
|
|
45
50
|
end
|
data/package.json
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
"scripts": {
|
|
6
6
|
"lint": "tfm-lint --plugin -d /webpack",
|
|
7
7
|
"lint:custom": "eslint ./webpack",
|
|
8
|
-
"test": "tfm-test --plugin",
|
|
9
|
-
"test:watch": "tfm-test --plugin --watchAll",
|
|
10
|
-
"test:current": "tfm-test --plugin --watch",
|
|
8
|
+
"test": "tfm-test --plugin --config jest.config.js",
|
|
9
|
+
"test:watch": "tfm-test --plugin --config jest.config.js --watchAll",
|
|
10
|
+
"test:current": "tfm-test --plugin --config jest.config.js --watch",
|
|
11
11
|
"publish-coverage": "tfm-publish-coverage"
|
|
12
12
|
},
|
|
13
13
|
"repository": {
|
|
@@ -27,7 +27,10 @@ module Api
|
|
|
27
27
|
template = ActiveSupport::JSON.decode(@response.body)
|
|
28
28
|
assert_not_empty template
|
|
29
29
|
assert_equal template['job_category'], @invocation.job_category
|
|
30
|
-
|
|
30
|
+
assert_not_nil template['targeting']
|
|
31
|
+
assert_not_empty template['pattern_template_invocations']
|
|
32
|
+
assert_not_nil template['targeting']['hosts']
|
|
33
|
+
assert_not_nil template['template_invocations']
|
|
31
34
|
end
|
|
32
35
|
|
|
33
36
|
test 'should get invocation detail when taxonomies are set' do
|
|
@@ -36,9 +39,17 @@ module Api
|
|
|
36
39
|
assert_response :success
|
|
37
40
|
end
|
|
38
41
|
|
|
42
|
+
test 'should exclude hosts when include_hosts=false' do
|
|
43
|
+
get :show, params: { :id => @invocation.id, :include_hosts => false }
|
|
44
|
+
assert_response :success
|
|
45
|
+
result = ActiveSupport::JSON.decode(@response.body)
|
|
46
|
+
assert_nil result['targeting']['hosts']
|
|
47
|
+
assert_nil result['template_invocations']
|
|
48
|
+
end
|
|
49
|
+
|
|
39
50
|
test 'should see only permitted hosts' do
|
|
40
|
-
@user = FactoryBot.create(:user, admin
|
|
41
|
-
@invocation.task.update(user
|
|
51
|
+
@user = FactoryBot.create(:user, :admin => false)
|
|
52
|
+
@invocation.task.update(:user => @user)
|
|
42
53
|
setup_user('view', 'job_invocations', nil, @user)
|
|
43
54
|
setup_user('view', 'hosts', 'name ~ nope.example.com', @user)
|
|
44
55
|
|
|
@@ -47,6 +58,29 @@ module Api
|
|
|
47
58
|
response = ActiveSupport::JSON.decode(@response.body)
|
|
48
59
|
assert_equal response['targeting']['hosts'], []
|
|
49
60
|
end
|
|
61
|
+
|
|
62
|
+
test 'should ignore host_status when include_hosts=false' do
|
|
63
|
+
get :show, params: { :id => @invocation.id, :include_hosts => false, :host_status => true }
|
|
64
|
+
assert_response :success
|
|
65
|
+
result = ActiveSupport::JSON.decode(@response.body)
|
|
66
|
+
assert_nil result['targeting']['hosts']
|
|
67
|
+
assert_nil result['template_invocations']
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
test 'should include job_status per host when host_status=true' do
|
|
71
|
+
invocation = FactoryBot.create(:job_invocation, :with_template, :with_task)
|
|
72
|
+
invocation.template_invocations << FactoryBot.create(:template_invocation, :with_task, :with_host, :job_invocation => invocation)
|
|
73
|
+
invocation.job_category = invocation.pattern_template_invocations.first.template.job_category
|
|
74
|
+
invocation.targeting.hosts = invocation.template_invocations.map(&:host)
|
|
75
|
+
invocation.save!
|
|
76
|
+
|
|
77
|
+
get :show, params: { :id => invocation.id, :include_hosts => true, :host_status => true }
|
|
78
|
+
assert_response :success
|
|
79
|
+
result = ActiveSupport::JSON.decode(@response.body)
|
|
80
|
+
hosts = result['targeting']['hosts']
|
|
81
|
+
assert_not_empty hosts
|
|
82
|
+
assert hosts.all? { |h| h.key?('job_status') }, 'Expected job_status to be present on each host'
|
|
83
|
+
end
|
|
50
84
|
end
|
|
51
85
|
|
|
52
86
|
context 'creation' do
|
|
@@ -66,6 +100,20 @@ module Api
|
|
|
66
100
|
assert_response :success
|
|
67
101
|
end
|
|
68
102
|
|
|
103
|
+
test 'should include hosts in create response by default' do
|
|
104
|
+
post :create, params: { job_invocation: @attrs }
|
|
105
|
+
invocation = ActiveSupport::JSON.decode(@response.body)
|
|
106
|
+
assert_not_nil invocation['targeting']['hosts']
|
|
107
|
+
assert_response :success
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
test 'should exclude hosts from create response when include_hosts=false' do
|
|
111
|
+
post :create, params: { job_invocation: @attrs, include_hosts: false }
|
|
112
|
+
invocation = ActiveSupport::JSON.decode(@response.body)
|
|
113
|
+
assert_nil invocation['targeting']['hosts']
|
|
114
|
+
assert_response :success
|
|
115
|
+
end
|
|
116
|
+
|
|
69
117
|
test 'should create with description format overridden' do
|
|
70
118
|
@attrs[:description_format] = 'format'
|
|
71
119
|
post :create, params: { job_invocation: @attrs }
|
|
@@ -235,6 +283,23 @@ module Api
|
|
|
235
283
|
end
|
|
236
284
|
end
|
|
237
285
|
|
|
286
|
+
describe '#hosts' do
|
|
287
|
+
test 'should compute job_status for the paginated subset' do
|
|
288
|
+
invocation = FactoryBot.create(:job_invocation, :with_template, :with_task)
|
|
289
|
+
2.times { invocation.template_invocations << FactoryBot.create(:template_invocation, :with_task, :with_host, :job_invocation => invocation) }
|
|
290
|
+
invocation.job_category = invocation.pattern_template_invocations.first.template.job_category
|
|
291
|
+
invocation.targeting.hosts = invocation.template_invocations.map(&:host)
|
|
292
|
+
invocation.save!
|
|
293
|
+
|
|
294
|
+
get :hosts, params: { :id => invocation.id, :page => 2, :per_page => 1 }
|
|
295
|
+
assert_response :success
|
|
296
|
+
result = ActiveSupport::JSON.decode(@response.body)
|
|
297
|
+
assert_equal 3, result['total']
|
|
298
|
+
assert_equal 1, result['results'].size
|
|
299
|
+
assert_equal(['success'], result['results'].map { |r| r['job_status'] })
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
|
|
238
303
|
describe 'raw output' do
|
|
239
304
|
let(:fake_output) do
|
|
240
305
|
(1..5).map do |i|
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
} from '@patternfly/react-icons';
|
|
15
15
|
import axios from 'axios';
|
|
16
16
|
import { foremanUrl } from 'foremanReact/common/helpers';
|
|
17
|
+
import { usePermissions } from 'foremanReact/common/hooks/Permissions/permissionHooks';
|
|
17
18
|
import { translate as __, sprintf } from 'foremanReact/common/I18n';
|
|
18
19
|
import { addToast } from 'foremanReact/components/ToastsList';
|
|
19
20
|
import PropTypes from 'prop-types';
|
|
@@ -24,10 +25,7 @@ import {
|
|
|
24
25
|
DIRECT_OPEN_HOST_LIMIT,
|
|
25
26
|
templateInvocationPageUrl,
|
|
26
27
|
} from './JobInvocationConstants';
|
|
27
|
-
import {
|
|
28
|
-
selectHasPermission,
|
|
29
|
-
selectTaskCancelable,
|
|
30
|
-
} from './JobInvocationSelectors';
|
|
28
|
+
import { selectTaskCancelable } from './JobInvocationSelectors';
|
|
31
29
|
import OpenAllInvocationsModal from './OpenAllInvocationsModal';
|
|
32
30
|
|
|
33
31
|
/* eslint-disable camelcase */
|
|
@@ -115,12 +113,8 @@ export const CheckboxesActions = ({
|
|
|
115
113
|
const dispatch = useDispatch();
|
|
116
114
|
const [toBeOpened, setToBeOpened] = useState([]);
|
|
117
115
|
|
|
118
|
-
const hasCreatePermission =
|
|
119
|
-
|
|
120
|
-
);
|
|
121
|
-
const hasCancelPermission = useSelector(
|
|
122
|
-
selectHasPermission('cancel_job_invocations')
|
|
123
|
-
);
|
|
116
|
+
const hasCreatePermission = usePermissions(['create_job_invocations']);
|
|
117
|
+
const hasCancelPermission = usePermissions(['cancel_job_invocations']);
|
|
124
118
|
const jobSearchQuery = `job_invocation.id = ${jobID}`;
|
|
125
119
|
const filterQuery =
|
|
126
120
|
filter && filter !== 'all_statuses'
|
|
@@ -1,51 +1,67 @@
|
|
|
1
1
|
import { translate as __, sprintf } from 'foremanReact/common/I18n';
|
|
2
|
-
import { foremanUrl } from 'foremanReact/common/helpers';
|
|
3
2
|
import { addToast } from 'foremanReact/components/ToastsList';
|
|
4
|
-
import { APIActions
|
|
5
|
-
import {
|
|
6
|
-
stopInterval,
|
|
7
|
-
withInterval,
|
|
8
|
-
} from 'foremanReact/redux/middlewares/IntervalMiddleware';
|
|
3
|
+
import { APIActions } from 'foremanReact/redux/API';
|
|
9
4
|
import {
|
|
10
5
|
CANCEL_JOB,
|
|
11
6
|
CANCEL_RECURRING_LOGIC,
|
|
12
7
|
CHANGE_ENABLED_RECURRING_LOGIC,
|
|
13
|
-
GET_TASK,
|
|
14
8
|
JOB_INVOCATION_KEY,
|
|
15
|
-
|
|
9
|
+
STATUS,
|
|
16
10
|
} from './JobInvocationConstants';
|
|
17
11
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
response?.data?.error?.message ||
|
|
33
|
-
'Error',
|
|
34
|
-
}),
|
|
35
|
-
1000
|
|
36
|
-
);
|
|
12
|
+
const POLL_INTERVAL = 5000;
|
|
13
|
+
|
|
14
|
+
export const isJobFinished = statusLabel =>
|
|
15
|
+
statusLabel === STATUS.FAILED ||
|
|
16
|
+
statusLabel === STATUS.SUCCEEDED ||
|
|
17
|
+
statusLabel === STATUS.CANCELLED;
|
|
18
|
+
|
|
19
|
+
const extractErrorMessage = response =>
|
|
20
|
+
// eslint-disable-next-line camelcase
|
|
21
|
+
response?.data?.error?.full_messages?.[0] ||
|
|
22
|
+
response?.data?.error?.message ||
|
|
23
|
+
__('Unknown error.');
|
|
24
|
+
|
|
25
|
+
const createPollState = cancel => ({ timeoutId: null, cancel });
|
|
37
26
|
|
|
38
|
-
|
|
27
|
+
export const getJobInvocation = (url, pollTimeoutRef) => dispatch => {
|
|
28
|
+
let cancelled = false;
|
|
29
|
+
|
|
30
|
+
const poll = () => {
|
|
31
|
+
if (cancelled) return;
|
|
32
|
+
dispatch(
|
|
33
|
+
APIActions.get({
|
|
34
|
+
key: JOB_INVOCATION_KEY,
|
|
35
|
+
params: { include_permissions: true, include_hosts: false },
|
|
36
|
+
url,
|
|
37
|
+
handleSuccess: ({ data }) => {
|
|
38
|
+
if (cancelled) return;
|
|
39
|
+
// eslint-disable-next-line camelcase
|
|
40
|
+
pollTimeoutRef.current.timeoutId = isJobFinished(data?.status_label)
|
|
41
|
+
? null
|
|
42
|
+
: setTimeout(poll, POLL_INTERVAL);
|
|
43
|
+
},
|
|
44
|
+
handleError: () => {
|
|
45
|
+
if (cancelled) return;
|
|
46
|
+
pollTimeoutRef.current.timeoutId = null;
|
|
47
|
+
},
|
|
48
|
+
errorToast: ({ response }) => extractErrorMessage(response),
|
|
49
|
+
})
|
|
50
|
+
);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
stopJobInvocationPolling(pollTimeoutRef);
|
|
54
|
+
pollTimeoutRef.current = createPollState(() => {
|
|
55
|
+
cancelled = true;
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
poll();
|
|
39
59
|
};
|
|
40
60
|
|
|
41
|
-
export const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
url,
|
|
46
|
-
key: UPDATE_JOB,
|
|
47
|
-
})
|
|
48
|
-
);
|
|
61
|
+
export const stopJobInvocationPolling = pollTimeoutRef => {
|
|
62
|
+
clearTimeout(pollTimeoutRef.current.timeoutId);
|
|
63
|
+
pollTimeoutRef.current.cancel();
|
|
64
|
+
pollTimeoutRef.current = createPollState(() => {});
|
|
49
65
|
};
|
|
50
66
|
|
|
51
67
|
export const cancelJob = (jobId, force) => dispatch => {
|
|
@@ -53,10 +69,6 @@ export const cancelJob = (jobId, force) => dispatch => {
|
|
|
53
69
|
force
|
|
54
70
|
? sprintf(__('Trying to abort the job %s.'), jobId)
|
|
55
71
|
: sprintf(__('Trying to cancel the job %s.'), jobId);
|
|
56
|
-
const errorToast = response =>
|
|
57
|
-
force
|
|
58
|
-
? sprintf(__(`Could not abort the job %s: ${response}`), jobId)
|
|
59
|
-
: sprintf(__(`Could not cancel the job %s: ${response}`), jobId);
|
|
60
72
|
const url = force
|
|
61
73
|
? `/job_invocations/${jobId}/cancel?force=true`
|
|
62
74
|
: `/job_invocations/${jobId}/cancel`;
|
|
@@ -66,12 +78,17 @@ export const cancelJob = (jobId, force) => dispatch => {
|
|
|
66
78
|
url,
|
|
67
79
|
key: CANCEL_JOB,
|
|
68
80
|
errorToast: ({ response }) =>
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
81
|
+
force
|
|
82
|
+
? sprintf(
|
|
83
|
+
__('Could not abort the job %s: %s'),
|
|
84
|
+
jobId,
|
|
85
|
+
extractErrorMessage(response)
|
|
86
|
+
)
|
|
87
|
+
: sprintf(
|
|
88
|
+
__('Could not cancel the job %s: %s'),
|
|
89
|
+
jobId,
|
|
90
|
+
extractErrorMessage(response)
|
|
91
|
+
),
|
|
75
92
|
handleSuccess: () => {
|
|
76
93
|
dispatch(
|
|
77
94
|
addToast({
|
|
@@ -80,40 +97,16 @@ export const cancelJob = (jobId, force) => dispatch => {
|
|
|
80
97
|
message: infoToast(),
|
|
81
98
|
})
|
|
82
99
|
);
|
|
83
|
-
dispatch(updateJob(jobId));
|
|
84
100
|
},
|
|
85
101
|
})
|
|
86
102
|
);
|
|
87
103
|
};
|
|
88
104
|
|
|
89
|
-
export const
|
|
90
|
-
dispatch(
|
|
91
|
-
get({
|
|
92
|
-
key: GET_TASK,
|
|
93
|
-
url: `/foreman_tasks/api/tasks/${taskId}`,
|
|
94
|
-
})
|
|
95
|
-
);
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
export const enableRecurringLogic = (
|
|
99
|
-
recurrenceId,
|
|
100
|
-
enabled,
|
|
101
|
-
jobId
|
|
102
|
-
) => dispatch => {
|
|
105
|
+
export const enableRecurringLogic = (recurrenceId, enabled) => dispatch => {
|
|
103
106
|
const successToast = () =>
|
|
104
107
|
enabled
|
|
105
108
|
? sprintf(__('Recurring logic %s disabled successfully.'), recurrenceId)
|
|
106
109
|
: sprintf(__('Recurring logic %s enabled successfully.'), recurrenceId);
|
|
107
|
-
const errorToast = response =>
|
|
108
|
-
enabled
|
|
109
|
-
? sprintf(
|
|
110
|
-
__(`Could not disable recurring logic %s: ${response}`),
|
|
111
|
-
recurrenceId
|
|
112
|
-
)
|
|
113
|
-
: sprintf(
|
|
114
|
-
__(`Could not enable recurring logic %s: ${response}`),
|
|
115
|
-
recurrenceId
|
|
116
|
-
);
|
|
117
110
|
const url = `/foreman_tasks/api/recurring_logics/${recurrenceId}`;
|
|
118
111
|
dispatch(
|
|
119
112
|
APIActions.put({
|
|
@@ -122,25 +115,24 @@ export const enableRecurringLogic = (
|
|
|
122
115
|
params: { recurring_logic: { enabled: !enabled } },
|
|
123
116
|
successToast,
|
|
124
117
|
errorToast: ({ response }) =>
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
118
|
+
enabled
|
|
119
|
+
? sprintf(
|
|
120
|
+
__('Could not disable recurring logic %s: %s'),
|
|
121
|
+
recurrenceId,
|
|
122
|
+
extractErrorMessage(response)
|
|
123
|
+
)
|
|
124
|
+
: sprintf(
|
|
125
|
+
__('Could not enable recurring logic %s: %s'),
|
|
126
|
+
recurrenceId,
|
|
127
|
+
extractErrorMessage(response)
|
|
128
|
+
),
|
|
132
129
|
})
|
|
133
130
|
);
|
|
134
131
|
};
|
|
135
132
|
|
|
136
|
-
export const cancelRecurringLogic =
|
|
133
|
+
export const cancelRecurringLogic = recurrenceId => dispatch => {
|
|
137
134
|
const successToast = () =>
|
|
138
135
|
sprintf(__('Recurring logic %s cancelled successfully.'), recurrenceId);
|
|
139
|
-
const errorToast = response =>
|
|
140
|
-
sprintf(
|
|
141
|
-
__(`Could not cancel recurring logic %s: ${response}`),
|
|
142
|
-
recurrenceId
|
|
143
|
-
);
|
|
144
136
|
const url = `/foreman_tasks/recurring_logics/${recurrenceId}/cancel`;
|
|
145
137
|
dispatch(
|
|
146
138
|
APIActions.post({
|
|
@@ -148,13 +140,11 @@ export const cancelRecurringLogic = (recurrenceId, jobId) => dispatch => {
|
|
|
148
140
|
key: CANCEL_RECURRING_LOGIC,
|
|
149
141
|
successToast,
|
|
150
142
|
errorToast: ({ response }) =>
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
'Unknown error.'
|
|
143
|
+
sprintf(
|
|
144
|
+
__('Could not cancel recurring logic %s: %s'),
|
|
145
|
+
recurrenceId,
|
|
146
|
+
extractErrorMessage(response)
|
|
156
147
|
),
|
|
157
|
-
handleSuccess: () => dispatch(updateJob(jobId)),
|
|
158
148
|
})
|
|
159
149
|
);
|
|
160
150
|
};
|
|
@@ -6,10 +6,7 @@ import { useForemanHostDetailsPageUrl } from 'foremanReact/Root/Context/ForemanC
|
|
|
6
6
|
import JobStatusIcon from '../react_app/components/RecentJobsCard/JobStatusIcon';
|
|
7
7
|
|
|
8
8
|
export const JOB_INVOCATION_KEY = 'JOB_INVOCATION_KEY';
|
|
9
|
-
export const CURRENT_PERMISSIONS = 'CURRENT_PERMISSIONS';
|
|
10
|
-
export const UPDATE_JOB = 'UPDATE_JOB';
|
|
11
9
|
export const CANCEL_JOB = 'CANCEL_JOB';
|
|
12
|
-
export const GET_TASK = 'GET_TASK';
|
|
13
10
|
export const GET_TEMPLATE_INVOCATIONS = 'GET_TEMPLATE_INVOCATIONS';
|
|
14
11
|
export const CHANGE_ENABLED_RECURRING_LOGIC = 'CHANGE_ENABLED_RECURRING_LOGIC';
|
|
15
12
|
export const CANCEL_RECURRING_LOGIC = 'CANCEL_RECURRING_LOGIC';
|
|
@@ -20,9 +17,8 @@ export const GET_TEMPLATE_INVOCATION = 'GET_TEMPLATE_INVOCATION';
|
|
|
20
17
|
export const DIRECT_OPEN_HOST_LIMIT = 3;
|
|
21
18
|
export const ALL_JOB_HOSTS = 'ALL_JOB_HOSTS';
|
|
22
19
|
export const AWAITING_STATUS_FILTER = '(job_invocation.result = N/A)';
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
);
|
|
20
|
+
|
|
21
|
+
export const AUTO_REFRESH_INTERVAL_MS = 5000;
|
|
26
22
|
|
|
27
23
|
export const showTemplateInvocationUrl = (hostID, jobID) =>
|
|
28
24
|
`/show_template_invocation_by_host/${hostID}/job_invocation/${jobID}`;
|
|
@@ -32,12 +28,17 @@ export const templateInvocationPageUrl = (hostID, jobID) =>
|
|
|
32
28
|
`/job_invocations_detail/${jobID}/host_invocation/${hostID}`;
|
|
33
29
|
|
|
34
30
|
export const jobInvocationDetailsUrl = id => `/job_invocations/${id}`;
|
|
31
|
+
export const jobInvocationsIndexPath = '/job_invocations';
|
|
32
|
+
export const jobInvocationsNewPath = '/job_invocations/new';
|
|
33
|
+
export const jobInvocationsIndexUrl = foremanUrl(jobInvocationsIndexPath);
|
|
34
|
+
export const jobInvocationsNewUrl = foremanUrl(jobInvocationsNewPath);
|
|
35
35
|
|
|
36
36
|
export const STATUS = {
|
|
37
37
|
PENDING: 'pending',
|
|
38
38
|
SUCCEEDED: 'succeeded',
|
|
39
39
|
FAILED: 'failed',
|
|
40
40
|
CANCELLED: 'cancelled',
|
|
41
|
+
RUNNING: 'running',
|
|
41
42
|
};
|
|
42
43
|
|
|
43
44
|
export const STATUS_UPPERCASE = {
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import PropTypes from 'prop-types';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { PageSection, PageSectionVariants } from '@patternfly/react-core';
|
|
4
|
+
import { translate as __ } from 'foremanReact/common/I18n';
|
|
5
|
+
import ResourceLoadFailedEmptyState from 'foremanReact/components/common/EmptyState/ResourceLoadFailedEmptyState';
|
|
6
|
+
import {
|
|
7
|
+
jobInvocationsIndexUrl,
|
|
8
|
+
jobInvocationsNewUrl,
|
|
9
|
+
} from './JobInvocationConstants';
|
|
10
|
+
|
|
11
|
+
const JobInvocationEmptyState = ({
|
|
12
|
+
jobInvocationId,
|
|
13
|
+
httpStatus,
|
|
14
|
+
errorMessage,
|
|
15
|
+
}) => (
|
|
16
|
+
<PageSection variant={PageSectionVariants.light}>
|
|
17
|
+
<ResourceLoadFailedEmptyState
|
|
18
|
+
resourceLabel={__('job invocation')}
|
|
19
|
+
resourceId={jobInvocationId}
|
|
20
|
+
httpStatus={httpStatus}
|
|
21
|
+
errorMessage={errorMessage}
|
|
22
|
+
viewPermissions={['view_job_invocations']}
|
|
23
|
+
requiredPermissions={['view_job_invocations']}
|
|
24
|
+
primaryAction={{
|
|
25
|
+
label: __('Go to job invocations'),
|
|
26
|
+
url: jobInvocationsIndexUrl,
|
|
27
|
+
ouiaId: 'job-invocation-empty-state-go-to-job-invocations-button',
|
|
28
|
+
}}
|
|
29
|
+
secondaryActions={[
|
|
30
|
+
{
|
|
31
|
+
label: __('Create a new job invocation'),
|
|
32
|
+
url: jobInvocationsNewUrl,
|
|
33
|
+
ouiaId: 'job-invocation-empty-state-create-new-job-invocation-button',
|
|
34
|
+
},
|
|
35
|
+
]}
|
|
36
|
+
ouiaIdPrefix="job-invocation-empty-state"
|
|
37
|
+
/>
|
|
38
|
+
</PageSection>
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
JobInvocationEmptyState.propTypes = {
|
|
42
|
+
jobInvocationId: PropTypes.string.isRequired,
|
|
43
|
+
httpStatus: PropTypes.number,
|
|
44
|
+
errorMessage: PropTypes.string,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
JobInvocationEmptyState.defaultProps = {
|
|
48
|
+
httpStatus: null,
|
|
49
|
+
errorMessage: null,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export default JobInvocationEmptyState;
|