foreman_rh_cloud 14.7.0 → 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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/foreman_rh_cloud/locale/fr/foreman_rh_cloud.js +32 -32
  3. data/app/assets/javascripts/foreman_rh_cloud/locale/ja/foreman_rh_cloud.js +43 -43
  4. data/app/assets/javascripts/foreman_rh_cloud/locale/ka/foreman_rh_cloud.js +3 -3
  5. data/app/assets/javascripts/foreman_rh_cloud/locale/ko/foreman_rh_cloud.js +27 -27
  6. data/app/assets/javascripts/foreman_rh_cloud/locale/zh_CN/foreman_rh_cloud.js +26 -26
  7. data/app/services/foreman_rh_cloud/tags_auth.rb +28 -13
  8. data/config/routes.rb +10 -0
  9. data/lib/foreman_rh_cloud/plugin.rb +14 -0
  10. data/lib/foreman_rh_cloud/version.rb +1 -1
  11. data/locale/foreman_rh_cloud.pot +2 -2
  12. data/locale/fr/LC_MESSAGES/foreman_rh_cloud.mo +0 -0
  13. data/locale/fr/foreman_rh_cloud.po +34 -34
  14. data/locale/ja/LC_MESSAGES/foreman_rh_cloud.mo +0 -0
  15. data/locale/ja/foreman_rh_cloud.po +43 -43
  16. data/locale/ka/LC_MESSAGES/foreman_rh_cloud.mo +0 -0
  17. data/locale/ka/foreman_rh_cloud.po +3 -3
  18. data/locale/ko/LC_MESSAGES/foreman_rh_cloud.mo +0 -0
  19. data/locale/ko/foreman_rh_cloud.po +27 -27
  20. data/locale/zh_CN/LC_MESSAGES/foreman_rh_cloud.mo +0 -0
  21. data/locale/zh_CN/foreman_rh_cloud.po +27 -27
  22. data/package.json +1 -1
  23. data/test/controllers/insights_cloud/api/machine_telemetries_controller_test.rb +0 -1
  24. data/test/test_plugin_helper.rb +0 -8
  25. data/test/unit/services/foreman_rh_cloud/cloud_request_forwarder_test.rb +0 -1
  26. data/test/unit/services/foreman_rh_cloud/tags_auth_test.rb +26 -0
  27. data/test/unit/tags_generator_test.rb +0 -1
  28. data/webpack/ComplianceHostDetailsTab/ComplianceHostDetailsTab.js +36 -0
  29. data/webpack/ComplianceHostDetailsTab/ComplianceHostDetailsTab.scss +3 -0
  30. data/webpack/ComplianceHostDetailsTab/__tests__/ComplianceHostDetailsTab.test.js +159 -0
  31. data/webpack/ComplianceHostDetailsTab/index.js +3 -0
  32. data/webpack/ForemanRhCloudFills.js +11 -0
  33. data/webpack/ForemanRhCloudPages.js +7 -0
  34. data/webpack/InsightsCompliance/ComplianceIframe.js +34 -0
  35. data/webpack/InsightsCompliance/ComplianceIframe.scss +12 -0
  36. data/webpack/InsightsCompliance/InsightsCompliancePage.js +11 -0
  37. data/webpack/InsightsCompliance/InsightsCompliancePage.test.js +35 -0
  38. data/webpack/InsightsCompliance/complianceIframeConstants.js +7 -0
  39. data/webpack/InsightsCompliance/complianceIframeHelpers.js +23 -0
  40. data/webpack/InsightsCompliance/complianceIframeHelpers.test.js +50 -0
  41. data/webpack/common/Hooks/PermissionsHooks.js +10 -0
  42. data/webpack/common/IopIframe/IopAppIframe.js +74 -0
  43. data/webpack/common/IopIframe/IopHostDetailsIframe.js +66 -0
  44. data/webpack/common/IopIframe/IopHostDetailsIframe.scss +6 -0
  45. data/webpack/common/IopIframe/IopHostDetailsIframeTab.js +81 -0
  46. data/webpack/common/IopIframe/__tests__/IopAppIframe.test.js +92 -0
  47. data/webpack/common/IopIframe/__tests__/IopHostDetailsIframeTab.test.js +93 -0
  48. data/webpack/common/IopIframe/__tests__/iopIframeHelpers.test.js +63 -0
  49. data/webpack/common/IopIframe/__tests__/postIopChromeToIframe.test.js +60 -0
  50. data/webpack/common/IopIframe/__tests__/useIopIframeHeight.test.js +37 -0
  51. data/webpack/common/IopIframe/iopIframeConstants.js +5 -0
  52. data/webpack/common/IopIframe/iopIframeHelpers.js +49 -0
  53. data/webpack/common/IopIframe/postIopChromeToIframe.js +35 -0
  54. data/webpack/common/IopIframe/useIopIframeChrome.js +54 -0
  55. data/webpack/common/IopIframe/useIopIframeHeight.js +35 -0
  56. data/webpack/common/IopIframe/useIopIframeNavigate.js +40 -0
  57. metadata +27 -1
