solidstats 0.0.4 → 1.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1e3efc1790ad2beb55d19f731457a39034c744524da80c85674bc0d02fdb1d83
4
- data.tar.gz: 50a12e44bf70c1b297d86f8645267f4648b1476df611cba0e72fffb26e40c638
3
+ metadata.gz: b1e51da79f65a50793bc1bfc777ed19df5d81795739ceda8558d3261745b53ae
4
+ data.tar.gz: e58f0018fd96135789f192372b2b85f7165cf51a6921fa7e2a56057bebdb14b5
5
5
  SHA512:
6
- metadata.gz: a5d7b257090f0869fc20c6c1144f7a120d7e8bdc12d8474df5db603d6918f417d7b59a24a38f0e89cf0e89c0d2507de6a7984f8f293a3a1d3b3f5e368e29d2de
7
- data.tar.gz: c7dccdf4109e89e33ccad5a58af97e9cb4ea7e317243a6de9eafc0e24a5001abef0650b9a133bc8af6b5c52bdbf187c4818a28a519e55df42731611d89fe74da
6
+ metadata.gz: 3fc113aaaf952e1d9a320b55618b72d4b6025ec81d3bc274f29a090ddbc6a0f500bbd50aa2873d8e02d850dac992ef28214e5709f0160bc2ce0e88e348182f0a
7
+ data.tar.gz: 976d3989eeffc2781c1040846af1435147be33a407b6e5a2bae1db8dfba091f9678635b1863c3dcc36916adf5d2b334cc0f0a7d523225066bb4fdac074ebf270
data/CHANGELOG.md CHANGED
@@ -5,6 +5,82 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.1.0] - 2025-05-23
9
+
10
+ ### Added
11
+ - Log Size Monitor feature:
12
+ - Comprehensive monitoring of all Rails application log files
13
+ - Total log directory size tracking with status indicators
14
+ - Individual log file monitoring with size and status details
15
+ - Visual indicators for log size status (OK, Warning, Danger)
16
+ - Visual meter showing log size relative to thresholds
17
+ - One-click log truncation for individual files or all logs
18
+ - Sortable log file table with size and last modified information
19
+ - Log management recommendations
20
+
21
+ ### Fixed
22
+ - Fixed log file truncation when filename is provided without extension
23
+ - Added constraints to route handling to improve filename parameter handling
24
+
25
+ ## [1.0.0] - 2025-05-22
26
+
27
+ ### Added
28
+ - Fully functional refresh button with AJAX updates
29
+ - Dynamic dashboard refreshing for all components:
30
+ - Security metrics and score update in real-time
31
+ - Vulnerability table regeneration with fresh data
32
+ - Donut chart visualization updates
33
+ - Gem impact analysis section updates
34
+ - Vulnerability details section updates
35
+ - Enhanced disabled button tooltips with inline display
36
+ - Comprehensive security dashboard features:
37
+ - Security score rating (A+, B, C) based on vulnerabilities
38
+ - Detailed vulnerability table with filtering and searching
39
+ - Visual severity breakdown with interactive donut chart
40
+ - Gem impact analysis with affected gem grouping
41
+ - Detailed vulnerability information with remediation options
42
+
43
+ ### Fixed
44
+ - Vulnerability details no longer hidden under sticky header when clicking "More details"
45
+ - Improved scroll offset calculations for better navigation
46
+ - Added visual feedback with animations when viewing details
47
+ - Fixed "Back to vulnerabilities table" button positioning
48
+
49
+ ## [0.0.4] - 2025-05-20
50
+
51
+ ### Added
52
+ - Service-based architecture for data collection with caching
53
+ - Base DataCollectorService class for shared functionality
54
+ - Specialized services: AuditService and TodoService
55
+ - Comprehensive UI/UX redesign with modern dashboard layout:
56
+ - Sticky navigation menu with intuitive section links
57
+ - Organized dashboard sections (Overview, Security, Code Quality, Tasks)
58
+ - Tabbed interfaces for better content organization
59
+ - Interactive summary cards with click-to-navigate functionality
60
+ - Enhanced security visualization:
61
+ - Visual security score indicator (A+, B, C ratings) with color coding
62
+ - Improved vulnerability metrics display with severity breakdown
63
+ - Detailed vulnerability table with filtering and search capabilities
64
+ - New "Affected Gems" tab with visual cards for vulnerable dependencies
65
+ - Timeline visualization for security history
66
+ - Improved code quality section:
67
+ - Dedicated tabs for Metrics, Test Coverage, and Code Health
68
+ - Visual progress bars for test coverage
69
+ - Task management improvements:
70
+ - Organized tabs for TODOs, FIXMEs, and HACKs
71
+ - Better categorization and display of code tasks
72
+ - Responsive layout adjustments for various screen sizes
73
+ - Quick floating navigation menu for easy section access
74
+ - Toast notification system for user feedback
75
+
76
+ ### Fixed
77
+ - Ruby version compatibility issues with dependency constraints
78
+ - Support for multiple Ruby versions (2.7+, 3.0+, 3.2+)
79
+ - Automatic Rails version selection based on Ruby version (6.1 through 8.x)
80
+ - Documentation of compatibility matrix in README
81
+ - Variable scope issues in nested template partials
82
+ - Consistent passing of data between dashboard partials
83
+
8
84
  ## [0.0.3] - 2025-05-20
