foreman_openscap 4.3.2 → 5.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/controllers/api/v2/compliance/arf_reports_controller.rb +0 -6
- data/app/controllers/api/v2/compliance/oval_policies_controller.rb +1 -1
- data/app/graphql/mutations/oval_contents/delete.rb +9 -0
- data/app/graphql/mutations/oval_policies/create.rb +33 -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/helpers/arf_report_dashboard_helper.rb +2 -4
- data/app/helpers/compliance_hosts_helper.rb +1 -1
- data/app/helpers/policies_helper.rb +2 -2
- data/app/models/concerns/foreman_openscap/data_stream_content.rb +1 -1
- data/app/models/concerns/foreman_openscap/host_extensions.rb +0 -6
- data/app/models/concerns/foreman_openscap/oval_facet_hostgroup_extensions.rb +16 -0
- data/app/models/foreman_openscap/arf_report.rb +1 -1
- data/app/models/foreman_openscap/oval_content.rb +2 -0
- data/app/services/foreman_openscap/client_config/base.rb +1 -0
- data/app/services/foreman_openscap/client_config/puppet.rb +6 -2
- data/app/services/foreman_openscap/oval/configure.rb +16 -13
- data/app/services/foreman_openscap/oval/setup.rb +5 -5
- data/app/services/foreman_openscap/oval/setup_check.rb +5 -2
- data/app/views/api/v2/compliance/oval_contents/destroy.json.rabl +3 -0
- data/app/views/arf_reports/_metrics.html.erb +4 -4
- data/app/views/compliance_hosts/show.html.erb +4 -6
- data/app/views/dashboard/_compliance_reports_breakdown_widget.html.erb +4 -3
- data/app/views/policy_dashboard/_policy_chart_widget.html.erb +3 -2
- data/db/migrate/20200117135424_migrate_port_overrides_to_int.rb +2 -1
- data/db/migrate/20201202110213_update_puppet_port_param_type.rb +2 -1
- data/db/migrate/20210819143316_drop_unused_tables.rb +6 -0
- data/lib/foreman_openscap/engine.rb +8 -9
- data/lib/foreman_openscap/version.rb +1 -1
- data/package.json +3 -6
- data/test/functional/api/v2/compliance/oval_reports_controller_test.rb +1 -1
- data/test/functional/api/v2/compliance/policies_controller_test.rb +2 -0
- 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/helpers/arf_report_dashboard_helper_test.rb +9 -10
- data/test/helpers/policy_dashboard_helper_test.rb +1 -1
- data/test/test_plugin_helper.rb +9 -4
- data/test/unit/policy_test.rb +1 -1
- data/test/unit/services/config_name_service_test.rb +1 -0
- data/test/unit/services/hostgroup_overrider_test.rb +2 -1
- data/test/unit/services/lookup_key_overrider_test.rb +4 -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 +163 -0
- data/webpack/components/EditableInput.scss +3 -0
- data/webpack/components/EmptyState.js +12 -3
- data/webpack/components/IndexLayout.js +11 -4
- data/webpack/components/IndexTable/index.js +21 -16
- data/webpack/components/LinkButton.js +38 -0
- data/webpack/components/withDeleteModal.js +51 -0
- data/webpack/components/withLoading.js +44 -5
- data/webpack/graphql/mutations/createOvalPolicy.gql +22 -0
- 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/currentUserAttributes.gql +11 -0
- data/webpack/graphql/queries/cves.gql +5 -0
- data/webpack/graphql/queries/hostgroups.gql +14 -0
- data/webpack/graphql/queries/ovalContent.gql +8 -0
- data/webpack/graphql/queries/ovalContents.gql +8 -0
- data/webpack/graphql/queries/ovalPolicies.gql +8 -0
- data/webpack/graphql/queries/ovalPolicy.gql +8 -0
- data/webpack/helpers/formFieldsHelper.js +113 -0
- data/webpack/helpers/globalIdHelper.js +4 -2
- data/webpack/helpers/mutationHelper.js +68 -0
- data/webpack/helpers/pathsHelper.js +10 -3
- data/webpack/helpers/permissionsHelper.js +42 -0
- data/webpack/helpers/toastHelper.js +3 -0
- data/webpack/helpers/toastsHelper.js +3 -0
- data/webpack/routes/OvalContents/OvalContentsIndex/OvalContentsIndex.js +26 -0
- data/webpack/routes/OvalContents/OvalContentsIndex/OvalContentsTable.js +50 -5
- 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 +98 -77
- data/webpack/routes/OvalContents/OvalContentsIndex/__tests__/OvalContentsIndex.test.js +53 -6
- 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 +18 -2
- data/webpack/routes/OvalPolicies/OvalPoliciesIndex/OvalPoliciesTable.js +34 -4
- 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 +71 -21
- data/webpack/routes/OvalPolicies/OvalPoliciesIndex/__tests__/OvalPoliciesIndex.test.js +34 -2
- data/webpack/routes/OvalPolicies/OvalPoliciesIndex/index.js +7 -1
- data/webpack/routes/OvalPolicies/OvalPoliciesNew/HostgroupSelect.js +135 -0
- data/webpack/routes/OvalPolicies/OvalPoliciesNew/NewOvalPolicyForm.js +119 -0
- data/webpack/routes/OvalPolicies/OvalPoliciesNew/NewOvalPolicyFormHelpers.js +107 -0
- data/webpack/routes/OvalPolicies/OvalPoliciesNew/OvalPoliciesNew.js +32 -0
- data/webpack/routes/OvalPolicies/OvalPoliciesNew/__tests__/OvalPoliciesNew.fixtures.js +147 -0
- data/webpack/routes/OvalPolicies/OvalPoliciesNew/__tests__/OvalPoliciesNew.test.js +172 -0
- data/webpack/routes/OvalPolicies/OvalPoliciesNew/index.js +11 -0
- data/webpack/routes/OvalPolicies/OvalPoliciesShow/CvesTab.js +1 -0
- data/webpack/routes/OvalPolicies/OvalPoliciesShow/CvesTable.js +2 -2
- data/webpack/routes/OvalPolicies/OvalPoliciesShow/DetailsTab.js +87 -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 +80 -2
- data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesEdit.fixtures.js +48 -0
- data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesEdit.test.js +202 -0
- data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesShow.fixtures.js +50 -4
- data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesShow.test.js +64 -4
- data/webpack/routes/OvalPolicies/OvalPoliciesShow/index.js +4 -0
- data/webpack/routes/routes.js +21 -0
- data/webpack/testHelper.js +64 -2
- metadata +63 -7
@@ -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}
|
@@ -34,10 +42,10 @@ const IndexTable = props => {
|
|
34
42
|
</FlexItem>
|
35
43
|
</Flex>
|
36
44
|
<Table
|
37
|
-
aria-label={
|
38
|
-
cells={
|
39
|
-
|
40
|
-
|
45
|
+
aria-label={ariaTableLabel}
|
46
|
+
cells={columns}
|
47
|
+
{...rest}
|
48
|
+
variant="compact"
|
41
49
|
>
|
42
50
|
<TableHeader />
|
43
51
|
<TableBody />
|
@@ -49,17 +57,14 @@ const IndexTable = props => {
|
|
49
57
|
IndexTable.propTypes = {
|
50
58
|
history: PropTypes.object.isRequired,
|
51
59
|
pagination: PropTypes.object.isRequired,
|
52
|
-
toolbarBtns: PropTypes.
|
60
|
+
toolbarBtns: PropTypes.node,
|
53
61
|
totalCount: PropTypes.number.isRequired,
|
54
62
|
ariaTableLabel: PropTypes.string.isRequired,
|
55
63
|
columns: PropTypes.array.isRequired,
|
56
|
-
rows: PropTypes.array.isRequired,
|
57
|
-
actions: PropTypes.array,
|
58
64
|
};
|
59
65
|
|
60
66
|
IndexTable.defaultProps = {
|
61
|
-
toolbarBtns:
|
62
|
-
actions: [],
|
67
|
+
toolbarBtns: null,
|
63
68
|
};
|
64
69
|
|
65
70
|
export default IndexTable;
|
@@ -0,0 +1,38 @@
|
|
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
|
+
}) => (
|
13
|
+
<Link to={path}>
|
14
|
+
<Button
|
15
|
+
variant={btnVariant}
|
16
|
+
isDisabled={isDisabled}
|
17
|
+
aria-label={btnAriaLabel}
|
18
|
+
>
|
19
|
+
{btnText}
|
20
|
+
</Button>
|
21
|
+
</Link>
|
22
|
+
);
|
23
|
+
|
24
|
+
LinkButton.propTypes = {
|
25
|
+
path: PropTypes.string.isRequired,
|
26
|
+
btnText: PropTypes.string.isRequired,
|
27
|
+
btnVariant: PropTypes.string,
|
28
|
+
isDisabled: PropTypes.bool,
|
29
|
+
btnAriaLabel: PropTypes.string,
|
30
|
+
};
|
31
|
+
|
32
|
+
LinkButton.defaultProps = {
|
33
|
+
btnVariant: 'primary',
|
34
|
+
isDisabled: false,
|
35
|
+
btnAriaLabel: null,
|
36
|
+
};
|
37
|
+
|
38
|
+
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,12 +1,15 @@
|
|
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
|
-
|
5
4
|
import Loading from 'foremanReact/components/Loading';
|
5
|
+
import {
|
6
|
+
permissionCheck,
|
7
|
+
permissionDeniedMsg,
|
8
|
+
} from '../helpers/permissionsHelper';
|
9
|
+
|
6
10
|
import EmptyState from './EmptyState';
|
7
11
|
|
8
12
|
const errorStateTitle = __('Error!');
|
9
|
-
const emptyStateBody = '';
|
10
13
|
|
11
14
|
const pluckData = (data, path) => {
|
12
15
|
const split = path.split('.');
|
@@ -24,9 +27,19 @@ const withLoading = Component => {
|
|
24
27
|
resultPath,
|
25
28
|
renameData,
|
26
29
|
emptyStateTitle,
|
30
|
+
emptyStateBody,
|
31
|
+
permissions,
|
32
|
+
primaryButton,
|
33
|
+
shouldRefetch,
|
27
34
|
...rest
|
28
35
|
}) => {
|
29
|
-
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]);
|
30
43
|
|
31
44
|
if (loading) {
|
32
45
|
return <Loading />;
|
@@ -42,10 +55,28 @@ const withLoading = Component => {
|
|
42
55
|
);
|
43
56
|
}
|
44
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
|
+
|
45
70
|
const result = pluckData(data, resultPath);
|
46
71
|
|
47
72
|
if ((Array.isArray(result) && result.length === 0) || !result) {
|
48
|
-
return
|
73
|
+
return (
|
74
|
+
<EmptyState
|
75
|
+
title={emptyStateTitle}
|
76
|
+
body={emptyStateBody}
|
77
|
+
primaryButton={primaryButton}
|
78
|
+
/>
|
79
|
+
);
|
49
80
|
}
|
50
81
|
|
51
82
|
return <Component {...rest} {...renameData(data)} />;
|
@@ -56,10 +87,18 @@ const withLoading = Component => {
|
|
56
87
|
resultPath: PropTypes.string.isRequired,
|
57
88
|
renameData: PropTypes.func,
|
58
89
|
emptyStateTitle: PropTypes.string.isRequired,
|
90
|
+
emptyStateBody: PropTypes.string,
|
91
|
+
permissions: PropTypes.array,
|
92
|
+
primaryButton: PropTypes.node,
|
93
|
+
shouldRefetch: PropTypes.bool,
|
59
94
|
};
|
60
95
|
|
61
96
|
Subcomponent.defaultProps = {
|
62
97
|
renameData: data => data,
|
98
|
+
permissions: [],
|
99
|
+
primaryButton: null,
|
100
|
+
shouldRefetch: false,
|
101
|
+
emptyStateBody: '',
|
63
102
|
};
|
64
103
|
|
65
104
|
return Subcomponent;
|
@@ -0,0 +1,22 @@
|
|
1
|
+
mutation CreateOvalPolicy($name: String!, $period: String!, $cronLine: String, $ovalContentId: Int!, $hostgroupIds: [Int!]) {
|
2
|
+
createOvalPolicy(input: {name: $name, period: $period, cronLine: $cronLine, ovalContentId: $ovalContentId, hostgroupIds: $hostgroupIds}) {
|
3
|
+
ovalPolicy {
|
4
|
+
name
|
5
|
+
id
|
6
|
+
period
|
7
|
+
cronLine
|
8
|
+
hostgroups {
|
9
|
+
nodes {
|
10
|
+
name
|
11
|
+
id
|
12
|
+
}
|
13
|
+
}
|
14
|
+
}
|
15
|
+
checkCollection {
|
16
|
+
id
|
17
|
+
errors
|
18
|
+
failMsg
|
19
|
+
result
|
20
|
+
}
|
21
|
+
}
|
22
|
+
}
|
@@ -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
|
+
}
|
@@ -1,3 +1,5 @@
|
|
1
|
+
#import "./currentUserAttributes.gql"
|
2
|
+
|
1
3
|
query($search: String, $first: Int, $last: Int) {
|
2
4
|
cves(search: $search, first: $first, last: $last) {
|
3
5
|
totalCount
|
@@ -15,4 +17,7 @@ query($search: String, $first: Int, $last: Int) {
|
|
15
17
|
}
|
16
18
|
}
|
17
19
|
}
|
20
|
+
currentUser {
|
21
|
+
...CurrentUserAttributes
|
22
|
+
}
|
18
23
|
}
|
@@ -1,3 +1,5 @@
|
|
1
|
+
#import "./currentUserAttributes.gql"
|
2
|
+
|
1
3
|
query($first: Int, $last: Int) {
|
2
4
|
ovalContents(first: $first, last: $last) {
|
3
5
|
totalCount
|
@@ -6,6 +8,12 @@ query($first: Int, $last: Int) {
|
|
6
8
|
name
|
7
9
|
url
|
8
10
|
originalFilename
|
11
|
+
meta {
|
12
|
+
canDestroy
|
13
|
+
}
|
9
14
|
}
|
10
15
|
}
|
16
|
+
currentUser {
|
17
|
+
...CurrentUserAttributes
|
18
|
+
}
|
11
19
|
}
|
@@ -1,12 +1,20 @@
|
|
1
|
+
#import "./currentUserAttributes.gql"
|
2
|
+
|
1
3
|
query($first: Int, $last: Int) {
|
2
4
|
ovalPolicies(first: $first, last: $last) {
|
3
5
|
totalCount
|
4
6
|
nodes {
|
5
7
|
id
|
6
8
|
name
|
9
|
+
meta {
|
10
|
+
canDestroy
|
11
|
+
}
|
7
12
|
ovalContent {
|
8
13
|
name
|
9
14
|
}
|
10
15
|
}
|
11
16
|
}
|
17
|
+
currentUser {
|
18
|
+
...CurrentUserAttributes
|
19
|
+
}
|
12
20
|
}
|
@@ -1,3 +1,5 @@
|
|
1
|
+
#import "./currentUserAttributes.gql"
|
2
|
+
|
1
3
|
query($id: String!) {
|
2
4
|
ovalPolicy(id: $id) {
|
3
5
|
id
|
@@ -7,6 +9,9 @@ query($id: String!) {
|
|
7
9
|
weekday
|
8
10
|
dayOfMonth
|
9
11
|
description
|
12
|
+
meta {
|
13
|
+
canEdit
|
14
|
+
}
|
10
15
|
hostgroups {
|
11
16
|
nodes {
|
12
17
|
id
|
@@ -18,4 +23,7 @@ query($id: String!) {
|
|
18
23
|
}
|
19
24
|
}
|
20
25
|
}
|
26
|
+
currentUser {
|
27
|
+
...CurrentUserAttributes
|
28
|
+
}
|
21
29
|
}
|
@@ -0,0 +1,113 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import PropTypes from 'prop-types';
|
3
|
+
|
4
|
+
import {
|
5
|
+
FormGroup,
|
6
|
+
TextInput,
|
7
|
+
TextArea,
|
8
|
+
FormSelect,
|
9
|
+
FormSelectOption,
|
10
|
+
} from '@patternfly/react-core';
|
11
|
+
import { ExclamationCircleIcon } from '@patternfly/react-icons';
|
12
|
+
|
13
|
+
export const SelectField = props => {
|
14
|
+
const { selectItems, field, form } = props;
|
15
|
+
const fieldProps = wrapFieldProps(field);
|
16
|
+
|
17
|
+
const valid = shouldValidate(form, field.name);
|
18
|
+
|
19
|
+
return (
|
20
|
+
<FormGroup
|
21
|
+
label={props.label}
|
22
|
+
isRequired={props.isRequired}
|
23
|
+
helperTextInvalid={form.errors[field.name]}
|
24
|
+
helperTextInvalidIcon={<ExclamationCircleIcon />}
|
25
|
+
validated={valid}
|
26
|
+
>
|
27
|
+
<FormSelect
|
28
|
+
{...fieldProps}
|
29
|
+
className="without_select2"
|
30
|
+
aria-label={fieldProps.name}
|
31
|
+
validated={valid}
|
32
|
+
isDisabled={form.isSubmitting}
|
33
|
+
>
|
34
|
+
<FormSelectOption key={0} value="" label={props.blankLabel} />
|
35
|
+
{selectItems.map((item, idx) => (
|
36
|
+
<FormSelectOption key={idx + 1} value={item.id} label={item.name} />
|
37
|
+
))}
|
38
|
+
</FormSelect>
|
39
|
+
</FormGroup>
|
40
|
+
);
|
41
|
+
};
|
42
|
+
|
43
|
+
SelectField.propTypes = {
|
44
|
+
selectItems: PropTypes.array,
|
45
|
+
label: PropTypes.string.isRequired,
|
46
|
+
isRequired: PropTypes.bool,
|
47
|
+
field: PropTypes.object.isRequired,
|
48
|
+
form: PropTypes.object.isRequired,
|
49
|
+
blankLabel: PropTypes.string.isRequired,
|
50
|
+
};
|
51
|
+
SelectField.defaultProps = {
|
52
|
+
selectItems: [],
|
53
|
+
isRequired: false,
|
54
|
+
};
|
55
|
+
|
56
|
+
const wrapFieldProps = fieldProps => {
|
57
|
+
const { onChange } = fieldProps;
|
58
|
+
// modify onChange args to correctly wire formik with pf4 input handlers
|
59
|
+
const wrappedOnChange = (value, event) => {
|
60
|
+
onChange(event);
|
61
|
+
};
|
62
|
+
|
63
|
+
return { ...fieldProps, onChange: wrappedOnChange };
|
64
|
+
};
|
65
|
+
|
66
|
+
const shouldValidate = (form, fieldName) => {
|
67
|
+
if (form.touched[fieldName]) {
|
68
|
+
return form.errors[fieldName] ? 'error' : 'success';
|
69
|
+
}
|
70
|
+
|
71
|
+
return 'noval';
|
72
|
+
};
|
73
|
+
|
74
|
+
const fieldWithHandlers = Component => {
|
75
|
+
const Subcomponent = ({ label, form, field, isRequired, ...rest }) => {
|
76
|
+
const fieldProps = wrapFieldProps(field);
|
77
|
+
const valid = shouldValidate(form, field.name);
|
78
|
+
|
79
|
+
return (
|
80
|
+
<FormGroup
|
81
|
+
label={label}
|
82
|
+
isRequired={isRequired}
|
83
|
+
helperTextInvalid={form.errors[field.name]}
|
84
|
+
helperTextInvalidIcon={<ExclamationCircleIcon />}
|
85
|
+
validated={valid}
|
86
|
+
>
|
87
|
+
<Component
|
88
|
+
aria-label={fieldProps.name}
|
89
|
+
{...fieldProps}
|
90
|
+
{...rest}
|
91
|
+
validated={valid}
|
92
|
+
isDisabled={form.isSubmitting}
|
93
|
+
/>
|
94
|
+
</FormGroup>
|
95
|
+
);
|
96
|
+
};
|
97
|
+
|
98
|
+
Subcomponent.propTypes = {
|
99
|
+
form: PropTypes.object.isRequired,
|
100
|
+
field: PropTypes.object.isRequired,
|
101
|
+
label: PropTypes.string.isRequired,
|
102
|
+
isRequired: PropTypes.bool,
|
103
|
+
};
|
104
|
+
|
105
|
+
Subcomponent.defaultProps = {
|
106
|
+
isRequired: false,
|
107
|
+
};
|
108
|
+
|
109
|
+
return Subcomponent;
|
110
|
+
};
|
111
|
+
|
112
|
+
export const TextField = fieldWithHandlers(TextInput);
|
113
|
+
export const TextAreaField = fieldWithHandlers(TextArea);
|
@@ -4,8 +4,10 @@ const idSeparator = '-';
|
|
4
4
|
const versionSeparator = ':';
|
5
5
|
const defaultVersion = '01';
|
6
6
|
|
7
|
-
export const
|
8
|
-
|
7
|
+
export const decodeModelId = model => decodeId(model.id);
|
8
|
+
|
9
|
+
export const decodeId = globalId => {
|
10
|
+
const split = atob(globalId).split(idSeparator);
|
9
11
|
return parseInt(last(split), 10);
|
10
12
|
};
|
11
13
|
|
@@ -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
|
+
};
|
@@ -1,10 +1,12 @@
|
|
1
|
-
import {
|
1
|
+
import { decodeModelId } from './globalIdHelper';
|
2
2
|
|
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
|
-
export const modelPath = (basePath, model) =>
|
8
|
+
export const modelPath = (basePath, model) =>
|
9
|
+
`${basePath}/${decodeModelId(model)}`;
|
8
10
|
|
9
11
|
// react-router uses path-to-regexp, should we use it as well in a future?
|
10
12
|
// https://github.com/pillarjs/path-to-regexp/tree/v1.7.0#compile-reverse-path-to-regexp
|
@@ -14,9 +16,14 @@ export const resolvePath = (path, params) =>
|
|
14
16
|
path
|
15
17
|
);
|
16
18
|
|
19
|
+
export const ovalContentsApiPath = '/api/v2/compliance/oval_contents';
|
20
|
+
|
17
21
|
export const ovalContentsPath = experimental('/compliance/oval_contents');
|
22
|
+
export const ovalContentsShowPath = showPath(ovalContentsPath);
|
23
|
+
export const ovalContentsNewPath = newPath(ovalContentsPath);
|
18
24
|
export const ovalPoliciesPath = experimental('/compliance/oval_policies');
|
19
25
|
export const ovalPoliciesShowPath = `${showPath(ovalPoliciesPath)}/:tab?`;
|
26
|
+
export const ovalPoliciesNewPath = newPath(ovalPoliciesPath);
|
20
27
|
export const hostsPath = '/hosts';
|
21
|
-
export const newJobPath = '/job_invocations
|
28
|
+
export const newJobPath = newPath('/job_invocations');
|
22
29
|
export const hostsShowPath = showPath(hostsPath);
|