foreman_openscap 4.3.1 → 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/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/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 +1 -1
- 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/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 +1 -1
- 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 +5 -7
- 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 +157 -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 +17 -18
- data/webpack/components/LinkButton.js +26 -0
- data/webpack/components/withDeleteModal.js +51 -0
- data/webpack/components/withLoading.js +41 -4
- 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 +5 -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/permissionsHelper.js +42 -0
- data/webpack/helpers/toastHelper.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 +93 -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 +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 +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/OvalPoliciesShow/CvesTab.js +1 -0
- 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 -12
- 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 +40 -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 +14 -0
- data/webpack/testHelper.js +42 -2
- metadata +53 -7
@@ -1,8 +1,12 @@
|
|
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!');
|
@@ -24,9 +28,18 @@ const withLoading = Component => {
|
|
24
28
|
resultPath,
|
25
29
|
renameData,
|
26
30
|
emptyStateTitle,
|
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,16 @@ const withLoading = Component => {
|
|
56
87
|
resultPath: PropTypes.string.isRequired,
|
57
88
|
renameData: PropTypes.func,
|
58
89
|
emptyStateTitle: PropTypes.string.isRequired,
|
90
|
+
permissions: PropTypes.array,
|
91
|
+
primaryButton: PropTypes.node,
|
92
|
+
shouldRefetch: PropTypes.bool,
|
59
93
|
};
|
60
94
|
|
61
95
|
Subcomponent.defaultProps = {
|
62
96
|
renameData: data => data,
|
97
|
+
permissions: [],
|
98
|
+
primaryButton: null,
|
99
|
+
shouldRefetch: false,
|
63
100
|
};
|
64
101
|
|
65
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
|
+
}
|
@@ -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
|
}
|
@@ -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';
|
@@ -0,0 +1,42 @@
|
|
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
|
+
};
|
@@ -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 =>
|
@@ -33,6 +38,25 @@ const OvalContentsIndex = props => {
|
|
33
38
|
resultPath="ovalContents.nodes"
|
34
39
|
pagination={pagination}
|
35
40
|
emptyStateTitle={__('No OVAL Contents found.')}
|
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}
|
36
60
|
/>
|
37
61
|
</IndexLayout>
|
38
62
|
);
|
@@ -40,6 +64,8 @@ const OvalContentsIndex = props => {
|
|
40
64
|
|
41
65
|
OvalContentsIndex.propTypes = {
|
42
66
|
history: PropTypes.object.isRequired,
|
67
|
+
showToast: PropTypes.func.isRequired,
|
68
|
+
location: PropTypes.object.isRequired,
|
43
69
|
};
|
44
70
|
|
45
71
|
export default OvalContentsIndex;
|
@@ -1,29 +1,73 @@
|
|
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
|
-
const columns = [
|
18
|
+
const columns = [
|
19
|
+
{ title: __('Name') },
|
20
|
+
{ title: __('URL') },
|
21
|
+
{ title: __('Original File Name') },
|
22
|
+
];
|
10
23
|
|
11
24
|
const rows = props.ovalContents.map(ovalContent => ({
|
12
|
-
cells: [
|
25
|
+
cells: [
|
26
|
+
{
|
27
|
+
title: linkCell(
|
28
|
+
modelPath(ovalContentsPath, ovalContent),
|
29
|
+
ovalContent.name
|
30
|
+
),
|
31
|
+
},
|
32
|
+
{ title: ovalContent.url || '' },
|
33
|
+
{ title: ovalContent.originalFilename || '' },
|
34
|
+
],
|
13
35
|
ovalContent,
|
14
36
|
}));
|
15
37
|
|
16
|
-
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
|
+
);
|
17
60
|
|
18
61
|
return (
|
19
62
|
<IndexTable
|
20
63
|
columns={columns}
|
21
64
|
rows={rows}
|
22
|
-
|
65
|
+
actionResolver={actionResolver}
|
23
66
|
pagination={props.pagination}
|
24
67
|
totalCount={props.totalCount}
|
25
68
|
history={props.history}
|
26
69
|
ariaTableLabel={__('OVAL Contents table')}
|
70
|
+
toolbarBtns={createBtn}
|
27
71
|
/>
|
28
72
|
);
|
29
73
|
};
|
@@ -33,6 +77,7 @@ OvalContentsTable.propTypes = {
|
|
33
77
|
pagination: PropTypes.object.isRequired,
|
34
78
|
totalCount: PropTypes.number.isRequired,
|
35
79
|
history: PropTypes.object.isRequired,
|
80
|
+
toggleModal: PropTypes.func.isRequired,
|
36
81
|
};
|
37
82
|
|
38
|
-
export default withLoading(OvalContentsTable);
|
83
|
+
export default withLoading(withDeleteModal(OvalContentsTable));
|
@@ -0,0 +1,105 @@
|
|
1
|
+
import { admin } from '../../../../testHelper';
|
2
|
+
|
3
|
+
import ovalContentsQuery from '../../../../graphql/queries/ovalContents.gql';
|
4
|
+
import deleteOvalContent from '../../../../graphql/mutations/deleteOvalContent.gql';
|
5
|
+
|
6
|
+
export const firstCall = {
|
7
|
+
data: {
|
8
|
+
ovalContents: {
|
9
|
+
totalCount: 5,
|
10
|
+
nodes: [
|
11
|
+
{
|
12
|
+
id: 'MDE6Rm9yZW1hbk9wZW5zY2FwOjpPdmFsQ29udGVudC0z',
|
13
|
+
name: 'ansible OVAL content',
|
14
|
+
url:
|
15
|
+
'http://oval-content-source/security/data/oval/ansible-2-including-unpatched.oval.xml.bz2',
|
16
|
+
originalFilename: '',
|
17
|
+
meta: { canDestroy: true },
|
18
|
+
},
|
19
|
+
{
|
20
|
+
id: 'MDE6Rm9yZW1hbk9wZW5zY2FwOjpPdmFsQ29udGVudC00',
|
21
|
+
name: 'dotnet OVAL content',
|
22
|
+
url:
|
23
|
+
'http://oval-content-source/security/data/oval/dotnet-2.2.oval.xml.bz2',
|
24
|
+
originalFilename: '',
|
25
|
+
meta: { canDestroy: true },
|
26
|
+
},
|
27
|
+
],
|
28
|
+
},
|
29
|
+
currentUser: admin,
|
30
|
+
},
|
31
|
+
};
|
32
|
+
|
33
|
+
export const secondCall = {
|
34
|
+
data: {
|
35
|
+
ovalContents: {
|
36
|
+
totalCount: 4,
|
37
|
+
nodes: [
|
38
|
+
{
|
39
|
+
id: 'MDE6Rm9yZW1hbk9wZW5zY2FwOjpPdmFsQ29udGVudC00',
|
40
|
+
name: 'dotnet OVAL content',
|
41
|
+
url:
|
42
|
+
'http://oval-content-source/security/data/oval/dotnet-2.2.oval.xml.bz2',
|
43
|
+
originalFilename: '',
|
44
|
+
meta: { canDestroy: true },
|
45
|
+
},
|
46
|
+
{
|
47
|
+
id: 'MDE6Rm9yZW1hbk9wZW5zY2FwOjpPdmFsQ29udGVudC03',
|
48
|
+
name: 'jboss OVAL content',
|
49
|
+
url: '',
|
50
|
+
originalFilename: 'jboss.oval.xml.bz2',
|
51
|
+
meta: { canDestroy: true },
|
52
|
+
},
|
53
|
+
],
|
54
|
+
},
|
55
|
+
currentUser: admin,
|
56
|
+
},
|
57
|
+
};
|
58
|
+
|
59
|
+
export const deleteMockFactory = (first, second, errors = null) => {
|
60
|
+
let called = false;
|
61
|
+
|
62
|
+
const deleteMocks = [
|
63
|
+
{
|
64
|
+
request: {
|
65
|
+
query: deleteOvalContent,
|
66
|
+
variables: {
|
67
|
+
id: 'MDE6Rm9yZW1hbk9wZW5zY2FwOjpPdmFsQ29udGVudC0z',
|
68
|
+
},
|
69
|
+
},
|
70
|
+
result: {
|
71
|
+
data: {
|
72
|
+
deleteOvalContent: {
|
73
|
+
id: 'MDE6Rm9yZW1hbk9wZW5zY2FwOjpPdmFsQ29udGVudC0z',
|
74
|
+
errors,
|
75
|
+
},
|
76
|
+
},
|
77
|
+
},
|
78
|
+
},
|
79
|
+
{
|
80
|
+
request: {
|
81
|
+
query: ovalContentsQuery,
|
82
|
+
variables: {
|
83
|
+
first: 2,
|
84
|
+
last: 2,
|
85
|
+
},
|
86
|
+
},
|
87
|
+
newData: () => {
|
88
|
+
if (called && !errors) {
|
89
|
+
return second;
|
90
|
+
} else if (called && errors) {
|
91
|
+
return first;
|
92
|
+
}
|
93
|
+
called = true;
|
94
|
+
return first;
|
95
|
+
},
|
96
|
+
},
|
97
|
+
];
|
98
|
+
return deleteMocks;
|
99
|
+
};
|
100
|
+
|
101
|
+
export const pageParamsHistoryMock = {
|
102
|
+
location: {
|
103
|
+
search: '?page=1&perPage=2',
|
104
|
+
},
|
105
|
+
};
|