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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +33 -0
  3. data/app/assets/javascripts/solidstats/application.js +257 -0
  4. data/app/assets/javascripts/solidstats/dashboard.js +179 -0
  5. data/app/assets/stylesheets/solidstats/application.css +6 -1
  6. data/app/controllers/solidstats/dashboard_controller.rb +28 -35
  7. data/app/controllers/solidstats/gem_metadata_controller.rb +12 -0
  8. data/app/controllers/solidstats/logs_controller.rb +12 -12
  9. data/app/controllers/solidstats/performance_controller.rb +2 -2
  10. data/app/controllers/solidstats/productivity_controller.rb +10 -10
  11. data/app/controllers/solidstats/quality_controller.rb +32 -32
  12. data/app/controllers/solidstats/securities_controller.rb +7 -7
  13. data/app/helpers/solidstats/application_helper.rb +10 -10
  14. data/app/helpers/solidstats/performance_helper.rb +32 -32
  15. data/app/helpers/solidstats/productivity_helper.rb +20 -20
  16. data/app/services/solidstats/bundler_audit_service.rb +13 -13
  17. data/app/services/solidstats/coverage_compass_service.rb +59 -59
  18. data/app/services/solidstats/load_lens_service.rb +90 -70
  19. data/app/services/solidstats/log_size_monitor_service.rb +59 -59
  20. data/app/services/solidstats/my_todo_service.rb +68 -68
  21. data/app/services/solidstats/style_patrol_service.rb +44 -44
  22. data/app/views/layouts/solidstats/application.html.erb +1 -1
  23. data/app/views/solidstats/shared/_quick_actions.html.erb +1 -1
  24. data/config/routes.rb +4 -4
  25. data/lib/generators/solidstats/clean/clean_generator.rb +24 -0
  26. data/lib/generators/solidstats/clean/templates/README +8 -0
  27. data/lib/generators/solidstats/install/install_generator.rb +32 -17
  28. data/lib/generators/solidstats/templates/initializer.rb +112 -0
  29. data/lib/solidstats/asset_compatibility.rb +238 -0
  30. data/lib/solidstats/asset_manifest.rb +205 -0
  31. data/lib/solidstats/engine.rb +49 -9
  32. data/lib/solidstats/version.rb +1 -1
  33. data/lib/solidstats.rb +24 -11
  34. data/lib/tasks/solidstats.rake +67 -0
  35. data/lib/tasks/solidstats_performance.rake +6 -29
  36. data/lib/tasks/solidstats_tasks.rake +16 -0
  37. metadata +14 -5
  38. data/lib/tasks/solidstats_install.rake +0 -13
@@ -4,9 +4,9 @@ module Solidstats
4
4
  # LoadLens - Development Performance Monitoring Service
5
5
  # Parses Rails development logs to extract performance metrics
6
6
  class LoadLensService
7
- DATA_DIR = Rails.root.join('solidstats')
8
- LOG_FILE = Rails.root.join('log', 'development.log')
9
- POSITION_FILE = DATA_DIR.join('last_position.txt')
7
+ DATA_DIR = Rails.root.join("solidstats")
8
+ LOG_FILE = Rails.root.join("log", "development.log")
9
+ POSITION_FILE = DATA_DIR.join("last_position.txt")
10
10
  CACHE_FILE = "loadlens.json"
11
11
  SUMMARY_FILE = "summary.json"
12
12
  RETENTION_DAYS = 7
@@ -22,7 +22,7 @@ module Solidstats
22
22
 
23
23
  def self.get_performance_data
24
24
  cache_file_path = solidstats_cache_path(CACHE_FILE)
25
-
25
+
26
26
  if File.exist?(cache_file_path) && cache_fresh?(cache_file_path, 15.minutes)
27
27
  raw_data = JSON.parse(File.read(cache_file_path))
28
28
  deep_indifferent_access(raw_data)
@@ -36,13 +36,13 @@ module Solidstats
36
36
 
37
37
  def self.scan_and_cache
38
38
  performance_data = scan_development_log
39
-
39
+
40
40
  # Cache the performance data
41
41
  cache_performance_data(performance_data)
