foreman_openscap 5.0.0 → 5.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/app/graphql/mutations/oval_contents/delete.rb +9 -0
  3. data/app/graphql/mutations/oval_policies/delete.rb +9 -0
  4. data/app/graphql/mutations/oval_policies/update.rb +15 -0
  5. data/app/graphql/types/oval_check.rb +11 -0
  6. data/app/graphql/types/oval_content.rb +2 -0
  7. data/app/graphql/types/oval_policy.rb +3 -0
  8. data/app/models/concerns/foreman_openscap/host_extensions.rb +0 -6
  9. data/app/models/concerns/foreman_openscap/oval_facet_hostgroup_extensions.rb +15 -0
  10. data/app/models/foreman_openscap/oval_content.rb +2 -0
  11. data/app/services/foreman_openscap/oval/configure.rb +1 -1
  12. data/app/services/foreman_openscap/oval/setup.rb +5 -5
  13. data/app/services/foreman_openscap/oval/setup_check.rb +5 -2
  14. data/db/migrate/20210819143316_drop_unused_tables.rb +6 -0
  15. data/lib/foreman_openscap/engine.rb +6 -1
  16. data/lib/foreman_openscap/version.rb +1 -1
  17. data/package.json +3 -6
  18. data/test/graphql/mutations/oval_policies/delete_mutation_test.rb +63 -0
  19. data/test/graphql/queries/oval_content_query_test.rb +29 -0
  20. data/test/unit/services/hostgroup_overrider_test.rb +1 -1
  21. data/test/unit/services/oval/setup_check_test.rb +37 -0
  22. data/webpack/components/ConfirmModal.js +63 -0
  23. data/webpack/components/ConfirmModal.scss +3 -0
  24. data/webpack/components/EditableInput.js +157 -0
  25. data/webpack/components/EditableInput.scss +3 -0
  26. data/webpack/components/EmptyState.js +4 -1
  27. data/webpack/components/IndexLayout.js +11 -4
  28. data/webpack/components/IndexTable/index.js +17 -17
  29. data/webpack/components/LinkButton.js +26 -0
  30. data/webpack/components/withDeleteModal.js +51 -0
  31. data/webpack/components/withLoading.js +21 -3
  32. data/webpack/graphql/mutations/deleteOvalContent.gql +9 -0
  33. data/webpack/graphql/mutations/deleteOvalPolicy.gql +9 -0
  34. data/webpack/graphql/mutations/updateOvalPolicy.gql +14 -0
  35. data/webpack/graphql/queries/hostgroups.gql +14 -0
  36. data/webpack/graphql/queries/ovalContent.gql +8 -0
  37. data/webpack/graphql/queries/ovalContents.gql +3 -0
  38. data/webpack/graphql/queries/ovalPolicies.gql +3 -0
  39. data/webpack/helpers/formFieldsHelper.js +63 -0
  40. data/webpack/helpers/mutationHelper.js +68 -0
  41. data/webpack/helpers/pathsHelper.js +5 -0
  42. data/webpack/helpers/toastHelper.js +3 -0
  43. data/webpack/routes/OvalContents/OvalContentsIndex/OvalContentsIndex.js +25 -0
  44. data/webpack/routes/OvalContents/OvalContentsIndex/OvalContentsTable.js +41 -4
  45. data/webpack/routes/OvalContents/OvalContentsIndex/__tests__/OvalContentsDestroy.fixtures.js +105 -0
  46. data/webpack/routes/OvalContents/OvalContentsIndex/__tests__/OvalContentsDestroy.test.js +124 -0
  47. data/webpack/routes/OvalContents/OvalContentsIndex/__tests__/OvalContentsIndex.fixtures.js +61 -59
  48. data/webpack/routes/OvalContents/OvalContentsIndex/__tests__/OvalContentsIndex.test.js +29 -8
  49. data/webpack/routes/OvalContents/OvalContentsIndex/index.js +7 -1
  50. data/webpack/routes/OvalContents/OvalContentsNew/OvalContentsNew.js +138 -0
  51. data/webpack/routes/OvalContents/OvalContentsNew/OvalContentsNew.scss +3 -0
  52. data/webpack/routes/OvalContents/OvalContentsNew/OvalContentsNewHelper.js +73 -0
  53. data/webpack/routes/OvalContents/OvalContentsNew/__tests__/OvalContentsNew.test.js +104 -0
  54. data/webpack/routes/OvalContents/OvalContentsNew/index.js +13 -0
  55. data/webpack/routes/OvalContents/OvalContentsShow/OvalContentsShow.js +62 -0
  56. data/webpack/routes/OvalContents/OvalContentsShow/OvalContentsShow.test.js +45 -0
  57. data/webpack/routes/OvalContents/OvalContentsShow/OvalContentsShowHelper.js +0 -0
  58. data/webpack/routes/OvalContents/OvalContentsShow/index.js +35 -0
  59. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/OvalPoliciesIndex.js +17 -2
  60. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/OvalPoliciesTable.js +16 -3
  61. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/__tests__/OvalPoliciesDestroy.fixtures.js +101 -0
  62. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/__tests__/OvalPoliciesDestroy.test.js +117 -0
  63. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/__tests__/OvalPoliciesIndex.fixtures.js +57 -41
  64. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/__tests__/OvalPoliciesIndex.test.js +14 -2
  65. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/index.js +7 -1
  66. data/webpack/routes/OvalPolicies/OvalPoliciesShow/DetailsTab.js +85 -0
  67. data/webpack/routes/OvalPolicies/OvalPoliciesShow/HostgroupsTab.js +49 -0
  68. data/webpack/routes/OvalPolicies/OvalPoliciesShow/HostgroupsTable.js +38 -0
  69. data/webpack/routes/OvalPolicies/OvalPoliciesShow/OvalPoliciesShow.js +15 -11
  70. data/webpack/routes/OvalPolicies/OvalPoliciesShow/OvalPoliciesShowHelper.js +77 -0
  71. data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesEdit.fixtures.js +48 -0
  72. data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesEdit.test.js +175 -0
  73. data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesShow.fixtures.js +28 -1
  74. data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesShow.test.js +47 -4
  75. data/webpack/routes/OvalPolicies/OvalPoliciesShow/index.js +3 -0
  76. data/webpack/routes/routes.js +14 -0
  77. data/webpack/testHelper.js +9 -1
  78. metadata +46 -3
