foreman_remote_execution 14.1.4 → 15.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/api/v2/job_invocations_controller.rb +8 -0
  3. data/app/controllers/template_invocations_controller.rb +57 -0
  4. data/app/controllers/ui_job_wizard_controller.rb +6 -3
  5. data/app/helpers/remote_execution_helper.rb +5 -6
  6. data/app/views/api/v2/job_invocations/base.json.rabl +1 -1
  7. data/app/views/api/v2/job_invocations/hosts.json.rabl +1 -1
  8. data/app/views/api/v2/job_invocations/main.json.rabl +1 -1
  9. data/app/views/api/v2/job_invocations/show.json.rabl +18 -0
  10. data/app/views/templates/script/convert2rhel_analyze.erb +4 -4
  11. data/config/routes.rb +2 -0
  12. data/lib/foreman_remote_execution/engine.rb +3 -3
  13. data/lib/foreman_remote_execution/version.rb +1 -1
  14. data/webpack/JobInvocationDetail/JobAdditionInfo.js +214 -0
  15. data/webpack/JobInvocationDetail/JobInvocationConstants.js +40 -2
  16. data/webpack/JobInvocationDetail/JobInvocationDetail.scss +70 -0
  17. data/webpack/JobInvocationDetail/JobInvocationHostTable.js +177 -80
  18. data/webpack/JobInvocationDetail/JobInvocationHostTableToolbar.js +63 -0
  19. data/webpack/JobInvocationDetail/JobInvocationSelectors.js +8 -1
  20. data/webpack/JobInvocationDetail/JobInvocationSystemStatusChart.js +61 -10
  21. data/webpack/JobInvocationDetail/OpenAlInvocations.js +111 -0
  22. data/webpack/JobInvocationDetail/TemplateInvocation.js +202 -0
  23. data/webpack/JobInvocationDetail/TemplateInvocationComponents/OutputCodeBlock.js +124 -0
  24. data/webpack/JobInvocationDetail/TemplateInvocationComponents/OutputToggleGroup.js +156 -0
  25. data/webpack/JobInvocationDetail/TemplateInvocationComponents/PreviewTemplate.js +50 -0
  26. data/webpack/JobInvocationDetail/TemplateInvocationComponents/TemplateActionButtons.js +224 -0
  27. data/webpack/JobInvocationDetail/TemplateInvocationPage.js +53 -0
  28. data/webpack/JobInvocationDetail/__tests__/MainInformation.test.js +1 -1
  29. data/webpack/JobInvocationDetail/__tests__/OpenAlInvocations.test.js +110 -0
  30. data/webpack/JobInvocationDetail/__tests__/OutputCodeBlock.test.js +69 -0
  31. data/webpack/JobInvocationDetail/__tests__/TemplateInvocation.test.js +131 -0
  32. data/webpack/JobInvocationDetail/__tests__/fixtures.js +130 -0
  33. data/webpack/JobInvocationDetail/index.js +18 -3
  34. data/webpack/JobWizard/JobWizard.js +38 -16
  35. data/webpack/JobWizard/{StartsBeforeErrorAlert.js → StartsErrorAlert.js} +16 -1
  36. data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +1 -1
  37. data/webpack/JobWizard/steps/Schedule/ScheduleFuture.js +1 -1
  38. data/webpack/JobWizard/steps/Schedule/ScheduleRecurring.js +5 -3
  39. data/webpack/JobWizard/steps/Schedule/ScheduleType.js +1 -1
  40. data/webpack/JobWizard/steps/Schedule/__tests__/Schedule.test.js +3 -3
  41. data/webpack/JobWizard/steps/form/DateTimePicker.js +13 -0
  42. data/webpack/JobWizard/steps/form/Formatter.js +1 -0
  43. data/webpack/JobWizard/steps/form/ResourceSelect.js +34 -9
  44. data/webpack/Routes/routes.js +6 -0
  45. data/webpack/__mocks__/foremanReact/Root/Context/ForemanContext/index.js +1 -0
  46. data/webpack/react_app/components/RegistrationExtension/RexPull.js +27 -2
  47. data/webpack/react_app/components/TargetingHosts/components/HostStatus.js +1 -1
  48. metadata +15 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2a300266f1fb00313f0bc59f37573802bbcf230fca5e9867bcee11bdc8279a93