42
-
42
+
43
43
  # Update summary.json with performance monitoring card
44
44
  update_summary_json(performance_data)
45
-
45
+
46
46
  deep_indifferent_access(performance_data)
47
47
  end
48
48
 
@@ -64,10 +64,10 @@ module Solidstats
64
64
  last_position = read_last_position
65
65
  processed_count = 0
66
66
  current_requests = []
67
-
68
- File.open(LOG_FILE, 'r') do |file|
67
+
68
+ File.open(LOG_FILE, "r") do |file|
69
69
  file.seek(last_position)
70
-
70
+
71
71
  file.each_line do |line|
72
72
  process_line(line.strip, current_requests)
73
73
  update_last_position(file.pos)
@@ -81,7 +81,7 @@ module Solidstats
81
81
  processed_count += 1
82
82
  end
83
83
  end
84
-
84
+
85
85
  Rails.logger.info("DevLogParser: Processed #{processed_count} requests")
86
86
  { success: true, processed: processed_count }
87
87
  rescue => e
@@ -105,23 +105,28 @@ module Solidstats
105
105
  end
106
106
 
107
107
  def self.scan_development_log
108
- # Load recent performance data for dashboard
108
+ # First, ensure the log parser runs to create perf files if they don't exist
109
+ parse_recent_log_entries_if_needed
110
+
111
+ # Now, load the performance data from the perf files
109
112
  data_files = []
110
-
113
+ found_perf_files = []
114
+
111
115
  # Get last 7 days of data files
112
116
  7.times do |i|
113
117
  date = i.days.ago.to_date
114
118
  file_path = DATA_DIR.join("perf_#{date.strftime('%Y-%m-%d')}.json")
115
-
119
+
116
120
  if File.exist?(file_path)
117
121
  file_data = JSON.parse(File.read(file_path))
118
122
  data_files.concat(file_data)
123
+ found_perf_files << file_path
119
124
  end
120
125
  end
121
126
 
122
127
  # Fallback: If no data files exist, parse recent entries from development.log
123
128
  if data_files.empty? && File.exist?(LOG_FILE) && Rails.env.development?
124
- Rails.logger.info("DevLogParser: No data files found, parsing recent development log entries")
129
+ Rails.logger.info("LoadLensService: No perf data files found, parsing recent log entries as a fallback.")
125
130
  data_files = parse_recent_log_entries
126
131
  end
127
132
 
@@ -129,58 +134,73 @@ module Solidstats
129
134
  calculate_performance_metrics(data_files)
130
135
  end
131
136
 
137
+ def self.parse_recent_log_entries_if_needed
138
+ # Check if any perf files exist from the last 7 days
139
+ has_recent_perf_files = 7.times.any? do |i|
140
+ date = i.days.ago.to_date
141
+ file_path = DATA_DIR.join("perf_#{date.strftime('%Y-%m-%d')}.json")
142
+ File.exist?(file_path)
143
+ end
144
+
145
+ # If no recent perf files, run the parser
146
+ unless has_recent_perf_files
147
+ Rails.logger.info("LoadLensService: No recent perf files found. Parsing development log to generate initial data.")
148
+ parse_recent_log_entries
149
+ end
150
+ end
151
+
132
152
  def self.parse_recent_log_entries
133
153
  # Parse recent entries from development.log as fallback when no data files exist
134
154
  recent_requests = []
135
155
  current_requests = []
136
156
  lines_processed = 0
137
157
  max_lines = 2000 # Process last 2000 lines for initial bootstrap
138
-
158
+
139
159
  begin
140
160
  # Get the last N lines from the log file efficiently
141
161
  log_lines = []
142
- File.open(LOG_FILE, 'r') do |file|
162
+ File.open(LOG_FILE, "r") do |file|
143
163
  file.each_line { |line| log_lines << line.strip }
144
164
  end
145
-
165
+
146
166
  # Take the last max_lines entries
147
167
  log_lines = log_lines.last(max_lines) if log_lines.size > max_lines
148
-
168
+
149
169
  # Process each line using existing parsing logic
150
170
  log_lines.each do |line|
151
171
  process_line(line, current_requests)
152
172
  lines_processed += 1
