rails_error_dashboard 0.5.15 → 0.6.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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/rails_error_dashboard/errors_controller.rb +31 -26
  3. data/app/helpers/rails_error_dashboard/application_helper.rb +12 -5
  4. data/app/views/layouts/rails_error_dashboard.html.erb +1217 -1935
  5. data/app/views/rails_error_dashboard/errors/_breadcrumbs_group.html.erb +4 -4
  6. data/app/views/rails_error_dashboard/errors/_co_occurring_errors.html.erb +1 -1
  7. data/app/views/rails_error_dashboard/errors/_discussion.html.erb +3 -3
  8. data/app/views/rails_error_dashboard/errors/_error_cascades.html.erb +1 -1
  9. data/app/views/rails_error_dashboard/errors/_error_row.html.erb +69 -79
  10. data/app/views/rails_error_dashboard/errors/_instance_variables.html.erb +1 -1
  11. data/app/views/rails_error_dashboard/errors/_issue_section.html.erb +1 -1
  12. data/app/views/rails_error_dashboard/errors/_local_variables.html.erb +1 -1
  13. data/app/views/rails_error_dashboard/errors/_pattern_insights.html.erb +2 -2
  14. data/app/views/rails_error_dashboard/errors/_request_context.html.erb +1 -1
  15. data/app/views/rails_error_dashboard/errors/_sidebar_metadata.html.erb +1 -1
  16. data/app/views/rails_error_dashboard/errors/_similar_errors.html.erb +1 -1
  17. data/app/views/rails_error_dashboard/errors/_timeline.html.erb +1 -1
  18. data/app/views/rails_error_dashboard/errors/actioncable_health_summary.html.erb +6 -6
  19. data/app/views/rails_error_dashboard/errors/activestorage_health_summary.html.erb +6 -6
  20. data/app/views/rails_error_dashboard/errors/analytics.html.erb +34 -50
  21. data/app/views/rails_error_dashboard/errors/cache_health_summary.html.erb +7 -7
  22. data/app/views/rails_error_dashboard/errors/correlation.html.erb +11 -11
  23. data/app/views/rails_error_dashboard/errors/database_health_summary.html.erb +114 -172
  24. data/app/views/rails_error_dashboard/errors/deprecations.html.erb +7 -7
  25. data/app/views/rails_error_dashboard/errors/diagnostic_dumps.html.erb +6 -6
  26. data/app/views/rails_error_dashboard/errors/index.html.erb +292 -620
  27. data/app/views/rails_error_dashboard/errors/job_health_summary.html.erb +7 -7
  28. data/app/views/rails_error_dashboard/errors/n_plus_one_summary.html.erb +7 -7
  29. data/app/views/rails_error_dashboard/errors/overview.html.erb +192 -363
  30. data/app/views/rails_error_dashboard/errors/platform_comparison.html.erb +11 -11
  31. data/app/views/rails_error_dashboard/errors/rack_attack_summary.html.erb +6 -6
  32. data/app/views/rails_error_dashboard/errors/releases.html.erb +6 -6
  33. data/app/views/rails_error_dashboard/errors/settings.html.erb +32 -52
  34. data/app/views/rails_error_dashboard/errors/show.html.erb +200 -203
  35. data/app/views/rails_error_dashboard/errors/swallowed_exceptions.html.erb +7 -7
  36. data/app/views/rails_error_dashboard/errors/user_impact.html.erb +6 -6
  37. data/lib/rails_error_dashboard/configuration.rb +6 -0
  38. data/lib/rails_error_dashboard/version.rb +1 -1
  39. metadata +2 -2
@@ -5,7 +5,7 @@
5
5
  <% deprecation_crumbs = breadcrumbs.select { |c| c["c"] == "deprecation" } %>
6
6
  <% if deprecation_crumbs.any? %>
7
7
  <div class="card mb-4" id="section-deprecation-warnings" style="border-color: var(--bs-danger);">
8
- <div class="card-header bg-white">
8
+ <div class="card-header">
9
9
  <h5 class="mb-0">
10
10
  <i class="bi bi-exclamation-triangle text-danger"></i> Deprecation Warnings
11
11
  <span class="badge bg-danger"><%= deprecation_crumbs.size %></span>
@@ -49,7 +49,7 @@
49
49
  <% n_plus_one_patterns = RailsErrorDashboard::Services::NplusOneDetector.call(breadcrumbs) %>
50
50
  <% if n_plus_one_patterns.any? %>
51
51
  <div class="card mb-4" id="section-n-plus-one" style="border-color: var(--bs-warning);">
52
- <div class="card-header bg-white">
52
+ <div class="card-header">
53
53
  <h5 class="mb-0">
54
54
  <i class="bi bi-arrow-repeat text-warning"></i> N+1 Query Detection
