foreman_openscap 13.0.0 → 13.0.1
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/lib/foreman_openscap/version.rb +1 -1
- data/webpack/components/LineChart/LineChartHelpers.js +23 -7
- data/webpack/components/OpenscapRemediationWizard/constants.js +3 -0
- 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/global_index.js +3 -1
- data/webpack/test_setup.js +1 -0
- metadata +3 -18
- 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/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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 31e3721d3419a9695bb06d27b4dc1f79daf7aa825fe091afdd5ce3ab84fec6d5
|
|
4
|
+
data.tar.gz: '09f41b5c13f636a9227ace3b2e2b7c27f5341d22400d98afdcddc597c5650af6'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 508fba001dca06e61ae108a10ca54193eec9ab3ba4f26b310481713a852aef69d09c720fdd44ea8ecf89a741a906c30b415640497894f4e0da92b14f0bb5de15
|
|
7
|
+
data.tar.gz: ffcc23a0d7bd9ecf624ee81f6e46eadd02fc1bf9111e8bd15648590e9f435a7872e6791f2197d024f680628bdf9de5f6e602c5b13f156984130b2865ad381d34
|
|
@@ -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
|
|
|
@@ -15,6 +15,9 @@ export const JOB_INVOCATION_API_REQUEST_KEY = 'OPENSCAP_REX_JOB_INVOCATIONS';
|
|
|
15
15
|
export const SNIPPET_SH = 'urn:xccdf:fix:script:sh';
|
|
16
16
|
export const SNIPPET_ANSIBLE = 'urn:xccdf:fix:script:ansible';
|
|
17
17
|
|
|
18
|
+
export const TOOLTIP_COPIED_EXIT_DELAY_MS = 1500;
|
|
19
|
+
export const TOOLTIP_DEFAULT_EXIT_DELAY_MS = 600;
|
|
20
|
+
|
|
18
21
|
export const WIZARD_TITLES = {
|
|
19
22
|
snippetSelect: __('Select snippet'),
|
|
20
23
|
reviewHosts: __('Review hosts'),
|
|
@@ -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
|
};
|
data/webpack/global_index.js
CHANGED
|
@@ -2,9 +2,11 @@ import React from 'react';
|
|
|
2
2
|
import { addGlobalFill } from 'foremanReact/components/common/Fill/GlobalFill';
|
|
3
3
|
import HostKebabItems from './components/HostExtentions/HostKebabItems';
|
|
4
4
|
|
|
5
|
+
const OPENSCAP_KEBAB_WEIGHT = 400;
|
|
6
|
+
|
|
5
7
|
addGlobalFill(
|
|
6
8
|
'host-details-kebab',
|
|
7
9
|
`openscap-kebab-items`,
|
|
8
10
|
<HostKebabItems key="openscap-host-kebab" />,
|
|
9
|
-
|
|
11
|
+
OPENSCAP_KEBAB_WEIGHT
|
|
10
12
|
);
|
|
@@ -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.
|
|
4
|
+
version: 13.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- slukasik@redhat.com
|
|
@@ -362,20 +362,14 @@ files:
|
|
|
362
362
|
- test/unit/services/lookup_key_overrider_test.rb
|
|
363
363
|
- test/unit/services/report_dashboard/data_test.rb
|
|
364
364
|
- test/unit/tailoring_file_test.rb
|
|
365
|
-
- webpack/components/ConfirmModal.js
|
|
366
|
-
- webpack/components/ConfirmModal.scss
|
|
367
365
|
- webpack/components/EmptyState.js
|
|
368
366
|
- webpack/components/HostExtentions/HostKebabItems.js
|
|
369
|
-
- webpack/components/IndexLayout.js
|
|
370
367
|
- webpack/components/IndexLayout.scss
|
|
371
|
-
- webpack/components/IndexTable/IndexTableHelper.js
|
|
372
|
-
- webpack/components/IndexTable/index.js
|
|
373
368
|
- webpack/components/LineChart/LineChart.fixtures.js
|
|
374
369
|
- webpack/components/LineChart/LineChart.scss
|
|
375
370
|
- webpack/components/LineChart/LineChart.test.js
|
|
376
371
|
- webpack/components/LineChart/LineChartHelpers.js
|
|
377
372
|
- webpack/components/LineChart/index.js
|
|
378
|
-
- webpack/components/LinkButton.js
|
|
379
373
|
- webpack/components/OpenscapRemediationWizard/Footer.js
|
|
380
374
|
- webpack/components/OpenscapRemediationWizard/OpenscapRemediationSelectors.js
|
|
381
375
|
- webpack/components/OpenscapRemediationWizard/OpenscapRemediationWizardContext.js
|
|
@@ -399,18 +393,9 @@ files:
|
|
|
399
393
|
- webpack/components/RuleSeverity/i_severity-med.svg
|
|
400
394
|
- webpack/components/RuleSeverity/i_unknown.svg
|
|
401
395
|
- webpack/components/RuleSeverity/index.js
|
|
402
|
-
- webpack/components/withDeleteModal.js
|
|
403
|
-
- webpack/components/withLoading.js
|
|
404
396
|
- 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
397
|
- webpack/index.js
|
|
413
|
-
- webpack/
|
|
398
|
+
- webpack/test_setup.js
|
|
414
399
|
homepage: https://github.com/theforeman/foreman_openscap
|
|
415
400
|
licenses:
|
|
416
401
|
- GPL-3.0
|
|
@@ -429,7 +414,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
429
414
|
- !ruby/object:Gem::Version
|
|
430
415
|
version: '0'
|
|
431
416
|
requirements: []
|
|
432
|
-
rubygems_version: 4.0.
|
|
417
|
+
rubygems_version: 4.0.16
|
|
433
418
|
specification_version: 4
|
|
434
419
|
summary: Foreman plug-in for displaying OpenSCAP audit reports
|
|
435
420
|
test_files:
|
|
@@ -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,44 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import PropTypes from 'prop-types';
|
|
3
|
-
import { Helmet } from 'react-helmet';
|
|
4
|
-
import ToastsList from 'foremanReact/components/ToastsList';
|
|
5
|
-
import {
|
|
6
|
-
Grid,
|
|
7
|
-
GridItem,
|
|
8
|
-
TextContent,
|
|
9
|
-
Text,
|
|
10
|
-
TextVariants,
|
|
11
|
-
} from '@patternfly/react-core';
|
|
12
|
-
|
|
13
|
-
import './IndexLayout.scss';
|
|
14
|
-
|
|
15
|
-
const IndexLayout = ({ pageTitle, children, contentWidthSpan }) => (
|
|
16
|
-
<React.Fragment>
|
|
17
|
-
<Helmet>
|
|
18
|
-
<title>{pageTitle}</title>
|
|
19
|
-
</Helmet>
|
|
20
|
-
<ToastsList />
|
|
21
|
-
<Grid className="scap-page-grid">
|
|
22
|
-
<GridItem span={12} className="pf-v5-u-pb-xl">
|
|
23
|
-
<TextContent>
|
|
24
|
-
<Text ouiaId="oscap-index-title" component={TextVariants.h1}>
|
|
25
|
-
{pageTitle}
|
|
26
|
-
</Text>
|
|
27
|
-
</TextContent>
|
|
28
|
-
</GridItem>
|
|
29
|
-
<GridItem span={contentWidthSpan}>{children}</GridItem>
|
|
30
|
-
</Grid>
|
|
31
|
-
</React.Fragment>
|
|
32
|
-
);
|
|
33
|
-
|
|
34
|
-
IndexLayout.propTypes = {
|
|
35
|
-
pageTitle: PropTypes.string.isRequired,
|
|
36
|
-
children: PropTypes.oneOfType([PropTypes.node, PropTypes.object]).isRequired,
|
|
37
|
-
contentWidthSpan: PropTypes.number,
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
IndexLayout.defaultProps = {
|
|
41
|
-
contentWidthSpan: 12,
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
export default IndexLayout;
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import PropTypes from 'prop-types';
|
|
3
|
-
import {
|
|
4
|
-
Table,
|
|
5
|
-
TableHeader,
|
|
6
|
-
TableBody,
|
|
7
|
-
} from '@patternfly/react-table/deprecated';
|
|
8
|
-
import { Flex, FlexItem } from '@patternfly/react-core';
|
|
9
|
-
import Pagination from 'foremanReact/components/Pagination';
|
|
10
|
-
import { refreshPage } from './IndexTableHelper';
|
|
11
|
-
|
|
12
|
-
const IndexTable = ({
|
|
13
|
-
history,
|
|
14
|
-
pagination,
|
|
15
|
-
totalCount,
|
|
16
|
-
toolbarBtns,
|
|
17
|
-
ariaTableLabel,
|
|
18
|
-
ouiaTableId,
|
|
19
|
-
columns,
|
|
20
|
-
...rest
|
|
21
|
-
}) => {
|
|
22
|
-
const handlePerPageSelected = perPage => {
|
|
23
|
-
refreshPage(history, { page: 1, perPage });
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
const handlePageSelected = page => {
|
|
27
|
-
refreshPage(history, { ...pagination, page });
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
return (
|
|
31
|
-
<React.Fragment>
|
|
32
|
-
<Flex className="pf-v5-u-pt-md">
|
|
33
|
-
<FlexItem>{toolbarBtns}</FlexItem>
|
|
34
|
-
<FlexItem align={{ default: 'alignRight' }}>
|
|
35
|
-
<Pagination
|
|
36
|
-
itemCount={totalCount}
|
|
37
|
-
page={pagination.page}
|
|
38
|
-
perPage={pagination.perPage}
|
|
39
|
-
onSetPage={handlePageSelected}
|
|
40
|
-
onPerPageSelect={handlePerPageSelected}
|
|
41
|
-
variant="top"
|
|
42
|
-
/>
|
|
43
|
-
</FlexItem>
|
|
44
|
-
</Flex>
|
|
45
|
-
<Table
|
|
46
|
-
ouiaId={ouiaTableId}
|
|
47
|
-
aria-label={ariaTableLabel}
|
|
48
|
-
cells={columns}
|
|
49
|
-
{...rest}
|
|
50
|
-
variant="compact"
|
|
51
|
-
>
|
|
52
|
-
<TableHeader />
|
|
53
|
-
<TableBody />
|
|
54
|
-
</Table>
|
|
55
|
-
</React.Fragment>
|
|
56
|
-
);
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
IndexTable.propTypes = {
|
|
60
|
-
history: PropTypes.object.isRequired,
|
|
61
|
-
pagination: PropTypes.object.isRequired,
|
|
62
|
-
toolbarBtns: PropTypes.node,
|
|
63
|
-
totalCount: PropTypes.number.isRequired,
|
|
64
|
-
ariaTableLabel: PropTypes.string.isRequired,
|
|
65
|
-
ouiaTableId: PropTypes.string.isRequired,
|
|
66
|
-
columns: PropTypes.array.isRequired,
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
IndexTable.defaultProps = {
|
|
70
|
-
toolbarBtns: null,
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
export default IndexTable;
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { Link } from 'react-router-dom';
|
|
3
|
-
import { Button } from '@patternfly/react-core';
|
|
4
|
-
import PropTypes from 'prop-types';
|
|
5
|
-
|
|
6
|
-
const LinkButton = ({
|
|
7
|
-
path,
|
|
8
|
-
btnVariant,
|
|
9
|
-
btnText,
|
|
10
|
-
isDisabled,
|
|
11
|
-
btnAriaLabel,
|
|
12
|
-
ouiaId,
|
|
13
|
-
}) => (
|
|
14
|
-
<Link to={path}>
|
|
15
|
-
<Button
|
|
16
|
-
ouiaId={ouiaId}
|
|
17
|
-
variant={btnVariant}
|
|
18
|
-
isDisabled={isDisabled}
|
|
19
|
-
aria-label={btnAriaLabel}
|
|
20
|
-
>
|
|
21
|
-
{btnText}
|
|
22
|
-
</Button>
|
|
23
|
-
</Link>
|
|
24
|
-
);
|
|
25
|
-
|
|
26
|
-
LinkButton.propTypes = {
|
|
27
|
-
path: PropTypes.string.isRequired,
|
|
28
|
-
btnText: PropTypes.string.isRequired,
|
|
29
|
-
btnVariant: PropTypes.string,
|
|
30
|
-
isDisabled: PropTypes.bool,
|
|
31
|
-
btnAriaLabel: PropTypes.string,
|
|
32
|
-
ouiaId: PropTypes.string,
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
LinkButton.defaultProps = {
|
|
36
|
-
btnVariant: 'primary',
|
|
37
|
-
isDisabled: false,
|
|
38
|
-
btnAriaLabel: null,
|
|
39
|
-
ouiaId: 'oscap-link-button',
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
export default LinkButton;
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
2
|
-
import PropTypes from 'prop-types';
|
|
3
|
-
import { translate as __, sprintf } from 'foremanReact/common/I18n';
|
|
4
|
-
import ConfirmModal from './ConfirmModal';
|
|
5
|
-
|
|
6
|
-
const withDelete = Component => {
|
|
7
|
-
const Subcomponent = ({
|
|
8
|
-
confirmDeleteTitle,
|
|
9
|
-
submitDelete,
|
|
10
|
-
prepareMutation,
|
|
11
|
-
...rest
|
|
12
|
-
}) => {
|
|
13
|
-
const [toDelete, setToDelete] = useState(null);
|
|
14
|
-
|
|
15
|
-
const toggleModal = (item = null) => {
|
|
16
|
-
setToDelete(item);
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
return (
|
|
20
|
-
<React.Fragment>
|
|
21
|
-
<Component {...rest} toggleModal={toggleModal} />
|
|
22
|
-
<ConfirmModal
|
|
23
|
-
title={confirmDeleteTitle}
|
|
24
|
-
text={
|
|
25
|
-
toDelete
|
|
26
|
-
? sprintf(
|
|
27
|
-
__('Are you sure you want to delete %s?'),
|
|
28
|
-
toDelete.name
|
|
29
|
-
)
|
|
30
|
-
: ''
|
|
31
|
-
}
|
|
32
|
-
onClose={toggleModal}
|
|
33
|
-
isOpen={!!toDelete}
|
|
34
|
-
onConfirm={submitDelete}
|
|
35
|
-
prepareMutation={() => prepareMutation(toggleModal)}
|
|
36
|
-
record={toDelete}
|
|
37
|
-
/>
|
|
38
|
-
</React.Fragment>
|
|
39
|
-
);
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
Subcomponent.propTypes = {
|
|
43
|
-
confirmDeleteTitle: PropTypes.string.isRequired,
|
|
44
|
-
submitDelete: PropTypes.func.isRequired,
|
|
45
|
-
prepareMutation: PropTypes.func.isRequired,
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
return Subcomponent;
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
export default withDelete;
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import React, { useEffect } from 'react';
|
|
2
|
-
import PropTypes from 'prop-types';
|
|
3
|
-
import { translate as __ } from 'foremanReact/common/I18n';
|
|
4
|
-
import Loading from 'foremanReact/components/Loading';
|
|
5
|
-
import {
|
|
6
|
-
permissionCheck,
|
|
7
|
-
permissionDeniedMsg,
|
|
8
|
-
} from '../helpers/permissionsHelper';
|
|
9
|
-
|
|
10
|
-
import EmptyState from './EmptyState';
|
|
11
|
-
|
|
12
|
-
const errorStateTitle = __('Error!');
|
|
13
|
-
|
|
14
|
-
const pluckData = (data, path) => {
|
|
15
|
-
const split = path.split('.');
|
|
16
|
-
return split.reduce((memo, item) => {
|
|
17
|
-
if (item) {
|
|
18
|
-
return memo[item];
|
|
19
|
-
}
|
|
20
|
-
throw new Error('Unexpected empty segment in response data path');
|
|
21
|
-
}, data);
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
const withLoading = Component => {
|
|
25
|
-
const Subcomponent = ({
|
|
26
|
-
fetchFn,
|
|
27
|
-
resultPath,
|
|
28
|
-
renameData,
|
|
29
|
-
emptyStateTitle,
|
|
30
|
-
emptyStateBody,
|
|
31
|
-
permissions,
|
|
32
|
-
primaryButton,
|
|
33
|
-
shouldRefetch,
|
|
34
|
-
...rest
|
|
35
|
-
}) => {
|
|
36
|
-
const { loading, error, data, refetch } = fetchFn(rest);
|
|
37
|
-
|
|
38
|
-
useEffect(() => {
|
|
39
|
-
if (shouldRefetch) {
|
|
40
|
-
refetch();
|
|
41
|
-
}
|
|
42
|
-
}, [shouldRefetch, refetch]);
|
|
43
|
-
|
|
44
|
-
if (loading) {
|
|
45
|
-
return <Loading />;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (error) {
|
|
49
|
-
return (
|
|
50
|
-
<EmptyState
|
|
51
|
-
error={error}
|
|
52
|
-
title={errorStateTitle}
|
|
53
|
-
body={error.message}
|
|
54
|
-
/>
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const check = permissionCheck(data.currentUser, permissions);
|
|
59
|
-
|
|
60
|
-
if (!check.allowed) {
|
|
61
|
-
return (
|
|
62
|
-
<EmptyState
|
|
63
|
-
lock
|
|
64
|
-
title={__('Permission denied')}
|
|
65
|
-
body={permissionDeniedMsg(check.permissions.map(item => item.name))}
|
|
66
|
-
/>
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const result = pluckData(data, resultPath);
|
|
71
|
-
|
|
72
|
-
if ((Array.isArray(result) && result.length === 0) || !result) {
|
|
73
|
-
return (
|
|
74
|
-
<EmptyState
|
|
75
|
-
title={emptyStateTitle}
|
|
76
|
-
body={emptyStateBody}
|
|
77
|
-
primaryButton={primaryButton}
|
|
78
|
-
/>
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
return <Component {...rest} {...renameData(data)} />;
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
Subcomponent.propTypes = {
|
|
86
|
-
fetchFn: PropTypes.func.isRequired,
|
|
87
|
-
resultPath: PropTypes.string.isRequired,
|
|
88
|
-
renameData: PropTypes.func,
|
|
89
|
-
emptyStateTitle: PropTypes.string.isRequired,
|
|
90
|
-
emptyStateBody: PropTypes.string,
|
|
91
|
-
permissions: PropTypes.array,
|
|
92
|
-
primaryButton: PropTypes.node,
|
|
93
|
-
shouldRefetch: PropTypes.bool,
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
Subcomponent.defaultProps = {
|
|
97
|
-
renameData: data => data,
|
|
98
|
-
permissions: [],
|
|
99
|
-
primaryButton: null,
|
|
100
|
-
shouldRefetch: false,
|
|
101
|
-
emptyStateBody: '',
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
return Subcomponent;
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
export default withLoading;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export const last = array => array[array.length - 1];
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { last } from './commonHelper';
|
|
2
|
-
|
|
3
|
-
const idSeparator = '-';
|
|
4
|
-
const versionSeparator = ':';
|
|
5
|
-
const defaultVersion = '01';
|
|
6
|
-
|
|
7
|
-
export const decodeModelId = model => decodeId(model.id);
|
|
8
|
-
|
|
9
|
-
export const decodeId = globalId => {
|
|
10
|
-
const split = atob(globalId).split(idSeparator);
|
|
11
|
-
return parseInt(last(split), 10);
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export const encodeId = (typename, id) =>
|
|
15
|
-
btoa([defaultVersion, versionSeparator, typename, idSeparator, id].join(''));
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import { useMutation } from '@apollo/client';
|
|
2
|
-
import { translate as __, sprintf } from 'foremanReact/common/I18n';
|
|
3
|
-
|
|
4
|
-
import { useCurrentPagination, pageToVars } from './pageParamsHelper';
|
|
5
|
-
|
|
6
|
-
const formatError = (error, name) =>
|
|
7
|
-
sprintf(__('There was a following error when deleting %(name)s: %(error)s'), {
|
|
8
|
-
name,
|
|
9
|
-
error,
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
const joinErrors = errors => errors.map(err => err.message).join(', ');
|
|
13
|
-
|
|
14
|
-
const onError = (showToast, resourceName) => error => {
|
|
15
|
-
showToast({ type: 'error', message: formatError(error, resourceName) });
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
const onCompleted = (
|
|
19
|
-
toggleModal,
|
|
20
|
-
showToast,
|
|
21
|
-
mutationName,
|
|
22
|
-
successMsg,
|
|
23
|
-
resourceName
|
|
24
|
-
) => data => {
|
|
25
|
-
toggleModal();
|
|
26
|
-
const { errors } = data[mutationName];
|
|
27
|
-
if (Array.isArray(errors) && errors.length > 0) {
|
|
28
|
-
showToast({
|
|
29
|
-
type: 'error',
|
|
30
|
-
message: formatError(joinErrors(errors), resourceName),
|
|
31
|
-
});
|
|
32
|
-
} else {
|
|
33
|
-
showToast({
|
|
34
|
-
type: 'success',
|
|
35
|
-
message: successMsg,
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
export const prepareMutation = (
|
|
41
|
-
history,
|
|
42
|
-
showToast,
|
|
43
|
-
refetchQuery,
|
|
44
|
-
mutationName,
|
|
45
|
-
successMsg,
|
|
46
|
-
mutation,
|
|
47
|
-
resourceName
|
|
48
|
-
) => toggleModal => {
|
|
49
|
-
const pagination = pageToVars(useCurrentPagination(history));
|
|
50
|
-
|
|
51
|
-
const options = {
|
|
52
|
-
refetchQueries: [{ query: refetchQuery, variables: pagination }],
|
|
53
|
-
onCompleted: onCompleted(
|
|
54
|
-
toggleModal,
|
|
55
|
-
showToast,
|
|
56
|
-
mutationName,
|
|
57
|
-
successMsg,
|
|
58
|
-
resourceName
|
|
59
|
-
),
|
|
60
|
-
onError: onError(showToast, resourceName),
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
return useMutation(mutation, options);
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
export const submitDelete = (mutation, id) => {
|
|
67
|
-
mutation({ variables: { id } });
|
|
68
|
-
};
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import URI from 'urijs';
|
|
2
|
-
import { useForemanSettings } from 'foremanReact/Root/Context/ForemanContext';
|
|
3
|
-
|
|
4
|
-
const parsePageParams = history => URI.parseQuery(history.location.search);
|
|
5
|
-
|
|
6
|
-
export const addSearch = (basePath, params) => {
|
|
7
|
-
let stringyfied = '';
|
|
8
|
-
if (Object.keys(params).length > 0) {
|
|
9
|
-
stringyfied = `?${URI.buildQuery(params)}`;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
return `${basePath}${stringyfied}`;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export const useCurrentPagination = history => {
|
|
16
|
-
const pageParams = parsePageParams(history);
|
|
17
|
-
const uiSettings = useForemanSettings();
|
|
18
|
-
|
|
19
|
-
return {
|
|
20
|
-
page: parseInt(pageParams.page, 10) || 1,
|
|
21
|
-
perPage: parseInt(pageParams.perPage, 10) || uiSettings.perPage,
|
|
22
|
-
};
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
export const pageToVars = pagination => ({
|
|
26
|
-
first: pagination.page * pagination.perPage,
|
|
27
|
-
last: pagination.perPage,
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
export const useParamsToVars = history =>
|
|
31
|
-
pageToVars(useCurrentPagination(history));
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { translate as __, sprintf } from 'foremanReact/common/I18n';
|
|
2
|
-
|
|
3
|
-
export const permissionCheck = (user, permissionsRequired) => {
|
|
4
|
-
if (permissionsRequired.length === 0) {
|
|
5
|
-
return { allowed: true };
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
if (!user) {
|
|
9
|
-
throw new Error(
|
|
10
|
-
'No user data when loading the page - cannot determine if current user is allowed to view the page.'
|
|
11
|
-
);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
if (user.admin) {
|
|
15
|
-
return { allowed: true };
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const permList = permissionsRequired.reduce((memo, item) => {
|
|
19
|
-
const found = user.permissions.nodes.find(
|
|
20
|
-
permission => permission.name === item
|
|
21
|
-
);
|
|
22
|
-
memo.push({ name: item, present: !!found });
|
|
23
|
-
return memo;
|
|
24
|
-
}, []);
|
|
25
|
-
|
|
26
|
-
if (permList.reduce((memo, item) => memo && item.present, true)) {
|
|
27
|
-
return { allowed: true, permissions: permList };
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return { allowed: false, permissions: permList };
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
export const permissionDeniedMsg = permissions => {
|
|
34
|
-
let msg = __('You are not authorized to view the page. ');
|
|
35
|
-
if (permissions?.length > 0) {
|
|
36
|
-
msg += sprintf(
|
|
37
|
-
__('Request the following permissions from administrator: %s.'),
|
|
38
|
-
permissions.join(', ')
|
|
39
|
-
);
|
|
40
|
-
}
|
|
41
|
-
return msg;
|
|
42
|
-
};
|
data/webpack/testHelper.js
DELETED
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
2
|
-
import { Provider } from 'react-redux';
|
|
3
|
-
import store from 'foremanReact/redux';
|
|
4
|
-
import { MockedProvider } from '@apollo/react-testing';
|
|
5
|
-
import { MemoryRouter } from 'react-router-dom';
|
|
6
|
-
import { getForemanContext } from 'foremanReact/Root/Context/ForemanContext';
|
|
7
|
-
import { waitFor } from '@testing-library/react';
|
|
8
|
-
|
|
9
|
-
export const withRedux = Component => props => (
|
|
10
|
-
<Provider store={store}>
|
|
11
|
-
<Component {...props} />
|
|
12
|
-
</Provider>
|
|
13
|
-
);
|
|
14
|
-
|
|
15
|
-
export const withRouter = Component => props => (
|
|
16
|
-
<MemoryRouter>
|
|
17
|
-
<Component {...props} />
|
|
18
|
-
</MemoryRouter>
|
|
19
|
-
);
|
|
20
|
-
|
|
21
|
-
export const withMockedProvider = Component => props => {
|
|
22
|
-
// eslint-disable-next-line react/prop-types
|
|
23
|
-
const { mocks, ...rest } = props;
|
|
24
|
-
|
|
25
|
-
const [context, setContext] = useState({
|
|
26
|
-
metadata: {
|
|
27
|
-
UISettings: {
|
|
28
|
-
perPage: 20,
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
const contextData = { context, setContext };
|
|
34
|
-
const ForemanContext = getForemanContext(contextData);
|
|
35
|
-
return (
|
|
36
|
-
<ForemanContext.Provider value={contextData}>
|
|
37
|
-
<MockedProvider mocks={mocks}>
|
|
38
|
-
<Component {...rest} />
|
|
39
|
-
</MockedProvider>
|
|
40
|
-
</ForemanContext.Provider>
|
|
41
|
-
);
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
// use to resolve async mock requests for apollo MockedProvider
|
|
45
|
-
export const tick = () => new Promise(resolve => setTimeout(resolve, 0));
|
|
46
|
-
|
|
47
|
-
export const wait = async (tickCount = 1) => {
|
|
48
|
-
for (let i = 1; i < tickCount; i++) {
|
|
49
|
-
// eslint-disable-next-line no-await-in-loop
|
|
50
|
-
await waitFor(tick);
|
|
51
|
-
}
|
|
52
|
-
return waitFor(tick);
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
export const historyMock = {
|
|
56
|
-
location: {
|
|
57
|
-
search: '',
|
|
58
|
-
},
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
export const admin = {
|
|
62
|
-
__typename: 'User',
|
|
63
|
-
id: 'MDE6VXNlci00',
|
|
64
|
-
login: 'admin',
|
|
65
|
-
admin: true,
|
|
66
|
-
permissions: {
|
|
67
|
-
nodes: [],
|
|
68
|
-
},
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
export const userFactory = (login, permissions = []) => ({
|
|
72
|
-
__typename: 'User',
|
|
73
|
-
id: 'MDE6VXNlci01',
|
|
74
|
-
login,
|
|
75
|
-
admin: false,
|
|
76
|
-
permissions: {
|
|
77
|
-
nodes: permissions,
|
|
78
|
-
},
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
export const intruder = userFactory('intruder', [
|
|
82
|
-
{
|
|
83
|
-
__typename: 'Permission',
|
|
84
|
-
id: 'MDE6UGVybWlzc2lvbi0x',
|
|
85
|
-
name: 'view_architectures',
|
|
86
|
-
},
|
|
87
|
-
]);
|
|
88
|
-
|
|
89
|
-
export const viewer = userFactory('viewer', [
|
|
90
|
-
{
|
|
91
|
-
__typename: 'Permission',
|
|
92
|
-
id: 'MDE6UGVybWlzc2lvbi0yOTY=',
|
|
93
|
-
name: 'view_oval_contents',
|
|
94
|
-
},
|
|
95
|
-
{
|
|
96
|
-
__typename: 'Permission',
|
|
97
|
-
id: 'MDE6UGVybWlzc2lvbi0yNzU=',
|
|
98
|
-
name: 'view_oval_policies',
|
|
99
|
-
},
|
|
100
|
-
]);
|
|
101
|
-
|
|
102
|
-
export const mockFactory = (resultName, query) => (
|
|
103
|
-
variables,
|
|
104
|
-
modelResults,
|
|
105
|
-
{ errors = [], currentUser = null } = {}
|
|
106
|
-
) => {
|
|
107
|
-
const mock = {
|
|
108
|
-
request: {
|
|
109
|
-
query,
|
|
110
|
-
variables,
|
|
111
|
-
},
|
|
112
|
-
result: {
|
|
113
|
-
data: {
|
|
114
|
-
[resultName]: modelResults,
|
|
115
|
-
},
|
|
116
|
-
},
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
if (errors.length !== 0) {
|
|
120
|
-
mock.result.errors = errors;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
if (currentUser) {
|
|
124
|
-
mock.result.data.currentUser = currentUser;
|
|
125
|
-
}
|
|
126
|
-
return [mock];
|
|
127
|
-
};
|