foreman_openscap 13.0.0 → 13.1.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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/api/v2/compliance/hosts_bulk_actions_controller.rb +89 -0
  3. data/config/routes.rb +2 -0
  4. data/lib/foreman_openscap/engine.rb +2 -1
  5. data/lib/foreman_openscap/version.rb +1 -1
  6. data/test/functional/api/v2/compliance/hosts_bulk_actions_controller_test.rb +132 -0
  7. data/webpack/components/HostsIndex/BulkActions/changeOpenscapProxy/BulkChangeOpenscapProxyModal.js +227 -0
  8. data/webpack/components/HostsIndex/BulkActions/changeOpenscapProxy/__tests__/BulkChangeOpenscapProxyModal.test.js +198 -0
  9. data/webpack/components/HostsIndex/ChangeOpenscapProxyAction.js +65 -0
  10. data/webpack/components/HostsIndex/__tests__/ChangeOpenscapProxyAction.test.js +172 -0
  11. data/webpack/components/LineChart/LineChartHelpers.js +23 -7
  12. data/webpack/components/OpenscapRemediationWizard/constants.js +8 -2
  13. data/webpack/components/OpenscapRemediationWizard/steps/Finish.js +2 -2
  14. data/webpack/components/OpenscapRemediationWizard/steps/ReviewRemediation.js +10 -2
  15. data/webpack/components/OpenscapRemediationWizard/steps/SnippetSelect.js +3 -1
  16. data/webpack/components/RuleSeverity/RuleSeverity.test.js +35 -8
  17. data/webpack/components/RuleSeverity/index.js +5 -5
  18. data/webpack/global_index.js +23 -1
  19. data/webpack/test_setup.js +1 -0
  20. metadata +10 -19
  21. data/webpack/components/ConfirmModal.js +0 -66
  22. data/webpack/components/ConfirmModal.scss +0 -3
  23. data/webpack/components/IndexLayout.js +0 -44
  24. data/webpack/components/IndexTable/IndexTableHelper.js +0 -6
  25. data/webpack/components/IndexTable/index.js +0 -73
  26. data/webpack/components/LinkButton.js +0 -42
  27. data/webpack/components/RuleSeverity/__snapshots__/RuleSeverity.test.js.snap +0 -41
  28. data/webpack/components/withDeleteModal.js +0 -51
  29. data/webpack/components/withLoading.js +0 -107
  30. data/webpack/helpers/commonHelper.js +0 -1
  31. data/webpack/helpers/globalIdHelper.js +0 -15
  32. data/webpack/helpers/mutationHelper.js +0 -68
  33. data/webpack/helpers/pageParamsHelper.js +0 -31
  34. data/webpack/helpers/permissionsHelper.js +0 -42
  35. data/webpack/helpers/tableHelper.js +0 -9
  36. data/webpack/helpers/toastHelper.js +0 -3
  37. data/webpack/testHelper.js +0 -127