@@ -0,0 +1,92 @@
1
+ import React from 'react';
2
+ import { fireEvent, render } from '@testing-library/react';
3
+ import { MemoryRouter, Route } from 'react-router-dom';
4
+ import IopAppIframe from '../IopAppIframe';
5
+
6
+ const mockHistoryPush = jest.fn();
7
+
8
+ jest.mock('react-router-dom', () => ({
9
+ ...jest.requireActual('react-router-dom'),
10
+ useHistory: () => ({
11
+ push: mockHistoryPush,
12
+ }),
13
+ }));
14
+
15
+ jest.mock('foremanReact/Root/Context/ForemanContext', () => ({
16
+ useForemanContext: () => ({
17
+ metadata: {
18
+ permissions: new Set(['view_hosts']),
19
+ },
20
+ }),
21
+ useForemanPermissions: () => new Set(['view_hosts']),
22
+ }));
23
+
24
+ const mockPostChrome = jest.fn();
25
+ jest.mock('../postIopChromeToIframe', () => ({
26
+ postIopChromeToIframe: (...args) => mockPostChrome(...args),
27
+ }));
28
+
29
+ const routePrefix = '/foreman_rh_cloud/insights_example';
30
+
31
+ const defaultProps = {
32
+ appName: 'Example',
33
+ iframeSrc: 'https://example.test/assets/apps/example/index.html',
34
+ getAppRoute: pathname =>
35
+ pathname.startsWith(routePrefix)
36
+ ? pathname.slice(routePrefix.length).replace(/^\//, '') || 'dashboard'
37
+ : 'dashboard',
38
+ getForemanPath: appRoute => `${routePrefix}/${appRoute}`,
39
+ defaultAppRoute: 'dashboard',
40
+ readyMessageType: 'IOP_EXAMPLE_READY',
41
+ navigateMessageType: 'IOP_EXAMPLE_NAVIGATE',
42
+ className: 'example-iframe',
43
+ testId: 'example-iframe',
44
+ };
45
+
46
+ describe('IopAppIframe', () => {
47
+ beforeEach(() => {
48
+ jest.clearAllMocks();
49
+ });
50
+
51
+ it('posts chrome context derived from the Foreman pathname', () => {
52
+ const { getByTestId } = render(
53
+ <MemoryRouter initialEntries={[`${routePrefix}/cves`]}>
54
+ <Route path="*">
55
+ <IopAppIframe {...defaultProps} />
56
+ </Route>
57
+ </MemoryRouter>
58
+ );
59
+
60
+ fireEvent.load(getByTestId('example-iframe'));
61
+
62
+ expect(mockPostChrome).toHaveBeenCalledWith(
63
+ expect.anything(),
64
+ expect.objectContaining({
65
+ appRoute: 'cves',
66
+ pathname: `${routePrefix}/cves`,
67
+ })
68
+ );
69
+ });
70
+
71
+ it('pushes Foreman path when the iframe sends NAVIGATE', () => {
72
+ render(
73
+ <MemoryRouter initialEntries={[`${routePrefix}/dashboard`]}>
74
+ <Route path="*">
75
+ <IopAppIframe {...defaultProps} />
76
+ </Route>
77
+ </MemoryRouter>
78
+ );
79
+
80
+ window.dispatchEvent(
81
+ new MessageEvent('message', {
82
+ origin: window.location.origin,
83
+ data: {
84
+ type: 'IOP_EXAMPLE_NAVIGATE',
85
+ payload: { appRoute: 'cves/cve-1' },
86
+ },
87
+ })
88
+ );
89
+
90
+ expect(mockHistoryPush).toHaveBeenCalledWith(`${routePrefix}/cves/cve-1`);
91
+ });
92
+ });
@@ -0,0 +1,93 @@
1
+ import React from 'react';
2
+ import { fireEvent, render } from '@testing-library/react';
3
+ import { MemoryRouter } from 'react-router-dom';
4
+ import IopHostDetailsIframeTab from '../IopHostDetailsIframeTab';
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
+ const mockPostChrome = jest.fn();
26
+ jest.mock('../postIopChromeToIframe', () => ({
27
+ postIopChromeToIframe: (...args) => mockPostChrome(...args),
28
+ }));
29
+
30
+ const defaultResponse = {
31
+ id: 1,
32
+ operatingsystem_name: 'Red Hat Enterprise Linux 8',
33
+ insights_attributes: { uuid: 'insights-1' },
34
+ subscription_facet_attributes: { uuid: 'inv-123' },
35
+ };
36
+
37
+ const defaultProps = {
38
+ response: defaultResponse,
39
+ iframeSrc: 'https://example.test/assets/apps/example/index.html',
40
+ buildAppRoute: inventoryId => `systems/${inventoryId}`,
41
+ title: 'Example',
42
+ readyMessageType: 'IOP_EXAMPLE_READY',
43
+ className: 'example-host-tab',
44
+ iframeClassName: 'example-host-iframe',
45
+ testId: 'example-host-iframe',
46
+ };
47
+
48
+ describe('IopHostDetailsIframeTab', () => {
49
+ beforeEach(() => {
50
+ jest.clearAllMocks();
51
+ });
52
+
53
+ it('renders iframe and posts host-tab chrome on load', () => {
54
+ const { getByTestId, container } = render(
55
+ <MemoryRouter>
56
+ <IopHostDetailsIframeTab {...defaultProps} />
57
+ </MemoryRouter>
58
+ );
59
+
60
+ expect(container.querySelector('.example-host-tab')).toBeTruthy();
61
+ const iframe = getByTestId('example-host-iframe');
62
+ expect(iframe.getAttribute('src')).toBe(defaultProps.iframeSrc);
63
+
64
+ fireEvent.load(iframe);
65
+
66
+ expect(mockPostChrome).toHaveBeenCalledWith(
67
+ expect.anything(),
68
+ expect.objectContaining({
69
+ appRoute: 'systems/inv-123',
70
+ embedded: 'host-tab',
71
+ })
72
+ );
73
+ });
74
+
75
+ it('redirects to Overview when tab should be hidden', () => {
76
+ const { container } = render(
77
+ <MemoryRouter>
78
+ <IopHostDetailsIframeTab
79
+ {...defaultProps}
80
+ response={{
81
+ id: 2,
82
+ operatingsystem_name: 'Ubuntu 20.04',
83
+ insights_attributes: { uuid: 'insights-1' },
84
+ subscription_facet_attributes: { uuid: 'inv-123' },
85
+ }}
86
+ />
87
+ </MemoryRouter>
88
+ );
89
+
90
+ expect(mockHistoryReplace).toHaveBeenCalledWith(OVERVIEW_TAB_PATH);
91
+ expect(container.querySelector('.example-host-tab')).toBeNull();
92
+ });
93
+ });
@@ -0,0 +1,63 @@
1
+ import {
2
+ getIopAppRoute,
3
+ getIopForemanPath,
4
+ getIopIframeSrc,
5
+ } from '../iopIframeHelpers';
6
+
7
+ describe('iopIframeHelpers', () => {
8
+ const options = {
9
+ routePrefix: '/foreman_rh_cloud/insights_example',
10
+ fallbackRoute: 'dashboard',
11
+ };
12
+
13
+ describe('getIopAppRoute', () => {
14
+ it('maps foreman paths under the prefix to app routes', () => {
15
+ expect(
16
+ getIopAppRoute('/foreman_rh_cloud/insights_example/dashboard', options)
17
+ ).toBe('dashboard');
18
+ expect(
19
+ getIopAppRoute('/foreman_rh_cloud/insights_example/cves/cve-1', options)
20
+ ).toBe('cves/cve-1');
21
+ });
22
+
23
+ it('returns fallbackRoute for paths outside the prefix', () => {
24
+ expect(getIopAppRoute('/foreman_rh_cloud/other', options)).toBe(
25
+ 'dashboard'
26
+ );
27
+ });
28
+
29
+ it('returns fallbackRoute when pathname is only the prefix', () => {
30
+ expect(
31
+ getIopAppRoute('/foreman_rh_cloud/insights_example', options)
32
+ ).toBe('dashboard');
33
+ expect(
34
+ getIopAppRoute('/foreman_rh_cloud/insights_example/', options)
35
+ ).toBe('dashboard');
36
+ });
37
+ });
38
+
39
+ describe('getIopForemanPath', () => {
40
+ it('maps app routes to foreman paths', () => {
41
+ expect(getIopForemanPath('dashboard', options)).toBe(
42
+ '/foreman_rh_cloud/insights_example/dashboard'
43
+ );
44
+ expect(getIopForemanPath('cves/cve-1', options)).toBe(
45
+ '/foreman_rh_cloud/insights_example/cves/cve-1'
46
+ );
47
+ });
48
+
49
+ it('uses fallbackRoute when appRoute is empty', () => {
50
+ expect(getIopForemanPath('', options)).toBe(
51
+ '/foreman_rh_cloud/insights_example/dashboard'
52
+ );
53
+ });
54
+ });
55
+
56
+ describe('getIopIframeSrc', () => {
57
+ it('returns origin + static app path', () => {
58
+ expect(getIopIframeSrc('/assets/apps/example/index.html')).toBe(
59
+ `${window.location.origin}/assets/apps/example/index.html`
60
+ );
61
+ });
62
+ });
63
+ });
@@ -0,0 +1,60 @@
1
+ import { postIopChromeToIframe } from '../postIopChromeToIframe';
2
+ import { IOP_CHROME_INIT } from '../iopIframeConstants';
3
+
4
+ describe('postIopChromeToIframe', () => {
5
+ it('posts appRoute and embedded mode', () => {
6
+ const postMessage = jest.fn();
7
+ const iframe = { contentWindow: { postMessage } };
8
+
9
+ postIopChromeToIframe(iframe, {
10
+ permissions: [{ permission: 'compliance:policies:read' }],
11
+ pathname: '/new/hosts/demo.example.com',
12
+ appRoute: 'systems/uuid-1',
13
+ embedded: 'host-tab',
14
+ });
15
+
16
+ expect(postMessage).toHaveBeenCalledWith(
17
+ {
18
+ type: IOP_CHROME_INIT,
19
+ payload: {
20
+ user: expect.any(Object),
21
+ permissions: [{ permission: 'compliance:policies:read' }],
22
+ appRoute: 'systems/uuid-1',
23
+ pathname: '/new/hosts/demo.example.com',
24
+ embedded: 'host-tab',
25
+ },
26
+ },
27
+ window.location.origin
28
+ );
29
+ });
30
+
31
+ it('omits embedded from payload when not provided', () => {
32
+ const postMessage = jest.fn();
33
+ const iframe = { contentWindow: { postMessage } };
34
+
35
+ postIopChromeToIframe(iframe, {
36
+ permissions: [],
37
+ pathname: '/foreman_rh_cloud/insights_compliance/scappolicies',
38
+ appRoute: 'scappolicies',
39
+ });
40
+
41
+ expect(postMessage).toHaveBeenCalledWith(
42
+ {
43
+ type: IOP_CHROME_INIT,
44
+ payload: {
45
+ user: expect.any(Object),
46
+ permissions: [],
47
+ appRoute: 'scappolicies',
48
+ pathname: '/foreman_rh_cloud/insights_compliance/scappolicies',
49
+ },
50
+ },
51
+ window.location.origin
52
+ );
53
+ });
54
+
55
+ it('no-ops when iframe has no contentWindow', () => {
56
+ expect(() =>
57
+ postIopChromeToIframe(null, { appRoute: 'reports' })
58
+ ).not.toThrow();
59
+ });
60
+ });
@@ -0,0 +1,37 @@
1
+ import React, { useRef } from 'react';
2
+ import { render } from '@testing-library/react';
3
+ import { useIopIframeHeight } from '../useIopIframeHeight';
4
+ import { IOP_IFRAME_HEIGHT } from '../iopIframeConstants';
5
+
6
+ const HostTabIframe = () => {
7
+ const iframeRef = useRef(null);
8
+ useIopIframeHeight({ iframeRef });
9
+
10
+ return (
11
+ <iframe
12
+ ref={iframeRef}
13
+ data-testid="height-iframe"
14
+ title="height-iframe"
15
+ src="about:blank"
16
+ />
17
+ );
18
+ };
19
+
20
+ describe('useIopIframeHeight', () => {
21
+ it('sizes the iframe from a HEIGHT message', () => {
22
+ const { getByTestId } = render(<HostTabIframe />);
23
+ const iframe = getByTestId('height-iframe');
24
+
25
+ window.dispatchEvent(
26
+ new MessageEvent('message', {
27
+ origin: window.location.origin,
28
+ data: {
29
+ type: IOP_IFRAME_HEIGHT,
30
+ payload: { height: 2100 },
31
+ },
32
+ })
33
+ );
34
+
35
+ expect(iframe.style.height).toBe('2100px');
36
+ });
37
+ });
@@ -0,0 +1,5 @@
1
+ // Shared postMessage types for IoP iframes.
2
+ export const IOP_CHROME_INIT = 'IOP_CHROME_INIT';
3
+ export const IOP_IFRAME_READY = 'IOP_IFRAME_READY';
4
+ export const IOP_IFRAME_NAVIGATE = 'IOP_IFRAME_NAVIGATE';
5
+ export const IOP_IFRAME_HEIGHT = 'IOP_IFRAME_HEIGHT';
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Foreman browser path for an IoP app sub-route.
3
+ *
4
+ * @param {string} appRoute Path inside the iframe application page
5
+ * @param {{ routePrefix: string, fallbackRoute: string }} options
6
+ * @returns {string}
7
+ */
8
+ export const getIopForemanPath = (
9
+ appRoute = '',
10
+ { routePrefix, fallbackRoute } = {}
11
+ ) => {
12
+ const subRoute = String(appRoute).replace(/^\/+/, '') || fallbackRoute;
13
+
14
+ return `${routePrefix}/${subRoute}`;
15
+ };
16
+
17
+ /**
18
+ * Path inside the iframe application page derived from a Foreman pathname.
19
+ *
20
+ * @param {string} pathname Foreman location pathname
21
+ * @param {{ routePrefix: string, fallbackRoute: string }} options
22
+ * @returns {string}
23
+ */
24
+ export const getIopAppRoute = (
25
+ pathname = '',
26
+ { routePrefix, fallbackRoute } = {}
27
+ ) => {
28
+ if (!pathname.startsWith(routePrefix)) {
29
+ return fallbackRoute;
30
+ }
31
+
32
+ const subRoute = pathname.slice(routePrefix.length).replace(/^\//, '');
33
+
34
+ return subRoute || fallbackRoute;
35
+ };
36
+
37
+ /**
38
+ * Absolute URL for a static IoP app entry under Satellite assets.
39
+ *
40
+ * @param {string} staticAppPath e.g. /assets/apps/compliance/index.html
41
+ * @returns {string}
42
+ */
43
+ export const getIopIframeSrc = staticAppPath => {
44
+ if (typeof window === 'undefined') {
45
+ return staticAppPath;
46
+ }
47
+
48
+ return `${window.location.origin}${staticAppPath}`;
49
+ };
@@ -0,0 +1,35 @@
1
+ import { mockUser } from '../ScalprumModule/ScalprumContext';
2
+ import { IOP_CHROME_INIT } from './iopIframeConstants';
3
+
4
+ /**
5
+ * Sends chrome/RBAC context into an IoP app iframe after it loads.
6
+ *
7
+ * @param {HTMLIFrameElement} iframe
8
+ * @param {Object} options
9
+ * @param {Array} options.permissions Insights-format permissions from useInsightsPermissions
10
+ * @param {string} options.appRoute Path inside the iframe application page (e.g. reports, systems/<uuid>)
11
+ * @param {string} [options.pathname] Foreman pathname (informational; passed through to the child)
12
+ * @param {string} [options.embedded] Embedding mode for the iframe (e.g. 'host-tab')
13
+ */
14
+ export const postIopChromeToIframe = (
15
+ iframe,
16
+ { permissions = [], pathname = '', appRoute = '', embedded } = {}
17
+ ) => {
18
+ if (!iframe?.contentWindow || typeof window === 'undefined') {
19
+ return;
20
+ }
21
+
22
+ iframe.contentWindow.postMessage(
23
+ {
24
+ type: IOP_CHROME_INIT,
25
+ payload: {
26
+ user: mockUser,
27
+ permissions,
28
+ appRoute,
29
+ pathname,
30
+ ...(embedded ? { embedded } : {}),
31
+ },
32
+ },
33
+ window.location.origin
34
+ );
35
+ };
@@ -0,0 +1,54 @@
1
+ import { useCallback, useEffect } from 'react';
2
+ import { postIopChromeToIframe } from './postIopChromeToIframe';
3
+
4
+ /**
5
+ * Posts IOP chrome context into an iframe and re-posts when the child signals READY.
6
+ *
7
+ * @param {Object} options
8
+ * @param {React.RefObject} options.iframeRef
9
+ * @param {Array} options.permissions
10
+ * @param {string} options.appRoute
11
+ * @param {string} [options.pathname]
12
+ * @param {string} [options.embedded]
13
+ * @param {string} options.readyMessageType postMessage type from the child (e.g. IOP_COMPLIANCE_READY)
14
+ * @returns {Function} sendChromeContext — also pass to iframe onLoad
15
+ */
16
+ export const useIopIframeChrome = ({
17
+ iframeRef,
18
+ permissions,
19
+ appRoute,
20
+ pathname = '',
21
+ embedded,
22
+ readyMessageType,
23
+ }) => {
24
+ const sendChromeContext = useCallback(() => {
25
+ postIopChromeToIframe(iframeRef.current, {
26
+ permissions,
27
+ appRoute,
28
+ pathname,
29
+ embedded,
30
+ });
31
+ }, [appRoute, embedded, iframeRef, pathname, permissions]);
32
+
33
+ useEffect(() => {
34
+ sendChromeContext();
35
+ }, [sendChromeContext]);
36
+
37
+ useEffect(() => {
38
+ const handleMessage = event => {
39
+ if (event.origin !== window.location.origin) {
40
+ return;
41
+ }
42
+
43
+ if (event.data?.type === readyMessageType) {
44
+ sendChromeContext();
45
+ }
46
+ };
47
+
48
+ window.addEventListener('message', handleMessage);
49
+
50
+ return () => window.removeEventListener('message', handleMessage);
51
+ }, [readyMessageType, sendChromeContext]);
52
+
53
+ return sendChromeContext;
54
+ };
@@ -0,0 +1,35 @@
1
+ import { useEffect } from 'react';
2
+ import { IOP_IFRAME_HEIGHT } from './iopIframeConstants';
3
+
4
+ /**
5
+ * Sizes a host-details iframe from IOP_IFRAME_HEIGHT messages sent by the child.
6
+ *
7
+ * @param {Object} options
8
+ * @param {React.RefObject} options.iframeRef
9
+ */
10
+ export const useIopIframeHeight = ({ iframeRef }) => {
11
+ useEffect(() => {
12
+ const handleMessage = event => {
13
+ if (event.origin !== window.location.origin) {
14
+ return;
15
+ }
16
+
17
+ if (event.data?.type !== IOP_IFRAME_HEIGHT) {
18
+ return;
19
+ }
20
+
21
+ const iframe = iframeRef.current;
22
+ const height = event.data.payload?.height;
23
+
24
+ if (!iframe || !height) {
25
+ return;
26
+ }
27
+
28
+ iframe.style.height = `${height}px`;
29
+ };
30
+
31
+ window.addEventListener('message', handleMessage);
32
+
33
+ return () => window.removeEventListener('message', handleMessage);
34
+ }, [iframeRef]);
35
+ };
@@ -0,0 +1,40 @@
1
+ import { useEffect } from 'react';
2
+
3
+ /**
4
+ * Listens for iframe NAVIGATE messages and pushes matching Foreman paths.
5
+ *
6
+ * @param {Object} options
7
+ * @param {Object} options.history react-router v5 history
8
+ * @param {string} options.navigateMessageType
9
+ * @param {(appRoute: string) => string} options.getForemanPath
10
+ * @param {string} options.defaultAppRoute fallback when payload.appRoute is missing
11
+ */
12
+ export const useIopIframeNavigate = ({
13
+ history,
14
+ navigateMessageType,
15
+ getForemanPath,
16
+ defaultAppRoute,
17
+ }) => {
18
+ useEffect(() => {
19
+ const handleMessage = event => {
20
+ if (event.origin !== window.location.origin) {
21
+ return;
22
+ }
23
+
24
+ if (event.data?.type !== navigateMessageType) {
25
+ return;
26
+ }
27
+
28
+ const appRoute = event.data.payload?.appRoute || defaultAppRoute;
29
+ const nextPath = getForemanPath(appRoute);
30
+
31
+ if (window.location.pathname !== nextPath) {
32
+ history.push(nextPath);
33
+ }
34
+ };
35
+
36
+ window.addEventListener('message', handleMessage);
37
+
38
+ return () => window.removeEventListener('message', handleMessage);
39
+ }, [defaultAppRoute, getForemanPath, history, navigateMessageType]);
40
+ };
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foreman_rh_cloud
3
3
  version: !ruby/object:Gem::Version
4
- version: 14.7.0
4
+ version: 15.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Foreman Red Hat Cloud team
@@ -282,6 +282,10 @@ files:
282
282
  - webpack/CVEsHostDetailsTab/CVEsHostDetailsTab.scss
283
283
  - webpack/CVEsHostDetailsTab/__tests__/CVEsHostDetailsTab.test.js
284
284
  - webpack/CVEsHostDetailsTab/index.js
285
+ - webpack/ComplianceHostDetailsTab/ComplianceHostDetailsTab.js
286
+ - webpack/ComplianceHostDetailsTab/ComplianceHostDetailsTab.scss
287
+ - webpack/ComplianceHostDetailsTab/__tests__/ComplianceHostDetailsTab.test.js
288
+ - webpack/ComplianceHostDetailsTab/index.js
285
289
  - webpack/CveDetailsPage/CveDetailsPage.js
286
290
  - webpack/CveDetailsPage/CveDetailsPage.test.js
287
291
  - webpack/CveDetailsPage/index.js
@@ -447,6 +451,13 @@ files:
447
451
  - webpack/InsightsCloudSync/InsightsCloudSyncReducers.js
448
452
  - webpack/InsightsCloudSync/__tests__/InsightsCloudSyncActions.test.js
449
453
  - webpack/InsightsCloudSync/index.js
454
+ - webpack/InsightsCompliance/ComplianceIframe.js
455
+ - webpack/InsightsCompliance/ComplianceIframe.scss
456
+ - webpack/InsightsCompliance/InsightsCompliancePage.js
457
+ - webpack/InsightsCompliance/InsightsCompliancePage.test.js
458
+ - webpack/InsightsCompliance/complianceIframeConstants.js
459
+ - webpack/InsightsCompliance/complianceIframeHelpers.js
460
+ - webpack/InsightsCompliance/complianceIframeHelpers.test.js
450
461
  - webpack/InsightsHostDetailsTab/InsightsTab.js
451
462
  - webpack/InsightsHostDetailsTab/InsightsTab.scss
452
463
  - webpack/InsightsHostDetailsTab/InsightsTabActions.js
@@ -494,6 +505,21 @@ files:
494
505
  - webpack/common/ForemanTasks/index.js
495
506
  - webpack/common/Hooks/ConfigHooks.js
496
507
  - webpack/common/Hooks/PermissionsHooks.js
508
+ - webpack/common/IopIframe/IopAppIframe.js
509
+ - webpack/common/IopIframe/IopHostDetailsIframe.js
510
+ - webpack/common/IopIframe/IopHostDetailsIframe.scss
511
+ - webpack/common/IopIframe/IopHostDetailsIframeTab.js
512
+ - webpack/common/IopIframe/__tests__/IopAppIframe.test.js
513
+ - webpack/common/IopIframe/__tests__/IopHostDetailsIframeTab.test.js
514
+ - webpack/common/IopIframe/__tests__/iopIframeHelpers.test.js
515
+ - webpack/common/IopIframe/__tests__/postIopChromeToIframe.test.js
516
+ - webpack/common/IopIframe/__tests__/useIopIframeHeight.test.js
517
+ - webpack/common/IopIframe/iopIframeConstants.js
518
+ - webpack/common/IopIframe/iopIframeHelpers.js
519
+ - webpack/common/IopIframe/postIopChromeToIframe.js
520
+ - webpack/common/IopIframe/useIopIframeChrome.js
521
+ - webpack/common/IopIframe/useIopIframeHeight.js
522
+ - webpack/common/IopIframe/useIopIframeNavigate.js
497
523
  - webpack/common/ScalprumModule/ScalprumContext.js
498
524
  - webpack/common/ScalprumModule/__tests__/ScalprumContext.test.js
499
525
  - webpack/common/Switcher/HelpLabel.js