inspec-core 4.20.2 → 4.22.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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -1
  3. data/inspec-core.gemspec +1 -1
  4. data/lib/inspec/base_cli.rb +1 -1
  5. data/lib/inspec/cli.rb +6 -5
  6. data/lib/inspec/config.rb +0 -1
  7. data/lib/inspec/exceptions.rb +1 -0
  8. data/lib/inspec/input_registry.rb +2 -1
  9. data/lib/inspec/metadata.rb +6 -1
  10. data/lib/inspec/profile.rb +30 -9
  11. data/lib/inspec/reporters.rb +12 -7
  12. data/lib/inspec/reporters/cli.rb +1 -0
  13. data/lib/inspec/reporters/json.rb +9 -4
  14. data/lib/inspec/resources/apt.rb +2 -0
  15. data/lib/inspec/resources/interface.rb +55 -0
  16. data/lib/inspec/resources/interfaces.rb +119 -0
  17. data/lib/inspec/resources/service.rb +1 -1
  18. data/lib/inspec/run_data.rb +10 -3
  19. data/lib/inspec/run_data/profile.rb +4 -4
  20. data/lib/inspec/runner.rb +8 -2
  21. data/lib/inspec/runner_rspec.rb +4 -1
  22. data/lib/inspec/schema.rb +2 -0
  23. data/lib/inspec/schema/exec_json.rb +4 -3
  24. data/lib/inspec/schema/primitives.rb +1 -1
  25. data/lib/inspec/utils/telemetry/run_context_probe.rb +48 -0
  26. data/lib/inspec/version.rb +1 -1
  27. data/lib/plugins/inspec-reporter-html2/README.md +53 -0
  28. data/lib/plugins/inspec-reporter-html2/lib/inspec-reporter-html2.rb +18 -0
  29. data/lib/plugins/inspec-reporter-html2/lib/inspec-reporter-html2/reporter.rb +24 -0
  30. data/lib/plugins/inspec-reporter-html2/lib/inspec-reporter-html2/version.rb +8 -0
  31. data/lib/plugins/inspec-reporter-html2/templates/body.html.erb +46 -0
  32. data/lib/plugins/inspec-reporter-html2/templates/control.html.erb +77 -0
  33. data/lib/plugins/inspec-reporter-html2/templates/default.css +107 -0
  34. data/lib/plugins/inspec-reporter-html2/templates/default.js +79 -0
  35. data/lib/plugins/inspec-reporter-html2/templates/profile.html.erb +23 -0
  36. data/lib/plugins/inspec-reporter-html2/templates/result.html.erb +15 -0
  37. data/lib/plugins/inspec-reporter-html2/templates/selector.html.erb +8 -0
  38. data/lib/plugins/inspec-reporter-json-min/README.md +10 -0
  39. data/lib/plugins/inspec-reporter-json-min/lib/inspec-reporter-json-min.rb +13 -0
  40. data/lib/plugins/inspec-reporter-json-min/lib/inspec-reporter-json-min/reporter.rb +50 -0
  41. data/lib/plugins/inspec-reporter-json-min/lib/inspec-reporter-json-min/version.rb +5 -0
  42. metadata +21 -5
  43. data/lib/inspec/reporters/json_min.rb +0 -48
@@ -154,7 +154,7 @@ module Inspec::Resources
154
154
  end
155
155
  when "wrlinux"
156
156
  SysV.new(inspec, service_ctl)
157
- when "mac_os_x"
157
+ when "mac_os_x", "darwin"
158
158
  LaunchCtl.new(inspec, service_ctl)
159
159
  when "freebsd"
160
160
  BSDInit.new(inspec, service_ctl)
@@ -1,12 +1,19 @@
1
1
 
2
2
  module Inspec
3
3
  module HashLikeStruct
4
+ # Only list keys whose value are non-nil
4
5
  def keys
5
- members
6
+ members.reject { |k| self[k].nil? }
6
7
  end
7
8
 
9
+ # Only list non-nil members for backwards compatibility
8
10
  def key?(item)
9
- members.include?(item)
11
+ members.include?(item) && non_nil?(item)
12
+ end
13
+
14
+ # This is provided for clarity - many locations make this test
15
+ def non_nil?(item)
16
+ !self[item].nil?
10
17
  end
11
18
  end
12
19
 