55
55
  <span class="badge bg-warning text-dark"><%= n_plus_one_patterns.size %> pattern<%= n_plus_one_patterns.size == 1 ? "" : "s" %></span>
@@ -108,7 +108,7 @@
108
108
  <% cache_analysis = RailsErrorDashboard::Services::CacheAnalyzer.call(breadcrumbs) %>
109
109
  <% if cache_analysis %>
110
110
  <div class="card mb-4" id="section-cache-health" style="border-color: var(--bs-info);">
111
- <div class="card-header bg-white">
111
+ <div class="card-header">
112
112
  <h5 class="mb-0">
113
113
  <i class="bi bi-lightning-charge text-info"></i> Cache Health
114
114
  <span class="badge bg-info text-dark"><%= cache_analysis[:reads] + cache_analysis[:writes] %> operations</span>
@@ -189,7 +189,7 @@
189
189
  <% end %>
190
190
 
191
191
  <div class="card mb-4" id="section-breadcrumbs">
192
- <div class="card-header bg-white">
192
+ <div class="card-header">
193
193
  <h5 class="mb-0">
194
194
  <i class="bi bi-signpost-2"></i> Breadcrumbs
195
195
  <span class="badge bg-secondary"><%= breadcrumbs.size %> events</span>
@@ -3,7 +3,7 @@
3
3
  <% co_occurring = error.co_occurring_errors(window_minutes: 5, min_frequency: 2, limit: 5) %>
4
4
  <% if co_occurring.any? %>
5
5
  <div class="card mb-4" id="section-co-occurring">
6
- <div class="card-header bg-white">
6
+ <div class="card-header">
7
7
  <h5 class="mb-0">
8
8
  <i class="bi bi-clock-history"></i> Co-occurring Errors
9
9
  <span class="badge bg-info text-dark">Time Patterns</span>
@@ -5,7 +5,7 @@
5
5
 
6
6
  <% if has_linked_issue && (platform_comments.any? || has_audit_comments) %>
7
7
  <div class="card mb-4" id="section-discussion">
8
- <div class="card-header bg-white d-flex justify-content-between align-items-center">
8
+ <div class="card-header d-flex justify-content-between align-items-center">
9
9
  <h5 class="mb-0">
10
10
  <i class="bi bi-chat-dots"></i> Discussion
11
11
  <% if platform_comments.any? %>
@@ -62,7 +62,7 @@
62
62
  <% elsif has_linked_issue %>
63
63
  <!-- Issue linked but no comments yet -->
64
64
  <div class="card mb-4" id="section-discussion">
65
- <div class="card-header bg-white d-flex justify-content-between align-items-center">
65
+ <div class="card-header d-flex justify-content-between align-items-center">
66
66
  <h5 class="mb-0">
67
67
  <i class="bi bi-chat-dots"></i> Discussion
68
68
  </h5>
@@ -77,7 +77,7 @@
77
77
  <% elsif has_audit_comments %>
78
78
  <!-- No issue linked, but has audit trail -->
79
79
  <div class="card mb-4" id="section-discussion">
80
- <div class="card-header bg-white">
80
+ <div class="card-header">
81
81
  <h5 class="mb-0">
82
82
  <i class="bi bi-clock-history"></i> Activity Log
83
83
  </h5>
@@ -3,7 +3,7 @@
3
3
  <% cascades = error.error_cascades %>
4
4
  <% if cascades[:parents].any? || cascades[:children].any? %>
5
5
  <div class="card" id="section-error-cascades">
6
- <div class="card-header bg-white">
6
+ <div class="card-header">
7
7
  <h5 class="mb-0">
8
8
  <i class="bi bi-diagram-3"></i> Error Cascades
9
9
  <small class="text-muted ms-2">Causal relationships between errors</small>
@@ -1,54 +1,83 @@
1
- <tr id="error_<%= error.id %>">
2
- <td onclick="event.stopPropagation();">
3
- <input type="checkbox" class="error-checkbox form-check-input" value="<%= error.id %>" data-error-id="<%= error.id %>">
1
+ <tr id="error_<%= error.id %>"
2
+ onclick="window.location='<%= error_path(error, **app_context) %>'"
3
+ style="border-bottom: 1px solid var(--border-primary); cursor: pointer; transition: background 0.1s ease;"
4
+ onmouseenter="this.style.background='var(--surface-hover)'; this.querySelector('.sev-bar').style.opacity='1';"
5
+ onmouseleave="this.style.background='transparent'; this.querySelector('.sev-bar').style.opacity='0';">
6
+ <td style="padding: var(--space-3) var(--space-4); text-align: center; position: relative;" onclick="event.stopPropagation(); var cb = this.querySelector('input'); cb.checked = !cb.checked; cb.dispatchEvent(new Event('change'));">
7
+ <span class="sev-bar" style="position: absolute; left: 0; top: 0; bottom: 0; width: 3px; background: <%= severity_color_var(error.severity) %>; opacity: 0; transition: opacity 0.1s ease; border-radius: 0 2px 2px 0;"></span>
8
+ <input type="checkbox" class="error-checkbox form-check-input" value="<%= error.id %>" data-error-id="<%= error.id %>" onclick="event.stopPropagation();" style="accent-color: var(--accent);">
4
9
  </td>
