foreman_openscap 4.3.2 → 5.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (119) 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/data_stream_content.rb +1 -1
  15. data/app/models/concerns/foreman_openscap/host_extensions.rb +0 -6
  16. data/app/models/concerns/foreman_openscap/oval_facet_hostgroup_extensions.rb +16 -0
  17. data/app/models/foreman_openscap/arf_report.rb +1 -1
  18. data/app/models/foreman_openscap/oval_content.rb +2 -0
  19. data/app/services/foreman_openscap/client_config/base.rb +1 -0
  20. data/app/services/foreman_openscap/client_config/puppet.rb +6 -2
  21. data/app/services/foreman_openscap/oval/configure.rb +16 -13
  22. data/app/services/foreman_openscap/oval/setup.rb +5 -5
  23. data/app/services/foreman_openscap/oval/setup_check.rb +5 -2
  24. data/app/views/api/v2/compliance/oval_contents/destroy.json.rabl +3 -0
  25. data/app/views/arf_reports/_metrics.html.erb +4 -4
  26. data/app/views/compliance_hosts/show.html.erb +4 -6
  27. data/app/views/dashboard/_compliance_reports_breakdown_widget.html.erb +4 -3
  28. data/app/views/policy_dashboard/_policy_chart_widget.html.erb +3 -2
  29. data/db/migrate/20200117135424_migrate_port_overrides_to_int.rb +2 -1
  30. data/db/migrate/20201202110213_update_puppet_port_param_type.rb +2 -1
  31. data/db/migrate/20210819143316_drop_unused_tables.rb +6 -0
  32. data/lib/foreman_openscap/engine.rb +8 -9
  33. data/lib/foreman_openscap/version.rb +1 -1
  34. data/package.json +3 -6
  35. data/test/functional/api/v2/compliance/oval_reports_controller_test.rb +1 -1
  36. data/test/functional/api/v2/compliance/policies_controller_test.rb +2 -0
  37. data/test/graphql/mutations/oval_policies/delete_mutation_test.rb +63 -0
  38. data/test/graphql/queries/oval_content_query_test.rb +29 -0
  39. data/test/helpers/arf_report_dashboard_helper_test.rb +9 -10
  40. data/test/helpers/policy_dashboard_helper_test.rb +1 -1
  41. data/test/test_plugin_helper.rb +9 -4
  42. data/test/unit/policy_test.rb +1 -1
  43. data/test/unit/services/config_name_service_test.rb +1 -0
  44. data/test/unit/services/hostgroup_overrider_test.rb +2 -1
  45. data/test/unit/services/lookup_key_overrider_test.rb +4 -1
  46. data/test/unit/services/oval/setup_check_test.rb +37 -0
  47. data/webpack/components/ConfirmModal.js +63 -0
  48. data/webpack/components/ConfirmModal.scss +3 -0
  49. data/webpack/components/EditableInput.js +163 -0
  50. data/webpack/components/EditableInput.scss +3 -0
  51. data/webpack/components/EmptyState.js +12 -3
  52. data/webpack/components/IndexLayout.js +11 -4
  53. data/webpack/components/IndexTable/index.js +21 -16
  54. data/webpack/components/LinkButton.js +38 -0
  55. data/webpack/components/withDeleteModal.js +51 -0
  56. data/webpack/components/withLoading.js +44 -5
  57. data/webpack/graphql/mutations/createOvalPolicy.gql +22 -0
  58. data/webpack/graphql/mutations/deleteOvalContent.gql +9 -0
  59. data/webpack/graphql/mutations/deleteOvalPolicy.gql +9 -0
  60. data/webpack/graphql/mutations/updateOvalPolicy.gql +14 -0
  61. data/webpack/graphql/queries/currentUserAttributes.gql +11 -0
  62. data/webpack/graphql/queries/cves.gql +5 -0
  63. data/webpack/graphql/queries/hostgroups.gql +14 -0
  64. data/webpack/graphql/queries/ovalContent.gql +8 -0
  65. data/webpack/graphql/queries/ovalContents.gql +8 -0
  66. data/webpack/graphql/queries/ovalPolicies.gql +8 -0
  67. data/webpack/graphql/queries/ovalPolicy.gql +8 -0
  68. data/webpack/helpers/formFieldsHelper.js +113 -0
  69. data/webpack/helpers/globalIdHelper.js +4 -2
  70. data/webpack/helpers/mutationHelper.js +68 -0
  71. data/webpack/helpers/pathsHelper.js +10 -3
  72. data/webpack/helpers/permissionsHelper.js +42 -0
  73. data/webpack/helpers/toastHelper.js +3 -0
  74. data/webpack/helpers/toastsHelper.js +3 -0
  75. data/webpack/routes/OvalContents/OvalContentsIndex/OvalContentsIndex.js +26 -0
  76. data/webpack/routes/OvalContents/OvalContentsIndex/OvalContentsTable.js +50 -5
  77. data/webpack/routes/OvalContents/OvalContentsIndex/__tests__/OvalContentsDestroy.fixtures.js +105 -0
  78. data/webpack/routes/OvalContents/OvalContentsIndex/__tests__/OvalContentsDestroy.test.js +124 -0
  79. data/webpack/routes/OvalContents/OvalContentsIndex/__tests__/OvalContentsIndex.fixtures.js +98 -77
  80. data/webpack/routes/OvalContents/OvalContentsIndex/__tests__/OvalContentsIndex.test.js +53 -6
  81. data/webpack/routes/OvalContents/OvalContentsIndex/index.js +7 -1
  82. data/webpack/routes/OvalContents/OvalContentsNew/OvalContentsNew.js +138 -0
  83. data/webpack/routes/OvalContents/OvalContentsNew/OvalContentsNew.scss +3 -0
  84. data/webpack/routes/OvalContents/OvalContentsNew/OvalContentsNewHelper.js +73 -0
  85. data/webpack/routes/OvalContents/OvalContentsNew/__tests__/OvalContentsNew.test.js +104 -0
  86. data/webpack/routes/OvalContents/OvalContentsNew/index.js +13 -0
  87. data/webpack/routes/OvalContents/OvalContentsShow/OvalContentsShow.js +62 -0
  88. data/webpack/routes/OvalContents/OvalContentsShow/OvalContentsShow.test.js +45 -0
  89. data/webpack/routes/OvalContents/OvalContentsShow/OvalContentsShowHelper.js +0 -0
  90. data/webpack/routes/OvalContents/OvalContentsShow/index.js +35 -0
  91. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/OvalPoliciesIndex.js +18 -2
  92. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/OvalPoliciesTable.js +34 -4
  93. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/__tests__/OvalPoliciesDestroy.fixtures.js +101 -0
  94. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/__tests__/OvalPoliciesDestroy.test.js +117 -0
  95. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/__tests__/OvalPoliciesIndex.fixtures.js +71 -21
  96. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/__tests__/OvalPoliciesIndex.test.js +34 -2
  97. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/index.js +7 -1
  98. data/webpack/routes/OvalPolicies/OvalPoliciesNew/HostgroupSelect.js +135 -0
  99. data/webpack/routes/OvalPolicies/OvalPoliciesNew/NewOvalPolicyForm.js +119 -0
  100. data/webpack/routes/OvalPolicies/OvalPoliciesNew/NewOvalPolicyFormHelpers.js +107 -0
  101. data/webpack/routes/OvalPolicies/OvalPoliciesNew/OvalPoliciesNew.js +32 -0
  102. data/webpack/routes/OvalPolicies/OvalPoliciesNew/__tests__/OvalPoliciesNew.fixtures.js +147 -0
  103. data/webpack/routes/OvalPolicies/OvalPoliciesNew/__tests__/OvalPoliciesNew.test.js +172 -0
  104. data/webpack/routes/OvalPolicies/OvalPoliciesNew/index.js +11 -0
  105. data/webpack/routes/OvalPolicies/OvalPoliciesShow/CvesTab.js +1 -0
  106. data/webpack/routes/OvalPolicies/OvalPoliciesShow/CvesTable.js +2 -2
  107. data/webpack/routes/OvalPolicies/OvalPoliciesShow/DetailsTab.js +87 -0
  108. data/webpack/routes/OvalPolicies/OvalPoliciesShow/HostgroupsTab.js +49 -0
  109. data/webpack/routes/OvalPolicies/OvalPoliciesShow/HostgroupsTable.js +38 -0
  110. data/webpack/routes/OvalPolicies/OvalPoliciesShow/OvalPoliciesShow.js +15 -11
  111. data/webpack/routes/OvalPolicies/OvalPoliciesShow/OvalPoliciesShowHelper.js +80 -2
  112. data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesEdit.fixtures.js +48 -0
  113. data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesEdit.test.js +202 -0
  114. data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesShow.fixtures.js +50 -4
  115. data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesShow.test.js +64 -4
  116. data/webpack/routes/OvalPolicies/OvalPoliciesShow/index.js +4 -0
  117. data/webpack/routes/routes.js +21 -0
  118. data/webpack/testHelper.js +64 -2
  119. metadata +63 -7
