solidstats 1.0.0 → 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: a77d4bb173582d123384ccdaad313743f1305432d26b03d9f9489512fd5a07fa
4
- data.tar.gz: 7ff358c8b80b43483c308956b408928fa2ef9d4e729b06c7446d37d730a86ecf
3
+ metadata.gz: b1e51da79f65a50793bc1bfc777ed19df5d81795739ceda8558d3261745b53ae
4
+ data.tar.gz: e58f0018fd96135789f192372b2b85f7165cf51a6921fa7e2a56057bebdb14b5
5
5
  SHA512:
6
- metadata.gz: 21c7e3383dd6e05ba382df3179a4935797b96872731e9e7cc4f9b3a7bed991a1cc93c44cc9b517a4333b97387475b73642ece0b69aba0e18df4a2a55508dcdb6
7
- data.tar.gz: 6ddc2a6c605f4975b6fd7e614cb01e9830b1616cf40597a2a779ec81c0673403517d39f961603f8ac12fda134461af0bd9b13823c3e5552e7d6ae3f36008c66e
6
+ metadata.gz: 3fc113aaaf952e1d9a320b55618b72d4b6025ec81d3bc274f29a090ddbc6a0f500bbd50aa2873d8e02d850dac992ef28214e5709f0160bc2ce0e88e348182f0a
7
+ data.tar.gz: 976d3989eeffc2781c1040846af1435147be33a407b6e5a2bae1db8dfba091f9678635b1863c3dcc36916adf5d2b334cc0f0a7d523225066bb4fdac074ebf270
data/CHANGELOG.md CHANGED
@@ -5,6 +5,23 @@ 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
+
8
25
  ## [1.0.0] - 2025-05-22
9
26
 
10
27
  ### Added
data/README.md CHANGED
@@ -7,6 +7,8 @@ Solidstats is a local-only Rails engine that shows your project's health at `/so
7
7
  - Bundler Audit scan with detailed remediation suggestions
8
8
  - Interactive vulnerability details with patched version information
9
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
10
12
  - Rubocop offense count and quality metrics
11
13
  - TODO/FIXME tracker with file hotspots
12
14
  - Test coverage summary
@@ -7,10 +7,12 @@ module Solidstats
7
7
  # Use new services for data collection
8
8
  audit_service = AuditService.new
9
9
  todo_service = TodoService.new
10
+ log_monitor_service = LogSizeMonitorService.new
10
11
 
11
12
  # Get full data for detailed views
12
13
  @audit_output = audit_service.fetch
13
14
  @todo_items = todo_service.fetch
15
+ @log_data = log_monitor_service.collect_data
14
16
 
15
17
  # Get summary data for dashboard cards
16
18
  @audit_summary = audit_service.summary
@@ -26,10 +28,12 @@ module Solidstats
26
28
  # Create services
27
29
  audit_service = AuditService.new
28
30
  todo_service = TodoService.new
31
+ log_monitor_service = LogSizeMonitorService.new
29
32
 
30
33
  # Force refresh of data
31
34
  audit_output = audit_service.fetch(true) # Force refresh
32
35
  todo_items = todo_service.fetch(true) # Force refresh
36
+ log_data = log_monitor_service.collect_data
33
37
 
34
38
  # Get updated summaries
35
39
  audit_summary = audit_service.summary
@@ -44,6 +48,7 @@ module Solidstats
44
48
  todo_items: todo_items,
45
49
  audit_summary: audit_summary,
46
50
  todo_summary: todo_summary,
51
+ log_data: log_data,
47
52
  last_updated: last_updated,
48
53
  status: "success"
49
54
  }
@@ -54,5 +59,19 @@ module Solidstats
54
59
  message: "Failed to refresh data: #{e.message}"
55
60
  }, status: :internal_server_error
56
61
  end
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"
70
+ end
71
+
72
+ result = log_monitor_service.truncate_log(filename)
73
+
74
+ render json: result
75
+ end
57
76
  end