5
- <td onclick="window.location='<%= error_path(error) %>';">
6
- <% severity = error.severity %>
7
- <% if severity == :critical %>
8
- <span class="badge bg-danger" data-bs-toggle="tooltip" title="Critical severity - requires immediate attention">CRITICAL</span>
9
- <% elsif severity == :high %>
10
- <span class="badge bg-warning text-dark" data-bs-toggle="tooltip" title="High severity - should be fixed soon">HIGH</span>
11
- <% elsif severity == :medium %>
12
- <span class="badge bg-info text-dark" data-bs-toggle="tooltip" title="Medium severity - fix when possible">MEDIUM</span>
13
- <% else %>
14
- <span class="badge bg-secondary" data-bs-toggle="tooltip" title="Low severity - minor issue">LOW</span>
15
- <% end %>
16
- <% if error.respond_to?(:priority_score) && error.priority_score %>
17
- <br><small class="text-muted" data-bs-toggle="tooltip" title="Priority score (0-100) based on severity, frequency, recency, and user impact">P<%= error.priority_score %></small>
18
- <% end %>
10
+ <td style="padding: var(--space-3) var(--space-4);" onclick="window.location='<%= error_path(error, **app_context) %>'">
11
+ <div style="display: flex; align-items: center; gap: 8px;">
12
+ <span class="badge bg-<%= severity_color(error.severity) %>" style="display: inline-flex; align-items: center; gap: 4px;">
13
+ <span style="width: 6px; height: 6px; border-radius: 50%; background: currentColor;"></span>
14
+ <%= error.severity %>
15
+ </span>
16
+ <div style="min-width: 0;">
17
+ <div style="font-weight: 600; color: var(--text-primary); font-family: var(--font-mono); font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
18
+ <%= error.error_type %>
19
+ <% if error.recent? %>
20
+ <span style="display: inline-block; padding: 1px 6px; font-size: 9px; font-weight: 600; border-radius: var(--radius-full); background: var(--status-success-bg); color: var(--status-success); margin-left: 4px; text-transform: uppercase;">NEW</span>
21
+ <% end %>
22
+ </div>
23
+ <div style="color: var(--text-tertiary); font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 400px;" title="<%= error.message %>"><%= error.message %></div>
24
+ </div>
25
+ </div>
19
26
  </td>
20
- <td onclick="window.location='<%= error_path(error) %>';">
21
- <code class="text-danger" data-bs-toggle="tooltip" title="<%= error.error_type %>"><%= error.error_type.split('::').last %></code>
22
- <% if error.recent? %>
23
- <span class="badge bg-success ms-1" data-bs-toggle="tooltip" title="Error occurred within the last hour">NEW</span>
24
- <% end %>
25
- <% if error.respond_to?(:app_version) && error.app_version.present? %>
26
- <br><small class="badge bg-light text-dark" data-bs-toggle="tooltip" title="App version when error occurred">v<%= error.app_version %></small>
27
+ <td style="padding: var(--space-3) var(--space-4);" onclick="window.location='<%= error_path(error, **app_context) %>'">
28
+ <%
29
+ # Use the model's status field if it has a meaningful workflow status
30
+ raw_status = error.respond_to?(:status) ? error.status : nil
31
+ status_text = if error.respond_to?(:muted?) && error.muted?
32
+ 'muted'
33
+ elsif error.respond_to?(:snoozed?) && error.snoozed?
34
+ 'snoozed'
35
+ elsif raw_status.present? && raw_status != 'new'
36
+ raw_status.tr('_', ' ')
37
+ elsif error.resolved?
38
+ 'resolved'
39
+ else
40
+ 'unresolved'
41
+ end
42
+ status_colors = {
43
+ 'unresolved' => { bg: 'var(--status-critical-bg)', color: 'var(--status-critical)' },
44
+ 'resolved' => { bg: 'var(--status-success-bg)', color: 'var(--status-success)' },
45
+ 'in progress' => { bg: 'var(--status-info-bg)', color: 'var(--status-info)' },
46
+ 'investigating' => { bg: 'var(--status-caution-bg)', color: 'var(--status-caution)' },
47
+ 'wont fix' => { bg: 'var(--surface-tertiary)', color: 'var(--text-tertiary)' },
48
+ 'assigned' => { bg: 'var(--accent-subtle)', color: 'var(--accent)' },
49
+ 'snoozed' => { bg: 'var(--status-caution-bg)', color: 'var(--status-caution)' },
50
+ 'muted' => { bg: 'var(--surface-tertiary)', color: 'var(--text-tertiary)' },
51
+ }
52
+ sc = status_colors[status_text] || status_colors['unresolved']
53
+ %>
54
+ <span style="display: inline-flex; align-items: center; gap: 4px; padding: 3px 10px; font-size: 11px; font-weight: 600; border-radius: var(--radius-full); background: <%= sc[:bg] %>; color: <%= sc[:color] %>; text-transform: capitalize;">
55
+ <%= status_text %>
56
+ </span>
57
+ <% if error.reopened? %>
58
+ <i class="bi bi-arrow-counterclockwise" style="color: var(--status-warning); margin-left: 4px; font-size: 12px;" title="Reopened"></i>
27
59
  <% end %>
