foreman_openscap 4.3.1 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/api/v2/compliance/arf_reports_controller.rb +0 -6
  3. data/app/controllers/api/v2/compliance/oval_policies_controller.rb +1 -1
  4. data/app/graphql/mutations/oval_contents/delete.rb +9 -0
  5. data/app/graphql/mutations/oval_policies/delete.rb +9 -0
  6. data/app/graphql/mutations/oval_policies/update.rb +15 -0
  7. data/app/graphql/types/oval_check.rb +11 -0
  8. data/app/graphql/types/oval_content.rb +2 -0
  9. data/app/graphql/types/oval_policy.rb +3 -0
  10. data/app/helpers/arf_report_dashboard_helper.rb +2 -4
  11. data/app/helpers/compliance_hosts_helper.rb +1 -1
  12. data/app/helpers/policies_helper.rb +1 -1
  13. data/app/models/concerns/foreman_openscap/host_extensions.rb +0 -6
  14. data/app/models/concerns/foreman_openscap/oval_facet_hostgroup_extensions.rb +15 -0
  15. data/app/models/foreman_openscap/oval_content.rb +2 -0
  16. data/app/services/foreman_openscap/client_config/base.rb +1 -0
  17. data/app/services/foreman_openscap/client_config/puppet.rb +6 -2
  18. data/app/services/foreman_openscap/oval/configure.rb +1 -1
  19. data/app/services/foreman_openscap/oval/setup.rb +5 -5
  20. data/app/services/foreman_openscap/oval/setup_check.rb +5 -2
  21. data/app/views/api/v2/compliance/oval_contents/destroy.json.rabl +3 -0
  22. data/app/views/arf_reports/_metrics.html.erb +4 -4
  23. data/app/views/compliance_hosts/show.html.erb +4 -6
  24. data/app/views/dashboard/_compliance_reports_breakdown_widget.html.erb +4 -3
  25. data/app/views/policy_dashboard/_policy_chart_widget.html.erb +3 -2
  26. data/db/migrate/20200117135424_migrate_port_overrides_to_int.rb +2 -1
  27. data/db/migrate/20201202110213_update_puppet_port_param_type.rb +2 -1
  28. data/db/migrate/20210819143316_drop_unused_tables.rb +6 -0
  29. data/lib/foreman_openscap/engine.rb +5 -7
  30. data/lib/foreman_openscap/version.rb +1 -1
  31. data/package.json +3 -6
  32. data/test/functional/api/v2/compliance/oval_reports_controller_test.rb +1 -1
  33. data/test/functional/api/v2/compliance/policies_controller_test.rb +2 -0
  34. data/test/graphql/mutations/oval_policies/delete_mutation_test.rb +63 -0
  35. data/test/graphql/queries/oval_content_query_test.rb +29 -0
  36. data/test/helpers/arf_report_dashboard_helper_test.rb +9 -10
  37. data/test/helpers/policy_dashboard_helper_test.rb +1 -1
  38. data/test/test_plugin_helper.rb +9 -4
  39. data/test/unit/policy_test.rb +1 -1
  40. data/test/unit/services/config_name_service_test.rb +1 -0
  41. data/test/unit/services/hostgroup_overrider_test.rb +2 -1
  42. data/test/unit/services/lookup_key_overrider_test.rb +4 -1
  43. data/test/unit/services/oval/setup_check_test.rb +37 -0
  44. data/webpack/components/ConfirmModal.js +63 -0
  45. data/webpack/components/ConfirmModal.scss +3 -0
  46. data/webpack/components/EditableInput.js +157 -0
  47. data/webpack/components/EditableInput.scss +3 -0
  48. data/webpack/components/EmptyState.js +12 -3
  49. data/webpack/components/IndexLayout.js +11 -4
  50. data/webpack/components/IndexTable/index.js +17 -18
  51. data/webpack/components/LinkButton.js +26 -0
  52. data/webpack/components/withDeleteModal.js +51 -0
  53. data/webpack/components/withLoading.js +41 -4
  54. data/webpack/graphql/mutations/deleteOvalContent.gql +9 -0
  55. data/webpack/graphql/mutations/deleteOvalPolicy.gql +9 -0
  56. data/webpack/graphql/mutations/updateOvalPolicy.gql +14 -0
  57. data/webpack/graphql/queries/currentUserAttributes.gql +11 -0
  58. data/webpack/graphql/queries/cves.gql +5 -0
  59. data/webpack/graphql/queries/hostgroups.gql +14 -0
  60. data/webpack/graphql/queries/ovalContent.gql +8 -0
  61. data/webpack/graphql/queries/ovalContents.gql +8 -0
  62. data/webpack/graphql/queries/ovalPolicies.gql +8 -0
  63. data/webpack/graphql/queries/ovalPolicy.gql +5 -0
  64. data/webpack/helpers/formFieldsHelper.js +63 -0
  65. data/webpack/helpers/mutationHelper.js +68 -0
  66. data/webpack/helpers/pathsHelper.js +5 -0
  67. data/webpack/helpers/permissionsHelper.js +42 -0
  68. data/webpack/helpers/toastHelper.js +3 -0
  69. data/webpack/routes/OvalContents/OvalContentsIndex/OvalContentsIndex.js +26 -0
  70. data/webpack/routes/OvalContents/OvalContentsIndex/OvalContentsTable.js +50 -5
  71. data/webpack/routes/OvalContents/OvalContentsIndex/__tests__/OvalContentsDestroy.fixtures.js +105 -0
  72. data/webpack/routes/OvalContents/OvalContentsIndex/__tests__/OvalContentsDestroy.test.js +124 -0
  73. data/webpack/routes/OvalContents/OvalContentsIndex/__tests__/OvalContentsIndex.fixtures.js +93 -77
  74. data/webpack/routes/OvalContents/OvalContentsIndex/__tests__/OvalContentsIndex.test.js +53 -6
  75. data/webpack/routes/OvalContents/OvalContentsIndex/index.js +7 -1
  76. data/webpack/routes/OvalContents/OvalContentsNew/OvalContentsNew.js +138 -0
  77. data/webpack/routes/OvalContents/OvalContentsNew/OvalContentsNew.scss +3 -0
  78. data/webpack/routes/OvalContents/OvalContentsNew/OvalContentsNewHelper.js +73 -0
  79. data/webpack/routes/OvalContents/OvalContentsNew/__tests__/OvalContentsNew.test.js +104 -0
  80. data/webpack/routes/OvalContents/OvalContentsNew/index.js +13 -0
  81. data/webpack/routes/OvalContents/OvalContentsShow/OvalContentsShow.js +62 -0
  82. data/webpack/routes/OvalContents/OvalContentsShow/OvalContentsShow.test.js +45 -0
  83. data/webpack/routes/OvalContents/OvalContentsShow/OvalContentsShowHelper.js +0 -0
  84. data/webpack/routes/OvalContents/OvalContentsShow/index.js +35 -0
  85. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/OvalPoliciesIndex.js +18 -2
  86. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/OvalPoliciesTable.js +16 -3
  87. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/__tests__/OvalPoliciesDestroy.fixtures.js +101 -0
  88. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/__tests__/OvalPoliciesDestroy.test.js +117 -0
  89. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/__tests__/OvalPoliciesIndex.fixtures.js +71 -21
  90. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/__tests__/OvalPoliciesIndex.test.js +34 -2
  91. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/index.js +7 -1
  92. data/webpack/routes/OvalPolicies/OvalPoliciesShow/CvesTab.js +1 -0
  93. data/webpack/routes/OvalPolicies/OvalPoliciesShow/DetailsTab.js +85 -0
  94. data/webpack/routes/OvalPolicies/OvalPoliciesShow/HostgroupsTab.js +49 -0
  95. data/webpack/routes/OvalPolicies/OvalPoliciesShow/HostgroupsTable.js +38 -0
  96. data/webpack/routes/OvalPolicies/OvalPoliciesShow/OvalPoliciesShow.js +15 -12
  97. data/webpack/routes/OvalPolicies/OvalPoliciesShow/OvalPoliciesShowHelper.js +77 -0
  98. data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesEdit.fixtures.js +48 -0
  99. data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesEdit.test.js +175 -0
  100. data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesShow.fixtures.js +40 -4
  101. data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesShow.test.js +64 -4
  102. data/webpack/routes/OvalPolicies/OvalPoliciesShow/index.js +4 -0
  103. data/webpack/routes/routes.js +14 -0
  104. data/webpack/testHelper.js +42 -2
  105. metadata +53 -7
