foreman_remote_execution 3.3.2 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +2 -1
- data/app/assets/stylesheets/foreman_remote_execution/job_invocations.scss +6 -5
- data/app/controllers/api/v2/job_invocations_controller.rb +3 -2
- data/app/controllers/job_invocations_controller.rb +22 -8
- data/app/lib/actions/remote_execution/run_host_job.rb +1 -1
- data/app/lib/actions/remote_execution/run_hosts_job.rb +1 -1
- data/app/lib/foreman_remote_execution/renderer/scope/input.rb +35 -0
- data/app/models/concerns/foreman_remote_execution/orchestration/ssh.rb +11 -4
- data/app/models/job_invocation.rb +6 -3
- data/app/models/job_invocation_composer.rb +2 -2
- data/app/models/remote_execution_provider.rb +2 -2
- data/app/models/setting/remote_execution.rb +2 -2
- data/app/models/ssh_execution_provider.rb +1 -1
- data/app/services/default_proxy_proxy_selector.rb +3 -1
- data/app/views/api/v2/job_invocations/main.json.rabl +2 -2
- data/app/views/job_invocations/_card_target_hosts.html.erb +1 -1
- data/app/views/job_invocations/_form.html.erb +1 -1
- data/app/views/job_invocations/_tab_hosts.html.erb +1 -20
- data/app/views/job_invocations/index.html.erb +2 -1
- data/app/views/job_invocations/show.html.erb +9 -0
- data/app/views/job_invocations/show.js.erb +5 -0
- data/app/views/job_invocations/show.json.erb +2 -1
- data/app/views/templates/ssh/package_action.erb +1 -0
- data/app/views/templates/ssh/puppet_agent_disable.erb +3 -0
- data/app/views/templates/ssh/puppet_agent_enable.erb +3 -0
- data/app/views/templates/ssh/puppet_install_modules_from_forge.erb +3 -0
- data/app/views/templates/ssh/puppet_run_once.erb +3 -0
- data/db/migrate/20200623073022_rename_sudo_password_to_effective_user_password.rb +34 -0
- data/db/seeds.d/20-permissions.rb +9 -0
- data/lib/foreman_remote_execution/engine.rb +4 -1
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/test/functional/api/v2/job_invocations_controller_test.rb +65 -2
- data/test/functional/job_invocations_controller_test.rb +71 -0
- data/test/models/orchestration/ssh_test.rb +1 -1
- data/test/support/remote_execution_helper.rb +5 -0
- data/test/unit/actions/run_host_job_test.rb +3 -3
- data/test/unit/actions/run_hosts_job_test.rb +1 -1
- data/test/unit/job_invocation_composer_test.rb +5 -5
- data/test/unit/remote_execution_provider_test.rb +6 -6
- data/webpack/__mocks__/foremanReact/components/Pagination/PaginationWrapper.js +2 -0
- data/webpack/__mocks__/foremanReact/components/SearchBar.js +2 -0
- data/webpack/__mocks__/foremanReact/constants.js +21 -0
- data/webpack/__mocks__/foremanReact/redux/API/APISelectors.js +2 -0
- data/webpack/__mocks__/foremanReact/redux/middlewares/IntervalMiddleware/IntervalSelectors.js +1 -0
- data/webpack/react_app/components/TargetingHosts/TargetingHosts.js +21 -15
- data/webpack/react_app/components/TargetingHosts/TargetingHostsHelpers.js +10 -0
- data/webpack/react_app/components/TargetingHosts/TargetingHostsPage.js +62 -0
- data/webpack/react_app/components/TargetingHosts/TargetingHostsPage.scss +6 -0
- data/webpack/react_app/components/TargetingHosts/TargetingHostsSelectors.js +10 -2
- data/webpack/react_app/components/TargetingHosts/__tests__/TargetingHostsPage.test.js +9 -0
- data/webpack/react_app/components/TargetingHosts/__tests__/TargetingHostsSelectors.test.js +26 -0
- data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHosts.test.js.snap +16 -1
- data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHostsPage.test.js.snap +68 -0
- data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHostsSelectors.test.js.snap +11 -0
- data/webpack/react_app/components/TargetingHosts/__tests__/fixtures.js +35 -19
- data/webpack/react_app/components/TargetingHosts/index.js +73 -13
- metadata +18 -3
- data/webpack/react_app/components/TargetingHosts/TargetingHostsActions.js +0 -8
@@ -33,4 +33,7 @@ template_inputs:
|
|
33
33
|
provider_type: SSH
|
34
34
|
kind: job_template
|
35
35
|
-%>
|
36
|
+
<% if @host.operatingsystem.family == 'Debian' -%>
|
37
|
+
export PATH=/opt/puppetlabs/bin:$PATH
|
38
|
+
<% end -%>
|
36
39
|
puppet module install <%= input('puppet_module') %> <%= "--target-dir #{input('target_dir')}" if input('target_dir').present? %> <%= "--version #{input('version')}" if input('version').present? %> <%= "--force" if input('force') == "true" %> <%= "--ignore-dependencies" if input('ignore_dependencies') == "true" %>
|
@@ -11,4 +11,7 @@ template_inputs:
|
|
11
11
|
input_type: user
|
12
12
|
required: false
|
13
13
|
%>
|
14
|
+
<% if @host.operatingsystem.family == 'Debian' -%>
|
15
|
+
export PATH=/opt/puppetlabs/bin:$PATH
|
16
|
+
<% end -%>
|
14
17
|
puppet agent --onetime --no-usecacheonfailure --no-daemonize <%= input("puppet_options") %>
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class RenameSudoPasswordToEffectiveUserPassword < ActiveRecord::Migration[6.0]
|
2
|
+
def up
|
3
|
+
rename_column :job_invocations, :sudo_password, :effective_user_password
|
4
|
+
|
5
|
+
Parameter.where(name: 'remote_execution_sudo_password').each do |parameter|
|
6
|
+
record = Parameter.find_by(type: parameter.type, reference_id: parameter.reference_id, name: "remote_execution_effective_user_password")
|
7
|
+
if record.nil?
|
8
|
+
parameter.update(name: "remote_execution_effective_user_password")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
return unless (password = Setting.find_by(:name => 'remote_execution_sudo_password').try(:value))
|
13
|
+
|
14
|
+
Setting.find_by(:name => 'remote_execution_effective_user_password').update(value: password)
|
15
|
+
|
16
|
+
Setting.find_by(:name => 'remote_execution_sudo_password').delete
|
17
|
+
end
|
18
|
+
|
19
|
+
def down
|
20
|
+
rename_column :job_invocations, :effective_user_password, :sudo_password
|
21
|
+
|
22
|
+
Parameter.where(name: 'remote_execution_effective_user_password').each do |parameter|
|
23
|
+
record = Parameter.find_by(type: parameter.type, reference_id: parameter.reference_id, name: "remote_execution_sudo_password")
|
24
|
+
if record.nil?
|
25
|
+
parameter.update(name: "remote_execution_sudo_password")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
return unless (password = Setting.find_by(:name => 'remote_execution_effective_user_password').try(:value))
|
30
|
+
|
31
|
+
Setting.create!(name: 'remote_execution_sudo_password', value: password, description: 'Sudo password', category: 'Setting::RemoteExecution', settings_type: 'string', full_name: 'Sudo password',encrypted: true, default: nil)
|
32
|
+
Setting.find_by(:name => 'remote_execution_effective_user_password').delete
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
view_permission = Permission.find_by(name: "view_job_invocations", resource_type: 'JobInvocation')
|
2
|
+
default_role = Role.default
|
3
|
+
|
4
|
+
# the view_permissions can be nil in tests: skipping in that case
|
5
|
+
if view_permission && !default_role.permissions.include?(view_permission)
|
6
|
+
default_role.filters.create(:search => 'user = current_user') do |filter|
|
7
|
+
filter.filterings.build { |f| f.permission = view_permission }
|
8
|
+
end
|
9
|
+
end
|
@@ -44,9 +44,12 @@ module ForemanRemoteExecution
|
|
44
44
|
|
45
45
|
initializer 'foreman_remote_execution.register_plugin', before: :finisher_hook do |_app|
|
46
46
|
Foreman::Plugin.register :foreman_remote_execution do
|
47
|
-
requires_foreman '>=
|
47
|
+
requires_foreman '>= 2.2'
|
48
48
|
|
49
49
|
apipie_documented_controllers ["#{ForemanRemoteExecution::Engine.root}/app/controllers/api/v2/*.rb"]
|
50
|
+
ApipieDSL.configuration.dsl_classes_matchers += [
|
51
|
+
"#{ForemanRemoteExecution::Engine.root}/app/lib/foreman_remote_execution/renderer/**/*.rb",
|
52
|
+
]
|
50
53
|
|
51
54
|
automatic_assets(false)
|
52
55
|
precompile_assets(*assets_to_precompile)
|
@@ -38,13 +38,14 @@ module Api
|
|
38
38
|
|
39
39
|
test 'should see only permitted hosts' do
|
40
40
|
@user = FactoryBot.create(:user, admin: false)
|
41
|
+
@invocation.task.update(user: @user)
|
41
42
|
setup_user('view', 'job_invocations', nil, @user)
|
42
43
|
setup_user('view', 'hosts', 'name ~ nope.example.com', @user)
|
43
44
|
|
44
|
-
get :show, params: { :id => @invocation.id }, session:
|
45
|
+
get :show, params: { :id => @invocation.id }, session: prepare_user(@user)
|
45
46
|
assert_response :success
|
46
47
|
response = ActiveSupport::JSON.decode(@response.body)
|
47
|
-
|
48
|
+
assert_equal response['targeting']['hosts'], []
|
48
49
|
end
|
49
50
|
end
|
50
51
|
|
@@ -298,6 +299,68 @@ module Api
|
|
298
299
|
post :rerun, params: { :id => @invocation.id }
|
299
300
|
assert_response 404
|
300
301
|
end
|
302
|
+
|
303
|
+
describe 'restricted access' do
|
304
|
+
setup do
|
305
|
+
@admin = FactoryBot.create(:user, mail: 'admin@test.foreman.com', admin: true)
|
306
|
+
@user = FactoryBot.create(:user, mail: 'user@test.foreman.com', admin: false)
|
307
|
+
@invocation = FactoryBot.create(:job_invocation, :with_template, :with_task, :with_unplanned_host)
|
308
|
+
@invocation2 = FactoryBot.create(:job_invocation, :with_template, :with_task, :with_unplanned_host)
|
309
|
+
|
310
|
+
@invocation.task.update(user: @admin)
|
311
|
+
@invocation2.task.update(user: @user)
|
312
|
+
|
313
|
+
setup_user 'view', 'hosts', nil, @user
|
314
|
+
setup_user 'view', 'job_invocations', 'user = current_user', @user
|
315
|
+
setup_user 'create', 'job_invocations', 'user = current_user', @user
|
316
|
+
setup_user 'cancel', 'job_invocations', 'user = current_user', @user
|
317
|
+
end
|
318
|
+
|
319
|
+
let(:host) { @invocation.targeting.hosts.first }
|
320
|
+
let(:host2) { @invocation2.targeting.hosts.first }
|
321
|
+
|
322
|
+
context 'without user filter' do
|
323
|
+
test '#index' do
|
324
|
+
get :index, session: prepare_user(@admin)
|
325
|
+
assert_response :success
|
326
|
+
assert JSON.parse(@response.body)['results'].size >= 2
|
327
|
+
end
|
328
|
+
|
329
|
+
test '#show' do
|
330
|
+
get :show, params: { id: @invocation2.id }, session: prepare_user(@admin)
|
331
|
+
assert_response :success
|
332
|
+
end
|
333
|
+
|
334
|
+
test '#output' do
|
335
|
+
get :output, params: { job_invocation_id: @invocation2.id, host_id: host2.id }, session: prepare_user(@admin)
|
336
|
+
assert_response :success
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
context 'with user filter' do
|
341
|
+
test '#index' do
|
342
|
+
get :index, session: prepare_user(@user)
|
343
|
+
assert_response :success
|
344
|
+
assert_equal 1, JSON.parse(@response.body)['results'].size
|
345
|
+
end
|
346
|
+
|
347
|
+
test '#show' do
|
348
|
+
get :show, params: { id: @invocation.id }, session: prepare_user(@user)
|
349
|
+
assert_response :not_found
|
350
|
+
end
|
351
|
+
|
352
|
+
test '#output' do
|
353
|
+
get :output, params: { job_invocation_id: @invocation.id, host_id: host.id }, session: prepare_user(@user)
|
354
|
+
assert_response :not_found
|
355
|
+
assert_includes @response.body, 'Job invocation not found'
|
356
|
+
end
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
def prepare_user(user)
|
361
|
+
User.current = user
|
362
|
+
set_session_user(user)
|
363
|
+
end
|
301
364
|
end
|
302
365
|
end
|
303
366
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'test_plugin_helper'
|
4
|
+
require_relative '../support/remote_execution_helper'
|
4
5
|
|
5
6
|
class JobInvocationsControllerTest < ActionController::TestCase
|
6
7
|
test 'should parse inputs coming from the URL params' do
|
@@ -58,4 +59,74 @@ class JobInvocationsControllerTest < ActionController::TestCase
|
|
58
59
|
post :new, params: params, session: set_session_user
|
59
60
|
assert_response :success
|
60
61
|
end
|
62
|
+
|
63
|
+
context 'restricted access' do
|
64
|
+
setup do
|
65
|
+
@admin = users(:admin)
|
66
|
+
@user = FactoryBot.create(:user, mail: 'test23@test.foreman.com', admin: false)
|
67
|
+
@invocation = FactoryBot.create(:job_invocation, :with_template, :with_task)
|
68
|
+
@invocation2 = FactoryBot.create(:job_invocation, :with_template, :with_task)
|
69
|
+
|
70
|
+
@invocation.task.update(user: @admin)
|
71
|
+
@invocation2.task.update(user: @user)
|
72
|
+
|
73
|
+
setup_user 'view', 'hosts', nil, @user
|
74
|
+
setup_user 'view', 'job_invocations', 'user = current_user', @user
|
75
|
+
setup_user 'create', 'job_invocations', 'user = current_user', @user
|
76
|
+
setup_user 'cancel', 'job_invocations', 'user = current_user', @user
|
77
|
+
end
|
78
|
+
|
79
|
+
context 'without user filter' do
|
80
|
+
test '#index' do
|
81
|
+
get :index, session: prepare_user(@admin)
|
82
|
+
assert_response :success
|
83
|
+
assert 2, assigns(:job_invocations).size
|
84
|
+
end
|
85
|
+
|
86
|
+
test '#show' do
|
87
|
+
get :show, params: { id: @invocation2.id }, session: prepare_user(@admin)
|
88
|
+
assert_response :success
|
89
|
+
end
|
90
|
+
|
91
|
+
test '#rerun' do
|
92
|
+
get :rerun, params: { id: @invocation2.id }, session: prepare_user(@admin)
|
93
|
+
assert_response :success
|
94
|
+
end
|
95
|
+
|
96
|
+
test '#cancel' do
|
97
|
+
ForemanTasks::Task.any_instance.expects(:cancel).returns(true)
|
98
|
+
post :cancel, params: { id: @invocation2.id }, session: prepare_user(@admin)
|
99
|
+
assert_response :redirect
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
context 'with user filter' do
|
104
|
+
test '#index' do
|
105
|
+
get :index, session: prepare_user(@user)
|
106
|
+
assert_response :success
|
107
|
+
assert_equal 1, assigns(:job_invocations).size
|
108
|
+
assert_equal @invocation2, assigns(:job_invocations)[0]
|
109
|
+
end
|
110
|
+
|
111
|
+
test '#show' do
|
112
|
+
get :show, params: { id: @invocation.id }, session: prepare_user(@user)
|
113
|
+
assert_response :not_found
|
114
|
+
end
|
115
|
+
|
116
|
+
test '#rerun' do
|
117
|
+
get :rerun, params: { id: @invocation.id }, session: prepare_user(@user)
|
118
|
+
assert_response :not_found
|
119
|
+
end
|
120
|
+
|
121
|
+
test 'cancel' do
|
122
|
+
post :cancel, params: { id: @invocation.id }, session: prepare_user(@user)
|
123
|
+
assert_response :not_found
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def prepare_user(user)
|
129
|
+
User.current = user
|
130
|
+
set_session_user(user)
|
131
|
+
end
|
61
132
|
end
|
@@ -30,7 +30,7 @@ class SSHOrchestrationTest < ActiveSupport::TestCase
|
|
30
30
|
|
31
31
|
it 'does not fail on 404 from the smart proxy' do
|
32
32
|
host.stubs(:skip_orchestration?).returns false
|
33
|
-
|
33
|
+
::ProxyAPI::RemoteExecutionSSH.any_instance.expects(:delete).raises(RestClient::ResourceNotFound).twice
|
34
34
|
host.build = true
|
35
35
|
host.save!
|
36
36
|
ids = ["ssh_remove_known_hosts_interface_#{interface.ip}_#{proxy.id}",
|
@@ -12,7 +12,7 @@ module ForemanRemoteExecution
|
|
12
12
|
let(:provider) do
|
13
13
|
provider = ::SSHExecutionProvider
|
14
14
|
provider.expects(:ssh_password).with(host).returns('sshpass')
|
15
|
-
provider.expects(:
|
15
|
+
provider.expects(:effective_user_password).with(host).returns('sudopass')
|
16
16
|
provider.expects(:ssh_key_passphrase).with(host).returns('keypass')
|
17
17
|
provider
|
18
18
|
end
|
@@ -21,7 +21,7 @@ module ForemanRemoteExecution
|
|
21
21
|
secrets = subject.secrets(host, job_invocation, provider)
|
22
22
|
|
23
23
|
assert_equal 'sshpass', secrets[:ssh_password]
|
24
|
-
assert_equal 'sudopass', secrets[:
|
24
|
+
assert_equal 'sudopass', secrets[:effective_user_password]
|
25
25
|
assert_equal 'keypass', secrets[:key_passphrase]
|
26
26
|
end
|
27
27
|
|
@@ -31,7 +31,7 @@ module ForemanRemoteExecution
|
|
31
31
|
secrets = subject.secrets(host, job_invocation, provider)
|
32
32
|
|
33
33
|
assert_equal 'jobsshpass', secrets[:ssh_password]
|
34
|
-
assert_equal 'sudopass', secrets[:
|
34
|
+
assert_equal 'sudopass', secrets[:effective_user_password]
|
35
35
|
assert_equal 'jobkeypass', secrets[:key_passphrase]
|
36
36
|
end
|
37
37
|
end
|
@@ -14,7 +14,7 @@ module ForemanRemoteExecution
|
|
14
14
|
invocation.description = 'Some short description'
|
15
15
|
invocation.password = 'changeme'
|
16
16
|
invocation.key_passphrase = 'changemetoo'
|
17
|
-
invocation.
|
17
|
+
invocation.effective_user_password = 'sudopassword'
|
18
18
|
invocation.save
|
19
19
|
end
|
20
20
|
end
|
@@ -523,15 +523,15 @@ class JobInvocationComposerTest < ActiveSupport::TestCase
|
|
523
523
|
end
|
524
524
|
end
|
525
525
|
|
526
|
-
describe '#
|
527
|
-
let(:
|
526
|
+
describe '#effective_user_password' do
|
527
|
+
let(:effective_user_password) { 'password' }
|
528
528
|
let(:params) do
|
529
|
-
{ :job_invocation => { :
|
529
|
+
{ :job_invocation => { :effective_user_password => effective_user_password }}
|
530
530
|
end
|
531
531
|
|
532
|
-
it 'sets the
|
532
|
+
it 'sets the effective_user_password password properly' do
|
533
533
|
composer
|
534
|
-
_(composer.job_invocation.
|
534
|
+
_(composer.job_invocation.effective_user_password).must_equal effective_user_password
|
535
535
|
end
|
536
536
|
end
|
537
537
|
|
@@ -78,12 +78,12 @@ class RemoteExecutionProviderTest < ActiveSupport::TestCase
|
|
78
78
|
end
|
79
79
|
end
|
80
80
|
|
81
|
-
describe '
|
82
|
-
it 'uses the
|
83
|
-
host.params['
|
84
|
-
host.host_parameters << FactoryBot.create(:host_parameter, :host => host, :name => '
|
85
|
-
assert_not proxy_options.key?(:
|
86
|
-
_(secrets[:
|
81
|
+
describe 'effective user password' do
|
82
|
+
it 'uses the remote_execution_effective_user_password on the host param' do
|
83
|
+
host.params['remote_execution_effective_user_password'] = 'mypassword'
|
84
|
+
host.host_parameters << FactoryBot.create(:host_parameter, :host => host, :name => 'remote_execution_effective_user_password', :value => 'mypassword')
|
85
|
+
assert_not proxy_options.key?(:effective_user_password)
|
86
|
+
_(secrets[:effective_user_password]).must_equal 'mypassword'
|
87
87
|
end
|
88
88
|
end
|
89
89
|
|
@@ -1,3 +1,24 @@
|
|
1
1
|
export const STATUS = {
|
2
|
+
PENDING: 'PENDING',
|
3
|
+
RESOLVED: 'RESOLVED',
|
2
4
|
ERROR: 'ERROR',
|
3
5
|
};
|
6
|
+
|
7
|
+
export const getControllerSearchProps = (
|
8
|
+
controller,
|
9
|
+
id = 'searchBar',
|
10
|
+
canCreate = true
|
11
|
+
) => ({
|
12
|
+
controller,
|
13
|
+
autocomplete: {
|
14
|
+
id,
|
15
|
+
searchQuery: '',
|
16
|
+
url: `${controller}/auto_complete_search`,
|
17
|
+
useKeyShortcuts: true,
|
18
|
+
},
|
19
|
+
bookmarks: {
|
20
|
+
url: '/api/bookmarks',
|
21
|
+
canCreate,
|
22
|
+
documentationUrl: `4.1.5Searching`,
|
23
|
+
},
|
24
|
+
});
|
@@ -0,0 +1 @@
|
|
1
|
+
export const selectDoesIntervalExist = () => false;
|
@@ -5,8 +5,8 @@ import { LoadingState, Alert } from 'patternfly-react';
|
|
5
5
|
import { STATUS } from 'foremanReact/constants';
|
6
6
|
import HostItem from './components/HostItem';
|
7
7
|
|
8
|
-
const TargetingHosts = ({
|
9
|
-
if (
|
8
|
+
const TargetingHosts = ({ apiStatus, items }) => {
|
9
|
+
if (apiStatus === STATUS.ERROR) {
|
10
10
|
return (
|
11
11
|
<Alert type="error">
|
12
12
|
{__(
|
@@ -16,8 +16,24 @@ const TargetingHosts = ({ status, items }) => {
|
|
16
16
|
);
|
17
17
|
}
|
18
18
|
|
19
|
+
const tableBodyRows = items.length ? (
|
20
|
+
items.map(({ name, link, status, actions }) => (
|
21
|
+
<HostItem
|
22
|
+
key={name}
|
23
|
+
name={name}
|
24
|
+
link={link}
|
25
|
+
status={status}
|
26
|
+
actions={actions}
|
27
|
+
/>
|
28
|
+
))
|
29
|
+
) : (
|
30
|
+
<tr>
|
31
|
+
<td colSpan="3">{__('No hosts found.')}</td>
|
32
|
+
</tr>
|
33
|
+
);
|
34
|
+
|
19
35
|
return (
|
20
|
-
<LoadingState loading={!items.length}>
|
36
|
+
<LoadingState loading={!items.length && apiStatus === STATUS.PENDING}>
|
21
37
|
<div>
|
22
38
|
<table className="table table-bordered table-striped table-hover">
|
23
39
|
<thead>
|
@@ -27,17 +43,7 @@ const TargetingHosts = ({ status, items }) => {
|
|
27
43
|
<th>{__('Actions')}</th>
|
28
44
|
</tr>
|
29
45
|
</thead>
|
30
|
-
<tbody>
|
31
|
-
{items.map(host => (
|
32
|
-
<HostItem
|
33
|
-
key={host.name}
|
34
|
-
name={host.name}
|
35
|
-
link={host.link}
|
36
|
-
status={host.status}
|
37
|
-
actions={host.actions}
|
38
|
-
/>
|
39
|
-
))}
|
40
|
-
</tbody>
|
46
|
+
<tbody>{tableBodyRows}</tbody>
|
41
47
|
</table>
|
42
48
|
</div>
|
43
49
|
</LoadingState>
|
@@ -45,7 +51,7 @@ const TargetingHosts = ({ status, items }) => {
|
|
45
51
|
};
|
46
52
|
|
47
53
|
TargetingHosts.propTypes = {
|
48
|
-
|
54
|
+
apiStatus: PropTypes.string.isRequired,
|
49
55
|
items: PropTypes.array.isRequired,
|
50
56
|
};
|
51
57
|
|