9
85
 
10
86
  ### Added
@@ -28,8 +104,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
28
104
 
29
105
  ## [Unreleased]
30
106
 
31
- - Improved TODO dashboard partial:
32
- - Ensured correct handling of nil keys in the hotspots hash.
33
- - Displayed TODO, FIXME, and HACK counts with color-coded badges.
34
- - Added expandable details for files with most TODOs and all TODO items.
35
- - Enhanced UI with better grouping and error handling for TODO data.
107
+ ### Added
108
+ - Future enhancements will be listed here
data/README.md CHANGED
@@ -1,12 +1,30 @@
1
-
2
- Solidstats is a local-only Rails engine that shows your project's health at `/solidstats`.
1
+ Solidstats is a local-only Rails engine that shows your project's health at `/solidstats`. The dashboard provides real-time insights into your application's security, code quality, and development tasks.
3
2
 
4
3
  ## Features
5
- - Bundler Audit scan
6
- - Rubocop offense count
7
- - TODO/FIXME tracker
4
+ - Interactive security dashboard with real-time refresh capability
5
+ - Comprehensive gem vulnerability analysis with severity breakdown
6
+ - Visual security score rating (A+, B, C) and metrics
7
+ - Bundler Audit scan with detailed remediation suggestions
8
+ - Interactive vulnerability details with patched version information
9
+ - Gem impact analysis showing affected gems by severity
10
+ - Log Size Monitor for tracking and managing application log files
11
+ - Log file truncation tool for individual or all log files
12
+ - Rubocop offense count and quality metrics
13
+ - TODO/FIXME tracker with file hotspots
8
14
  - Test coverage summary
9
15
 
16
+ ## Compatibility
17
+
18
+ - Ruby 2.7+: Compatible with Rails 6.1 through Rails 7.0
19
+ - Ruby 3.0-3.1: Compatible with Rails 6.1 through Rails 7.x
20
+ - Ruby 3.2+: Compatible with all Rails 6.1+ versions
21
+
22
+ Solidstats automatically detects your Ruby version and selects a compatible Rails version.
23
+
24
+ ### CI/Testing
25
+
26
+ This gem is automatically tested across multiple Ruby versions (2.7, 3.0, 3.1, and 3.2) to ensure compatibility. If you're contributing to this gem, make sure your changes work across all supported Ruby versions.
27
+
10
28
  ## Installation
11
29
 
12
30
  ```ruby
@@ -35,5 +53,36 @@ The installer will automatically mount the engine in your routes:
35
53
  mount Solidstats::Engine => '/solidstats' if Rails.env.development?
36
54
  ```
37
55
 