@@ -7,14 +7,13 @@ class ArfReportDashboardHelperTest < ActionView::TestCase
7
7
  categories = { :passed => 'passed', :failed => 'failed' }
8
8
  report = { :passed => 23, :failed => 24 }
9
9
  colors = { :passed => '#FFF', :failed => '#000' }
10
- res = JSON.parse(breakdown_chart_data(categories, report, colors))
10
+ res = breakdown_chart_data(categories, report, colors)
11
11
  assert_equal ["passed", 23, "#FFF"], res.first
12
12
  assert_equal ["failed", 24, "#000"], res.last
13
13
  end
14
14
 
15
15
  test 'should return breakdown chart data for donut as json' do
16
- report = { :passed => 4, :failed => 7, :othered => 5 }
17
- res = JSON.parse(donut_breakdown_chart_data(report))
16
+ res = donut_breakdown_chart_data(:passed => 4, :failed => 7, :othered => 5)
18
17
  assert_equal 3, res.size
19
18
  assert_include res, ["Passed", 4, ArfReportDashboardHelper::COLORS[:passed]]
20
19
  assert_include res, ["Failed", 7, ArfReportDashboardHelper::COLORS[:failed]]
@@ -22,12 +21,12 @@ class ArfReportDashboardHelperTest < ActionView::TestCase
22
21
  end
