foreman_remote_execution 16.7.0 → 17.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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/api/v2/job_invocations_controller.rb +24 -2
  3. data/app/controllers/job_invocations_controller.rb +1 -28
  4. data/app/views/api/v2/job_invocations/hosts.json.rabl +8 -0
  5. data/app/views/api/v2/job_invocations/main.json.rabl +1 -0
  6. data/app/views/job_templates/_alerts.html.erb +4 -0
  7. data/config/routes.rb +0 -1
  8. data/lib/foreman_remote_execution/plugin.rb +3 -3
  9. data/lib/foreman_remote_execution/version.rb +1 -1
  10. data/test/functional/api/v2/job_invocations_controller_test.rb +46 -7
  11. data/webpack/JobInvocationDetail/JobInvocationActions.js +86 -91
  12. data/webpack/JobInvocationDetail/JobInvocationConstants.js +6 -4
  13. data/webpack/JobInvocationDetail/JobInvocationDetail.scss +5 -3
  14. data/webpack/JobInvocationDetail/JobInvocationHostTable.js +92 -46
  15. data/webpack/JobInvocationDetail/JobInvocationSelectors.js +1 -9
  16. data/webpack/JobInvocationDetail/JobInvocationSystemStatusChart.js +5 -2
  17. data/webpack/JobInvocationDetail/JobInvocationToolbarButtons.js +10 -12
  18. data/webpack/JobInvocationDetail/TemplateInvocation.js +38 -26
  19. data/webpack/JobInvocationDetail/TemplateInvocationComponents/OutputCodeBlock.js +2 -1
  20. data/webpack/JobInvocationDetail/TemplateInvocationComponents/TemplateActionButtons.js +17 -6
  21. data/webpack/JobInvocationDetail/TemplateInvocationComponents/index.scss +0 -1
  22. data/webpack/JobInvocationDetail/__tests__/JobInvocationHostTablePolling.test.js +369 -0
  23. data/webpack/JobInvocationDetail/__tests__/JobInvocationPolling.test.js +228 -0
  24. data/webpack/JobInvocationDetail/__tests__/MainInformation.test.js +205 -154
  25. data/webpack/JobInvocationDetail/__tests__/TemplateInvocationPolling.test.js +254 -0
  26. data/webpack/JobInvocationDetail/__tests__/fixtures.js +2 -0
  27. data/webpack/JobInvocationDetail/index.js +25 -25
  28. data/webpack/JobWizard/JobWizard.js +5 -1
  29. data/webpack/JobWizard/JobWizard.scss +21 -0
  30. data/webpack/JobWizard/JobWizardConstants.js +10 -1
  31. data/webpack/JobWizard/steps/Schedule/RepeatHour.js +4 -2
  32. data/webpack/JobWizard/steps/Schedule/RepeatWeek.js +4 -2
  33. data/webpack/JobWizard/steps/Schedule/ScheduleRecurring.js +2 -1
  34. data/webpack/JobWizard/steps/Schedule/ScheduleType.js +4 -1
  35. data/webpack/JobWizard/steps/form/DateTimePicker.js +12 -6
  36. data/webpack/JobWizard/steps/form/FormHelpers.js +5 -4
  37. data/webpack/react_app/components/FeaturesDropdown/index.scss +2 -0
  38. data/webpack/react_app/components/TargetingHosts/TargetingHostsLabelsRow.scss +6 -2
  39. data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHostsPage.test.js.snap +1 -1
  40. data/webpack/react_app/components/TargetingHosts/index.js +2 -1
  41. data/webpack/react_app/redux/actions/jobInvocations/index.js +3 -2
  42. data/webpack/test_setup.js +1 -13
  43. metadata +7 -4
  44. data/webpack/__mocks__/foremanReact/constants.js +0 -25
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 87f61f660cc9bb15e87453747aca6cef5d600d4fd3994d090c5af8a4e48f1b8a
4
- data.tar.gz: 16e86afd877b2c2dec48daba1ff05c98822208a9c777d5351a9bf04edbd21322
3
+ metadata.gz: 248ba991802a19c3ad70d4c93a1a80931065c77f5e075520c01d35d07d3ef4e3
4
+ data.tar.gz: 83cad360b0893506f1ace4b8c38e60bdab716a74aaa2ff0a741908059f92e56f
5
5
  SHA512:
6
- metadata.gz: 16a08a5afdbab46351b5fe9eb4fcf55c82ad0a56160585dbedbec48a32e2d47c9f595a8562505d5c97f69141d5e6174c0fcbb6e3fd0df481dd692b6c2b811b91
7
- data.tar.gz: f1e886b6f85fc08820986b812d2d959b65c42e49ab411a42df47da1ec5ad077842f472f58e78f635fd71712fdf15a7bf2541d6921fc1d3788ebec9be1bcad995
6
+ metadata.gz: d4221e0916237573f00934dac48f0e169cb78ac278ec127b6f57e72cb8982ba12e79624a082986efe878ccbb325dd426ee41dcf0f2a155e08830791e389e3124
7
+ data.tar.gz: 927fa72b970f31090f729ade6acdbf7b73f33c4ca337af7e3ae4f0dfc998d0f77bc69a6bc32ede1ba3a052a46e98acf38d016277ab0085c75b0bd1edeec93068
@@ -117,12 +117,14 @@ module Api
117
117
  param_group :search_and_pagination, ::Api::V2::BaseController
118
118
  add_scoped_search_description_for(JobInvocation)
119
119
  param :id, :identifier, :required => true
120
+ param :include_permissions, :bool, :required => false, :desc => N_('Include per-host task and permission data in the response')
120
121
  def hosts
121
122
  set_hosts_and_template_invocations
122
123
  @total = @hosts.size
123
124
  @hosts = @hosts.search_for(params[:search], :order => params[:order]).paginate(:page => params[:page], :per_page => params[:per_page])
124
125
  @subtotal = @hosts.total_entries
125
126
  set_statuses_and_smart_proxies
127
+ set_tasks_and_permissions if Foreman::Cast.to_bool(params[:include_permissions])
126
128
  if params[:awaiting]
127
129
  @hosts = @hosts.select { |host| @host_statuses[host.id] == 'N/A' }
128
130
  end
@@ -321,15 +323,35 @@ module Api
321
323
  template_invocations = @template_invocations.where(host_id: @hosts.select(:id))
322
324
  .includes(:run_host_job_task).to_a
323
325
  hosts = @hosts.to_a
324
- template_invocations_by_host_id = template_invocations.index_by(&:host_id)
326
+ @template_invocations_by_host_id = template_invocations.index_by(&:host_id)
325
327
  @host_statuses = hosts.to_h do |host|
326
- template_invocation = template_invocations_by_host_id[host.id]
328
+ template_invocation = @template_invocations_by_host_id[host.id]
327
329
  task = template_invocation.try(:run_host_job_task)
328
330
  [host.id, template_invocation_status(task, @job_invocation.task)]
329
331
  end
330
332
  @smart_proxy_id = template_invocations.to_h { |ti| [ti.host_id, ti.smart_proxy_id] }
331
333
  @smart_proxy_name = template_invocations.to_h { |ti| [ti.host_id, ti.smart_proxy_name] }
332
334
  end
335
+
336
+ def set_tasks_and_permissions
337
+ can_cancel = authorized_for(:permission => :cancel_job_invocations, :auth_object => @job_invocation)
338
+ can_create = authorized_for(controller: :job_invocations, action: :create)
339
+ can_execute_on_infra = User.current.can?(:execute_jobs_on_infrastructure_hosts)
340
+
341
+ hosts = @hosts.to_a
342
+ @task_by_host = hosts.to_h do |host|
343
+ task = @template_invocations_by_host_id[host.id].try(:run_host_job_task)
344
+ [host.id, task ? { :id => task.id, :cancellable => task.try(:cancellable?) || false } : nil]
345
+ end
346
+ @permissions_by_host = hosts.to_h do |host|
347
+ task = @template_invocations_by_host_id[host.id].try(:run_host_job_task)
348
+ [host.id, {
349
+ :view_foreman_tasks => authorized_for(:permission => :view_foreman_tasks, :auth_object => task),
350
+ :cancel_job_invocations => can_cancel,
351
+ :execute_jobs => can_create && (!host.infrastructure_host? || can_execute_on_infra),
352
+ }]
353
+ end
354
+ end
333
355
  end
334
356
  end
335
357
  end