4
- data.tar.gz: bc52a77d346cd77c834fd134a872fab19c8b12fc98c30f27deba4a7a64a776b0
3
+ metadata.gz: f4dc9acfc2bf412af59bead68426f726248f020964fa433156bb341eefcee3ec
4
+ data.tar.gz: cf88f8f262a83fb638e855043be9f5f5d9bd7f3c1b365131b1906ace271eb288
5
5
  SHA512:
6
- metadata.gz: ba0a20114b7d30cf6cc7fe236b34794ab98163cc9bbd6d78a02e3573c996b2fb0eaccdf96af081cfc96afb0c8a95af8ad9ec906d2b8e103806ebe6d1d74bc413
7
- data.tar.gz: fe6aa4f48b414729aa0678589b4dc55eab153b5c6505edb21700601d7bb993b4cae2465ccb33eeb24becb12c7b68be45d7a88bea67507b8917b2ad23acf738ba
6
+ metadata.gz: a50e49596517959f17ba39dd99b4729942051991ae3488f6a56e28505464dd422fd952ad1a98e3c5174d6b36c8a0658b827a69f4b8833c40707119414c39aca4
7
+ data.tar.gz: a50742f65d872373f2b1bc1a7aee03323adc42c6a667a18d12b40b0895c3ffc711658d3a300dab05163688f999f7d7c7b2546ea3219107ba8a0b2897c02f4923
@@ -22,6 +22,8 @@ module Api
22
22
  param :host_status, :bool, required: false, desc: N_('Show Job status for the hosts')
23
23
  def show
24
24
  set_hosts_and_template_invocations
25
+ @job_organization = Taxonomy.find_by(id: @job_invocation.task.input[:current_organization_id])
26
+ @job_location = Taxonomy.find_by(id: @job_invocation.task.input[:current_location_id])
25
27
  if params[:host_status] == 'true'
26
28
  set_statuses_and_smart_proxies
27
29
  end
@@ -266,7 +268,13 @@ module Api
266
268
  end
267
269
 
268
270
  def set_hosts_and_template_invocations
271
+ @pattern_template_invocations = @job_invocation.pattern_template_invocations.includes(:input_values)
269
272
  @hosts = @job_invocation.targeting.hosts.authorized(:view_hosts, Host)
273
+
274
+ unless params[:search].nil?
275
+ @hosts = @hosts.joins(:template_invocations)
276
+ .where(:template_invocations => { :job_invocation_id => @job_invocation.id})
277
+ end
270
278
  @template_invocations = @job_invocation.template_invocations
271
279
  .where(host: @hosts)
272
280
  .includes(:input_values)
@@ -1,5 +1,10 @@
1
1
  class TemplateInvocationsController < ApplicationController
2
2
  include Foreman::Controller::AutoCompleteSearch
3
+ include RemoteExecutionHelper
4
+ include JobInvocationsHelper
5
+
6
+ before_action :find_job_invocation, :only => %w{show_template_invocation_by_host}
7
+ before_action :find_host, :only => %w{show_template_invocation_by_host}
3
8
 
4
9
  def controller_permission
5
10
  'job_invocations'
@@ -17,4 +22,56 @@ class TemplateInvocationsController < ApplicationController
17
22
  @line_sets = @line_sets.drop_while { |o| o['timestamp'].to_f <= @since } if @since
18
23
  @line_counter = params[:line_counter].to_i
19
24
  end