23
22
 
24
23
  test 'should return data for report status chart' do
25
- res = JSON.parse(arf_report_status_chart_data(:passed => 6, :failed => 7, :othered => 8))
26
- assert_equal "Number of Events", res['yAxisLabel']
27
- assert_equal "Rule Results", res['xAxisLabel']
28
- assert_equal 3, res['data'].size
29
- assert_include res['data'], ["passed", 6]
30
- assert_include res['data'], ["failed", 7]
31
- assert_include res['data'], ["othered", 8]
24
+ res = arf_report_status_chart_data(:passed => 6, :failed => 7, :othered => 8)
25
+ assert_equal "Number of Events", res[:yAxisLabel]
26
+ assert_equal "Rule Results", res[:xAxisLabel]
27
+ assert_equal 3, res[:data].size
28
+ assert_include res[:data], [:passed, 6]
29
+ assert_include res[:data], [:failed, 7]
30
+ assert_include res[:data], [:othered, 8]
32
31
  end
33
32
  end
@@ -11,7 +11,7 @@ class PolicyDashboardHelperTest < ActionView::TestCase
11
11
  :inconclusive_hosts => 7,
12
12
  :report_missing => 8
13
13
  }
14
- res = JSON.parse(policy_breakdown_chart_data(report))
14
+ res = policy_breakdown_chart_data(report)
15
15
  assert_equal 4, res.size
16
16
  assert_include res, ['Compliant hosts', 5, PolicyDashboardHelper::COLORS[:compliant_hosts]]
17
17
  assert_include res, ['Incompliant hosts', 6, PolicyDashboardHelper::COLORS[:incompliant_hosts]]
@@ -5,19 +5,24 @@ require 'test_helper'
5
5
  FactoryBot.definition_file_paths << File.join(File.dirname(__FILE__), 'factories')
6
6
  # Add factories from foreman_ansible
7
7
  FactoryBot.definition_file_paths << File.join(ForemanAnsible::Engine.root, '/test/factories')
8
+ FactoryBot.definition_file_paths << File.join(ForemanPuppet::Engine.root, '/test/factories') if defined?(ForemanPuppet)
8
9
  FactoryBot.reload
9
10
 
10
11
  require "#{ForemanOpenscap::Engine.root}/test/fixtures/cve_fixtures"
