foreman_openscap 5.0.0 → 5.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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));
|