foreman_rh_cloud 12.2.1 → 12.2.2
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/rh_cloud/advisor_engine_config_controller.rb +1 -1
- data/app/controllers/api/v2/rh_cloud/cloud_request_controller.rb +3 -0
- data/app/controllers/api/v2/rh_cloud/inventory_controller.rb +3 -0
- data/app/controllers/concerns/foreman_rh_cloud/iop_smart_proxy_access.rb +28 -0
- data/app/controllers/concerns/insights_cloud/package_profile_upload_extensions.rb +1 -1
- data/app/controllers/foreman_inventory_upload/uploads_controller.rb +3 -0
- data/app/controllers/foreman_rh_cloud/foreman_rh_cloud_controller.rb +22 -0
- data/app/controllers/insights_cloud/api/machine_telemetries_controller.rb +19 -5
- data/app/services/foreman_rh_cloud/cert_auth.rb +1 -1
- data/app/services/foreman_rh_cloud/cloud_request.rb +1 -1
- data/app/services/foreman_rh_cloud/cloud_request_forwarder.rb +1 -1
- data/app/services/foreman_rh_cloud/hit_remediations_retriever.rb +27 -10
- data/app/views/api/v2/hosts/insights/base.rabl +2 -2
- data/app/views/api/v2/hosts/insights/single.rabl +1 -1
- data/config/routes.rb +6 -14
- data/lib/foreman_inventory_upload/async/generate_all_reports_job.rb +2 -2
- data/lib/foreman_inventory_upload/async/upload_report_job.rb +1 -1
- data/lib/foreman_inventory_upload/generators/slice.rb +24 -0
- data/lib/foreman_rh_cloud/engine.rb +6 -2
- data/lib/foreman_rh_cloud/plugin.rb +14 -2
- data/lib/foreman_rh_cloud/version.rb +1 -1
- data/lib/foreman_rh_cloud.rb +5 -6
- data/lib/insights_cloud/async/insights_scheduled_sync.rb +2 -2
- data/lib/inventory_sync/async/inventory_hosts_sync.rb +1 -1
- data/lib/inventory_sync/async/inventory_scheduled_sync.rb +2 -2
- data/lib/tasks/insights.rake +1 -1
- data/lib/tasks/rh_cloud_inventory.rake +20 -2
- data/package.json +1 -1
- data/test/controllers/insights_cloud/api/machine_telemetries_controller_test.rb +20 -3
- data/test/controllers/insights_sync/settings_controller_test.rb +1 -1
- data/test/factories/inventory_upload_factories.rb +4 -112
- data/test/jobs/inventory_scheduled_sync_test.rb +3 -3
- data/test/test_plugin_helper.rb +8 -2
- data/test/unit/rh_cloud_http_proxy_test.rb +3 -3
- data/test/unit/services/foreman_rh_cloud/cloud_request_forwarder_test.rb +4 -1
- data/test/unit/slice_generator_test.rb +33 -0
- data/test/unit/tags_generator_test.rb +4 -1
- data/webpack/CVEsHostDetailsTab/CVEsHostDetailsTab.js +1 -1
- data/webpack/CveDetailsPage/CveDetailsPage.js +1 -1
- data/webpack/CveDetailsPage/CveDetailsPage.test.js +1 -3
- data/webpack/ForemanRhCloudPages.js +1 -0
- data/webpack/InsightsCloudSync/Components/RemediationModal/RemediationHelpers.js +26 -4
- data/webpack/InsightsCloudSync/Components/RemediationModal/RemediationModal.js +85 -11
- data/webpack/InsightsCloudSync/Components/RemediationModal/RemediationModalFooter.js +39 -5
- data/webpack/InsightsCloudSync/Components/RemediationModal/Resolutions.js +13 -0
- data/webpack/InsightsCloudSync/InsightsCloudSync.js +9 -7
- data/webpack/InsightsHostDetailsTab/NewHostDetailsTab.js +12 -10
- data/webpack/InsightsVulnerability/InsightsVulnerabilityListPage.js +1 -1
- data/webpack/IopRecommendationDetails/IopRecommendationDetails.js +1 -1
- data/webpack/common/Hooks/ConfigHooks.js +1 -2
- data/webpack/common/styles.scss +7 -0
- metadata +4 -1
@@ -1,87 +1,3 @@
|
|
1
|
-
# redefine katello factories, as long as katello is not compatible with dynamic properties
|
2
|
-
FactoryBot.define do
|
3
|
-
factory :katello_organization, :class => "Organization" do
|
4
|
-
type { "Organization" }
|
5
|
-
sequence(:name) { |n| "Organization#{n}" }
|
6
|
-
sequence(:label) { |n| "org#{n}" }
|
7
|
-
sequence(:id) { |n| n }
|
8
|
-
|
9
|
-
trait :acme_corporation do
|
10
|
-
name { "ACME_Corporation" }
|
11
|
-
type { "Organization" }
|
12
|
-
description { "This is the first Organization." }
|
13
|
-
label { "acme_corporation_label" }
|
14
|
-
end
|
15
|
-
|
16
|
-
trait :with_library do
|
17
|
-
association :library, :factory => :katello_library
|
18
|
-
end
|
19
|
-
|
20
|
-
factory :acme_corporation, :traits => [:acme_corporation]
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
FactoryBot.define do
|
25
|
-
factory :katello_content_view, :class => Katello::ContentView do
|
26
|
-
sequence(:name) { |n| "Database#{n}" }
|
27
|
-
description { "This content view is for database content" }
|
28
|
-
association :organization, :factory => :katello_organization
|
29
|
-
|
30
|
-
trait :composite do
|
31
|
-
composite { true }
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
FactoryBot.define do
|
37
|
-
factory :katello_content_view_environment, :class => Katello::ContentViewEnvironment do
|
38
|
-
sequence(:name) { |n| "name#{n}" }
|
39
|
-
sequence(:label) { |n| "label#{n}" }
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
FactoryBot.define do
|
44
|
-
factory :katello_k_t_environment, :aliases => [:katello_environment], :class => Katello::KTEnvironment do
|
45
|
-
sequence(:name) { |n| "Environment#{n}" }
|
46
|
-
sequence(:label) { |n| "environment#{n}" }
|
47
|
-
association :organization, :factory => :katello_organization
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
FactoryBot.define do
|
52
|
-
factory :katello_content_facets, :aliases => [:content_facet], :class => ::Katello::Host::ContentFacet do
|
53
|
-
sequence(:uuid) { |n| "uuid-#{n}" }
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
FactoryBot.define do
|
58
|
-
factory :katello_subscription_facets, :aliases => [:subscription_facet], :class => ::Katello::Host::SubscriptionFacet do
|
59
|
-
sequence(:uuid) { |n| "00000000-%<n>04d-%<r>04d-0000-000000000000" % { n: n, r: rand(500) } }
|
60
|
-
facts { { 'memory.memtotal' => "12 GB" } }
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
FactoryBot.define do
|
65
|
-
factory :katello_subscription, :class => Katello::Subscription do
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
FactoryBot.define do
|
70
|
-
factory :katello_pool, :class => Katello::Pool do
|
71
|
-
active { true }
|
72
|
-
end_date { Date.today + 1.year }
|
73
|
-
cp_id { 1 }
|
74
|
-
|
75
|
-
association :organization, :factory => :katello_organization
|
76
|
-
|
77
|
-
after(:build) do |pool, _evaluator|
|
78
|
-
pool.subscription.organization = pool.organization
|
79
|
-
end
|
80
|
-
|
81
|
-
association :subscription, :factory => :katello_subscription
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
1
|
FactoryBot.define do
|
86
2
|
factory :katello_host_collection_host, :class => Katello::HostCollectionHosts do
|
87
3
|
host_id { nil }
|
@@ -94,34 +10,10 @@ FactoryBot.define do
|
|
94
10
|
end
|
95
11
|
end
|
96
12
|
|
13
|
+
# Fix Katello factories to return a valid UUID
|
97
14
|
FactoryBot.modify do
|
98
|
-
factory :
|
99
|
-
|
100
|
-
|
101
|
-
lifecycle_environment { nil }
|
102
|
-
content_source { nil }
|
103
|
-
content_view_environments { [] }
|
104
|
-
end
|
105
|
-
|
106
|
-
trait :with_content do
|
107
|
-
association :content_facet, :factory => :content_facet, :strategy => :build
|
108
|
-
|
109
|
-
after(:build) do |host, evaluator|
|
110
|
-
if host.content_facet
|
111
|
-
if evaluator.content_view && evaluator.lifecycle_environment
|
112
|
-
host.content_facet.assign_single_environment(
|
113
|
-
content_view_id: evaluator.content_view.id,
|
114
|
-
lifecycle_environment_id: evaluator.lifecycle_environment.id
|
115
|
-
)
|
116
|
-
end
|
117
|
-
host.content_facet.content_source = evaluator.content_source if evaluator.content_source
|
118
|
-
host.content_facet.content_view_environments = evaluator.content_view_environments unless evaluator.content_view_environments.empty?
|
119
|
-
end
|
120
|
-
end
|
121
|
-
end
|
122
|
-
|
123
|
-
trait :with_subscription do
|
124
|
-
association :subscription_facet, :factory => :subscription_facet, :strategy => :build
|
125
|
-
end
|
15
|
+
factory :katello_subscription_facets, :aliases => [:subscription_facet], :class => ::Katello::Host::SubscriptionFacet do
|
16
|
+
sequence(:uuid) { |n| "00000000-%<n>04d-%<r>04d-0000-000000000000" % { n: n, r: rand(500) } }
|
17
|
+
facts { { 'memory.memtotal' => "12 GB" } }
|
126
18
|
end
|
127
19
|
end
|
@@ -14,14 +14,14 @@ class InventoryScheduledSyncTest < ActiveSupport::TestCase
|
|
14
14
|
ForemanTasks.sync_task(InventorySync::Async::InventoryScheduledSync)
|
15
15
|
end
|
16
16
|
|
17
|
-
test 'Skips execution if
|
18
|
-
ForemanRhCloud.stubs(:
|
17
|
+
test 'Skips execution if with_iop_smart_proxy? is true' do
|
18
|
+
ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(true)
|
19
19
|
|
20
20
|
InventorySync::Async::InventoryScheduledSync.any_instance.expects(:plan_org_sync).never
|
21
21
|
|
22
22
|
task = ForemanTasks.sync_task(InventorySync::Async::InventoryScheduledSync)
|
23
23
|
status = task.output[:status].to_s
|
24
|
-
assert_match(/Foreman is configured with
|
24
|
+
assert_match(/Foreman is configured with a local IoP Smart Proxy/, status)
|
25
25
|
end
|
26
26
|
|
27
27
|
test 'Skips execution if auto upload is disabled' do
|
data/test/test_plugin_helper.rb
CHANGED
@@ -2,10 +2,16 @@
|
|
2
2
|
require 'test_helper'
|
3
3
|
|
4
4
|
# Add plugin to FactoryBot's paths
|
5
|
-
|
5
|
+
katello_files = Dir.glob("#{Katello::Engine.root}/test/factories/**/*.rb")
|
6
|
+
FactoryBot.definition_file_paths +=
|
7
|
+
katello_files
|
8
|
+
# skip foreman_task factories, since we already have those definitions in ForemanTasks
|
9
|
+
.reject { |f| f =~ /foreman_task|recurring_logic/ }
|
10
|
+
.map { |f| f.gsub('.rb', '') } # .rb extension is will be appended by factorybot
|
6
11
|
FactoryBot.definition_file_paths << "#{ForemanRemoteExecution::Engine.root}/test/factories"
|
12
|
+
FactoryBot.definition_file_paths << "#{ForemanTasks::Engine.root}/test/factories"
|
13
|
+
|
7
14
|
FactoryBot.definition_file_paths << File.join(File.dirname(__FILE__), 'factories')
|
8
|
-
# FactoryBot.definition_file_paths << "#{Katello::Engine.root}/test/factories"
|
9
15
|
FactoryBot.reload
|
10
16
|
|
11
17
|
begin
|
@@ -4,7 +4,7 @@ class RhCloudHttpProxyTest < ActiveSupport::TestCase
|
|
4
4
|
setup do
|
5
5
|
@global_content_proxy_mock = 'http://global:content@localhost:80'
|
6
6
|
@global_foreman_proxy_mock = 'http://global:foreman@localhost:80'
|
7
|
-
ForemanRhCloud.stubs(:
|
7
|
+
ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(false)
|
8
8
|
end
|
9
9
|
|
10
10
|
test 'selects global content proxy' do
|
@@ -20,8 +20,8 @@ class RhCloudHttpProxyTest < ActiveSupport::TestCase
|
|
20
20
|
end
|
21
21
|
|
22
22
|
test 'returns empty string in on-prem setup' do
|
23
|
-
ForemanRhCloud.unstub(:
|
24
|
-
ForemanRhCloud.stubs(:
|
23
|
+
ForemanRhCloud.unstub(:with_iop_smart_proxy?)
|
24
|
+
ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(true)
|
25
25
|
|
26
26
|
assert_empty ForemanRhCloud.proxy_setting
|
27
27
|
end
|
@@ -22,7 +22,10 @@ class CloudRequestForwarderTest < ActiveSupport::TestCase
|
|
22
22
|
:with_content,
|
23
23
|
:with_hostgroup,
|
24
24
|
:with_parameter,
|
25
|
-
|
25
|
+
content_facet: FactoryBot.build(
|
26
|
+
:content_facet,
|
27
|
+
content_view_environments: [make_cve(lifecycle_environment: env), make_cve(lifecycle_environment: env2)]
|
28
|
+
),
|
26
29
|
organization: env.organization
|
27
30
|
)
|
28
31
|
|
@@ -998,6 +998,39 @@ class SliceGeneratorTest < ActiveSupport::TestCase
|
|
998
998
|
assert_not_nil actual_host['insights_id']
|
999
999
|
end
|
1000
1000
|
|
1001
|
+
test 'reports yum repos' do
|
1002
|
+
ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(true)
|
1003
|
+
FactoryBot.create(:katello_content, cp_content_id: '1', organization: @host.organization, name: 'Test Content', label: 'test-content')
|
1004
|
+
repo = FactoryBot.build(
|
1005
|
+
:katello_repository,
|
1006
|
+
:fedora_17_x86_64_dev,
|
1007
|
+
:with_content_view,
|
1008
|
+
product: FactoryBot.create(:katello_product, :redhat, :with_provider, organization: @host.organization)
|
1009
|
+
)
|
1010
|
+
# force-save the root repo to avoid validation errors
|
1011
|
+
repo.root.save(validate: false)
|
1012
|
+
repo.save(validate: false)
|
1013
|
+
@host.content_facet.bound_repositories << repo
|
1014
|
+
@host.content_facet.content_source = FactoryBot.create(:smart_proxy, :with_pulp3)
|
1015
|
+
@host.save(validate: false)
|
1016
|
+
|
1017
|
+
batch = Host.where(id: @host.id).in_batches.first
|
1018
|
+
generator = create_generator(batch)
|
1019
|
+
json_str = generator.render
|
1020
|
+
actual = JSON.parse(json_str.join("\n"))
|
1021
|
+
|
1022
|
+
assert_not_nil(actual_host = actual['hosts'].first)
|
1023
|
+
assert_not_nil(actual_system_profile = actual_host['system_profile'])
|
1024
|
+
assert_not_nil(actual_yum_repos = actual_system_profile['yum_repos'])
|
1025
|
+
assert_equal 1, actual_yum_repos.count
|
1026
|
+
assert_not_nil(actual_yum_repo = actual_yum_repos.first)
|
1027
|
+
assert_equal 'Test Content', actual_yum_repo['name']
|
1028
|
+
assert_equal 'test-content', actual_yum_repo['id']
|
1029
|
+
assert_match(/fedora_17/, actual_yum_repo['base_url'])
|
1030
|
+
assert_equal true, actual_yum_repo['enabled']
|
1031
|
+
assert_equal true, actual_yum_repo['gpgcheck']
|
1032
|
+
end
|
1033
|
+
|
1001
1034
|
private
|
1002
1035
|
|
1003
1036
|
def create_generator(batch, name = '00000000-0000-0000-0000-000000000000')
|
@@ -26,7 +26,10 @@ class TagsGeneratorTest < ActiveSupport::TestCase
|
|
26
26
|
organization: env.organization,
|
27
27
|
location: @location2,
|
28
28
|
hostgroup: @hostgroup2,
|
29
|
-
|
29
|
+
content_facet: FactoryBot.build(
|
30
|
+
:content_facet,
|
31
|
+
content_view_environments: [make_cve(lifecycle_environment: env), make_cve(lifecycle_environment: env2)]
|
32
|
+
)
|
30
33
|
)
|
31
34
|
|
32
35
|
@host.organization.pools << FactoryBot.create(:katello_pool, account_number: '1234', cp_id: 1)
|
@@ -8,7 +8,7 @@ const CVEsHostDetailsTab = ({ systemId }) => {
|
|
8
8
|
const scope = 'vulnerability';
|
9
9
|
const module = './SystemDetailTable';
|
10
10
|
return (
|
11
|
-
<div className="rh-cloud-insights-vulnerability-host-details-component">
|
11
|
+
<div className="rh-cloud-insights-vulnerability-host-details-component vulnerability">
|
12
12
|
<ScalprumComponent scope={scope} module={module} systemId={systemId} />
|
13
13
|
</div>
|
14
14
|
);
|
@@ -10,7 +10,7 @@ const CveDetailsPage = () => {
|
|
10
10
|
|
11
11
|
return (
|
12
12
|
<ScalprumProvider {...providerOptions}>
|
13
|
-
<div className="rh-cloud-cve-details-page">
|
13
|
+
<div className="rh-cloud-cve-details-page vulnerability">
|
14
14
|
<ScalprumComponent scope={scope} module={module} cveId={cveId} />
|
15
15
|
</div>
|
16
16
|
</ScalprumProvider>
|
@@ -18,9 +18,7 @@ jest.mock('@scalprum/react-core', () => ({
|
|
18
18
|
describe('CveDetailsPage component', () => {
|
19
19
|
it('renders the container with correct class', () => {
|
20
20
|
const { container } = render(<CveDetailsPage />);
|
21
|
-
expect(
|
22
|
-
container.querySelector('.rh-cloud-cve-details-page')
|
23
|
-
).toBeTruthy();
|
21
|
+
expect(container.querySelector('.rh-cloud-cve-details-page')).toBeTruthy();
|
24
22
|
});
|
25
23
|
|
26
24
|
it('passes cveId from URL params to ScalprumComponent', () => {
|
@@ -8,6 +8,7 @@ import InsightsCloudSync from './InsightsCloudSync';
|
|
8
8
|
import IopRecommendationDetails from './IopRecommendationDetails/IopRecommendationDetails';
|
9
9
|
import InsightsHostDetailsTab from './InsightsHostDetailsTab';
|
10
10
|
import CveDetailsPage from './CveDetailsPage';
|
11
|
+
import './common/styles.scss';
|
11
12
|
|
12
13
|
const pages = [
|
13
14
|
{ name: 'ForemanInventoryUpload', type: ForemanInventoryUpload },
|
@@ -3,7 +3,15 @@ import React from 'react';
|
|
3
3
|
import { orderBy } from 'lodash';
|
4
4
|
import Resolutions from './Resolutions';
|
5
5
|
|
6
|
-
export const
|
6
|
+
export const getResolutionId = (selectedResolution, id) =>
|
7
|
+
`${id}_${selectedResolution}`;
|
8
|
+
|
9
|
+
export const modifyRows = (
|
10
|
+
remediations,
|
11
|
+
setResolutions,
|
12
|
+
setHostsIds,
|
13
|
+
isIop
|
14
|
+
) => {
|
7
15
|
if (remediations.length === 0) return [];
|
8
16
|
|
9
17
|
const resolutionToSubmit = [];
|
@@ -15,9 +23,22 @@ export const modifyRows = (remediations, setResolutions, setHostsIds) => {
|
|
15
23
|
).map(({ id, host_id, hostname, title, resolutions, reboot }) => {
|
16
24
|
hostsIdsToSubmit.add(host_id);
|
17
25
|
const selectedResolution = resolutions[0]?.id;
|
26
|
+
/* eslint-disable spellcheck/spell-checker */
|
27
|
+
|
28
|
+
// For IoP: {
|
29
|
+
// hit_id: "c7c6727e-2966-4f7c-87f1-20ef14db7a2d",
|
30
|
+
// rule_id: "hardening_ssh_client_alive|OPENSSH_HARDENING_CLIENT_ALIVE",
|
31
|
+
// resolution_type: "less_secure",
|
32
|
+
// resolution_id:"hardening_ssh_client_alive|OPENSSH_HARDENING_CLIENT_ALIVE_less_secure",
|
33
|
+
// }
|
34
|
+
// for Hosted, hit_id and rule_id will be Foreman database IDs
|
35
|
+
|
36
|
+
/* eslint-enable spellcheck/spell-checker */
|
18
37
|
resolutionToSubmit.push({
|
19
|
-
hit_id: id,
|
20
|
-
|
38
|
+
hit_id: isIop ? host_id : id,
|
39
|
+
rule_id: id,
|
40
|
+
resolution_type: selectedResolution /** defaults to the first resolution if many */,
|
41
|
+
resolution_id: getResolutionId(selectedResolution, id),
|
21
42
|
});
|
22
43
|
return {
|
23
44
|
cells: [
|
@@ -25,10 +46,11 @@ export const modifyRows = (remediations, setResolutions, setHostsIds) => {
|
|
25
46
|
title,
|
26
47
|
<div>
|
27
48
|
<Resolutions
|
28
|
-
hit_id={id}
|
49
|
+
hit_id={isIop ? host_id : id}
|
29
50
|
resolutions={resolutions}
|
30
51
|
setResolutions={setResolutions}
|
31
52
|
selectedResolution={selectedResolution}
|
53
|
+
isIop={isIop}
|
32
54
|
/>
|
33
55
|
</div>,
|
34
56
|
reboot,
|
@@ -1,5 +1,6 @@
|
|
1
1
|
/* eslint-disable react-hooks/exhaustive-deps */
|
2
2
|
import React, { useEffect } from 'react';
|
3
|
+
import Immutable from 'seamless-immutable';
|
3
4
|
import PropTypes from 'prop-types';
|
4
5
|
import {
|
5
6
|
Table,
|
@@ -7,7 +8,7 @@ import {
|
|
7
8
|
TableBody,
|
8
9
|
} from '@patternfly/react-table/deprecated';
|
9
10
|
import { Modal, ModalVariant, Button } from '@patternfly/react-core';
|
10
|
-
import { isEmpty } from 'lodash';
|
11
|
+
import { isEmpty, noop } from 'lodash';
|
11
12
|
import { STATUS } from 'foremanReact/constants';
|
12
13
|
import { translate as __ } from 'foremanReact/common/I18n';
|
13
14
|
import { columns } from './RemediationTableConstants';
|
@@ -15,8 +16,48 @@ import { modifyRows } from './RemediationHelpers';
|
|
15
16
|
import ModalFooter from './RemediationModalFooter';
|
16
17
|
import TableEmptyState from '../../../common/table/EmptyState';
|
17
18
|
import './RemediationModal.scss';
|
19
|
+
import { useAdvisorEngineConfig } from '../../../common/Hooks/ConfigHooks';
|
20
|
+
|
21
|
+
/* eslint-disable spellcheck/spell-checker */
|
22
|
+
|
23
|
+
// Sample iopData:
|
24
|
+
// const iopTestData = Immutable([
|
25
|
+
// {
|
26
|
+
// hostid: 'c7c6727e-2966-4f7c-87f1-20ef14db7a2d',
|
27
|
+
// host_name: 'advisor-test.local',
|
28
|
+
// rulename: 'hardening_cryptopol_krb5|NO_CPOL_KRB5',
|
29
|
+
// resolutions: [
|
30
|
+
// {
|
31
|
+
// description: 'Remove manual crypto-policies',
|
32
|
+
// id: 'fix',
|
33
|
+
// needs_reboot: true,
|
34
|
+
// resolution_risk: 1,
|
35
|
+
// },
|
36
|
+
// ],
|
37
|
+
// rebootable: true,
|
38
|
+
// description: 'Decreased security: krb5 crypto-policies overridden',
|
39
|
+
// },
|
40
|
+
// {
|
41
|
+
// hostid: 'c7c6727e-2966-4f7c-87f1-20ef14db7a2d',
|
42
|
+
// host_name: 'advisor-test.local',
|
43
|
+
// rulename: 'hardening_logging_auditd|HARDENING_LOGGING_5_AUDITD',
|
44
|
+
// resolutions: [
|
45
|
+
// {
|
46
|
+
// description: 'Install and enable auditd',
|
47
|
+
// id: 'fix',
|
48
|
+
// needs_reboot: false,
|
49
|
+
// resolution_risk: 1,
|
50
|
+
// },
|
51
|
+
// ],
|
52
|
+
// rebootable: false,
|
53
|
+
// description: 'Decreased security: auditd not running',
|
54
|
+
// },
|
55
|
+
// ]);
|
56
|
+
|
57
|
+
/* eslint-enable spellcheck/spell-checker */
|
18
58
|
|
19
59
|
const RemediationModal = ({
|
60
|
+
iopData,
|
20
61
|
selectedIds,
|
21
62
|
fetchRemediations,
|
22
63
|
remediations,
|
@@ -24,31 +65,49 @@ const RemediationModal = ({
|
|
24
65
|
error,
|
25
66
|
isAllSelected,
|
26
67
|
query,
|
68
|
+
isDisabled,
|
27
69
|
}) => {
|
28
|
-
const
|
70
|
+
const iopRows = Immutable(iopData ?? []).map(recommendation => ({
|
71
|
+
id: recommendation.rulename,
|
72
|
+
host_id: recommendation.hostid,
|
73
|
+
hostname: recommendation.host_name,
|
74
|
+
title: recommendation.description,
|
75
|
+
resolutions: recommendation.resolutions ?? [],
|
76
|
+
reboot: recommendation.rebootable,
|
77
|
+
}));
|
78
|
+
|
29
79
|
const [open, setOpen] = React.useState(false);
|
30
80
|
const [resolutions, setResolutions] = React.useState([]);
|
31
81
|
const [hostsIds, setHostsIds] = React.useState([]);
|
82
|
+
const [rows, setRows] = React.useState([]);
|
32
83
|
const toggleModal = () => setOpen(prevValue => !prevValue);
|
33
84
|
|
85
|
+
const isIop = useAdvisorEngineConfig();
|
34
86
|
useEffect(() => {
|
35
|
-
|
87
|
+
// only fetch for Hosted. IoP provides via props.
|
88
|
+
if (!isIop && open)
|
89
|
+
fetchRemediations({ selectedIds, isAllSelected, query });
|
36
90
|
}, [open]);
|
37
91
|
|
38
92
|
useEffect(() => {
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
93
|
+
let modifiedRows;
|
94
|
+
if (isIop) {
|
95
|
+
modifiedRows = modifyRows(iopRows, setResolutions, setHostsIds, true);
|
96
|
+
} else {
|
97
|
+
modifiedRows =
|
98
|
+
status === STATUS.PENDING
|
99
|
+
? []
|
100
|
+
: modifyRows(remediations, setResolutions, setHostsIds, false);
|
101
|
+
}
|
43
102
|
setRows(modifiedRows);
|
44
|
-
}, [remediations, status]);
|
103
|
+
}, [remediations, status, iopData, isIop]);
|
45
104
|
|
46
105
|
return (
|
47
106
|
<React.Fragment>
|
48
107
|
<Button
|
49
108
|
ouiaId="button-remediate"
|
50
109
|
variant="primary"
|
51
|
-
isDisabled={isEmpty(selectedIds)}
|
110
|
+
isDisabled={isDisabled || isEmpty(selectedIds)}
|
52
111
|
onClick={() => {
|
53
112
|
toggleModal();
|
54
113
|
}}
|
@@ -68,6 +127,7 @@ const RemediationModal = ({
|
|
68
127
|
toggleModal={toggleModal}
|
69
128
|
resolutions={resolutions}
|
70
129
|
hostsIds={hostsIds}
|
130
|
+
isIop={isIop}
|
71
131
|
/>
|
72
132
|
}
|
73
133
|
>
|
@@ -92,22 +152,36 @@ const RemediationModal = ({
|
|
92
152
|
};
|
93
153
|
|
94
154
|
RemediationModal.propTypes = {
|
95
|
-
|
96
|
-
|
155
|
+
iopData: PropTypes.arrayOf(
|
156
|
+
PropTypes.shape({
|
157
|
+
hostid: PropTypes.string,
|
158
|
+
host_name: PropTypes.string,
|
159
|
+
rulename: PropTypes.string,
|
160
|
+
resolutions: PropTypes.array,
|
161
|
+
rebootable: PropTypes.bool,
|
162
|
+
description: PropTypes.string,
|
163
|
+
})
|
164
|
+
),
|
165
|
+
selectedIds: PropTypes.shape({}),
|
166
|
+
fetchRemediations: PropTypes.func,
|
97
167
|
remediations: PropTypes.array,
|
98
168
|
status: PropTypes.string,
|
99
169
|
error: PropTypes.string,
|
100
170
|
isAllSelected: PropTypes.bool,
|
101
171
|
query: PropTypes.string,
|
172
|
+
isDisabled: PropTypes.bool,
|
102
173
|
};
|
103
174
|
|
104
175
|
RemediationModal.defaultProps = {
|
176
|
+
iopData: null,
|
105
177
|
selectedIds: {},
|
178
|
+
fetchRemediations: noop,
|
106
179
|
remediations: [],
|
107
180
|
status: null,
|
108
181
|
error: null,
|
109
182
|
isAllSelected: false,
|
110
183
|
query: null,
|
184
|
+
isDisabled: false,
|
111
185
|
};
|
112
186
|
|
113
187
|
export default RemediationModal;
|
@@ -2,18 +2,44 @@ import React from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
3
3
|
import { Button } from '@patternfly/react-core';
|
4
4
|
import { translate as __ } from 'foremanReact/common/I18n';
|
5
|
+
import { useBulkSelect } from 'foremanReact/components/PF4/TableIndexPage/Table/TableHooks';
|
5
6
|
import { JOB_INVOCATION_PATH } from './RemediationTableConstants';
|
6
7
|
|
7
|
-
const ModalFooter = ({ toggleModal, resolutions, hostsIds }) => {
|
8
|
+
const ModalFooter = ({ toggleModal, resolutions, hostsIds, isIop }) => {
|
8
9
|
let token = document.querySelector('meta[name="csrf-token"]');
|
9
10
|
token = token?.content || '';
|
11
|
+
|
12
|
+
const [jobInProgress, setJobInProgress] = React.useState(false);
|
13
|
+
const formRef = React.useRef(null);
|
14
|
+
|
15
|
+
const { fetchBulkParams } = useBulkSelect({
|
16
|
+
initialArry: hostsIds,
|
17
|
+
idColumn: 'insights_uuid',
|
18
|
+
});
|
19
|
+
|
20
|
+
const handleSubmit = e => {
|
21
|
+
e.preventDefault();
|
22
|
+
setJobInProgress(true);
|
23
|
+
|
24
|
+
setTimeout(() => {
|
25
|
+
// eslint-disable-next-line no-unused-expressions
|
26
|
+
formRef.current?.submit?.();
|
27
|
+
}, 100);
|
28
|
+
};
|
10
29
|
return (
|
11
|
-
<form
|
30
|
+
<form
|
31
|
+
action={JOB_INVOCATION_PATH}
|
32
|
+
method="post"
|
33
|
+
ref={formRef}
|
34
|
+
onSubmit={handleSubmit}
|
35
|
+
>
|
12
36
|
<Button
|
13
37
|
type="submit"
|
14
38
|
ouiaId="button-confirm"
|
15
39
|
key="confirm"
|
16
40
|
variant="primary"
|
41
|
+
isDisabled={jobInProgress}
|
42
|
+
isLoading={jobInProgress}
|
17
43
|
>
|
18
44
|
{__('Remediate')}
|
19
45
|
</Button>
|
@@ -32,9 +58,15 @@ const ModalFooter = ({ toggleModal, resolutions, hostsIds }) => {
|
|
32
58
|
name="inputs[hit_remediation_pairs]"
|
33
59
|
value={JSON.stringify(resolutions)}
|
34
60
|
/>
|
35
|
-
{
|
36
|
-
|
37
|
-
|
61
|
+
{!isIop &&
|
62
|
+
hostsIds.map(id => (
|
63
|
+
<input type="hidden" name="host_ids[]" key={id} value={id} />
|
64
|
+
))}
|
65
|
+
{isIop && (
|
66
|
+
<>
|
67
|
+
<input type="hidden" name="search" value={fetchBulkParams()} />
|
68
|
+
</>
|
69
|
+
)}
|
38
70
|
</form>
|
39
71
|
);
|
40
72
|
};
|
@@ -43,11 +75,13 @@ ModalFooter.propTypes = {
|
|
43
75
|
toggleModal: PropTypes.func.isRequired,
|
44
76
|
resolutions: PropTypes.array,
|
45
77
|
hostsIds: PropTypes.array,
|
78
|
+
isIop: PropTypes.bool,
|
46
79
|
};
|
47
80
|
|
48
81
|
ModalFooter.defaultProps = {
|
49
82
|
resolutions: [],
|
50
83
|
hostsIds: [],
|
84
|
+
isIop: false,
|
51
85
|
};
|
52
86
|
|
53
87
|
export default ModalFooter;
|
@@ -2,12 +2,14 @@
|
|
2
2
|
import React from 'react';
|
3
3
|
import PropTypes from 'prop-types';
|
4
4
|
import { Radio } from '@patternfly/react-core';
|
5
|
+
import { getResolutionId } from './RemediationHelpers';
|
5
6
|
|
6
7
|
const Resolutions = ({
|
7
8
|
resolutions,
|
8
9
|
setResolutions,
|
9
10
|
selectedResolution,
|
10
11
|
hit_id,
|
12
|
+
isIop,
|
11
13
|
}) => {
|
12
14
|
const [checkedID, setCheckedID] = React.useState(selectedResolution);
|
13
15
|
|
@@ -27,6 +29,15 @@ const Resolutions = ({
|
|
27
29
|
stateRes.map(res => {
|
28
30
|
if (hit_id === res.hit_id) {
|
29
31
|
setCheckedID(resolution_id);
|
32
|
+
if (isIop)
|
33
|
+
return {
|
34
|
+
...res,
|
35
|
+
resolution_id: getResolutionId(
|
36
|
+
resolution_id,
|
37
|
+
res.rule_id
|
38
|
+
),
|
39
|
+
resolution_type: resolution_id,
|
40
|
+
};
|
30
41
|
return { ...res, resolution_id };
|
31
42
|
}
|
32
43
|
return res;
|
@@ -45,12 +56,14 @@ Resolutions.propTypes = {
|
|
45
56
|
resolutions: PropTypes.array,
|
46
57
|
hit_id: PropTypes.number,
|
47
58
|
selectedResolution: PropTypes.number,
|
59
|
+
isIop: PropTypes.bool,
|
48
60
|
};
|
49
61
|
|
50
62
|
Resolutions.defaultProps = {
|
51
63
|
resolutions: [],
|
52
64
|
hit_id: null,
|
53
65
|
selectedResolution: null,
|
66
|
+
isIop: false,
|
54
67
|
};
|
55
68
|
|
56
69
|
export default Resolutions;
|
@@ -67,13 +67,15 @@ export const generateRuleUrl = ruleId =>
|
|
67
67
|
foremanUrl(`/foreman_rh_cloud/recommendations/${ruleId}`);
|
68
68
|
|
69
69
|
const IopRecommendationsPage = props => (
|
70
|
-
<
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
70
|
+
<div className="advisor">
|
71
|
+
<ScalprumComponent
|
72
|
+
scope={scope}
|
73
|
+
module={module}
|
74
|
+
IopRemediationModal={RemediationModal}
|
75
|
+
generateRuleUrl={generateRuleUrl}
|
76
|
+
{...props}
|
77
|
+
/>
|
78
|
+
</div>
|
77
79
|
);
|
78
80
|
|
79
81
|
const IopRecommendationsPageWrapped = props => (
|