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 +4 -4
- data/CHANGELOG.md +78 -5
- data/README.md +54 -5
- data/app/controllers/solidstats/dashboard_controller.rb +60 -121
- data/app/services/solidstats/audit_service.rb +56 -0
- data/app/services/solidstats/data_collector_service.rb +83 -0
- data/app/services/solidstats/log_size_monitor_service.rb +94 -0
- data/app/services/solidstats/todo_service.rb +114 -0
- data/app/views/solidstats/dashboard/_log_monitor.html.erb +759 -0
- data/app/views/solidstats/dashboard/_todos.html.erb +6 -27
- data/app/views/solidstats/dashboard/audit/_additional_styles.css +22 -0
- data/app/views/solidstats/dashboard/audit/_audit_details.html.erb +47 -58
- data/app/views/solidstats/dashboard/audit/_audit_summary.html.erb +1 -1
- data/app/views/solidstats/dashboard/audit/_security_audit.html.erb +0 -23
- data/app/views/solidstats/dashboard/audit/_vulnerabilities_table.html.erb +1092 -38
- data/app/views/solidstats/dashboard/audit/_vulnerability_details.html.erb +4 -3
- data/app/views/solidstats/dashboard/index.html.erb +1166 -162
- data/config/routes.rb +5 -0
- data/lib/solidstats/version.rb +1 -1
- data/lib/tasks/solidstats_release.rake +69 -0
- metadata +12 -5
- data/app/views/solidstats/dashboard/audit/_audit_filters.html.erb +0 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b1e51da79f65a50793bc1bfc777ed19df5d81795739ceda8558d3261745b53ae
|
4
|
+
data.tar.gz: e58f0018fd96135789f192372b2b85f7165cf51a6921fa7e2a56057bebdb14b5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
32
|
-
|
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
|
-
-
|
6
|
-
-
|
7
|
-
-
|
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
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
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
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
-
#
|
41
|
-
|
17
|
+
# Get summary data for dashboard cards
|
18
|
+
@audit_summary = audit_service.summary
|
19
|
+
@todo_summary = todo_service.summary
|
42
20
|
|
43
|
-
#
|
44
|
-
|
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
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
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
|
-
#
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
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
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
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
|
-
|
123
|
-
|
124
|
-
|
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
|