foreman_remote_execution 1.5.0 → 1.5.1

Sign up to get free protection for your applications and to get access to all the features.
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>