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.
- checksums.yaml +4 -4
- data/app/controllers/api/v2/compliance/hosts_bulk_actions_controller.rb +89 -0
- data/config/routes.rb +2 -0
- data/lib/foreman_openscap/engine.rb +2 -1
- data/lib/foreman_openscap/version.rb +1 -1
- data/test/functional/api/v2/compliance/hosts_bulk_actions_controller_test.rb +132 -0
- data/webpack/components/HostsIndex/BulkActions/changeOpenscapProxy/BulkChangeOpenscapProxyModal.js +227 -0
- data/webpack/components/HostsIndex/BulkActions/changeOpenscapProxy/__tests__/BulkChangeOpenscapProxyModal.test.js +198 -0
- data/webpack/components/HostsIndex/ChangeOpenscapProxyAction.js +65 -0
- data/webpack/components/HostsIndex/__tests__/ChangeOpenscapProxyAction.test.js +172 -0
- data/webpack/components/LineChart/LineChartHelpers.js +23 -7
- data/webpack/components/OpenscapRemediationWizard/constants.js +8 -2
- data/webpack/components/OpenscapRemediationWizard/steps/Finish.js +2 -2
- data/webpack/components/OpenscapRemediationWizard/steps/ReviewRemediation.js +10 -2
- data/webpack/components/OpenscapRemediationWizard/steps/SnippetSelect.js +3 -1
- data/webpack/components/RuleSeverity/RuleSeverity.test.js +35 -8
- data/webpack/components/RuleSeverity/index.js +5 -5
- data/webpack/global_index.js +23 -1
- data/webpack/test_setup.js +1 -0
- metadata +10 -19
- data/webpack/components/ConfirmModal.js +0 -66
- data/webpack/components/ConfirmModal.scss +0 -3
- data/webpack/components/IndexLayout.js +0 -44
- data/webpack/components/IndexTable/IndexTableHelper.js +0 -6
- data/webpack/components/IndexTable/index.js +0 -73
- data/webpack/components/LinkButton.js +0 -42
- data/webpack/components/RuleSeverity/__snapshots__/RuleSeverity.test.js.snap +0 -41
- data/webpack/components/withDeleteModal.js +0 -51
- data/webpack/components/withLoading.js +0 -107
- data/webpack/helpers/commonHelper.js +0 -1
- data/webpack/helpers/globalIdHelper.js +0 -15
- data/webpack/helpers/mutationHelper.js +0 -68
- data/webpack/helpers/pageParamsHelper.js +0 -31
- data/webpack/helpers/permissionsHelper.js +0 -42
- data/webpack/helpers/tableHelper.js +0 -9
- data/webpack/helpers/toastHelper.js +0 -3
- 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 >=
|
|
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,
|
|
116
|
+
if (maxY <= 0) return [0, DEFAULT_TICK_MID, 1.0];
|
|
105
117
|
|
|
106
|
-
const step = Math.max(
|
|
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) >=
|
|
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 *
|
|
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 *
|
|
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 =
|
|
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 ===
|
|
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 {
|
|
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={
|
|
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, ':'),
|
|
49
|
+
map(slice(split(system, ':'), URN_TAIL_SEGMENTS), n => capitalize(n)),
|
|
48
50
|
' '
|
|
49
51
|
);
|
|
50
52
|
};
|
|
@@ -1,13 +1,40 @@
|
|
|
1
|
-
import
|
|
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
|
-
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
13
|
-
|
|
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
|
|
15
|
-
medium: { alt: 'Medium
|
|
16
|
-
high: { alt: 'High
|
|
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
|
|
18
|
+
alt: 'Critical Severity',
|
|
19
19
|
src: SeverityCritical,
|
|
20
20
|
},
|
|
21
|
-
unknown: { alt: 'Unknown
|
|
21
|
+
unknown: { alt: 'Unknown Severity', src: SeverityUnknown },
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
const imgProps = propsMapping[props.severity] || propsMapping.unknown;
|
data/webpack/global_index.js
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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/
|
|
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/
|
|
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.
|
|
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;
|