58
77
  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
@@ -0,0 +1,759 @@
1
+ <%
2
+ # Convert status symbol to CSS class and display text
3
+ status_class = case @log_data[:status]
4
+ when :ok then "status-ok"
5
+ when :warning then "status-warning"
6
+ when :danger then "status-danger"
7
+ else ""
8
+ end
9
+
10
+ status_text = case @log_data[:status]
11
+ when :ok then "✅ OK: Log files are within acceptable limits"
12
+ when :warning then "⚠️ Warning: Log files are getting large"
13
+ when :danger then "❌ Danger: Log files are too large"
14
+ else "Unknown status"
15
+ end
16
+
17
+ # Calculate meter fill percentage (capped at 100%)
18
+ fill_percentage = [(@log_data[:total_size_mb] / 50.0) * 100, 100].min
19
+
20
+ # Set color based on status
21
+ fill_color = case @log_data[:status]
22
+ when :ok then "#28a745"
23
+ when :warning then "#ffc107"
24
+ when :danger then "#dc3545"
25
+ else "#6c757d"
26
+ end
27
+ %>
28
+
29
+ <div class="log-monitor-card">
30
+ <div class="log-header">
31
+ <div>
32
+ <h3><span class="icon">📊</span> Log Size Monitor</h3>
33
+ <p class="log-path"><%= @log_data[:log_dir_path] %></p>
34
+ </div>
35
+ <div class="log-actions">
36
+ <button id="truncate-all-logs-btn" class="action-button truncate-button">
37
+ <span class="action-icon">✂️</span> Truncate All Logs
38
+ </button>
39
+ </div>
40
+ </div>
41
+
42
+ <div class="log-content">
43
+ <div class="log-status">
44
+ <div class="log-indicator <%= status_class %>"></div>
45
+ <div class="log-status-text"><%= status_text %></div>
46
+ </div>
47
+
48
+ <div class="log-size-container">
49
+ <div class="log-size-label">Total Size:</div>
50
+ <div class="log-size-value"><%= @log_data[:total_size_mb] %> MB</div>
51
+ <div class="log-files-count">(<%= @log_data[:logs_count] %> log files)</div>
52
+ </div>
53
+
54
+ <div class="log-size-meter">
55
+ <div class="meter-bg"></div>
56
+ <div class="meter-fill" style="width: <%= fill_percentage %>%; background-color: <%= fill_color %>;"></div>
57
+ <div class="meter-markers">
58
+ <div class="marker" style="left: 0%">0</div>
59
+ <div class="marker warning-marker" style="left: 50%">25 MB</div>
60
+ <div class="marker danger-marker" style="left: 100%">50+ MB</div>
61
+ </div>
62
+ </div>
63
+ </div>
64
+
65
+ <div class="log-files-table">
66
+ <h4>Individual Log Files</h4>
67
+ <table>
68
+ <thead>
69
+ <tr>
70
+ <th>File Name</th>
71
+ <th>Size</th>
72
+ <th>Last Modified</th>
73
+ <th>Status</th>
74
+ <th>Actions</th>
75
+ </tr>
76
+ </thead>
77
+ <tbody>
78
+ <% @log_data[:log_files].each do |log_file| %>
79
+ <%
80
+ file_status_class = case log_file[:status]
81
+ when :ok then "status-ok"
82
+ when :warning then "status-warning"
83
+ when :danger then "status-danger"
84
+ else ""
85
+ end
86
+ %>
87
+ <tr class="<%= file_status_class %>">
88
+ <td class="filename"><%= log_file[:filename] %></td>
89
+ <td class="size"><%= log_file[:size_mb] %> MB</td>
90
+ <td class="modified"><%= log_file[:last_modified].strftime("%b %d, %Y %H:%M") %></td>
91
+ <td class="status">
92
+ <% if log_file[:status] == :ok %>
93
+ <span class="status-icon">✅</span>
94
+ <% elsif log_file[:status] == :warning %>
95
+ <span class="status-icon">⚠️</span>
96
+ <% else %>
97
+ <span class="status-icon">❌</span>
98
+ <% end %>
99
+ </td>
100
+ <td class="actions">
101
+ <button class="truncate-file-btn" data-filename="<%= log_file[:filename] %>">
102
+ <span class="action-icon">✂️</span> Truncate
103
+ </button>
104
+ </td>
105
+ </tr>
106
+ <% end %>
107
+ </tbody>
108
+ </table>
109
+ </div>
110
+
111
+ <div class="log-recommendations">
112
+ <h4>Recommendations</h4>
113
+ <ul>
114
+ <% if @log_data[:status] == :danger %>
115
+ <li><strong>Immediate action required:</strong> Your log files exceed 50MB in total. Consider truncating them now.</li>
116
+ <% elsif @log_data[:status] == :warning %>
117
+ <li><strong>Monitor closely:</strong> Your log files exceed 25MB in total. Consider truncating them soon.</li>
118
+ <% end %>
119
+ <li>Configure log rotation to automatically manage log file sizes</li>
120
+ <li>For production environments, consider using a log management service</li>
121
+ </ul>
122
+ </div>
123
+ </div>
124
+
125
+ <style>
126
+ .log-monitor-card {
127
+ background-color: #fff;
128
+ border-radius: 8px;
129
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
130
+ overflow: hidden;
131
+ margin-top: 1rem;
132
+ margin-bottom: 1.5rem;
133
+ }
134
+
135
+ .log-header {
136
+ padding: 1rem;
137
+ display: flex;
138
+ justify-content: space-between;
139
+ align-items: flex-start;
140
+ border-bottom: 1px solid #eee;
141
+ }
142
+
143
+ .log-header h3 {
144
+ font-size: 1.2rem;
145
+ margin: 0 0 0.5rem;
146
+ font-weight: 600;
147
+ display: flex;
148
+ align-items: center;
149
+ gap: 0.5rem;
150
+ }
151
+
152
+ .log-path {
153
+ font-family: monospace;
154
+ font-size: 0.8rem;
155
+ color: #666;
156
+ margin: 0;
157
+ word-break: break-all;
158
+ }
159
+
160
+ .log-actions {
161
+ flex-shrink: 0;
162
+ }
163
+
164
+ .truncate-button {
165
+ display: inline-flex;
166
+ align-items: center;
167
+ padding: 0.5rem 1rem;
168
+ background-color: #f8f9fa;
169
+ border: 1px solid #dee2e6;
170
+ color: #333;
171
+ border-radius: 4px;
172
+ font-size: 0.9rem;
173
+ cursor: pointer;
174
+ transition: all 0.2s;
175
+ }
176
+
177
+ .truncate-button:hover {
178
+ background-color: #e9ecef;
179
+ }
180
+
181
+ .log-content {
182
+ padding: 1rem;
183
+ }
184
+
185
+ .log-status {
186
+ display: flex;
187
+ align-items: center;
188
+ gap: 0.75rem;
189
+ margin-bottom: 1rem;
190
+ }
191
+
192
+ .log-indicator {
193
+ width: 12px;
194
+ height: 12px;
195
+ border-radius: 50%;
196
+ background-color: #6c757d;
197
+ }
198
+
199
+ .log-indicator.status-ok {
200
+ background-color: #28a745;
201
+ }
202
+
203
+ .log-indicator.status-warning {
204
+ background-color: #ffc107;
205
+ }
206
+
207
+ .log-indicator.status-danger {
208
+ background-color: #dc3545;
209
+ }
210
+
211
+ .log-status-text {
212
+ font-weight: 500;
213
+ }
214
+
215
+ .log-size-container {
216
+ display: flex;
217
+ align-items: center;
218
+ gap: 0.5rem;
219
+ margin-bottom: 0.75rem;
220
+ }
221
+
222
+ .log-size-label {
223
+ font-size: 0.9rem;
224
+ color: #495057;
225
+ }
226
+
227
+ .log-size-value {
228
+ font-weight: 600;
229
+ font-size: 1.1rem;
230
+ }
231
+
232
+ .log-files-count {
233
+ font-size: 0.9rem;
234
+ color: #6c757d;
235
+ }
236
+
237
+ .log-size-meter {
238
+ position: relative;
239
+ height: 30px;
240
+ background-color: #f8f9fa;
241
+ border-radius: 4px;
242
+ overflow: hidden;
243
+ margin-top: 1rem;
244
+ margin-bottom: 1rem;
245
+ }
246
+
247
+ .meter-bg {
248
+ position: absolute;
249
+ top: 0;
250
+ left: 0;
251
+ width: 100%;
252
+ height: 100%;
253
+ background: linear-gradient(to right, #28a745 0%, #28a745 50%, #ffc107 50%, #ffc107 75%, #dc3545 75%, #dc3545 100%);
254
+ opacity: 0.15;
255
+ }
256
+
257
+ .meter-fill {
258
+ position: absolute;
259
+ top: 0;
260
+ left: 0;
261
+ height: 100%;
262
+ transition: width 0.5s;
263
+ }
264
+
265
+ .meter-markers {
266
+ position: absolute;
267
+ bottom: 0;
268
+ left: 0;
269
+ width: 100%;
270
+ height: 100%;
271
+ display: flex;
272
+ align-items: flex-end;
273
+ padding-bottom: 0.25rem;
274
+ }
275
+
276
+ .marker {
277
+ position: absolute;
278
+ font-size: 0.7rem;
279
+ transform: translateX(-50%);
280
+ padding: 2px 4px;
281
+ color: #495057;
282
+ }
283
+
284
+ .warning-marker {
285
+ color: #856404;
286
+ }
287
+
288
+ .danger-marker {
289
+ color: #721c24;
290
+ }
291
+
292
+ .log-files-table {
293
+ padding: 1rem;
294
+ border-top: 1px solid #eee;
295
+ }
296
+
297
+ .log-files-table h4 {
298
+ font-size: 1rem;
299
+ margin-top: 0;
300
+ margin-bottom: 0.75rem;
301
+ }
302
+
303
+ .log-files-table table {
304
+ width: 100%;
305
+ border-collapse: collapse;
306
+ font-size: 0.9rem;
307
+ }
308
+
309
+ .log-files-table th,
310
+ .log-files-table td {
311
+ padding: 0.5rem;
312
+ text-align: left;
313
+ border-bottom: 1px solid #eee;
314
+ }
315
+
316
+ .log-files-table th {
317
+ font-weight: 600;
318
+ color: #495057;
319
+ }
320
+
321
+ .log-files-table tr:last-child td {
322
+ border-bottom: none;
323
+ }
324
+
325
+ .log-files-table .filename {
326
+ font-family: monospace;
327
+ max-width: 200px;
328
+ overflow: hidden;
329
+ text-overflow: ellipsis;
330
+ white-space: nowrap;
331
+ }
332
+
333
+ .log-files-table .status {
334
+ text-align: center;
335
+ }
336
+
337
+ .log-files-table .actions {
338
+ text-align: right;
339
+ }
340
+
341
+ .log-files-table .truncate-file-btn {
342
+ padding: 0.25rem 0.5rem;
343
+ font-size: 0.8rem;
344
+ background-color: #f8f9fa;
345
+ border: 1px solid #dee2e6;
346
+ border-radius: 4px;
347
+ cursor: pointer;
348
+ display: inline-flex;
349
+ align-items: center;
350
+ gap: 0.25rem;
351
+ }
352
+
353
+ .log-files-table .truncate-file-btn:hover {
354
+ background-color: #e9ecef;
355
+ }
356
+
357
+ .log-files-table tr.status-danger {
358
+ background-color: rgba(220, 53, 69, 0.05);
359
+ }
360
+
361
+ .log-files-table tr.status-warning {
362
+ background-color: rgba(255, 193, 7, 0.05);
363
+ }
364
+
365
+ .log-recommendations {
366
+ padding: 1rem;
367
+ background-color: #f8f9fa;
368
+ border-top: 1px solid #eee;
369
+ }
370
+
371
+ .log-recommendations h4 {
372
+ font-size: 1rem;
373
+ margin-top: 0;
374
+ margin-bottom: 0.5rem;
375
+ }
376
+
377
+ .log-recommendations ul {
378
+ margin-bottom: 0;
379
+ padding-left: 1.5rem;
380
+ }
381
+
382
+ .log-recommendations li {
383
+ margin-bottom: 0.3rem;
384
+ font-size: 0.9rem;
385
+ }
386
+
387
+ .log-recommendations li:last-child {
388
+ margin-bottom: 0;
389
+ }
390
+ </style>
391
+
392
+ <script>
393
+ document.addEventListener('DOMContentLoaded', function() {
394
+ // Truncate all logs
395
+ const truncateAllLogsBtn = document.getElementById('truncate-all-logs-btn');
396
+ if (truncateAllLogsBtn) {
397
+ truncateAllLogsBtn.addEventListener('click', function() {
398
+ // Confirm before truncating
399
+ if (!confirm('Are you sure you want to truncate ALL log files? This action cannot be undone.')) {
400
+ return;
401
+ }
402
+
403
+ truncateLog(null, this);
404
+ });
405
+ }
406
+
407
+ // Individual file truncate buttons
408
+ document.querySelectorAll('.truncate-file-btn').forEach(function(button) {
409
+ button.addEventListener('click', function() {
410
+ const filename = this.getAttribute('data-filename');
411
+
412
+ // Confirm before truncating
413
+ if (!confirm(`Are you sure you want to truncate log file: ${filename}? This action cannot be undone.`)) {
414
+ return;
415
+ }
416
+
417
+ truncateLog(filename, this);
418
+ });
419
+ });
420
+
421
+ function truncateLog(filename, button) {
422
+ const originalText = button.innerHTML;
423
+ button.innerHTML = '<span class="action-icon">⏳</span> Truncating...';
424
+ button.disabled = true;
425
+
426
+ // Strip .log extension from filename if present (controller will add it back)
427
+ let urlFilename = filename;
428
+ if (urlFilename && urlFilename.endsWith('.log')) {
429
+ urlFilename = urlFilename.substring(0, urlFilename.length - 4);
430
+ }
431
+
432
+ const url = urlFilename ?
433
+ '<%= solidstats.truncate_log_path %>/' + encodeURIComponent(urlFilename) :
434
+ '<%= solidstats.truncate_log_path %>';
435
+
436
+ fetch(url, {
437
+ method: 'POST',
438
+ headers: {
439
+ 'Accept': 'application/json',
440
+ 'X-Requested-With': 'XMLHttpRequest',
441
+ 'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || ''
442
+ },
443
+ credentials: 'same-origin'
444
+ })
445
+ .then(response => response.json())
446
+ .then(data => {
447
+ if (data.success) {
448
+ showNotification(data.message, 'success');
449
+ // Reload the page to see updated log sizes
450
+ window.location.reload();
451
+ } else {
452
+ showNotification(data.message, 'error');
453
+ // Reset button state
454
+ button.innerHTML = originalText;
455
+ button.disabled = false;
456
+ }
457
+ })
458
+ .catch(error => {
459
+ console.error('Error truncating log file:', error);
460
+ showNotification('Failed to truncate log file', 'error');
461
+
462
+ // Reset button state
463
+ button.innerHTML = originalText;
464
+ button.disabled = false;
465
+ });
466
+ }
467
+ });
468
+
469
+ // Notification function if not already defined
470
+ if (typeof showNotification !== 'function') {
471
+ function showNotification(message, type) {
472
+ // Create notification element
473
+ const notification = document.createElement('div');
474
+ notification.className = `toast-notification ${type}`;
475
+ notification.textContent = message;
476
+
477
+ // Add to body
478
+ document.body.appendChild(notification);
479
+
480
+ // Show notification
481
+ setTimeout(() => {
482
+ notification.classList.add('visible');
483
+ }, 10);
484
+
485
+ // Hide and remove notification
486
+ setTimeout(() => {
487
+ notification.classList.remove('visible');
488
+ setTimeout(() => {
489
+ notification.remove();
490
+ }, 300);
491
+ }, 5000);
492
+ }
493
+ }
494
+ </script>
495
+
496
+ <style>
497
+ .log-monitor-card {
498
+ background-color: #fff;
499
+ border-radius: 8px;
500
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
501
+ overflow: hidden;
502
+ margin-top: 1rem;
503
+ margin-bottom: 1.5rem;
504
+ }
505
+
506
+ .log-header {
507
+ padding: 1rem;
508
+ display: flex;
509
+ justify-content: space-between;
510
+ align-items: flex-start;
511
+ border-bottom: 1px solid #eee;
512
+ }
513
+
514
+ .log-header h3 {
515
+ font-size: 1.2rem;
516
+ margin: 0 0 0.5rem;
517
+ font-weight: 600;
518
+ display: flex;
519
+ align-items: center;
520
+ gap: 0.5rem;
521
+ }
522
+
523
+ .log-path {
524
+ font-family: monospace;
525
+ font-size: 0.8rem;
526
+ color: #666;
527
+ margin: 0;
528
+ word-break: break-all;
529
+ }
530
+
531
+ .log-actions {
532
+ flex-shrink: 0;
533
+ }
534
+
535
+ .truncate-button {
536
+ display: inline-flex;
537
+ align-items: center;
538
+ padding: 0.5rem 1rem;
539
+ background-color: #f8f9fa;
540
+ border: 1px solid #dee2e6;
541
+ color: #333;
542
+ border-radius: 4px;
543
+ font-size: 0.9rem;
544
+ cursor: pointer;
545
+ transition: all 0.2s;
546
+ }
547
+
548
+ .truncate-button:hover {
549
+ background-color: #e9ecef;
550
+ }
551
+
552
+ .log-content {
553
+ padding: 1rem;
554
+ }
555
+
556
+ .log-status {
557
+ display: flex;
558
+ align-items: center;
559
+ gap: 0.75rem;
560
+ margin-bottom: 1rem;
561
+ }
562
+
563
+ .log-indicator {
564
+ width: 12px;
565
+ height: 12px;
566
+ border-radius: 50%;
567
+ background-color: #6c757d;
568
+ }
569
+
570
+ .log-indicator.status-ok {
571
+ background-color: #28a745;
572
+ }
573
+
574
+ .log-indicator.status-warning {
575
+ background-color: #ffc107;
576
+ }
577
+
578
+ .log-indicator.status-danger {
579
+ background-color: #dc3545;
580
+ }
581
+
582
+ .log-status-text {
583
+ font-weight: 500;
584
+ }
585
+
586
+ .log-size-container {
587
+ display: flex;
588
+ align-items: center;
589
+ gap: 0.5rem;
590
+ margin-bottom: 0.75rem;
591
+ }
592
+
593
+ .log-size-label {
594
+ font-size: 0.9rem;
595
+ color: #495057;
596
+ }
597
+
598
+ .log-size-value {
599
+ font-weight: 600;
600
+ font-size: 1.1rem;
601
+ }
602
+
603
+ .log-size-meter {
604
+ position: relative;
605
+ height: 30px;
606
+ background-color: #f8f9fa;
607
+ border-radius: 4px;
608
+ overflow: hidden;
609
+ margin-top: 1rem;
610
+ margin-bottom: 1rem;
611
+ }
612
+
613
+ .meter-bg {
614
+ position: absolute;
615
+ top: 0;
616
+ left: 0;
617
+ width: 100%;
618
+ height: 100%;
619
+ background: linear-gradient(to right, #28a745 0%, #28a745 50%, #ffc107 50%, #ffc107 75%, #dc3545 75%, #dc3545 100%);
620
+ opacity: 0.15;
621
+ }
622
+
623
+ .meter-fill {
624
+ position: absolute;
625
+ top: 0;
626
+ left: 0;
627
+ height: 100%;
628
+ transition: width 0.5s;
629
+ }
630
+
631
+ .meter-markers {
632
+ position: absolute;
633
+ bottom: 0;
634
+ left: 0;
635
+ width: 100%;
636
+ height: 100%;
637
+ display: flex;
638
+ align-items: flex-end;
639
+ padding-bottom: 0.25rem;
640
+ }
641
+
642
+ .marker {
643
+ position: absolute;
644
+ font-size: 0.7rem;
645
+ transform: translateX(-50%);
646
+ padding: 2px 4px;
647
+ color: #495057;
648
+ }
649
+
650
+ .warning-marker {
651
+ color: #856404;
652
+ }
653
+
654
+ .danger-marker {
655
+ color: #721c24;
656
+ }
657
+
658
+ .log-recommendations {
659
+ padding: 1rem;
660
+ background-color: #f8f9fa;
661
+ border-top: 1px solid #eee;
662
+ }
663
+
664
+ .log-recommendations h4 {
665
+ font-size: 1rem;
666
+ margin-top: 0;
667
+ margin-bottom: 0.5rem;
668
+ }
669
+
670
+ .log-recommendations ul {
671
+ margin-bottom: 0;
672
+ padding-left: 1.5rem;
673
+ }
674
+
675
+ .log-recommendations li {
676
+ margin-bottom: 0.3rem;
677
+ font-size: 0.9rem;
678
+ }
679
+
680
+ .log-recommendations li:last-child {
681
+ margin-bottom: 0;
682
+ }
683
+ </style>
684
+
685
+ <script>
686
+ document.addEventListener('DOMContentLoaded', function() {
687
+ const truncateButton = document.getElementById('truncate-log-btn');
688
+ if (truncateButton) {
689
+ truncateButton.addEventListener('click', function() {
690
+ // Confirm before truncating
691
+ if (!confirm('Are you sure you want to truncate the log file? This action cannot be undone.')) {
692
+ return;
693
+ }
694
+
695
+ const button = this;
696
+ const originalText = button.innerHTML;
697
+ button.innerHTML = '<span class="action-icon">⏳</span> Truncating...';
698
+ button.disabled = true;
699
+
700
+ fetch('<%= solidstats.truncate_log_path %>', {
701
+ method: 'POST',
702
+ headers: {
703
+ 'Accept': 'application/json',
704
+ 'X-Requested-With': 'XMLHttpRequest',
705
+ 'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || ''
706
+ },
707
+ credentials: 'same-origin'
708
+ })
709
+ .then(response => response.json())
710
+ .then(data => {
711
+ if (data.success) {
712
+ showNotification(data.message, 'success');
713
+ // Reload the page to see updated log size
714
+ window.location.reload();
715
+ } else {
716
+ showNotification(data.message, 'error');
717
+ // Reset button state
718
+ button.innerHTML = originalText;
719
+ button.disabled = false;
720
+ }
721
+ })
722
+ .catch(error => {
723
+ console.error('Error truncating log file:', error);
724
+ showNotification('Failed to truncate log file', 'error');
725
+
726
+ // Reset button state
727
+ button.innerHTML = originalText;
728
+ button.disabled = false;
729
+ });
730
+ });
731
+ }
732
+ });
733
+
734
+ // Notification function if not already defined
735
+ if (typeof showNotification !== 'function') {
736
+ function showNotification(message, type) {
737
+ // Create notification element
738
+ const notification = document.createElement('div');
739
+ notification.className = `toast-notification ${type}`;
740
+ notification.textContent = message;
741
+
742
+ // Add to body
743
+ document.body.appendChild(notification);
744
+
745
+ // Show notification
746
+ setTimeout(() => {
747
+ notification.classList.add('visible');
748
+ }, 10);
749
+
750
+ // Hide and remove notification
751
+ setTimeout(() => {
752
+ notification.classList.remove('visible');
753
+ setTimeout(() => {
754
+ notification.remove();
755
+ }, 300);
756
+ }, 5000);
757
+ }
758
+ }
759
+ </script>
@@ -53,6 +53,14 @@
53
53
  <div class="summary-label">Test Coverage</div>
54
54
  </div>
55
55
  </div>
56
+
57
+ <div class="summary-card status-<%= @log_data[:status] %>" data-section="code-quality" data-tab="log-monitor">
58
+ <div class="summary-icon">📊</div>
59
+ <div class="summary-data">
60
+ <div class="summary-value"><%= @log_data[:total_size_mb] %> MB</div>
61
+ <div class="summary-label">Log Files Size</div>
62
+ </div>
63
+ </div>
56
64
  </div>
57
65
  </section>
58
66
 
@@ -237,6 +245,9 @@
237
245
  <button class="tab-button active" data-tab="quality-metrics">Metrics</button>
238
246
  <button class="tab-button" data-tab="test-coverage">Test Coverage</button>
239
247
  <button class="tab-button" data-tab="code-health">Code Health</button>
248
+ <button class="tab-button" data-tab="log-monitor">
249
+ <span class="tab-icon">📊</span> Log Monitor
250
+ </button>
240
251
  </div>
241
252
 
242
253
  <div class="tabs-content">
@@ -271,6 +282,9 @@
271
282
  </div>
272
283
  </div>
273
284
  </div>
285
+ <div class="tab-content" id="log-monitor">
286
+ <%= render partial: 'solidstats/dashboard/log_monitor' %>
287
+ </div>
274
288
  </div>
275
289
  </div>
276
290
  </section>
data/config/routes.rb CHANGED
@@ -1,4 +1,8 @@
1
1
  Solidstats::Engine.routes.draw do
2
2
  root to: "dashboard#index"
3
3
  get "refresh", to: "dashboard#refresh", as: :refresh
4
+
5
+ # Route for truncating logs - accepts filename without extension
6
+ post "truncate-log(/:filename)", to: "dashboard#truncate_log", as: :truncate_log,
7
+ constraints: { filename: /[^\.]+/ }
4
8
  end
@@ -1,3 +1,3 @@
1
1
  module Solidstats
2
- VERSION = "1.0.0"
2
+ VERSION = "1.1.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solidstats
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - MezbahAlam
@@ -59,8 +59,10 @@ files:
59
59
  - app/models/solidstats/application_record.rb
60
60
  - app/services/solidstats/audit_service.rb
61
61
  - app/services/solidstats/data_collector_service.rb
62
+ - app/services/solidstats/log_size_monitor_service.rb
62
63
  - app/services/solidstats/todo_service.rb
63
64
  - app/views/layouts/solidstats/application.html.erb
65
+ - app/views/solidstats/dashboard/_log_monitor.html.erb
64
66
  - app/views/solidstats/dashboard/_todos.html.erb
65
67
  - app/views/solidstats/dashboard/audit/_additional_styles.css
66
68
  - app/views/solidstats/dashboard/audit/_audit_badge.html.erb