153
-
173
+
154
174
  # Collect completed requests
155
175
  current_requests.each do |req|
156
176
  if req[:completed]
157
177
  # Convert to the same format as saved requests
158
178
  clean_request = {
159
- 'controller' => req[:controller],
160
- 'action' => req[:action],
161
- 'http_method' => req[:http_method],
162
- 'path' => req[:path],
163
- 'status' => req[:status],
164
- 'total_time_ms' => req[:total_time_ms] || 0.0,
165
- 'view_time_ms' => req[:view_time_ms] || 0.0,
166
- 'activerecord_time_ms' => req[:activerecord_time_ms] || 0.0,
167
- 'timestamp' => req[:timestamp]
179
+ "controller" => req[:controller],
180
+ "action" => req[:action],
181
+ "http_method" => req[:http_method],
182
+ "path" => req[:path],
183
+ "status" => req[:status],
184
+ "total_time_ms" => req[:total_time_ms] || 0.0,
185
+ "view_time_ms" => req[:view_time_ms] || 0.0,
186
+ "activerecord_time_ms" => req[:activerecord_time_ms] || 0.0,
187
+ "timestamp" => req[:timestamp]
168
188
  }
169
-
189
+
170
190
  # Only include complete requests with controller/action
171
- if clean_request['controller'] && clean_request['action']
191
+ if clean_request["controller"] && clean_request["action"]
172
192
  recent_requests << clean_request
173
193
  end
174
194
  end
175
195
  end
176
-
196
+
177
197
  # Remove completed requests from processing queue
178
198
  current_requests.reject! { |req| req[:completed] }
179
199
  end
180
-
200
+
181
201
  Rails.logger.info("DevLogParser: Parsed #{recent_requests.size} recent requests from #{lines_processed} log lines")
182
202
  recent_requests
183
-
203
+
184
204
  rescue => e
185
205
  Rails.logger.error("DevLogParser: Failed to parse recent log entries: #{e.message}")
186
206
  []
@@ -191,11 +211,11 @@ module Solidstats
191
211
  return default_metrics if requests.empty?
192
212
 
193
213
  total_requests = requests.size
194
- avg_response_time = (requests.sum { |req| req['total_time_ms'] || 0 } / total_requests).round(2)
195
- avg_view_time = (requests.sum { |req| req['view_time_ms'] || 0 } / total_requests).round(2)
196
- avg_db_time = (requests.sum { |req| req['activerecord_time_ms'] || 0 } / total_requests).round(2)
197
- slow_requests = requests.count { |req| (req['total_time_ms'] || 0) > 1000 }
198
- error_rate = ((requests.count { |req| (req['status'] || 200) >= 400 }.to_f / total_requests) * 100).round(2)
214
+ avg_response_time = (requests.sum { |req| req["total_time_ms"] || 0 } / total_requests).round(2)
215
+ avg_view_time = (requests.sum { |req| req["view_time_ms"] || 0 } / total_requests).round(2)
216
+ avg_db_time = (requests.sum { |req| req["activerecord_time_ms"] || 0 } / total_requests).round(2)
217
+ slow_requests = requests.count { |req| (req["total_time_ms"] || 0) > 1000 }
218
+ error_rate = ((requests.count { |req| (req["status"] || 200) >= 400 }.to_f / total_requests) * 100).round(2)
199
219
 
200
220
  status = determine_status(avg_response_time, error_rate, slow_requests, total_requests)
201
221
 
@@ -210,7 +230,7 @@ module Solidstats
210
230
  status: status,
211
231
  last_updated: Time.current.iso8601
212
232
  },
213
- recent_requests: requests.sort_by { |req| req['timestamp'] }.reverse.first(20),
233
+ recent_requests: requests.sort_by { |req| req["timestamp"] }.reverse.first(20),
214
234
  last_updated: Time.current.iso8601
215
235
  }
216
236
  end
@@ -224,7 +244,7 @@ module Solidstats
224
244
  avg_db_time: 0,
225
245
  slow_requests: 0,
226
246
  error_rate: 0,
227
- status: 'info',
247
+ status: "info",
228
248
  last_updated: Time.current.iso8601