25
+
26
+ def show_template_invocation_by_host
27
+ @template_invocation = @job_invocation.template_invocations.find { |template_inv| template_inv.host_id == @host.id }
28
+ if @template_invocation.nil?
29
+ render :json => { :error => _('Template invocation not found') }, :status => :not_found
30
+ end
31
+ @template_invocation_task = @template_invocation.run_host_job_task
32
+
33
+ lines = normalize_line_sets(@template_invocation_task.main_action.live_output)
34
+ transformed_input_values = @template_invocation.input_values.joins(:template_input).map do |input_value|
35
+ {
36
+ name: input_value&.template_input&.name,
37
+ value: input_safe_value(input_value),
38
+ }
39
+ end
40
+
41
+ auto_refresh = @job_invocation.task.try(:pending?)
42
+ finished = @job_invocation.status_label == 'failed' || @job_invocation.status_label == 'succeeded' || @job_invocation.status_label == 'cancelled'
43
+ render :json => { :output => lines, :preview => template_invocation_preview(@template_invocation, @host), :input_values => transformed_input_values, :job_invocation_description => @job_invocation.description, :task_id => @template_invocation_task.id, :task_cancellable => @template_invocation_task.cancellable?, :host_name => @host.name, :permissions => {
44
+ :view_foreman_tasks => User.current.allowed_to?(:view_foreman_tasks),
45
+ :cancel_job_invocations => User.current.allowed_to?(:cancel_job_invocations),
46
+ :execute_jobs => User.current.allowed_to?(:create_job_invocations) && (!@host.infrastructure_host? || User.current.can?(:execute_jobs_on_infrastructure_hosts)),
47
+
48
+ },
49
+ :auto_refresh => auto_refresh, :finished => finished}, status: :ok
50
+ end
51
+
52
+ private
53
+
54
+ def find_job_invocation
55
+ @job_invocation = JobInvocation.find(params[:id])
56
+ rescue ActiveRecord::RecordNotFound
57
+ render :json => { :error => { :message => format(_("Job with id '%{id}' was not found"), :id => params['id']) } }, :status => :not_found
58
+ end
59
+
60
+ def find_host
61
+ @host = Host.find(params[:host_id])
62
+ rescue ActiveRecord::RecordNotFound
63
+ render :json => { :error => { :message => format(_("Host with id '%{id}' was not found"), :id => params['host_id']) } }, :status => :not_found
64
+ end
65
+
66
+ def template_invocation_preview(template_invocation, host)
67
+ renderer = InputTemplateRenderer.new(template_invocation.template, host, template_invocation)
68
+ output = load_template_from_task(template_invocation, host) || renderer.preview
69
+ if output
70
+ {:plain => output}
71
+ else
72
+ {status: :bad_request,
73
+ plain: renderer.error_message }
74
+ end
75
+ end
76
+
20
77
  end
@@ -55,9 +55,12 @@ class UIJobWizardController < Api::V2::BaseController
55
55
 
56
56
  def resources
57
57
  resource_type = params[:resource]
58
- resource_list = resource_type.constantize.authorized("view_#{resource_type.underscore.pluralize}").all.map { |r| {:name => r.to_s, :id => r.id } }.select { |v| v[:name] =~ /#{params[:name]}/ }
59
- render :json => { :results =>
60
- resource_list.sort_by { |r| r[:name] }.take(100), :subtotal => resource_list.count}
58
+ attribute_name = resource_type.constantize.attribute_name
59
+ resource_list = resource_type.constantize.authorized("view_#{resource_type.underscore.pluralize}")
60
+ .search_for("#{attribute_name} ~ \"#{params[:name]}\"")
61
+ .reorder(attribute_name)
62
+ .limit(Setting[:entries_per_page] + 1).pluck(attribute_name, :id).map { |name, id| { name: name, id: id } }
63
+ render :json => { :results => resource_list, :subtotal => resource_list.count }
61
64
  end
62
65
 
63
66
  def job_invocation
@@ -99,12 +99,11 @@ module RemoteExecutionHelper
99
99
  :disabled => !task.cancellable?,
100
100
  :method => :post)
101
101
  end
102
- if Setting[:lab_features]
103
- buttons << link_to(_('New UI'), new_job_invocation_detail_path(:id => job_invocation.id),
104
- class: 'btn btn-default',
105
- title: _('Switch to the new job invocation detail UI'))
106
- end
107
- return buttons
102
+ buttons << link_to(_('New UI'), new_job_invocation_detail_path(:id => job_invocation.id),
103
+ class: 'btn btn-default',
104
+ title: _('Switch to the new job invocation detail UI'))
105
+
106
+ buttons
108
107
  end
