foreman_openscap 4.3.1 → 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.
Files changed (105) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/api/v2/compliance/arf_reports_controller.rb +0 -6
  3. data/app/controllers/api/v2/compliance/oval_policies_controller.rb +1 -1
  4. data/app/graphql/mutations/oval_contents/delete.rb +9 -0
  5. data/app/graphql/mutations/oval_policies/delete.rb +9 -0
  6. data/app/graphql/mutations/oval_policies/update.rb +15 -0
  7. data/app/graphql/types/oval_check.rb +11 -0
  8. data/app/graphql/types/oval_content.rb +2 -0
  9. data/app/graphql/types/oval_policy.rb +3 -0
  10. data/app/helpers/arf_report_dashboard_helper.rb +2 -4
  11. data/app/helpers/compliance_hosts_helper.rb +1 -1
  12. data/app/helpers/policies_helper.rb +1 -1
  13. data/app/models/concerns/foreman_openscap/host_extensions.rb +0 -6
  14. data/app/models/concerns/foreman_openscap/oval_facet_hostgroup_extensions.rb +15 -0
  15. data/app/models/foreman_openscap/oval_content.rb +2 -0
  16. data/app/services/foreman_openscap/client_config/base.rb +1 -0
  17. data/app/services/foreman_openscap/client_config/puppet.rb +6 -2
  18. data/app/services/foreman_openscap/oval/configure.rb +1 -1
  19. data/app/services/foreman_openscap/oval/setup.rb +5 -5
  20. data/app/services/foreman_openscap/oval/setup_check.rb +5 -2
  21. data/app/views/api/v2/compliance/oval_contents/destroy.json.rabl +3 -0
  22. data/app/views/arf_reports/_metrics.html.erb +4 -4
  23. data/app/views/compliance_hosts/show.html.erb +4 -6
  24. data/app/views/dashboard/_compliance_reports_breakdown_widget.html.erb +4 -3
  25. data/app/views/policy_dashboard/_policy_chart_widget.html.erb +3 -2
  26. data/db/migrate/20200117135424_migrate_port_overrides_to_int.rb +2 -1
  27. data/db/migrate/20201202110213_update_puppet_port_param_type.rb +2 -1
  28. data/db/migrate/20210819143316_drop_unused_tables.rb +6 -0
  29. data/lib/foreman_openscap/engine.rb +5 -7
  30. data/lib/foreman_openscap/version.rb +1 -1
  31. data/package.json +3 -6
  32. data/test/functional/api/v2/compliance/oval_reports_controller_test.rb +1 -1
  33. data/test/functional/api/v2/compliance/policies_controller_test.rb +2 -0
  34. data/test/graphql/mutations/oval_policies/delete_mutation_test.rb +63 -0
  35. data/test/graphql/queries/oval_content_query_test.rb +29 -0
  36. data/test/helpers/arf_report_dashboard_helper_test.rb +9 -10
  37. data/test/helpers/policy_dashboard_helper_test.rb +1 -1
  38. data/test/test_plugin_helper.rb +9 -4
  39. data/test/unit/policy_test.rb +1 -1
  40. data/test/unit/services/config_name_service_test.rb +1 -0
  41. data/test/unit/services/hostgroup_overrider_test.rb +2 -1
  42. data/test/unit/services/lookup_key_overrider_test.rb +4 -1
  43. data/test/unit/services/oval/setup_check_test.rb +37 -0
  44. data/webpack/components/ConfirmModal.js +63 -0
  45. data/webpack/components/ConfirmModal.scss +3 -0
  46. data/webpack/components/EditableInput.js +157 -0
  47. data/webpack/components/EditableInput.scss +3 -0
  48. data/webpack/components/EmptyState.js +12 -3
  49. data/webpack/components/IndexLayout.js +11 -4
  50. data/webpack/components/IndexTable/index.js +17 -18
  51. data/webpack/components/LinkButton.js +26 -0
  52. data/webpack/components/withDeleteModal.js +51 -0
  53. data/webpack/components/withLoading.js +41 -4
  54. data/webpack/graphql/mutations/deleteOvalContent.gql +9 -0
  55. data/webpack/graphql/mutations/deleteOvalPolicy.gql +9 -0
  56. data/webpack/graphql/mutations/updateOvalPolicy.gql +14 -0
  57. data/webpack/graphql/queries/currentUserAttributes.gql +11 -0
  58. data/webpack/graphql/queries/cves.gql +5 -0
  59. data/webpack/graphql/queries/hostgroups.gql +14 -0
  60. data/webpack/graphql/queries/ovalContent.gql +8 -0
  61. data/webpack/graphql/queries/ovalContents.gql +8 -0
  62. data/webpack/graphql/queries/ovalPolicies.gql +8 -0
  63. data/webpack/graphql/queries/ovalPolicy.gql +5 -0
  64. data/webpack/helpers/formFieldsHelper.js +63 -0
  65. data/webpack/helpers/mutationHelper.js +68 -0
  66. data/webpack/helpers/pathsHelper.js +5 -0
  67. data/webpack/helpers/permissionsHelper.js +42 -0
  68. data/webpack/helpers/toastHelper.js +3 -0
  69. data/webpack/routes/OvalContents/OvalContentsIndex/OvalContentsIndex.js +26 -0
  70. data/webpack/routes/OvalContents/OvalContentsIndex/OvalContentsTable.js +50 -5
  71. data/webpack/routes/OvalContents/OvalContentsIndex/__tests__/OvalContentsDestroy.fixtures.js +105 -0
  72. data/webpack/routes/OvalContents/OvalContentsIndex/__tests__/OvalContentsDestroy.test.js +124 -0
  73. data/webpack/routes/OvalContents/OvalContentsIndex/__tests__/OvalContentsIndex.fixtures.js +93 -77
  74. data/webpack/routes/OvalContents/OvalContentsIndex/__tests__/OvalContentsIndex.test.js +53 -6
  75. data/webpack/routes/OvalContents/OvalContentsIndex/index.js +7 -1
  76. data/webpack/routes/OvalContents/OvalContentsNew/OvalContentsNew.js +138 -0
  77. data/webpack/routes/OvalContents/OvalContentsNew/OvalContentsNew.scss +3 -0
  78. data/webpack/routes/OvalContents/OvalContentsNew/OvalContentsNewHelper.js +73 -0
  79. data/webpack/routes/OvalContents/OvalContentsNew/__tests__/OvalContentsNew.test.js +104 -0
  80. data/webpack/routes/OvalContents/OvalContentsNew/index.js +13 -0
  81. data/webpack/routes/OvalContents/OvalContentsShow/OvalContentsShow.js +62 -0
  82. data/webpack/routes/OvalContents/OvalContentsShow/OvalContentsShow.test.js +45 -0
  83. data/webpack/routes/OvalContents/OvalContentsShow/OvalContentsShowHelper.js +0 -0
  84. data/webpack/routes/OvalContents/OvalContentsShow/index.js +35 -0
  85. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/OvalPoliciesIndex.js +18 -2
  86. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/OvalPoliciesTable.js +16 -3
  87. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/__tests__/OvalPoliciesDestroy.fixtures.js +101 -0
  88. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/__tests__/OvalPoliciesDestroy.test.js +117 -0
  89. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/__tests__/OvalPoliciesIndex.fixtures.js +71 -21
  90. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/__tests__/OvalPoliciesIndex.test.js +34 -2
  91. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/index.js +7 -1
  92. data/webpack/routes/OvalPolicies/OvalPoliciesShow/CvesTab.js +1 -0
  93. data/webpack/routes/OvalPolicies/OvalPoliciesShow/DetailsTab.js +85 -0
  94. data/webpack/routes/OvalPolicies/OvalPoliciesShow/HostgroupsTab.js +49 -0
  95. data/webpack/routes/OvalPolicies/OvalPoliciesShow/HostgroupsTable.js +38 -0
  96. data/webpack/routes/OvalPolicies/OvalPoliciesShow/OvalPoliciesShow.js +15 -12
  97. data/webpack/routes/OvalPolicies/OvalPoliciesShow/OvalPoliciesShowHelper.js +77 -0
  98. data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesEdit.fixtures.js +48 -0
  99. data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesEdit.test.js +175 -0
  100. data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesShow.fixtures.js +40 -4
  101. data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesShow.test.js +64 -4
  102. data/webpack/routes/OvalPolicies/OvalPoliciesShow/index.js +4 -0
  103. data/webpack/routes/routes.js +14 -0
  104. data/webpack/testHelper.js +42 -2
  105. 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 <EmptyState title={emptyStateTitle} body={emptyStateBody} />;
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,9 @@
1
+ mutation DeleteOvalContent($id: ID!) {
2
+ deleteOvalContent(input: { id: $id }) {
3
+ id
4
+ errors {
5
+ path
6
+ message
7
+ }
8
+ }
9
+ }
@@ -0,0 +1,9 @@
1
+ mutation DeleteOvalPolicy($id: ID!) {
2
+ deleteOvalPolicy(input: { id: $id }) {
3
+ id
4
+ errors {
5
+ path
6
+ message
7
+ }
8
+ }
9
+ }
@@ -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,11 @@
1
+ fragment CurrentUserAttributes on User {
2
+ id
3
+ login
4
+ admin
5
+ permissions {
6
+ nodes {
7
+ id
8
+ name
9
+ }
10
+ }
11
+ }
@@ -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
  }
@@ -0,0 +1,14 @@
1
+ #import "./currentUserAttributes.gql"
2
+
3
+ query($search: String, $first: Int, $last: Int) {
4
+ hostgroups(search: $search, first: $first, last: $last) {
5
+ totalCount
6
+ nodes {
7
+ id
8
+ name
9
+ }
10
+ }
11
+ currentUser {
12
+ ...CurrentUserAttributes
13
+ }
14
+ }
@@ -0,0 +1,8 @@
1
+ query($id: String!) {
2
+ ovalContent(id: $id) {
3
+ id
4
+ name
5
+ url
6
+ originalFilename
7
+ }
8
+ }
@@ -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
@@ -18,4 +20,7 @@ query($id: String!) {
18
20
  }
19
21
  }
20
22
  }
23
+ currentUser {
24
+ ...CurrentUserAttributes
25
+ }
21
26
  }
@@ -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
+ };
@@ -0,0 +1,3 @@
1
+ import { addToast } from 'foremanReact/components/ToastsList';
2
+
3
+ export const showToast = dispatch => toast => dispatch(addToast(toast));
@@ -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 = [{ title: __('Name') }];
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: [{ title: ovalContent.name }],
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 actions = [];
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
- actions={actions}
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
+ };