@@ -40,7 +47,7 @@ module Inspec
40
47
  # core reporters have been migrated to plugins. It is probable that new data elements
41
48
  # and new Hash compatibility behavior will be added during the core reporter plugin
42
49
  # conversion process.
43
- SCHEMA_VERSION = "0.1.0".freeze
50
+ SCHEMA_VERSION = "0.2.0".freeze
44
51
 
45
52
  def self.compatible_schema?(constraints)
46
53
  reqs = Gem::Requirement.create(constraints)
@@ -15,7 +15,7 @@ module Inspec
15
15
  :summary,
16
16
  :supports, # complex local
17
17
  :parent_profile,
18
- :skip_message,
18
+ :status_message,
19
19
  :waiver_data, # Undocumented but used in JSON reporter - should not be?
20
20
  :title,
21
21
  :version
@@ -40,7 +40,7 @@ module Inspec
40
40
  title
41
41
  version
42
42
  parent_profile
43
- skip_message
43
+ status_message
44
44
  waiver_data
45
45
  }.each do |field|
46
46
  self[field] = raw_prof_data[field]
@@ -51,11 +51,11 @@ module Inspec
51
51
  class Profile
52
52
  # Good candidate for keyword_init, but that is not in 2.4
53
53
  Dependency = Struct.new(
54
- :name, :path, :status, :skip_message, :git, :url, :compliance, :supermarket, :branch, :tag, :commit, :version, :relative_path
54
+ :name, :path, :status, :status_message, :git, :url, :compliance, :supermarket, :branch, :tag, :commit, :version, :relative_path
55
55
  ) do
56
56
  include HashLikeStruct
57
57
  def initialize(raw_dep_data)
58
- %i{name path status skip_message git url supermarket compliance branch tag commit version relative_path}.each { |f| self[f] = raw_dep_data[f] }
58
+ %i{name path status status_message git url supermarket compliance branch tag commit version relative_path}.each { |f| self[f] = raw_dep_data[f] }
59
59
  end
60
60
  end
61
61
 
@@ -115,8 +115,14 @@ module Inspec
115
115
  @test_collector.add_profile(requirement.profile)
116
116
  end
117
117
 
118
- tests = profile.collect_tests
119
- all_controls += tests unless tests.nil?
118
+ begin
119
+ tests = profile.collect_tests
120
+ all_controls += tests unless tests.nil?
121
+ rescue Inspec::Exceptions::ProfileLoadFailed => e
122
+ Inspec::Log.error "Failed to load profile #{profile.name}: #{e}"
123
+ profile.set_status_message e.to_s
124
+ next
125
+ end
120
126
  end
121
127
 
122
128
  all_controls.each do |rule|
@@ -90,9 +90,12 @@ module Inspec
90
90
  return @rspec_exit_code if @formatter.results.empty?
91
91
 
92
92
  stats = @formatter.results[:statistics][:controls]
93
+ load_failures = @formatter.results[:profiles]&.select { |p| p[:status] == "failed" }&.any?
93
94
  skipped = @formatter.results.dig(:profiles, 0, :status) == "skipped"
94
- if stats[:failed][:total] == 0 && stats[:skipped][:total] == 0 && !skipped
95
+ if stats[:failed][:total] == 0 && stats[:skipped][:total] == 0 && !skipped && !load_failures
95
96
  0
97
+ elsif load_failures
98
+ @conf["distinct_exit"] ? 102 : 1
96
99
  elsif stats[:failed][:total] > 0
97
100
  @conf["distinct_exit"] ? 100 : 1
98
101
  elsif stats[:skipped][:total] > 0 || skipped
@@ -147,6 +147,8 @@ module Inspec
147
147
  "license" => { "type" => "string", "optional" => true },
148
148
  "summary" => { "type" => "string", "optional" => true },
149
149
  "status" => { "type" => "string", "optional" => false },
150
+ "status_message" => { "type" => "string", "optional" => true },
151
+ # skip_message is deprecated, status_message should be used to store the reason for skipping
150
152
  "skip_message" => { "type" => "string", "optional" => true },
151
153
 