109
108
 
110
109
  def template_invocation_task_buttons(task, invocation)
@@ -1,6 +1,6 @@
1
1
  object @job_invocation
2
2
 
3
- attributes :id, :description, :job_category, :targeting_id, :status, :start_at, :status_label, :ssh_user, :time_to_pickup
3
+ attributes :id, :description, :job_category, :targeting_id, :status, :start_at, :status_label, :ssh_user, :time_to_pickup, :concurrency_level, :execution_timeout_interval
4
4
 
5
5
  node do |invocation|
6
6
  pattern_template = invocation.pattern_template_invocations.first
@@ -1,6 +1,6 @@
1
1
  collection @hosts
2
2
 
3
- attribute :name, :operatingsystem_id, :operatingsystem_name, :hostgroup_id, :hostgroup_name
3
+ attribute :name, :operatingsystem_id, :operatingsystem_name, :hostgroup_id, :hostgroup_name, :id
4
4
 
5
5
  node :job_status do |host|
6
6
  @host_statuses[host.id]
@@ -16,7 +16,7 @@ node do |invocation|
16
16
  end
17
17
 
18
18
  child :targeting do
19
- attributes :bookmark_id, :search_query, :targeting_type, :user_id, :status, :status_label,
19
+ attributes :bookmark_id, :bookmark_name, :search_query, :targeting_type, :user_id, :status, :status_label,
20
20
  :randomized_ordering
21
21
 
22
22
  child @hosts => :hosts do
@@ -1,3 +1,21 @@
1
1
  object @job_invocation
2
2
 
3
+ child @pattern_template_invocations => :pattern_template_invocations do
4
+ attributes :template_id, :template_name, :host_id
5
+ child :input_values do
6
+ attributes :template_input_name, :template_input_id
7
+ node :value do |iv|
8
+ iv.template_input.respond_to?(:hidden_value) && iv.template_input.hidden_value? ? '*' * 5 : iv.value
9
+ end
10
+ end
11
+ end
12
+
3
13
  extends 'api/v2/job_invocations/main'
14
+
15
+ node :job_organization do
16
+ @job_organization
17
+ end
18
+
19
+ node :job_location do
20
+ @job_location
21
+ end
@@ -7,17 +7,17 @@ provider_type: script
7
7
  kind: job_template
8
8
  template_inputs:
9
9
  - name: ELS
10
- required: true
10
+ required: false
11
11
  input_type: user
12
12
  description: Use an Extended Lifecycle Support (ELS) add-on subscription
13
13
  advanced: false
14
14
  value_type: plain
15
- options: "true\nfalse"
16
- default: "true"
15
+ options: "yes\r\nno"
16
+ default: "no"
17
17
  hidden_value: false
18
18
  %>
19
19
  <%-
20
- els = input('ELS') == "true" ? "--els" : ""
20
+ els = input('ELS') == "yes" ? "--els" : ""
21
21
  -%>
22
22
  <% if @host.operatingsystem.family == 'Redhat' -%>
23
23
  if ! [ $(id -u) -eq 0 ]; then
data/config/routes.rb CHANGED
@@ -23,6 +23,8 @@ Rails.application.routes.draw do
23
23
  match 'old/job_invocations/new', to: 'job_invocations#new', via: [:get], as: 'form_new_job_invocation'
24
24
  match 'old/job_invocations/:id/rerun', to: 'job_invocations#rerun', via: [:get, :post], as: 'form_rerun_job_invocation'
25
25
  match 'experimental/job_invocations_detail/:id', to: 'react#index', :via => [:get], as: 'new_job_invocation_detail'
26
+ match 'job_invocations_detail/:id/host_invocation/:host_id', to: 'react#index', :via => [:get], as: 'new_job_invocation_detail_by_host'
27
+ get 'show_template_invocation_by_host/:host_id/job_invocation/:id', to: 'template_invocations#show_template_invocation_by_host'
26
28
 
27
29
  resources :job_invocations, :only => [:create, :show, :index] do
28
30
  collection do