@@ -149,33 +149,6 @@ class JobInvocationsController < ApplicationController
149
149
  render :json => {:job_invocations => job_invocations}
150
150
  end
151
151
 
152
- def list_jobs_hosts
153
- @job_invocation = resource_base.find(params[:id])
154
- hosts = @job_invocation.targeting.hosts.authorized(:view_hosts, Host)
155
- hosts = hosts.search_for(params[:search])
156
- template_invocations_task_by_hosts = {}
157
- hosts.each do |host|
158
- template_invocation = @job_invocation.template_invocations.find { |template_inv| template_inv.host_id == host.id }
159
- next unless template_invocation
160
- template_invocation_task = template_invocation.run_host_job_task
161
- template_invocations_task_by_hosts[host.id] =
162
- {
163
- :host_name => host.name,
164
- :id => host.id,
165
- :task => template_invocation_task.attributes.merge({cancellable: template_invocation_task.cancellable? }),
166
- :permissions => {
167
- :view_foreman_tasks => authorized_for(:permission => :view_foreman_tasks, :auth_object => template_invocation_task),
168
- :cancel_job_invocations => authorized_for(:permission => :cancel_job_invocations, :auth_object => @job_invocation),
169
- :execute_jobs => authorized_for(controller: :job_invocations, action: :create) && (!host.infrastructure_host? || User.current.can?(:execute_jobs_on_infrastructure_hosts)),
170
- },
171
- }
172
- end
173
-
174
- render json: {
175
- :template_invocations_task_by_hosts => template_invocations_task_by_hosts,
176
- }
177
- end
178
-
179
152
  private
180
153
 
181
154
  def action_permission
@@ -186,7 +159,7 @@ class JobInvocationsController < ApplicationController
186
159
  'create'
187
160
  when 'cancel'
188
161
  'cancel'
189
- when 'chart', 'preview_job_invocations_per_host', 'list_jobs_hosts'
162
+ when 'chart', 'preview_job_invocations_per_host'
190
163
  'view'
191
164
  else
192
165
  super
@@ -13,3 +13,11 @@ end
13
13
  node :smart_proxy_name do |host|
14
14
  @smart_proxy_name[host.id]
15
15
  end
16
+
17
+ node(:task, :if => ->(_host) { @task_by_host }) do |host|
18
+ @task_by_host[host.id]
19
+ end
20
+
21
+ node(:permissions, :if => ->(_host) { @permissions_by_host }) do |host|
22
+ @permissions_by_host[host.id]
23
+ end
@@ -34,6 +34,7 @@ end
34
34
 
35
35
  child :task do
36
36
  attributes :id, :state, :started_at
37
+ node(:cancellable) { |task| task.try(:cancellable?) }
37
38
  end
38
39
 
39
40
  if @template_invocations
@@ -0,0 +1,4 @@
1
+ <% if @template.locked? -%>
2
+ <%= locked_warning(::Template.find(params[:id])) %>
3
+ <% end -%>
4
+
data/config/routes.rb CHANGED
@@ -35,7 +35,6 @@ Rails.application.routes.draw do
35
35
  get 'auto_complete_search'
36
36
  end
37
37
  member do
38
- get 'hosts', to: 'job_invocations#list_jobs_hosts'
39
38
  post 'cancel'
40
39
  end
41
40
  end
@@ -1,5 +1,5 @@
1
1
  Foreman::Plugin.register :foreman_remote_execution do
2
- requires_foreman '>= 3.17'
2
+ requires_foreman '>= 5.0'
3
3
  register_global_js_file 'global'
4
4
  register_gettext
5
5
 
@@ -134,9 +134,9 @@ Foreman::Plugin.register :foreman_remote_execution do
134
134
  permission :lock_job_templates, { :job_templates => [:lock, :unlock] }, :resource_type => 'JobTemplate'
135
135
  permission :create_job_invocations, { :job_invocations => [:new, :create, :legacy_create, :refresh, :rerun, :preview_hosts],
136
136
  'api/v2/job_invocations' => [:create, :rerun] }, :resource_type => 'JobInvocation'
