foreman_openscap 5.1.0 → 5.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/app/graphql/mutations/oval_policies/create.rb +33 -0
  3. data/app/helpers/policies_helper.rb +1 -1
  4. data/app/models/concerns/foreman_openscap/data_stream_content.rb +1 -1
  5. data/app/models/concerns/foreman_openscap/oval_facet_hostgroup_extensions.rb +1 -0
  6. data/app/models/foreman_openscap/arf_report.rb +1 -1
  7. data/app/models/foreman_openscap/oval_content.rb +1 -1
  8. data/app/services/foreman_openscap/oval/configure.rb +16 -13
  9. data/app/services/foreman_openscap/oval/setup_check.rb +1 -1
  10. data/lib/foreman_openscap/engine.rb +3 -2
  11. data/lib/foreman_openscap/version.rb +1 -1
  12. data/webpack/components/EditableInput.js +16 -10
  13. data/webpack/components/IndexTable/index.js +7 -2
  14. data/webpack/components/LinkButton.js +14 -2
  15. data/webpack/components/withLoading.js +3 -1
  16. data/webpack/graphql/mutations/createOvalPolicy.gql +22 -0
  17. data/webpack/graphql/queries/ovalPolicy.gql +3 -0
  18. data/webpack/helpers/formFieldsHelper.js +51 -1
  19. data/webpack/helpers/globalIdHelper.js +4 -2
  20. data/webpack/helpers/pathsHelper.js +5 -3
  21. data/webpack/helpers/toastsHelper.js +3 -0
  22. data/webpack/routes/OvalContents/OvalContentsIndex/__tests__/OvalContentsIndex.fixtures.js +6 -1
  23. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/OvalPoliciesTable.js +18 -1
  24. data/webpack/routes/OvalPolicies/OvalPoliciesNew/HostgroupSelect.js +135 -0
  25. data/webpack/routes/OvalPolicies/OvalPoliciesNew/NewOvalPolicyForm.js +119 -0
  26. data/webpack/routes/OvalPolicies/OvalPoliciesNew/NewOvalPolicyFormHelpers.js +107 -0
  27. data/webpack/routes/OvalPolicies/OvalPoliciesNew/OvalPoliciesNew.js +32 -0
  28. data/webpack/routes/OvalPolicies/OvalPoliciesNew/__tests__/OvalPoliciesNew.fixtures.js +147 -0
  29. data/webpack/routes/OvalPolicies/OvalPoliciesNew/__tests__/OvalPoliciesNew.test.js +172 -0
  30. data/webpack/routes/OvalPolicies/OvalPoliciesNew/index.js +11 -0
  31. data/webpack/routes/OvalPolicies/OvalPoliciesShow/CvesTable.js +2 -2
  32. data/webpack/routes/OvalPolicies/OvalPoliciesShow/DetailsTab.js +2 -0
  33. data/webpack/routes/OvalPolicies/OvalPoliciesShow/OvalPoliciesShowHelper.js +4 -3
  34. data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesEdit.test.js +27 -0
  35. data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesShow.fixtures.js +11 -1
  36. data/webpack/routes/routes.js +7 -0
  37. data/webpack/testHelper.js +22 -0
  38. metadata +12 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0deaa4503a6ab004120595983e5b6fac947691d57e7b8da5d38a0aed3316f1a6
4
- data.tar.gz: 7447310d905705fbf71ca93f1cca7b2314e7d13827e90ce9c3a8b321cfcba411
3
+ metadata.gz: 4bc5e1e84b8fb45ea3276289081235a25c71eeeef8442a31861361c7888f2a0d
4
+ data.tar.gz: 8c768d06360b78e08a54b471aab8d2a8599e40e1809c6a859390b2af709c9a77
5
5
  SHA512:
