foreman_rh_cloud 14.7.1 → 15.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/app/services/foreman_rh_cloud/tags_auth.rb +28 -13
- data/config/routes.rb +10 -0
- data/lib/foreman_rh_cloud/plugin.rb +14 -0
- data/lib/foreman_rh_cloud/version.rb +1 -1
- data/package.json +1 -1
- data/test/unit/services/foreman_rh_cloud/tags_auth_test.rb +26 -0
- data/webpack/ComplianceHostDetailsTab/ComplianceHostDetailsTab.js +36 -0
- data/webpack/ComplianceHostDetailsTab/ComplianceHostDetailsTab.scss +3 -0
- data/webpack/ComplianceHostDetailsTab/__tests__/ComplianceHostDetailsTab.test.js +159 -0
- data/webpack/ComplianceHostDetailsTab/index.js +3 -0
- data/webpack/ForemanRhCloudFills.js +11 -0
- data/webpack/ForemanRhCloudPages.js +7 -0
- data/webpack/InsightsCompliance/ComplianceIframe.js +34 -0
- data/webpack/InsightsCompliance/ComplianceIframe.scss +12 -0
- data/webpack/InsightsCompliance/InsightsCompliancePage.js +11 -0
- data/webpack/InsightsCompliance/InsightsCompliancePage.test.js +35 -0
- data/webpack/InsightsCompliance/complianceIframeConstants.js +7 -0
- data/webpack/InsightsCompliance/complianceIframeHelpers.js +23 -0
- data/webpack/InsightsCompliance/complianceIframeHelpers.test.js +50 -0
- data/webpack/common/Hooks/PermissionsHooks.js +10 -0
- data/webpack/common/IopIframe/IopAppIframe.js +74 -0
- data/webpack/common/IopIframe/IopHostDetailsIframe.js +66 -0
- data/webpack/common/IopIframe/IopHostDetailsIframe.scss +6 -0
- data/webpack/common/IopIframe/IopHostDetailsIframeTab.js +81 -0
- data/webpack/common/IopIframe/__tests__/IopAppIframe.test.js +92 -0
- data/webpack/common/IopIframe/__tests__/IopHostDetailsIframeTab.test.js +93 -0
- data/webpack/common/IopIframe/__tests__/iopIframeHelpers.test.js +63 -0
- data/webpack/common/IopIframe/__tests__/postIopChromeToIframe.test.js +60 -0
- data/webpack/common/IopIframe/__tests__/useIopIframeHeight.test.js +37 -0
- data/webpack/common/IopIframe/iopIframeConstants.js +5 -0
- data/webpack/common/IopIframe/iopIframeHelpers.js +49 -0
- data/webpack/common/IopIframe/postIopChromeToIframe.js +35 -0
- data/webpack/common/IopIframe/useIopIframeChrome.js +54 -0
- data/webpack/common/IopIframe/useIopIframeHeight.js +35 -0
- data/webpack/common/IopIframe/useIopIframeNavigate.js +40 -0
- metadata +27 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 637a2b5c3eb9d907b2c20a3921b728efe617dd2dccb6cd1f99135784f2721bed
|
|
4
|
+
data.tar.gz: 2b254832b522c4c60c6f11ff8e727a890c3b0632d1df3e0abc024657ad8a0343
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9e3c2a143ad9a3e7ff76ddf83440655c0fd0c558f423acc0dac4ddbb88e9245850d39f270e7f455849d58fd2e37904804a0e1611641f7f8438ca2a79e15e1e36
|
|
7
|
+
data.tar.gz: 22886acec69b9b059802e3ba750bb43bda9132990c4c104e07a49ed6852f37b54db2302ead5acfeb973e136ba435256437ef786e396253420e89322946d929e7
|
|
@@ -5,6 +5,7 @@ module ForemanRhCloud
|
|
|
5
5
|
TAG_NAMESPACE = 'sat_iam'.freeze
|
|
6
6
|
TAG_SHORT_NAME = 'scope'.freeze
|
|
7
7
|
TAG_NAME = "#{TAG_NAMESPACE}/#{TAG_SHORT_NAME}".freeze
|
|
8
|
+
SYNC_CACHE_TTL = 10.seconds
|
|
8
9
|
|
|
9
10
|
def self.auth_tag_for(user, org, loc)
|
|
10
11
|
new(user, org, loc, nil).auth_tag
|
|
@@ -20,20 +21,28 @@ module ForemanRhCloud
|
|
|
20
21
|
end
|
|
21
22
|
|
|
22
23
|
def update_tag
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
cached = Rails.cache.read(sync_identity)
|
|
25
|
+
return cached if cached
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
27
|
+
Foreman::AdvisoryLockManager.with_session_lock(sync_identity) do
|
|
28
|
+
Rails.cache.fetch(sync_identity, expires_in: SYNC_CACHE_TTL) do
|
|
29
|
+
loc_name = location_name_for_tag
|
|
30
|
+
logger.debug("Updating tags for user: #{@user}, org: #{@org.name}, loc: #{loc_name}")
|
|
31
|
+
|
|
32
|
+
payload = tags_query_payload
|
|
33
|
+
params = {
|
|
34
|
+
organization: @org,
|
|
35
|
+
method: :post,
|
|
36
|
+
url: "#{InsightsCloud.gateway_url}/tags",
|
|
37
|
+
headers: {
|
|
38
|
+
content_type: :json,
|
|
39
|
+
},
|
|
40
|
+
payload: payload.to_json,
|
|
41
|
+
}
|
|
42
|
+
execute_cloud_request(params) unless payload[:host_id_list].empty?
|
|
43
|
+
true
|
|
44
|
+
end
|
|
45
|
+
end
|
|
37
46
|
end
|
|
38
47
|
|
|
39
48
|
def allowed_hosts
|
|
@@ -63,5 +72,11 @@ module ForemanRhCloud
|
|
|
63
72
|
def location_name_for_tag
|
|
64
73
|
@loc ? @loc.name : '*'
|
|
65
74
|
end
|
|
75
|
+
|
|
76
|
+
# Single identity string reused as BOTH the advisory-lock name and the
|
|
77
|
+
# Rails.cache key, so the lock and the cached entry always correspond 1:1.
|
|
78
|
+
def sync_identity
|
|
79
|
+
"rh_cloud_tags_auth_sync_#{tag_value}"
|
|
80
|
+
end
|
|
66
81
|
end
|
|
67
82
|
end
|
data/config/routes.rb
CHANGED
|
@@ -36,6 +36,16 @@ Rails.application.routes.draw do
|
|
|
36
36
|
get 'insights_cloud', to: '/react#index' # Uses foreman's react controller
|
|
37
37
|
get 'insights_vulnerability', to: '/react#index'
|
|
38
38
|
get 'insights_vulnerability/:cve_id', to: '/react#index'
|
|
39
|
+
scope 'insights_compliance' do
|
|
40
|
+
get 'reports', to: '/react#index'
|
|
41
|
+
get 'reports/:report_id', to: '/react#index'
|
|
42
|
+
get 'reports/:report_id/delete', to: '/react#index'
|
|
43
|
+
get 'scappolicies', to: '/react#index'
|
|
44
|
+
get 'scappolicies/new', to: '/react#index'
|
|
45
|
+
get 'scappolicies/:policy_id', to: '/react#index'
|
|
46
|
+
get 'scappolicies/:policy_id/edit', to: '/react#index'
|
|
47
|
+
get 'scappolicies/:policy_id/delete', to: '/react#index'
|
|
48
|
+
end
|
|
39
49
|
end
|
|
40
50
|
|
|
41
51
|
scope :module => :'insights_cloud/api', :path => :redhat_access do
|
|
@@ -145,6 +145,20 @@ module ForemanRhCloud
|
|
|
145
145
|
url_hash: { controller: :react, action: :index },
|
|
146
146
|
parent: :insights_menu,
|
|
147
147
|
if: -> { ForemanRhCloud.with_iop_smart_proxy? }
|
|
148
|
+
menu :top_menu,
|
|
149
|
+
:insights_compliance_reports,
|
|
150
|
+
caption: N_('Compliance Reports'),
|
|
151
|
+
url: '/foreman_rh_cloud/insights_compliance/reports',
|
|
152
|
+
url_hash: { controller: :react, action: :index },
|
|
153
|
+
parent: :insights_menu,
|
|
154
|
+
if: -> { ForemanRhCloud.with_iop_smart_proxy? }
|
|
155
|
+
menu :top_menu,
|
|
156
|
+
:insights_compliance_scappolicies,
|
|
157
|
+
caption: N_('SCAP Policies'),
|
|
158
|
+
url: '/foreman_rh_cloud/insights_compliance/scappolicies',
|
|
159
|
+
url_hash: { controller: :react, action: :index },
|
|
160
|
+
parent: :insights_menu,
|
|
161
|
+
if: -> { ForemanRhCloud.with_iop_smart_proxy? }
|
|
148
162
|
end
|
|
149
163
|
|
|
150
164
|
# In IoP case we want the page to be in the admin menu
|
data/package.json
CHANGED
|
@@ -3,6 +3,7 @@ require 'json'
|
|
|
3
3
|
|
|
4
4
|
class TagsAuthTest < ActiveSupport::TestCase
|
|
5
5
|
setup do
|
|
6
|
+
Rails.cache.clear
|
|
6
7
|
@user = FactoryBot.build(:user)
|
|
7
8
|
@logger = Logger.new(IO::NULL)
|
|
8
9
|
@org = FactoryBot.build(:organization)
|
|
@@ -41,6 +42,31 @@ class TagsAuthTest < ActiveSupport::TestCase
|
|
|
41
42
|
@auth.update_tag
|
|
42
43
|
end
|
|
43
44
|
|
|
45
|
+
test 'Does not repeat cloud request for the same user/org/location within the sync TTL' do
|
|
46
|
+
@auth.stubs(:allowed_hosts).returns(['test_uuid1'])
|
|
47
|
+
@auth.expects(:execute_cloud_request).once
|
|
48
|
+
|
|
49
|
+
@auth.update_tag
|
|
50
|
+
@auth.update_tag
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
test 'Serializes sync through an advisory lock on a cold cache miss' do
|
|
54
|
+
@auth.stubs(:allowed_hosts).returns(['test_uuid1'])
|
|
55
|
+
@auth.stubs(:execute_cloud_request)
|
|
56
|
+
Foreman::AdvisoryLockManager.expects(:with_session_lock).yields
|
|
57
|
+
|
|
58
|
+
@auth.update_tag
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
test 'Skips the advisory lock entirely on a warm cache hit' do
|
|
62
|
+
@auth.stubs(:allowed_hosts).returns(['test_uuid1'])
|
|
63
|
+
@auth.stubs(:execute_cloud_request)
|
|
64
|
+
@auth.update_tag
|
|
65
|
+
|
|
66
|
+
Foreman::AdvisoryLockManager.expects(:with_session_lock).never
|
|
67
|
+
@auth.update_tag
|
|
68
|
+
end
|
|
69
|
+
|
|
44
70
|
test 'Generates tags with wildcard location when location is nil' do
|
|
45
71
|
auth_with_nil_loc = ::ForemanRhCloud::TagsAuth.new(@user, @org, nil, @logger)
|
|
46
72
|
uuid1 = 'test_uuid1'
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { translate as __ } from 'foremanReact/common/I18n';
|
|
4
|
+
import IopHostDetailsIframeTab from '../common/IopIframe/IopHostDetailsIframeTab';
|
|
5
|
+
import { getComplianceIframeSrc } from '../InsightsCompliance/complianceIframeHelpers';
|
|
6
|
+
import { IOP_COMPLIANCE_READY } from '../InsightsCompliance/complianceIframeConstants';
|
|
7
|
+
import './ComplianceHostDetailsTab.scss';
|
|
8
|
+
|
|
9
|
+
const ComplianceHostDetailsTabWrapper = ({ response = {} }) => (
|
|
10
|
+
<IopHostDetailsIframeTab
|
|
11
|
+
response={response}
|
|
12
|
+
iframeSrc={getComplianceIframeSrc()}
|
|
13
|
+
buildAppRoute={inventoryId => `systems/${inventoryId}`}
|
|
14
|
+
title={__('Compliance')}
|
|
15
|
+
readyMessageType={IOP_COMPLIANCE_READY}
|
|
16
|
+
className="rh-cloud-insights-compliance-host-details-component compliance"
|
|
17
|
+
testId="compliance-host-details-iframe"
|
|
18
|
+
/>
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
ComplianceHostDetailsTabWrapper.propTypes = {
|
|
22
|
+
response: PropTypes.shape({
|
|
23
|
+
id: PropTypes.number,
|
|
24
|
+
operatingsystem_name: PropTypes.string,
|
|
25
|
+
insights_attributes: PropTypes.object,
|
|
26
|
+
subscription_facet_attributes: PropTypes.shape({
|
|
27
|
+
uuid: PropTypes.string,
|
|
28
|
+
}),
|
|
29
|
+
}),
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
ComplianceHostDetailsTabWrapper.defaultProps = {
|
|
33
|
+
response: {},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export default ComplianceHostDetailsTabWrapper;
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { fireEvent, render } from '@testing-library/react';
|
|
3
|
+
import { MemoryRouter } from 'react-router-dom';
|
|
4
|
+
import ComplianceHostDetailsTabWrapper from '../ComplianceHostDetailsTab';
|
|
5
|
+
import { OVERVIEW_TAB_PATH } from '../../ForemanRhCloudHelpers';
|
|
6
|
+
|
|
7
|
+
const mockHistoryReplace = jest.fn();
|
|
8
|
+
|
|
9
|
+
jest.mock('react-router-dom', () => ({
|
|
10
|
+
...jest.requireActual('react-router-dom'),
|
|
11
|
+
useHistory: () => ({
|
|
12
|
+
replace: mockHistoryReplace,
|
|
13
|
+
}),
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
jest.mock('foremanReact/Root/Context/ForemanContext', () => ({
|
|
17
|
+
useForemanContext: () => ({
|
|
18
|
+
metadata: {
|
|
19
|
+
permissions: new Set(['view_hosts']),
|
|
20
|
+
},
|
|
21
|
+
}),
|
|
22
|
+
useForemanPermissions: () => new Set(['view_hosts']),
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
jest.mock('foremanReact/common/I18n', () => ({
|
|
26
|
+
translate: str => str,
|
|
27
|
+
sprintf: str => str,
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
jest.mock('../../InsightsCompliance/complianceIframeHelpers', () => ({
|
|
31
|
+
getComplianceIframeSrc: () =>
|
|
32
|
+
'https://example.test/assets/apps/compliance/index.html',
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
const mockPostChrome = jest.fn();
|
|
36
|
+
jest.mock('../../common/IopIframe/postIopChromeToIframe', () => ({
|
|
37
|
+
postIopChromeToIframe: (...args) => mockPostChrome(...args),
|
|
38
|
+
}));
|
|
39
|
+
|
|
40
|
+
const defaultResponse = {
|
|
41
|
+
id: 1,
|
|
42
|
+
operatingsystem_name: 'Red Hat Enterprise Linux 8',
|
|
43
|
+
insights_attributes: { uuid: 'insights-1' },
|
|
44
|
+
subscription_facet_attributes: { uuid: '1-2-3' },
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
describe('ComplianceHostDetailsTabWrapper', () => {
|
|
48
|
+
beforeEach(() => {
|
|
49
|
+
jest.clearAllMocks();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('renders compliance iframe for a valid host without redirecting', () => {
|
|
53
|
+
const { getByTestId, container } = render(
|
|
54
|
+
<MemoryRouter>
|
|
55
|
+
<ComplianceHostDetailsTabWrapper response={defaultResponse} />
|
|
56
|
+
</MemoryRouter>
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
expect(
|
|
60
|
+
container.querySelector(
|
|
61
|
+
'.rh-cloud-insights-compliance-host-details-component'
|
|
62
|
+
)
|
|
63
|
+
).toBeTruthy();
|
|
64
|
+
|
|
65
|
+
const iframe = getByTestId('compliance-host-details-iframe');
|
|
66
|
+
expect(iframe.getAttribute('src')).toBe(
|
|
67
|
+
'https://example.test/assets/apps/compliance/index.html'
|
|
68
|
+
);
|
|
69
|
+
expect(mockHistoryReplace).not.toHaveBeenCalled();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('posts systems/<uuid> appRoute for the host-tab iframe', () => {
|
|
73
|
+
const { getByTestId } = render(
|
|
74
|
+
<MemoryRouter>
|
|
75
|
+
<ComplianceHostDetailsTabWrapper response={defaultResponse} />
|
|
76
|
+
</MemoryRouter>
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
fireEvent.load(getByTestId('compliance-host-details-iframe'));
|
|
80
|
+
|
|
81
|
+
expect(mockPostChrome).toHaveBeenCalledWith(
|
|
82
|
+
expect.anything(),
|
|
83
|
+
expect.objectContaining({
|
|
84
|
+
appRoute: 'systems/1-2-3',
|
|
85
|
+
embedded: 'host-tab',
|
|
86
|
+
})
|
|
87
|
+
);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('posts updated appRoute when inventoryId changes', () => {
|
|
91
|
+
const responseHostA = {
|
|
92
|
+
...defaultResponse,
|
|
93
|
+
subscription_facet_attributes: { uuid: 'uuid-host-A' },
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const { rerender, getByTestId } = render(
|
|
97
|
+
<MemoryRouter>
|
|
98
|
+
<ComplianceHostDetailsTabWrapper response={responseHostA} />
|
|
99
|
+
</MemoryRouter>
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
expect(getByTestId('compliance-host-details-iframe')).toBeTruthy();
|
|
103
|
+
|
|
104
|
+
const responseHostB = {
|
|
105
|
+
...defaultResponse,
|
|
106
|
+
subscription_facet_attributes: { uuid: 'uuid-host-B' },
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
rerender(
|
|
110
|
+
<MemoryRouter>
|
|
111
|
+
<ComplianceHostDetailsTabWrapper response={responseHostB} />
|
|
112
|
+
</MemoryRouter>
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
fireEvent.load(getByTestId('compliance-host-details-iframe'));
|
|
116
|
+
|
|
117
|
+
expect(mockPostChrome).toHaveBeenCalledWith(
|
|
118
|
+
expect.anything(),
|
|
119
|
+
expect.objectContaining({
|
|
120
|
+
appRoute: 'systems/uuid-host-B',
|
|
121
|
+
embedded: 'host-tab',
|
|
122
|
+
})
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('redirects to Overview when tab should be hidden', () => {
|
|
127
|
+
const nonRhelResponse = {
|
|
128
|
+
id: 2,
|
|
129
|
+
operatingsystem_name: 'Ubuntu 20.04',
|
|
130
|
+
insights_attributes: { uuid: 'insights-1' },
|
|
131
|
+
subscription_facet_attributes: { uuid: '1-2-3' },
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const { container } = render(
|
|
135
|
+
<MemoryRouter>
|
|
136
|
+
<ComplianceHostDetailsTabWrapper response={nonRhelResponse} />
|
|
137
|
+
</MemoryRouter>
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
expect(mockHistoryReplace).toHaveBeenCalledWith(OVERVIEW_TAB_PATH);
|
|
141
|
+
expect(
|
|
142
|
+
container.querySelector(
|
|
143
|
+
'.rh-cloud-insights-compliance-host-details-component'
|
|
144
|
+
)
|
|
145
|
+
).toBeNull();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('does not redirect when host data is not yet loaded', () => {
|
|
149
|
+
const emptyResponse = { subscription_facet_attributes: { uuid: '1-2-3' } };
|
|
150
|
+
|
|
151
|
+
render(
|
|
152
|
+
<MemoryRouter>
|
|
153
|
+
<ComplianceHostDetailsTabWrapper response={emptyResponse} />
|
|
154
|
+
</MemoryRouter>
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
expect(mockHistoryReplace).not.toHaveBeenCalled();
|
|
158
|
+
});
|
|
159
|
+
});
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
hasNoInsightsFacet,
|
|
11
11
|
} from './ForemanRhCloudHelpers';
|
|
12
12
|
import CVEsHostDetailsTabWrapper from './CVEsHostDetailsTab/CVEsHostDetailsTab';
|
|
13
|
+
import ComplianceHostDetailsTabWrapper from './ComplianceHostDetailsTab/ComplianceHostDetailsTab';
|
|
13
14
|
import InsightsVulnerabilityActionsBar from './InsightsVulnerabilityActionsBar';
|
|
14
15
|
|
|
15
16
|
const fills = [
|
|
@@ -51,6 +52,16 @@ const fills = [
|
|
|
51
52
|
title: __('Vulnerabilities'),
|
|
52
53
|
},
|
|
53
54
|
},
|
|
55
|
+
{
|
|
56
|
+
slot: 'host-details-page-tabs',
|
|
57
|
+
name: 'Compliance',
|
|
58
|
+
component: props => <ComplianceHostDetailsTabWrapper {...props} />,
|
|
59
|
+
weight: 350,
|
|
60
|
+
metadata: {
|
|
61
|
+
hideTab: props => isNotRhelHost(props) || hasNoInsightsFacet(props),
|
|
62
|
+
title: __('Compliance'),
|
|
63
|
+
},
|
|
64
|
+
},
|
|
54
65
|
];
|
|
55
66
|
|
|
56
67
|
export const registerFills = () => {
|
|
@@ -8,6 +8,7 @@ import IopRecommendationDetails from './IopRecommendationDetails/IopRecommendati
|
|
|
8
8
|
import IopPathwayDetails from './IopPathwayDetails/IopPathwayDetails';
|
|
9
9
|
import InsightsHostDetailsTab from './InsightsHostDetailsTab';
|
|
10
10
|
import CveDetailsPage from './CveDetailsPage';
|
|
11
|
+
import InsightsCompliancePage from './InsightsCompliance/InsightsCompliancePage';
|
|
11
12
|
import './common/styles.scss';
|
|
12
13
|
|
|
13
14
|
const pages = [
|
|
@@ -20,6 +21,7 @@ const pages = [
|
|
|
20
21
|
type: InsightsVulnerabilityListPage,
|
|
21
22
|
},
|
|
22
23
|
{ name: 'CveDetailsPage', type: CveDetailsPage },
|
|
24
|
+
{ name: 'InsightsCompliancePage', type: InsightsCompliancePage },
|
|
23
25
|
];
|
|
24
26
|
|
|
25
27
|
export const registerPages = () => {
|
|
@@ -57,6 +59,11 @@ export const routes = [
|
|
|
57
59
|
exact: true,
|
|
58
60
|
render: props => <CveDetailsPage {...props} />,
|
|
59
61
|
},
|
|
62
|
+
{
|
|
63
|
+
path: '/foreman_rh_cloud/insights_compliance',
|
|
64
|
+
exact: false,
|
|
65
|
+
render: props => <InsightsCompliancePage {...props} />,
|
|
66
|
+
},
|
|
60
67
|
];
|
|
61
68
|
|
|
62
69
|
export const registerRoutes = () => {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import IopAppIframe from '../common/IopIframe/IopAppIframe';
|
|
4
|
+
import {
|
|
5
|
+
getComplianceAppRoute,
|
|
6
|
+
getComplianceForemanPath,
|
|
7
|
+
getComplianceIframeSrc,
|
|
8
|
+
} from './complianceIframeHelpers';
|
|
9
|
+
import {
|
|
10
|
+
IOP_COMPLIANCE_NAVIGATE,
|
|
11
|
+
IOP_COMPLIANCE_READY,
|
|
12
|
+
} from './complianceIframeConstants';
|
|
13
|
+
|
|
14
|
+
import './ComplianceIframe.scss';
|
|
15
|
+
|
|
16
|
+
const ComplianceIframe = ({ appName }) => (
|
|
17
|
+
<IopAppIframe
|
|
18
|
+
appName={appName}
|
|
19
|
+
iframeSrc={getComplianceIframeSrc()}
|
|
20
|
+
getAppRoute={getComplianceAppRoute}
|
|
21
|
+
getForemanPath={getComplianceForemanPath}
|
|
22
|
+
defaultAppRoute="reports"
|
|
23
|
+
readyMessageType={IOP_COMPLIANCE_READY}
|
|
24
|
+
navigateMessageType={IOP_COMPLIANCE_NAVIGATE}
|
|
25
|
+
className="rh-cloud-insights-compliance-iframe"
|
|
26
|
+
testId="compliance-iframe"
|
|
27
|
+
/>
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
ComplianceIframe.propTypes = {
|
|
31
|
+
appName: PropTypes.string.isRequired,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export default ComplianceIframe;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
.rh-cloud-insights-compliance-page {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
min-height: calc(100vh - var(--pf-v5-global--spacer--2xl, 3rem));
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.rh-cloud-insights-compliance-iframe {
|
|
8
|
+
border: 0;
|
|
9
|
+
flex: 1 1 auto;
|
|
10
|
+
min-height: 70vh;
|
|
11
|
+
width: 100%;
|
|
12
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import ComplianceIframe from './ComplianceIframe';
|
|
4
|
+
|
|
5
|
+
const InsightsCompliancePage = () => (
|
|
6
|
+
<div className="rh-cloud-insights-compliance-page compliance">
|
|
7
|
+
<ComplianceIframe appName="Compliance" />
|
|
8
|
+
</div>
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
export default InsightsCompliancePage;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import '@testing-library/jest-dom';
|
|
4
|
+
import { MemoryRouter } from 'react-router-dom';
|
|
5
|
+
|
|
6
|
+
import InsightsCompliancePage from './InsightsCompliancePage';
|
|
7
|
+
|
|
8
|
+
jest.mock('foremanReact/Root/Context/ForemanContext', () => ({
|
|
9
|
+
useForemanContext: () => ({
|
|
10
|
+
metadata: {
|
|
11
|
+
permissions: new Set(['view_compliance']),
|
|
12
|
+
},
|
|
13
|
+
}),
|
|
14
|
+
useForemanPermissions: () => new Set(['view_compliance']),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
describe('InsightsCompliancePage', () => {
|
|
18
|
+
it('renders the compliance iframe container', () => {
|
|
19
|
+
render(
|
|
20
|
+
<MemoryRouter
|
|
21
|
+
initialEntries={['/foreman_rh_cloud/insights_compliance/reports']}
|
|
22
|
+
>
|
|
23
|
+
<InsightsCompliancePage />
|
|
24
|
+
</MemoryRouter>
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
expect(
|
|
28
|
+
document.querySelector('.rh-cloud-insights-compliance-page')
|
|
29
|
+
).toBeTruthy();
|
|
30
|
+
expect(screen.getByTestId('compliance-iframe')).toHaveAttribute(
|
|
31
|
+
'src',
|
|
32
|
+
`${window.location.origin}/assets/apps/compliance/index.html`
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export const COMPLIANCE_STATIC_APP_PATH = '/assets/apps/compliance/index.html';
|
|
2
|
+
|
|
3
|
+
export const IOP_COMPLIANCE_READY = 'IOP_COMPLIANCE_READY';
|
|
4
|
+
|
|
5
|
+
export const IOP_COMPLIANCE_NAVIGATE = 'IOP_COMPLIANCE_NAVIGATE';
|
|
6
|
+
|
|
7
|
+
export const COMPLIANCE_ROUTE_PREFIX = '/foreman_rh_cloud/insights_compliance';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import {
|
|
2
|
+
COMPLIANCE_ROUTE_PREFIX,
|
|
3
|
+
COMPLIANCE_STATIC_APP_PATH,
|
|
4
|
+
} from './complianceIframeConstants';
|
|
5
|
+
import {
|
|
6
|
+
getIopAppRoute,
|
|
7
|
+
getIopForemanPath,
|
|
8
|
+
getIopIframeSrc,
|
|
9
|
+
} from '../common/IopIframe/iopIframeHelpers';
|
|
10
|
+
|
|
11
|
+
const COMPLIANCE_IOP_PATH = {
|
|
12
|
+
routePrefix: COMPLIANCE_ROUTE_PREFIX,
|
|
13
|
+
fallbackRoute: 'reports',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const getComplianceForemanPath = (appRoute = 'reports') =>
|
|
17
|
+
getIopForemanPath(appRoute, COMPLIANCE_IOP_PATH);
|
|
18
|
+
|
|
19
|
+
export const getComplianceAppRoute = (pathname = '') =>
|
|
20
|
+
getIopAppRoute(pathname, COMPLIANCE_IOP_PATH);
|
|
21
|
+
|
|
22
|
+
export const getComplianceIframeSrc = () =>
|
|
23
|
+
getIopIframeSrc(COMPLIANCE_STATIC_APP_PATH);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getComplianceAppRoute,
|
|
3
|
+
getComplianceForemanPath,
|
|
4
|
+
getComplianceIframeSrc,
|
|
5
|
+
} from './complianceIframeHelpers';
|
|
6
|
+
|
|
7
|
+
describe('complianceIframeHelpers', () => {
|
|
8
|
+
describe('getComplianceAppRoute', () => {
|
|
9
|
+
it('maps foreman compliance paths to app routes', () => {
|
|
10
|
+
expect(
|
|
11
|
+
getComplianceAppRoute('/foreman_rh_cloud/insights_compliance/reports')
|
|
12
|
+
).toBe('reports');
|
|
13
|
+
expect(
|
|
14
|
+
getComplianceAppRoute(
|
|
15
|
+
'/foreman_rh_cloud/insights_compliance/reports/report-1'
|
|
16
|
+
)
|
|
17
|
+
).toBe('reports/report-1');
|
|
18
|
+
expect(
|
|
19
|
+
getComplianceAppRoute(
|
|
20
|
+
'/foreman_rh_cloud/insights_compliance/scappolicies/new'
|
|
21
|
+
)
|
|
22
|
+
).toBe('scappolicies/new');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('defaults to reports for unknown paths', () => {
|
|
26
|
+
expect(getComplianceAppRoute('/foreman_rh_cloud/insights_cloud')).toBe(
|
|
27
|
+
'reports'
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('getComplianceForemanPath', () => {
|
|
33
|
+
it('maps app routes to foreman compliance paths', () => {
|
|
34
|
+
expect(getComplianceForemanPath('reports')).toBe(
|
|
35
|
+
'/foreman_rh_cloud/insights_compliance/reports'
|
|
36
|
+
);
|
|
37
|
+
expect(getComplianceForemanPath('scappolicies/new')).toBe(
|
|
38
|
+
'/foreman_rh_cloud/insights_compliance/scappolicies/new'
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe('getComplianceIframeSrc', () => {
|
|
44
|
+
it('returns the static compliance app entry', () => {
|
|
45
|
+
expect(getComplianceIframeSrc()).toBe(
|
|
46
|
+
`${window.location.origin}/assets/apps/compliance/index.html`
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -25,6 +25,16 @@ const PERMISSION_MAPPING = {
|
|
|
25
25
|
],
|
|
26
26
|
view_advisor: ['advisor:recommendation-results:read', 'advisor:exports:read'],
|
|
27
27
|
edit_advisor: ['advisor:disable-recommendations:write'],
|
|
28
|
+
view_compliance: [
|
|
29
|
+
'inventory:hosts:read',
|
|
30
|
+
'compliance:report:read',
|
|
31
|
+
'compliance:policy:read',
|
|
32
|
+
],
|
|
33
|
+
edit_compliance: [
|
|
34
|
+
'compliance:policy:write',
|
|
35
|
+
'compliance:policy:create',
|
|
36
|
+
'compliance:policy:delete',
|
|
37
|
+
],
|
|
28
38
|
};
|
|
29
39
|
|
|
30
40
|
/**
|