28
60
  </td>
29
- <td onclick="window.location='<%= error_path(error) %>';">
30
- <div class="text-truncate" style="max-width: 300px;" title="<%= error.message %>">
31
- <%= error.message %>
32
- </div>
61
+ <td style="padding: var(--space-3) var(--space-4); text-align: right; font-weight: 600; font-variant-numeric: tabular-nums; color: var(--text-primary);" onclick="window.location='<%= error_path(error, **app_context) %>'">
62
+ <%= error.occurrence_count.to_s(:delimited) rescue error.occurrence_count %>
33
63
  </td>
34
- <td onclick="window.location='<%= error_path(error) %>';">
35
- <span class="badge bg-primary"><%= error.occurrence_count %>x</span>
64
+ <td style="padding: var(--space-3) var(--space-4); text-align: right; font-variant-numeric: tabular-nums; color: var(--text-secondary);" onclick="window.location='<%= error_path(error, **app_context) %>'">
65
+ <% if error.user_id %>
66
+ <%= error.user_id %>
67
+ <% else %>
68
+ <span style="color: var(--text-tertiary);">&mdash;</span>
69
+ <% end %>
36
70
  </td>
37
- <td onclick="window.location='<%= error_path(error) %>';">
38
- <small>
39
- <strong>First:</strong> <%= local_time(error.first_seen_at, format: :short) %><br>
40
- <strong>Last:</strong> <%= local_time(error.last_seen_at, format: :short) %>
41
- </small>
71
+ <td style="padding: var(--space-3) var(--space-4); text-align: right; color: var(--text-tertiary); font-size: 12px;" onclick="window.location='<%= error_path(error, **app_context) %>'">
72
+ <%= local_time_ago(error.last_seen_at) %>
42
73
  </td>
43
74
  <% if local_assigns[:show_application] %>
44
- <td onclick="window.location='<%= error_path(error) %>';">
45
- <span class="badge bg-info">
46
- <%= error.application&.name || 'Unknown' %>
47
- </span>
75
+ <td style="padding: var(--space-3) var(--space-4); font-size: 12px;" onclick="window.location='<%= error_path(error, **app_context) %>'">
76
+ <span class="badge bg-info"><%= error.application&.name || 'Unknown' %></span>
48
77
  </td>
49
78
  <% end %>
50
79
  <% if local_assigns[:show_platform] %>
51
- <td onclick="window.location='<%= error_path(error) %>';">
80
+ <td style="padding: var(--space-3) var(--space-4); font-size: 12px;" onclick="window.location='<%= error_path(error, **app_context) %>'">
52
81
  <% if error.platform == 'iOS' %>
53
82
  <span class="badge badge-ios"><i class="bi bi-apple"></i> iOS</span>
54
83
  <% elsif error.platform == 'Android' %>
@@ -60,43 +89,4 @@
60
89
  <% end %>
61
90
  </td>
62
91
  <% end %>