@@ -0,0 +1,65 @@
1
+ import React, { useContext } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { MenuItem } from '@patternfly/react-core';
4
+ import { translate as __ } from 'foremanReact/common/I18n';
5
+ import {
6
+ openBulkModal,
7
+ useBulkModalOpen,
8
+ } from 'foremanReact/common/BulkModalStateHelper';
9
+ import { ForemanActionsBarContext } from 'foremanReact/components/HostDetails/ActionsBar';
10
+ import BulkChangeOpenscapProxyModal from './BulkActions/changeOpenscapProxy/BulkChangeOpenscapProxyModal';
11
+ import { CHANGE_OPENSCAP_MODAL_ID } from '../OpenscapRemediationWizard/constants';
12
+
13
+ export const ChangeOpenscapProxyMenuItem = ({ selectedCount }) => {
14
+ const openModal = () => openBulkModal(CHANGE_OPENSCAP_MODAL_ID, true);
15
+
16
+ return (
17
+ <MenuItem
18
+ itemId="change-openscap-proxy-dropdown-item"
19
+ key="change-openscap-proxy-dropdown-item"
20
+ onClick={openModal}
21
+ isDisabled={selectedCount === 0}
22
+ >
23
+ {__('OpenSCAP Proxy')}
24
+ </MenuItem>
25
+ );
26
+ };
27
+
28
+ ChangeOpenscapProxyMenuItem.propTypes = {
29
+ selectedCount: PropTypes.number,
30
+ };
31
+
32
+ ChangeOpenscapProxyMenuItem.defaultProps = {
33
+ selectedCount: 0,
34
+ };
35
+
36
+ const BulkChangeOpenscapProxyModalScene = () => {
37
+ const {
38
+ selectAllHostsMode = false,
39
+ selectedCount = 0,
40
+ fetchBulkParams,
41
+ organizationId,
42
+ locationId,
43
+ refreshTableData,
44
+ } = useContext(ForemanActionsBarContext) || {};
45
+
46
+ const { isOpen, close: closeModal } = useBulkModalOpen(
47
+ CHANGE_OPENSCAP_MODAL_ID
48
+ );
49
+
50
+ return (
51
+ <BulkChangeOpenscapProxyModal
52
+ key="bulk-change-openscap-proxy-modal"
53
+ selectAllHostsMode={selectAllHostsMode}
54
+ selectedCount={selectedCount}
55
+ fetchBulkParams={fetchBulkParams}
56
+ organizationId={organizationId}
57
+ locationId={locationId}
58
+ isOpen={isOpen}
59
+ closeModal={closeModal}
60
+ onSuccess={refreshTableData}
61
+ />
62
+ );
63
+ };
64
+
65
+ export default BulkChangeOpenscapProxyModalScene;
@@ -0,0 +1,172 @@
1
+ import React from 'react';
2
+ import { screen, fireEvent } from '@testing-library/react';
3
+ import '@testing-library/jest-dom';
4
+ import { Menu, MenuContent, MenuList } from '@patternfly/react-core';
5
+ import { rtlHelpers, initMockStore } from 'foremanReact/common/testHelpers';
6
+ import { openBulkModal } from 'foremanReact/common/BulkModalStateHelper';
7
+ import { ForemanActionsBarContext } from 'foremanReact/components/HostDetails/ActionsBar';
8
+ import { STATUS } from 'foremanReact/constants';
9
+ import { APIActions } from 'foremanReact/redux/API';
10
+ import {
11
+ CHANGE_OPENSCAP_MODAL_ID,
12
+ OPENSCAP_PROXIES_KEY,
13
+ } from '../../OpenscapRemediationWizard/constants';
14
+ import BulkChangeOpenscapProxyModalScene, {
15
+ ChangeOpenscapProxyMenuItem,
16
+ } from '../ChangeOpenscapProxyAction';
17
+
18
+ const { renderWithStore } = rtlHelpers;
19
+
20
+ jest.spyOn(APIActions, 'get');
21
+ jest.spyOn(APIActions, 'put');
22
+
23
+ const proxiesResolvedState = {
24
+ API: {
25
+ [OPENSCAP_PROXIES_KEY]: {
26
+ status: STATUS.RESOLVED,
27
+ response: {
28
+ results: [
29
+ { id: 1, name: 'openscap-proxy-1.example.com' },
30
+ { id: 2, name: 'openscap-proxy-2.example.com' },
31
+ ],
32
+ },
33
+ },
34
+ },
35
+ };
36
+
37
+ const actionsBarValue = {
38
+ selectAllHostsMode: false,
39
+ selectedCount: 3,
40
+ fetchBulkParams: jest.fn(() => 'id ^ (1,2,3)'),
41
+ organizationId: 1,
42
+ locationId: 2,
43
+ };
44
+
45
+ const renderMenuItem = (props = {}) =>
46
+ renderWithStore(
47
+ // eslint-disable-next-line @theforeman/rules/require-ouiaid
48
+ <Menu activeItemId={null}>
49
+ <MenuContent>
50
+ <MenuList>
51
+ <ChangeOpenscapProxyMenuItem selectedCount={3} {...props} />
52
+ </MenuList>
53
+ </MenuContent>
54
+ </Menu>
55
+ );
56
+
57
+ const renderScene = (contextValue = {}, initialState = proxiesResolvedState) =>
58
+ renderWithStore(
59
+ <ForemanActionsBarContext.Provider
60
+ value={{ ...actionsBarValue, ...contextValue }}
61
+ >
62
+ <BulkChangeOpenscapProxyModalScene />
63
+ </ForemanActionsBarContext.Provider>,
64
+ initialState
65
+ );
66
+
67
+ const renderMenuItemWithScene = (
68
+ menuProps = {},
69
+ contextValue = {},
70
+ initialState = proxiesResolvedState
71
+ ) =>
72
+ renderWithStore(
73
+ <ForemanActionsBarContext.Provider
74
+ value={{ ...actionsBarValue, ...contextValue }}
75
+ >
76
+ {/* eslint-disable-next-line @theforeman/rules/require-ouiaid */}
77
+ <Menu activeItemId={null}>
78
+ <MenuContent>
79
+ <MenuList>
80
+ <ChangeOpenscapProxyMenuItem selectedCount={3} {...menuProps} />
81
+ </MenuList>
82
+ </MenuContent>
83
+ </Menu>
84
+ <BulkChangeOpenscapProxyModalScene />
85
+ </ForemanActionsBarContext.Provider>,
86
+ initialState
87
+ );
88
+
89
+ describe('ChangeOpenscapProxyMenuItem', () => {
90
+ beforeEach(() => {
91
+ jest.clearAllMocks();
92
+ delete initMockStore.API[OPENSCAP_PROXIES_KEY];
93
+ openBulkModal(CHANGE_OPENSCAP_MODAL_ID, false);
94
+ APIActions.get.mockImplementation(payload => ({
95
+ type: 'TEST_API_GET',
96
+ payload,
97
+ }));
98
+ APIActions.put.mockImplementation(payload => ({
99
+ type: 'TEST_API_PUT',
100
+ payload,
101
+ }));
102
+ });
103
+
104
+ it('renders the OpenSCAP Proxy menu item', () => {
105
+ renderMenuItem();
106
+ expect(screen.getByText('OpenSCAP Proxy')).toBeInTheDocument();
107
+ });
108
+
109
+ it('is disabled when no hosts are selected', () => {
110
+ renderMenuItem({ selectedCount: 0 });
111
+ expect(
112
+ screen.getByRole('menuitem', { name: 'OpenSCAP Proxy' })
113
+ ).toBeDisabled();
114
+ });
115
+
116
+ it('opens the bulk modal when clicked', () => {
117
+ renderMenuItemWithScene({ selectedCount: 2 });
118
+
119
+ expect(screen.queryByText('Assign OpenSCAP Proxy')).not.toBeInTheDocument();
120
+
121
+ fireEvent.click(screen.getByText('OpenSCAP Proxy'));
122
+
123
+ expect(screen.getByText('Assign OpenSCAP Proxy')).toBeInTheDocument();
124
+ });
125
+ });
126
+
127
+ describe('BulkChangeOpenscapProxyModalScene', () => {
128
+ beforeEach(() => {
129
+ jest.clearAllMocks();
130
+ delete initMockStore.API[OPENSCAP_PROXIES_KEY];
131
+ openBulkModal(CHANGE_OPENSCAP_MODAL_ID, false);
132
+ APIActions.get.mockImplementation(payload => ({
133
+ type: 'TEST_API_GET',
134
+ payload,
135
+ }));
136
+ APIActions.put.mockImplementation(payload => ({
137
+ type: 'TEST_API_PUT',
138
+ payload,
139
+ }));
140
+ });
141
+
142
+ it('does not render the modal when closed', () => {
143
+ renderScene();
144
+ expect(screen.queryByText('Assign OpenSCAP Proxy')).not.toBeInTheDocument();
145
+ });
146
+
147
+ it('passes actions-bar context props when the modal is open', () => {
148
+ openBulkModal(CHANGE_OPENSCAP_MODAL_ID, true);
149
+ renderScene({
150
+ selectAllHostsMode: true,
151
+ selectedCount: 5,
152
+ organizationId: 10,
153
+ locationId: 20,
154
+ });
155
+
156
+ expect(screen.getByText('Assign OpenSCAP Proxy')).toBeInTheDocument();
157
+ expect(screen.getByText('ALL selected hosts.')).toBeInTheDocument();
158
+ expect(
159
+ screen.getByRole('button', { name: 'Select OpenSCAP Proxy' })
160
+ ).toBeInTheDocument();
161
+ });
162
+
163
+ it('closes the modal via Cancel', () => {
164
+ openBulkModal(CHANGE_OPENSCAP_MODAL_ID, true);
165
+ renderScene();
166
+
167
+ expect(screen.getByText('Assign OpenSCAP Proxy')).toBeInTheDocument();
168
+
169
+ fireEvent.click(screen.getByRole('button', { name: 'Cancel' }));
170
+ expect(screen.queryByText('Assign OpenSCAP Proxy')).not.toBeInTheDocument();
171
+ });
172
+ });
@@ -1,5 +1,17 @@
1
1
  /** Backend sends [label, values, color]; legacy Foreman charts used spread columns. */