137
- permission :view_job_invocations, { :job_invocations => [:index, :chart, :show, :auto_complete_search, :preview_job_invocations_per_host, :list_jobs_hosts], :template_invocations => [:show, :show_template_invocation_by_host],
137
+ permission :view_job_invocations, { :job_invocations => [:index, :chart, :show, :auto_complete_search, :preview_job_invocations_per_host], :template_invocations => [:show, :show_template_invocation_by_host],
138
138
  'api/v2/job_invocations' => [:index, :show, :output, :raw_output, :outputs, :hosts] }, :resource_type => 'JobInvocation'
139
- permission :view_template_invocations, { :template_invocations => [:show, :template_invocation_preview, :show_template_invocation_by_host], :job_invocations => [:list_jobs_hosts],
139
+ permission :view_template_invocations, { :template_invocations => [:show, :template_invocation_preview, :show_template_invocation_by_host],
140
140
  'api/v2/template_invocations' => [:template_invocations], :ui_job_wizard => [:job_invocation] }, :resource_type => 'TemplateInvocation'
141
141
  permission :create_template_invocations, {}, :resource_type => 'TemplateInvocation'
142
142
  permission :execute_jobs_on_infrastructure_hosts, {}, :resource_type => 'JobInvocation'
@@ -1,3 +1,3 @@
1
1
  module ForemanRemoteExecution
2
- VERSION = '16.7.0'.freeze
2
+ VERSION = '17.1.0'.freeze
3
3
  end
@@ -284,20 +284,59 @@ module Api
284
284
  end
285
285
 
286
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!
287
+ setup do
288
+ @hosts_invocation = FactoryBot.create(:job_invocation, :with_template, :with_task)
289
+ 2.times { @hosts_invocation.template_invocations << FactoryBot.create(:template_invocation, :with_task, :with_host, :job_invocation => @hosts_invocation) }
290
+ @hosts_invocation.job_category = @hosts_invocation.pattern_template_invocations.first.template.job_category
291
+ @hosts_invocation.targeting.hosts = @hosts_invocation.template_invocations.map(&:host)
292
+ @hosts_invocation.save!
293
+ end
293
294
 
294
- get :hosts, params: { :id => invocation.id, :page => 2, :per_page => 1 }
295
+ test 'should compute job_status for the paginated subset' do
296
+ get :hosts, params: { :id => @hosts_invocation.id, :page => 2, :per_page => 1 }
295
297
  assert_response :success
296
298
  result = ActiveSupport::JSON.decode(@response.body)
297
299
  assert_equal 3, result['total']
298
300
  assert_equal 1, result['results'].size
299
301
  assert_equal(['success'], result['results'].map { |r| r['job_status'] })
300
302
  end
303
+
304
+ test 'should not include task and permissions without include_permissions' do
305
+ get :hosts, params: { :id => @hosts_invocation.id }
306
+ assert_response :success
307
+ result = ActiveSupport::JSON.decode(@response.body)
308
+ result['results'].each do |host|
309
+ assert_not host.key?('task'), 'Expected task to be absent'
310
+ assert_not host.key?('permissions'), 'Expected permissions to be absent'
311
+ end
312
+ end
313
+
314
+ test 'should include task and permissions with include_permissions=true' do
315
+ get :hosts, params: { :id => @hosts_invocation.id, :include_permissions => true }
316
+ assert_response :success
317
+ result = ActiveSupport::JSON.decode(@response.body)
318
+ result['results'].each do |host|
319
+ assert host.key?('task'), 'Expected task to be present'
320
+ assert host.key?('permissions'), 'Expected permissions to be present'
321
+ permissions = host['permissions']
322
+ assert permissions.key?('view_foreman_tasks')
323
+ assert permissions.key?('cancel_job_invocations')
324
+ assert permissions.key?('execute_jobs')
325
+ end
326
+ end
327
+
328
+ test 'should handle hosts with nil task gracefully when include_permissions=true' do
329
+ invocation = FactoryBot.create(:job_invocation, :with_template, :with_task, :with_unplanned_host)
330
+ invocation.job_category = invocation.pattern_template_invocations.first.template.job_category
331
+ invocation.save!
332
+
333
+ get :hosts, params: { :id => invocation.id, :include_permissions => true }
334
+ assert_response :success
335
+ result = ActiveSupport::JSON.decode(@response.body)
336
+ result['results'].each do |host|
337
+ assert host.key?('permissions'), 'Expected permissions to be present'
338
+ end
339
+ end
301
340
  end
302
341
 
303
342
  describe 'raw output' do
@@ -1,52 +1,73 @@
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, get } from 'foremanReact/redux/API';
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
- UPDATE_JOB,
9
+ STATUS,
10
+ AUTO_REFRESH_INTERVAL_MS,
16
11
  } from './JobInvocationConstants';