63
- <td onclick="window.location='<%= error_path(error) %>';">
64
- <% if error.user_id %>
65
- <small data-bs-toggle="tooltip" title="User affected by this error">User #<%= error.user_id %></small>
66
- <% if error.respond_to?(:user_impact_percentage) %>
67
- <% impact = error.user_impact_percentage %>
68
- <% if impact > 0 %>
69
- <br><small class="badge bg-warning text-dark" data-bs-toggle="tooltip" title="Percentage of users affected by this error type"><%= impact %>% impact</small>
70
- <% end %>
71
- <% end %>
72
- <% else %>
73
- <small class="text-muted" data-bs-toggle="tooltip" title="No specific user affected">-</small>
74
- <% end %>
75
- </td>
76
- <td onclick="window.location='<%= error_path(error) %>';">
77
- <% if error.resolved? %>
78
- <i class="bi bi-check-circle-fill text-success"
79
- data-bs-toggle="tooltip"
80
- data-bs-placement="left"
81
- data-bs-html="true"
82
- title="<%= if error.resolution_comment.present?
83
- "<strong>Resolved</strong><br><em>#{error.resolution_comment.truncate(100).gsub('"', '&quot;').gsub("'", '&#39;')}</em>"
84
- else
85
- 'Resolved'
86
- end %>"></i>
87
- <% else %>
88
- <i class="bi bi-exclamation-circle-fill text-danger" data-bs-toggle="tooltip" title="Unresolved"></i>
89
- <% end %>
90
- <% if error.reopened? %>
91
- <i class="bi bi-arrow-counterclockwise text-warning ms-1" data-bs-toggle="tooltip" title="Reopened"></i>
92
- <% end %>
93
- <% if error.respond_to?(:muted?) && error.muted? %>
94
- <i class="bi bi-bell-slash text-secondary ms-1" data-bs-toggle="tooltip" title="Muted - notifications silenced"></i>
95
- <% end %>
96
- </td>
97
- <td onclick="event.stopPropagation();">
98
- <%= link_to error_path(error), class: "btn btn-sm btn-outline-primary" do %>
99
- <i class="bi bi-eye"></i>
100
- <% end %>
101
- </td>
102
92
  </tr>
@@ -4,7 +4,7 @@
4
4
  <% self_class = instance_vars.delete("_self_class") %>
5
5
  <% if instance_vars.any? %>
6
6
  <div class="card mb-4" id="section-instance-variables">
7
- <div class="card-header bg-white">
7
+ <div class="card-header">
8
8
  <h5 class="mb-0">
9
9
  <i class="bi bi-box"></i> Instance Variables
10
10
  <span class="badge bg-secondary"><%= instance_vars.size %> captured</span>
@@ -1,6 +1,6 @@
1
1
  <% if RailsErrorDashboard.configuration.enable_issue_tracking %>
2
2
  <div class="card mb-4" id="issue-tracking">
3
- <div class="card-header bg-white d-flex justify-content-between align-items-center">
3
+ <div class="card-header d-flex justify-content-between align-items-center">
4
4
  <h5 class="mb-0">
5
5
  <i class="bi bi-link-45deg me-2"></i>
6
6
  Issue Tracker
@@ -3,7 +3,7 @@
3
3
  <% local_vars = JSON.parse(error.local_variables) rescue {} %>
4
4
  <% if local_vars.any? %>
5
5
  <div class="card mb-4" id="section-local-variables">
6
- <div class="card-header bg-white">
6
+ <div class="card-header">
7
7
  <h5 class="mb-0">
8
8
  <i class="bi bi-braces"></i> Local Variables
9
9
  <span class="badge bg-secondary"><%= local_vars.size %> captured</span>
@@ -7,7 +7,7 @@
7
7
 
8
8
  <% if pattern_data.present? && pattern_data[:total_errors] > 0 %>
9
9
  <div class="card mb-4" id="section-occurrence-patterns">
10
- <div class="card-header bg-white">
10
+ <div class="card-header">
11
11
  <h5 class="mb-0">
12
12
  <i class="bi bi-graph-up"></i> Occurrence Patterns
13
13
  <small class="text-muted">(Last 30 days)</small>
@@ -128,7 +128,7 @@
128
128
  <!-- Error Bursts -->
129
129
  <% if bursts.present? && bursts.any? %>
130
130
  <div class="card mb-4">
131
- <div class="card-header bg-white">
131
+ <div class="card-header">
132
132
  <h5 class="mb-0">
133
133
  <i class="bi bi-lightning-charge-fill text-warning"></i> Error Bursts
134
134
  <span class="badge bg-warning text-dark"><%= bursts.count %></span>
@@ -1,7 +1,7 @@
1
1
  <!-- Request Context -->
2
2
  <% cache [error, 'request_context_v3'] do %>
3
3
  <div class="card mb-4" id="section-request-context">
4
- <div class="card-header bg-white">
4
+ <div class="card-header">
5
5
  <h5 class="mb-0"><i class="bi bi-globe"></i> Request Context</h5>
6
6
  </div>
7
7
  <div class="card-body">
@@ -1,6 +1,6 @@
1
1
  <!-- Metadata Card -->
2
2
  <div class="card mb-4">
3
- <div class="card-header bg-white">
3
+ <div class="card-header">
4
4
  <h5 class="mb-0"><i class="bi bi-info-circle"></i> Metadata</h5>
5
5
  </div>
6
6
  <div class="card-body">
@@ -4,7 +4,7 @@
4
4
  <% if similar.any? %>
5
5
  <% cache [error, 'similar_errors_v1', similar.map { |s| s[:error]&.updated_at }.compact.max] do %>
6
6
  <div class="card mb-4" id="section-similar-errors">
7
- <div class="card-header bg-white">
7
+ <div class="card-header">
8
8
  <h5 class="mb-0">