56
+ ## Usage
57
+
58
+ Visit `/solidstats` in your development environment to access the dashboard. The dashboard provides an overview of your application's health and is organized into the following sections:
59
+
60
+ ### Overview
61
+ Shows summary cards for security issues, TODO items, and code quality metrics.
62
+
63
+ ### Security
64
+ Provides a comprehensive security dashboard with:
65
+ - Security score rating based on vulnerability severity
66
+ - Vulnerability metrics showing critical, high, medium and low issues
67
+ - Interactive vulnerability table with filtering and searching
68
+ - Gem impact analysis showing which gems are affected
69
+ - Detailed vulnerability information with remediation suggestions
70
+
71
+ You can refresh the dashboard data at any time by clicking the "Refresh" button in the top navigation bar. This will:
72
+ 1. Trigger a fresh security audit of your application
73
+ 2. Update all metrics and visualizations with current data
74
+ 3. Show real-time feedback during the refresh process
75
+ 4. Update the "Last Updated" timestamp
76
+
77
+ ### Code Quality
78
+ Displays code quality metrics, test coverage, and code health indicators.
79
+
80
+ ### Tasks
81
+ Shows a breakdown of TODO, FIXME, and HACK annotations found in your codebase, with file hotspots and expandable details.
82
+
83
+ ## Contributing
84
+
85
+ Bug reports and pull requests are welcome on GitHub at https://github.com/infolily/solidstats.
86
+
38
87
  ## License
39
88
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -1,138 +1,77 @@
1
1
  module Solidstats
2
2
  class DashboardController < ApplicationController
3
- AUDIT_CACHE_FILE = Rails.root.join("tmp", "solidstats_audit.json")
4
3
  TODO_CACHE_FILE = Rails.root.join("tmp", "solidstats_todos.json")
5
4
  AUDIT_CACHE_HOURS = 12 # Configure how many hours before refreshing
6
5
 
7
6
  def index
8
- @audit_output = fetch_audit_output
9
- @rubocop_output = "JSON.parse(`rubocop --format json`)"
10
- @todo_items = fetch_todo_items
11
- @todo_stats = calculate_todo_stats(@todo_items)
12
- @coverage = "read_coverage_percent"
13
- end
14
-
15
- private
16
-
17
- def fetch_audit_output
18
- # Check if cache file exists and is recent enough
19
- if File.exist?(AUDIT_CACHE_FILE)
20
- cached_data = JSON.parse(File.read(AUDIT_CACHE_FILE))
21
- last_run_time = Time.parse(cached_data["timestamp"])
7
+ # Use new services for data collection
8
+ audit_service = AuditService.new
9
+ todo_service = TodoService.new
10
+ log_monitor_service = LogSizeMonitorService.new
22
11
 
23
- # Use cached data if it's less than AUDIT_CACHE_HOURS old
24
- if Time.now - last_run_time < AUDIT_CACHE_HOURS.hours
25
- return cached_data["output"]
26
- end
27
- end
28
-
29
- # Cache expired or doesn't exist, run the audit
30
- raw_output = `bundle audit check --update --format json`
31
- json_part = raw_output[/\{.*\}/m] # extract JSON starting from first '{'
32
- audit_output = JSON.parse(json_part) rescue { error: "Failed to parse JSON" }
33
-
34
- # Save to cache file with timestamp
35
- cache_data = {
36
- "output" => audit_output,
37
- "timestamp" => Time.now.iso8601
38
- }
12
+ # Get full data for detailed views
13
+ @audit_output = audit_service.fetch
14
+ @todo_items = todo_service.fetch
15
+ @log_data = log_monitor_service.collect_data
39
16
 
40
- # Ensure the tmp directory exists
41
- FileUtils.mkdir_p(File.dirname(AUDIT_CACHE_FILE))
17
+ # Get summary data for dashboard cards
18
+ @audit_summary = audit_service.summary
19
+ @todo_summary = todo_service.summary
42
20
 
43
- # Write the cache file
44
- File.write(AUDIT_CACHE_FILE, JSON.generate(cache_data))
45
-
46
- audit_output
21
+ # TODO: Refactor these to use services as well
22
+ @rubocop_output = "JSON.parse(`rubocop --format json`)"
23
+ @coverage = "100"
47
24
  end
48
25
 