17
12
 
18
- export const getJobInvocation = url => dispatch => {
19
- const fetchData = withInterval(
20
- get({
21
- key: JOB_INVOCATION_KEY,
22
- params: { include_permissions: true, include_hosts: false },
23
- url,
24
- handleError: () => {
25
- dispatch(stopInterval(JOB_INVOCATION_KEY));
26
- },
27
- errorToast: ({ response }) =>
28
- // eslint-disable-next-line camelcase
29
- response?.data?.error?.full_messages?.[0] ||
30
- // eslint-disable-next-line camelcase
31
- response?.data?.error?.full_messages ||
32
- response?.data?.error?.message ||
33
- 'Error',
34
- }),
35
- 1000
36
- );
13
+ export const isJobFinished = statusLabel =>
14
+ statusLabel === STATUS.FAILED ||
15
+ statusLabel === STATUS.SUCCEEDED ||
16
+ statusLabel === STATUS.CANCELLED;
17
+
18
+ const extractErrorMessage = response =>
19
+ // eslint-disable-next-line camelcase
20
+ response?.data?.error?.full_messages?.[0] ||
21
+ response?.data?.error?.message ||
22
+ __('Unknown error.');
23
+
24
+ const createPollState = cancel => ({ timeoutId: null, cancel });
25
+
26
+ export const getJobInvocation = (url, pollTimeoutRef) => dispatch => {
27
+ let cancelled = false;
28
+ let isFirstCall = true;
37
29
 
38
- dispatch(fetchData);
30
+ const poll = () => {
31
+ if (cancelled) return;
32
+ const params = { include_hosts: false };
33
+ if (isFirstCall) {
34
+ params.include_permissions = true;
35
+ isFirstCall = false;
36
+ }
37
+
38
+ dispatch(
39
+ APIActions.get({
40
+ key: JOB_INVOCATION_KEY,
41
+ params,
42
+ url,
43
+ handleSuccess: ({ data }) => {
44
+ if (cancelled) return;
45
+ // eslint-disable-next-line camelcase
46
+ pollTimeoutRef.current.timeoutId = isJobFinished(data?.status_label)
47
+ ? null
48
+ : setTimeout(poll, AUTO_REFRESH_INTERVAL_MS);
49
+ },
50
+ handleError: () => {
51
+ if (cancelled) return;
52
+ pollTimeoutRef.current.timeoutId = null;
53
+ },
54
+ errorToast: ({ response }) => extractErrorMessage(response),
55
+ })
56
+ );
57
+ };
58
+
59
+ stopJobInvocationPolling(pollTimeoutRef);
60
+ pollTimeoutRef.current = createPollState(() => {
61
+ cancelled = true;
62
+ });
63
+
64
+ poll();
39
65
  };
40
66
 
41
- export const updateJob = jobId => dispatch => {
42
- const url = foremanUrl(`/api/job_invocations/${jobId}`);
43
- dispatch(
44
- APIActions.get({
45
- url,
46
- key: UPDATE_JOB,
47
- params: { include_hosts: false },
48
- })
49
- );
67
+ export const stopJobInvocationPolling = pollTimeoutRef => {
68
+ clearTimeout(pollTimeoutRef.current.timeoutId);
69
+ pollTimeoutRef.current.cancel();
70
+ pollTimeoutRef.current = createPollState(() => {});
50
71
  };
51
72
 
52
73
  export const cancelJob = (jobId, force) => dispatch => {
@@ -54,10 +75,6 @@ export const cancelJob = (jobId, force) => dispatch => {
54
75
  force
55
76
  ? sprintf(__('Trying to abort the job %s.'), jobId)
56
77
  : sprintf(__('Trying to cancel the job %s.'), jobId);
57
- const errorToast = response =>
58
- force
59
- ? sprintf(__(`Could not abort the job %s: ${response}`), jobId)
60
- : sprintf(__(`Could not cancel the job %s: ${response}`), jobId);
61
78
  const url = force
62
79
  ? `/job_invocations/${jobId}/cancel?force=true`
63
80
  : `/job_invocations/${jobId}/cancel`;
@@ -67,12 +84,17 @@ export const cancelJob = (jobId, force) => dispatch => {
67
84
  url,
68
85
  key: CANCEL_JOB,
69
86
  errorToast: ({ response }) =>
70
- errorToast(
71
- // eslint-disable-next-line camelcase
72
- response?.data?.error?.full_messages ||
73
- response?.data?.error?.message ||
74
- 'Unknown error.'
75
- ),
87
+ force
88
+ ? sprintf(
89
+ __('Could not abort the job %s: %s'),
90
+ jobId,
91
+ extractErrorMessage(response)
92
+ )
93
+ : sprintf(
94
+ __('Could not cancel the job %s: %s'),
95
+ jobId,
96
+ extractErrorMessage(response)
97
+ ),
76
98
  handleSuccess: () => {
77
99
  dispatch(
78
100
  addToast({
@@ -81,40 +103,16 @@ export const cancelJob = (jobId, force) => dispatch => {
81
103
  message: infoToast(),
82
104
  })
83
105
  );
84
- dispatch(updateJob(jobId));
85
106
  },
86
107
  })
87
108
  );
88
109
  };
89
110
 
90
- export const getTask = taskId => dispatch => {
91
- dispatch(
92
- get({
93
- key: GET_TASK,
94
- url: `/foreman_tasks/api/tasks/${taskId}`,
95
- })
96
- );
97
- };
98
-
99
- export const enableRecurringLogic = (
100
- recurrenceId,
101
- enabled,
102
- jobId
103
- ) => dispatch => {
111
+ export const enableRecurringLogic = (recurrenceId, enabled) => dispatch => {
104
112
  const successToast = () =>
105
113
  enabled
106
114
  ? sprintf(__('Recurring logic %s disabled successfully.'), recurrenceId)
107
115
  : sprintf(__('Recurring logic %s enabled successfully.'), recurrenceId);
108
- const errorToast = response =>
109
- enabled
110
- ? sprintf(
111
- __(`Could not disable recurring logic %s: ${response}`),
112
- recurrenceId
113
- )
114
- : sprintf(
115
- __(`Could not enable recurring logic %s: ${response}`),
116
- recurrenceId
117
- );
118
116
  const url = `/foreman_tasks/api/recurring_logics/${recurrenceId}`;
119
117
  dispatch(
120
118
  APIActions.put({
@@ -123,25 +121,24 @@ export const enableRecurringLogic = (
123
121
  params: { recurring_logic: { enabled: !enabled } },
124
122
  successToast,
125
123
  errorToast: ({ response }) =>
126
- errorToast(
127
- // eslint-disable-next-line camelcase
128
- response?.data?.error?.full_messages ||
129
- response?.data?.error?.message ||
130
- 'Unknown error.'
131
- ),
132
- handleSuccess: () => dispatch(updateJob(jobId)),
124
+ enabled
125
+ ? sprintf(
126
+ __('Could not disable recurring logic %s: %s'),
127
+ recurrenceId,
128
+ extractErrorMessage(response)
129
+ )
130
+ : sprintf(
131
+ __('Could not enable recurring logic %s: %s'),
132
+ recurrenceId,
133
+ extractErrorMessage(response)
134
+ ),
133
135
  })
134
136
  );
135
137
  };
136
138
 
137
- export const cancelRecurringLogic = (recurrenceId, jobId) => dispatch => {
139
+ export const cancelRecurringLogic = recurrenceId => dispatch => {
138
140
  const successToast = () =>
139
141
  sprintf(__('Recurring logic %s cancelled successfully.'), recurrenceId);
140
- const errorToast = response =>
141
- sprintf(
142
- __(`Could not cancel recurring logic %s: ${response}`),
143
- recurrenceId
144
- );
145
142
  const url = `/foreman_tasks/recurring_logics/${recurrenceId}/cancel`;
146
143
  dispatch(
147
144
  APIActions.post({
@@ -149,13 +146,11 @@ export const cancelRecurringLogic = (recurrenceId, jobId) => dispatch => {
149
146
  key: CANCEL_RECURRING_LOGIC,
150
147
  successToast,
151
148
  errorToast: ({ response }) =>
152
- errorToast(
153
- // eslint-disable-next-line camelcase
154
- response?.data?.error?.full_messages ||
155
- response?.data?.error?.message ||
156
- 'Unknown error.'
149
+ sprintf(
150
+ __('Could not cancel recurring logic %s: %s'),
151
+ recurrenceId,
152
+ extractErrorMessage(response)
157
153
  ),
158
- handleSuccess: () => dispatch(updateJob(jobId)),
159
154
  })
160
155
  );
161
156
  };
@@ -6,9 +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 UPDATE_JOB = 'UPDATE_JOB';
10
9
  export const CANCEL_JOB = 'CANCEL_JOB';
11
- export const GET_TASK = 'GET_TASK';
12
10
  export const GET_TEMPLATE_INVOCATIONS = 'GET_TEMPLATE_INVOCATIONS';
13
11
  export const CHANGE_ENABLED_RECURRING_LOGIC = 'CHANGE_ENABLED_RECURRING_LOGIC';
14
12
  export const CANCEL_RECURRING_LOGIC = 'CANCEL_RECURRING_LOGIC';
@@ -17,12 +15,15 @@ export const GET_REPORT_TEMPLATE_INPUTS = 'GET_REPORT_TEMPLATE_INPUTS';
17
15
  export const JOB_INVOCATION_HOSTS = 'JOB_INVOCATION_HOSTS';
18
16
  export const GET_TEMPLATE_INVOCATION = 'GET_TEMPLATE_INVOCATION';
19
17
  export const DIRECT_OPEN_HOST_LIMIT = 3;
20
- export const ALL_JOB_HOSTS = 'ALL_JOB_HOSTS';
21
18
  export const AWAITING_STATUS_FILTER = '(job_invocation.result = N/A)';
22
19
 
20
+ export const AUTO_REFRESH_INTERVAL_MS = 5000;
21
+ export const DEFAULT_CHART_LEGEND_WIDTH = 270;
22
+ export const CLIPBOARD_COPIED_EXIT_DELAY_MS = 1500;
23
+ export const CLIPBOARD_DEFAULT_EXIT_DELAY_MS = 600;
24
+
23
25
  export const showTemplateInvocationUrl = (hostID, jobID) =>
24
26
  `/show_template_invocation_by_host/${hostID}/job_invocation/${jobID}`;
25
- export const LIST_TEMPLATE_INVOCATIONS = 'LIST_TEMPLATE_INVOCATIONS';
26
27
 
27
28
  export const templateInvocationPageUrl = (hostID, jobID) =>
28
29
  `/job_invocations_detail/${jobID}/host_invocation/${hostID}`;
@@ -38,6 +39,7 @@ export const STATUS = {
38
39
  SUCCEEDED: 'succeeded',
39
40
  FAILED: 'failed',
40
41
  CANCELLED: 'cancelled',
42
+ RUNNING: 'running',
41
43
  };
42
44
 
43
45
  export const STATUS_UPPERCASE = {
@@ -1,5 +1,6 @@
1
1
  .job-invocation-detail-flex {
2
2
  $chart_size: 105px;
3
+
3
4
  padding-top: 0px;
4
5
  padding-left: 10px;
5
6
  display: flex;
@@ -92,16 +93,17 @@ section.job-additional-info {
92
93
  max-height: 25em;
93
94
  }
94
95
  }
96
+
95
97
  div.invocation-output {
96
98
  display: block;
97
99
  padding: 9.5px;
98
100
  margin: 0 0 10px;
99
101
  font-size: 12px;
100
102
  word-break: break-all;
101
- word-wrap: break-word;
103
+ overflow-wrap: break-word;
102
104
  color: rgba(255, 255, 255, 1);
103
105
  background-color: rgba(47, 47, 47, 1);
104
- border: 1px solid #000000;
106
+ border: 1px solid #000;
105
107
  border-radius: 0px;
106
108
  font-family: Menlo, Monaco, Consolas, monospace;
107
109
 
@@ -127,7 +129,7 @@ section.job-additional-info {
127
129
  }
128
130
 
129
131
  a {
130
- color: #ffffff;
132
+ color: #fff;
131
133
  }
132
134
 
133
135
  a.scroll-link{