9
9
  <i class="bi bi-diagram-3"></i> Similar Errors
10
10
  <span class="badge bg-info text-dark">Fuzzy Matching</span>
@@ -1,7 +1,7 @@
1
1
  <%# Timeline view for related errors leading up to this error %>
2
2
  <% if @related_errors.any? %>
3
3
  <div class="card" id="section-timeline">
4
- <div class="card-header bg-white">
4
+ <div class="card-header">
5
5
  <h5 class="mb-0">
6
6
  <i class="bi bi-clock-history"></i> Timeline
7
7
  <small class="text-muted ms-2">Recent related errors</small>
@@ -1,8 +1,8 @@
1
1
  <% content_for :page_title, "ActionCable Health" %>
2
2
 
3
- <div class="container-fluid py-4">
3
+ <div>
4
4
  <div class="d-flex justify-content-between align-items-center mb-4">
5
- <h1 class="h3 mb-0">
5
+ <h1 style="font-size: 20px; font-weight: 700; margin: 0;">
6
6
  <i class="bi bi-broadcast me-2"></i>
7
7
  ActionCable Health
8
8
  </h1>
@@ -21,9 +21,9 @@
21
21
  </div>
22
22
 
23
23
  <% if @unique_channels == 0 %>
24
- <div class="text-center py-5">
24
+ <div class="red-empty-state">
25
25
  <i class="bi bi-broadcast display-1 text-success mb-3"></i>
26
- <h4 class="text-muted">No ActionCable Events Found</h4>
26
+ <div class="red-empty-state-title">No ActionCable Events Found</div>
27
27
  <p class="text-muted">
28
28
  No ActionCable channel actions, transmissions, or subscription events were detected in error breadcrumbs over the last <%= @days %> days.
29
29
  </p>
@@ -68,7 +68,7 @@
68
68
  </div>
69
69
 
70
70
  <div class="card mb-4">
71
- <div class="card-header bg-white d-flex justify-content-between align-items-center">
71
+ <div class="card-header d-flex justify-content-between align-items-center">
72
72
  <h5 class="mb-0">
73
73
  <i class="bi bi-broadcast text-primary me-2"></i>
74
74
  ActionCable Events by Channel
@@ -112,7 +112,7 @@
112
112
  </table>
113
113
  </div>
114
114
  </div>
115
- <div class="card-footer bg-white border-top d-flex justify-content-between align-items-center">
115
+ <div class="card-footer border-top d-flex justify-content-between align-items-center">
116
116
  <div>
117
117
  <small class="text-muted">
118
118
  <i class="bi bi-lightbulb text-warning"></i> ActionCable events are captured when they coincide with errors. High rejection counts may indicate authentication or authorization issues.
@@ -1,8 +1,8 @@
1
1
  <% content_for :page_title, "ActiveStorage Health" %>
2
2
 
3
- <div class="container-fluid py-4">
3
+ <div>
4
4
  <div class="d-flex justify-content-between align-items-center mb-4">
5
- <h1 class="h3 mb-0">
5
+ <h1 style="font-size: 20px; font-weight: 700; margin: 0;">
6
6
  <i class="bi bi-cloud-arrow-up me-2"></i>
7
7
  ActiveStorage Health
8
8
  </h1>
@@ -21,9 +21,9 @@
21
21
  </div>
22
22
 
23
23
  <% if @unique_services == 0 %>
24
- <div class="text-center py-5">
24
+ <div class="red-empty-state">
25
25
  <i class="bi bi-cloud-arrow-up display-1 text-success mb-3"></i>
26
- <h4 class="text-muted">No ActiveStorage Events Found</h4>
26
+ <div class="red-empty-state-title">No ActiveStorage Events Found</div>
27
27
  <p class="text-muted">
28
28
  No ActiveStorage service operations were detected in error breadcrumbs over the last <%= @days %> days.
29
29
  </p>
@@ -68,7 +68,7 @@
68
68
  </div>
69
69
 
70
70
  <div class="card mb-4">
71
- <div class="card-header bg-white d-flex justify-content-between align-items-center">
71
+ <div class="card-header d-flex justify-content-between align-items-center">
72
72
  <h5 class="mb-0">
73
73
  <i class="bi bi-cloud-arrow-up text-primary me-2"></i>
74
74
  Storage Operations by Service
@@ -128,7 +128,7 @@
128
128
  </table>
129
129
  </div>
130
130
  </div>
131
- <div class="card-footer bg-white border-top d-flex justify-content-between align-items-center">
131
+ <div class="card-footer border-top d-flex justify-content-between align-items-center">
132
132
  <div>
133
133
  <small class="text-muted">
134
134
  <i class="bi bi-lightbulb text-warning"></i> Slow storage operations may indicate network issues, large file sizes, or service provider throttling.
