solidstats 1.1.0 → 3.0.0.beta.1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +17 -0
- data/README.md +27 -0
- data/Rakefile +3 -3
- data/app/assets/stylesheets/solidstats/dashboard.css +48 -0
- data/app/controllers/solidstats/dashboard_controller.rb +82 -60
- data/app/controllers/solidstats/logs_controller.rb +72 -0
- data/app/controllers/solidstats/performance_controller.rb +25 -0
- data/app/controllers/solidstats/productivity_controller.rb +39 -0
- data/app/controllers/solidstats/quality_controller.rb +152 -0
- data/app/controllers/solidstats/securities_controller.rb +30 -0
- data/app/helpers/solidstats/application_helper.rb +155 -0
- data/app/helpers/solidstats/performance_helper.rb +87 -0
- data/app/helpers/solidstats/productivity_helper.rb +38 -0
- data/app/services/solidstats/bundler_audit_service.rb +206 -0
- data/app/services/solidstats/coverage_compass_service.rb +335 -0
- data/app/services/solidstats/load_lens_service.rb +454 -0
- data/app/services/solidstats/log_size_monitor_service.rb +205 -74
- data/app/services/solidstats/my_todo_service.rb +242 -0
- data/app/services/solidstats/style_patrol_service.rb +319 -0
- data/app/views/layouts/solidstats/application.html.erb +9 -2
- data/app/views/layouts/solidstats/dashboard.html.erb +84 -0
- data/app/views/solidstats/dashboard/dashboard.html.erb +39 -0
- data/app/views/solidstats/logs/logs_size.html.erb +409 -0
- data/app/views/solidstats/performance/load_lens.html.erb +158 -0
- data/app/views/solidstats/productivity/_todo_list.html.erb +49 -0
- data/app/views/solidstats/productivity/my_todos.html.erb +84 -0
- data/app/views/solidstats/quality/coverage_compass.html.erb +420 -0
- data/app/views/solidstats/quality/style_patrol.html.erb +463 -0
- data/app/views/solidstats/securities/bundler_audit.html.erb +345 -0
- data/app/views/solidstats/shared/_dashboard_card.html.erb +160 -0
- data/app/views/solidstats/shared/_quick_actions.html.erb +26 -0
- data/config/routes.rb +32 -4
- data/lib/generators/solidstats/install/install_generator.rb +28 -2
- data/lib/generators/solidstats/install/templates/README +7 -0
- data/lib/solidstats/version.rb +1 -1
- data/lib/tasks/solidstats_performance.rake +84 -0
- metadata +43 -19
- data/app/services/solidstats/audit_service.rb +0 -56
- data/app/services/solidstats/data_collector_service.rb +0 -83
- data/app/services/solidstats/todo_service.rb +0 -114
- data/app/views/solidstats/dashboard/_log_monitor.html.erb +0 -759
- data/app/views/solidstats/dashboard/_todos.html.erb +0 -151
- data/app/views/solidstats/dashboard/audit/_additional_styles.css +0 -22
- data/app/views/solidstats/dashboard/audit/_audit_badge.html.erb +0 -5
- data/app/views/solidstats/dashboard/audit/_audit_details.html.erb +0 -495
- data/app/views/solidstats/dashboard/audit/_audit_summary.html.erb +0 -26
- data/app/views/solidstats/dashboard/audit/_no_vulnerabilities.html.erb +0 -3
- data/app/views/solidstats/dashboard/audit/_security_audit.html.erb +0 -14
- data/app/views/solidstats/dashboard/audit/_vulnerabilities_table.html.erb +0 -1120
- data/app/views/solidstats/dashboard/audit/_vulnerability_details.html.erb +0 -63
- data/app/views/solidstats/dashboard/index.html.erb +0 -1351
- data/lib/tasks/solidstats_tasks.rake +0 -4
@@ -1,4 +1,159 @@
|
|
1
1
|
module Solidstats
|
2
2
|
module ApplicationHelper
|
3
|
+
def time_ago_in_words(from_time)
|
4
|
+
return "just now" if from_time.nil?
|
5
|
+
|
6
|
+
distance_in_seconds = (Time.current - from_time).to_i
|
7
|
+
|
8
|
+
case distance_in_seconds
|
9
|
+
when 0..29
|
10
|
+
"just now"
|
11
|
+
when 30..59
|
12
|
+
"#{distance_in_seconds} seconds"
|
13
|
+
when 60..3599
|
14
|
+
minutes = distance_in_seconds / 60
|
15
|
+
"#{minutes} minute#{'s' if minutes != 1}"
|
16
|
+
when 3600..86399
|
17
|
+
hours = distance_in_seconds / 3600
|
18
|
+
"#{hours} hour#{'s' if hours != 1}"
|
19
|
+
else
|
20
|
+
days = distance_in_seconds / 86400
|
21
|
+
"#{days} day#{'s' if days != 1}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def quick_actions
|
26
|
+
[
|
27
|
+
{ icon: 'refresh-ccw', label: 'Refresh All', color: 'blue' },
|
28
|
+
{ icon: 'download', label: 'Export Data', color: 'green' },
|
29
|
+
{ icon: 'bar-chart-2', label: 'View Reports', color: 'purple' },
|
30
|
+
{ icon: 'tool', label: 'Settings', color: 'orange' }
|
31
|
+
]
|
32
|
+
end
|
33
|
+
|
34
|
+
# Inline CSS helper - reads all CSS files and returns them as a style tag
|
35
|
+
def solidstats_styles
|
36
|
+
return @solidstats_styles_cache if defined?(@solidstats_styles_cache)
|
37
|
+
|
38
|
+
begin
|
39
|
+
engine_root = Solidstats::Engine.root
|
40
|
+
css_files = Dir.glob("#{engine_root}/app/assets/stylesheets/solidstats/*.css")
|
41
|
+
|
42
|
+
combined_css = css_files.map do |file_path|
|
43
|
+
relative_path = file_path.gsub("#{engine_root}/app/assets/stylesheets/solidstats/", "")
|
44
|
+
|
45
|
+
# Skip manifest files (application.css with require statements)
|
46
|
+
next if relative_path == "application.css"
|
47
|
+
|
48
|
+
"/* === #{relative_path} === */\n#{File.read(file_path)}"
|
49
|
+
end.compact.join("\n\n")
|
50
|
+
|
51
|
+
@solidstats_styles_cache = content_tag(:style, combined_css.html_safe, type: "text/css")
|
52
|
+
rescue => e
|
53
|
+
Rails.logger.error "Solidstats CSS loading error: #{e.message}"
|
54
|
+
content_tag(:style, "/* Solidstats CSS loading failed: #{e.message} */", type: "text/css")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Inline JavaScript helper - reads all JS files and returns them as a script tag
|
59
|
+
def solidstats_scripts
|
60
|
+
return @solidstats_scripts_cache if defined?(@solidstats_scripts_cache)
|
61
|
+
|
62
|
+
begin
|
63
|
+
engine_root = Solidstats::Engine.root
|
64
|
+
js_files = Dir.glob("#{engine_root}/app/assets/javascripts/solidstats/**/*.js")
|
65
|
+
|
66
|
+
if js_files.any?
|
67
|
+
combined_js = js_files.map do |file_path|
|
68
|
+
relative_path = file_path.gsub("#{engine_root}/app/assets/javascripts/solidstats/", "")
|
69
|
+
javascript_content = File.read(file_path)
|
70
|
+
# Remove Sprockets directives since we're inlining
|
71
|
+
cleaned_js = javascript_content.gsub(/^\/\/=.*$/, "").strip
|
72
|
+
|
73
|
+
"/* === #{relative_path} === */\n#{cleaned_js}"
|
74
|
+
end.join("\n\n")
|
75
|
+
|
76
|
+
dashboard_js = <<~JS
|
77
|
+
/* === Dashboard Core Scripts === */
|
78
|
+
document.addEventListener('DOMContentLoaded', function() {
|
79
|
+
// Initialize Feather icons if available
|
80
|
+
if (typeof feather !== 'undefined') {
|
81
|
+
feather.replace();
|
82
|
+
}
|
83
|
+
|
84
|
+
// Auto-refresh functionality with animations
|
85
|
+
setInterval(function() {
|
86
|
+
document.querySelectorAll('.card').forEach(function(card) {
|
87
|
+
card.style.transform = 'scale(1.02)';
|
88
|
+
setTimeout(function() {
|
89
|
+
card.style.transform = '';
|
90
|
+
}, 200);
|
91
|
+
});
|
92
|
+
}, 30000);
|
93
|
+
|
94
|
+
// Add loading states to forms
|
95
|
+
document.querySelectorAll('form[data-turbo-submits-with]').forEach(function(form) {
|
96
|
+
form.addEventListener('submit', function() {
|
97
|
+
var submitBtn = form.querySelector('button[type="submit"]');
|
98
|
+
if (submitBtn) {
|
99
|
+
submitBtn.classList.add('loading');
|
100
|
+
submitBtn.disabled = true;
|
101
|
+
}
|
102
|
+
});
|
103
|
+
});
|
104
|
+
});
|
105
|
+
JS
|
106
|
+
|
107
|
+
final_js = combined_js + "\n\n" + dashboard_js
|
108
|
+
else
|
109
|
+
# No JS files found, just include dashboard scripts
|
110
|
+
final_js = <<~JS
|
111
|
+
/* === Dashboard Core Scripts === */
|
112
|
+
document.addEventListener('DOMContentLoaded', function() {
|
113
|
+
// Initialize Feather icons if available
|
114
|
+
if (typeof feather !== 'undefined') {
|
115
|
+
feather.replace();
|
116
|
+
}
|
117
|
+
|
118
|
+
// Auto-refresh functionality with animations
|
119
|
+
setInterval(function() {
|
120
|
+
document.querySelectorAll('.card').forEach(function(card) {
|
121
|
+
card.style.transform = 'scale(1.02)';
|
122
|
+
setTimeout(function() {
|
123
|
+
card.style.transform = '';
|
124
|
+
}, 200);
|
125
|
+
});
|
126
|
+
}, 30000);
|
127
|
+
|
128
|
+
// Add loading states to forms
|
129
|
+
document.querySelectorAll('form[data-turbo-submits-with]').forEach(function(form) {
|
130
|
+
form.addEventListener('submit', function() {
|
131
|
+
var submitBtn = form.querySelector('button[type="submit"]');
|
132
|
+
if (submitBtn) {
|
133
|
+
submitBtn.classList.add('loading');
|
134
|
+
submitBtn.disabled = true;
|
135
|
+
}
|
136
|
+
});
|
137
|
+
});
|
138
|
+
});
|
139
|
+
JS
|
140
|
+
end
|
141
|
+
|
142
|
+
@solidstats_scripts_cache = content_tag(:script, final_js.html_safe, type: "text/javascript")
|
143
|
+
rescue => e
|
144
|
+
Rails.logger.error "Solidstats JavaScript loading error: #{e.message}"
|
145
|
+
content_tag(:script, "console.error('Solidstats JavaScript loading failed: #{e.message}');", type: "text/javascript")
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# CDN dependencies helper
|
150
|
+
def solidstats_cdn_dependencies
|
151
|
+
[
|
152
|
+
tag(:meta, name: "viewport", content: "width=device-width,initial-scale=1"),
|
153
|
+
stylesheet_link_tag("https://cdn.jsdelivr.net/npm/daisyui@4.12.10/dist/full.min.css", media: "all"),
|
154
|
+
javascript_include_tag("https://cdn.tailwindcss.com"),
|
155
|
+
javascript_include_tag("https://unpkg.com/feather-icons")
|
156
|
+
].join.html_safe
|
157
|
+
end
|
3
158
|
end
|
4
159
|
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Solidstats
|
4
|
+
# LoadLens Performance Helper
|
5
|
+
# Provides view helpers for displaying performance metrics
|
6
|
+
module PerformanceHelper
|
7
|
+
def load_lens_status_class(avg_response_time, error_rate)
|
8
|
+
return 'text-error' if error_rate > 10
|
9
|
+
return 'text-warning' if avg_response_time > 1000
|
10
|
+
'text-success'
|
11
|
+
end
|
12
|
+
|
13
|
+
def load_lens_status_icon(avg_response_time, error_rate)
|
14
|
+
return 'alert-circle' if error_rate > 10
|
15
|
+
return 'alert-triangle' if avg_response_time > 1000
|
16
|
+
'check-circle'
|
17
|
+
end
|
18
|
+
|
19
|
+
def format_response_time(time_ms)
|
20
|
+
return "0ms" if time_ms.nil? || time_ms == 0
|
21
|
+
"#{time_ms.round(1)}ms"
|
22
|
+
end
|
23
|
+
|
24
|
+
def format_percentage(value)
|
25
|
+
return "0%" if value.nil? || value == 0
|
26
|
+
"#{value.round(1)}%"
|
27
|
+
end
|
28
|
+
|
29
|
+
def request_status_badge_class(status)
|
30
|
+
case status.to_i
|
31
|
+
when 200..299
|
32
|
+
'badge-success'
|
33
|
+
when 300..399
|
34
|
+
'badge-info'
|
35
|
+
when 400..499
|
36
|
+
'badge-warning'
|
37
|
+
when 500..599
|
38
|
+
'badge-error'
|
39
|
+
else
|
40
|
+
'badge-neutral'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def response_time_color_class(time_ms)
|
45
|
+
return 'text-base-content' if time_ms.nil? || time_ms == 0
|
46
|
+
|
47
|
+
case time_ms.to_f
|
48
|
+
when 0..100
|
49
|
+
'text-success'
|
50
|
+
when 100..500
|
51
|
+
'text-info'
|
52
|
+
when 500..1000
|
53
|
+
'text-warning'
|
54
|
+
else
|
55
|
+
'text-error'
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def performance_trend_indicator(current, previous)
|
60
|
+
return '' if current.nil? || previous.nil? || previous == 0
|
61
|
+
|
62
|
+
percentage_change = ((current - previous) / previous.to_f) * 100
|
63
|
+
|
64
|
+
if percentage_change > 5
|
65
|
+
content_tag(:span, '↑', class: 'text-error', title: "#{percentage_change.round(1)}% slower")
|
66
|
+
elsif percentage_change < -5
|
67
|
+
content_tag(:span, '↓', class: 'text-success', title: "#{percentage_change.abs.round(1)}% faster")
|
68
|
+
else
|
69
|
+
content_tag(:span, '→', class: 'text-info', title: 'Similar performance')
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def load_lens_metric_badge(value, threshold_warning, threshold_error, unit = 'ms')
|
74
|
+
formatted_value = "#{value}#{unit}"
|
75
|
+
|
76
|
+
badge_class = if value > threshold_error
|
77
|
+
'badge-error'
|
78
|
+
elsif value > threshold_warning
|
79
|
+
'badge-warning'
|
80
|
+
else
|
81
|
+
'badge-success'
|
82
|
+
end
|
83
|
+
|
84
|
+
content_tag(:span, formatted_value, class: "badge #{badge_class}")
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Solidstats
|
4
|
+
module ProductivityHelper
|
5
|
+
def todo_type_color(type)
|
6
|
+
case type.to_s.downcase
|
7
|
+
when 'todo' then 'primary'
|
8
|
+
when 'fixme' then 'error'
|
9
|
+
when 'hack' then 'warning'
|
10
|
+
when 'note' then 'info'
|
11
|
+
when 'bug' then 'error'
|
12
|
+
else 'neutral'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def todo_type_icon(type)
|
17
|
+
case type.to_s.downcase
|
18
|
+
when 'todo' then '📝'
|
19
|
+
when 'fixme' then '🔧'
|
20
|
+
when 'hack' then '⚠️'
|
21
|
+
when 'note' then '📋'
|
22
|
+
when 'bug' then '🐛'
|
23
|
+
else '📌'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def todo_priority_class(type)
|
28
|
+
case type.to_s.downcase
|
29
|
+
when 'bug' then 'priority-high'
|
30
|
+
when 'fixme' then 'priority-high'
|
31
|
+
when 'hack' then 'priority-medium'
|
32
|
+
when 'todo' then 'priority-low'
|
33
|
+
when 'note' then 'priority-low'
|
34
|
+
else 'priority-low'
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,206 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Solidstats
|
4
|
+
# Service to collect and process bundler audit security vulnerability data
|
5
|
+
class BundlerAuditService
|
6
|
+
CACHE_FILE = Rails.root.join("solidstats", "bundler_audit.json")
|
7
|
+
CACHE_HOURS = 24 # Cache for 24 hours
|
8
|
+
|
9
|
+
class << self
|
10
|
+
# Get cached vulnerabilities or scan if cache is stale
|
11
|
+
# @return [Hash] The vulnerability data from JSON file
|
12
|
+
def fetch_vulnerabilities
|
13
|
+
if cache_stale?
|
14
|
+
scan_and_cache
|
15
|
+
end
|
16
|
+
|
17
|
+
load_cached_data
|
18
|
+
end
|
19
|
+
|
20
|
+
# Force a fresh scan and update cache
|
21
|
+
# @return [Hash] Fresh vulnerability data
|
22
|
+
def scan_and_cache
|
23
|
+
Rails.logger.info("Running bundler audit scan...")
|
24
|
+
|
25
|
+
begin
|
26
|
+
vulnerabilities_data = collect_bundler_audit_data
|
27
|
+
save_to_cache(vulnerabilities_data)
|
28
|
+
update_summary_json(vulnerabilities_data)
|
29
|
+
vulnerabilities_data
|
30
|
+
rescue => e
|
31
|
+
Rails.logger.error("Error running bundler audit: #{e.message}")
|
32
|
+
{ "output" => { "results" => [], "error" => e.message } }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Get summary for dashboard display
|
37
|
+
# @return [Hash] Summary information with status, count, and message
|
38
|
+
def summary
|
39
|
+
data = fetch_vulnerabilities
|
40
|
+
results = data.dig("output", "results") || []
|
41
|
+
vuln_count = results.count
|
42
|
+
|
43
|
+
{
|
44
|
+
count: vuln_count,
|
45
|
+
status: determine_status(vuln_count),
|
46
|
+
message: generate_message(vuln_count),
|
47
|
+
last_updated: data.dig("output", "created_at") || Time.current
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
# Check if cache file exists and is fresh
|
54
|
+
# @return [Boolean] true if cache is stale or missing
|
55
|
+
def cache_stale?
|
56
|
+
return true unless File.exist?(CACHE_FILE)
|
57
|
+
|
58
|
+
file_age = Time.current - File.mtime(CACHE_FILE)
|
59
|
+
file_age > CACHE_HOURS.hours
|
60
|
+
end
|
61
|
+
|
62
|
+
# Load data from cache file
|
63
|
+
# @return [Hash] Cached vulnerability data
|
64
|
+
def load_cached_data
|
65
|
+
return { "output" => { "results" => [] } } unless File.exist?(CACHE_FILE)
|
66
|
+
|
67
|
+
JSON.parse(File.read(CACHE_FILE))
|
68
|
+
rescue JSON::ParserError => e
|
69
|
+
Rails.logger.error("Error parsing bundler audit cache: #{e.message}")
|
70
|
+
{ "output" => { "results" => [] } }
|
71
|
+
end
|
72
|
+
|
73
|
+
# Run bundler audit and collect vulnerability data
|
74
|
+
# @return [Hash] Fresh vulnerability data
|
75
|
+
def collect_bundler_audit_data
|
76
|
+
# Run bundler audit with JSON format
|
77
|
+
result = `bundle audit check --update --format json 2>&1`
|
78
|
+
|
79
|
+
# Check if bundler-audit is installed
|
80
|
+
if $?.exitstatus == 127 || result.include?("command not found")
|
81
|
+
raise "bundler-audit gem is not installed. Please run: gem install bundler-audit"
|
82
|
+
end
|
83
|
+
|
84
|
+
# Extract JSON part from output (bundler-audit may include extra text)
|
85
|
+
json_match = result.match(/(\{.*\})/m)
|
86
|
+
if json_match
|
87
|
+
parsed_data = JSON.parse(json_match[1])
|
88
|
+
|
89
|
+
# Add metadata
|
90
|
+
{
|
91
|
+
"output" => {
|
92
|
+
"version" => parsed_data.dig("version") || "unknown",
|
93
|
+
"created_at" => Time.current.strftime("%Y-%m-%d %H:%M:%S %z"),
|
94
|
+
"results" => parsed_data.dig("results") || []
|
95
|
+
}
|
96
|
+
}
|
97
|
+
else
|
98
|
+
# If no JSON found, create empty structure
|
99
|
+
{
|
100
|
+
"output" => {
|
101
|
+
"version" => "unknown",
|
102
|
+
"created_at" => Time.current.strftime("%Y-%m-%d %H:%M:%S %z"),
|
103
|
+
"results" => []
|
104
|
+
}
|
105
|
+
}
|
106
|
+
end
|
107
|
+
rescue JSON::ParserError => e
|
108
|
+
raise "Failed to parse bundler audit JSON output: #{e.message}"
|
109
|
+
end
|
110
|
+
|
111
|
+
# Save vulnerability data to cache file
|
112
|
+
# @param data [Hash] Vulnerability data to cache
|
113
|
+
def save_to_cache(data)
|
114
|
+
FileUtils.mkdir_p(File.dirname(CACHE_FILE))
|
115
|
+
File.write(CACHE_FILE, JSON.pretty_generate(data))
|
116
|
+
Rails.logger.info("Bundler audit data cached to #{CACHE_FILE}")
|
117
|
+
end
|
118
|
+
|
119
|
+
# Update the main summary.json file with security vulnerabilities
|
120
|
+
# @param data [Hash] Vulnerability data
|
121
|
+
def update_summary_json(data)
|
122
|
+
summary_file = Rails.root.join("solidstats", "summary.json")
|
123
|
+
|
124
|
+
# Ensure directory exists
|
125
|
+
FileUtils.mkdir_p(File.dirname(summary_file))
|
126
|
+
|
127
|
+
# Load existing summary or create new
|
128
|
+
existing_summary = if File.exist?(summary_file)
|
129
|
+
JSON.parse(File.read(summary_file))
|
130
|
+
else
|
131
|
+
{}
|
132
|
+
end
|
133
|
+
|
134
|
+
# Get vulnerability count and status
|
135
|
+
results = data.dig("output", "results") || []
|
136
|
+
vuln_count = results.count
|
137
|
+
status = determine_status(vuln_count)
|
138
|
+
|
139
|
+
# Calculate severity distribution
|
140
|
+
severity_counts = results.group_by { |r| r.dig("advisory", "criticality") || "unknown" }
|
141
|
+
.transform_values(&:count)
|
142
|
+
|
143
|
+
# Create badges for severity levels
|
144
|
+
badges = []
|
145
|
+
%w[critical high medium low].each do |severity|
|
146
|
+
count = severity_counts[severity] || 0
|
147
|
+
if count > 0
|
148
|
+
badges << {
|
149
|
+
"text" => "#{severity.capitalize}: #{count}",
|
150
|
+
"color" => severity_badge_color(severity)
|
151
|
+
}
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
# Update summary
|
156
|
+
existing_summary["Security Vulnerabilities"] = {
|
157
|
+
"icon" => "shield-alert",
|
158
|
+
"status" => status,
|
159
|
+
"value" => generate_message(vuln_count),
|
160
|
+
"last_updated" => data.dig("output", "created_at") || Time.current.iso8601,
|
161
|
+
"url" => "/solidstats/securities/bundler_audit",
|
162
|
+
"badges" => badges
|
163
|
+
}
|
164
|
+
|
165
|
+
# Save updated summary
|
166
|
+
File.write(summary_file, JSON.pretty_generate(existing_summary))
|
167
|
+
Rails.logger.info("Updated summary.json with security vulnerabilities")
|
168
|
+
end
|
169
|
+
|
170
|
+
# Determine status color based on vulnerability count
|
171
|
+
# @param count [Integer] Number of vulnerabilities
|
172
|
+
# @return [String] Status indicator
|
173
|
+
def determine_status(count)
|
174
|
+
case count
|
175
|
+
when 0 then "success"
|
176
|
+
when 1..2 then "warning"
|
177
|
+
else "danger"
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
# Generate status message based on vulnerability count
|
182
|
+
# @param count [Integer] Number of vulnerabilities
|
183
|
+
# @return [String] Human-readable status message
|
184
|
+
def generate_message(count)
|
185
|
+
case count
|
186
|
+
when 0 then "No vulnerabilities"
|
187
|
+
when 1 then "1 vulnerability"
|
188
|
+
else "#{count} vulnerabilities"
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
# Get badge color for severity level
|
193
|
+
# @param severity [String] Severity level
|
194
|
+
# @return [String] Badge color
|
195
|
+
def severity_badge_color(severity)
|
196
|
+
case severity.downcase
|
197
|
+
when "critical" then "red"
|
198
|
+
when "high" then "orange"
|
199
|
+
when "medium" then "yellow"
|
200
|
+
when "low" then "blue"
|
201
|
+
else "gray"
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|