solidstats 3.0.0.beta.1 → 3.0.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.
- checksums.yaml +4 -4
- data/README.md +33 -0
- data/app/assets/javascripts/solidstats/application.js +257 -0
- data/app/assets/javascripts/solidstats/dashboard.js +179 -0
- data/app/assets/stylesheets/solidstats/application.css +6 -1
- data/app/controllers/solidstats/dashboard_controller.rb +28 -35
- data/app/controllers/solidstats/gem_metadata_controller.rb +12 -0
- data/app/controllers/solidstats/logs_controller.rb +12 -12
- data/app/controllers/solidstats/performance_controller.rb +2 -2
- data/app/controllers/solidstats/productivity_controller.rb +10 -10
- data/app/controllers/solidstats/quality_controller.rb +32 -32
- data/app/controllers/solidstats/securities_controller.rb +7 -7
- data/app/helpers/solidstats/application_helper.rb +10 -10
- data/app/helpers/solidstats/performance_helper.rb +32 -32
- data/app/helpers/solidstats/productivity_helper.rb +20 -20
- data/app/services/solidstats/bundler_audit_service.rb +13 -13
- data/app/services/solidstats/coverage_compass_service.rb +59 -59
- data/app/services/solidstats/load_lens_service.rb +90 -70
- data/app/services/solidstats/log_size_monitor_service.rb +59 -59
- data/app/services/solidstats/my_todo_service.rb +68 -68
- data/app/services/solidstats/style_patrol_service.rb +44 -44
- data/app/views/layouts/solidstats/application.html.erb +1 -1
- data/app/views/solidstats/shared/_quick_actions.html.erb +1 -1
- data/config/routes.rb +4 -4
- data/lib/generators/solidstats/clean/clean_generator.rb +24 -0
- data/lib/generators/solidstats/clean/templates/README +8 -0
- data/lib/generators/solidstats/install/install_generator.rb +32 -17
- data/lib/generators/solidstats/templates/initializer.rb +112 -0
- data/lib/solidstats/asset_compatibility.rb +238 -0
- data/lib/solidstats/asset_manifest.rb +205 -0
- data/lib/solidstats/engine.rb +49 -9
- data/lib/solidstats/version.rb +1 -1
- data/lib/solidstats.rb +24 -11
- data/lib/tasks/solidstats.rake +67 -0
- data/lib/tasks/solidstats_performance.rake +6 -29
- data/lib/tasks/solidstats_tasks.rake +16 -0
- metadata +14 -5
- data/lib/tasks/solidstats_install.rake +0 -13
@@ -1,14 +1,14 @@
|
|
1
1
|
module Solidstats
|
2
2
|
class LogsController < ApplicationController
|
3
|
-
layout
|
4
|
-
|
3
|
+
layout "solidstats/dashboard"
|
4
|
+
|
5
5
|
def logs_size
|
6
6
|
@logs_data = Solidstats::LogSizeMonitorService.get_logs_data
|
7
7
|
end
|
8
8
|
|
9
9
|
def truncate
|
10
10
|
filename = params[:filename]
|
11
|
-
|
11
|
+
|
12
12
|
# Validate filename presence
|
13
13
|
if filename.blank?
|
14
14
|
return render json: {
|
@@ -16,20 +16,20 @@ module Solidstats
|
|
16
16
|
message: "Filename is required"
|
17
17
|
}, status: :bad_request
|
18
18
|
end
|
19
|
-
|
19
|
+
|
20
20
|
# Remove any path traversal attempts for security
|
21
21
|
filename = File.basename(filename)
|
22
|
-
|
22
|
+
|
23
23
|
# Ensure it's a .log file
|
24
|
-
unless filename.end_with?(
|
24
|
+
unless filename.end_with?(".log")
|
25
25
|
filename = "#{filename}.log" if filename.present?
|
26
26
|
end
|
27
27
|
|
28
28
|
# Additional security check
|
29
|
-
unless filename.end_with?(
|
30
|
-
return render json: {
|
31
|
-
status: "error",
|
32
|
-
message: "Invalid file type. Only .log files can be truncated."
|
29
|
+
unless filename.end_with?(".log")
|
30
|
+
return render json: {
|
31
|
+
status: "error",
|
32
|
+
message: "Invalid file type. Only .log files can be truncated."
|
33
33
|
}, status: :bad_request
|
34
34
|
end
|
35
35
|
|
@@ -56,7 +56,7 @@ module Solidstats
|
|
56
56
|
def refresh
|
57
57
|
# Force refresh of log monitoring data
|
58
58
|
result = Solidstats::LogSizeMonitorService.scan_and_cache
|
59
|
-
|
59
|
+
|
60
60
|
render json: {
|
61
61
|
status: "success",
|
62
62
|
message: "Log data refreshed",
|
@@ -64,7 +64,7 @@ module Solidstats
|
|
64
64
|
}
|
65
65
|
rescue StandardError => e
|
66
66
|
render json: {
|
67
|
-
status: "error",
|
67
|
+
status: "error",
|
68
68
|
message: "Failed to refresh log data: #{e.message}"
|
69
69
|
}, status: :internal_server_error
|
70
70
|
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module Solidstats
|
4
4
|
class PerformanceController < ApplicationController
|
5
|
-
layout
|
5
|
+
layout "solidstats/dashboard"
|
6
6
|
|
7
7
|
def load_lens
|
8
8
|
@performance_data = LoadLensService.get_performance_data
|
@@ -14,7 +14,7 @@ module Solidstats
|
|
14
14
|
# Parse new log entries and refresh cache
|
15
15
|
parse_result = LoadLensService.parse_log_and_save
|
16
16
|
LoadLensService.refresh_data
|
17
|
-
|
17
|
+
|
18
18
|
if parse_result[:success]
|
19
19
|
redirect_to load_lens_performance_index_path, notice: "LoadLens data refreshed! Parsed #{parse_result[:processed]} new requests."
|
20
20
|
else
|
@@ -2,37 +2,37 @@
|
|
2
2
|
|
3
3
|
module Solidstats
|
4
4
|
class ProductivityController < ApplicationController
|
5
|
-
layout
|
6
|
-
|
5
|
+
layout "solidstats/dashboard"
|
6
|
+
|
7
7
|
def my_todos
|
8
8
|
@todos = MyTodoService.collect_todos
|
9
9
|
@summary = MyTodoService.get_summary
|
10
|
-
|
10
|
+
|
11
11
|
# Group todos by type for display
|
12
12
|
@todos_by_type = @todos.group_by { |todo| todo[:type] }
|
13
|
-
|
13
|
+
|
14
14
|
# Recent todos (first 10 for quick view)
|
15
15
|
@recent_todos = @todos.first(10)
|
16
|
-
|
16
|
+
|
17
17
|
# Stats for dashboard
|
18
18
|
@stats = {
|
19
19
|
total: @todos.length,
|
20
20
|
by_type: @todos_by_type.transform_values(&:count),
|
21
21
|
files_with_todos: @todos.map { |t| t[:file] }.uniq.length
|
22
22
|
}
|
23
|
-
|
23
|
+
|
24
24
|
respond_to do |format|
|
25
25
|
format.html
|
26
26
|
format.json { render json: { todos: @todos, summary: @summary, stats: @stats } }
|
27
27
|
end
|
28
28
|
end
|
29
|
-
|
29
|
+
|
30
30
|
def refresh_todos
|
31
31
|
@todos = MyTodoService.collect_todos(force_refresh: true)
|
32
|
-
|
32
|
+
|
33
33
|
respond_to do |format|
|
34
|
-
format.html { redirect_to my_todos_productivity_index_path, notice:
|
35
|
-
format.json { render json: { status:
|
34
|
+
format.html { redirect_to my_todos_productivity_index_path, notice: "TODOs refreshed successfully!" }
|
35
|
+
format.json { render json: { status: "success", message: "TODOs refreshed", count: @todos.length } }
|
36
36
|
end
|
37
37
|
end
|
38
38
|
end
|
@@ -2,13 +2,13 @@
|
|
2
2
|
|
3
3
|
module Solidstats
|
4
4
|
class QualityController < ApplicationController
|
5
|
-
layout
|
6
|
-
|
5
|
+
layout "solidstats/dashboard"
|
6
|
+
|
7
7
|
# Display Standard gem code quality analysis
|
8
8
|
def style_patrol
|
9
9
|
@analysis_data = Solidstats::StylePatrolService.collect_data
|
10
10
|
@summary = Solidstats::StylePatrolService.get_summary
|
11
|
-
|
11
|
+
|
12
12
|
# Group issues by file for better display
|
13
13
|
if @analysis_data[:issues].present?
|
14
14
|
@issues_by_file = @analysis_data[:issues].group_by { |issue| issue[:file] }
|
@@ -17,56 +17,56 @@ module Solidstats
|
|
17
17
|
@issues_by_file = {}
|
18
18
|
@issues_by_severity = {}
|
19
19
|
end
|
20
|
-
|
21
|
-
render
|
20
|
+
|
21
|
+
render "style_patrol"
|
22
22
|
end
|
23
|
-
|
23
|
+
|
24
24
|
# Force refresh of style patrol data
|
25
25
|
def refresh_style_patrol
|
26
26
|
@analysis_data = Solidstats::StylePatrolService.refresh_cache
|
27
|
-
redirect_to quality_style_patrol_path, notice:
|
27
|
+
redirect_to quality_style_patrol_path, notice: "Code quality analysis refreshed successfully."
|
28
28
|
rescue => e
|
29
29
|
redirect_to quality_style_patrol_path, alert: "Error refreshing analysis: #{e.message}"
|
30
30
|
end
|
31
|
-
|
31
|
+
|
32
32
|
# Display SimpleCov code coverage analysis
|
33
33
|
def coverage_compass
|
34
34
|
# Get coverage data using the correct method
|
35
35
|
@analysis_data = Solidstats::CoverageCompassService.collect_data
|
36
|
-
|
36
|
+
|
37
37
|
# Handle different response types from service
|
38
38
|
if @analysis_data
|
39
39
|
if @analysis_data[:setup_required]
|
40
40
|
@setup_instructions = @analysis_data[:instructions]
|
41
41
|
@show_setup = true
|
42
|
-
@analysis_data[:status] =
|
42
|
+
@analysis_data[:status] = "setup_required"
|
43
43
|
elsif @analysis_data[:stale_data]
|
44
44
|
@stale_warning = true
|
45
45
|
@data_age_hours = @analysis_data[:data_age_hours]
|
46
46
|
@suggestions = @analysis_data[:suggestions]
|
47
47
|
@analysis_data = @analysis_data[:last_coverage] || @analysis_data
|
48
|
-
@analysis_data[:status] =
|
48
|
+
@analysis_data[:status] = "stale"
|
49
49
|
elsif @analysis_data[:error]
|
50
50
|
@error_message = @analysis_data[:error]
|
51
|
-
@analysis_data[:status] =
|
51
|
+
@analysis_data[:status] = "error"
|
52
52
|
else
|
53
|
-
@analysis_data[:status] =
|
53
|
+
@analysis_data[:status] = "success"
|
54
54
|
end
|
55
|
-
|
55
|
+
|
56
56
|
# Organize file coverage data for better display
|
57
57
|
organize_coverage_data
|
58
58
|
else
|
59
59
|
set_error_state
|
60
|
-
@analysis_data = { status:
|
60
|
+
@analysis_data = { status: "error" }
|
61
61
|
end
|
62
|
-
|
63
|
-
render
|
62
|
+
|
63
|
+
render "coverage_compass"
|
64
64
|
end
|
65
|
-
|
65
|
+
|
66
66
|
# Force refresh of coverage compass data
|
67
67
|
def refresh_coverage_compass
|
68
68
|
Solidstats::CoverageCompassService.refresh_cache
|
69
|
-
redirect_to quality_coverage_compass_path, notice:
|
69
|
+
redirect_to quality_coverage_compass_path, notice: "Code coverage analysis refreshed successfully."
|
70
70
|
rescue => e
|
71
71
|
redirect_to quality_coverage_compass_path, alert: "Error refreshing coverage analysis: #{e.message}"
|
72
72
|
end
|
@@ -85,7 +85,7 @@ module Solidstats
|
|
85
85
|
missed_lines: data[:missed_lines] || []
|
86
86
|
}
|
87
87
|
end
|
88
|
-
|
88
|
+
|
89
89
|
# Group files by coverage ranges for better visualization
|
90
90
|
@coverage_ranges = {
|
91
91
|
excellent: @file_coverage.select { |f| f[:coverage_percent] >= 90 },
|
@@ -97,7 +97,7 @@ module Solidstats
|
|
97
97
|
@file_coverage = []
|
98
98
|
@coverage_ranges = { excellent: [], good: [], needs_improvement: [], poor: [] }
|
99
99
|
end
|
100
|
-
|
100
|
+
|
101
101
|
# Set summary data
|
102
102
|
@summary = {
|
103
103
|
overall_coverage: @analysis_data[:overall_coverage] || 0,
|
@@ -115,15 +115,15 @@ module Solidstats
|
|
115
115
|
@error_message = "Failed to retrieve coverage data"
|
116
116
|
@file_coverage = []
|
117
117
|
@coverage_ranges = { excellent: [], good: [], needs_improvement: [], poor: [] }
|
118
|
-
@summary = {
|
119
|
-
overall_coverage: 0,
|
118
|
+
@summary = {
|
119
|
+
overall_coverage: 0,
|
120
120
|
coverage_percent: 0,
|
121
|
-
total_lines: 0,
|
122
|
-
covered_lines: 0,
|
121
|
+
total_lines: 0,
|
122
|
+
covered_lines: 0,
|
123
123
|
missed_lines: 0,
|
124
124
|
total_files: 0,
|
125
125
|
health_score: 0,
|
126
|
-
coverage_grade:
|
126
|
+
coverage_grade: "F"
|
127
127
|
}
|
128
128
|
end
|
129
129
|
|
@@ -140,12 +140,12 @@ module Solidstats
|
|
140
140
|
|
141
141
|
def determine_coverage_grade(coverage_percent)
|
142
142
|
case coverage_percent
|
143
|
-
when 90..100 then
|
144
|
-
when 80..89 then
|
145
|
-
when 70..79 then
|
146
|
-
when 60..69 then
|
147
|
-
when 50..59 then
|
148
|
-
else
|
143
|
+
when 90..100 then "A+"
|
144
|
+
when 80..89 then "A"
|
145
|
+
when 70..79 then "B"
|
146
|
+
when 60..69 then "C"
|
147
|
+
when 50..59 then "D"
|
148
|
+
else "F"
|
149
149
|
end
|
150
150
|
end
|
151
151
|
end
|
@@ -2,27 +2,27 @@
|
|
2
2
|
|
3
3
|
module Solidstats
|
4
4
|
class SecuritiesController < ApplicationController
|
5
|
-
layout
|
6
|
-
|
5
|
+
layout "solidstats/dashboard"
|
6
|
+
|
7
7
|
# Display bundler audit security vulnerabilities
|
8
8
|
def bundler_audit
|
9
9
|
@vulnerabilities_data = Solidstats::BundlerAuditService.fetch_vulnerabilities
|
10
10
|
@vulnerabilities = @vulnerabilities_data.dig("output", "results") || []
|
11
11
|
@summary = Solidstats::BundlerAuditService.summary
|
12
12
|
@last_updated = @vulnerabilities_data.dig("output", "created_at")
|
13
|
-
|
13
|
+
|
14
14
|
# Group vulnerabilities by severity for better display
|
15
15
|
@vulnerabilities_by_severity = @vulnerabilities.group_by do |vuln|
|
16
16
|
vuln.dig("advisory", "criticality") || "unknown"
|
17
17
|
end
|
18
|
-
|
19
|
-
render
|
18
|
+
|
19
|
+
render "bundler_audit"
|
20
20
|
end
|
21
|
-
|
21
|
+
|
22
22
|
# Force refresh of bundler audit data
|
23
23
|
def refresh_bundler_audit
|
24
24
|
@vulnerabilities_data = Solidstats::BundlerAuditService.scan_and_cache
|
25
|
-
redirect_to securities_bundler_audit_path, notice:
|
25
|
+
redirect_to securities_bundler_audit_path, notice: "Security vulnerabilities refreshed successfully."
|
26
26
|
rescue => e
|
27
27
|
redirect_to securities_bundler_audit_path, alert: "Error refreshing vulnerabilities: #{e.message}"
|
28
28
|
end
|
@@ -2,9 +2,9 @@ module Solidstats
|
|
2
2
|
module ApplicationHelper
|
3
3
|
def time_ago_in_words(from_time)
|
4
4
|
return "just now" if from_time.nil?
|
5
|
-
|
5
|
+
|
6
6
|
distance_in_seconds = (Time.current - from_time).to_i
|
7
|
-
|
7
|
+
|
8
8
|
case distance_in_seconds
|
9
9
|
when 0..29
|
10
10
|
"just now"
|
@@ -24,10 +24,10 @@ module Solidstats
|
|
24
24
|
|
25
25
|
def quick_actions
|
26
26
|
[
|
27
|
-
{ icon:
|
28
|
-
{ icon:
|
29
|
-
{ icon:
|
30
|
-
{ icon:
|
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
31
|
]
|
32
32
|
end
|
33
33
|
|
@@ -41,10 +41,10 @@ module Solidstats
|
|
41
41
|
|
42
42
|
combined_css = css_files.map do |file_path|
|
43
43
|
relative_path = file_path.gsub("#{engine_root}/app/assets/stylesheets/solidstats/", "")
|
44
|
-
|
44
|
+
|
45
45
|
# Skip manifest files (application.css with require statements)
|
46
46
|
next if relative_path == "application.css"
|
47
|
-
|
47
|
+
|
48
48
|
"/* === #{relative_path} === */\n#{File.read(file_path)}"
|
49
49
|
end.compact.join("\n\n")
|
50
50
|
|
@@ -80,7 +80,7 @@ module Solidstats
|
|
80
80
|
if (typeof feather !== 'undefined') {
|
81
81
|
feather.replace();
|
82
82
|
}
|
83
|
-
|
83
|
+
#{' '}
|
84
84
|
// Auto-refresh functionality with animations
|
85
85
|
setInterval(function() {
|
86
86
|
document.querySelectorAll('.card').forEach(function(card) {
|
@@ -114,7 +114,7 @@ module Solidstats
|
|
114
114
|
if (typeof feather !== 'undefined') {
|
115
115
|
feather.replace();
|
116
116
|
}
|
117
|
-
|
117
|
+
#{' '}
|
118
118
|
// Auto-refresh functionality with animations
|
119
119
|
setInterval(function() {
|
120
120
|
document.querySelectorAll('.card').forEach(function(card) {
|
@@ -5,15 +5,15 @@ module Solidstats
|
|
5
5
|
# Provides view helpers for displaying performance metrics
|
6
6
|
module PerformanceHelper
|
7
7
|
def load_lens_status_class(avg_response_time, error_rate)
|
8
|
-
return
|
9
|
-
return
|
10
|
-
|
8
|
+
return "text-error" if error_rate > 10
|
9
|
+
return "text-warning" if avg_response_time > 1000
|
10
|
+
"text-success"
|
11
11
|
end
|
12
12
|
|
13
13
|
def load_lens_status_icon(avg_response_time, error_rate)
|
14
|
-
return
|
15
|
-
return
|
16
|
-
|
14
|
+
return "alert-circle" if error_rate > 10
|
15
|
+
return "alert-triangle" if avg_response_time > 1000
|
16
|
+
"check-circle"
|
17
17
|
end
|
18
18
|
|
19
19
|
def format_response_time(time_ms)
|
@@ -29,58 +29,58 @@ module Solidstats
|
|
29
29
|
def request_status_badge_class(status)
|
30
30
|
case status.to_i
|
31
31
|
when 200..299
|
32
|
-
|
32
|
+
"badge-success"
|
33
33
|
when 300..399
|
34
|
-
|
34
|
+
"badge-info"
|
35
35
|
when 400..499
|
36
|
-
|
36
|
+
"badge-warning"
|
37
37
|
when 500..599
|
38
|
-
|
38
|
+
"badge-error"
|
39
39
|
else
|
40
|
-
|
40
|
+
"badge-neutral"
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
44
|
def response_time_color_class(time_ms)
|
45
|
-
return
|
46
|
-
|
45
|
+
return "text-base-content" if time_ms.nil? || time_ms == 0
|
46
|
+
|
47
47
|
case time_ms.to_f
|
48
48
|
when 0..100
|
49
|
-
|
49
|
+
"text-success"
|
50
50
|
when 100..500
|
51
|
-
|
51
|
+
"text-info"
|
52
52
|
when 500..1000
|
53
|
-
|
53
|
+
"text-warning"
|
54
54
|
else
|
55
|
-
|
55
|
+
"text-error"
|
56
56
|
end
|
57
57
|
end
|
58
58
|
|
59
59
|
def performance_trend_indicator(current, previous)
|
60
|
-
return
|
61
|
-
|
60
|
+
return "" if current.nil? || previous.nil? || previous == 0
|
61
|
+
|
62
62
|
percentage_change = ((current - previous) / previous.to_f) * 100
|
63
|
-
|
63
|
+
|
64
64
|
if percentage_change > 5
|
65
|
-
content_tag(:span,
|
65
|
+
content_tag(:span, "↑", class: "text-error", title: "#{percentage_change.round(1)}% slower")
|
66
66
|
elsif percentage_change < -5
|
67
|
-
content_tag(:span,
|
67
|
+
content_tag(:span, "↓", class: "text-success", title: "#{percentage_change.abs.round(1)}% faster")
|
68
68
|
else
|
69
|
-
content_tag(:span,
|
69
|
+
content_tag(:span, "→", class: "text-info", title: "Similar performance")
|
70
70
|
end
|
71
71
|
end
|
72
72
|
|
73
|
-
def load_lens_metric_badge(value, threshold_warning, threshold_error, unit =
|
73
|
+
def load_lens_metric_badge(value, threshold_warning, threshold_error, unit = "ms")
|
74
74
|
formatted_value = "#{value}#{unit}"
|
75
|
-
|
75
|
+
|
76
76
|
badge_class = if value > threshold_error
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
77
|
+
"badge-error"
|
78
|
+
elsif value > threshold_warning
|
79
|
+
"badge-warning"
|
80
|
+
else
|
81
|
+
"badge-success"
|
82
|
+
end
|
83
|
+
|
84
84
|
content_tag(:span, formatted_value, class: "badge #{badge_class}")
|
85
85
|
end
|
86
86
|
end
|
@@ -4,34 +4,34 @@ module Solidstats
|
|
4
4
|
module ProductivityHelper
|
5
5
|
def todo_type_color(type)
|
6
6
|
case type.to_s.downcase
|
7
|
-
when
|
8
|
-
when
|
9
|
-
when
|
10
|
-
when
|
11
|
-
when
|
12
|
-
else
|
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
13
|
end
|
14
14
|
end
|
15
|
-
|
15
|
+
|
16
16
|
def todo_type_icon(type)
|
17
17
|
case type.to_s.downcase
|
18
|
-
when
|
19
|
-
when
|
20
|
-
when
|
21
|
-
when
|
22
|
-
when
|
23
|
-
else
|
18
|
+
when "todo" then "📝"
|
19
|
+
when "fixme" then "🔧"
|
20
|
+
when "hack" then "⚠️"
|
21
|
+
when "note" then "📋"
|
22
|
+
when "bug" then "🐛"
|
23
|
+
else "📌"
|
24
24
|
end
|
25
25
|
end
|
26
|
-
|
26
|
+
|
27
27
|
def todo_priority_class(type)
|
28
28
|
case type.to_s.downcase
|
29
|
-
when
|
30
|
-
when
|
31
|
-
when
|
32
|
-
when
|
33
|
-
when
|
34
|
-
else
|
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
35
|
end
|
36
36
|
end
|
37
37
|
end
|
@@ -21,7 +21,7 @@ module Solidstats
|
|
21
21
|
# @return [Hash] Fresh vulnerability data
|
22
22
|
def scan_and_cache
|
23
23
|
Rails.logger.info("Running bundler audit scan...")
|
24
|
-
|
24
|
+
|
25
25
|
begin
|
26
26
|
vulnerabilities_data = collect_bundler_audit_data
|
27
27
|
save_to_cache(vulnerabilities_data)
|
@@ -54,7 +54,7 @@ module Solidstats
|
|
54
54
|
# @return [Boolean] true if cache is stale or missing
|
55
55
|
def cache_stale?
|
56
56
|
return true unless File.exist?(CACHE_FILE)
|
57
|
-
|
57
|
+
|
58
58
|
file_age = Time.current - File.mtime(CACHE_FILE)
|
59
59
|
file_age > CACHE_HOURS.hours
|
60
60
|
end
|
@@ -63,7 +63,7 @@ module Solidstats
|
|
63
63
|
# @return [Hash] Cached vulnerability data
|
64
64
|
def load_cached_data
|
65
65
|
return { "output" => { "results" => [] } } unless File.exist?(CACHE_FILE)
|
66
|
-
|
66
|
+
|
67
67
|
JSON.parse(File.read(CACHE_FILE))
|
68
68
|
rescue JSON::ParserError => e
|
69
69
|
Rails.logger.error("Error parsing bundler audit cache: #{e.message}")
|
@@ -75,17 +75,17 @@ module Solidstats
|
|
75
75
|
def collect_bundler_audit_data
|
76
76
|
# Run bundler audit with JSON format
|
77
77
|
result = `bundle audit check --update --format json 2>&1`
|
78
|
-
|
78
|
+
|
79
79
|
# Check if bundler-audit is installed
|
80
80
|
if $?.exitstatus == 127 || result.include?("command not found")
|
81
81
|
raise "bundler-audit gem is not installed. Please run: gem install bundler-audit"
|
82
82
|
end
|
83
|
-
|
83
|
+
|
84
84
|
# Extract JSON part from output (bundler-audit may include extra text)
|
85
85
|
json_match = result.match(/(\{.*\})/m)
|
86
86
|
if json_match
|
87
87
|
parsed_data = JSON.parse(json_match[1])
|
88
|
-
|
88
|
+
|
89
89
|
# Add metadata
|
90
90
|
{
|
91
91
|
"output" => {
|
@@ -120,26 +120,26 @@ module Solidstats
|
|
120
120
|
# @param data [Hash] Vulnerability data
|
121
121
|
def update_summary_json(data)
|
122
122
|
summary_file = Rails.root.join("solidstats", "summary.json")
|
123
|
-
|
123
|
+
|
124
124
|
# Ensure directory exists
|
125
125
|
FileUtils.mkdir_p(File.dirname(summary_file))
|
126
|
-
|
126
|
+
|
127
127
|
# Load existing summary or create new
|
128
128
|
existing_summary = if File.exist?(summary_file)
|
129
129
|
JSON.parse(File.read(summary_file))
|
130
130
|
else
|
131
131
|
{}
|
132
132
|
end
|
133
|
-
|
133
|
+
|
134
134
|
# Get vulnerability count and status
|
135
135
|
results = data.dig("output", "results") || []
|
136
136
|
vuln_count = results.count
|
137
137
|
status = determine_status(vuln_count)
|
138
|
-
|
138
|
+
|
139
139
|
# Calculate severity distribution
|
140
140
|
severity_counts = results.group_by { |r| r.dig("advisory", "criticality") || "unknown" }
|
141
141
|
.transform_values(&:count)
|
142
|
-
|
142
|
+
|
143
143
|
# Create badges for severity levels
|
144
144
|
badges = []
|
145
145
|
%w[critical high medium low].each do |severity|
|
@@ -151,7 +151,7 @@ module Solidstats
|
|
151
151
|
}
|
152
152
|
end
|
153
153
|
end
|
154
|
-
|
154
|
+
|
155
155
|
# Update summary
|
156
156
|
existing_summary["Security Vulnerabilities"] = {
|
157
157
|
"icon" => "shield-alert",
|
@@ -161,7 +161,7 @@ module Solidstats
|
|
161
161
|
"url" => "/solidstats/securities/bundler_audit",
|
162
162
|
"badges" => badges
|
163
163
|
}
|
164
|
-
|
164
|
+
|
165
165
|
# Save updated summary
|
166
166
|
File.write(summary_file, JSON.pretty_generate(existing_summary))
|
167
167
|
Rails.logger.info("Updated summary.json with security vulnerabilities")
|