229
249
  },
230
250
  recent_requests: [],
@@ -233,9 +253,9 @@ module Solidstats
233
253
  end
234
254
 
235
255
  def self.determine_status(avg_response_time, error_rate, slow_requests, total_requests)
236
- return 'error' if error_rate > 10
237
- return 'warning' if avg_response_time > 1000 || slow_requests > (total_requests * 0.1)
238
- return 'success'
256
+ return "error" if error_rate > 10
257
+ return "warning" if avg_response_time > 1000 || slow_requests > (total_requests * 0.1)
258
+ "success"
239
259
  end
240
260
 
241
261
  def self.process_line(line, current_requests)
@@ -248,7 +268,7 @@ module Solidstats
248
268
  timestamp: parse_timestamp(timestamp),
249
269
  started_at: Time.current
250
270
  }
251
-
271
+
252
272
  # Controller and action info
253
273
  elsif match = line.match(CONTROLLER_ACTION_REGEX)
254
274
  controller, action = match.captures
@@ -256,7 +276,7 @@ module Solidstats
256
276
  current_request[:controller] = controller
257
277
  current_request[:action] = action
258
278
  end
259
-
279
+
260
280
  # Request completion with timing
261
281
  elsif match = line.match(COMPLETED_REGEX)
262
282
  status, total_time, timing_details = match.captures
@@ -264,26 +284,26 @@ module Solidstats
264
284
  current_request[:status] = status.to_i
265
285
  current_request[:total_time_ms] = total_time.to_f
266
286
  current_request[:completed] = true
267
-
287
+
268
288
  # Extract view and ActiveRecord times from timing details if present
269
289
  if timing_details
270
290
  if view_match = timing_details.match(VIEW_RENDERING_REGEX)
271
291
  current_request[:view_time_ms] = view_match[1].to_f
272
292
  end
273
-
293
+
274
294
  if ar_match = timing_details.match(ACTIVERECORD_REGEX)
275
295
  current_request[:activerecord_time_ms] = ar_match[1].to_f
276
296
  end
277
297
  end
278
298
  end
279
-
299
+
280
300
  # View rendering time
281
301
  elsif match = line.match(VIEW_RENDERING_REGEX)
282
302
  view_time = match.captures.first
283
303
  if current_request = current_requests.last
284
304
  current_request[:view_time_ms] = view_time.to_f
285
305
  end
286
-
306
+
287
307
  # ActiveRecord time
288
308
  elsif match = line.match(ACTIVERECORD_REGEX)
289
309
  ar_time = match.captures.first
@@ -319,7 +339,7 @@ module Solidstats
319
339
  return unless clean_request[:controller] && clean_request[:action]
320
340
 
321
341
  perf_file = current_perf_file
322
-
342
+
323
343
  # Read existing data or create new array
324
344
  existing_data = if File.exist?(perf_file)
325
345
  JSON.parse(File.read(perf_file))
@@ -335,7 +355,7 @@ module Solidstats
335
355
  end
336
356
 
337
357
  def self.current_perf_file
338
- date_suffix = Date.current.strftime('%Y-%m-%d')
358
+ date_suffix = Date.current.strftime("%Y-%m-%d")
339
359
  DATA_DIR.join("perf_#{date_suffix}.json")
340
360
  end
341
361
 
@@ -345,8 +365,8 @@ module Solidstats
345
365
 
346
366
  def self.cleanup_old_files
347
367
  cutoff_date = RETENTION_DAYS.days.ago.to_date
348
-
349
- Dir.glob(DATA_DIR.join('perf_*.json')).each do |file|
368
+
369
+ Dir.glob(DATA_DIR.join("perf_*.json")).each do |file|
350
370
  if match = File.basename(file).match(/perf_(\d{4}-\d{2}-\d{2})\.json/)
351
371
  file_date = Date.parse(match[1])
352
372
  File.delete(file) if file_date < cutoff_date
@@ -373,7 +393,7 @@ module Solidstats
373
393
  def self.cache_performance_data(data)
374
394
  cache_file_path = solidstats_cache_path(CACHE_FILE)
375
395
  ensure_solidstats_directory