@@ -40,6 +40,7 @@ class Api::V2::Compliance::PoliciesControllerTest < ActionController::TestCase
40
40
  end
41
41
 
42
42
  test "should get index and show hostgroups" do
43
+ skip unless puppet_available?
43
44
  ForemanOpenscap::Policy.any_instance.stubs(:find_scap_puppetclass).returns(FactoryBot.create(:puppetclass, :name => 'foreman_scap_client'))
44
45
  ForemanOpenscap::Policy.any_instance.stubs(:populate_overrides)
45
46
  hostgroup = FactoryBot.create(:hostgroup)
@@ -61,6 +62,7 @@ class Api::V2::Compliance::PoliciesControllerTest < ActionController::TestCase
61
62
  end
62
63
 
63
64
  test "should show a policy hosts and hostgroups" do
65
+ skip unless puppet_available?
64
66
  ForemanOpenscap::Policy.any_instance.stubs(:find_scap_puppetclass).returns(FactoryBot.create(:puppetclass, :name => 'foreman_scap_client'))
65
67
  ForemanOpenscap::Policy.any_instance.stubs(:populate_overrides)
66
68
  hostgroup = FactoryBot.create(:hostgroup)
@@ -0,0 +1,63 @@
1
+ require 'test_plugin_helper'
2
+
3
+ module Mutations
4
+ module OvalPolicies
5
+ class DeleteMutationTest < ActiveSupport::TestCase
6
+ let(:policy) { FactoryBot.create(:oval_policy, :oval_content => FactoryBot.create(:oval_content)) }
7
+ let(:policy_id) { Foreman::GlobalId.for(policy) }
8
+ let(:variables) do
9
+ {
10
+ id: policy_id,
11
+ }
12
+ end
13
+ let(:query) do
14
+ <<-GRAPHQL
15
+ mutation DeleteOvalPolicyMutation($id:ID!){
16
+ deleteOvalPolicy(input:{id:$id}) {
17
+ id
18
+ errors {
19
+ message
20
+ path
21
+ }
22
+ }
23
+ }
24
+ GRAPHQL
25
+ end
26
+
27
+ context 'with admin user' do
28
+ let(:user) { FactoryBot.create(:user, :admin) }
29
+
30
+ test 'should delete oval policy' do
31
+ context = { current_user: user }
32
+
33
+ policy
34
+
35
+ assert_difference('::ForemanOpenscap::OvalPolicy.count', -1) do
36
+ result = ForemanGraphqlSchema.execute(query, variables: variables, context: context)
37
+ assert_empty result['errors']
38
+ assert_empty result['data']['deleteOvalPolicy']['errors']
39
+ assert_equal policy_id, result['data']['deleteOvalPolicy']['id']
40
+ end
41
+ assert_equal user.id, Audit.last.user_id
42
+ end
43
+ end
44
+
45
+ context 'with user with view permissions' do
46
+ setup do
47
+ policy
48
+ @user = setup_user 'view', 'oval_policies'
49
+ end
50
+
51
+ test 'should not delete oval policy' do
52
+ context = { current_user: @user }
53
+
54
+ assert_difference('ForemanOpenscap::OvalPolicy.count', 0) do
55
+ result = ForemanGraphqlSchema.execute(query, variables: variables, context: context)
56
+ assert_not_empty result['errors']
57
+ assert_includes result['errors'].map { |error| error['message'] }.to_sentence, 'Unauthorized.'
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,29 @@
1
+ require 'test_plugin_helper'
2
+
3
+ module Queries
4
+ class OvalContentQueryTest < GraphQLQueryTestCase
5
+ let(:query) do
6
+ <<-GRAPHQL
7
+ query($id:String!) {
8
+ ovalContent(id: $id) {
9
+ id
10
+ name
11
+ originalFilename
12
+ url
13
+ }
14
+ }
15
+ GRAPHQL
16
+ end
17
+
18
+ let(:oval_content) { FactoryBot.create(:oval_content) }
19
+
20
+ let(:global_id) { Foreman::GlobalId.for(oval_content) }
21
+ let(:variables) { { id: global_id } }
22
+ let(:data) { result['data']['ovalContent'] }
23
+
24
+ test 'should return OVAL Content' do
25
+ assert_equal global_id, data['id']
26
+ assert_equal oval_content.name, data['name']
27
+ end
28
+ end
29
+ end
@@ -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,163 @@
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
+ setError('');
53
+ };
54
+
55
+ const onChange = value => {
56
+ if (!touched) {
57
+ setTouched(true);
58
+ }
59
+ setInputValue(value);
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
+
75
+ if (!editing) {
76
+ return (
77
+ <Split>
78
+ <SplitItem>{props.value || <i>{__('None provided')}</i>}</SplitItem>
79
+ {props.allowed && editBtn}
80
+ </Split>
81
+ );
82
+ }
83
+
84
+ const Component = props.component;
85
+
86
+ const shouldValidate = (isTouched, err) => {
87
+ if (!isTouched) {
88
+ return err ? 'error' : 'success';
89
+ }
90
+ return 'noval';
91
+ };
92
+
93
+ const valid = shouldValidate(touched, error);
94
+
95
+ return (
96
+ <Split>
97
+ <SplitItem>
98
+ <form onSubmit={handleSubmit} className="pf-c-form">
99
+ <FormGroup
100
+ helperTextInvalid={error}
101
+ helperTextInvalidIcon={<ExclamationCircleIcon />}
102
+ validated={valid}
103
+ >
104
+ <Component
105
+ {...props.inputProps}
106
+ type="text"
107
+ aria-label={`${props.attrName} text input`}
108
+ isDisabled={submitting}
109
+ value={inputValue || ''}
110
+ onChange={onChange}
111
+ validated={valid}
112
+ />
113
+ </FormGroup>
114
+ </form>
115
+ </SplitItem>
116
+ <SplitItem>
117
+ <Button
118
+ aria-label={`submit ${props.attrName}`}
119
+ variant="plain"
120
+ onClick={onSubmit}
121
+ isDisabled={submitting}
122
+ >
123
+ <CheckIcon />
124
+ </Button>
125
+ </SplitItem>
126
+ <SplitItem>
127
+ <Button
128
+ aria-label={`cancel editing ${props.attrName}`}
129
+ variant="plain"
130
+ onClick={onCancel}
131
+ isDisabled={submitting}
132
+ >
133
+ <TimesIcon />
134
+ </Button>
135
+ </SplitItem>
136
+ <SplitItem>
137
+ {submitting && (
138
+ <Spinner
139
+ key="spinner"
140
+ size="lg"
141
+ id={`edit-${props.attrName}-spinner`}
142
+ />
143
+ )}
144
+ </SplitItem>
145
+ </Split>
146
+ );
147
+ };
148
+
149
+ EditableInput.propTypes = {
150
+ allowed: PropTypes.bool.isRequired,
151
+ value: PropTypes.string,
152
+ onConfirm: PropTypes.func.isRequired,
153
+ attrName: PropTypes.string.isRequired,
154
+ component: PropTypes.object.isRequired,
155
+ inputProps: PropTypes.object,
156
+ };
157
+
158
+ EditableInput.defaultProps = {
159
+ inputProps: {},
160
+ value: '',
161
+ };
162
+
163
+ 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;