2
2
  import { chart_color_black_500 as chartColorBlack500 } from '@patternfly/react-tokens';
3
+ import {
4
+ MS_PER_SECOND,
5
+ SECONDS_PER_MINUTE,
6
+ MINUTES_PER_HOUR,
7
+ HOURS_PER_HALF_DAY,
8
+ } from 'foremanReact/constants';
9
+
10
+ const TIMESTAMP_THRESHOLD = 1e12;
11
+ const DEFAULT_TICK_MID = 0.5;
12
+ const MIN_TICK_STEP = 0.1;
13
+ const EXPONENTIAL_THRESHOLD = 1e21;
14
+ const CLAMP_SCALE_FACTOR = 0.9;
3
15
 
4
16
  const getColumnValues = col => {
5
17
  if (Array.isArray(col[1])) return col[1];
@@ -32,7 +44,7 @@ const getSeriesColor = col => {
32
44
 
33
45
  const toMs = val => {
34
46
  const n = Number(val);
35
- return n >= 1e12 ? n : n * 1000;
47
+ return n >= TIMESTAMP_THRESHOLD ? n : n * MS_PER_SECOND;
36
48
  };
37
49
 
38
50
  /** Process raw backend data into chart series for PatternFly line charts. */
@@ -101,9 +113,9 @@ export const getYTickValues = (chartData, hiddenSeries = new Set()) => {
101
113
  });
102
114
  });