11
12
 
12
13
  module ScapClientPuppetclass
14
+ def puppet_available?
15
+ defined?(ForemanPuppet)
16
+ end
17
+
13
18
  def setup_puppet_class
14
19
  puppet_config = ::ForemanOpenscap::ClientConfig::Puppet.new
15
- Puppetclass.find_by(:name => puppet_config.puppetclass_name)&.destroy
20
+ ForemanPuppet::Puppetclass.find_by(:name => puppet_config.puppetclass_name)&.destroy
16
21
 
17
22
  puppet_class = FactoryBot.create(:puppetclass, :name => puppet_config.puppetclass_name)
18
- server_param = FactoryBot.create(:puppetclass_lookup_key, :key => puppet_config.server_param, :default_value => nil)
19
- port_param = FactoryBot.create(:puppetclass_lookup_key, :key => puppet_config.port_param, :default_value => nil)
20
- policies_param = FactoryBot.create(:puppetclass_lookup_key, :key => puppet_config.policies_param, :default_value => nil)
23
+ server_param = FactoryBot.create(:puppetclass_lookup_key, :key => puppet_config.server_param, :default_value => nil, :override => false)
24
+ port_param = FactoryBot.create(:puppetclass_lookup_key, :key => puppet_config.port_param, :default_value => nil, :override => false)
25
+ policies_param = FactoryBot.create(:puppetclass_lookup_key, :key => puppet_config.policies_param, :default_value => nil, :override => false)
21
26
 
22
27
  env = FactoryBot.create :environment
23
28
 
@@ -75,7 +75,6 @@ class PolicyTest < ActiveSupport::TestCase
75
75
  asset = FactoryBot.create(:asset, :assetable_id => hg.id, :assetable_type => 'Hostgroup')
76
76
  policy = FactoryBot.create(:policy, :assets => [asset], :scap_content => @scap_content, :scap_content_profile => @scap_profile)
77
77
  policy.save!
78
- hg.hostgroup_classes.destroy_all
79
78
  hg.destroy
80
79
  assert_equal 0, policy.hostgroups.count
81
80
  end
@@ -314,6 +313,7 @@ class PolicyTest < ActiveSupport::TestCase
314
313
  end
315
314
 
316
315
  test "should change deploy type" do
316
+ skip unless puppet_available?
317
317
  policy = FactoryBot.create(:policy, :scap_content => @scap_content, :scap_content_profile => @scap_profile)
318
318
  setup_puppet_class
319
319
  policy.change_deploy_type({ :deploy_by => 'puppet' })
@@ -24,6 +24,7 @@ class ConfigNameServiceTest < ActiveSupport::TestCase
24
24
  end
25
25
 
26
26
  test 'should find all available except Manual' do
27
+ skip unless puppet_available?
27
28
  ForemanOpenscap::ClientConfig::Ansible.any_instance.stubs(:available?).returns(false)
28
29
  configs = @name_service.all_available_except(:manual)
29
30
  assert_equal 1, configs.size
@@ -9,11 +9,12 @@ class HostgroupOverriderTest < ActiveSupport::TestCase
9
9
  end
10
10
 
11
11
  test 'should populate puppet overrides' do
12
+ skip unless puppet_available?
12
13
  puppet_class, env, port_param, server_param = setup_puppet_class.values_at :puppet_class, :env, :port_param, :server_param
13
14
 
14
15
  proxy = FactoryBot.create(:openscap_proxy, :url => 'https://override-keys.example.com:8998')
15
16
 
16
- hostgroup = FactoryBot.create(:hostgroup, :environment_id => env.id, :openscap_proxy_id => proxy.id)
17
+ hostgroup = FactoryBot.create(:hostgroup, :environment => env, :openscap_proxy_id => proxy.id, :puppet => FactoryBot.create(:hostgroup_puppet_facet))
17
18
  refute hostgroup.puppetclasses.include? puppet_class
