foreman_openscap 5.0.0 → 5.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/graphql/mutations/oval_contents/delete.rb +9 -0
- data/app/graphql/mutations/oval_policies/delete.rb +9 -0
- data/app/graphql/mutations/oval_policies/update.rb +15 -0
- data/app/graphql/types/oval_check.rb +11 -0
- data/app/graphql/types/oval_content.rb +2 -0
- data/app/graphql/types/oval_policy.rb +3 -0
- data/app/models/concerns/foreman_openscap/host_extensions.rb +0 -6
- data/app/models/concerns/foreman_openscap/oval_facet_hostgroup_extensions.rb +15 -0
- data/app/models/foreman_openscap/oval_content.rb +2 -0
- data/app/services/foreman_openscap/oval/configure.rb +1 -1
- data/app/services/foreman_openscap/oval/setup.rb +5 -5
- data/app/services/foreman_openscap/oval/setup_check.rb +5 -2
- data/db/migrate/20210819143316_drop_unused_tables.rb +6 -0
- data/lib/foreman_openscap/engine.rb +6 -1
- data/lib/foreman_openscap/version.rb +1 -1
- data/package.json +3 -6
- data/test/graphql/mutations/oval_policies/delete_mutation_test.rb +63 -0
- data/test/graphql/queries/oval_content_query_test.rb +29 -0
- data/test/unit/services/hostgroup_overrider_test.rb +1 -1
- data/test/unit/services/oval/setup_check_test.rb +37 -0
- data/webpack/components/ConfirmModal.js +63 -0
- data/webpack/components/ConfirmModal.scss +3 -0
- data/webpack/components/EditableInput.js +157 -0
- data/webpack/components/EditableInput.scss +3 -0
- data/webpack/components/EmptyState.js +4 -1
- data/webpack/components/IndexLayout.js +11 -4
- data/webpack/components/IndexTable/index.js +17 -17
- data/webpack/components/LinkButton.js +26 -0
- data/webpack/components/withDeleteModal.js +51 -0
- data/webpack/components/withLoading.js +21 -3
- data/webpack/graphql/mutations/deleteOvalContent.gql +9 -0
- data/webpack/graphql/mutations/deleteOvalPolicy.gql +9 -0
- data/webpack/graphql/mutations/updateOvalPolicy.gql +14 -0
- data/webpack/graphql/queries/hostgroups.gql +14 -0
- data/webpack/graphql/queries/ovalContent.gql +8 -0
- data/webpack/graphql/queries/ovalContents.gql +3 -0
- data/webpack/graphql/queries/ovalPolicies.gql +3 -0
- data/webpack/helpers/formFieldsHelper.js +63 -0
- data/webpack/helpers/mutationHelper.js +68 -0
- data/webpack/helpers/pathsHelper.js +5 -0
- data/webpack/helpers/toastHelper.js +3 -0
- data/webpack/routes/OvalContents/OvalContentsIndex/OvalContentsIndex.js +25 -0
- data/webpack/routes/OvalContents/OvalContentsIndex/OvalContentsTable.js +41 -4
- data/webpack/routes/OvalContents/OvalContentsIndex/__tests__/OvalContentsDestroy.fixtures.js +105 -0
- data/webpack/routes/OvalContents/OvalContentsIndex/__tests__/OvalContentsDestroy.test.js +124 -0
- data/webpack/routes/OvalContents/OvalContentsIndex/__tests__/OvalContentsIndex.fixtures.js +61 -59
- data/webpack/routes/OvalContents/OvalContentsIndex/__tests__/OvalContentsIndex.test.js +29 -8
- data/webpack/routes/OvalContents/OvalContentsIndex/index.js +7 -1
- data/webpack/routes/OvalContents/OvalContentsNew/OvalContentsNew.js +138 -0
- data/webpack/routes/OvalContents/OvalContentsNew/OvalContentsNew.scss +3 -0
- data/webpack/routes/OvalContents/OvalContentsNew/OvalContentsNewHelper.js +73 -0
- data/webpack/routes/OvalContents/OvalContentsNew/__tests__/OvalContentsNew.test.js +104 -0
- data/webpack/routes/OvalContents/OvalContentsNew/index.js +13 -0
- data/webpack/routes/OvalContents/OvalContentsShow/OvalContentsShow.js +62 -0
- data/webpack/routes/OvalContents/OvalContentsShow/OvalContentsShow.test.js +45 -0
- data/webpack/routes/OvalContents/OvalContentsShow/OvalContentsShowHelper.js +0 -0
- data/webpack/routes/OvalContents/OvalContentsShow/index.js +35 -0
- data/webpack/routes/OvalPolicies/OvalPoliciesIndex/OvalPoliciesIndex.js +17 -2
- data/webpack/routes/OvalPolicies/OvalPoliciesIndex/OvalPoliciesTable.js +16 -3
- data/webpack/routes/OvalPolicies/OvalPoliciesIndex/__tests__/OvalPoliciesDestroy.fixtures.js +101 -0
- data/webpack/routes/OvalPolicies/OvalPoliciesIndex/__tests__/OvalPoliciesDestroy.test.js +117 -0
- data/webpack/routes/OvalPolicies/OvalPoliciesIndex/__tests__/OvalPoliciesIndex.fixtures.js +57 -41
- data/webpack/routes/OvalPolicies/OvalPoliciesIndex/__tests__/OvalPoliciesIndex.test.js +14 -2
- data/webpack/routes/OvalPolicies/OvalPoliciesIndex/index.js +7 -1
- data/webpack/routes/OvalPolicies/OvalPoliciesShow/DetailsTab.js +85 -0
- data/webpack/routes/OvalPolicies/OvalPoliciesShow/HostgroupsTab.js +49 -0
- data/webpack/routes/OvalPolicies/OvalPoliciesShow/HostgroupsTable.js +38 -0
- data/webpack/routes/OvalPolicies/OvalPoliciesShow/OvalPoliciesShow.js +15 -11
- data/webpack/routes/OvalPolicies/OvalPoliciesShow/OvalPoliciesShowHelper.js +77 -0
- data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesEdit.fixtures.js +48 -0
- data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesEdit.test.js +175 -0
- data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesShow.fixtures.js +28 -1
- data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesShow.test.js +47 -4
- data/webpack/routes/OvalPolicies/OvalPoliciesShow/index.js +3 -0
- data/webpack/routes/routes.js +14 -0
- data/webpack/testHelper.js +9 -1
- metadata +46 -3
|
@@ -29,7 +29,7 @@ const EmptyStateIcon = ({ error, search, lock }) => {
|
|
|
29
29
|
return <PfEmptyStateIcon icon={CubeIcon} />;
|
|
30
30
|
};
|
|
31
31
|
|
|
32
|
-
const EmptyState = ({ title, body, error, search, lock }) => (
|
|
32
|
+
const EmptyState = ({ title, body, error, search, lock, primaryButton }) => (
|
|
33
33
|
<Bullseye>
|
|
34
34
|
<PfEmptyState variant={EmptyStateVariant.small}>
|
|
35
35
|
<EmptyStateIcon error={!!error} search={search} lock={lock} />
|
|
@@ -37,6 +37,7 @@ const EmptyState = ({ title, body, error, search, lock }) => (
|
|
|
37
37
|
{title}
|
|
38
38
|
</Title>
|
|
39
39
|
<EmptyStateBody>{body}</EmptyStateBody>
|
|
40
|
+
{primaryButton}
|
|
40
41
|
</PfEmptyState>
|
|
41
42
|
</Bullseye>
|
|
42
43
|
);
|
|
@@ -59,6 +60,7 @@ EmptyState.propTypes = {
|
|
|
59
60
|
error: PropTypes.oneOfType([PropTypes.shape({}), PropTypes.string]),
|
|
60
61
|
search: PropTypes.bool,
|
|
61
62
|
lock: PropTypes.bool,
|
|
63
|
+
primaryButton: PropTypes.node,
|
|
62
64
|
};
|
|
63
65
|
|
|
64
66
|
EmptyState.defaultProps = {
|
|
@@ -68,6 +70,7 @@ EmptyState.defaultProps = {
|
|
|
68
70
|
error: undefined,
|
|
69
71
|
search: false,
|
|
70
72
|
lock: false,
|
|
73
|
+
primaryButton: null,
|
|
71
74
|
};
|
|
72
75
|
|
|
73
76
|
export default EmptyState;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
3
|
import { Helmet } from 'react-helmet';
|
|
4
|
+
import ToastsList from 'foremanReact/components/ToastsList';
|
|
4
5
|
import {
|
|
5
6
|
Grid,
|
|
6
7
|
GridItem,
|
|
@@ -11,25 +12,31 @@ import {
|
|
|
11
12
|
|
|
12
13
|
import './IndexLayout.scss';
|
|
13
14
|
|
|
14
|
-
const IndexLayout = ({ pageTitle, children }) => (
|
|
15
|
+
const IndexLayout = ({ pageTitle, children, contentWidthSpan }) => (
|
|
15
16
|
<React.Fragment>
|
|
16
17
|
<Helmet>
|
|
17
18
|
<title>{pageTitle}</title>
|
|
18
19
|
</Helmet>
|
|
20
|
+
<ToastsList />
|
|
19
21
|
<Grid className="scap-page-grid">
|
|
20
|
-
<GridItem span={12}>
|
|
22
|
+
<GridItem span={12} className="pf-u-pb-xl">
|
|
21
23
|
<TextContent>
|
|
22
24
|
<Text component={TextVariants.h1}>{pageTitle}</Text>
|
|
23
25
|
</TextContent>
|
|
24
26
|
</GridItem>
|
|
25
|
-
<GridItem span={
|
|
27
|
+
<GridItem span={contentWidthSpan}>{children}</GridItem>
|
|
26
28
|
</Grid>
|
|
27
29
|
</React.Fragment>
|
|
28
30
|
);
|
|
29
31
|
|
|
30
32
|
IndexLayout.propTypes = {
|
|
31
33
|
pageTitle: PropTypes.string.isRequired,
|
|
32
|
-
children: PropTypes.object.isRequired,
|
|
34
|
+
children: PropTypes.oneOfType([PropTypes.node, PropTypes.object]).isRequired,
|
|
35
|
+
contentWidthSpan: PropTypes.number,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
IndexLayout.defaultProps = {
|
|
39
|
+
contentWidthSpan: 12,
|
|
33
40
|
};
|
|
34
41
|
|
|
35
42
|
export default IndexLayout;
|
|
@@ -6,13 +6,21 @@ import { usePaginationOptions } from 'foremanReact/components/Pagination/Paginat
|
|
|
6
6
|
|
|
7
7
|
import { preparePerPageOptions, refreshPage } from './IndexTableHelper';
|
|
8
8
|
|
|
9
|
-
const IndexTable =
|
|
9
|
+
const IndexTable = ({
|
|
10
|
+
history,
|
|
11
|
+
pagination,
|
|
12
|
+
totalCount,
|
|
13
|
+
toolbarBtns,
|
|
14
|
+
ariaTableLabel,
|
|
15
|
+
columns,
|
|
16
|
+
...rest
|
|
17
|
+
}) => {
|
|
10
18
|
const handlePerPageSelected = (event, perPage) => {
|
|
11
|
-
refreshPage(
|
|
19
|
+
refreshPage(history, { page: 1, perPage });
|
|
12
20
|
};
|
|
13
21
|
|
|
14
22
|
const handlePageSelected = (event, page) => {
|
|
15
|
-
refreshPage(
|
|
23
|
+
refreshPage(history, { ...pagination, page });
|
|
16
24
|
};
|
|
17
25
|
|
|
18
26
|
const perPageOptions = preparePerPageOptions(usePaginationOptions());
|
|
@@ -20,12 +28,12 @@ const IndexTable = props => {
|
|
|
20
28
|
return (
|
|
21
29
|
<React.Fragment>
|
|
22
30
|
<Flex className="pf-u-pt-md">
|
|
23
|
-
<FlexItem>{
|
|
31
|
+
<FlexItem>{toolbarBtns}</FlexItem>
|
|
24
32
|
<FlexItem align={{ default: 'alignRight' }}>
|
|
25
33
|
<Pagination
|
|
26
|
-
itemCount={
|
|
27
|
-
page={
|
|
28
|
-
perPage={
|
|
34
|
+
itemCount={totalCount}
|
|
35
|
+
page={pagination.page}
|
|
36
|
+
perPage={pagination.perPage}
|
|
29
37
|
onSetPage={handlePageSelected}
|
|
30
38
|
onPerPageSelect={handlePerPageSelected}
|
|
31
39
|
perPageOptions={perPageOptions}
|
|
@@ -33,12 +41,7 @@ const IndexTable = props => {
|
|
|
33
41
|
/>
|
|
34
42
|
</FlexItem>
|
|
35
43
|
</Flex>
|
|
36
|
-
<Table
|
|
37
|
-
aria-label={props.ariaTableLabel}
|
|
38
|
-
cells={props.columns}
|
|
39
|
-
rows={props.rows}
|
|
40
|
-
actions={props.actions}
|
|
41
|
-
>
|
|
44
|
+
<Table aria-label={ariaTableLabel} cells={columns} {...rest}>
|
|
42
45
|
<TableHeader />
|
|
43
46
|
<TableBody />
|
|
44
47
|
</Table>
|
|
@@ -49,17 +52,14 @@ const IndexTable = props => {
|
|
|
49
52
|
IndexTable.propTypes = {
|
|
50
53
|
history: PropTypes.object.isRequired,
|
|
51
54
|
pagination: PropTypes.object.isRequired,
|
|
52
|
-
toolbarBtns: PropTypes.
|
|
55
|
+
toolbarBtns: PropTypes.node,
|
|
53
56
|
totalCount: PropTypes.number.isRequired,
|
|
54
57
|
ariaTableLabel: PropTypes.string.isRequired,
|
|
55
58
|
columns: PropTypes.array.isRequired,
|
|
56
|
-
rows: PropTypes.array.isRequired,
|
|
57
|
-
actions: PropTypes.array,
|
|
58
59
|
};
|
|
59
60
|
|
|
60
61
|
IndexTable.defaultProps = {
|
|
61
62
|
toolbarBtns: [],
|
|
62
|
-
actions: [],
|
|
63
63
|
};
|
|
64
64
|
|
|
65
65
|
export default IndexTable;
|
|
@@ -0,0 +1,26 @@
|
|
|
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 = ({ path, btnVariant, btnText, isDisabled }) => (
|
|
7
|
+
<Link to={path}>
|
|
8
|
+
<Button variant={btnVariant} isDisabled={isDisabled}>
|
|
9
|
+
{btnText}
|
|
10
|
+
</Button>
|
|
11
|
+
</Link>
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
LinkButton.propTypes = {
|
|
15
|
+
path: PropTypes.string.isRequired,
|
|
16
|
+
btnText: PropTypes.string.isRequired,
|
|
17
|
+
btnVariant: PropTypes.string,
|
|
18
|
+
isDisabled: PropTypes.bool,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
LinkButton.defaultProps = {
|
|
22
|
+
btnVariant: 'primary',
|
|
23
|
+
isDisabled: false,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export default LinkButton;
|
|
@@ -0,0 +1,51 @@
|
|
|
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,4 +1,4 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useEffect } from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
3
|
import { translate as __ } from 'foremanReact/common/I18n';
|
|
4
4
|
import Loading from 'foremanReact/components/Loading';
|
|
@@ -29,9 +29,17 @@ const withLoading = Component => {
|
|
|
29
29
|
renameData,
|
|
30
30
|
emptyStateTitle,
|
|
31
31
|
permissions,
|
|
32
|
+
primaryButton,
|
|
33
|
+
shouldRefetch,
|
|
32
34
|
...rest
|
|
33
35
|
}) => {
|
|
34
|
-
const { loading, error, data } = fetchFn(rest);
|
|
36
|
+
const { loading, error, data, refetch } = fetchFn(rest);
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (shouldRefetch) {
|
|
40
|
+
refetch();
|
|
41
|
+
}
|
|
42
|
+
}, [shouldRefetch, refetch]);
|
|
35
43
|
|
|
36
44
|
if (loading) {
|
|
37
45
|
return <Loading />;
|
|
@@ -62,7 +70,13 @@ const withLoading = Component => {
|
|
|
62
70
|
const result = pluckData(data, resultPath);
|
|
63
71
|
|
|
64
72
|
if ((Array.isArray(result) && result.length === 0) || !result) {
|
|
65
|
-
return
|
|
73
|
+
return (
|
|
74
|
+
<EmptyState
|
|
75
|
+
title={emptyStateTitle}
|
|
76
|
+
body={emptyStateBody}
|
|
77
|
+
primaryButton={primaryButton}
|
|
78
|
+
/>
|
|
79
|
+
);
|
|
66
80
|
}
|
|
67
81
|
|
|
68
82
|
return <Component {...rest} {...renameData(data)} />;
|
|
@@ -74,11 +88,15 @@ const withLoading = Component => {
|
|
|
74
88
|
renameData: PropTypes.func,
|
|
75
89
|
emptyStateTitle: PropTypes.string.isRequired,
|
|
76
90
|
permissions: PropTypes.array,
|
|
91
|
+
primaryButton: PropTypes.node,
|
|
92
|
+
shouldRefetch: PropTypes.bool,
|
|
77
93
|
};
|
|
78
94
|
|
|
79
95
|
Subcomponent.defaultProps = {
|
|
80
96
|
renameData: data => data,
|
|
81
97
|
permissions: [],
|
|
98
|
+
primaryButton: null,
|
|
99
|
+
shouldRefetch: false,
|
|
82
100
|
};
|
|
83
101
|
|
|
84
102
|
return Subcomponent;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
mutation UpdateOvalPolicy($id: ID!, $name: String, $description: String, $cronLine: String) {
|
|
2
|
+
updateOvalPolicy(input:{ id:$id, name:$name, description: $description, cronLine: $cronLine }) {
|
|
3
|
+
ovalPolicy {
|
|
4
|
+
id
|
|
5
|
+
name
|
|
6
|
+
description
|
|
7
|
+
cronLine
|
|
8
|
+
}
|
|
9
|
+
errors {
|
|
10
|
+
path
|
|
11
|
+
message
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
|
|
4
|
+
import { FormGroup, TextInput } from '@patternfly/react-core';
|
|
5
|
+
import { ExclamationCircleIcon } from '@patternfly/react-icons';
|
|
6
|
+
|
|
7
|
+
const wrapFieldProps = fieldProps => {
|
|
8
|
+
const { onChange } = fieldProps;
|
|
9
|
+
// modify onChange args to correctly wire formik with pf4 input handlers
|
|
10
|
+
const wrappedOnChange = (value, event) => {
|
|
11
|
+
onChange(event);
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
return { ...fieldProps, onChange: wrappedOnChange };
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const shouldValidate = (form, fieldName) => {
|
|
18
|
+
if (form.touched[fieldName]) {
|
|
19
|
+
return form.errors[fieldName] ? 'error' : 'success';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return 'noval';
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const fieldWithHandlers = Component => {
|
|
26
|
+
const Subcomponent = ({ label, form, field, isRequired, ...rest }) => {
|
|
27
|
+
const fieldProps = wrapFieldProps(field);
|
|
28
|
+
const valid = shouldValidate(form, field.name);
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<FormGroup
|
|
32
|
+
label={label}
|
|
33
|
+
isRequired={isRequired}
|
|
34
|
+
helperTextInvalid={form.errors[field.name]}
|
|
35
|
+
helperTextInvalidIcon={<ExclamationCircleIcon />}
|
|
36
|
+
validated={valid}
|
|
37
|
+
>
|
|
38
|
+
<Component
|
|
39
|
+
aria-label={fieldProps.name}
|
|
40
|
+
{...fieldProps}
|
|
41
|
+
{...rest}
|
|
42
|
+
validated={valid}
|
|
43
|
+
isDisabled={form.isSubmitting}
|
|
44
|
+
/>
|
|
45
|
+
</FormGroup>
|
|
46
|
+
);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
Subcomponent.propTypes = {
|
|
50
|
+
form: PropTypes.object.isRequired,
|
|
51
|
+
field: PropTypes.object.isRequired,
|
|
52
|
+
label: PropTypes.string.isRequired,
|
|
53
|
+
isRequired: PropTypes.bool,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
Subcomponent.defaultProps = {
|
|
57
|
+
isRequired: false,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
return Subcomponent;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const TextField = fieldWithHandlers(TextInput);
|
|
@@ -0,0 +1,68 @@
|
|
|
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
|
+
};
|
|
@@ -3,6 +3,7 @@ import { decodeId } from './globalIdHelper';
|
|
|
3
3
|
const experimental = path => `/experimental${path}`;
|
|
4
4
|
|
|
5
5
|
const showPath = path => `${path}/:id`;
|
|
6
|
+
const newPath = path => `${path}/new`;
|
|
6
7
|
|
|
7
8
|
export const modelPath = (basePath, model) => `${basePath}/${decodeId(model)}`;
|
|
8
9
|
|
|
@@ -14,7 +15,11 @@ export const resolvePath = (path, params) =>
|
|
|
14
15
|
path
|
|
15
16
|
);
|
|
16
17
|
|
|
18
|
+
export const ovalContentsApiPath = '/api/v2/compliance/oval_contents';
|
|
19
|
+
|
|
17
20
|
export const ovalContentsPath = experimental('/compliance/oval_contents');
|
|
21
|
+
export const ovalContentsShowPath = showPath(ovalContentsPath);
|
|
22
|
+
export const ovalContentsNewPath = newPath(ovalContentsPath);
|
|
18
23
|
export const ovalPoliciesPath = experimental('/compliance/oval_policies');
|
|
19
24
|
export const ovalPoliciesShowPath = `${showPath(ovalPoliciesPath)}/:tab?`;
|
|
20
25
|
export const hostsPath = '/hosts';
|
|
@@ -4,12 +4,17 @@ import { useQuery } from '@apollo/client';
|
|
|
4
4
|
import { translate as __ } from 'foremanReact/common/I18n';
|
|
5
5
|
|
|
6
6
|
import IndexLayout from '../../../components/IndexLayout';
|
|
7
|
+
import LinkButton from '../../../components/LinkButton';
|
|
7
8
|
import OvalContentsTable from './OvalContentsTable';
|
|
9
|
+
import { ovalContentsNewPath } from '../../../helpers/pathsHelper';
|
|
8
10
|
import {
|
|
9
11
|
useParamsToVars,
|
|
10
12
|
useCurrentPagination,
|
|
11
13
|
} from '../../../helpers/pageParamsHelper';
|
|
14
|
+
|
|
15
|
+
import { submitDelete, prepareMutation } from '../../../helpers/mutationHelper';
|
|
12
16
|
import ovalContentsQuery from '../../../graphql/queries/ovalContents.gql';
|
|
17
|
+
import deleteOvalContentMutation from '../../../graphql/mutations/deleteOvalContent.gql';
|
|
13
18
|
|
|
14
19
|
const OvalContentsIndex = props => {
|
|
15
20
|
const useFetchFn = componentProps =>
|
|
@@ -34,6 +39,24 @@ const OvalContentsIndex = props => {
|
|
|
34
39
|
pagination={pagination}
|
|
35
40
|
emptyStateTitle={__('No OVAL Contents found.')}
|
|
36
41
|
permissions={['view_oval_contents']}
|
|
42
|
+
confirmDeleteTitle={__('Delete OVAL Content')}
|
|
43
|
+
submitDelete={submitDelete}
|
|
44
|
+
prepareMutation={prepareMutation(
|
|
45
|
+
props.history,
|
|
46
|
+
props.showToast,
|
|
47
|
+
ovalContentsQuery,
|
|
48
|
+
'deleteOvalContent',
|
|
49
|
+
__('OVAL Content successfully deleted.'),
|
|
50
|
+
deleteOvalContentMutation,
|
|
51
|
+
__('OVAL Content')
|
|
52
|
+
)}
|
|
53
|
+
primaryButton={
|
|
54
|
+
<LinkButton
|
|
55
|
+
path={ovalContentsNewPath}
|
|
56
|
+
btnText={__('Create OVAL Content')}
|
|
57
|
+
/>
|
|
58
|
+
}
|
|
59
|
+
shouldRefetch={props.location?.state?.refreshOvalContents}
|
|
37
60
|
/>
|
|
38
61
|
</IndexLayout>
|
|
39
62
|
);
|
|
@@ -41,6 +64,8 @@ const OvalContentsIndex = props => {
|
|
|
41
64
|
|
|
42
65
|
OvalContentsIndex.propTypes = {
|
|
43
66
|
history: PropTypes.object.isRequired,
|
|
67
|
+
showToast: PropTypes.func.isRequired,
|
|
68
|
+
location: PropTypes.object.isRequired,
|
|
44
69
|
};
|
|
45
70
|
|
|
46
71
|
export default OvalContentsIndex;
|
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
3
|
import { translate as __ } from 'foremanReact/common/I18n';
|
|
4
|
+
import { Button } from '@patternfly/react-core';
|
|
4
5
|
|
|
5
6
|
import withLoading from '../../../components/withLoading';
|
|
7
|
+
import withDeleteModal from '../../../components/withDeleteModal';
|
|
6
8
|
import IndexTable from '../../../components/IndexTable';
|
|
9
|
+
import {
|
|
10
|
+
ovalContentsNewPath,
|
|
11
|
+
ovalContentsPath,
|
|
12
|
+
modelPath,
|
|
13
|
+
} from '../../../helpers/pathsHelper';
|
|
14
|
+
|
|
15
|
+
import { linkCell } from '../../../helpers/tableHelper';
|
|
7
16
|
|
|
8
17
|
const OvalContentsTable = props => {
|
|
9
18
|
const columns = [
|
|
@@ -14,24 +23,51 @@ const OvalContentsTable = props => {
|
|
|
14
23
|
|
|
15
24
|
const rows = props.ovalContents.map(ovalContent => ({
|
|
16
25
|
cells: [
|
|
17
|
-
{
|
|
26
|
+
{
|
|
27
|
+
title: linkCell(
|
|
28
|
+
modelPath(ovalContentsPath, ovalContent),
|
|
29
|
+
ovalContent.name
|
|
30
|
+
),
|
|
31
|
+
},
|
|
18
32
|
{ title: ovalContent.url || '' },
|
|
19
33
|
{ title: ovalContent.originalFilename || '' },
|
|
20
34
|
],
|
|
21
35
|
ovalContent,
|
|
22
36
|
}));
|
|
23
37
|
|
|
24
|
-
const
|
|
38
|
+
const actionResolver = (rowData, rest) => {
|
|
39
|
+
const actions = [];
|
|
40
|
+
if (rowData.ovalContent.meta.canDestroy) {
|
|
41
|
+
actions.push({
|
|
42
|
+
title: __('Delete OVAL Content'),
|
|
43
|
+
onClick: (event, rowId, rData, extra) => {
|
|
44
|
+
props.toggleModal(rData.ovalContent);
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
return actions;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const createBtn = (
|
|
52
|
+
<Button
|
|
53
|
+
onClick={() => props.history.push(ovalContentsNewPath)}
|
|
54
|
+
variant="primary"
|
|
55
|
+
aria-label="create_oval_content"
|
|
56
|
+
>
|
|
57
|
+
{__('Create OVAL Content')}
|
|
58
|
+
</Button>
|
|
59
|
+
);
|
|
25
60
|
|
|
26
61
|
return (
|
|
27
62
|
<IndexTable
|
|
28
63
|
columns={columns}
|
|
29
64
|
rows={rows}
|
|
30
|
-
|
|
65
|
+
actionResolver={actionResolver}
|
|
31
66
|
pagination={props.pagination}
|
|
32
67
|
totalCount={props.totalCount}
|
|
33
68
|
history={props.history}
|
|
34
69
|
ariaTableLabel={__('OVAL Contents table')}
|
|
70
|
+
toolbarBtns={createBtn}
|
|
35
71
|
/>
|
|
36
72
|
);
|
|
37
73
|
};
|
|
@@ -41,6 +77,7 @@ OvalContentsTable.propTypes = {
|
|
|
41
77
|
pagination: PropTypes.object.isRequired,
|
|
42
78
|
totalCount: PropTypes.number.isRequired,
|
|
43
79
|
history: PropTypes.object.isRequired,
|
|
80
|
+
toggleModal: PropTypes.func.isRequired,
|
|
44
81
|
};
|
|
45
82
|
|
|
46
|
-
export default withLoading(OvalContentsTable);
|
|
83
|
+
export default withLoading(withDeleteModal(OvalContentsTable));
|