103
115
 
104
- if (maxY <= 0) return [0, 0.5, 1.0];
116
+ if (maxY <= 0) return [0, DEFAULT_TICK_MID, 1.0];
105
117
 
106
- const step = Math.max(0.1, Math.ceil((maxY / 4) * 10) / 10);
118
+ const step = Math.max(MIN_TICK_STEP, Math.ceil((maxY / 4) * 10) / 10);
107
119
  return [0, 1, 2, 3, 4, 5].map(i => Math.round(i * step * 10) / 10);
108
120
  };
109
121
 
@@ -111,7 +123,7 @@ export const getYTickValues = (chartData, hiddenSeries = new Set()) => {
111
123
  export const formatTooltipValue = value => {
112
124
  const num = Number(value);
113
125
  if (!Number.isFinite(num)) return '';
114
- if (Math.abs(num) >= 1e21) {
126
+ if (Math.abs(num) >= EXPONENTIAL_THRESHOLD) {
115
127
  return num.toExponential(1);
116
128
  }
117
129
  return String(Math.round(num));
@@ -128,14 +140,14 @@ export const clampChartPadding = (padding, width, height) => {
128
140
 
129
141
  const horizontalTotal = left + right;
130
142
  if (horizontalTotal >= width) {
131
- const scale = (width * 0.9) / horizontalTotal;
143
+ const scale = (width * CLAMP_SCALE_FACTOR) / horizontalTotal;
132
144
  left *= scale;
133
145
  right *= scale;
134
146
  }
135
147
 
136
148
  const verticalTotal = top + bottom;
137
149
  if (verticalTotal >= height) {
138
- const scale = (height * 0.9) / verticalTotal;
150
+ const scale = (height * CLAMP_SCALE_FACTOR) / verticalTotal;
139
151
  top *= scale;
140
152
  bottom *= scale;
141
153
  }
@@ -154,7 +166,11 @@ export const getTimeseriesXDomain = chartData => {
154
166
  const max = Math.max(...times);
155
167
 
156
168
  if (min === max) {
157
- const offset = 12 * 60 * 60 * 1000;
169
+ const offset =
170
+ HOURS_PER_HALF_DAY *
171
+ MINUTES_PER_HOUR *
172
+ SECONDS_PER_MINUTE *
173
+ MS_PER_SECOND;
158
174
  return [new Date(min - offset), new Date(max + offset)];
159
175
  }
160
176
 
@@ -1,7 +1,5 @@
1
1
  import { translate as __ } from 'foremanReact/common/I18n';
2
2
 
3
- export const OPENSCAP_REMEDIATION_MODAL_ID = 'openscapRemediationModal';
4
- export const HOSTS_PATH = '/hosts';
5
3
  export const FAIL_RULE_SEARCH = 'fails_xccdf_rule';
6
4
 
7
5
  export const HOSTS_API_PATH = '/api/hosts';
@@ -15,9 +13,17 @@ export const JOB_INVOCATION_API_REQUEST_KEY = 'OPENSCAP_REX_JOB_INVOCATIONS';
15
13
  export const SNIPPET_SH = 'urn:xccdf:fix:script:sh';
16
14
  export const SNIPPET_ANSIBLE = 'urn:xccdf:fix:script:ansible';
17
15
 
16
+ export const TOOLTIP_COPIED_EXIT_DELAY_MS = 1500;
17
+ export const TOOLTIP_DEFAULT_EXIT_DELAY_MS = 600;
18
+
18
19
  export const WIZARD_TITLES = {
19
20
  snippetSelect: __('Select snippet'),
20
21
  reviewHosts: __('Review hosts'),
21
22
  reviewRemediation: __('Review remediation'),
22
23
  finish: __('Done'),
23
24
  };
25
+
26
+ export const BULK_CHANGE_OPENSCAP_PROXY_KEY = 'BULK_CHANGE_OPENSCAP_PROXY';
27
+ export const OPENSCAP_PROXIES_KEY = 'OPENSCAP_PROXIES_KEY';
28
+
29
+ export const CHANGE_OPENSCAP_MODAL_ID = 'BULK_CHANGE_OPENSCAP_PROXY_MODAL';
@@ -6,7 +6,7 @@ import { ExternalLinkSquareAltIcon } from '@patternfly/react-icons';
6
6
 
7
7
  import { translate as __ } from 'foremanReact/common/I18n';
8
8
  import { foremanUrl } from 'foremanReact/common/helpers';
9
- import { STATUS } from 'foremanReact/constants';
9
+ import { STATUS, HTTP_STATUS_CODES } from 'foremanReact/constants';
10
10
  import { useAPI } from 'foremanReact/common/hooks/API/APIHooks';
11
11
  import Loading from 'foremanReact/components/Loading';
12
12
  import PermissionDenied from 'foremanReact/components/PermissionDenied';
@@ -91,7 +91,7 @@ const Finish = ({ onClose }) => {
91
91
  </Button>
92
92
  );
93
93
  const errorComponent =
94
- statusCode === 403 ? (
94
+ statusCode === HTTP_STATUS_CODES.FORBIDDEN ? (
95
95
  <PermissionDenied
96
96
  missingPermissions={data?.error?.missing_permissions}
97
97
  primaryButton={closeBtn}
@@ -24,7 +24,11 @@ import {
24
24
  import OpenscapRemediationWizardContext from '../OpenscapRemediationWizardContext';
25
25
  import WizardHeader from '../WizardHeader';
26
26
  import ViewSelectedHostsLink from '../ViewSelectedHostsLink';
27
- import { FAIL_RULE_SEARCH } from '../constants';
27
+ import {
28
+ FAIL_RULE_SEARCH,
29
+ TOOLTIP_COPIED_EXIT_DELAY_MS,
30
+ TOOLTIP_DEFAULT_EXIT_DELAY_MS,
31
+ } from '../constants';
28
32
  import { findFixBySnippet } from '../helpers';
29
33
 
30
34
  import './ReviewRemediation.scss';
@@ -78,7 +82,11 @@ const ReviewRemediation = () => {
78
82
  textId="code-content"
79
83
  aria-label="Copy to clipboard"
80
84
  onClick={e => onCopyClick(e, snippetText)}
81
- exitDelay={copied ? 1500 : 600}
85
+ exitDelay={
86
+ copied
87
+ ? TOOLTIP_COPIED_EXIT_DELAY_MS
88
+ : TOOLTIP_DEFAULT_EXIT_DELAY_MS
89
+ }
82
90
  maxWidth="110px"
83
91
  variant="plain"
84
92
  onTooltipHidden={() => setCopied(false)}
@@ -18,6 +18,8 @@ import WizardHeader from '../WizardHeader';
18
18
  import EmptyState from '../../EmptyState';
19
19
  import { errorMsg, supportedRemediationSnippets } from '../helpers';
20
20
 
21
+ const URN_TAIL_SEGMENTS = -2;
22
+
21
23
  const SnippetSelect = () => {
22
24
  const {
23
25
  fixes,
@@ -44,7 +46,7 @@ const SnippetSelect = () => {
44
46
  if (mapped) return mapped;
45
47
 
46
48
  return join(
47
- map(slice(split(system, ':'), -2), n => capitalize(n)),
49
+ map(slice(split(system, ':'), URN_TAIL_SEGMENTS), n => capitalize(n)),
48
50
  ' '
49
51
  );
50
52
  };
@@ -1,13 +1,40 @@
1
- import { testComponentSnapshotsWithFixtures } from '@theforeman/test';
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
+ import '@testing-library/jest-dom';
2
4
 
3
5
  import RuleSeverity from './index';
4
6
 
5
- const levels = ['Low', 'Medium', 'High', 'Critical', 'foo'];
7
+ jest.mock('./i_severity-critical.svg', () => 'critical.svg');
8
+ jest.mock('./i_severity-high.svg', () => 'high.svg');
9
+ jest.mock('./i_severity-med.svg', () => 'med.svg');
10
+ jest.mock('./i_severity-low.svg', () => 'low.svg');
11
+ jest.mock('./i_unknown.svg', () => 'unknown.svg');
6
12
 
7
- const fixtures = levels.reduce((memo, level) => {
8
- memo[`should render for ${level} severity`] = { severity: level };
9
- return memo;
10
- }, {});
13
+ describe('RuleSeverity', () => {
14
+ it.each([
15
+ ['low', 'Low Severity', 'low.svg'],
16
+ ['medium', 'Medium Severity', 'med.svg'],
17
+ ['high', 'High Severity', 'high.svg'],
18
+ ['critical', 'Critical Severity', 'critical.svg'],
19
+ ['unknown', 'Unknown Severity', 'unknown.svg'],
20
+ ])('renders the %s severity icon', (severity, altText, iconSrc) => {
21
+ render(<RuleSeverity severity={severity} />);
11
22
 
12
- describe('RuleSeverity', () =>
13
- testComponentSnapshotsWithFixtures(RuleSeverity, fixtures));
23
+ const icon = screen.getByRole('img', { name: altText });
24
+
25
+ expect(icon).toBeInTheDocument();
26
+ expect(icon).toHaveAttribute('src', iconSrc);
27
+ });
28
+
29
+ it.each(['foo', 'Low', 'Medium', 'High', 'Critical'])(
30
+ 'renders the unknown severity icon for unrecognized severity %s',
31
+ severity => {
32
+ render(<RuleSeverity severity={severity} />);
33
+
34
+ const icon = screen.getByRole('img', { name: 'Unknown Severity' });
35
+
36
+ expect(icon).toBeInTheDocument();
37
+ expect(icon).toHaveAttribute('src', 'unknown.svg');
38
+ }
39
+ );
40
+ });
@@ -11,14 +11,14 @@ import './RuleSeverity.scss';
11
11
 
12
12
  const RuleSeverity = props => {
13
13
  const propsMapping = {
14
- low: { alt: 'Low Serverity', src: SeverityLow },
15
- medium: { alt: 'Medium Serverity', src: SeverityMedium },
16
- high: { alt: 'High Serverity', src: SeverityHigh },
14
+ low: { alt: 'Low Severity', src: SeverityLow },
15
+ medium: { alt: 'Medium Severity', src: SeverityMedium },
16
+ high: { alt: 'High Severity', src: SeverityHigh },
17
17
  critical: {
18
- alt: 'Critical Serverity',
18
+ alt: 'Critical Severity',
19
19
  src: SeverityCritical,
20
20
  },
21
- unknown: { alt: 'Unknown Serverity', src: SeverityUnknown },
21
+ unknown: { alt: 'Unknown Severity', src: SeverityUnknown },
22
22
  };
23
23
 
24
24
  const imgProps = propsMapping[props.severity] || propsMapping.unknown;
@@ -1,10 +1,32 @@
1
1
  import React from 'react';
2
2
  import { addGlobalFill } from 'foremanReact/components/common/Fill/GlobalFill';
3
3
  import HostKebabItems from './components/HostExtentions/HostKebabItems';
4
+ import BulkChangeOpenscapProxyModalScene, {
5
+ ChangeOpenscapProxyMenuItem,
6
+ } from './components/HostsIndex/ChangeOpenscapProxyAction';
7
+
8
+ const HOST_ASSOCIATIONS_WEIGHT = 1212;
9
+ const BULK_MODAL_WEIGHT = 100;
10
+
11
+ const OPENSCAP_KEBAB_WEIGHT = 400;
4
12
 
5
13
  addGlobalFill(
6
14
  'host-details-kebab',
7
15
  `openscap-kebab-items`,
8
16
  <HostKebabItems key="openscap-host-kebab" />,
9
- 400
17
+ OPENSCAP_KEBAB_WEIGHT
18
+ );
19
+
20
+ addGlobalFill(
21
+ '_host-associations',
22
+ 'openscap-change-proxy-menu-item',
23
+ <ChangeOpenscapProxyMenuItem key="openscap-change-proxy-menu-item" />,
24
+ HOST_ASSOCIATIONS_WEIGHT
25
+ );
26
+
27
+ addGlobalFill(
28
+ '_all-hosts-modals',
29
+ 'BulkChangeOpenscapProxyModal',
30
+ <BulkChangeOpenscapProxyModalScene key="bulk-change-openscap-proxy-modal" />,
31
+ BULK_MODAL_WEIGHT
10
32
  );
@@ -0,0 +1 @@
1
+ import 'foremanJSTestSetup';
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foreman_openscap
3
3
  version: !ruby/object:Gem::Version
4
- version: 13.0.0
4
+ version: 13.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - slukasik@redhat.com
@@ -45,6 +45,7 @@ files:
45
45
  - app/assets/stylesheets/foreman_openscap/reports.css
46
46
  - app/assets/stylesheets/foreman_openscap/scap_breakdown_chart.css
47
47
  - app/controllers/api/v2/compliance/arf_reports_controller.rb
48
+ - app/controllers/api/v2/compliance/hosts_bulk_actions_controller.rb
48
49
  - app/controllers/api/v2/compliance/policies_controller.rb
49
50
  - app/controllers/api/v2/compliance/scap_content_profiles_controller.rb
50
51
  - app/controllers/api/v2/compliance/scap_contents_controller.rb
@@ -333,6 +334,7 @@ files:
333
334
  - test/files/tailoring_files/ssg-firefox-ds-tailoring-2.xml
334
335
  - test/files/tailoring_files/ssg-firefox-ds-tailoring.xml
335
336
  - test/functional/api/v2/compliance/arf_reports_controller_test.rb
337
+ - test/functional/api/v2/compliance/hosts_bulk_actions_controller_test.rb
336
338
  - test/functional/api/v2/compliance/policies_controller_test.rb
337
339
  - test/functional/api/v2/compliance/scap_content_profiles_controller_test.rb
338
340
  - test/functional/api/v2/compliance/scap_contents_controller_test.rb
@@ -362,20 +364,18 @@ files:
362
364
  - test/unit/services/lookup_key_overrider_test.rb
363
365
  - test/unit/services/report_dashboard/data_test.rb
364
366
  - test/unit/tailoring_file_test.rb
365
- - webpack/components/ConfirmModal.js
366
- - webpack/components/ConfirmModal.scss
367
367
  - webpack/components/EmptyState.js
368
368
  - webpack/components/HostExtentions/HostKebabItems.js
369
- - webpack/components/IndexLayout.js
369
+ - webpack/components/HostsIndex/BulkActions/changeOpenscapProxy/BulkChangeOpenscapProxyModal.js
370
+ - webpack/components/HostsIndex/BulkActions/changeOpenscapProxy/__tests__/BulkChangeOpenscapProxyModal.test.js
371
+ - webpack/components/HostsIndex/ChangeOpenscapProxyAction.js
372
+ - webpack/components/HostsIndex/__tests__/ChangeOpenscapProxyAction.test.js
370
373
  - webpack/components/IndexLayout.scss
371
- - webpack/components/IndexTable/IndexTableHelper.js
372
- - webpack/components/IndexTable/index.js
373
374
  - webpack/components/LineChart/LineChart.fixtures.js
374
375
  - webpack/components/LineChart/LineChart.scss
375
376
  - webpack/components/LineChart/LineChart.test.js
376
377
  - webpack/components/LineChart/LineChartHelpers.js
377
378
  - webpack/components/LineChart/index.js
378
- - webpack/components/LinkButton.js
379
379
  - webpack/components/OpenscapRemediationWizard/Footer.js
380
380
  - webpack/components/OpenscapRemediationWizard/OpenscapRemediationSelectors.js
381
381
  - webpack/components/OpenscapRemediationWizard/OpenscapRemediationWizardContext.js
@@ -392,25 +392,15 @@ files:
392
392
  - webpack/components/OpenscapRemediationWizard/steps/index.js
393
393
  - webpack/components/RuleSeverity/RuleSeverity.scss
394
394
  - webpack/components/RuleSeverity/RuleSeverity.test.js
395
- - webpack/components/RuleSeverity/__snapshots__/RuleSeverity.test.js.snap
396
395
  - webpack/components/RuleSeverity/i_severity-critical.svg
397
396
  - webpack/components/RuleSeverity/i_severity-high.svg
398
397
  - webpack/components/RuleSeverity/i_severity-low.svg
399
398
  - webpack/components/RuleSeverity/i_severity-med.svg
400
399
  - webpack/components/RuleSeverity/i_unknown.svg
401
400
  - webpack/components/RuleSeverity/index.js
402
- - webpack/components/withDeleteModal.js
403
- - webpack/components/withLoading.js
404
401
  - webpack/global_index.js
405
- - webpack/helpers/commonHelper.js
406
- - webpack/helpers/globalIdHelper.js
407
- - webpack/helpers/mutationHelper.js
408
- - webpack/helpers/pageParamsHelper.js
409
- - webpack/helpers/permissionsHelper.js
410
- - webpack/helpers/tableHelper.js
411
- - webpack/helpers/toastHelper.js
412
402
  - webpack/index.js
413
- - webpack/testHelper.js
403
+ - webpack/test_setup.js
414
404
  homepage: https://github.com/theforeman/foreman_openscap
415
405
  licenses:
416
406
  - GPL-3.0
@@ -429,7 +419,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
429
419
  - !ruby/object:Gem::Version
430
420
  version: '0'
431
421
  requirements: []
432
- rubygems_version: 4.0.10
422
+ rubygems_version: 4.0.16
433
423
  specification_version: 4
434
424
  summary: Foreman plug-in for displaying OpenSCAP audit reports
435
425
  test_files:
@@ -452,6 +442,7 @@ test_files:
452
442
  - test/files/tailoring_files/ssg-firefox-ds-tailoring-2.xml
453
443
  - test/files/tailoring_files/ssg-firefox-ds-tailoring.xml
454
444
  - test/functional/api/v2/compliance/arf_reports_controller_test.rb
445
+ - test/functional/api/v2/compliance/hosts_bulk_actions_controller_test.rb
455
446
  - test/functional/api/v2/compliance/policies_controller_test.rb
456
447
  - test/functional/api/v2/compliance/scap_content_profiles_controller_test.rb
457
448
  - test/functional/api/v2/compliance/scap_contents_controller_test.rb
@@ -1,66 +0,0 @@
1
- import React from 'react';
2
- import PropTypes from 'prop-types';
3
- import { Modal, Button, ModalVariant, Spinner } from '@patternfly/react-core';
4
-
5
- import { translate as __ } from 'foremanReact/common/I18n';
6
-
7
- import './ConfirmModal.scss';
8
-
9
- const ConfirmModal = props => {
10
- const [callMutation, { loading }] = props.prepareMutation();
11
-
12
- const actions = [
13
- <Button
14
- ouiaId={`oscap-conf-modal-${props.record?.id}-confirm`}
15
- key="confirm"
16
- variant="primary"
17
- onClick={() => props.onConfirm(callMutation, props.record.id)}
18
- isDisabled={loading}
19
- >
20
- {__('Confirm')}
21
- </Button>,
22
- <Button
23
- ouiaId={`oscap-conf-modal-${props.record?.id}-cancel`}
24
- key="cancel"
25
- variant="link"
26
- onClick={event => props.onClose()}
27
- isDisabled={loading}
28
- >
29
- {__('Cancel')}
30
- </Button>,
31
- ];
32
-
33
- if (loading) {
34
- actions.push(<Spinner key="spinner" size="lg" />);
35
- }
36
-
37
- return (
38
- <Modal
39
- ouiaId={`oscap-conf-modal-${props.record?.id}`}
40
- variant={ModalVariant.medium}
41
- title={props.title}
42
- isOpen={props.isOpen}
43
- className="foreman-modal"
44
- showClose={false}
45
- actions={actions}
46
- >
47
- {props.text}
48
- </Modal>
49
- );
50
- };
51
-
52
- ConfirmModal.propTypes = {
53
- prepareMutation: PropTypes.func.isRequired,
54
- onConfirm: PropTypes.func.isRequired,
55
- record: PropTypes.object,
56
- onClose: PropTypes.func.isRequired,
57
- title: PropTypes.string.isRequired,
58
- isOpen: PropTypes.bool.isRequired,
59
- text: PropTypes.string.isRequired,
60
- };
61
-
62
- ConfirmModal.defaultProps = {
63
- record: null,
64
- };
65
-
66
- export default ConfirmModal;
@@ -1,3 +0,0 @@
1
- .pf-v5-c-backdrop {
2
- z-index: 1040;
3
- }