376
-
396
+
377
397
  File.write(cache_file_path, JSON.pretty_generate(data))
378
398
  rescue => e
379
399
  Rails.logger.error("Failed to cache performance data: #{e.message}")
@@ -381,33 +401,33 @@ module Solidstats
381
401
 
382
402
  def self.update_summary_json(performance_data)
383
403
  summary_file_path = solidstats_cache_path(SUMMARY_FILE)
384
-
404
+
385
405
  # Read existing summary or create new one
386
406
  begin
387
407
  existing_summary = File.exist?(summary_file_path) ? JSON.parse(File.read(summary_file_path)) : {}
388
408
  rescue JSON::ParserError
389
409
  existing_summary = {}
390
410
  end
391
-
411
+
392
412
  summary = performance_data[:summary]
393
-
413
+
394
414
  # Create badges based on performance metrics
395
415
  badges = []
396
416
  badges << { "text" => "#{summary[:total_requests]} Requests", "color" => "info" }
397
-
417
+
398
418
  case summary[:status]
399
- when 'error'
419
+ when "error"
400
420
  badges << { "text" => "High Errors", "color" => "error" }
401
- when 'warning'
421
+ when "warning"
402
422
  badges << { "text" => "Slow Responses", "color" => "warning" }
403
423
  else
404
424
  badges << { "text" => "Healthy", "color" => "success" }
405
425
  end
406
-
426
+
407
427
  if summary[:avg_response_time] > 0
408
428
  badges << { "text" => "#{summary[:avg_response_time]}ms avg", "color" => "neutral" }
409
429
  end
410
-
430
+
411
431
  # Update the LoadLens monitoring entry
412
432
  existing_summary["LoadLens"] = {
413
433
  "icon" => "activity",
@@ -417,7 +437,7 @@ module Solidstats
417
437
  "url" => "/solidstats/performance/load_lens",
418
438
  "badges" => badges
419
439
  }
420
-
440
+
421
441
  # Write updated summary
422
442
  File.write(summary_file_path, JSON.pretty_generate(existing_summary))
423
443
  rescue => e
@@ -427,9 +447,9 @@ module Solidstats
427
447
  def self.generate_performance_message(summary)
428
448
  if summary[:total_requests] == 0
429
449
  "No requests tracked"
430
- elsif summary[:status] == 'error'
450
+ elsif summary[:status] == "error"
431
451
  "#{summary[:error_rate]}% error rate"
432
- elsif summary[:status] == 'warning'
452
+ elsif summary[:status] == "warning"
433
453
  "#{summary[:avg_response_time]}ms avg"
434
454
  else
435
455
  "#{summary[:avg_response_time]}ms avg"
@@ -441,13 +461,13 @@ module Solidstats
441
461
  rescue
442
462
  false
443
463
  end
444
-
464
+
445
465
  def self.solidstats_cache_path(filename)
446
- Rails.root.join('solidstats', filename)
466
+ Rails.root.join("solidstats", filename)
447
467
  end
448
-
468
+
449
469
  def self.ensure_solidstats_directory
450
- dir_path = File.dirname(solidstats_cache_path('dummy'))
470
+ dir_path = File.dirname(solidstats_cache_path("dummy"))
451
471
  FileUtils.mkdir_p(dir_path) unless Dir.exist?(dir_path)
452
472
  end
453
473
  end
@@ -2,14 +2,14 @@ module Solidstats
2
2
  class LogSizeMonitorService
3
3
  CACHE_FILE = "logs.json"
4
4
  SUMMARY_FILE = "summary.json"
5
-
5
+
6
6
  # Size thresholds in bytes
7
7
  WARNING_SIZE = 500.megabytes
8
8
  ERROR_SIZE = 1.gigabyte
9
-
9
+
10
10
  def self.get_logs_data
11
11
  cache_file_path = solidstats_cache_path(CACHE_FILE)
12
-
12
+
13
13
  if File.exist?(cache_file_path) && cache_fresh?(cache_file_path)
14
14
  JSON.parse(File.read(cache_file_path))
15
15
  else
@@ -19,40 +19,40 @@ module Solidstats
19
19
  Rails.logger.error("Error reading logs cache, regenerating...")
