foreman_openscap 4.3.2 → 5.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (130) 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/create.rb +33 -0
  6. data/app/graphql/mutations/oval_policies/delete.rb +9 -0
  7. data/app/graphql/mutations/oval_policies/update.rb +15 -0
  8. data/app/graphql/types/oval_check.rb +11 -0
  9. data/app/graphql/types/oval_content.rb +2 -0
  10. data/app/graphql/types/oval_policy.rb +3 -0
  11. data/app/helpers/arf_report_dashboard_helper.rb +2 -4
  12. data/app/helpers/compliance_hosts_helper.rb +1 -1
  13. data/app/helpers/policies_helper.rb +2 -2
  14. data/app/models/concerns/foreman_openscap/host_extensions.rb +0 -6
  15. data/app/models/concerns/foreman_openscap/oval_facet_hostgroup_extensions.rb +16 -0
  16. data/app/models/concerns/foreman_openscap/policy_common.rb +1 -1
  17. data/app/models/foreman_openscap/oval_content.rb +2 -0
  18. data/app/services/foreman_openscap/client_config/base.rb +1 -0
  19. data/app/services/foreman_openscap/client_config/puppet.rb +6 -2
  20. data/app/services/foreman_openscap/oval/configure.rb +16 -13
  21. data/app/services/foreman_openscap/oval/setup.rb +5 -5
  22. data/app/services/foreman_openscap/oval/setup_check.rb +5 -2
  23. data/app/views/api/v2/compliance/oval_contents/destroy.json.rabl +3 -0
  24. data/app/views/arf_reports/_metrics.html.erb +4 -4
  25. data/app/views/compliance_hosts/show.html.erb +4 -6
  26. data/app/views/dashboard/_compliance_reports_breakdown_widget.html.erb +4 -3
  27. data/app/views/policy_dashboard/_policy_chart_widget.html.erb +3 -2
  28. data/db/migrate/20200117135424_migrate_port_overrides_to_int.rb +2 -1
  29. data/db/migrate/20201202110213_update_puppet_port_param_type.rb +2 -1
  30. data/db/migrate/20210819143316_drop_unused_tables.rb +6 -0
  31. data/lib/foreman_openscap/engine.rb +6 -7
  32. data/lib/foreman_openscap/version.rb +1 -1
  33. data/package.json +3 -6
  34. data/test/functional/api/v2/compliance/oval_reports_controller_test.rb +1 -1
  35. data/test/functional/api/v2/compliance/policies_controller_test.rb +2 -0
  36. data/test/graphql/mutations/oval_policies/delete_mutation_test.rb +63 -0
  37. data/test/graphql/queries/oval_content_query_test.rb +29 -0
  38. data/test/helpers/arf_report_dashboard_helper_test.rb +9 -10
  39. data/test/helpers/policy_dashboard_helper_test.rb +1 -1
  40. data/test/test_plugin_helper.rb +9 -4
  41. data/test/unit/policy_test.rb +1 -1
  42. data/test/unit/services/config_name_service_test.rb +1 -0
  43. data/test/unit/services/hostgroup_overrider_test.rb +2 -1
  44. data/test/unit/services/lookup_key_overrider_test.rb +4 -1
  45. data/test/unit/services/oval/setup_check_test.rb +37 -0
  46. data/webpack/components/ConfirmModal.js +63 -0
  47. data/webpack/components/ConfirmModal.scss +3 -0
  48. data/webpack/components/EditableInput.js +163 -0
  49. data/webpack/components/EditableInput.scss +3 -0
  50. data/webpack/components/EmptyState.js +12 -3
  51. data/webpack/components/IndexLayout.js +11 -4
  52. data/webpack/components/IndexTable/index.js +21 -16
  53. data/webpack/components/LinkButton.js +38 -0
  54. data/webpack/components/withDeleteModal.js +51 -0
  55. data/webpack/components/withLoading.js +44 -5
  56. data/webpack/graphql/mutations/createOvalPolicy.gql +22 -0
  57. data/webpack/graphql/mutations/deleteOvalContent.gql +9 -0
  58. data/webpack/graphql/mutations/deleteOvalPolicy.gql +9 -0
  59. data/webpack/graphql/mutations/updateOvalPolicy.gql +14 -0
  60. data/webpack/graphql/queries/currentUserAttributes.gql +11 -0
  61. data/webpack/graphql/queries/cves.gql +5 -0
  62. data/webpack/graphql/queries/hostgroups.gql +14 -0
  63. data/webpack/graphql/queries/ovalContent.gql +8 -0
  64. data/webpack/graphql/queries/ovalContents.gql +8 -0
  65. data/webpack/graphql/queries/ovalPolicies.gql +8 -0
  66. data/webpack/graphql/queries/ovalPolicy.gql +8 -0
  67. data/webpack/helpers/formFieldsHelper.js +113 -0
  68. data/webpack/helpers/globalIdHelper.js +4 -2
  69. data/webpack/helpers/mutationHelper.js +68 -0
  70. data/webpack/helpers/pathsHelper.js +10 -3
  71. data/webpack/helpers/permissionsHelper.js +42 -0
  72. data/webpack/helpers/toastHelper.js +3 -0
  73. data/webpack/helpers/toastsHelper.js +3 -0
  74. data/webpack/routes/OvalContents/OvalContentsIndex/OvalContentsIndex.js +26 -0
  75. data/webpack/routes/OvalContents/OvalContentsIndex/OvalContentsTable.js +50 -5
  76. data/webpack/routes/OvalContents/OvalContentsIndex/__tests__/OvalContentsDestroy.fixtures.js +105 -0
  77. data/webpack/routes/OvalContents/OvalContentsIndex/__tests__/OvalContentsDestroy.test.js +124 -0
  78. data/webpack/routes/OvalContents/OvalContentsIndex/__tests__/OvalContentsIndex.fixtures.js +98 -77
  79. data/webpack/routes/OvalContents/OvalContentsIndex/__tests__/OvalContentsIndex.test.js +53 -6
  80. data/webpack/routes/OvalContents/OvalContentsIndex/index.js +7 -1
  81. data/webpack/routes/OvalContents/OvalContentsNew/OvalContentsNew.js +138 -0
  82. data/webpack/routes/OvalContents/OvalContentsNew/OvalContentsNew.scss +3 -0
  83. data/webpack/routes/OvalContents/OvalContentsNew/OvalContentsNewHelper.js +73 -0
  84. data/webpack/routes/OvalContents/OvalContentsNew/__tests__/OvalContentsNew.test.js +104 -0
  85. data/webpack/routes/OvalContents/OvalContentsNew/index.js +13 -0
  86. data/webpack/routes/OvalContents/OvalContentsShow/OvalContentsShow.js +62 -0
  87. data/webpack/routes/OvalContents/OvalContentsShow/OvalContentsShow.test.js +45 -0
  88. data/{locale/de/foreman_openscap.edit.po → webpack/routes/OvalContents/OvalContentsShow/OvalContentsShowHelper.js} +0 -0
  89. data/webpack/routes/OvalContents/OvalContentsShow/index.js +35 -0
  90. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/OvalPoliciesIndex.js +18 -2
  91. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/OvalPoliciesTable.js +34 -4
  92. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/__tests__/OvalPoliciesDestroy.fixtures.js +101 -0
  93. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/__tests__/OvalPoliciesDestroy.test.js +117 -0
  94. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/__tests__/OvalPoliciesIndex.fixtures.js +71 -21
  95. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/__tests__/OvalPoliciesIndex.test.js +34 -2
  96. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/index.js +7 -1
  97. data/webpack/routes/OvalPolicies/OvalPoliciesNew/HostgroupSelect.js +135 -0
  98. data/webpack/routes/OvalPolicies/OvalPoliciesNew/NewOvalPolicyForm.js +119 -0
  99. data/webpack/routes/OvalPolicies/OvalPoliciesNew/NewOvalPolicyFormHelpers.js +107 -0
  100. data/webpack/routes/OvalPolicies/OvalPoliciesNew/OvalPoliciesNew.js +32 -0
  101. data/webpack/routes/OvalPolicies/OvalPoliciesNew/__tests__/OvalPoliciesNew.fixtures.js +147 -0
  102. data/webpack/routes/OvalPolicies/OvalPoliciesNew/__tests__/OvalPoliciesNew.test.js +172 -0
  103. data/webpack/routes/OvalPolicies/OvalPoliciesNew/index.js +11 -0
  104. data/webpack/routes/OvalPolicies/OvalPoliciesShow/CvesTab.js +1 -0
  105. data/webpack/routes/OvalPolicies/OvalPoliciesShow/CvesTable.js +2 -2
  106. data/webpack/routes/OvalPolicies/OvalPoliciesShow/DetailsTab.js +87 -0
  107. data/webpack/routes/OvalPolicies/OvalPoliciesShow/HostgroupsTab.js +49 -0
  108. data/webpack/routes/OvalPolicies/OvalPoliciesShow/HostgroupsTable.js +38 -0
  109. data/webpack/routes/OvalPolicies/OvalPoliciesShow/OvalPoliciesShow.js +15 -11
  110. data/webpack/routes/OvalPolicies/OvalPoliciesShow/OvalPoliciesShowHelper.js +80 -2
  111. data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesEdit.fixtures.js +48 -0
  112. data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesEdit.test.js +202 -0
  113. data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesShow.fixtures.js +50 -4
  114. data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesShow.test.js +64 -4
  115. data/webpack/routes/OvalPolicies/OvalPoliciesShow/index.js +4 -0
  116. data/webpack/routes/routes.js +21 -0
  117. data/webpack/testHelper.js +64 -2
  118. metadata +80 -37
  119. data/locale/en_GB/foreman_openscap.edit.po +0 -0
  120. data/locale/es/foreman_openscap.edit.po +0 -0
  121. data/locale/fr/foreman_openscap.edit.po +0 -0
  122. data/locale/gl/foreman_openscap.edit.po +0 -0
  123. data/locale/it/foreman_openscap.edit.po +0 -0
  124. data/locale/ja/foreman_openscap.edit.po +0 -0
  125. data/locale/ko/foreman_openscap.edit.po +0 -0
  126. data/locale/pt_BR/foreman_openscap.edit.po +0 -0
  127. data/locale/ru/foreman_openscap.edit.po +0 -0
  128. data/locale/sv_SE/foreman_openscap.edit.po +0 -0
  129. data/locale/zh_CN/foreman_openscap.edit.po +0 -0
  130. data/locale/zh_TW/foreman_openscap.edit.po +0 -0
