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.
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
+ };