20
20
  scan_and_cache
21
21
  end
22
-
22
+
23
23
  def self.scan_and_cache
24
24
  logs_data = scan_log_files
25
-
25
+
26
26
  # Cache the logs data
27
27
  cache_logs_data(logs_data)
28
-
28
+
29
29
  # Update summary.json with log monitoring card
30
30
  update_summary_json(logs_data)
31
-
31
+
32
32
  logs_data
33
33
  end
34
-
34
+
35
35
  def self.truncate_log(filename)
36
36
  return { status: "error", message: "Filename required" } if filename.blank?
37
-
37
+
38
38
  log_file_path = find_log_file(filename)
39
39
  return { status: "error", message: "Log file not found" } unless log_file_path
40
-
40
+
41
41
  begin
42
42
  # Check if file is writable
43
43
  unless File.writable?(log_file_path)
44
44
  return { status: "error", message: "File is not writable" }
45
45
  end
46
-
46
+
47
47
  # Get original size for reporting
48
48
  original_size = File.size(log_file_path)
49
-
49
+
50
50
  # Truncate the file
51
51
  File.truncate(log_file_path, 0)
52
-
52
+
53
53
  # Update cache after truncation
54
54
  scan_and_cache
55
-
55
+
56
56
  {
57
57
  status: "success",
58
58
  message: "Log file truncated successfully",
@@ -64,21 +64,21 @@ module Solidstats
64
64
  { status: "error", message: "Failed to truncate: #{e.message}" }
65
65
  end
66
66
  end
67
-
67
+
68
68
  private
69
-
69
+
70
70
  def self.scan_log_files
71
71
  log_files = discover_log_files
72
72
  total_size = 0
73
73
  files_data = []
74
-
74
+
75
75
  log_files.each do |file_path|
76
76
  next unless File.exist?(file_path) && File.readable?(file_path)
77
-
77
+
78
78
  file_stat = File.stat(file_path)
79
79
  file_size = file_stat.size
80
80
  total_size += file_size
81
-
81
+
82
82
  files_data << {
83
83
  name: File.basename(file_path),
84
84
  path: file_path,
@@ -89,10 +89,10 @@ module Solidstats
89
89
  can_truncate: File.writable?(file_path)
90
90
  }
91
91
  end
92
-
92
+
93
93
  # Sort by size descending
94
94
  files_data.sort_by! { |f| -f[:size_bytes] }
95
-
95
+
96
96
  {
97
97
  summary: {
98
98
  total_files: files_data.length,
@@ -106,91 +106,91 @@ module Solidstats
106
106
  files: files_data
107
107
  }
108
108
  end
109
-
109
+
110
110
  def self.discover_log_files
111
111
  log_paths = []
112
-
112
+
113
113
  # Rails log directory
114
114
  if defined?(Rails) && Rails.root
115
- rails_log_dir = Rails.root.join('log')
116
- log_paths += Dir.glob(rails_log_dir.join('*.log')) if Dir.exist?(rails_log_dir)
115
+ rails_log_dir = Rails.root.join("log")
116
+ log_paths += Dir.glob(rails_log_dir.join("*.log")) if Dir.exist?(rails_log_dir)
117
117
  end
118
-
118
+
119
119
  # Remove duplicates and filter readable files
120
120
  log_paths.uniq.select { |path| File.readable?(path) }
121
121
  end
122
-
122
+
123
123
  def self.find_log_file(filename)
124
124
  discovered_files = discover_log_files
125
125
  discovered_files.find { |path| File.basename(path) == filename }
126
126
  end
127
-
127
+
128
128
  def self.determine_file_status(size_bytes)
129
129
  if size_bytes >= ERROR_SIZE
130
- 'error'
130
+ "error"
131
131
  elsif size_bytes >= WARNING_SIZE
132
- 'warning'
132
+ "warning"
133
133
  else
134
- 'success'
134
+ "success"
135
135
  end
136
136
  end
137
-
137
+
138
138
  def self.determine_overall_status(total_size, files_data)
139
- error_files = files_data.count { |f| f[:status] == 'error' }
140
- warning_files = files_data.count { |f| f[:status] == 'warning' }
141
-
139
+ error_files = files_data.count { |f| f[:status] == "error" }
140
+ warning_files = files_data.count { |f| f[:status] == "warning" }
141
+
142
142
  if error_files > 0 || total_size >= ERROR_SIZE * 2
143
- 'error'
143
+ "error"
144
144
  elsif warning_files > 0 || total_size >= WARNING_SIZE * 3
145
- 'warning'
145
+ "warning"
146
146
  else
147
- 'success'
147
+ "success"
148
148
  end
149
149
  end
150
-
150
+
151
151
  def self.format_file_size(size_bytes)
152
152
  return "0 B" if size_bytes.zero?
153
-
154
- units = ['B', 'KB', 'MB', 'GB', 'TB']
153
+
154
+ units = [ "B", "KB", "MB", "GB", "TB" ]
155
155
  base = 1024.0
156
156
  exp = (Math.log(size_bytes) / Math.log(base)).to_i
157
- exp = [exp, units.length - 1].min
158
-
159
- "%.1f %s" % [size_bytes / (base ** exp), units[exp]]
157
+ exp = [ exp, units.length - 1 ].min
158
+
159
+ "%.1f %s" % [ size_bytes / (base ** exp), units[exp] ]
160
160
  end
161
-
161
+
162
162
  def self.cache_logs_data(logs_data)
163
163
  cache_file_path = solidstats_cache_path(CACHE_FILE)
164
164
  ensure_solidstats_directory
165
-
165
+
166
166
  File.write(cache_file_path, JSON.pretty_generate(logs_data))
167
167
  rescue => e
168
168
  Rails.logger.error("Failed to cache logs data: #{e.message}")
169
169
  end
170
-
170
+
171
171
  def self.update_summary_json(logs_data)
172
172
  summary_file_path = solidstats_cache_path(SUMMARY_FILE)
173
-
173
+
174
174
  # Read existing summary or create new one
175
175
  begin
176
176
  existing_summary = File.exist?(summary_file_path) ? JSON.parse(File.read(summary_file_path)) : {}
177
177
  rescue JSON::ParserError
178
178
  existing_summary = {}
179
179
  end
180
-
180
+
181
181
  # Create badges based on status
182
182
  badges = []
183
183
  badges << { "text" => "#{logs_data[:summary][:total_files]} Files", "color" => "info" }
184
-
184
+
185
185
  case logs_data[:summary][:status]
186
- when 'error'
186
+ when "error"
187
187
  badges << { "text" => "Large Size", "color" => "error" }
188
- when 'warning'
188
+ when "warning"
189
189
  badges << { "text" => "Growing", "color" => "warning" }
190
190
  else
191
191
  badges << { "text" => "Healthy", "color" => "success" }
192
192
  end
193
-
193
+
194
194
  # Update the log monitoring entry
195
195
  existing_summary["Log Files"] = {
196
196
  "icon" => "file-text",
@@ -200,25 +200,25 @@ module Solidstats
200
200
  "url" => "/solidstats/logs/size",
201
201
  "badges" => badges
202
202
  }
203
-
203
+
204
204
  # Write updated summary
205
205
  File.write(summary_file_path, JSON.pretty_generate(existing_summary))
206
206
  rescue => e
207
207
  Rails.logger.error("Failed to update summary.json: #{e.message}")
208
208
  end
209
-
209
+
210
210
  def self.cache_fresh?(cache_file_path, max_age = 30.minutes)
211
211
  File.mtime(cache_file_path) > max_age.ago
212
212
  rescue
213
213
  false
214
214
  end
215
-
215
+
216
216
  def self.solidstats_cache_path(filename)
217
- Rails.root.join( 'solidstats', filename)
217
+ Rails.root.join("solidstats", filename)
218
218
  end
219
-
219
+
220
220
  def self.ensure_solidstats_directory
221
- dir_path = File.dirname(solidstats_cache_path('dummy'))
221
+ dir_path = File.dirname(solidstats_cache_path("dummy"))
222
222
  FileUtils.mkdir_p(dir_path) unless Dir.exist?(dir_path)
223
223
  end
224
224
  end