6
- metadata.gz: 192e4e96375311fbf3225aa5e715eed99797338a8ba2be9d5b19cdf3f49dcae9ff5a78cb0136df175744360dbcc5ffc2ce453058ac31839a71f0373e79fa22cc
7
- data.tar.gz: 2b231f618e80bc0bd187417328afae0c2b7f4c7a10d0954dc26ff554fd4ad34c67073b252cdc6f86ae6194d9a9d7c53c5c08bb00ba7348d884e334baac82f1fe
6
+ metadata.gz: 68ba2f54eed3dfdeb62b5495d9a33ceb8ca808fa233a4eedfb642e270e42721a72502adaff8fb4d19c1666dcd5db72368ba078230358bf2d645560d169fe142d
7
+ data.tar.gz: a6f3b3c43456945f87a2c0349acd4e41187456e690f29bad16576c84dde9ffa23974a58a574fa93849f481f370504dd053cacdc88e9fd81e0c9fca4f44ed915c
@@ -0,0 +1,33 @@
1
+ module Mutations
2
+ module OvalPolicies
3
+ class Create < ::Mutations::BaseMutation
4
+ description 'Creates a new OVAL Policy'
5
+ graphql_name 'CreateOvalPolicyMutation'
6
+
7
+ resource_class ::ForemanOpenscap::OvalPolicy
8
+
9
+ argument :name, String
10
+ argument :description, String, required: false
11
+ argument :period, String
12
+ argument :weekday, String, required: false
13
+ argument :day_of_month, Integer, required: false
14
+ argument :cron_line, String, required: false
15
+ argument :oval_content_id, Integer, required: true
16
+ argument :hostgroup_ids, [Integer], required: false
17
+
18
+ field :oval_policy, Types::OvalPolicy, 'The new OVAL Policy.', null: true
19
+ field :check_collection, [Types::OvalCheck], 'A collection of checks to detect OVAL policy configuration error', null: false
20
+
21
+ def resolve(hostgroup_ids:, **params)
22
+ policy = ::ForemanOpenscap::OvalPolicy.new params
23
+ validate_object(policy)
24
+ authorize!(policy, :create)
25
+ check_collection = ::ForemanOpenscap::Oval::Configure.new.assign(policy, hostgroup_ids, ::Hostgroup)
26
+ {
27
+ :oval_policy => policy,
28
+ :check_collection => check_collection.checks
29
+ }
30
+ end
31
+ end
32
+ end
33
+ end
@@ -93,7 +93,7 @@ module PoliciesHelper
93
93
  def tailoring_file_profile_selector(form, tailoring_file)
94
94
  if tailoring_file
95
95
  select_f form, :tailoring_file_profile_id, tailoring_file.scap_content_profiles, :id, :title,
96
- { :selected => tailoring_file.scap_content_profiles.first.id },
96
+ { :selected => @policy.tailoring_file_profile_id },
97
97
  { :label => _("XCCDF Profile in Tailoring File"),
98
98
  :help_inline => _("This profile will be used to override the one from scap content") }
99
99
  else
@@ -6,7 +6,7 @@ module ForemanOpenscap
6
6
  validates_with ForemanOpenscap::DataStreamValidator
7
7
 
8
8
  after_save :create_profiles, :if => lambda { |ds_content| ds_content.scap_file_previously_changed? }
9
- before_destroy ActiveRecord::Base::EnsureNotUsedBy.new(:policies)
9
+ before_destroy EnsureNotUsedBy.new(:policies)
10
10
  end
11
11
 
12
12
  def proxy_url
@@ -11,6 +11,7 @@ module ForemanOpenscap
11
11
  :on => :id,
12
12
  :rename => :oval_policy_id,
13
13
  :complete_value => false,
14
+ :only_explicit => true,
14
15
  :ext_method => :find_by_oval_policy_id,
15
16
  :operators => ['= ']
16
17
  end
@@ -119,7 +119,7 @@ module ForemanOpenscap
119
119
  PolicyArfReport.where(:arf_report_id => arf_report.id, :policy_id => policy.id, :digest => params[:digest]).first_or_create!
120
120
  if params[:logs]
121
121
  params[:logs].each do |log|
122
- src = Source.find_or_create(log[:source])
122
+ src = Source.find_or_create_by(value: log[:source])
123
123
  msg = nil
124
124
  if src.logs.count > 0
