inspec-core 4.20.2 → 4.22.0

Sign up to get free protection for your applications and to get access to all the features.
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
+ }