@@ -3,7 +3,7 @@
3
3
  <script>
4
4
  // Dynamic chart colors based on theme
5
5
  window.getChartColors = function() {
6
- const isDark = document.body.classList.contains('dark-mode');
6
+ const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
7
7
  return {
8
8
  textColor: isDark ? '#cdd6f4' : '#1f2937',
9
9
  gridColor: isDark ? 'rgba(88, 91, 112, 0.2)' : 'rgba(0, 0, 0, 0.1)'
@@ -32,53 +32,37 @@
32
32
  };
33
33
  </script>
34
34
 
35
- <div class="py-4">
36
- <div class="d-flex justify-content-between align-items-center mb-4">
37
- <h2 class="mb-0"><i class="bi bi-graph-up text-primary"></i> Error Analytics</h2>
38
- <div>
39
- <%= form_with url: analytics_errors_path, method: :get, class: "d-flex gap-2" do %>
40
- <%= select_tag :days, options_for_select([
41
- ['Last 7 Days', 7],
42
- ['Last 14 Days', 14],
43
- ['Last 30 Days', 30],
44
- ['Last 60 Days', 60],
45
- ['Last 90 Days', 90]
46
- ], @days), class: "form-select", onchange: "this.form.submit()" %>
47
- <% end %>
48
- </div>
35
+ <div>
36
+ <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--space-4);">
37
+ <h1 style="font-size: 20px; font-weight: 700; margin: 0;">Analytics</h1>
38
+ </div>
39
+
40
+ <!-- Time range pills -->
41
+ <div style="display: flex; gap: 6px; margin-bottom: var(--space-6);">
42
+ <% [['7d', 7], ['14d', 14], ['30d', 30], ['60d', 60], ['90d', 90]].each do |label, days| %>
43
+ <%= link_to analytics_errors_path(days: days), class: "filter-pill #{@days == days ? 'active' : ''}", style: "text-decoration: none;" do %><%= label %><% end %>
44
+ <% end %>
49
45
  </div>
50
46
 
51
47
  <!-- Summary Stats -->
52
- <div class="row g-4 mb-4">
53
- <div class="col-md-4">
54
- <div class="card stat-card border-primary">
55
- <div class="card-body">
56
- <div class="stat-label mb-2 text-primary">Total Errors</div>
57
- <div class="stat-value text-primary"><%= @error_stats[:total] %></div>
58
- </div>
59
- </div>
48
+ <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-4); margin-bottom: var(--space-6);">
49
+ <div class="card stat-card" style="border-left: 3px solid var(--accent); padding: var(--space-5) var(--space-6);">
50
+ <div class="stat-label" style="margin-bottom: 4px;">Total Errors</div>
51
+ <span class="stat-value"><%= @error_stats[:total] %></span>
60
52
  </div>
61
- <div class="col-md-4">
62
- <div class="card stat-card border-danger">
63
- <div class="card-body">
64
- <div class="stat-label mb-2 text-danger">Unresolved</div>
65
- <div class="stat-value text-danger"><%= @error_stats[:unresolved] %></div>
66
- </div>
67
- </div>
53
+ <div class="card stat-card" style="border-left: 3px solid var(--status-critical); padding: var(--space-5) var(--space-6);">
54
+ <div class="stat-label" style="margin-bottom: 4px;">Unresolved</div>
55
+ <span class="stat-value" style="color: var(--status-critical);"><%= @error_stats[:unresolved] %></span>
68
56
  </div>
69
- <div class="col-md-4">
70
- <div class="card stat-card border-success">
71
- <div class="card-body">
72
- <div class="stat-label mb-2 text-success">Resolution Rate</div>
73
- <div class="stat-value text-success"><%= @resolution_rate %>%</div>
74
- </div>
75
- </div>
57
+ <div class="card stat-card" style="border-left: 3px solid var(--status-success); padding: var(--space-5) var(--space-6);">
58
+ <div class="stat-label" style="margin-bottom: 4px;">Resolution Rate</div>
59
+ <span class="stat-value" style="color: var(--status-success);"><%= @resolution_rate %>%</span>
76
60
  </div>
77
61
  </div>
78
62
 
79
63
  <!-- Error Trend Chart -->
80
64
  <div class="card mb-4">
81
- <div class="card-header bg-white">
65
+ <div class="card-header">
82
66
  <h5 class="mb-0"><i class="bi bi-graph-up"></i> Error Trend (Last <%= @days %> Days)</h5>
83
67
  </div>
84
68
  <div class="card-body">