125
125
  msg = Log.where(:source_id => src.id).order(:id => :desc).first.message
@@ -5,7 +5,7 @@ module ForemanOpenscap
5
5
  include Taxonomix
6
6
  include ScapFileContent
7
7
 
8
- before_destroy ActiveRecord::Base::EnsureNotUsedBy.new(:oval_policies)
8
+ before_destroy EnsureNotUsedBy.new(:oval_policies)
9
9
 
10
10
  scoped_search :on => :name, :complete_value => true
11
11
 
@@ -16,21 +16,26 @@ module ForemanOpenscap
16
16
  if model_class == ::Hostgroup
17
17
  roles_method = :inherited_and_own_ansible_roles
18
18
  ids_setter = :hostgroup_ids=
19
+ check_id = :hostgroups_without_proxy
19
20
  elsif model_class == ::Host::Managed
20
21
  roles_method = :all_ansible_roles
21
22
  ids_setter = :host_ids=
23
+ check_id = :hosts_without_proxy
22
24
  else
23
25
  raise "Unexpected model_class, expected ::Hostgroup or ::Host::Managed, got: #{model_class}"
24
26
  end
25
27
 
26
28
  items_with_proxy, items_without_proxy = openscap_proxy_associated(ids, model_class)
27
29
 
28
- oval_policy.send(ids_setter, items_with_proxy.pluck(:id))
29
30
 
30
- check_collection = without_proxy_to_check items_without_proxy
31
+ if items_without_proxy.any?
32
+ return without_proxy_to_check items_without_proxy, check_id
33
+ end
34
+
35
+ oval_policy.send(ids_setter, items_with_proxy.pluck(:id))
31
36
 
32
37
  unless oval_policy.save
33
- return check_collection.add_check model_to_check(oval_policy)
38
+ return check_collection.add_check model_to_check(oval_policy, :oval_policy_errors)
34
39
  end
35
40
 
36
41
  check_collection.merge modify_items(items_with_proxy, oval_policy, ansible_role, roles_method)
@@ -47,31 +52,29 @@ module ForemanOpenscap
47
52
  role_ids = item.ansible_role_ids + [ansible_role.id]
48
53
  item.ansible_role_ids = role_ids unless item.send(roles_method).include? ansible_role
49
54
  item.save if item.changed?
50
- memo.add_check model_to_check(item)
55
+ memo.add_check model_to_check(item, item.is_a?(::Hostgroup) ? 'hostgroup' : 'host')
51
56
  add_overrides ansible_role.ansible_variables, item, @config
52
57
  memo
53
58
  end
54
59
  end
55
60
 
56
- def without_proxy_to_check(items)
61
+ def without_proxy_to_check(items, check_id)
57
62
  items.reduce(CheckCollection.new) do |memo, item|
58
63
  memo.add_check(
59
64
  SetupCheck.new(
60
65
  :title => (_("Was %s configured successfully?") % item.class.name),
61
- :fail_msg => (_("Assign openscap_proxy to %s before proceeding.") % item.name)
66
+ :fail_msg => (_("Assign openscap_proxy to %s before proceeding.") % item.name),
67
+ :id => check_id
62
68
  ).fail!
63
69
  )
64
70
  end
65
71
  end
66
72
 
67
- def model_to_s(model)
68
- model.is_a?(::Hostgroup) ? 'hostgroup' : 'host'
69
- end
70
-
71
- def model_to_check(model)
73
+ def model_to_check(model, check_id)
72
74
  check = SetupCheck.new(
73
- :title => (_("Was %{model_name} %{name} configured successfully?") % { :model_name => model_to_s(model), :name => model.name }),
74
- :errors => model.errors.to_h
75
+ :title => (_("Was %{model_name} %{name} configured successfully?") % { :model_name => model.class.name, :name => model.name }),
76
+ :errors => model.errors.to_h,
77
+ :id => check_id
75
78
  )
76
79
  model.errors.any? ? check.fail! : check.pass!
77
80
  end
@@ -1,7 +1,7 @@
1
1
  module ForemanOpenscap
2
2
  module Oval
