foreman_remote_execution 16.0.3 → 16.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/controllers/api/v2/job_templates_controller.rb +2 -1
- data/app/controllers/cockpit_controller.rb +2 -2
- data/app/helpers/hosts_extensions_helper.rb +1 -1
- data/app/lib/actions/remote_execution/run_host_job.rb +5 -28
- data/app/models/concerns/foreman_remote_execution/host_extensions.rb +7 -1
- data/app/models/job_template.rb +5 -0
- data/app/models/{ssh_execution_provider.rb → script_execution_provider.rb} +2 -4
- data/config/initializers/inflections.rb +0 -1
- data/lib/foreman_remote_execution/engine.rb +3 -3
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/test/functional/api/v2/job_templates_controller_test.rb +29 -0
- data/test/unit/actions/run_host_job_test.rb +1 -1
- data/test/unit/remote_execution_provider_test.rb +19 -19
- data/webpack/JobInvocationDetail/JobInvocationConstants.js +2 -2
- data/webpack/JobInvocationDetail/JobInvocationDetail.scss +14 -0
- data/webpack/JobInvocationDetail/JobInvocationHostTable.js +18 -4
- data/webpack/JobInvocationDetail/JobInvocationSystemStatusChart.js +8 -22
- data/webpack/JobInvocationDetail/index.js +4 -2
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 263ac53880d0a4d4673659013457388598c58706f452a8f6db7c94df017ac634
|
4
|
+
data.tar.gz: c7806ab71384b036d0ce50e2f8b55c0f9c13095fcfc455dd8057d28fabc74682
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8c20ef4f8c84e5b57e3b1562dba4191d3552269d327de0ce51e7dd9d737db87a3e15a390abb60003568035e168adf2e3f3053fdbaf3f36a3f0e3282941081008
|
7
|
+
data.tar.gz: 33c31c42d97a26a67331097c545e731c3b44a4430e6c8a2ce26f516b4296db78908639ab410b5959128f182d58d05681d4fc46e495c95c6cce120b2a2ff25e80
|
@@ -108,8 +108,9 @@ module Api
|
|
108
108
|
param :id, :identifier, :required => true
|
109
109
|
param_group :job_template_clone, :as => :create
|
110
110
|
def clone
|
111
|
+
original = @job_template
|
111
112
|
@job_template = @job_template.clone
|
112
|
-
|
113
|
+
@job_template.cloned_from = original
|
113
114
|
@job_template.name = job_template_params[:name]
|
114
115
|
process_response @job_template.save
|
115
116
|
end
|
@@ -2,7 +2,7 @@ class CockpitController < ApplicationController
|
|
2
2
|
before_action :find_resource, :only => [:host_ssh_params]
|
3
3
|
|
4
4
|
def host_ssh_params
|
5
|
-
render :json =>
|
5
|
+
render :json => ScriptExecutionProvider.ssh_params(@host)
|
6
6
|
end
|
7
7
|
|
8
8
|
def redirect
|
@@ -10,7 +10,7 @@ class CockpitController < ApplicationController
|
|
10
10
|
|
11
11
|
redir_url = URI.parse(params[:redirect_uri])
|
12
12
|
|
13
|
-
cockpit_url =
|
13
|
+
cockpit_url = ScriptExecutionProvider.cockpit_url_for_host('')
|
14
14
|
redir_url.query = if redir_url.hostname == URI.join(Setting[:foreman_url], cockpit_url).hostname
|
15
15
|
"access_token=#{request.session_options[:id]}"
|
16
16
|
else
|
@@ -44,7 +44,7 @@ module HostsExtensionsHelper
|
|
44
44
|
def web_console_button(host, *_args)
|
45
45
|
return if !authorized_for(permission: 'cockpit_hosts', auth_object: host) || !can_execute_on_infrastructure_host?(host)
|
46
46
|
|
47
|
-
url =
|
47
|
+
url = ScriptExecutionProvider.cockpit_url_for_host(host.name)
|
48
48
|
url ? link_to(_('Web Console'), url, :class => 'btn btn-default', :id => :'web-console-button', :target => '_new') : nil
|
49
49
|
end
|
50
50
|
|
@@ -70,7 +70,7 @@ module Actions
|
|
70
70
|
.merge(additional_options)
|
71
71
|
|
72
72
|
plan_delegated_action(proxy, provider.proxy_action_class, action_options, proxy_action_class: ::Actions::RemoteExecution::ProxyAction)
|
73
|
-
plan_self
|
73
|
+
plan_self
|
74
74
|
end
|
75
75
|
|
76
76
|
def finalize(*args)
|
@@ -127,32 +127,6 @@ module Actions
|
|
127
127
|
end
|
128
128
|
|
129
129
|
def fill_continuous_output(continuous_output)
|
130
|
-
if input[:with_event_logging]
|
131
|
-
continuous_output_from_template_invocation_events(continuous_output)
|
132
|
-
return
|
133
|
-
end
|
134
|
-
|
135
|
-
delegated_output.fetch('result', []).each do |raw_output|
|
136
|
-
continuous_output.add_raw_output(raw_output)
|
137
|
-
end
|
138
|
-
|
139
|
-
final_timestamp = (continuous_output.last_timestamp || task.ended_at).to_f + 1
|
140
|
-
|
141
|
-
if task.state == 'stopped' && task.result == 'cancelled'
|
142
|
-
continuous_output.add_output(_('Job cancelled by user'), 'debug', final_timestamp)
|
143
|
-
else
|
144
|
-
fill_planning_errors_to_continuous_output(continuous_output) unless exit_status
|
145
|
-
end
|
146
|
-
if exit_status
|
147
|
-
continuous_output.add_output(_('Exit status: %s') % exit_status, 'stdout', final_timestamp)
|
148
|
-
elsif run_step&.error
|
149
|
-
continuous_output.add_output(_('Job finished with error') + ": #{run_step.error.exception_class} - #{run_step.error.message}", 'debug', final_timestamp)
|
150
|
-
end
|
151
|
-
rescue => e
|
152
|
-
continuous_output.add_exception(_('Error loading data from proxy'), e)
|
153
|
-
end
|
154
|
-
|
155
|
-
def continuous_output_from_template_invocation_events(continuous_output)
|
156
130
|
begin
|
157
131
|
# Trigger reload
|
158
132
|
delegated_output unless task.state == 'stopped'
|
@@ -160,6 +134,7 @@ module Actions
|
|
160
134
|
# This is enough, the error will get shown using add_exception at the end of the method
|
161
135
|
end
|
162
136
|
|
137
|
+
# Show the outputs which are already stored in the database
|
163
138
|
task.template_invocation.template_invocation_events.order(:timestamp).find_each do |output|
|
164
139
|
if output.event_type == 'exit'
|
165
140
|
continuous_output.add_output(_('Exit status: %s') % output.event, 'stdout', output.timestamp)
|
@@ -167,11 +142,13 @@ module Actions
|
|
167
142
|
continuous_output.add_raw_output(output.as_raw_continuous_output)
|
168
143
|
end
|
169
144
|
end
|
145
|
+
|
146
|
+
# Attach the exception at the end
|
170
147
|
continuous_output.add_exception(_('Error loading data from proxy'), e) if e
|
171
148
|
end
|
172
149
|
|
173
150
|
def exit_status
|
174
|
-
|
151
|
+
task.template_invocation.template_invocation_events.find_by(event_type: 'exit')&.event
|
175
152
|
end
|
176
153
|
|
177
154
|
def host_id
|
@@ -28,6 +28,8 @@ module ForemanRemoteExecution
|
|
28
28
|
end
|
29
29
|
}
|
30
30
|
|
31
|
+
after_build :reset_host_proxy_invocations
|
32
|
+
|
31
33
|
def search_by_job_invocation(key, operator, value)
|
32
34
|
if key == 'job_invocation.result'
|
33
35
|
operator = operator == '=' ? 'IN' : 'NOT IN'
|
@@ -46,8 +48,12 @@ module ForemanRemoteExecution
|
|
46
48
|
end
|
47
49
|
end
|
48
50
|
|
51
|
+
def reset_host_proxy_invocations
|
52
|
+
HostProxyInvocation.where(host_id: self.id).delete_all
|
53
|
+
end
|
54
|
+
|
49
55
|
def cockpit_url
|
50
|
-
|
56
|
+
ScriptExecutionProvider.cockpit_url_for_host(self.name)
|
51
57
|
end
|
52
58
|
|
53
59
|
def execution_status(options = {})
|
data/app/models/job_template.rb
CHANGED
@@ -129,6 +129,11 @@ class JobTemplate < ::Template
|
|
129
129
|
end
|
130
130
|
end
|
131
131
|
|
132
|
+
def clone
|
133
|
+
deep_clone(:include => [:effective_user, :foreign_input_sets, :organizations, :locations],
|
134
|
+
:except => [:name, :locked])
|
135
|
+
end
|
136
|
+
|
132
137
|
def provider
|
133
138
|
RemoteExecutionProvider.provider_for(provider_type)
|
134
139
|
end
|
@@ -40,7 +40,7 @@ class ScriptExecutionProvider < RemoteExecutionProvider
|
|
40
40
|
proxy = proxy_for_cockpit(host)
|
41
41
|
{
|
42
42
|
:hostname => find_ip_or_hostname(host),
|
43
|
-
:proxy => proxy.
|
43
|
+
:proxy => proxy.instance_of?(Symbol) ? proxy : proxy.url,
|
44
44
|
:ssh_user => ssh_user(host),
|
45
45
|
:ssh_port => ssh_port(host),
|
46
46
|
:ssh_password => ssh_password(host),
|
@@ -50,7 +50,7 @@ class ScriptExecutionProvider < RemoteExecutionProvider
|
|
50
50
|
end
|
51
51
|
|
52
52
|
def cockpit_url_for_host(host)
|
53
|
-
Setting[:remote_execution_cockpit_url]
|
53
|
+
format(Setting[:remote_execution_cockpit_url], host: host) if Setting[:remote_execution_cockpit_url].present?
|
54
54
|
end
|
55
55
|
|
56
56
|
def proxy_feature
|
@@ -76,5 +76,3 @@ class ScriptExecutionProvider < RemoteExecutionProvider
|
|
76
76
|
end
|
77
77
|
end
|
78
78
|
end
|
79
|
-
|
80
|
-
SSHExecutionProvider = ScriptExecutionProvider
|
@@ -71,10 +71,10 @@ module ForemanRemoteExecution
|
|
71
71
|
full_name: N_('Effective User')
|
72
72
|
setting 'remote_execution_effective_user_method',
|
73
73
|
type: :string,
|
74
|
-
description: N_('What command should be used to switch to the effective user. One of %s') % ::
|
74
|
+
description: N_('What command should be used to switch to the effective user. One of %s') % ::ScriptExecutionProvider::EFFECTIVE_USER_METHODS.inspect,
|
75
75
|
default: 'sudo',
|
76
76
|
full_name: N_('Effective User Method'),
|
77
|
-
collection: proc { Hash[::
|
77
|
+
collection: proc { Hash[::ScriptExecutionProvider::EFFECTIVE_USER_METHODS.map { |method| [method, method] }] }
|
78
78
|
setting 'remote_execution_effective_user_password',
|
79
79
|
type: :string,
|
80
80
|
description: N_('Effective user password'),
|
@@ -328,7 +328,7 @@ module ForemanRemoteExecution
|
|
328
328
|
require_dependency 'foreman_tasks/task'
|
329
329
|
ForemanTasks::Task.include ForemanRemoteExecution::ForemanTasksTaskExtensions
|
330
330
|
ForemanTasks::Cleaner.include ForemanRemoteExecution::ForemanTasksCleanerExtensions
|
331
|
-
RemoteExecutionProvider.register(:SSH, ::
|
331
|
+
RemoteExecutionProvider.register(:SSH, ::ScriptExecutionProvider)
|
332
332
|
RemoteExecutionProvider.register(:script, ::ScriptExecutionProvider)
|
333
333
|
|
334
334
|
ForemanRemoteExecution.register_rex_feature
|
@@ -90,6 +90,35 @@ module Api
|
|
90
90
|
assert_response :unprocessable_entity
|
91
91
|
end
|
92
92
|
|
93
|
+
test 'should clone locked template' do
|
94
|
+
@template.locked = true
|
95
|
+
@template.save!
|
96
|
+
post :clone, params: { :id => @template.to_param, :job_template => {:name => 'MyClone'} }
|
97
|
+
assert_response :success
|
98
|
+
|
99
|
+
template = ActiveSupport::JSON.decode(@response.body)
|
100
|
+
assert_equal(template['locked'], false)
|
101
|
+
end
|
102
|
+
|
103
|
+
test 'should clone template with associations' do
|
104
|
+
template2 = FactoryBot.create(:job_template)
|
105
|
+
@template.organizations << FactoryBot.create(:organization)
|
106
|
+
@template.locations << FactoryBot.create(:location)
|
107
|
+
@template.foreign_input_sets << FactoryBot.build(:foreign_input_set).tap { |fis| fis.target_template_id = template2.id }
|
108
|
+
@template.effective_user.value = 'toor'
|
109
|
+
@template.save!
|
110
|
+
|
111
|
+
post :clone, params: { :id => @template.to_param, :job_template => {:name => 'MyClone'} }
|
112
|
+
template = ActiveSupport::JSON.decode(@response.body)
|
113
|
+
|
114
|
+
clone = JobTemplate.unscoped.find(template['id'])
|
115
|
+
assert_equal(clone.organizations.sort, @template.organizations.sort)
|
116
|
+
assert_equal(clone.locations.sort, @template.locations.sort)
|
117
|
+
assert_equal(clone.foreign_input_sets.map(&:target_template_id), [template2.id])
|
118
|
+
assert_equal(clone.effective_user.value, 'toor')
|
119
|
+
assert_equal(clone.cloned_from.id, @template.id)
|
120
|
+
end
|
121
|
+
|
93
122
|
test 'should export template' do
|
94
123
|
get :export, params: { :id => @template.to_param }
|
95
124
|
User.current = users(:admin)
|
@@ -10,7 +10,7 @@ module ForemanRemoteExecution
|
|
10
10
|
let(:job_invocation) { FactoryBot.create(:job_invocation, :with_task) }
|
11
11
|
let(:host) { job_invocation.template_invocations.first.host }
|
12
12
|
let(:provider) do
|
13
|
-
provider = ::
|
13
|
+
provider = ::ScriptExecutionProvider
|
14
14
|
provider.expects(:ssh_password).with(host).returns('sshpass')
|
15
15
|
provider.expects(:effective_user_password).with(host).returns('sudopass')
|
16
16
|
provider.expects(:ssh_key_passphrase).with(host).returns('keypass')
|
@@ -5,11 +5,11 @@ class RemoteExecutionProviderTest < ActiveSupport::TestCase
|
|
5
5
|
let(:providers) { RemoteExecutionProvider.providers }
|
6
6
|
it { assert_kind_of HashWithIndifferentAccess, providers }
|
7
7
|
it 'makes providers accessible using symbol' do
|
8
|
-
assert_equal
|
8
|
+
assert_equal ScriptExecutionProvider, providers[:SSH]
|
9
9
|
assert_equal ScriptExecutionProvider, providers[:script]
|
10
10
|
end
|
11
11
|
it 'makes providers accessible using string' do
|
12
|
-
assert_equal
|
12
|
+
assert_equal ScriptExecutionProvider, providers['SSH']
|
13
13
|
assert_equal ScriptExecutionProvider, providers['script']
|
14
14
|
end
|
15
15
|
end
|
@@ -27,11 +27,11 @@ class RemoteExecutionProviderTest < ActiveSupport::TestCase
|
|
27
27
|
|
28
28
|
describe '.provider_for' do
|
29
29
|
it 'accepts symbols' do
|
30
|
-
assert_equal
|
30
|
+
assert_equal ScriptExecutionProvider, RemoteExecutionProvider.provider_for(:SSH)
|
31
31
|
end
|
32
32
|
|
33
33
|
it 'accepts strings' do
|
34
|
-
assert_equal
|
34
|
+
assert_equal ScriptExecutionProvider, RemoteExecutionProvider.provider_for('SSH')
|
35
35
|
end
|
36
36
|
|
37
37
|
it 'returns a default one if unknown value is provided' do
|
@@ -81,7 +81,7 @@ class RemoteExecutionProviderTest < ActiveSupport::TestCase
|
|
81
81
|
describe '.provider_proxy_features' do
|
82
82
|
it 'returns correct values' do
|
83
83
|
RemoteExecutionProvider.stubs(:providers).returns(
|
84
|
-
:SSH =>
|
84
|
+
:SSH => ScriptExecutionProvider,
|
85
85
|
:script => ScriptExecutionProvider
|
86
86
|
)
|
87
87
|
|
@@ -112,15 +112,15 @@ class RemoteExecutionProviderTest < ActiveSupport::TestCase
|
|
112
112
|
end
|
113
113
|
end
|
114
114
|
|
115
|
-
describe
|
115
|
+
describe ScriptExecutionProvider do
|
116
116
|
before { User.current = FactoryBot.build(:user, :admin) }
|
117
117
|
after { User.current = nil }
|
118
118
|
|
119
119
|
let(:job_invocation) { FactoryBot.create(:job_invocation, :with_template) }
|
120
120
|
let(:template_invocation) { job_invocation.pattern_template_invocations.first }
|
121
121
|
let(:host) { FactoryBot.create(:host) }
|
122
|
-
let(:proxy_options) {
|
123
|
-
let(:secrets) {
|
122
|
+
let(:proxy_options) { ScriptExecutionProvider.proxy_command_options(template_invocation, host) }
|
123
|
+
let(:secrets) { ScriptExecutionProvider.secrets(host) }
|
124
124
|
|
125
125
|
describe 'effective user' do
|
126
126
|
it 'takes the effective user from value from the template invocation' do
|
@@ -153,11 +153,11 @@ class RemoteExecutionProviderTest < ActiveSupport::TestCase
|
|
153
153
|
assert_equal 'sudo', proxy_options[:effective_user_method]
|
154
154
|
method_param.update!(:value => 'su')
|
155
155
|
host.clear_host_parameters_cache!
|
156
|
-
proxy_options =
|
156
|
+
proxy_options = ScriptExecutionProvider.proxy_command_options(template_invocation, host)
|
157
157
|
assert_equal 'su', proxy_options[:effective_user_method]
|
158
158
|
method_param.update!(:value => 'dzdo')
|
159
159
|
host.clear_host_parameters_cache!
|
160
|
-
proxy_options =
|
160
|
+
proxy_options = ScriptExecutionProvider.proxy_command_options(template_invocation, host)
|
161
161
|
assert_equal 'dzdo', proxy_options[:effective_user_method]
|
162
162
|
end
|
163
163
|
end
|
@@ -219,7 +219,7 @@ class RemoteExecutionProviderTest < ActiveSupport::TestCase
|
|
219
219
|
|
220
220
|
it 'gets fqdn from flagged interfaces if not preferring ips' do
|
221
221
|
# falling to primary interface
|
222
|
-
assert_equal 'somehost.somedomain.org',
|
222
|
+
assert_equal 'somehost.somedomain.org', ScriptExecutionProvider.find_ip_or_hostname(host)
|
223
223
|
|
224
224
|
# execution wins if present
|
225
225
|
execution_interface = FactoryBot.build(:nic_managed,
|
@@ -228,13 +228,13 @@ class RemoteExecutionProviderTest < ActiveSupport::TestCase
|
|
228
228
|
host.primary_interface.update(:execution => false)
|
229
229
|
host.interfaces.each(&:save)
|
230
230
|
host.reload
|
231
|
-
assert_equal execution_interface.fqdn,
|
231
|
+
assert_equal execution_interface.fqdn, ScriptExecutionProvider.find_ip_or_hostname(host)
|
232
232
|
end
|
233
233
|
|
234
234
|
it 'gets ip from flagged interfaces' do
|
235
235
|
host.host_params['remote_execution_connect_by_ip'] = true
|
236
236
|
# no ip address set on relevant interface - fallback to fqdn
|
237
|
-
assert_equal 'somehost.somedomain.org',
|
237
|
+
assert_equal 'somehost.somedomain.org', ScriptExecutionProvider.find_ip_or_hostname(host)
|
238
238
|
|
239
239
|
# provision interface with ip while primary without
|
240
240
|
provision_interface = FactoryBot.build(:nic_managed,
|
@@ -243,12 +243,12 @@ class RemoteExecutionProviderTest < ActiveSupport::TestCase
|
|
243
243
|
host.primary_interface.update(:provision => false)
|
244
244
|
host.interfaces.each(&:save)
|
245
245
|
host.reload
|
246
|
-
assert_equal provision_interface.ip,
|
246
|
+
assert_equal provision_interface.ip, ScriptExecutionProvider.find_ip_or_hostname(host)
|
247
247
|
|
248
248
|
# both primary and provision interface have IPs: the primary wins
|
249
249
|
host.primary_interface.update(:ip => '10.0.0.2', :execution => false)
|
250
250
|
host.reload
|
251
|
-
assert_equal host.primary_interface.ip,
|
251
|
+
assert_equal host.primary_interface.ip, ScriptExecutionProvider.find_ip_or_hostname(host)
|
252
252
|
|
253
253
|
# there is an execution interface with IP: it wins
|
254
254
|
execution_interface = FactoryBot.build(:nic_managed,
|
@@ -257,7 +257,7 @@ class RemoteExecutionProviderTest < ActiveSupport::TestCase
|
|
257
257
|
host.primary_interface.update(:execution => false)
|
258
258
|
host.interfaces.each(&:save)
|
259
259
|
host.reload
|
260
|
-
assert_equal execution_interface.ip,
|
260
|
+
assert_equal execution_interface.ip, ScriptExecutionProvider.find_ip_or_hostname(host)
|
261
261
|
|
262
262
|
# there is an execution interface with both IPv6 and IPv4: IPv4 is being preferred over IPv6 by default
|
263
263
|
execution_interface = FactoryBot.build(:nic_managed,
|
@@ -265,7 +265,7 @@ class RemoteExecutionProviderTest < ActiveSupport::TestCase
|
|
265
265
|
host.interfaces = [execution_interface]
|
266
266
|
host.interfaces.each(&:save)
|
267
267
|
host.reload
|
268
|
-
assert_equal execution_interface.ip,
|
268
|
+
assert_equal execution_interface.ip, ScriptExecutionProvider.find_ip_or_hostname(host)
|
269
269
|
end
|
270
270
|
|
271
271
|
it 'gets ipv6 from flagged interfaces with IPv6 preference' do
|
@@ -278,7 +278,7 @@ class RemoteExecutionProviderTest < ActiveSupport::TestCase
|
|
278
278
|
host.interfaces = [execution_interface]
|
279
279
|
host.interfaces.each(&:save)
|
280
280
|
host.reload
|
281
|
-
assert_equal execution_interface.ip6,
|
281
|
+
assert_equal execution_interface.ip6, ScriptExecutionProvider.find_ip_or_hostname(host)
|
282
282
|
end
|
283
283
|
|
284
284
|
it 'gets ipv6 from flagged interfaces with IPv4 preference but without IPv4 address' do
|
@@ -291,7 +291,7 @@ class RemoteExecutionProviderTest < ActiveSupport::TestCase
|
|
291
291
|
host.interfaces = [execution_interface]
|
292
292
|
host.interfaces.each(&:save)
|
293
293
|
host.reload
|
294
|
-
assert_equal execution_interface.ip6,
|
294
|
+
assert_equal execution_interface.ip6, ScriptExecutionProvider.find_ip_or_hostname(host)
|
295
295
|
end
|
296
296
|
end
|
297
297
|
end
|
@@ -83,7 +83,7 @@ const Columns = () => {
|
|
83
83
|
|
84
84
|
return {
|
85
85
|
expand: {
|
86
|
-
title: '',
|
86
|
+
title: ' ',
|
87
87
|
weight: 0,
|
88
88
|
wrapper: () => null,
|
89
89
|
},
|
@@ -136,7 +136,7 @@ const Columns = () => {
|
|
136
136
|
weight: 5,
|
137
137
|
},
|
138
138
|
actions: {
|
139
|
-
title: '',
|
139
|
+
title: ' ',
|
140
140
|
weight: 6,
|
141
141
|
wrapper: () => null,
|
142
142
|
},
|
@@ -7,6 +7,16 @@
|
|
7
7
|
height: $chart_size;
|
8
8
|
width: $chart_size;
|
9
9
|
margin-bottom: 10px;
|
10
|
+
|
11
|
+
svg {
|
12
|
+
position: relative;
|
13
|
+
overflow: visible;
|
14
|
+
z-index: 999;
|
15
|
+
}
|
16
|
+
|
17
|
+
path {
|
18
|
+
cursor: pointer;
|
19
|
+
}
|
10
20
|
}
|
11
21
|
|
12
22
|
.chart-legend {
|
@@ -28,6 +38,10 @@
|
|
28
38
|
font-weight: normal;
|
29
39
|
}
|
30
40
|
}
|
41
|
+
|
42
|
+
text, path {
|
43
|
+
cursor: pointer;
|
44
|
+
}
|
31
45
|
}
|
32
46
|
|
33
47
|
.pf-v5-c-divider {
|
@@ -1,3 +1,4 @@
|
|
1
|
+
/* eslint-disable max-lines */
|
1
2
|
/* eslint-disable camelcase */
|
2
3
|
import PropTypes from 'prop-types';
|
3
4
|
import React, { useMemo, useEffect, useState } from 'react';
|
@@ -36,11 +37,20 @@ const JobInvocationHostTable = ({
|
|
36
37
|
finished,
|
37
38
|
autoRefresh,
|
38
39
|
initialFilter,
|
40
|
+
onFilterUpdate,
|
39
41
|
}) => {
|
40
42
|
const columns = Columns();
|
41
43
|
const columnNamesKeys = Object.keys(columns);
|
42
44
|
const apiOptions = { key: JOB_INVOCATION_HOSTS };
|
43
|
-
const [selectedFilter, setSelectedFilter] = useState(initialFilter
|
45
|
+
const [selectedFilter, setSelectedFilter] = useState(initialFilter);
|
46
|
+
|
47
|
+
useEffect(() => {
|
48
|
+
if (initialFilter !== selectedFilter) {
|
49
|
+
wrapSetSelectedFilter(initialFilter);
|
50
|
+
}
|
51
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
52
|
+
}, [initialFilter]);
|
53
|
+
|
44
54
|
const {
|
45
55
|
searchParam: urlSearchQuery = '',
|
46
56
|
page: urlPage,
|
@@ -111,6 +121,7 @@ const JobInvocationHostTable = ({
|
|
111
121
|
return prevOptions;
|
112
122
|
});
|
113
123
|
setSelectedFilter(filter);
|
124
|
+
onFilterUpdate(filter);
|
114
125
|
};
|
115
126
|
|
116
127
|
useEffect(() => {
|
@@ -262,7 +273,7 @@ const JobInvocationHostTable = ({
|
|
262
273
|
expandId: 'host-expandable',
|
263
274
|
}}
|
264
275
|
/>
|
265
|
-
{columnNamesKeys.
|
276
|
+
{columnNamesKeys.map(k => (
|
266
277
|
<Td key={k}>{columns[k].wrapper(result)}</Td>
|
267
278
|
))}
|
268
279
|
<Td isActionCell>
|
@@ -275,7 +286,7 @@ const JobInvocationHostTable = ({
|
|
275
286
|
>
|
276
287
|
<Td
|
277
288
|
dataLabel={`${result.id}-expandable-content`}
|
278
|
-
colSpan={columnNamesKeys.length
|
289
|
+
colSpan={columnNamesKeys.length}
|
279
290
|
>
|
280
291
|
<ExpandableRowContent>
|
281
292
|
{result.job_status === 'cancelled' ||
|
@@ -303,8 +314,11 @@ JobInvocationHostTable.propTypes = {
|
|
303
314
|
finished: PropTypes.bool.isRequired,
|
304
315
|
autoRefresh: PropTypes.bool.isRequired,
|
305
316
|
initialFilter: PropTypes.string.isRequired,
|
317
|
+
onFilterUpdate: PropTypes.func,
|
306
318
|
};
|
307
319
|
|
308
|
-
JobInvocationHostTable.defaultProps = {
|
320
|
+
JobInvocationHostTable.defaultProps = {
|
321
|
+
onFilterUpdate: () => {},
|
322
|
+
};
|
309
323
|
|
310
324
|
export default JobInvocationHostTable;
|
@@ -53,7 +53,6 @@ const JobInvocationSystemStatusChart = ({
|
|
53
53
|
};
|
54
54
|
const chartSize = 105;
|
55
55
|
const [legendWidth, setLegendWidth] = useState(270);
|
56
|
-
const [cursor, setCursor] = useState('default');
|
57
56
|
|
58
57
|
// Calculates chart legend width based on its content
|
59
58
|
useEffect(() => {
|
@@ -79,7 +78,7 @@ const JobInvocationSystemStatusChart = ({
|
|
79
78
|
|
80
79
|
return (
|
81
80
|
<>
|
82
|
-
<FlexItem className="chart-donut"
|
81
|
+
<FlexItem className="chart-donut">
|
83
82
|
<ChartDonut
|
84
83
|
allowTooltip
|
85
84
|
constrainToVisibleArea
|
@@ -96,12 +95,6 @@ const JobInvocationSystemStatusChart = ({
|
|
96
95
|
target: 'data',
|
97
96
|
eventHandlers: {
|
98
97
|
onClick: onChartClick,
|
99
|
-
onMouseOver: () => {
|
100
|
-
setCursor('pointer');
|
101
|
-
},
|
102
|
-
onMouseOut: () => {
|
103
|
-
setCursor('default');
|
104
|
-
},
|
105
98
|
},
|
106
99
|
},
|
107
100
|
]}
|
@@ -109,7 +102,12 @@ const JobInvocationSystemStatusChart = ({
|
|
109
102
|
total > 0 ? chartData.map(d => d.color) : [emptyChartDonut.value]
|
110
103
|
}
|
111
104
|
labelComponent={
|
112
|
-
<ChartTooltip
|
105
|
+
<ChartTooltip
|
106
|
+
pointerLength={0}
|
107
|
+
renderInPortal={false}
|
108
|
+
constrainToVisibleArea
|
109
|
+
center={{ x: 15, y: 0 }}
|
110
|
+
/>
|
113
111
|
}
|
114
112
|
title={chartDonutTitle}
|
115
113
|
titleComponent={
|
@@ -133,7 +131,7 @@ const JobInvocationSystemStatusChart = ({
|
|
133
131
|
height={chartSize}
|
134
132
|
/>
|
135
133
|
</FlexItem>
|
136
|
-
<FlexItem className="chart-legend"
|
134
|
+
<FlexItem className="chart-legend">
|
137
135
|
<Text ouiaId="legend-title" className="legend-title">
|
138
136
|
{__('System status')}
|
139
137
|
</Text>
|
@@ -156,24 +154,12 @@ const JobInvocationSystemStatusChart = ({
|
|
156
154
|
target: 'data',
|
157
155
|
eventHandlers: {
|
158
156
|
onClick: onChartClick,
|
159
|
-
onMouseOver: () => {
|
160
|
-
setCursor('pointer');
|
161
|
-
},
|
162
|
-
onMouseOut: () => {
|
163
|
-
setCursor('default');
|
164
|
-
},
|
165
157
|
},
|
166
158
|
},
|
167
159
|
{
|
168
160
|
target: 'labels',
|
169
161
|
eventHandlers: {
|
170
162
|
onClick: onChartClick,
|
171
|
-
onMouseOver: () => {
|
172
|
-
setCursor('pointer');
|
173
|
-
},
|
174
|
-
onMouseOut: () => {
|
175
|
-
setCursor('default');
|
176
|
-
},
|
177
163
|
},
|
178
164
|
},
|
179
165
|
]}
|
@@ -55,8 +55,8 @@ const JobInvocationDetailPage = ({
|
|
55
55
|
);
|
56
56
|
const [selectedFilter, setSelectedFilter] = useState('');
|
57
57
|
|
58
|
-
const handleFilterChange =
|
59
|
-
setSelectedFilter(
|
58
|
+
const handleFilterChange = newFilter => {
|
59
|
+
setSelectedFilter(newFilter);
|
60
60
|
};
|
61
61
|
|
62
62
|
let isAlreadyStarted = false;
|
@@ -119,6 +119,7 @@ const JobInvocationDetailPage = ({
|
|
119
119
|
data={items}
|
120
120
|
isAlreadyStarted={isAlreadyStarted}
|
121
121
|
formattedStartDate={formattedStartDate}
|
122
|
+
onFilterChange={handleFilterChange}
|
122
123
|
/>
|
123
124
|
<Divider
|
124
125
|
orientation={{
|
@@ -155,6 +156,7 @@ const JobInvocationDetailPage = ({
|
|
155
156
|
finished={finished}
|
156
157
|
autoRefresh={autoRefresh}
|
157
158
|
initialFilter={selectedFilter}
|
159
|
+
onFilterUpdate={handleFilterChange}
|
158
160
|
/>
|
159
161
|
)}
|
160
162
|
</PageSection>
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: foreman_remote_execution
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 16.0.
|
4
|
+
version: 16.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Foreman Remote Execution team
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-
|
10
|
+
date: 2025-06-26 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: deface
|
@@ -187,7 +187,7 @@ files:
|
|
187
187
|
- app/models/remote_execution_feature.rb
|
188
188
|
- app/models/remote_execution_provider.rb
|
189
189
|
- app/models/rex_mail_notification.rb
|
190
|
-
- app/models/
|
190
|
+
- app/models/script_execution_provider.rb
|
191
191
|
- app/models/target_remote_execution_proxy.rb
|
192
192
|
- app/models/targeting.rb
|
193
193
|
- app/models/targeting_host.rb
|