@@ -87,7 +71,7 @@
87
71
  document.addEventListener('DOMContentLoaded', function() {
88
72
  const colors = window.getChartColors();
89
73
  new Chartkick.LineChart("errors-over-time-chart", <%= raw @errors_over_time.to_json %>, {
90
- color: "#8B5CF6",
74
+ color: "#DC2626",
91
75
  curve: false,
92
76
  points: true,
93
77
  height: "300px",
@@ -123,7 +107,7 @@
123
107
  <div class="row g-4 mb-4">
124
108
  <div class="col-md-12">
125
109
  <div class="card">
126
- <div class="card-header bg-white">
110
+ <div class="card-header">
127
111
  <h5 class="mb-0"><i class="bi bi-phone"></i> Errors by Platform</h5>
128
112
  </div>
129
113
  <div class="card-body">
@@ -160,7 +144,7 @@
160
144
  });
161
145
  </script>
162
146
  </div>
163
- <div class="card-footer bg-white border-top-0">
147
+ <div class="card-footer border-top-0">
164
148
  <div class="d-flex gap-2 flex-wrap">
165
149
  <small class="text-muted me-2">Quick Links:</small>
166
150
  <% @errors_by_platform.keys.each do |platform| %>
@@ -177,7 +161,7 @@
177
161
  <div class="row g-4 mb-4">
178
162
  <div class="col-md-12">
179
163
  <div class="card">
180
- <div class="card-header bg-white">
164
+ <div class="card-header">
181
165
  <h5 class="mb-0"><i class="bi bi-bug"></i> Top 10 Error Types</h5>
182
166
  </div>
183
167
  <div class="card-body">
@@ -219,7 +203,7 @@
219
203
  <div class="row g-4 mb-4">
220
204
  <div class="col-md-12">
221
205
  <div class="card">
222
- <div class="card-header bg-white">
206
+ <div class="card-header">
223
207
  <h5 class="mb-0"><i class="bi bi-clock"></i> Errors by Hour of Day</h5>
224
208
  </div>
225
209
  <div class="card-body">
@@ -228,7 +212,7 @@
228
212
  document.addEventListener('DOMContentLoaded', function() {
229
213
  const colors = window.getChartColors();
230
214
  new Chartkick.ColumnChart("errors-by-hour-chart", <%= raw @errors_by_hour.to_json %>, {
231
- color: "#8B5CF6",
215
+ color: "#DC2626",
232
216
  height: "300px",
233
217
  xtitle: "Hour",
234
218
  ytitle: "Number of Errors",
@@ -262,7 +246,7 @@
262
246
  <div class="row g-4 mb-4">
263
247
  <div class="col-md-12">
264
248
  <div class="card">
265
- <div class="card-header bg-white">
249
+ <div class="card-header">
266
250
  <h5 class="mb-0"><i class="bi bi-people"></i> Top 10 Affected Users</h5>
267
251
  </div>
268
252
  <div class="card-body p-0">
@@ -285,7 +269,7 @@
285
269
  <div class="row g-4">
286
270
  <div class="col-md-12">
287
271
  <div class="card">
288
- <div class="card-header bg-white d-flex justify-content-between align-items-center">
272
+ <div class="card-header d-flex justify-content-between align-items-center">
289
273
  <h5 class="mb-0"><i class="bi bi-list-check"></i> Error Type Breakdown</h5>
290
274
  <small class="text-muted">Last <%= @days %> days</small>
291
275
  </div>
@@ -334,7 +318,7 @@
334
318
 
335
319
  <!-- Recurring Issues Analysis -->
336
320
  <div class="card mb-4">
337
- <div class="card-header bg-white">
321
+ <div class="card-header">
338
322
  <h5 class="mb-0"><i class="bi bi-arrow-repeat"></i> Recurring Issues</h5>
339
323
  </div>
340
324
  <div class="card-body">
@@ -421,7 +405,7 @@
421
405
  <!-- Errors by Version Chart -->
422
406
  <div class="col-md-8">
423
407
  <div class="card">
424
- <div class="card-header bg-white">
408
+ <div class="card-header">
425
409
  <h5 class="mb-0"><i class="bi bi-git"></i> Errors by Release Version</h5>
426
410
  </div>
427
411
  <div class="card-body">
@@ -447,7 +431,7 @@
447
431
  <!-- Release Comparison Card -->
448
432
  <div class="col-md-4">
449
433
  <div class="card">
450
- <div class="card-header bg-white">
434
+ <div class="card-header">
451
435
  <h5 class="mb-0">Latest vs Previous</h5>
452
436
  </div>
453
437
  <div class="card-body">
@@ -510,7 +494,7 @@
510
494
  <!-- MTTR (Mean Time to Resolution) -->
511
495
  <% if @mttr_stats[:total_resolved] > 0 %>
512
496
  <div class="card mb-4">
513
- <div class="card-header bg-white">
497
+ <div class="card-header">
514
498
  <h5 class="mb-0"><i class="bi bi-clock-history"></i> Resolution Performance (MTTR)</h5>
515
499
  </div>
516
500
  <div class="card-body">