foreman_remote_execution 1.5.0 → 1.5.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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.babelrc +9 -0
  3. data/.eslintignore +3 -0
  4. data/.eslintrc +49 -0
  5. data/.gitignore +1 -0
  6. data/.hound.yml +5 -0
  7. data/.rubocop.yml +4 -1
  8. data/.rubocop_todo.yml +63 -35
  9. data/.travis.yml +6 -0
  10. data/app/assets/javascripts/foreman_remote_execution/template_invocation.js +1 -1
  11. data/app/assets/stylesheets/foreman_remote_execution/job_invocations.css.scss +0 -14
  12. data/app/controllers/job_invocations_controller.rb +18 -0
  13. data/app/helpers/job_invocations_chart_helper.rb +77 -0
  14. data/app/helpers/job_invocations_helper.rb +79 -0
  15. data/app/helpers/remote_execution_helper.rb +10 -50
  16. data/app/models/job_invocation.rb +4 -0
  17. data/app/models/remote_execution_provider.rb +4 -0
  18. data/app/views/job_invocations/_card_results.html.erb +11 -0
  19. data/app/views/job_invocations/_card_schedule.html.erb +32 -0
  20. data/app/views/job_invocations/_card_target_hosts.html.erb +33 -0
  21. data/app/views/job_invocations/_card_user_input.html.erb +16 -0
  22. data/app/views/job_invocations/_tab_hosts.html.erb +1 -1
  23. data/app/views/job_invocations/_tab_overview.html.erb +25 -55
  24. data/app/views/job_invocations/_tab_preview_templates.html.erb +20 -0
  25. data/app/views/job_invocations/_user_input.html.erb +21 -0
  26. data/app/views/job_invocations/show.html.erb +14 -8
  27. data/app/views/job_invocations/show.js.erb +3 -6
  28. data/config/routes.rb +1 -0
  29. data/lib/foreman_remote_execution/engine.rb +2 -2
  30. data/lib/foreman_remote_execution/version.rb +1 -1
  31. data/package.json +62 -0
  32. data/test/unit/job_invocation_test.rb +15 -0
  33. data/webpack/index.js +29 -0
  34. data/webpack/react_app/components/jobInvocations/AggregateStatus/index.js +34 -0
  35. data/webpack/react_app/components/jobInvocations/AggregateStatus/index.test.js +36 -0
  36. data/webpack/react_app/components/jobInvocations/index.js +58 -0
  37. data/webpack/react_app/redux/actions/jobInvocations/index.js +74 -0
  38. data/webpack/react_app/redux/consts.js +6 -0
  39. data/webpack/react_app/redux/reducers/index.js +6 -0
  40. data/webpack/react_app/redux/reducers/jobInvocations/index.fixtures.js +78 -0
  41. data/webpack/react_app/redux/reducers/jobInvocations/index.js +32 -0
  42. data/webpack/react_app/redux/reducers/jobInvocations/index.test.js +37 -0
  43. data/webpack/test_setup.js +11 -0
  44. metadata +26 -2
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal:true
2
+
3
+ module JobInvocationsHelper
4
+ def minicard(icon, number, text)
5
+ content_tag(:div, :class => 'card-pf card-pf-accented
6
+ card-pf-aggregate-status card-pf-aggregate-status-mini') do
7
+ content_tag(:h2, :class => 'card-pf-title', :style => 'line-height: 1.1') do
8
+ icon_text(icon, '', :kind => 'pficon') +
9
+ content_tag(:span, number, :class =>'card-pf-aggregate-status-count') +
10
+ text
11
+ end
12
+ end
13
+ end
14
+
15
+ def job_invocation_task_buttons(task)
16
+ job_invocation = task.task_groups.find { |group| group.class == JobInvocationTaskGroup }.job_invocation
17
+ buttons = []
18
+ if authorized_for(hash_for_new_job_invocation_path)
19
+ buttons << link_to(_('Rerun'), rerun_job_invocation_path(:id => job_invocation.id),
20
+ :class => 'btn btn-default',
21
+ :title => _('Rerun the job'))
22
+ end
23
+ if authorized_for(hash_for_new_job_invocation_path)
24
+ buttons << link_to(_('Rerun failed'), rerun_job_invocation_path(:id => job_invocation.id, :failed_only => 1),
25
+ :class => 'btn btn-default',
26
+ :disabled => task.sub_tasks.none? { |sub_task| task_failed?(sub_task) },
27
+ :title => _('Rerun on failed hosts'))
28
+ end
29
+ if authorized_for(:permission => :view_foreman_tasks, :auth_object => task)
30
+ buttons << link_to(_('Job Task'), foreman_tasks_task_path(task),
31
+ :class => 'btn btn-default',
32
+ :title => _('See the last task details'))
33
+ end
34
+ if authorized_for(:permission => :edit_foreman_tasks, :auth_object => task)
35
+ buttons << link_to(_('Cancel Job'), cancel_foreman_tasks_task_path(task),
36
+ :class => 'btn btn-danger',
37
+ :title => _('Try to cancel the job'),
38
+ :disabled => !task.cancellable?,
39
+ :method => :post)
40
+ end
41
+ buttons
42
+ end
43
+
44
+ def job_invocations_buttons
45
+ [
46
+ documentation_button_rex('3.2ExecutingaJob'),
47
+ display_link_if_authorized(_('Run Job'), hash_for_new_job_invocation_path)
48
+ ]
49
+ end
50
+
51
+ def template_name_and_provider_link(template)
52
+ template_name = content_tag(:strong, template.name)
53
+ provider = template.provider.humanized_name
54
+ link_content = template_name + ' - ' + provider + ' ' +
55
+ icon_text('edit', '', :kind => 'pficon')
56
+ link_to_if_authorized(link_content,
57
+ hash_for_edit_job_template_path(:id => template.id))
58
+ end
59
+
60
+ def preview_hosts(template_invocation)
61
+ hosts = template_invocation.targeting.hosts.take(20)
62
+ hosts.map do |host|
63
+ collapsed_preview(host) +
64
+ render(:partial => 'job_invocations/user_input',
65
+ :locals => { :template_invocation => template_invocation,
66
+ :target => host })
67
+ end.reduce(:+)
68
+ end
69
+
70
+ def collapsed_preview(target)
71
+ title = target.try(:name) || 'N/A'
72
+ content_tag(:h5,
73
+ :class => "expander collapsed out",
74
+ :data => { :toggle => 'collapse',
75
+ :target => "#preview_#{target.id}" }) do
76
+ content_tag(:span, '', :class => 'caret') + title
77
+ end
78
+ end
79
+ end
@@ -8,47 +8,10 @@ module RemoteExecutionHelper
8
8
  TemplateInput::TYPES.map { |key, name| [ _(name), key ] }
9
9
  end
10
10
 
11
- def job_invocation_chart(invocation)
12
- options = { :class => 'statistics-pie small', :expandable => true, :border => 0, :show_title => true }
13
-
14
- if invocation.task
15
- # Request precise subtask counts only if the task is stopped
16
- report = invocation.progress_report
17
- flot_pie_chart('status', job_invocation_status(invocation, report[:progress]),
18
- [{:label => _('Success'), :data => report[:success], :color => '#5CB85C'},
19
- {:label => _('Failed'), :data => report[:failed], :color => '#D9534F'},
20
- {:label => _('Pending'), :data => report[:pending], :color => '#DEDEDE'},
21
- {:label => _('Cancelled'), :data => report[:cancelled], :color => '#B7312D'}],
22
- options)
23
- else
24
- content_tag(:h4, job_invocation_status(invocation))
25
- end
26
- end
27
-
28
11
  def job_hosts_authorizer
29
12
  @job_hosts_authorizer ||= Authorizer.new(User.current, :collection => @hosts)
30
13
  end
31
14
 
32
- def job_invocation_status(invocation, percent = nil, verbose = true)
33
- case invocation.status
34
- when HostStatus::ExecutionStatus::QUEUED
35
- if verbose && invocation.task
36
- _('queued to start executing in %{time}') % {:time => time_ago_in_words(invocation.task.start_at) }
37
- else
38
- _('queued')
39
- end
40
- when HostStatus::ExecutionStatus::RUNNING
41
- percent ||= invocation.progress_report[:progress]
42
- _('running %{percent}%%') % {:percent => percent}
43
- when HostStatus::ExecutionStatus::OK
44
- _('succeeded')
45
- when HostStatus::ExecutionStatus::ERROR
46
- _('failed')
47
- else
48
- _('unknown status')
49
- end
50
- end
51
-
52
15
  def host_counter(label, count)
53
16
  content_tag(:div, :class => 'host_counter') do
54
17
  content_tag(:div, label, :class => 'header') + content_tag(:div, count.to_s, :class => 'count')
@@ -77,9 +40,17 @@ module RemoteExecutionHelper
77
40
  end
78
41
 
79
42
  def template_invocation_actions(task, host, job_invocation, template_invocation)
43
+ host_task = template_invocation.try(:run_host_job_task)
80
44
  [
81
45
  display_link_if_authorized(_('Host detail'), hash_for_host_path(host).merge(:auth_object => host, :permission => :view_hosts, :authorizer => job_hosts_authorizer)),
82
46
  display_link_if_authorized(_('Rerun on %s') % host.name, hash_for_rerun_job_invocation_path(:id => job_invocation, :host_ids => [ host.id ], :authorizer => job_hosts_authorizer)),
47
+ if host_task.present?
48
+ display_link_if_authorized(
49
+ _('Host task'),
50
+ hash_for_foreman_tasks_task_path(host_task)
51
+ .merge(:auth_object => host_task, :permission => :view_foreman_tasks)
52
+ )
53
+ end
83
54
  ]
84
55
  end
85
56
 
@@ -99,7 +70,6 @@ module RemoteExecutionHelper
99
70
  job_invocation = task.task_groups.find { |group| group.class == JobInvocationTaskGroup }.job_invocation
100
71
  task_authorizer = Authorizer.new(User.current, :collection => [task])
101
72
  buttons = []
102
- buttons << link_to(_('Refresh'), {}, :class => 'btn btn-default', :title => _('Refresh this page'))
103
73
  if authorized_for(hash_for_new_job_invocation_path)
104
74
  buttons << link_to(_('Rerun'), rerun_job_invocation_path(:id => job_invocation.id),
105
75
  :class => 'btn btn-default',
@@ -130,6 +100,7 @@ module RemoteExecutionHelper
130
100
  end
131
101
  return buttons
132
102
  end
103
+
133
104
  # rubocop:enable Metrics/AbcSize
134
105
 
135
106
  def template_invocation_task_buttons(task, invocation)
@@ -151,7 +122,7 @@ module RemoteExecutionHelper
151
122
  :disabled => !task.cancellable?,
152
123
  :method => :post)
153
124
  end
154
- return buttons
125
+ buttons
155
126
  end
156
127
 
157
128
  def link_to_invocation_task_if_authorized(invocation)
@@ -200,17 +171,6 @@ module RemoteExecutionHelper
200
171
  end
201
172
  end
202
173
 
203
- def job_invocation_active_tab(tab, params)
204
- active = 'active'
205
- inactive = ''
206
- hosts_tab_active = params[:page].present? || params[:search].present? || params[:order].present?
207
- if hosts_tab_active
208
- tab == :hosts ? active : inactive
209
- else
210
- tab == :overview ? active : inactive
211
- end
212
- end
213
-
214
174
  def time_in_words_span(time)
215
175
  if time.nil?
216
176
  _('N/A')
@@ -230,6 +230,10 @@ class JobInvocation < ApplicationRecord
230
230
  task.send(method)
231
231
  end
232
232
 
233
+ def finished?
234
+ !task.pending?
235
+ end
236
+
233
237
  private
234
238
 
235
239
  def failed_template_invocations
@@ -72,5 +72,9 @@ class RemoteExecutionProvider
72
72
  def host_setting(host, setting)
73
73
  host.params[setting.to_s] || Setting[setting]
74
74
  end
75
+
76
+ def ssh_password(_host) end
77
+
78
+ def ssh_key_passphrase(_host) end
75
79
  end
76
80
  end
@@ -0,0 +1,11 @@
1
+ <div id="job-invocation-chart">
2
+ <div class="card-pf card-pf-accented card-pf-aggregate-status">
3
+ <h2 style="height: 18px;" class="card-pf-title">
4
+ <a href="#"><%= _('Results') %></a>
5
+ </h2>
6
+ <div class="card-pf-body">
7
+ <div id='status_chart'>
8
+ </div>
9
+ </div>
10
+ </div>
11
+ </div>
@@ -0,0 +1,32 @@
1
+ <% if job_invocation.concurrency_level || job_invocation.time_span ||
2
+ (job_invocation.task && (job_invocation.task.delayed? || job_invocation.task.recurring?)) ||
3
+ job_invocation.execution_timeout_interval.present? %>
4
+ <div class="card-pf card-pf-accented">
5
+ <div class="card-pf-title">
6
+ <h2 style="height: 18px;" class="card-pf-title">
7
+ <%= _('Schedule') %>
8
+ </h2>
9
+ </div>
10
+ <div class="card-pf-body">
11
+ <p>
12
+ <ul>
13
+ <% if job_invocation.concurrency_level %>
14
+ <li><b><%= _("Concurrency level limited to") %></b>: <%= job_invocation.concurrency_level %> <%= _('tasks at a time') %><br></li>
15
+ <% end %>
16
+ <% if job_invocation.time_span %>
17
+ <li><b><%= _("Set to distribute over") %></b>: <%= job_invocation.time_span %> <%= _('seconds') %><br></li>
18
+ <% end %>
19
+ <% if job_invocation.start_before %>
20
+ <li><b><%= _("Scheduled to start before") %></b>: <%= job_invocation.start_before %><br></li>
21
+ <% end %>
22
+ <% if job_invocation.task && (job_invocation.task.delayed? || job_invocation.task.recurring?) %>
23
+ <li><b><%= _("Scheduled to start at") %></b>: <%= job_invocation.task.start_at.try(:in_time_zone) %><br></li>
24
+ <% end %>
25
+ <% if job_invocation.execution_timeout_interval.present? %>
26
+ <li><b><%= _("Timeout to kill after") %></b>: <%= job_invocation.execution_timeout_interval %> <%= _('seconds') %><br></li>
27
+ <% end %>
28
+ </ul>
29
+ </p>
30
+ </div>
31
+ </div>
32
+ <% end %>
@@ -0,0 +1,33 @@
1
+ <% template_invocations = job_invocation.pattern_template_invocations %>
2
+ <div class="card-pf card-pf-accented">
3
+ <div class="card-pf-title">
4
+ <h2 style="height: 18px;" class="card-pf-title">
5
+ <%= _('Target hosts') %>
6
+ </h2>
7
+ </div>
8
+ <div class="card-pf-body">
9
+ <p>
10
+ <% if job_invocation.bookmark.present? %>
11
+ <%= _('Bookmark') %> <%= job_invocation.bookmark.name %><br>
12
+ <% else %>
13
+ <%= _('Manual selection') %>
14
+ <% end %>
15
+ <%= _('using ') %>
16
+ <strong><%= _(Targeting::TYPES[job_invocation.targeting.targeting_type]).downcase %></strong>
17
+ <pre><%= job_invocation.targeting.search_query %></pre>
18
+ </p>
19
+ </div>
20
+ <div class='card-pf-footer'>
21
+ <p>
22
+ <%= _('Evaluated at:') %> <%= job_invocation.targeting.resolved_at %><br>
23
+ <% if template_invocations.size > 1 %>
24
+ <% template_invocations.each do |template_invocation| %>
25
+ <%= host_counter template_invocation.template.provider.humanized_name,
26
+ ForemanTasks::Task::DynflowTask.
27
+ for_action(Actions::RemoteExecution::RunHostJob).
28
+ for_resource(template_invocation).uniq.size %>
29
+ <% end %>
30
+ <% end %>
31
+ </p>
32
+ </div>
33
+ </div>
@@ -0,0 +1,16 @@
1
+ <div class="card-pf card-pf-accented">
2
+ <div class="card-pf-title">
3
+ <h2 style="height: 18px;" class="card-pf-title">
4
+ <%= _('User Inputs') %>
5
+ </h2>
6
+ </div>
7
+ <div class="card-pf-body">
8
+ <p>
9
+ <ul>
10
+ <% template_invocation.input_values.joins(:template_input).each do |input_value| %>
11
+ <li><b><%= input_value.template_input.name %></b>: <%= trunc_with_tooltip(input_value.value, 255) %></li>
12
+ <% end %>
13
+ </ul>
14
+ </p>
15
+ </div>
16
+ </div>
@@ -38,7 +38,7 @@
38
38
  </tbody>
39
39
  </table>
40
40
 
41
- <%= will_paginate_with_info @hosts %>
41
+ <%= will_paginate_with_info @hosts, :container => true %>
42
42
  <% else %>
43
43
  <div class="alert alert-warning">
44
44
  <%=
@@ -1,59 +1,29 @@
1
- <div id="status_chart">
2
- <%= job_invocation_chart(job_invocation) %>
3
- </div>
4
-
5
- <div class="col-md-6 infoblock">
6
- <h4><%= _('Target hosts') %></h4>
7
- <% if job_invocation.bookmark.present? %>
8
- <%= _('Bookmark') %> <%= job_invocation.bookmark.name %><br>
9
- <% else %>
10
- <%= _('Manual selection') %>
11
- <% end %>
12
- <%= 'using ' + _(Targeting::TYPES[job_invocation.targeting.targeting_type]) %>
13
- <pre><%= job_invocation.targeting.search_query %></pre>
14
-
15
- <%= _('Evaluated at:') %> <%= job_invocation.targeting.resolved_at %><br>
16
- <% if job_invocation.pattern_template_invocations.size > 1 %>
17
- <% job_invocation.pattern_template_invocations.each do |template_invocation| %>
18
- <%= host_counter template_invocation.template.provider.humanized_name, ForemanTasks::Task::DynflowTask.for_action(Actions::RemoteExecution::RunHostJob).for_resource(template_invocation).uniq.size %>
19
- <% end %>
20
- <% end %>
21
- <%= host_counter(_('Total hosts'), job_invocation.total_hosts_count) %>
22
- </div>
23
-
24
- <div class="col-md-6 infoblock">
25
- <h4><%= _('Providers and templates') %></h4>
26
- <% job_invocation.pattern_template_invocations.each do |template_invocation| %>
27
- <h5>
28
- <b><%= template_invocation.template.name %></b> <%= 'through' %> <%= template_invocation.template.provider.humanized_name %>
29
- </h5>
30
- <% target = template_invocation.targeting.hosts.with_os.first || template_invocation.targeting.hosts.first %>
31
- <%= _('Preview for target %s') % target.try(:name) || 'N/A' %>
32
-
33
- <%= preview_box(template_invocation, target) %>
34
-
1
+ <% template_invocations = job_invocation.pattern_template_invocations %>
2
+ <div class='row'>
3
+ <div class="col-xs-6 col-sm-6 col-md-6">
4
+ <%= render :partial => 'card_results' %>
5
+ <%= render :partial => 'card_schedule', :locals => { :job_invocation => job_invocation } %>
6
+ </div>
7
+ <div class='col-xs-12 col-sm-6 col-md-6'>
8
+ <%= render :partial => 'card_target_hosts', :locals => { :job_invocation => job_invocation } %>
9
+ <% template_invocations.each do |template_invocation| %>
10
+ <%= minicard('user', template_invocation.effective_user || Setting[:remote_execution_effective_user],
11
+ template_invocation.template.name + ' ' + _('effective user')) %>
12
+ <%= minicard('cluster', job_invocation.total_hosts_count, _('Total hosts')) %>
35
13
  <% if template_invocation.input_values.present? %>
36
- <%= _('following user inputs were provided') %>
37
- <ul>
38
- <% template_invocation.input_values.joins(:template_input).each do |input_value| %>
39
- <li><b><%= input_value.template_input.name %></b>: <%= trunc_with_tooltip(input_value.value, 255) %></li>
40
- <% end %>
41
- </ul>
42
- <% end %>
43
- <% if template_invocation.effective_user %>
44
- <b><%= _("Effective user") %></b>: <%= template_invocation.effective_user %><br>
45
- <% end %>
46
- <% if job_invocation.concurrency_level %>
47
- <b><%= _("Concurrency level limited to") %></b>: <%= job_invocation.concurrency_level %> <%= _('tasks at a time') %><br>
48
- <% end %>
49
- <% if job_invocation.time_span %>
50
- <b><%= _("Set to distribute over") %></b>: <%= job_invocation.time_span %> <%= _('seconds') %><br>
51
- <% end %>
52
- <% if job_invocation.task && (job_invocation.task.delayed? || job_invocation.task.recurring?) %>
53
- <b><%= _("Scheduled to start at") %></b>: <%= job_invocation.task.start_at.try(:in_time_zone) %><br>
54
- <% end %>
55
- <% unless job_invocation.execution_timeout_interval.nil? %>
56
- <b><%= _("Timeout to kill after") %></b>: <%= job_invocation.execution_timeout_interval %> <%= _('seconds') %><br>
14
+ <%= render :partial => 'card_user_input', :locals => { :template_invocation => template_invocation } %>
57
15
  <% end %>
58
16
  <% end %>
17
+ </div>
59
18
  </div>
19
+ <hr/>
20
+ <% if job_invocation.resolved? %>
21
+ <div id="hosts" data-refresh_required="<%= job_invocation.resolved? ? '' : 'true' %>">
22
+ <%= render :partial => 'tab_hosts', :locals => { :job_invocation => job_invocation, :hosts => hosts } %>
23
+ </div>
24
+ <% else %>
25
+ <div class="alert alert-warning">
26
+ <%= _("The dynamic query '#{job_invocation.targeting.search_query}' was not resolved yet. The list of hosts to which it would resolve now can be seen") %>
27
+ <%= link_to('here.', hosts_url(:search => job_invocation.targeting.search_query)) %>
28
+ </div>
29
+ <% end %>
@@ -0,0 +1,20 @@
1
+ <div class='row'>
2
+ <div class="col-xs-6 col-sm-12 col-md-12">
3
+ <div class="card-pf">
4
+ <div class="card-pf-heading">
5
+ <h2 style="height: 18px;" class="card-pf-title">
6
+ <%= _('Providers and templates') %>
7
+ </h2>
8
+ </div>
9
+ <div class="card-pf-body">
10
+ <p>
11
+ <% template_invocation = job_invocation.pattern_template_invocations.first %>
12
+ <h5>
13
+ <%= template_name_and_provider_link(template_invocation.template) %>
14
+ </h5>
15
+ <%= preview_hosts(template_invocation) %>
16
+ </p>
17
+ </div>
18
+ </div>
19
+ </div>
20
+ </div>
@@ -0,0 +1,21 @@
1
+ <div id='preview_<%= target.id %>' class='collapse out'>
2
+ <%= preview_box(template_invocation, target) %>
3
+ <% if template_invocation.input_values.present? %>
4
+ <table class='<%= table_css_classes %>'>
5
+ <thead>
6
+ <tr>
7
+ <th><%= _('User input') %></th>
8
+ <th><%= _('Value') %></th>
9
+ </tr>
10
+ </thead>
11
+ <tbody>
12
+ <% template_invocation.input_values.joins(:template_input).each do |input_value| %>
13
+ <tr>
14
+ <td><b><%= input_value.template_input.name %></b></td>
15
+ <td><%= input_value.value %></td>
16
+ </tr>
17
+ <% end %>
18
+ </tbody>
19
+ </table>
20
+ <% end %>
21
+ </div>