152
154
  "supports" => {
@@ -83,7 +83,7 @@ module Inspec
83
83
  "required" => %w{name sha256 supports attributes groups controls},
84
84
  # Name is mandatory in inspec.yml.
85
85
  # supports, controls, groups, and attributes are always present, even if empty
86
- # sha256, status, skip_message
86
+ # sha256, status, status_message
87
87
  "properties" => {
88
88
  # These are provided in inspec.yml
89
89
  "name" => Primitives::STRING,
@@ -100,10 +100,11 @@ module Inspec
100
100
  "description" => Primitives::STRING,
101
101
  "inspec_version" => Primitives::STRING,
102
102
 
103
- # These are generated at runtime, and all except skip_message are guaranteed
103
+ # These are generated at runtime, and all except status_message and skip_message are guaranteed
104
104
  "sha256" => Primitives::STRING,
105
105
  "status" => Primitives::STRING,
106
- "skip_message" => Primitives::STRING, # If skipped, why
106
+ "status_message" => Primitives::STRING, # If skipped or failed to load, why
107
+ "skip_message" => Primitives::STRING, # Deprecated field storing reason for skipping. status_message should be used instead.
107
108
  "controls" => Primitives.array(CONTROL.ref),
108
109
  "groups" => Primitives.array(Primitives::CONTROL_GROUP.ref),
109
110
  "attributes" => Primitives.array(Primitives::INPUT),
@@ -160,7 +160,7 @@ module Inspec
160
160
  "url" => URL,
161
161
  "branch" => STRING,
162
162
  "path" => STRING,
163
- "skip_message" => STRING,
163
+ "status_message" => STRING,
164
164
  "status" => STRING,
165
165
  "git" => URL,
166
166
  "supermarket" => STRING,
@@ -0,0 +1,48 @@
1
+ module Inspec
2
+ module Telemetry
3
+ # Guesses the run context of InSpec - how were we invoked?
4
+ # All stack values here are determined experimentally
5
+
6
+ class RunContextProbe
7
+ def self.guess_run_context(stack = nil)
8
+ stack ||= caller_locations
9
+ return "test-kitchen" if kitchen?(stack)
10
+ return "cli" if run_by_thor?(stack)
11
+ return "audit-cookbook" if audit_cookbook?(stack)
12
+
13
+ "unknown"
14
+ end
15
+
16
+ def self.run_by_thor?(stack)
17
+ stack_match(stack: stack, path: "thor/command", label: "run") &&
18
+ stack_match(stack: stack, path: "thor/invocation", label: "invoke_command")
19
+ end
20
+
21
+ def self.kitchen?(stack)
22
+ stack_match(stack: stack, path: "kitchen/instance", label: "verify_action") &&
23
+ stack_match(stack: stack, path: "kitchen/instance", label: "verify")
24
+ end
25
+
26
+ def self.audit_cookbook?(stack)
27
+ stack_match(stack: stack, path: "chef/handler", label: "run_report_handlers") &&
28
+ stack_match(stack: stack, path: "handler/audit_report", label: "report")
29
+ end
30
+
31
+ def self.stack_match(stack: [], label: nil, path: nil)
32
+ return false if stack.nil?
33
+
34
+ stack.any? do |frame|
35
+ if label && path
36
+ frame.label == label && frame.absolute_path.include?(path)
37
+ elsif label
38
+ frame.label == label
39
+ elsif path
40
+ frame.absolute_path.include?(path)
41
+ else
42
+ false
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -1,3 +1,3 @@
1
1
  module Inspec
2
- VERSION = "4.20.2".freeze
2
+ VERSION = "4.22.0".freeze
3
3
  end
@@ -0,0 +1,53 @@
1
+ # inspec-reporter-html2 Plugin
2
+
3
+ An "improved" HTML output reporter specifically for Chef InSpec. Unlike the default `html` reporter, which is RSpec-based, this reporter knows about Chef InSpec structures like Controls and Profiles, and includes full metadata such as control tags, etc.
4
+
5
+ ## To Install This Plugin
6
+
7
+ This plugin ships with Chef InSpec and requires no installation.
8
+
9
+ It should appear when you run:
10
+
11
+ ```
12
+ you@machine $ inspec plugin list
13
+ ```
14
+
15
+ ## How to use this plugin
16
+
17
+ To generate an HTML report using this plugin and save the output to a file named `report.html`, run:
18
+
19
+ ```
20
+ you@machine $ inspec exec some_profile --reporter html2:report.html
21
+ ```
22
+
23
+ Note the `2` in the reporter name. If you omit it and run `--reporter html` instead, you will run the legacy RSpec HTML reporter.
24
+
25
+ ## Configuring the Plugin
26
+
27
+ The `html2` reporter requires no configuration to function. However, two options--`alternate_css_file` and `alternate_js_file`--are available for customization. The options are set in the JSON-formatted configuration file that Chef InSpec consumes. For details, see [our configuration file documentation](https://www.inspec.io/docs/reference/config/).
28
+
29
+ For example:
30
+
31
+ ```json
32
+ {
33
+ "version": "1.2",
34
+ "plugins": {
35
+ "inspec-reporter-html2": {
36
+ "alternate_js_file":"/var/www/js/my-javascript.js",
37
+ "alternate_css_file":"/var/www/css/my-style.css"
38
+ }
39
+ }
40
+ }
41
+ ```
42
+
43
+ ### alternate\_css\_file
44
+
45
+ Specifies the full path to the location of a CSS file that will be read and inlined into the HTML report. The default CSS will not be included.
46
+
47
+ ### alternate\_js\_file
48
+
49
+ Specifies the full path to the location of a JavaScript file that will be read and inlined into the HTML report. The default JavaScript will not be included. The JavaScript file should implement at least a `pageLoaded()` function, which will be called by the `onload` event of the HTML `body` element.
50
+
51
+ ## Developing This Plugin
52
+
53
+ This plugin is part of the Chef InSpec source code. While it has its own tests, the general contribution policy is dictated by the Chef InSpec project at https://github.com/inspec/inspec/blob/master/CONTRIBUTING.md
@@ -0,0 +1,18 @@
1
+ libdir = File.dirname(__FILE__)
2
+ $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
3
+
4
+ require "inspec-reporter-html2/version"
5
+ module InspecPlugins
6
+ module Html2Reporter
7
+ class Plugin < ::Inspec.plugin(2)
8
+ # Internal machine name of the plugin. InSpec will use this in errors, etc.
9
+ plugin_name :'inspec-reporter-html2'
10
+
11
+ # Define a new Reporter.
12
+ reporter :html2 do
13
+ require "inspec-reporter-html2/reporter"
14
+ InspecPlugins::Html2Reporter::Reporter
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,24 @@
1
+ require "erb"
2
+ require "inspec/config"
3
+
4
+ module InspecPlugins::Html2Reporter
5
+ class Reporter < Inspec.plugin(2, :reporter)
6
+ def render
7
+ template_path = File.expand_path(__FILE__ + "../../../../templates")
8
+
9
+ # Read config data from the user's config file. Supports two settings, both of which are absolute filesystem paths:
10
+ # alternate_css_file - contents will be used instead of default CSS
11
+ # alternate_js_file - contents will be used instead of default JavaScript
12
+ cfg = Inspec::Config.cached.fetch_plugin_config("inspec-reporter-html2")
13
+ js_path = cfg[:alternate_js_file] || (template_path + "/default.js")
14
+ css_path = cfg[:alternate_css_file] || (template_path + "/default.css")
15
+
16
+ template = ERB.new(File.read(template_path + "/body.html.erb"))
17
+ output(template.result(binding))
18
+ end
19
+
20
+ def self.run_data_schema_constraints
21
+ "~> 0.0"
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,8 @@
1
+ # This file simply makes it easier for CI engines to update
2
+ # the version stamp, and provide a clean way for the gemspec
3
+ # to learn the current version.
4
+ module InspecPlugins
5
+ module Html2Reporter
6
+ VERSION = "0.1.0".freeze
7
+ end
8
+ end
@@ -0,0 +1,46 @@
1
+ <!DOCTYPE html>
2
+ <!-- saved from url=(0014)about:internet -->
3
+ <!-- prior comment allows JS to execute on IE when saved as a local file, "MOTW" -->
4
+ <html lang="en">
5
+ <head>
6
+ <title><%= Inspec::Dist::PRODUCT_NAME %> Results</title>
7
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
8
+ <style type="text/css">
9
+ /* Must inline all CSS files, this is a single-file output that may be airgapped */
10
+ <%= ERB.new(File.read(css_path), nil, nil, "_css").result(binding) %>
11
+ </style>
12
+ <script type="text/javascript">
13
+ // <![CDATA[
14
+ /* Must inline all JavaScript files, this is a single-file output that may be airgapped */
15
+ <%= ERB.new(File.read(js_path), nil, nil, "_js").result(binding) %>
16
+ // ]]>
17
+ </script>
18
+ </head>
19
+ <body onload="pageLoaded()">
20
+ <%= ERB.new(File.read(template_path + "/selector.html.erb"), nil, nil, "_select").result(binding) %>
21
+ <div class="inspec-report">
22
+ <h1><%= Inspec::Dist::PRODUCT_NAME %> Report</h1>
23
+ <% run_data.profiles.each do |profile| %>
24
+ <%= ERB.new(File.read(template_path + "/profile.html.erb"), nil, nil, "_prof").result(binding) %>
25
+ <% end %>
26
+
27
+ <div class="inspec-summary">
28
+ <table id="platform" class="info">
29
+ <tr><th colspan=2><h4 id="platform-label">Platform Information</h4></th></tr>
30
+ <tr class= "name"><th>Name:</th><td><%= run_data.platform.name %></td></tr>
31
+ <tr class= "release"><th>Release:</th><td><%= run_data.platform.release %></td></tr>
32
+ <tr class= "target"><th>Target:</th><td><%= run_data.platform.target %></td></tr>
33
+ </table>
34
+ <table id="statistics" class="info">
35
+ <tr><th colspan="2"><h4 id="statistics-label">Control Statistics</h4></th></tr>
36
+ <tr class= "passed"><th>Passed:</th><td><%= run_data.statistics.controls.passed.total %></td></tr>
37
+ <tr class= "skipped"><th>Skipped:</th><td><%= run_data.statistics.controls.skipped.total %></td></tr>
38
+ <tr class= "failed"><th>Failed:</th><td><%= run_data.statistics.controls.failed.total %></td></tr>
39
+ <tr class= "duration"><th>Duration:</th><td><%= run_data.statistics.duration %> seconds</td></tr>
40
+ <tr class= "date"><th>Time Finished:</th><td><%= Time.now %></td></tr>
41
+ </table>
42
+ <span id="inspec-version"><%= Inspec::Dist::PRODUCT_NAME %> version <%= run_data.version %></span>
43
+ </div>
44
+ </div>
45
+ </body>
46
+ </html>
@@ -0,0 +1,77 @@
1
+ <% slugged_id = control.id.tr(" ", "_") %>
2
+ <%
3
+ # Determine status of control
4
+ status = "passed"
5
+ if control.results.any? { |r| r.status == "failed" }
6
+ status = "failed"
7
+ elsif control.results.any? { |r| r.status == "skipped" }
8
+ status = "skipped"
9
+ end
10
+ %>
11
+
12
+ <div class="control control-status-<%= status %>" id="control-<%= slugged_id %>">
13
+
14
+ <%
15
+ # Determine range of impact
16
+ i = control.impact || 0.0
17
+ impact_level = "none"
18
+ if i < 0.3
19
+ impact_level = "low"
20
+ elsif i < 0.7
21
+ impact_level = "medium"
22
+ else
23
+ impact_level = "high"
24
+ end
25
+ %>
26
+
27
+ <h3 class="control-title">Control <code><%= control.id %></code></h3>
28
+ <table class="control-metadata info" id="control-metadata-<%= slugged_id %>">
29
+ <tr class="status status-<%= status %>"><th>Status:</th><td><div><%= status.capitalize %></div></td></tr>
30
+ <% if control.title %><tr class="title"><th>Title:</th><td><%= control.title %></td></tr> <% end %>
31
+ <% if control.desc %><tr class="desc"><th>Description:</th><td><%= control.desc %></td></tr> <% end %>
32
+ <% if control.impact %><tr class="impact impact-<%= impact_level %>"><th>Impact:</th><td><%= control.impact %></td></tr> <% end %>
33
+ <% unless control.tags.empty? %>
34
+ <tr class="tags">
35
+ <th>Tags:</th>
36
+ <td>
37
+ <table class="tags">
38
+ <% control.tags.each do |tag_name, tag_text| %>
39
+ <tr><td><%= tag_name %></td><td><%= tag_text %></td></tr>
40
+ <% end %>
41
+ </table>
42
+ </td>
43
+ </tr>
44
+ <% end %>
45
+ <% unless control.refs.empty? %>
46
+ <tr class="refs">
47
+ <th>References:</th>
48
+ <td>
49
+ <ul>
50
+ <% control.refs.each do |r| %>
51
+ <li><a href="<%= r.url %>"><%= r.ref %></a></li>
52
+ <% end %>
53
+ </ul>
54
+ </td>
55
+ </tr>
56
+ <% end %>
57
+ <tr class="code">
58
+ <th>Source Code:</th>
59
+ <td>
60
+ <input type="button" class="show-source-code" id="show-code-<%= slugged_id %>" value="Show Source"/>
61
+ <input type="button" class="hide-source-code hidden" id="hide-code-<%= slugged_id %>" value="Hide Source"/>
62
+ <pre class="source-code hidden" id="source-code-<%= slugged_id %>">
63
+ <code>
64
+ <%= control.code %>
65
+ </code>
66
+ </pre>
67
+ </td>
68
+ </tr>
69
+ <!-- TODO waiver data -->
70
+
71
+ </table>
72
+
73
+ <% control.results.each do |result| %>
74
+ <%= ERB.new(File.read(template_path + "/result.html.erb"), nil, nil, "_rslt").result(binding) %>
75
+ <% end %>
76
+
77
+ </div>
@@ -0,0 +1,107 @@
1
+ body {
2
+ margin: 20px 10% 20px 10%;
3
+ padding: 0;
4
+ background: #fff;
5
+ }
6
+ h1, h2, h3, h4, h5 {
7
+ font-family: "Lucida Grande", Helvetica, sans-serif;
8
+ }
9
+ h1, h2 {
10
+ padding: 10px;
11
+ text-align: center
12
+ }
13
+ table.info th, table.info th {
14
+ padding: 2px;
15
+ }
16
+ table.info th {
17
+ text-align: right;
18
+ }
19
+ .hidden {
20
+ display: none;
21
+ }
22
+ pre code {
23
+ background-color: #eee;
24
+ border: 1px solid #999;
25
+ display: block;
26
+ padding: 20px;
27
+ }
28
+
29
+ .profile, .control, .profile-metadata {
30
+ border: 1px solid #ccc;
31
+ padding: 10px;
32
+ margin: 5px auto;
33
+ }
34
+
35
+ .resource-title {
36
+ margin-left: 2.5%;
37
+ }
38
+
39
+ .control-title code,
40
+ .resource-title code {
41
+ font-size: larger;
42
+ }
43
+ .control-metadata .status div,
44
+ .result-metadata .status div {
45
+ color: white;
46
+ font-weight: bold;
47
+ width: fit-content;
48
+ padding: 0px 2px;
49
+ }
50
+
51
+ .control-metadata .status-passed div,
52
+ .result-metadata .status-passed div {
53
+ background-color: darkgreen;
54
+ }
55
+ .control-metadata .status-failed div,
56
+ .result-metadata .status-failed div {
57
+ background-color: red;
58
+ }
59
+ .control-metadata .status-skipped div,
60
+ .result-metadata .status-skipped div {
61
+ background-color: grey;
62
+ }
63
+ .result-metadata,
64
+ .control-metadata {
65
+ margin: 0 0 0 5%;
66
+ }
67
+
68
+ .selector-panel {
69
+ position: fixed;
70
+ z-index: 100;
71
+ background-color: #ccc;
72
+ padding: 10px;
73
+ top: 0;
74
+ margin-left: -10%;
75
+ border-bottom-right-radius: 10px;
76
+ }
77
+
78
+ @media print {
79
+ .selector-panel {
80
+ visibility: hidden;
81
+ }
82
+ }
83
+
84
+ .inspec-summary {
85
+ border: 1px solid #ccc;
86
+ padding: 10px;
87
+ margin: 5px auto;
88
+ width: fit-content
89
+ }
90
+
91
+ .inspec-summary h4 {
92
+ margin-bottom: 0px;
93
+ }
94
+
95
+ .inspec-summary #platform, .inspec-summary #statistics {
96
+ display: inline;
97
+ }
98
+
99
+ #statistics .date td {
100
+ width: 100px
101
+ }
102
+
103
+ #inspec-version {
104
+ display: block;
105
+ text-align: center;
106
+ font-style: italic;
107
+ }