18
19
  assert LookupValue.where(:match => "hostgroup=#{hostgroup.to_label}",
19
20
  :lookup_key_id => port_param.id,
@@ -8,6 +8,7 @@ class LookupKeyOverriderTest < ActiveSupport::TestCase
8
8
  end
9
9
 
10
10
  test 'should override puppet class parameters' do
11
+ skip unless puppet_available?
11
12
  server_param, port_param, policies_param = setup_puppet_class.values_at :server_param, :port_param, :policies_param
12
13
  refute server_param.override
13
14
  refute port_param.override
@@ -21,7 +22,8 @@ class LookupKeyOverriderTest < ActiveSupport::TestCase
21
22
  end
22
23
 
23
24
  test 'should add error when no puppet class found' do
24
- puppet_class = Puppetclass.find_by :name => ForemanOpenscap::ClientConfig::Puppet.new.puppetclass_name
25
+ skip unless puppet_available?
26
+ puppet_class = ::ForemanPuppet::Puppetclass.find_by :name => ForemanOpenscap::ClientConfig::Puppet.new.puppetclass_name
25
27
  puppet_class.destroy if puppet_class
26
28
  policy = FactoryBot.create(:policy, :scap_content => @scap_content, :scap_content_profile => @scap_content_profile, :deploy_by => :puppet)
27
29
  ForemanOpenscap::LookupKeyOverrider.new(policy).override
@@ -41,6 +43,7 @@ class LookupKeyOverriderTest < ActiveSupport::TestCase
41
43
  end
42
44
 
43
45
  test 'should add error when lookup keys not present' do
46
+ skip unless puppet_available?
44
47
  server_param, port_param, policies_param = setup_puppet_class.values_at :server_param, :port_param, :policies_param
45
48
  server_param.destroy
46
49
  port_param.destroy
@@ -0,0 +1,37 @@
1
+ require 'test_plugin_helper'
2
+
3
+ class ForemanOpenscap::Oval::SetupCheckTest < ActiveSupport::TestCase
4
+ test 'should show error message with filled in data' do
5
+ check = ::ForemanOpenscap::Oval::SetupCheck.new(
6
+ :id => :test_check,
7
+ :title => _("Will it pass?"),
8
+ :fail_msg => ->(hash) { "There was an error in #{hash[:name]}, you need to #{hash[:action]}" }
9
+ )
10
+
11
+ check.fail_with!(:name => 'your engine', :action => 'run')
12
+ assert_equal 'There was an error in your engine, you need to run', check.fail_msg
13
+ end
14
+
15
+ test 'should show error message when it is a string' do
16
+ msg = "Do not panic"
17
+ check = ::ForemanOpenscap::Oval::SetupCheck.new(
18
+ :id => :test_check,
19
+ :title => _("Will it pass?"),
20
+ :fail_msg => msg
21
+ )
22
+ check.fail!
23
+ assert_equal msg, check.fail_msg
24
+ end
25
+
26
+ test 'should not show error message when check not failed' do
27
+ check = ::ForemanOpenscap::Oval::SetupCheck.new(
28
+ :id => :test_check,
29
+ :title => _("Will it pass?"),
30
+ :fail_msg => 'foo'
31
+ )
32
+
33
+ assert_nil check.fail_msg
34
+ check.fail!
35
+ assert_not_nil check.fail_msg
36
+ end
37
+ end
@@ -0,0 +1,63 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { Modal, Button, ModalVariant, Spinner } from '@patternfly/react-core';
4
+
5
+ import { translate as __ } from 'foremanReact/common/I18n';
6
+
7
+ import './ConfirmModal.scss';
8
+
9
+ const ConfirmModal = props => {
10
+ const [callMutation, { loading }] = props.prepareMutation();
11
+
12
+ const actions = [
13
+ <Button
14
+ key="confirm"
15
+ variant="primary"
16
+ onClick={() => props.onConfirm(callMutation, props.record.id)}
17
+ isDisabled={loading}
18
+ >
19
+ {__('Confirm')}
20
+ </Button>,
21
+ <Button
22
+ key="cancel"
23
+ variant="link"
24
+ onClick={event => props.onClose()}
25
+ isDisabled={loading}
26
+ >
27
+ {__('Cancel')}
28
+ </Button>,
29
+ ];
30
+
31
+ if (loading) {
32
+ actions.push(<Spinner key="spinner" size="lg" />);
33
+ }
34
+
35
+ return (
36
+ <Modal
37
+ variant={ModalVariant.medium}
38
+ title={props.title}
39
+ isOpen={props.isOpen}
40
+ className="foreman-modal"
41
+ showClose={false}
42
+ actions={actions}
43
+ >
44
+ {props.text}
45
+ </Modal>
46
+ );
47
+ };
48
+
49
+ ConfirmModal.propTypes = {
50
+ prepareMutation: PropTypes.func.isRequired,
51
+ onConfirm: PropTypes.func.isRequired,
52
+ record: PropTypes.object,
53
+ onClose: PropTypes.func.isRequired,
54
+ title: PropTypes.string.isRequired,
55
+ isOpen: PropTypes.bool.isRequired,
56
+ text: PropTypes.string.isRequired,
57
+ };
58
+
59
+ ConfirmModal.defaultProps = {
60
+ record: null,
61
+ };
62
+
63
+ export default ConfirmModal;
@@ -0,0 +1,3 @@
1
+ .pf-c-backdrop {
2
+ z-index: 1040;
3
+ }
@@ -0,0 +1,157 @@
1
+ import React, { useState } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { translate as __ } from 'foremanReact/common/I18n';
4
+ import {
5
+ Button,
6
+ Split,
7
+ SplitItem,
8
+ Spinner,
9
+ FormGroup,
10
+ } from '@patternfly/react-core';
11
+ import {
12
+ TimesIcon,
13
+ CheckIcon,
14
+ PencilAltIcon,
15
+ ExclamationCircleIcon,
16
+ } from '@patternfly/react-icons';
17
+
18
+ import './EditableInput.scss';
19
+
20
+ const EditableInput = props => {
21
+ const [editing, setEditing] = useState(false);
22
+ const [submitting, setSubmitting] = useState(false);
23
+ const [inputValue, setInputValue] = useState(props.value);
24
+ const [error, setError] = useState('');
25
+ const [touched, setTouched] = useState(false);
26
+
27
+ const stopSubmitting = () => setSubmitting(false);
28
+
29
+ const handleSubmit = event => {
30
+ event.preventDefault();
31
+ onSubmit();
32
+ };
33
+
34
+ const onFinish = () => {
35
+ setSubmitting(false);
36
+ setEditing(false);
37
+ };
38
+
39
+ const onSubmit = () => {
40
+ setSubmitting(true);
41
+ props.onConfirm(inputValue, onFinish, stopSubmitting, onError);
42
+ };
43
+
44
+ const onError = err => {
45
+ setTouched(false);
46
+ setError(err);
47
+ };
48
+
49
+ const onCancel = () => {
50
+ setInputValue(props.value);
51
+ setEditing(false);
52
+ };
53
+
54
+ const onChange = value => {
55
+ if (!touched) {
56
+ setTouched(true);
57
+ }
58
+ setInputValue(value);
59
+ };
60
+
61
+ if (!editing) {
62
+ return (
63
+ <Split>
64
+ <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>
75
+ </Split>
76
+ );
77
+ }
78
+
79
+ const Component = props.component;
80
+
81
+ const shouldValidate = (isTouched, err) => {
82
+ if (!isTouched) {
83
+ return err ? 'error' : 'success';
84
+ }
85
+ return 'noval';
86
+ };
87
+
88
+ const valid = shouldValidate(touched, error);
89
+
90
+ return (
91
+ <Split>
92
+ <SplitItem>
93
+ <form onSubmit={handleSubmit} className="pf-c-form">
94
+ <FormGroup
95
+ helperTextInvalid={error}
96
+ helperTextInvalidIcon={<ExclamationCircleIcon />}
97
+ validated={valid}
98
+ >
99
+ <Component
100
+ {...props.inputProps}
101
+ type="text"
102
+ aria-label={`${props.attrName} text input`}
103
+ isDisabled={submitting}
104
+ value={inputValue || ''}
105
+ onChange={onChange}
106
+ validated={valid}
107
+ />
108
+ </FormGroup>
109
+ </form>
110
+ </SplitItem>
111
+ <SplitItem>
112
+ <Button
113
+ aria-label={`submit ${props.attrName}`}
114
+ variant="plain"
115
+ onClick={onSubmit}
116
+ isDisabled={submitting}
117
+ >
118
+ <CheckIcon />
119
+ </Button>
120
+ </SplitItem>
121
+ <SplitItem>
122
+ <Button
123
+ aria-label={`cancel editing ${props.attrName}`}
124
+ variant="plain"
125
+ onClick={onCancel}
126
+ isDisabled={submitting}
127
+ >
128
+ <TimesIcon />
129
+ </Button>
130
+ </SplitItem>
131
+ <SplitItem>
132
+ {submitting && (
133
+ <Spinner
134
+ key="spinner"
135
+ size="lg"
136
+ id={`edit-${props.attrName}-spinner`}
137
+ />
138
+ )}
139
+ </SplitItem>
140
+ </Split>
141
+ );
142
+ };
143
+
144
+ EditableInput.propTypes = {
145
+ value: PropTypes.string,
146
+ onConfirm: PropTypes.func.isRequired,
147
+ attrName: PropTypes.string.isRequired,
148
+ component: PropTypes.object.isRequired,
149
+ inputProps: PropTypes.object,
150
+ };
151
+
152
+ EditableInput.defaultProps = {
153
+ inputProps: {},
154
+ value: '',
155
+ };
156
+
157
+ export default EditableInput;
@@ -0,0 +1,3 @@
1
+ .inline-edit-icon {
2
+ padding-top: 2px;
3
+ }
@@ -12,10 +12,11 @@ import {
12
12
  CubeIcon,
13
13
  ExclamationCircleIcon,
14
14
  SearchIcon,
15
+ LockIcon,
15
16
  } from '@patternfly/react-icons';
16
17
  import { global_danger_color_200 as dangerColor } from '@patternfly/react-tokens';
17
18
 
18
- const EmptyStateIcon = ({ error, search }) => {
19
+ const EmptyStateIcon = ({ error, search, lock }) => {
19
20
  if (error)
20
21
  return (
21
22
  <PfEmptyStateIcon
@@ -23,18 +24,20 @@ const EmptyStateIcon = ({ error, search }) => {
23
24
  color={dangerColor.value}
24
25
  />
25
26
  );
27
+ if (lock) return <PfEmptyStateIcon icon={LockIcon} />;
26
28
  if (search) return <PfEmptyStateIcon icon={SearchIcon} />;
27
29
  return <PfEmptyStateIcon icon={CubeIcon} />;
28
30
  };
29
31
 
30
- const EmptyState = ({ title, body, error, search }) => (
32
+ const EmptyState = ({ title, body, error, search, lock, primaryButton }) => (
31
33
  <Bullseye>
32
34
  <PfEmptyState variant={EmptyStateVariant.small}>
33
- <EmptyStateIcon error={!!error} search={search} />
35
+ <EmptyStateIcon error={!!error} search={search} lock={lock} />
34
36
  <Title headingLevel="h2" size="lg">
35
37
  {title}
36
38
  </Title>
37
39
  <EmptyStateBody>{body}</EmptyStateBody>
40
+ {primaryButton}
38
41
  </PfEmptyState>
39
42
  </Bullseye>
40
43
  );
@@ -42,11 +45,13 @@ const EmptyState = ({ title, body, error, search }) => (
42
45
  EmptyStateIcon.propTypes = {
43
46
  error: PropTypes.bool,
44
47
  search: PropTypes.bool,
48
+ lock: PropTypes.bool,
45
49
  };
46
50
 
47
51
  EmptyStateIcon.defaultProps = {
48
52
  error: false,
49
53
  search: false,
54
+ lock: false,
50
55
  };
51
56
 
52
57
  EmptyState.propTypes = {
@@ -54,6 +59,8 @@ EmptyState.propTypes = {
54
59
  body: PropTypes.string,
55
60
  error: PropTypes.oneOfType([PropTypes.shape({}), PropTypes.string]),
56
61
  search: PropTypes.bool,
62
+ lock: PropTypes.bool,
63
+ primaryButton: PropTypes.node,
57
64
  };
58
65
 
59
66
  EmptyState.defaultProps = {
@@ -62,6 +69,8 @@ EmptyState.defaultProps = {
62
69
  'There was an error retrieving data from the server. Check your connection and try again.',
63
70
  error: undefined,
64
71
  search: false,
72
+ lock: false,
73
+ primaryButton: null,
65
74
  };
66
75
 
67
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;
@@ -2,18 +2,25 @@ import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import { Table, TableHeader, TableBody } from '@patternfly/react-table';
4
4
  import { Pagination, Flex, FlexItem } from '@patternfly/react-core';
5
- import '@patternfly/patternfly/patternfly-addons.scss';
6
5
  import { usePaginationOptions } from 'foremanReact/components/Pagination/PaginationHooks';
7
6
 
8
7
  import { preparePerPageOptions, refreshPage } from './IndexTableHelper';
9
8
 
10
- const IndexTable = props => {
9
+ const IndexTable = ({
10
+ history,
11
+ pagination,
12
+ totalCount,
13
+ toolbarBtns,
14
+ ariaTableLabel,
15
+ columns,
16
+ ...rest
17
+ }) => {
11
18
  const handlePerPageSelected = (event, perPage) => {
12
- refreshPage(props.history, { page: 1, perPage });
19
+ refreshPage(history, { page: 1, perPage });
13
20
  };
14
21
 
15
22
  const handlePageSelected = (event, page) => {
16
- refreshPage(props.history, { ...props.pagination, page });
23
+ refreshPage(history, { ...pagination, page });
17
24
  };
18
25
 
19
26
  const perPageOptions = preparePerPageOptions(usePaginationOptions());
@@ -21,12 +28,12 @@ const IndexTable = props => {
21
28
  return (
22
29
  <React.Fragment>
23
30
  <Flex className="pf-u-pt-md">
24
- <FlexItem>{props.toolbarBtns}</FlexItem>
31
+ <FlexItem>{toolbarBtns}</FlexItem>
25
32
  <FlexItem align={{ default: 'alignRight' }}>
26
33
  <Pagination
27
- itemCount={props.totalCount}
28
- page={props.pagination.page}
29
- perPage={props.pagination.perPage}
34
+ itemCount={totalCount}
35
+ page={pagination.page}
36
+ perPage={pagination.perPage}
30
37
  onSetPage={handlePageSelected}
31
38
  onPerPageSelect={handlePerPageSelected}
32
39
  perPageOptions={perPageOptions}
@@ -34,12 +41,7 @@ const IndexTable = props => {
34
41
  />
35
42
  </FlexItem>
36
43
  </Flex>
37
- <Table
38
- aria-label={props.ariaTableLabel}
39
- cells={props.columns}
40
- rows={props.rows}
41
- actions={props.actions}
42
- >
44
+ <Table aria-label={ariaTableLabel} cells={columns} {...rest}>
43
45
  <TableHeader />
44
46
  <TableBody />
45
47
  </Table>
@@ -50,17 +52,14 @@ const IndexTable = props => {
50
52
  IndexTable.propTypes = {
51
53
  history: PropTypes.object.isRequired,
52
54
  pagination: PropTypes.object.isRequired,
53
- toolbarBtns: PropTypes.array,
55
+ toolbarBtns: PropTypes.node,
54
56
  totalCount: PropTypes.number.isRequired,
55
57
  ariaTableLabel: PropTypes.string.isRequired,
56
58
  columns: PropTypes.array.isRequired,
57
- rows: PropTypes.array.isRequired,
58
- actions: PropTypes.array,
59
59
  };
60
60
 
61
61
  IndexTable.defaultProps = {
62
62
  toolbarBtns: [],
63
- actions: [],
64
63
  };
65
64
 
66
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;