@@ -35,7 +35,7 @@ module ForemanRemoteExecution
35
35
  initializer 'foreman_remote_execution.register_plugin', before: :finisher_hook do |app|
36
36
  app.reloader.to_prepare do
37
37
  Foreman::Plugin.register :foreman_remote_execution do
38
- requires_foreman '>= 3.13'
38
+ requires_foreman '>= 3.14'
39
39
  register_global_js_file 'global'
40
40
  register_gettext
41
41
 
@@ -170,9 +170,9 @@ module ForemanRemoteExecution
170
170
  permission :lock_job_templates, { :job_templates => [:lock, :unlock] }, :resource_type => 'JobTemplate'
171
171
  permission :create_job_invocations, { :job_invocations => [:new, :create, :legacy_create, :refresh, :rerun, :preview_hosts],
172
172
  'api/v2/job_invocations' => [:create, :rerun] }, :resource_type => 'JobInvocation'
173
- permission :view_job_invocations, { :job_invocations => [:index, :chart, :show, :auto_complete_search, :preview_job_invocations_per_host], :template_invocations => [:show],
173
+ 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],
174
174
  'api/v2/job_invocations' => [:index, :show, :output, :raw_output, :outputs, :hosts] }, :resource_type => 'JobInvocation'
175
- permission :view_template_invocations, { :template_invocations => [:show],
175
+ permission :view_template_invocations, { :template_invocations => [:show, :template_invocation_preview, :show_template_invocation_by_host],
176
176
  'api/v2/template_invocations' => [:template_invocations], :ui_job_wizard => [:job_invocation] }, :resource_type => 'TemplateInvocation'
177
177
  permission :create_template_invocations, {}, :resource_type => 'TemplateInvocation'
178
178
  permission :execute_jobs_on_infrastructure_hosts, {}, :resource_type => 'JobInvocation'
@@ -1,3 +1,3 @@
1
1
  module ForemanRemoteExecution
2
- VERSION = '14.1.4'.freeze
2
+ VERSION = '15.0.0'.freeze
3
3
  end
@@ -0,0 +1,214 @@
1
+ /* eslint-disable max-lines */
2
+ /* eslint-disable camelcase */
3
+ import React, { useState } from 'react';
4
+ import PropTypes from 'prop-types';
5
+ import {
6
+ ExpandableSection,
7
+ DataList,
8
+ DataListCell,
9
+ DataListItemCells,
10
+ DataListItem,
11
+ DataListItemRow,
12
+ } from '@patternfly/react-core';
13
+ import { translate as __ } from 'foremanReact/common/I18n';
14
+ import { TARGETING_TYPES } from './JobInvocationConstants';
15
+
16
+ const ItemsParser = ({ items }) => (
17
+ <>
18
+ {items.map(
19
+ ({ title, value, wrappedValue }, index) =>
20
+ value && (
21
+ <DataListItem key={index}>
22
+ <DataListItemRow>
23
+ <DataListItemCells
24
+ dataListCells={[
25
+ <DataListCell width={1} key={0}>
26
+ {title}
27
+ </DataListCell>,
28
+ <DataListCell width={4} key={1}>
29
+ {wrappedValue || value}
30
+ </DataListCell>,
31
+ ]}
32
+ />
33
+ </DataListItemRow>
34
+ </DataListItem>
35
+ )
36
+ )}
37
+ </>
38
+ );
39
+ const Schedule = ({ data }) => {
40
+ const [isExpanded, setIsExpanded] = useState(false);
41
+ const {
42
+ concurrency_level,
43
+ scheduling,
44
+ time_to_pickup,
45
+ execution_timeout_interval,
46
+ } = data;
47
+ if (
48
+ !concurrency_level &&
49
+ !scheduling &&
50
+ !time_to_pickup &&
51
+ !execution_timeout_interval
52
+ )
53
+ return null;
54
+ const items = [
55
+ { title: __('Concurrency level limited to'), value: concurrency_level },
56
+ { title: __('Scheduled to start before'), value: scheduling?.start_before },
57
+ { title: __('Scheduled to start at'), value: scheduling?.start_at },
58
+ {
59
+ title: __('Timeout to kill after'),
60
+ value: execution_timeout_interval,
61
+ wrappedValue: `${execution_timeout_interval} ${__('seconds')}`,
62
+ },
63
+ {
64
+ title: __('Time to pickup'),
65
+ value: time_to_pickup,
66
+ wrappedValue: `${time_to_pickup} ${__('seconds')}`,
67
+ },
68
+ ];
69
+ return (
70
+ <ExpandableSection
71
+ toggleText={__('Schedule')}
72
+ onToggle={setIsExpanded}
73
+ isExpanded={isExpanded}
74
+ >
75
+ <DataList isCompact>
76
+ <ItemsParser items={items} />
77
+ </DataList>
78
+ </ExpandableSection>
79
+ );
80
+ };
81
+ const Recurring = ({ data }) => {
82
+ const [isExpanded, setIsExpanded] = useState(false);
83
+ const { recurrence } = data;
84
+
85
+ if (!recurrence) return null;
86
+ const items = [
87
+ {
88
+ title: __('ID'),
89
+ value: recurrence.id,
90
+ wrappedValue: (
91
+ <a href={`/foreman_tasks/recurring_logics/${recurrence.id}`}>
92
+ {recurrence.id}
93
+ </a>
94
+ ),
95
+ },
96
+ { title: __('Cron line'), value: recurrence.cron_line },
97
+ // TODO { title:__('Action') , value: {format_task_input(recurring_logic.tasks.last)} },
98
+ { title: __('Last occurrence'), value: recurrence.last_occurrence },
99
+ { title: __('Next occurrence'), value: recurrence.next_occurrence },
100
+ { title: __('Current iteration'), value: recurrence.iteration },
101
+ { title: __('Iteration limit'), value: recurrence.max_iteration },
102
+ { title: __('Repeat until'), value: recurrence.end_time },
103
+ // TODO { title:__('State') , value: {recurring_logic_state(recurring_logic)} },
104
+ { title: __('Purpose'), value: recurrence.purpose },
105
+ { title: __('Task count'), value: recurrence.task_count },
106
+ ];
107
+ return (
108
+ <ExpandableSection
109
+ toggleText={__('Recurring logic')}
110
+ onToggle={setIsExpanded}
111
+ isExpanded={isExpanded}
112
+ >
113
+ <DataList isCompact>
114
+ <ItemsParser items={items} />
115
+ </DataList>
116
+ </ExpandableSection>
117
+ );
118
+ };
119
+ const TargetHosts = ({ data }) => {
120
+ const [isExpanded, setIsExpanded] = useState(false);
121
+ const {
122
+ targeting,
123
+ job_location: location,
124
+ job_organization: organization,
125
+ } = data;
126
+
127
+ const targetingSelectioning = targeting.bookmark_name
128
+ ? `${__('Bookmark')} ${targeting.bookmark_name}`
129
+ : __('Manual selection');
130
+ const items = [
131
+ {
132
+ title: __('Organization'),
133
+ value: true,
134
+ wrappedValue: organization || __('Any organization'),
135
+ },
136
+ {
137
+ title: __('Location'),
138
+ value: true,
139
+ wrappedValue: location || __('Any location'),
140
+ },
141
+ {
142
+ title: __('Execution order'),
143
+ value: true,
144
+ wrappedValue: targeting.randomized_ordering
145
+ ? __('Randomized')
146
+ : __('Alphabetical'),
147
+ },
148
+ ];
149
+ return (
150
+ <ExpandableSection
151
+ toggleText={__('Target Hosts')}
152
+ onToggle={setIsExpanded}
153
+ isExpanded={isExpanded}
154
+ >
155
+ <span>{targetingSelectioning}</span>{' '}
156
+ <span>
157
+ {__('using ')}
158
+ <b>{TARGETING_TYPES[targeting.targeting_type].toLowerCase()}</b>
159
+ </span>
160
+ <pre>{targeting.search_query}</pre>
161
+ <DataList isCompact>
162
+ <ItemsParser items={items} />
163
+ </DataList>
164
+ </ExpandableSection>
165
+ );
166
+ };
167
+ const Inputs = ({ data }) => {
168
+ const [isExpanded, setIsExpanded] = useState(false);
169
+ const inputs =
170
+ data?.pattern_template_invocations?.[0]?.template_invocation_input_values;
171
+
172
+ if (!inputs) return null;
173
+ return (
174
+ <ExpandableSection
175
+ toggleText={__('User Inputs')}
176
+ onToggle={setIsExpanded}
177
+ isExpanded={isExpanded}
178
+ >
179
+ <DataList isCompact>
180
+ <ItemsParser
181
+ items={inputs.map(({ template_input_name: title, value }) => ({
182
+ title,
183
+ value: true,
184
+ wrappedValue: value,
185
+ }))}
186
+ />
187
+ </DataList>
188
+ </ExpandableSection>
189
+ );
190
+ };
191
+ export const JobAdditionInfo = ({ data }) => (
192
+ <>
193
+ <Recurring data={data} />
194
+ <TargetHosts data={data} />
195
+ <Inputs data={data} />
196
+ <Schedule data={data} />
197
+ </>
198
+ );
199
+
200
+ JobAdditionInfo.propTypes = {
201
+ data: PropTypes.shape({
202
+ recurrence: PropTypes.object,
203
+ targeting: PropTypes.object,
204
+ }).isRequired,
205
+ };
206
+
207
+ Recurring.propTypes = JobAdditionInfo.propTypes;
208
+ TargetHosts.propTypes = JobAdditionInfo.propTypes;
209
+ Inputs.propTypes = JobAdditionInfo.propTypes;
210
+ Schedule.propTypes = JobAdditionInfo.propTypes;
211
+
212
+ ItemsParser.propTypes = {
213
+ items: PropTypes.array.isRequired,
214
+ };
@@ -19,6 +19,15 @@ export const JOB_INVOCATION_HOSTS = 'JOB_INVOCATION_HOSTS';
19
19
  export const currentPermissionsUrl = foremanUrl(
20
20
  '/api/v2/permissions/current_permissions'
21
21
  );
22
+ export const GET_TEMPLATE_INVOCATION = 'GET_TEMPLATE_INVOCATION';
23
+ export const showTemplateInvocationUrl = (hostID, jobID) =>
24
+ `/show_template_invocation_by_host/${hostID}/job_invocation/${jobID}`;
25
+
26
+ export const templateInvocationPageUrl = (hostID, jobID) =>
27
+ `/job_invocations_detail/${jobID}/host_invocation/${hostID}`;
28
+
29
+ export const jobInvocationDetailsUrl = id =>
30
+ `/experimental/job_invocations_detail/${id}`;
22
31
 
23
32
  export const STATUS = {
24
33
  PENDING: 'pending',
@@ -33,6 +42,14 @@ export const STATUS_UPPERCASE = {
33
42
  PENDING: 'PENDING',
34
43
  };
35
44
 
45
+ export const STATUS_TITLES = {
46
+ ALL_STATUSES: { id: 'all_statuses', title: __('All statuses') },
47
+ SUCCESS: { id: 'success', title: __('Succeeded') },
48
+ FAILED: { id: 'failed', title: __('Failed') },
49
+ PENDING: { id: 'pending', title: __('In Progress') },
50
+ CANCELLED: { id: 'cancelled', title: __('Cancelled') },
51
+ };
52
+
36
53
  export const DATE_OPTIONS = {
37
54
  day: 'numeric',
38
55
  month: 'short',
@@ -52,7 +69,7 @@ const Columns = () => {
52
69
  return { title: __('Failed'), status: 1 };
53
70
  case 'planned':
54
71
  return { title: __('Scheduled'), status: 2 };
55
- case 'running':
72
+ case 'running' || 'pending':
56
73
  return { title: __('Pending'), status: 3 };
57
74
  case 'cancelled':
58
75
  return { title: __('Cancelled'), status: 4 };
@@ -65,18 +82,25 @@ const Columns = () => {
65
82
  const hostDetailsPageUrl = useForemanHostDetailsPageUrl();
66
83
 
67
84
  return {
85
+ expand: {
86
+ title: '',
87
+ weight: 0,
88
+ wrapper: () => null,
89
+ },
68
90
  name: {
69
91
  title: __('Name'),
70
92
  wrapper: ({ name }) => (
71
93
  <a href={`${hostDetailsPageUrl}${name}`}>{name}</a>
72
94
  ),
95
+ isSorted: true,
73
96
  weight: 1,
74
97
  },
75
- groups: {
98
+ hostgroup: {
76
99
  title: __('Host group'),
77
100
  wrapper: ({ hostgroup_id, hostgroup_name }) => (
78
101
  <a href={`/hostgroups/${hostgroup_id}/edit`}>{hostgroup_name}</a>
79
102
  ),
103
+ isSorted: true,
80
104
  weight: 2,
81
105
  },
82
106
  os: {
@@ -86,6 +110,7 @@ const Columns = () => {
86
110
  {operatingsystem_name}
87
111
  </a>
88
112
  ),
113
+ isSorted: true,
89
114
  weight: 3,
90
115
  },
91
116
  smart_proxy: {
@@ -93,6 +118,7 @@ const Columns = () => {
93
118
  wrapper: ({ smart_proxy_name, smart_proxy_id }) => (
94
119
  <a href={`/smart_proxies/${smart_proxy_id}`}>{smart_proxy_name}</a>
95
120
  ),
121
+ isSorted: true,
96
122
  weight: 4,
97
123
  },
98
124
  status: {
@@ -109,7 +135,19 @@ const Columns = () => {
109
135
  },
110
136
  weight: 5,
111
137
  },
138
+ actions: {
139
+ title: '',
140
+ weight: 6,
141
+ wrapper: () => null,
142
+ },
112
143
  };
113
144
  };
114
145
 
115
146
  export default Columns;
147
+
148
+ const STATIC_TYPE = 'static_query';
149
+ const DYNAMIC_TYPE = 'dynamic_query';
150
+ export const TARGETING_TYPES = {
151
+ [STATIC_TYPE]: __('Static Query'),
152
+ [DYNAMIC_TYPE]: __('Dynamic Query'),
153
+ };
@@ -38,3 +38,73 @@
38
38
  height: $chart_size;
39
39
  }
40
40
  }
41
+ .job-additional-info {
42
+ padding: 0;
43
+ margin-bottom: -10px;
44
+ }
45
+ .job-details-table-section {
46
+ section:nth-child(1) {
47
+ padding: 0;
48
+ }
49
+ }
50
+
51
+ .template-invocation {
52
+ &.output-in-table-view {
53
+ div.invocation-output {
54
+ overflow: auto;
55
+ max-height: 25em;
56
+ }
57
+ }
58
+ div.invocation-output {
59
+ display: block;
60
+ padding: 9.5px;
61
+ margin: 0 0 10px;
62
+ font-size: 12px;
63
+ word-break: break-all;
64
+ word-wrap: break-word;
65
+ color: rgba(255, 255, 255, 1);
66
+ background-color: rgba(47, 47, 47, 1);
67
+ border: 1px solid #000000;
68
+ border-radius: 0px;
69
+ font-family: Menlo, Monaco, Consolas, monospace;
70
+
71
+ div.printable {
72
+ min-height: 50px;
73
+ }
74
+
75
+ div.line.stderr,
76
+ div.line.error,
77
+ div.line.debug {
78
+ color: red;
79
+ }
80
+
81
+ div.line span.counter {
82
+ float: left;
83
+ clear: left;
84
+ }
85
+
86
+ div.line div.content {
87
+ position: relative;
88
+ margin-left: 50px;
89
+ white-space: pre-wrap;
90
+ }
91
+
92
+ a {
93
+ color: #ffffff;
94
+ }
95
+
96
+ a.scroll-link{
97
+ position: relative;
98
+ bottom: 10px;
99
+ float: right;
100
+ }
101
+ }
102
+
103
+ .template-invocation-preview {
104
+ margin-top: 10px;
105
+ }
106
+
107
+ .pf-c-toggle-group {
108
+ margin-bottom: 10px;
109
+ }
110
+ }