3
3
  class SetupCheck
4
- attr_reader :result, :id, :errors
4
+ attr_reader :result, :id, :title, :errors
5
5
 
6
6
  def initialize(hash)
7
7
  @id = hash[:id]
@@ -191,8 +191,8 @@ module ForemanOpenscap
191
191
  "dashboard link hosts with no reports", "dashboard link hosts with alerts disabled",
192
192
  "widgets not in dashboard show up in list"] })
193
193
  # strong params
194
- parameter_filter Host::Managed, :openscap_proxy_id, :openscap_proxy
195
- parameter_filter Hostgroup, :openscap_proxy_id, :openscap_proxy
194
+ parameter_filter ::Host::Managed, :openscap_proxy_id, :openscap_proxy
195
+ parameter_filter ::Hostgroup, :openscap_proxy_id, :openscap_proxy
196
196
  parameter_filter Log, :result
197
197
 
198
198
  proxy_description = N_('OpenSCAP Proxy to use for fetching SCAP content and uploading ARF reports. Leave blank and override appropriate parameters when using proxy load balancer.')
@@ -227,6 +227,7 @@ module ForemanOpenscap
227
227
  register_graphql_mutation_field :delete_oval_policy, ::Mutations::OvalPolicies::Delete
228
228
  register_graphql_mutation_field :delete_oval_content, ::Mutations::OvalContents::Delete
229
229
  register_graphql_mutation_field :update_oval_policy, ::Mutations::OvalPolicies::Update
230
+ register_graphql_mutation_field :create_oval_policy, ::Mutations::OvalPolicies::Create
230
231
 
231
232
  register_facet ForemanOpenscap::Host::OvalFacet, :oval_facet do
232
233
  configure_host do
@@ -1,3 +1,3 @@
1
1
  module ForemanOpenscap
2
- VERSION = "5.1.0".freeze
2
+ VERSION = "5.2.0".freeze
3
3
  end