49
- def fetch_todo_items
50
- # Check if cache file exists and is recent enough
51
- if File.exist?(TODO_CACHE_FILE)
52
- cached_data = JSON.parse(File.read(TODO_CACHE_FILE))
53
- last_run_time = Time.parse(cached_data["timestamp"])
54
-
55
- # Use cached data if it's less than AUDIT_CACHE_HOURS old
56
- if Time.now - last_run_time < AUDIT_CACHE_HOURS.hours
57
- return cached_data["output"]
58
- end
59
- end
60
-
61
- todos = []
62
- # Updated grep pattern to match only all-uppercase or all-lowercase variants
63
- raw_output = `grep -r -n -E "(TODO|FIXME|HACK|todo|fixme|hack)" app lib`.split("\n")
64
-
65
- raw_output.each do |line|
66
- if line =~ /^(.+):(\d+):(.+)$/
67
- file = $1
68
- line_num = $2
69
- content = $3
70
-
71
- # Match only exact lowercase or uppercase variants
72
- type_match = content.match(/(TODO|FIXME|HACK|todo|fixme|hack)/)
73
- if type_match
74
- # Convert to uppercase for consistency
75
- type = type_match.to_s.upcase
76
-
77
- todos << {
78
- file: file,
79
- line: line_num.to_i,
80
- content: content.strip,
81
- type: type
82
- }
83
- end
84
- end
85
- end
86
-
87
- # Save to cache file with timestamp
88
- cache_data = {
89
- "output" => todos,
90
- "timestamp" => Time.now.iso8601
26
+ # Force refresh all dashboard data
27
+ def refresh
28
+ # Create services
29
+ audit_service = AuditService.new
30
+ todo_service = TodoService.new
31
+ log_monitor_service = LogSizeMonitorService.new
32
+
33
+ # Force refresh of data
34
+ audit_output = audit_service.fetch(true) # Force refresh
35
+ todo_items = todo_service.fetch(true) # Force refresh
36
+ log_data = log_monitor_service.collect_data
37
+
38
+ # Get updated summaries
39
+ audit_summary = audit_service.summary
40
+ todo_summary = todo_service.summary
41
+
42
+ # Get current time for last updated display
43
+ last_updated = Time.now.strftime("%B %d, %Y at %H:%M")
44
+
45
+ # Return JSON response with refreshed data
46
+ render json: {
47
+ audit_output: audit_output,
48
+ todo_items: todo_items,
49
+ audit_summary: audit_summary,
50
+ todo_summary: todo_summary,
51
+ log_data: log_data,
52
+ last_updated: last_updated,
53
+ status: "success"
91
54
  }
92
-
93
- # Ensure the tmp directory exists
94
- FileUtils.mkdir_p(File.dirname(TODO_CACHE_FILE))
95
-
96
- # Write the cache file
97
- File.write(TODO_CACHE_FILE, JSON.generate(cache_data))
98
-
99
- todos
55
+ rescue StandardError => e
56
+ # Return error information
57
+ render json: {
58
+ status: "error",
59
+ message: "Failed to refresh data: #{e.message}"
60
+ }, status: :internal_server_error
100
61
  end
101
-
102
- def calculate_todo_stats(todos)
103
- return {} if todos.nil? || todos.empty?
104
-
105
- stats = {
106
- total: todos.count,
107
- by_type: {
108
- "TODO" => todos.count { |t| t[:type] == "TODO" },
109
- "FIXME" => todos.count { |t| t[:type] == "FIXME" },
110
- "HACK" => todos.count { |t| t[:type] == "HACK" }
111
- },
112
- by_file: {}
113
- }
114
-
115
- # Group by file path
116
- todos.each do |todo|
117
- file_path = todo[:file]
118
- stats[:by_file][file_path] ||= 0
119
- stats[:by_file][file_path] += 1
62
+
63
+ def truncate_log
64
+ log_monitor_service = LogSizeMonitorService.new
65
+ filename = params[:filename]
66
+
67
+ # Add .log extension if not included in the filename
68
+ if filename.present? && !filename.end_with?('.log')
69
+ filename = "#{filename}.log"
120
70
  end
121
-
122
- # Find files with most TODOs (top 5)
123
- stats[:hotspots] = stats[:by_file].sort_by { |_, count| -count }
124
- .first(5)
125
- .to_h
126
-
127
- stats
128
- end
129
-
130
- def read_coverage_percent
131
- file = Rails.root.join("coverage", ".last_run.json")
132
- return 0 unless File.exist?(file)
133
-
134
- data = JSON.parse(File.read(file))
135
- data.dig("result", "covered_percent").to_f.round(2)
71
+
72
+ result = log_monitor_service.truncate_log(filename)
73
+
74
+ render json: result
136
75
  end
137
76
  end
138
77
  end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solidstats
4
+ # Service to collect and process security audit data
5
+ class AuditService < DataCollectorService
6
+ # Initialize with default cache settings for audits
7
+ def initialize
8
+ super(Rails.root.join("tmp", "solidstats_audit.json"))
9
+ end
10
+
11
+ # Generate a summary for the dashboard display
12
+ # @return [Hash] Summary information with status, count, and message
13
+ def summary
14
+ data = fetch
15
+ vuln_count = data["vulnerabilities"]&.count || 0
16
+
17
+ {
18
+ count: vuln_count,
19
+ status: determine_status(vuln_count),
20
+ message: generate_message(vuln_count)
21
+ }
22
+ end
23
+
24
+ private
25
+
26
+ # Determine the status indicator based on vulnerability count
27
+ # @param count [Integer] Number of vulnerabilities
28
+ # @return [String] Status indicator (success, warning, or danger)
29
+ def determine_status(count)
30
+ case count
31
+ when 0 then "success"
32
+ when 1..2 then "warning"
33
+ else "danger"
34
+ end
35
+ end
36
+
37
+ # Generate a status message based on vulnerability count
38
+ # @param count [Integer] Number of vulnerabilities
39
+ # @return [String] Human-readable status message
40
+ def generate_message(count)
41
+ case count
42
+ when 0 then "No vulnerabilities found"
43
+ when 1 then "1 vulnerability found"
44
+ else "#{count} vulnerabilities found"
45
+ end
46
+ end
47
+
48
+ # Collect fresh audit data by running bundle-audit
49
+ # @return [Hash] Parsed audit data
50
+ def collect_data
51
+ raw_output = `bundle audit check --update --format json`
52
+ json_part = raw_output[/\{.*\}/m] # extract JSON starting from first '{'
53
+ JSON.parse(json_part) rescue { "error" => "Failed to parse JSON", "vulnerabilities" => [] }
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solidstats
4
+ # Base service class for all data collectors
5
+ # This class handles caching logic and provides a common interface
6
+ # for all data collection services in SolidStats
7
+ class DataCollectorService
8
+ attr_reader :cache_file, :cache_duration
9
+
10
+ # Initialize the data collector with cache configuration
11
+ # @param cache_file [String] Path to the cache file
12
+ # @param cache_duration [ActiveSupport::Duration] Duration to keep the cache valid
13
+ def initialize(cache_file, cache_duration = 12.hours)
14
+ @cache_file = cache_file
15
+ @cache_duration = cache_duration
16
+ end
17
+
18
+ # Fetch data with caching
19
+ # Returns cached data if available and not expired,
20
+ # otherwise collects fresh data
21
+ # @param force_refresh [Boolean] Whether to force a refresh regardless of cache state
22
+ # @return [Hash] The collected data
23
+ def fetch(force_refresh = false)
24
+ return cached_data if fresh_cache? && !force_refresh
25
+
26
+ data = collect_data
27
+ save_to_cache(data)
28
+ data
29
+ end
30
+
31
+ # Get a summary of the data for dashboard display
32
+ # @return [Hash] Summary information
33
+ def summary
34
+ raise NotImplementedError, "Subclasses must implement summary"
35
+ end
36
+
37
+ private
38
+
39
+ # Get data from the cache file
40
+ # @return [Hash, nil] Cached data or nil if cache invalid
41
+ def cached_data
42
+ JSON.parse(File.read(cache_file))["output"]
43
+ rescue StandardError => e
44
+ Rails.logger.error "Error reading cache: #{e.message}"
45
+ nil
46
+ end
47
+
48
+ # Check if the cache is fresh (exists and not expired)
49
+ # @return [Boolean] True if cache is valid and not expired
50
+ def fresh_cache?
51
+ return false unless File.exist?(cache_file)
52
+
53
+ begin
54
+ cached_data = JSON.parse(File.read(cache_file))
55
+ last_run_time = Time.parse(cached_data["timestamp"])
56
+ Time.now - last_run_time < cache_duration
57
+ rescue StandardError => e
58
+ Rails.logger.error "Error checking cache freshness: #{e.message}"
59
+ false
60
+ end
61
+ end
62
+
63
+ # Save data to the cache file
64
+ # @param data [Hash] Data to save to cache
65
+ def save_to_cache(data)
66
+ cache_data = {
67
+ "output" => data,
68
+ "timestamp" => Time.now.iso8601
69
+ }
70
+
71
+ FileUtils.mkdir_p(File.dirname(cache_file))
72
+ File.write(cache_file, JSON.generate(cache_data))
73
+ rescue StandardError => e
74
+ Rails.logger.error "Error saving to cache: #{e.message}"
75
+ end
76
+
77
+ # Collect fresh data - to be implemented by subclasses
78
+ # @return [Hash] The collected data
79
+ def collect_data
80
+ raise NotImplementedError, "Subclasses must implement collect_data"
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,94 @@
1
+ module Solidstats
2
+ class LogSizeMonitorService
3
+ WARNING_THRESHOLD = 25 # In megabytes
4
+ DANGER_THRESHOLD = 50 # In megabytes
5
+
6
+ def collect_data
7
+ log_files = scan_log_directory
8
+ total_size_bytes = log_files.sum { |file| file[:size_bytes] }
9
+
10
+ # Create aggregated data
11
+ {
12
+ log_dir_path: log_directory,
13
+ total_size_bytes: total_size_bytes,
14
+ total_size_mb: bytes_to_mb(total_size_bytes),
15
+ status: calculate_status(total_size_bytes),
16
+ logs_count: log_files.size,
17
+ log_files: log_files,
18
+ created_at: Time.now.iso8601
19
+ }
20
+ end
21
+
22
+ def truncate_log(filename = nil)
23
+ begin
24
+ if filename.present?
25
+ # Truncate specific log file
26
+ # Ensure filename has .log extension
27
+ filename = "#{filename}.log" unless filename.end_with?('.log')
28
+
29
+ file_path = File.join(log_directory, filename)
30
+ if File.exist?(file_path)
31
+ File.open(file_path, 'w') { |f| f.truncate(0) }
32
+ { success: true, message: "Log file '#{filename}' truncated successfully" }
33
+ else
34
+ { success: false, message: "Log file '#{filename}' not found" }
35
+ end
36
+ else
37
+ # Truncate all log files
38
+ scan_log_directory.each do |log_file|
39
+ File.open(log_file[:path], 'w') { |f| f.truncate(0) }
40
+ end
41
+ { success: true, message: "All log files truncated successfully" }
42
+ end
43
+ rescue => e
44
+ { success: false, message: "Failed to truncate log file: #{e.message}" }
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def log_directory
51
+ Rails.root.join("log").to_s
52
+ end
53
+
54
+ def scan_log_directory
55
+ log_files = []
56
+
57
+ # Get all files in the log directory
58
+ Dir.glob(File.join(log_directory, "*.log")).each do |file_path|
59
+ if File.file?(file_path)
60
+ size_bytes = File.size(file_path)
61
+ filename = File.basename(file_path)
62
+
63
+ log_files << {
64
+ filename: filename,
65
+ path: file_path,
66
+ size_bytes: size_bytes,
67
+ size_mb: bytes_to_mb(size_bytes),
68
+ status: calculate_status(size_bytes),
69
+ last_modified: File.mtime(file_path)
70
+ }
71
+ end
72
+ end
73
+
74
+ # Sort by size (largest first)
75
+ log_files.sort_by { |file| -file[:size_bytes] }
76
+ end
77
+
78
+ def bytes_to_mb(bytes)
79
+ (bytes.to_f / (1024 * 1024)).round(2)
80
+ end
81
+
82
+ def calculate_status(size_bytes)
83
+ size_mb = bytes_to_mb(size_bytes)
84
+
85
+ if size_mb >= DANGER_THRESHOLD
86
+ :danger
87
+ elsif size_mb >= WARNING_THRESHOLD
88
+ :warning
89
+ else
90
+ :ok
91
+ end
92
+ end
93
+ end
94
+ end