@@ -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}
@@ -34,10 +42,10 @@ const IndexTable = props => {
34
42
  </FlexItem>
35
43
  </Flex>
36
44
  <Table
37
- aria-label={props.ariaTableLabel}
38
- cells={props.columns}
39
- rows={props.rows}
40
- actions={props.actions}
45
+ aria-label={ariaTableLabel}
46
+ cells={columns}
47
+ {...rest}
48
+ variant="compact"
41
49
  >
42
50
  <TableHeader />
43
51
  <TableBody />
@@ -49,17 +57,14 @@ const IndexTable = props => {
49
57
  IndexTable.propTypes = {
50
58
  history: PropTypes.object.isRequired,
51
59
  pagination: PropTypes.object.isRequired,
52
- toolbarBtns: PropTypes.array,
60
+ toolbarBtns: PropTypes.node,
53
61
  totalCount: PropTypes.number.isRequired,
54
62
  ariaTableLabel: PropTypes.string.isRequired,
55
63
  columns: PropTypes.array.isRequired,
56
- rows: PropTypes.array.isRequired,
57
- actions: PropTypes.array,
58
64
  };
59
65
 
60
66
  IndexTable.defaultProps = {
61
- toolbarBtns: [],
62
- actions: [],
67
+ toolbarBtns: null,
63
68
  };
64
69
 
65
70
  export default IndexTable;
@@ -0,0 +1,38 @@
1
+ import React from 'react';
2
+ import { Link } from 'react-router-dom';
3
+ import { Button } from '@patternfly/react-core';
4
+ import PropTypes from 'prop-types';
5
+
6
+ const LinkButton = ({
7
+ path,
8
+ btnVariant,
9
+ btnText,
10
+ isDisabled,
11
+ btnAriaLabel,
12
+ }) => (
13
+ <Link to={path}>
14
+ <Button
15
+ variant={btnVariant}
16
+ isDisabled={isDisabled}
17
+ aria-label={btnAriaLabel}
18
+ >
19
+ {btnText}
20
+ </Button>
21
+ </Link>
22
+ );
23
+
24
+ LinkButton.propTypes = {
25
+ path: PropTypes.string.isRequired,
26
+ btnText: PropTypes.string.isRequired,
27
+ btnVariant: PropTypes.string,
28
+ isDisabled: PropTypes.bool,
29
+ btnAriaLabel: PropTypes.string,
30
+ };
31
+
32
+ LinkButton.defaultProps = {
33
+ btnVariant: 'primary',
34
+ isDisabled: false,
35
+ btnAriaLabel: null,
36
+ };
37
+
38
+ export default LinkButton;
@@ -0,0 +1,51 @@
1
+ import React, { useState } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { translate as __, sprintf } from 'foremanReact/common/I18n';
4
+ import ConfirmModal from './ConfirmModal';
5
+
6
+ const withDelete = Component => {
7
+ const Subcomponent = ({
8
+ confirmDeleteTitle,
9
+ submitDelete,
10
+ prepareMutation,
11
+ ...rest
12
+ }) => {
13
+ const [toDelete, setToDelete] = useState(null);
14
+
15
+ const toggleModal = (item = null) => {
16
+ setToDelete(item);
17
+ };
18
+
19
+ return (
20
+ <React.Fragment>
21
+ <Component {...rest} toggleModal={toggleModal} />
22
+ <ConfirmModal
23
+ title={confirmDeleteTitle}
24
+ text={
25
+ toDelete
26
+ ? sprintf(
27
+ __('Are you sure you want to delete %s?'),
28
+ toDelete.name
29
+ )
30
+ : ''
31
+ }
32
+ onClose={toggleModal}
33
+ isOpen={!!toDelete}
34
+ onConfirm={submitDelete}
35
+ prepareMutation={() => prepareMutation(toggleModal)}
36
+ record={toDelete}
37
+ />
38
+ </React.Fragment>
39
+ );
40
+ };
41
+
42
+ Subcomponent.propTypes = {
43
+ confirmDeleteTitle: PropTypes.string.isRequired,
44
+ submitDelete: PropTypes.func.isRequired,
45
+ prepareMutation: PropTypes.func.isRequired,
46
+ };
47
+
48
+ return Subcomponent;
49
+ };
50
+
51
+ export default withDelete;
@@ -1,12 +1,15 @@
1
- import React from 'react';
1
+ import React, { useEffect } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import { translate as __ } from 'foremanReact/common/I18n';
4
-
5
4
  import Loading from 'foremanReact/components/Loading';
5
+ import {
6
+ permissionCheck,
7
+ permissionDeniedMsg,
8
+ } from '../helpers/permissionsHelper';
9
+
6
10
  import EmptyState from './EmptyState';
7
11
 
8
12
  const errorStateTitle = __('Error!');
9
- const emptyStateBody = '';
10
13
 
11
14
  const pluckData = (data, path) => {
12
15
  const split = path.split('.');
@@ -24,9 +27,19 @@ const withLoading = Component => {
24
27
  resultPath,
25
28
  renameData,
26
29
  emptyStateTitle,
30
+ emptyStateBody,
31
+ permissions,
32
+ primaryButton,
33
+ shouldRefetch,
27
34
  ...rest
28
35
  }) => {
29
- const { loading, error, data } = fetchFn(rest);
36
+ const { loading, error, data, refetch } = fetchFn(rest);
37
+
38
+ useEffect(() => {
39
+ if (shouldRefetch) {
40
+ refetch();
41
+ }
42
+ }, [shouldRefetch, refetch]);
30
43
 
31
44
  if (loading) {
32
45
  return <Loading />;
@@ -42,10 +55,28 @@ const withLoading = Component => {
42
55
  );
43
56
  }
44
57
 
58
+ const check = permissionCheck(data.currentUser, permissions);
59
+
60
+ if (!check.allowed) {
61
+ return (
62
+ <EmptyState
63
+ lock
64
+ title={__('Permission denied')}
65
+ body={permissionDeniedMsg(check.permissions.map(item => item.name))}
66
+ />
67
+ );
68
+ }
69
+
45
70
  const result = pluckData(data, resultPath);
46
71
 
47
72
  if ((Array.isArray(result) && result.length === 0) || !result) {
48
- return <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,18 @@ const withLoading = Component => {
56
87
  resultPath: PropTypes.string.isRequired,
57
88
  renameData: PropTypes.func,
58
89
  emptyStateTitle: PropTypes.string.isRequired,
90
+ emptyStateBody: PropTypes.string,
91
+ permissions: PropTypes.array,
92
+ primaryButton: PropTypes.node,
93
+ shouldRefetch: PropTypes.bool,
59
94
  };
60
95
 
61
96
  Subcomponent.defaultProps = {
62
97
  renameData: data => data,
98
+ permissions: [],
99
+ primaryButton: null,
100
+ shouldRefetch: false,
101
+ emptyStateBody: '',
63
102
  };
64
103
 
65
104
  return Subcomponent;
@@ -0,0 +1,22 @@
1
+ mutation CreateOvalPolicy($name: String!, $period: String!, $cronLine: String, $ovalContentId: Int!, $hostgroupIds: [Int!]) {
2
+ createOvalPolicy(input: {name: $name, period: $period, cronLine: $cronLine, ovalContentId: $ovalContentId, hostgroupIds: $hostgroupIds}) {
3
+ ovalPolicy {
4
+ name
5
+ id
6
+ period
7
+ cronLine
8
+ hostgroups {
9
+ nodes {
10
+ name
11
+ id
12
+ }
13
+ }
14
+ }
15
+ checkCollection {
16
+ id
17
+ errors
18
+ failMsg
19
+ result
20
+ }
21
+ }
22
+ }
@@ -0,0 +1,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
@@ -7,6 +9,9 @@ query($id: String!) {
7
9
  weekday
8
10
  dayOfMonth
9
11
  description
12
+ meta {
13
+ canEdit
14
+ }
10
15
  hostgroups {
11
16
  nodes {
12
17
  id
@@ -18,4 +23,7 @@ query($id: String!) {
18
23
  }
19
24
  }
20
25
  }
26
+ currentUser {
27
+ ...CurrentUserAttributes
28
+ }
21
29
  }
@@ -0,0 +1,113 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+
4
+ import {
5
+ FormGroup,
6
+ TextInput,
7
+ TextArea,
8
+ FormSelect,
9
+ FormSelectOption,
10
+ } from '@patternfly/react-core';
11
+ import { ExclamationCircleIcon } from '@patternfly/react-icons';
12
+
13
+ export const SelectField = props => {
14
+ const { selectItems, field, form } = props;
15
+ const fieldProps = wrapFieldProps(field);
16
+
17
+ const valid = shouldValidate(form, field.name);
18
+
19
+ return (
20
+ <FormGroup
21
+ label={props.label}
22
+ isRequired={props.isRequired}
23
+ helperTextInvalid={form.errors[field.name]}
24
+ helperTextInvalidIcon={<ExclamationCircleIcon />}
25
+ validated={valid}
26
+ >
27
+ <FormSelect
28
+ {...fieldProps}
29
+ className="without_select2"
30
+ aria-label={fieldProps.name}
31
+ validated={valid}
32
+ isDisabled={form.isSubmitting}
33
+ >
34
+ <FormSelectOption key={0} value="" label={props.blankLabel} />
35
+ {selectItems.map((item, idx) => (
36
+ <FormSelectOption key={idx + 1} value={item.id} label={item.name} />
37
+ ))}
38
+ </FormSelect>
39
+ </FormGroup>
40
+ );
41
+ };
42
+
43
+ SelectField.propTypes = {
44
+ selectItems: PropTypes.array,
45
+ label: PropTypes.string.isRequired,
46
+ isRequired: PropTypes.bool,
47
+ field: PropTypes.object.isRequired,
48
+ form: PropTypes.object.isRequired,
49
+ blankLabel: PropTypes.string.isRequired,
50
+ };
51
+ SelectField.defaultProps = {
52
+ selectItems: [],
53
+ isRequired: false,
54
+ };
55
+
56
+ const wrapFieldProps = fieldProps => {
57
+ const { onChange } = fieldProps;
58
+ // modify onChange args to correctly wire formik with pf4 input handlers
59
+ const wrappedOnChange = (value, event) => {
60
+ onChange(event);
61
+ };
62
+
63
+ return { ...fieldProps, onChange: wrappedOnChange };
64
+ };
65
+
66
+ const shouldValidate = (form, fieldName) => {
67
+ if (form.touched[fieldName]) {
68
+ return form.errors[fieldName] ? 'error' : 'success';
69
+ }
70
+
71
+ return 'noval';
72
+ };
73
+
74
+ const fieldWithHandlers = Component => {
75
+ const Subcomponent = ({ label, form, field, isRequired, ...rest }) => {
76
+ const fieldProps = wrapFieldProps(field);
77
+ const valid = shouldValidate(form, field.name);
78
+
79
+ return (
80
+ <FormGroup
81
+ label={label}
82
+ isRequired={isRequired}
83
+ helperTextInvalid={form.errors[field.name]}
84
+ helperTextInvalidIcon={<ExclamationCircleIcon />}
85
+ validated={valid}
86
+ >
87
+ <Component
88
+ aria-label={fieldProps.name}
89
+ {...fieldProps}
90
+ {...rest}
91
+ validated={valid}
92
+ isDisabled={form.isSubmitting}
93
+ />
94
+ </FormGroup>
95
+ );
96
+ };
97
+
98
+ Subcomponent.propTypes = {
99
+ form: PropTypes.object.isRequired,
100
+ field: PropTypes.object.isRequired,
101
+ label: PropTypes.string.isRequired,
102
+ isRequired: PropTypes.bool,
103
+ };
104
+
105
+ Subcomponent.defaultProps = {
106
+ isRequired: false,
107
+ };
108
+
109
+ return Subcomponent;
110
+ };
111
+
112
+ export const TextField = fieldWithHandlers(TextInput);
113
+ export const TextAreaField = fieldWithHandlers(TextArea);
@@ -4,8 +4,10 @@ const idSeparator = '-';
4
4
  const versionSeparator = ':';
5
5
  const defaultVersion = '01';
6
6
 
7
- export const decodeId = model => {
8
- const split = atob(model.id).split(idSeparator);
7
+ export const decodeModelId = model => decodeId(model.id);
8
+
9
+ export const decodeId = globalId => {
10
+ const split = atob(globalId).split(idSeparator);
9
11
  return parseInt(last(split), 10);
10
12
  };
11
13
 
@@ -0,0 +1,68 @@
1
+ import { useMutation } from '@apollo/client';
2
+ import { translate as __, sprintf } from 'foremanReact/common/I18n';
3
+
4
+ import { useCurrentPagination, pageToVars } from './pageParamsHelper';
5
+
6
+ const formatError = (error, name) =>
7
+ sprintf(__('There was a following error when deleting %(name)s: %(error)s'), {
8
+ name,
9
+ error,
10
+ });
11
+
12
+ const joinErrors = errors => errors.map(err => err.message).join(', ');
13
+
14
+ const onError = (showToast, resourceName) => error => {
15
+ showToast({ type: 'error', message: formatError(error, resourceName) });
16
+ };
17
+
18
+ const onCompleted = (
19
+ toggleModal,
20
+ showToast,
21
+ mutationName,
22
+ successMsg,
23
+ resourceName
24
+ ) => data => {
25
+ toggleModal();
26
+ const { errors } = data[mutationName];
27
+ if (Array.isArray(errors) && errors.length > 0) {
28
+ showToast({
29
+ type: 'error',
30
+ message: formatError(joinErrors(errors), resourceName),
31
+ });
32
+ } else {
33
+ showToast({
34
+ type: 'success',
35
+ message: successMsg,
36
+ });
37
+ }
38
+ };
39
+
40
+ export const prepareMutation = (
41
+ history,
42
+ showToast,
43
+ refetchQuery,
44
+ mutationName,
45
+ successMsg,
46
+ mutation,
47
+ resourceName
48
+ ) => toggleModal => {
49
+ const pagination = pageToVars(useCurrentPagination(history));
50
+
51
+ const options = {
52
+ refetchQueries: [{ query: refetchQuery, variables: pagination }],
53
+ onCompleted: onCompleted(
54
+ toggleModal,
55
+ showToast,
56
+ mutationName,
57
+ successMsg,
58
+ resourceName
59
+ ),
60
+ onError: onError(showToast, resourceName),
61
+ };
62
+
63
+ return useMutation(mutation, options);
64
+ };
65
+
66
+ export const submitDelete = (mutation, id) => {
67
+ mutation({ variables: { id } });
68
+ };
@@ -1,10 +1,12 @@
1
- import { decodeId } from './globalIdHelper';
1
+ import { decodeModelId } from './globalIdHelper';
2
2
 
3
3
  const experimental = path => `/experimental${path}`;
4
4
 
5
5
  const showPath = path => `${path}/:id`;
6
+ const newPath = path => `${path}/new`;
6
7
 
7
- export const modelPath = (basePath, model) => `${basePath}/${decodeId(model)}`;
8
+ export const modelPath = (basePath, model) =>
9
+ `${basePath}/${decodeModelId(model)}`;
8
10
 
9
11
  // react-router uses path-to-regexp, should we use it as well in a future?
10
12
  // https://github.com/pillarjs/path-to-regexp/tree/v1.7.0#compile-reverse-path-to-regexp
@@ -14,9 +16,14 @@ export const resolvePath = (path, params) =>
14
16
  path
15
17
  );
16
18
 
19
+ export const ovalContentsApiPath = '/api/v2/compliance/oval_contents';
20
+
17
21
  export const ovalContentsPath = experimental('/compliance/oval_contents');
22
+ export const ovalContentsShowPath = showPath(ovalContentsPath);
23
+ export const ovalContentsNewPath = newPath(ovalContentsPath);
18
24
  export const ovalPoliciesPath = experimental('/compliance/oval_policies');
19
25
  export const ovalPoliciesShowPath = `${showPath(ovalPoliciesPath)}/:tab?`;
26
+ export const ovalPoliciesNewPath = newPath(ovalPoliciesPath);
20
27
  export const hostsPath = '/hosts';
21
- export const newJobPath = '/job_invocations/new';
28
+ export const newJobPath = newPath('/job_invocations');
22
29
  export const hostsShowPath = showPath(hostsPath);