@@ -29,7 +29,7 @@ const EmptyStateIcon = ({ error, search, lock }) => {
29
29
  return <PfEmptyStateIcon icon={CubeIcon} />;
30
30
  };
31
31
 
32
- const EmptyState = ({ title, body, error, search, lock }) => (
32
+ const EmptyState = ({ title, body, error, search, lock, primaryButton }) => (
33
33
  <Bullseye>
34
34
  <PfEmptyState variant={EmptyStateVariant.small}>
35
35
  <EmptyStateIcon error={!!error} search={search} lock={lock} />
@@ -37,6 +37,7 @@ const EmptyState = ({ title, body, error, search, lock }) => (
37
37
  {title}
38
38
  </Title>
39
39
  <EmptyStateBody>{body}</EmptyStateBody>
40
+ {primaryButton}
40
41
  </PfEmptyState>
41
42
  </Bullseye>
42
43
  );
@@ -59,6 +60,7 @@ EmptyState.propTypes = {
59
60
  error: PropTypes.oneOfType([PropTypes.shape({}), PropTypes.string]),
60
61
  search: PropTypes.bool,
61
62
  lock: PropTypes.bool,
63
+ primaryButton: PropTypes.node,
62
64
  };
63
65
 
64
66
  EmptyState.defaultProps = {
@@ -68,6 +70,7 @@ EmptyState.defaultProps = {
68
70
  error: undefined,
69
71
  search: false,
70
72
  lock: false,
73
+ primaryButton: null,
71
74
  };
72
75
 
73
76
  export default EmptyState;
@@ -1,6 +1,7 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import { Helmet } from 'react-helmet';
4
+ import ToastsList from 'foremanReact/components/ToastsList';
4
5
  import {
5
6
  Grid,
6
7
  GridItem,
@@ -11,25 +12,31 @@ import {
11
12
 
12
13
  import './IndexLayout.scss';
13
14
 
14
- const IndexLayout = ({ pageTitle, children }) => (
15
+ const IndexLayout = ({ pageTitle, children, contentWidthSpan }) => (
15
16
  <React.Fragment>
16
17
  <Helmet>
17
18
  <title>{pageTitle}</title>
18
19
  </Helmet>
20
+ <ToastsList />
19
21
  <Grid className="scap-page-grid">
20
- <GridItem span={12}>
22
+ <GridItem span={12} className="pf-u-pb-xl">
21
23
  <TextContent>
22
24
  <Text component={TextVariants.h1}>{pageTitle}</Text>
23
25
  </TextContent>
24
26
  </GridItem>
25
- <GridItem span={12}>{children}</GridItem>
27
+ <GridItem span={contentWidthSpan}>{children}</GridItem>
26
28
  </Grid>
27
29
  </React.Fragment>
28
30
  );
29
31
 
30
32
  IndexLayout.propTypes = {
31
33
  pageTitle: PropTypes.string.isRequired,
32
- children: PropTypes.object.isRequired,
34
+ children: PropTypes.oneOfType([PropTypes.node, PropTypes.object]).isRequired,
35
+ contentWidthSpan: PropTypes.number,
36
+ };
37
+
38
+ IndexLayout.defaultProps = {
39
+ contentWidthSpan: 12,
33
40
  };
34
41
 
35
42
  export default IndexLayout;
@@ -6,13 +6,21 @@ import { usePaginationOptions } from 'foremanReact/components/Pagination/Paginat
6
6
 
7
7
  import { preparePerPageOptions, refreshPage } from './IndexTableHelper';
8
8
 
9
- const IndexTable = props => {
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(props.history, { page: 1, perPage });
19
+ refreshPage(history, { page: 1, perPage });
12
20
  };
13
21
 
14
22
  const handlePageSelected = (event, page) => {
15
- refreshPage(props.history, { ...props.pagination, page });
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>{props.toolbarBtns}</FlexItem>
31
+ <FlexItem>{toolbarBtns}</FlexItem>
24
32
  <FlexItem align={{ default: 'alignRight' }}>
25
33
  <Pagination
26
- itemCount={props.totalCount}
27
- page={props.pagination.page}
28
- perPage={props.pagination.perPage}
34
+ itemCount={totalCount}
35
+ page={pagination.page}
36
+ perPage={pagination.perPage}
29
37
  onSetPage={handlePageSelected}
30
38
  onPerPageSelect={handlePerPageSelected}
31
39
  perPageOptions={perPageOptions}
@@ -33,12 +41,7 @@ const IndexTable = props => {
33
41
  />
34
42
  </FlexItem>
35
43
  </Flex>
36
- <Table
37
- aria-label={props.ariaTableLabel}
38
- cells={props.columns}
39
- rows={props.rows}
40
- actions={props.actions}
41
- >
44
+ <Table aria-label={ariaTableLabel} cells={columns} {...rest}>
42
45
  <TableHeader />
43
46
  <TableBody />
44
47
  </Table>
@@ -49,17 +52,14 @@ const IndexTable = props => {
49
52
  IndexTable.propTypes = {
50
53
  history: PropTypes.object.isRequired,
51
54
  pagination: PropTypes.object.isRequired,
52
- toolbarBtns: PropTypes.array,
55
+ toolbarBtns: PropTypes.node,
53
56
  totalCount: PropTypes.number.isRequired,
54
57
  ariaTableLabel: PropTypes.string.isRequired,
55
58
  columns: PropTypes.array.isRequired,
56
- rows: PropTypes.array.isRequired,
57
- actions: PropTypes.array,
58
59
  };
59
60
 
60
61
  IndexTable.defaultProps = {
61
62
  toolbarBtns: [],
62
- actions: [],
63
63
  };
64
64
 
65
65
  export default IndexTable;
@@ -0,0 +1,26 @@
1
+ import React from 'react';
2
+ import { Link } from 'react-router-dom';
3
+ import { Button } from '@patternfly/react-core';
4
+ import PropTypes from 'prop-types';
5
+
6
+ const LinkButton = ({ path, btnVariant, btnText, isDisabled }) => (
7
+ <Link to={path}>
8
+ <Button variant={btnVariant} isDisabled={isDisabled}>
9
+ {btnText}
10
+ </Button>
11
+ </Link>
12
+ );
13
+
14
+ LinkButton.propTypes = {
15
+ path: PropTypes.string.isRequired,
16
+ btnText: PropTypes.string.isRequired,
17
+ btnVariant: PropTypes.string,
18
+ isDisabled: PropTypes.bool,
19
+ };
20
+
21
+ LinkButton.defaultProps = {
22
+ btnVariant: 'primary',
23
+ isDisabled: false,
24
+ };
25
+
26
+ export default LinkButton;
@@ -0,0 +1,51 @@
1
+ import React, { useState } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { translate as __, sprintf } from 'foremanReact/common/I18n';
4
+ import ConfirmModal from './ConfirmModal';
5
+
6
+ const withDelete = Component => {
7
+ const Subcomponent = ({
8
+ confirmDeleteTitle,
9
+ submitDelete,
10
+ prepareMutation,
11
+ ...rest
12
+ }) => {
13
+ const [toDelete, setToDelete] = useState(null);
14
+
15
+ const toggleModal = (item = null) => {
16
+ setToDelete(item);
17
+ };
18
+
19
+ return (
20
+ <React.Fragment>
21
+ <Component {...rest} toggleModal={toggleModal} />
22
+ <ConfirmModal
23
+ title={confirmDeleteTitle}
24
+ text={
25
+ toDelete
26
+ ? sprintf(
27
+ __('Are you sure you want to delete %s?'),
28
+ toDelete.name
29
+ )
30
+ : ''
31
+ }
32
+ onClose={toggleModal}
33
+ isOpen={!!toDelete}
34
+ onConfirm={submitDelete}
35
+ prepareMutation={() => prepareMutation(toggleModal)}
36
+ record={toDelete}
37
+ />
38
+ </React.Fragment>
39
+ );
40
+ };
41
+
42
+ Subcomponent.propTypes = {
43
+ confirmDeleteTitle: PropTypes.string.isRequired,
44
+ submitDelete: PropTypes.func.isRequired,
45
+ prepareMutation: PropTypes.func.isRequired,
46
+ };
47
+
48
+ return Subcomponent;
49
+ };
50
+
51
+ export default withDelete;
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import React, { useEffect } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import { translate as __ } from 'foremanReact/common/I18n';
4
4
  import Loading from 'foremanReact/components/Loading';
@@ -29,9 +29,17 @@ const withLoading = Component => {
29
29
  renameData,
30
30
  emptyStateTitle,
31
31
  permissions,
32
+ primaryButton,
33
+ shouldRefetch,
32
34
  ...rest
33
35
  }) => {
34
- const { loading, error, data } = fetchFn(rest);
36
+ const { loading, error, data, refetch } = fetchFn(rest);
37
+
38
+ useEffect(() => {
39
+ if (shouldRefetch) {
40
+ refetch();
41
+ }
42
+ }, [shouldRefetch, refetch]);
35
43
 
36
44
  if (loading) {
37
45
  return <Loading />;
@@ -62,7 +70,13 @@ const withLoading = Component => {
62
70
  const result = pluckData(data, resultPath);
63
71
 
64
72
  if ((Array.isArray(result) && result.length === 0) || !result) {
65
- return <EmptyState title={emptyStateTitle} body={emptyStateBody} />;
73
+ return (
74
+ <EmptyState
75
+ title={emptyStateTitle}
76
+ body={emptyStateBody}
77
+ primaryButton={primaryButton}
78
+ />
79
+ );
66
80
  }
67
81
 
68
82
  return <Component {...rest} {...renameData(data)} />;
@@ -74,11 +88,15 @@ const withLoading = Component => {
74
88
  renameData: PropTypes.func,
75
89
  emptyStateTitle: PropTypes.string.isRequired,
76
90
  permissions: PropTypes.array,
91
+ primaryButton: PropTypes.node,
92
+ shouldRefetch: PropTypes.bool,
77
93
  };
78
94
 
79
95
  Subcomponent.defaultProps = {
80
96
  renameData: data => data,
81
97
  permissions: [],
98
+ primaryButton: null,
99
+ shouldRefetch: false,
82
100
  };
83
101
 
84
102
  return Subcomponent;
@@ -0,0 +1,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,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
+ }
@@ -8,6 +8,9 @@ query($first: Int, $last: Int) {
8
8
  name
9
9
  url
10
10
  originalFilename
11
+ meta {
12
+ canDestroy
13
+ }
11
14
  }
12
15
  }
13
16
  currentUser {
@@ -6,6 +6,9 @@ query($first: Int, $last: Int) {
6
6
  nodes {
7
7
  id
8
8
  name
9
+ meta {
10
+ canDestroy
11
+ }
9
12
  ovalContent {
10
13
  name
11
14
  }
@@ -0,0 +1,63 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+
4
+ import { FormGroup, TextInput } from '@patternfly/react-core';
5
+ import { ExclamationCircleIcon } from '@patternfly/react-icons';
6
+
7
+ const wrapFieldProps = fieldProps => {
8
+ const { onChange } = fieldProps;
9
+ // modify onChange args to correctly wire formik with pf4 input handlers
10
+ const wrappedOnChange = (value, event) => {
11
+ onChange(event);
12
+ };
13
+
14
+ return { ...fieldProps, onChange: wrappedOnChange };
15
+ };
16
+
17
+ const shouldValidate = (form, fieldName) => {
18
+ if (form.touched[fieldName]) {
19
+ return form.errors[fieldName] ? 'error' : 'success';
20
+ }
21
+
22
+ return 'noval';
23
+ };
24
+
25
+ const fieldWithHandlers = Component => {
26
+ const Subcomponent = ({ label, form, field, isRequired, ...rest }) => {
27
+ const fieldProps = wrapFieldProps(field);
28
+ const valid = shouldValidate(form, field.name);
29
+
30
+ return (
31
+ <FormGroup
32
+ label={label}
33
+ isRequired={isRequired}
34
+ helperTextInvalid={form.errors[field.name]}
35
+ helperTextInvalidIcon={<ExclamationCircleIcon />}
36
+ validated={valid}
37
+ >
38
+ <Component
39
+ aria-label={fieldProps.name}
40
+ {...fieldProps}
41
+ {...rest}
42
+ validated={valid}
43
+ isDisabled={form.isSubmitting}
44
+ />
45
+ </FormGroup>
46
+ );
47
+ };
48
+
49
+ Subcomponent.propTypes = {
50
+ form: PropTypes.object.isRequired,
51
+ field: PropTypes.object.isRequired,
52
+ label: PropTypes.string.isRequired,
53
+ isRequired: PropTypes.bool,
54
+ };
55
+
56
+ Subcomponent.defaultProps = {
57
+ isRequired: false,
58
+ };
59
+
60
+ return Subcomponent;
61
+ };
62
+
63
+ export const TextField = fieldWithHandlers(TextInput);
@@ -0,0 +1,68 @@
1
+ import { useMutation } from '@apollo/client';
2
+ import { translate as __, sprintf } from 'foremanReact/common/I18n';
3
+
4
+ import { useCurrentPagination, pageToVars } from './pageParamsHelper';
5
+
6
+ const formatError = (error, name) =>
7
+ sprintf(__('There was a following error when deleting %(name)s: %(error)s'), {
8
+ name,
9
+ error,
10
+ });
11
+
12
+ const joinErrors = errors => errors.map(err => err.message).join(', ');
13
+
14
+ const onError = (showToast, resourceName) => error => {
15
+ showToast({ type: 'error', message: formatError(error, resourceName) });
16
+ };
17
+
18
+ const onCompleted = (
19
+ toggleModal,
20
+ showToast,
21
+ mutationName,
22
+ successMsg,
23
+ resourceName
24
+ ) => data => {
25
+ toggleModal();
26
+ const { errors } = data[mutationName];
27
+ if (Array.isArray(errors) && errors.length > 0) {
28
+ showToast({
29
+ type: 'error',
30
+ message: formatError(joinErrors(errors), resourceName),
31
+ });
32
+ } else {
33
+ showToast({
34
+ type: 'success',
35
+ message: successMsg,
36
+ });
37
+ }
38
+ };
39
+
40
+ export const prepareMutation = (
41
+ history,
42
+ showToast,
43
+ refetchQuery,
44
+ mutationName,
45
+ successMsg,
46
+ mutation,
47
+ resourceName
48
+ ) => toggleModal => {
49
+ const pagination = pageToVars(useCurrentPagination(history));
50
+
51
+ const options = {
52
+ refetchQueries: [{ query: refetchQuery, variables: pagination }],
53
+ onCompleted: onCompleted(
54
+ toggleModal,
55
+ showToast,
56
+ mutationName,
57
+ successMsg,
58
+ resourceName
59
+ ),
60
+ onError: onError(showToast, resourceName),
61
+ };
62
+
63
+ return useMutation(mutation, options);
64
+ };
65
+
66
+ export const submitDelete = (mutation, id) => {
67
+ mutation({ variables: { id } });
68
+ };
@@ -3,6 +3,7 @@ import { decodeId } from './globalIdHelper';
3
3
  const experimental = path => `/experimental${path}`;
4
4
 
5
5
  const showPath = path => `${path}/:id`;
6
+ const newPath = path => `${path}/new`;
6
7
 
7
8
  export const modelPath = (basePath, model) => `${basePath}/${decodeId(model)}`;
8
9
 
@@ -14,7 +15,11 @@ export const resolvePath = (path, params) =>
14
15
  path
15
16
  );
16
17
 
18
+ export const ovalContentsApiPath = '/api/v2/compliance/oval_contents';
19
+
17
20
  export const ovalContentsPath = experimental('/compliance/oval_contents');
21
+ export const ovalContentsShowPath = showPath(ovalContentsPath);
22
+ export const ovalContentsNewPath = newPath(ovalContentsPath);
18
23
  export const ovalPoliciesPath = experimental('/compliance/oval_policies');
19
24
  export const ovalPoliciesShowPath = `${showPath(ovalPoliciesPath)}/:tab?`;
20
25
  export const hostsPath = '/hosts';
@@ -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 =>
@@ -34,6 +39,24 @@ const OvalContentsIndex = props => {
34
39
  pagination={pagination}
35
40
  emptyStateTitle={__('No OVAL Contents found.')}
36
41
  permissions={['view_oval_contents']}
42
+ confirmDeleteTitle={__('Delete OVAL Content')}
43
+ submitDelete={submitDelete}
44
+ prepareMutation={prepareMutation(
45
+ props.history,
46
+ props.showToast,
47
+ ovalContentsQuery,
48
+ 'deleteOvalContent',
49
+ __('OVAL Content successfully deleted.'),
50
+ deleteOvalContentMutation,
51
+ __('OVAL Content')
52
+ )}
53
+ primaryButton={
54
+ <LinkButton
55
+ path={ovalContentsNewPath}
56
+ btnText={__('Create OVAL Content')}
57
+ />
58
+ }
59
+ shouldRefetch={props.location?.state?.refreshOvalContents}
37
60
  />
38
61
  </IndexLayout>
39
62
  );
@@ -41,6 +64,8 @@ const OvalContentsIndex = props => {
41
64
 
42
65
  OvalContentsIndex.propTypes = {
43
66
  history: PropTypes.object.isRequired,
67
+ showToast: PropTypes.func.isRequired,
68
+ location: PropTypes.object.isRequired,
44
69
  };
45
70
 
46
71
  export default OvalContentsIndex;
@@ -1,9 +1,18 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import { translate as __ } from 'foremanReact/common/I18n';
4
+ import { Button } from '@patternfly/react-core';
4
5
 
5
6
  import withLoading from '../../../components/withLoading';
7
+ import withDeleteModal from '../../../components/withDeleteModal';
6
8
  import IndexTable from '../../../components/IndexTable';
9
+ import {
10
+ ovalContentsNewPath,
11
+ ovalContentsPath,
12
+ modelPath,
13
+ } from '../../../helpers/pathsHelper';
14
+
15
+ import { linkCell } from '../../../helpers/tableHelper';
7
16
 
8
17
  const OvalContentsTable = props => {
9
18
  const columns = [
@@ -14,24 +23,51 @@ const OvalContentsTable = props => {
14
23
 
15
24
  const rows = props.ovalContents.map(ovalContent => ({
16
25
  cells: [
17
- { title: ovalContent.name },
26
+ {
27
+ title: linkCell(
28
+ modelPath(ovalContentsPath, ovalContent),
29
+ ovalContent.name
30
+ ),
31
+ },
18
32
  { title: ovalContent.url || '' },
19
33
  { title: ovalContent.originalFilename || '' },
20
34
  ],
21
35
  ovalContent,
22
36
  }));
23
37
 
24
- const 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
+ );
25
60
 
26
61
  return (
27
62
  <IndexTable
28
63
  columns={columns}
29
64
  rows={rows}
30
- actions={actions}
65
+ actionResolver={actionResolver}
31
66
  pagination={props.pagination}
32
67
  totalCount={props.totalCount}
33
68
  history={props.history}
34
69
  ariaTableLabel={__('OVAL Contents table')}
70
+ toolbarBtns={createBtn}
35
71
  />
36
72
  );
37
73
  };
@@ -41,6 +77,7 @@ OvalContentsTable.propTypes = {
41
77
  pagination: PropTypes.object.isRequired,
42
78
  totalCount: PropTypes.number.isRequired,
43
79
  history: PropTypes.object.isRequired,
80
+ toggleModal: PropTypes.func.isRequired,
44
81
  };
45
82
 
46
- export default withLoading(OvalContentsTable);
83
+ export default withLoading(withDeleteModal(OvalContentsTable));