@@ -49,6 +49,7 @@ const EditableInput = props => {
49
49
  const onCancel = () => {
50
50
  setInputValue(props.value);
51
51
  setEditing(false);
52
+ setError('');
52
53
  };
53
54
 
54
55
  const onChange = value => {
@@ -58,20 +59,24 @@ const EditableInput = props => {
58
59
  setInputValue(value);
59
60
  };
60
61
 
62
+ const editBtn = (
63
+ <SplitItem>
64
+ <Button
65
+ className="inline-edit-icon"
66
+ aria-label={`edit ${props.attrName}`}
67
+ variant="plain"
68
+ onClick={() => setEditing(true)}
69
+ >
70
+ <PencilAltIcon />
71
+ </Button>
72
+ </SplitItem>
73
+ );
74
+
61
75
  if (!editing) {
62
76
  return (
63
77
  <Split>
64
78
  <SplitItem>{props.value || <i>{__('None provided')}</i>}</SplitItem>
65
- <SplitItem>
66
- <Button
67
- className="inline-edit-icon"
68
- aria-label={`edit ${props.attrName}`}
69
- variant="plain"
70
- onClick={() => setEditing(true)}
71
- >
72
- <PencilAltIcon />
73
- </Button>
74
- </SplitItem>
79
+ {props.allowed && editBtn}
75
80
  </Split>
76
81
  );
77
82
  }
@@ -142,6 +147,7 @@ const EditableInput = props => {
142
147
  };
143
148
 
144
149
  EditableInput.propTypes = {
150
+ allowed: PropTypes.bool.isRequired,
145
151
  value: PropTypes.string,
146
152
  onConfirm: PropTypes.func.isRequired,
147
153
  attrName: PropTypes.string.isRequired,
@@ -41,7 +41,12 @@ const IndexTable = ({
41
41
  />
42
42
  </FlexItem>
43
43
  </Flex>
44
- <Table aria-label={ariaTableLabel} cells={columns} {...rest}>
44
+ <Table
45
+ aria-label={ariaTableLabel}
46
+ cells={columns}
47
+ {...rest}
48
+ variant="compact"
49
+ >
45
50
  <TableHeader />
46
51
  <TableBody />
47
52
  </Table>
@@ -59,7 +64,7 @@ IndexTable.propTypes = {
59
64
  };
60
65
 
61
66
  IndexTable.defaultProps = {
62
- toolbarBtns: [],
67
+ toolbarBtns: null,
63
68
  };
64
69
 
65
70
  export default IndexTable;
@@ -3,9 +3,19 @@ import { Link } from 'react-router-dom';
3
3
  import { Button } from '@patternfly/react-core';
4
4
  import PropTypes from 'prop-types';
5
5
 
6
- const LinkButton = ({ path, btnVariant, btnText, isDisabled }) => (
6
+ const LinkButton = ({
7
+ path,
8
+ btnVariant,
9
+ btnText,
10
+ isDisabled,
11
+ btnAriaLabel,
12
+ }) => (
7
13
  <Link to={path}>
8
- <Button variant={btnVariant} isDisabled={isDisabled}>
14
+ <Button
15
+ variant={btnVariant}
16
+ isDisabled={isDisabled}
17
+ aria-label={btnAriaLabel}
18
+ >
9
19
  {btnText}
10
20
  </Button>
11
21
  </Link>
@@ -16,11 +26,13 @@ LinkButton.propTypes = {
16
26
  btnText: PropTypes.string.isRequired,
17
27
  btnVariant: PropTypes.string,
18
28
  isDisabled: PropTypes.bool,
29
+ btnAriaLabel: PropTypes.string,
19
30
  };
20
31
 
21
32
  LinkButton.defaultProps = {
22
33
  btnVariant: 'primary',
23
34
  isDisabled: false,
35
+ btnAriaLabel: null,
24
36
  };
25
37
 
26
38
  export default LinkButton;
@@ -10,7 +10,6 @@ import {
10
10
  import EmptyState from './EmptyState';
11
11
 
12
12
  const errorStateTitle = __('Error!');
13
- const emptyStateBody = '';
14
13
 
15
14
  const pluckData = (data, path) => {
16
15
  const split = path.split('.');
@@ -28,6 +27,7 @@ const withLoading = Component => {
28
27
  resultPath,
29
28
  renameData,
30
29
  emptyStateTitle,
30
+ emptyStateBody,
31
31
  permissions,
32
32
  primaryButton,
33
33
  shouldRefetch,
@@ -87,6 +87,7 @@ const withLoading = Component => {
87
87
  resultPath: PropTypes.string.isRequired,
88
88
  renameData: PropTypes.func,
89
89
  emptyStateTitle: PropTypes.string.isRequired,
90
+ emptyStateBody: PropTypes.string,
90
91
  permissions: PropTypes.array,
91
92
  primaryButton: PropTypes.node,
92
93
  shouldRefetch: PropTypes.bool,
@@ -97,6 +98,7 @@ const withLoading = Component => {
97
98
  permissions: [],
98
99
  primaryButton: null,
99
100
  shouldRefetch: false,
101
+ emptyStateBody: '',
100
102
  };
101
103
 
102
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
+ }
@@ -9,6 +9,9 @@ query($id: String!) {
9
9
  weekday
10
10
  dayOfMonth
11
11
  description
12
+ meta {
13
+ canEdit
14
+ }
12
15
  hostgroups {
13
16
  nodes {
14
17
  id
@@ -1,9 +1,58 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
 
4
- import { FormGroup, TextInput } from '@patternfly/react-core';
4
+ import {
5
+ FormGroup,
6
+ TextInput,
7
+ TextArea,
8
+ FormSelect,
9
+ FormSelectOption,
10
+ } from '@patternfly/react-core';
5
11
  import { ExclamationCircleIcon } from '@patternfly/react-icons';
6
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
+
7
56
  const wrapFieldProps = fieldProps => {
8
57
  const { onChange } = fieldProps;
9
58
  // modify onChange args to correctly wire formik with pf4 input handlers
@@ -61,3 +110,4 @@ const fieldWithHandlers = Component => {
61
110
  };
62
111
 
63
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
 
@@ -1,11 +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
6
  const newPath = path => `${path}/new`;
7
7
 
8
- export const modelPath = (basePath, model) => `${basePath}/${decodeId(model)}`;
8
+ export const modelPath = (basePath, model) =>
9
+ `${basePath}/${decodeModelId(model)}`;
9
10
 
10
11
  // react-router uses path-to-regexp, should we use it as well in a future?
11
12
  // https://github.com/pillarjs/path-to-regexp/tree/v1.7.0#compile-reverse-path-to-regexp
@@ -22,6 +23,7 @@ export const ovalContentsShowPath = showPath(ovalContentsPath);
22
23
  export const ovalContentsNewPath = newPath(ovalContentsPath);
23
24
  export const ovalPoliciesPath = experimental('/compliance/oval_policies');
24
25
  export const ovalPoliciesShowPath = `${showPath(ovalPoliciesPath)}/:tab?`;
26
+ export const ovalPoliciesNewPath = newPath(ovalPoliciesPath);
25
27
  export const hostsPath = '/hosts';
26
- export const newJobPath = '/job_invocations/new';
28
+ export const newJobPath = newPath('/job_invocations');
27
29
  export const hostsShowPath = showPath(hostsPath);
@@ -0,0 +1,3 @@
1
+ import { addToast } from 'foremanReact/redux/actions/toasts';
2
+
3
+ export const showToast = dispatch => toast => dispatch(addToast(toast));
@@ -56,7 +56,8 @@ const ovalContentNodes = [
56
56
  thirdContent(),
57
57
  fourthContent(),
58
58
  ];
59
- const ovalContents = {
59
+
60
+ export const ovalContents = {
60
61
  totalCount: ovalContentNodes.length,
61
62
  nodes: ovalContentNodes,
62
63
  };
@@ -70,6 +71,10 @@ export const mocks = ovalContentMockFactory(
70
71
  { currentUser: admin }
71
72
  );
72
73
 
74
+ export const unpagedMocks = ovalContentMockFactory({}, ovalContents, {
75
+ currentUser: admin,
76
+ });
77
+
73
78
  export const paginatedMocks = ovalContentMockFactory(
74
79
  { first: 10, last: 5 },
75
80
  { totalCount: 7, nodes: [secondContent(), fourthContent()] },
@@ -1,5 +1,7 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
+ import { Button } from '@patternfly/react-core';
4
+
3
5
  import { translate as __ } from 'foremanReact/common/I18n';
4
6
 
5
7
  import IndexTable from '../../../components/IndexTable';
@@ -7,7 +9,11 @@ import withLoading from '../../../components/withLoading';
7
9
  import withDeleteModal from '../../../components/withDeleteModal';
8
10
 
9
11
  import { linkCell } from '../../../helpers/tableHelper';
10
- import { ovalPoliciesPath, modelPath } from '../../../helpers/pathsHelper';
12
+ import {
13
+ ovalPoliciesPath,
14
+ modelPath,
15
+ ovalPoliciesNewPath,
16
+ } from '../../../helpers/pathsHelper';
11
17
 
12
18
  const OvalPoliciesTable = props => {
13
19
  const columns = [{ title: __('Name') }, { title: __('OVAL Content') }];
@@ -33,6 +39,16 @@ const OvalPoliciesTable = props => {
33
39
  return actions;
34
40
  };
35
41
 
42
+ const createBtn = (
43
+ <Button
44
+ onClick={() => props.history.push(ovalPoliciesNewPath)}
45
+ variant="primary"
46
+ aria-label="create_oval_policy"
47
+ >
48
+ {__('Create OVAL Policy')}
49
+ </Button>
50
+ );
51
+
36
52
  return (
37
53
  <IndexTable
38
54
  columns={columns}
@@ -42,6 +58,7 @@ const OvalPoliciesTable = props => {
42
58
  totalCount={props.totalCount}
43
59
  history={props.history}
44
60
  ariaTableLabel={__('OVAL Policies Table')}
61
+ toolbarBtns={createBtn}
45
62
  />
46
63
  );
47
64
  };