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
@@ -2,245 +2,221 @@
2
2
 
3
3
  <%= render "show_scripts", error: @error %>
4
4
 
5
- <div class="py-4" data-controller="loading">
6
- <!-- Breadcrumbs -->
7
- <nav aria-label="breadcrumb" class="mb-3">
8
- <ol class="breadcrumb">
9
- <li class="breadcrumb-item"><%= link_to root_path do %><i class="bi bi-house-door"></i> Dashboard<% end %></li>
10
- <li class="breadcrumb-item"><%= link_to errors_path do %><i class="bi bi-bug"></i> Errors<% end %></li>
11
- <li class="breadcrumb-item active" aria-current="page">Error #<%= @error.id %></li>
12
- </ol>
13
- </nav>
5
+ <div data-controller="loading">
6
+ <!-- Back + breadcrumb -->
7
+ <div style="display: flex; align-items: center; gap: 8px; margin-bottom: var(--space-4); font-size: 13px; color: var(--text-tertiary);">
8
+ <%= link_to errors_path(app_context), style: "background: none; border: none; color: var(--text-tertiary); display: flex; align-items: center; gap: 4px; text-decoration: none; font-size: 13px;" do %>
9
+ <i class="bi bi-arrow-left"></i> Errors
10
+ <% end %>
11
+ <span>/</span>
12
+ <span style="color: var(--text-secondary);"><%= @error.error_type %></span>
13
+ </div>
14
14
 
15
- <div class="d-flex justify-content-between align-items-center mb-4">
16
- <div>
17
- <h2 class="mb-0">
18
- Error Details
19
- <% severity = @error.severity %>
20
- <% if severity == :critical %>
21
- <span class="badge bg-danger fs-5">CRITICAL</span>
22
- <% elsif severity == :high %>
23
- <span class="badge bg-warning text-dark fs-5">HIGH</span>
24
- <% elsif severity == :medium %>
25
- <span class="badge bg-info text-dark fs-5">MEDIUM</span>
26
- <% else %>
27
- <span class="badge bg-secondary fs-5">LOW</span>
15
+ <!-- Hero card -->
16
+ <div style="display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-4); padding: var(--space-6); background: var(--surface-primary); border-radius: var(--radius-md); border: 1px solid var(--border-primary); margin-bottom: var(--space-4);">
17
+ <div style="flex: 1; min-width: 0;">
18
+ <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 6px;">
19
+ <span class="badge bg-<%= severity_color(@error.severity) %>" style="display: inline-flex; align-items: center; gap: 4px;">
20
+ <span style="width: 6px; height: 6px; border-radius: 50%; background: currentColor;"></span>
21
+ <%= @error.severity %>
22
+ </span>
23
+ <%
24
+ raw_status = @error.respond_to?(:status) ? @error.status : nil
25
+ status_text = if @error.respond_to?(:muted?) && @error.muted?
26
+ 'muted'
27
+ elsif @error.respond_to?(:snoozed?) && @error.snoozed?
28
+ 'snoozed'
29
+ elsif raw_status.present? && raw_status != 'new'
30
+ raw_status.tr('_', ' ')
31
+ elsif @error.resolved?
32
+ 'resolved'
33
+ else
34
+ 'unresolved'
35
+ end
36
+ status_colors = {
37
+ 'unresolved' => { bg: 'var(--status-critical-bg)', color: 'var(--status-critical)' },
38
+ 'resolved' => { bg: 'var(--status-success-bg)', color: 'var(--status-success)' },
39
+ 'in progress' => { bg: 'var(--status-info-bg)', color: 'var(--status-info)' },
40
+ 'investigating' => { bg: 'var(--status-caution-bg)', color: 'var(--status-caution)' },
41
+ 'wont fix' => { bg: 'var(--surface-tertiary)', color: 'var(--text-tertiary)' },
42
+ 'assigned' => { bg: 'var(--accent-subtle)', color: 'var(--accent)' },
43
+ 'snoozed' => { bg: 'var(--status-caution-bg)', color: 'var(--status-caution)' },
44
+ 'muted' => { bg: 'var(--surface-tertiary)', color: 'var(--text-tertiary)' },
45
+ }
46
+ sc = status_colors[status_text] || status_colors['unresolved']
47
+ %>
48
+ <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;">
49
+ <%= status_text %>
50
+ </span>
51
+ <% if @error.reopened? %>
52
+ <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: var(--status-warning-bg); color: var(--status-warning);">
53
+ <i class="bi bi-arrow-counterclockwise" style="font-size: 10px;"></i> Reopened
54
+ </span>
55
+ <% end %>
56
+ </div>
57
+ <h2 style="font-size: 20px; font-weight: 700; color: var(--text-primary); font-family: var(--font-mono); margin: 0 0 4px; word-break: break-word;"><%= @error.error_type %></h2>
58
+ <p style="font-size: 14px; color: var(--text-secondary); margin: 0; font-family: var(--font-mono);"><%= @error.message&.truncate(200) %></p>
59
+ <div style="display: flex; gap: var(--space-6); margin-top: var(--space-4); font-size: 12px; color: var(--text-tertiary); flex-wrap: wrap;">
60
+ <span><strong style="color: var(--text-primary);"><%= @error.occurrence_count %></strong> occurrences</span>
61
+ <% if @error.application&.name.present? %>
62
+ <span><i class="bi bi-layers" style="font-size: 10px;"></i> <strong style="color: var(--text-primary);"><%= @error.application.name %></strong></span>
28
63
  <% end %>
29
- </h2>
64
+ <% if @error.user_id %>
65
+ <span>User <strong style="color: var(--text-primary);">#<%= @error.user_id %></strong></span>
66
+ <% end %>
67
+ <span>First seen <%= local_time_ago(@error.first_seen_at) %></span>
68
+ <span>Last seen <%= local_time_ago(@error.last_seen_at) %></span>
69
+ </div>
30
70
  </div>
31
- <div class="d-flex gap-2 align-items-center">
32
- <button type="button" class="btn btn-outline-secondary" onclick="downloadErrorJSON(event)" title="Download error details as JSON">
33
- <i class="bi bi-download"></i> Export JSON
34
- </button>
35
- <button type="button" class="btn btn-outline-secondary" onclick="copyToClipboard(this.dataset.markdown.replace(/\\(.)/g, function(m,c){return c==='n'?'\n':c}), this)" data-markdown="<%= j @error_markdown %>" title="Copy error details as Markdown for LLM debugging">
36
- <i class="bi bi-clipboard"></i> Copy for LLM
37
- </button>
38
- <% if @error.respond_to?(:muted?) && @error.muted? %>
39
- <button type="button" class="btn btn-secondary" disabled>
40
- <i class="bi bi-bell-slash"></i> Muted
71
+ <div style="display: flex; gap: 6px; flex-shrink: 0;">
72
+ <% unless @error.resolved? %>
73
+ <button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#resolveModal" style="font-weight: 600;">
74
+ Resolve
41
75
  </button>
42
76
  <% end %>
43
- <% unless RailsErrorDashboard.configuration.enable_issue_tracking %>
44
- <% if @error.resolved? %>
45
- <span class="badge bg-success fs-6">
46
- <i class="bi bi-check-circle"></i> Resolved
47
- </span>
48
- <% else %>
49
- <button type="button" class="btn btn-success" data-bs-toggle="modal" data-bs-target="#resolveModal">
50
- <i class="bi bi-check-circle"></i> Mark as Resolved
51
- </button>
52
- <% end %>
53
- <% end %>
77
+ <button type="button" class="btn" data-bs-toggle="modal" data-bs-target="#assignModal" id="hero-assign-btn">
78
+ Assign
79
+ </button>
80
+ <button type="button" class="btn" onclick="copyToClipboard(this.dataset.markdown.replace(/\\(.)/g, function(m,c){return c==='n'?'\n':c}), this)" data-markdown="<%= j @error_markdown %>">
81
+ <i class="bi bi-clipboard" style="margin-right: 4px;"></i>Copy for LLM
82
+ </button>
83
+ <button type="button" class="btn" onclick="downloadErrorJSON(event)" title="Export JSON">
84
+ <i class="bi bi-download"></i>
85
+ </button>
54
86
  </div>
55
87
  </div>
56
88
 
57
- <!-- Section Navigation -->
58
- <div id="section-nav-wrapper">
59
- <div id="section-nav" class="d-flex align-items-center gap-2 py-2 mb-0">
60
- <span class="section-nav-label text-muted me-1"><i class="bi bi-compass"></i></span>
61
- <div class="section-nav-scroll d-flex gap-1 overflow-auto flex-nowrap">
62
- <%# Pills are generated by JavaScript based on which sections exist in the DOM %>
89
+ <!-- Main content: tabs + sidebar -->
90
+ <div style="display: grid; grid-template-columns: 1fr 280px; gap: var(--space-4);">
91
+ <!-- Left: tabbed content -->
92
+ <div>
93
+ <!-- Tab bar -->
94
+ <div style="display: flex; gap: 0; border-bottom: 1px solid var(--border-primary); margin-bottom: var(--space-4);">
95
+ <button onclick="switchTab('details')" class="red-tab active" id="tab-details" style="padding: 10px 16px; font-size: 13px; font-weight: 600; color: var(--accent); background: none; border: none; border-bottom: 2px solid var(--accent); margin-bottom: -1px; cursor: pointer;">
96
+ <i class="bi bi-code-slash" style="margin-right: 6px;"></i>Details
97
+ </button>
98
+ <button onclick="switchTab('context')" class="red-tab" id="tab-context" style="padding: 10px 16px; font-size: 13px; font-weight: 400; color: var(--text-secondary); background: none; border: none; border-bottom: 2px solid transparent; margin-bottom: -1px; cursor: pointer;">
99
+ <i class="bi bi-layers" style="margin-right: 6px;"></i>Context
100
+ </button>
101
+ <button onclick="switchTab('history')" class="red-tab" id="tab-history" style="padding: 10px 16px; font-size: 13px; font-weight: 400; color: var(--text-secondary); background: none; border: none; border-bottom: 2px solid transparent; margin-bottom: -1px; cursor: pointer;">
102
+ <i class="bi bi-clock-history" style="margin-right: 6px;"></i>History
103
+ </button>
104
+ <% if RailsErrorDashboard.configuration.enable_issue_tracking %>
105
+ <button onclick="switchTab('issues')" class="red-tab" id="tab-issues" style="padding: 10px 16px; font-size: 13px; font-weight: 400; color: var(--text-secondary); background: none; border: none; border-bottom: 2px solid transparent; margin-bottom: -1px; cursor: pointer;">
106
+ <i class="bi bi-github" style="margin-right: 6px;"></i>Issues
107
+ </button>
108
+ <% end %>
63
109
  </div>
64
- </div>
65
- </div>
66
110
 
67
- <div class="row g-4">
68
- <!-- Error Information -->
69
- <div class="col-md-8">
70
- <% if RailsErrorDashboard.configuration.enable_coverage_tracking && RailsErrorDashboard.configuration.enable_source_code_integration %>
71
- <div class="alert mb-3 py-2 d-flex justify-content-between align-items-center <%= RailsErrorDashboard::Services::CoverageTracker.active? ? 'alert-info' : 'alert-light border' %>">
72
- <div>
73
- <% if RailsErrorDashboard::Services::CoverageTracker.active? %>
74
- <i class="bi bi-broadcast text-info"></i>
75
- <strong>Coverage Active</strong> — expand source code to see executed lines
76
- <% else %>
77
- <i class="bi bi-code-square"></i>
78
- <strong>Code Path Coverage</strong> — enable to see which lines were executed in production
79
- <% end %>
80
- </div>
81
- <div>
82
- <% if RailsErrorDashboard::Services::CoverageTracker.active? %>
83
- <%= button_to "Disable Coverage", disable_coverage_errors_path, method: :post, class: "btn btn-sm btn-outline-secondary" %>
84
- <% else %>
85
- <%= button_to "Enable Coverage", enable_coverage_errors_path, method: :post, class: "btn btn-sm btn-info" %>
86
- <% end %>
111
+ <!-- Tab: Details -->
112
+ <div id="panel-details">
113
+ <% if RailsErrorDashboard.configuration.enable_coverage_tracking && RailsErrorDashboard.configuration.enable_source_code_integration %>
114
+ <div class="alert alert-info mb-3" style="display: flex; justify-content: space-between; align-items: center; padding: var(--space-3) var(--space-4);">
115
+ <div style="font-size: 12px;">
116
+ <% if RailsErrorDashboard::Services::CoverageTracker.active? %>
117
+ <i class="bi bi-broadcast"></i> <strong>Coverage Active</strong> — expand source code to see executed lines
118
+ <% else %>
119
+ <i class="bi bi-code-square"></i> <strong>Code Path Coverage</strong> — enable to see executed lines
120
+ <% end %>
121
+ </div>
122
+ <div>
123
+ <% if RailsErrorDashboard::Services::CoverageTracker.active? %>
124
+ <%= button_to "Disable", disable_coverage_errors_path, method: :post, class: "btn btn-sm" %>
125
+ <% else %>
126
+ <%= button_to "Enable", enable_coverage_errors_path, method: :post, class: "btn btn-sm btn-primary" %>
127
+ <% end %>
128
+ </div>
87
129
  </div>
88
- </div>
89
- <% end %>
90
-
91
- <%= render "error_info", error: @error %>
92
-
93
- <%= render "local_variables", error: @error %>
94
-
95
- <%= render "instance_variables", error: @error %>
96
-
97
- <%= render "request_context", error: @error %>
98
-
99
- <%= render "breadcrumbs_group", error: @error %>
100
-
101
- <%= render "similar_errors", error: @error %>
102
-
103
- <%= render "co_occurring_errors", error: @error %>
104
-
105
- <!-- Timeline: Related Errors -->
106
- <%= render "timeline" %>
130
+ <% end %>
107
131
 
108
- <%= render "issue_section", error: @error %>
132
+ <%= render "error_info", error: @error %>
133
+ <%= render "local_variables", error: @error %>
134
+ <%= render "instance_variables", error: @error %>
135
+ </div>
109
136
 
110
- <%= render "discussion", error: @error %>
137
+ <!-- Tab: Context -->
138
+ <div id="panel-context">
139
+ <%= render "request_context", error: @error %>
140
+ <%= render "breadcrumbs_group", error: @error %>
141
+ </div>
111
142
 
112
- <%= render "error_cascades", error: @error %>
143
+ <!-- Tab: History -->
144
+ <div id="panel-history">
145
+ <%= render "timeline" %>
146
+ <% unless RailsErrorDashboard.configuration.enable_issue_tracking %>
147
+ <%= render "discussion", error: @error %>
148
+ <% end %>
149
+ <%= render "similar_errors", error: @error %>
150
+ <%= render "co_occurring_errors", error: @error %>
151
+ <%= render "error_cascades", error: @error %>
152
+ <%= render "pattern_insights" %>
153
+ </div>
113
154
 
114
- <!-- Occurrence Patterns and Bursts -->
115
- <%= render "pattern_insights" %>
155
+ <!-- Tab: Issues -->
156
+ <% if RailsErrorDashboard.configuration.enable_issue_tracking %>
157
+ <div id="panel-issues">
158
+ <%= render "issue_section", error: @error %>
159
+ <%= render "discussion", error: @error %>
160
+ </div>
161
+ <% end %>
116
162
  </div>
117
163
 
118
- <!-- Sidebar -->
119
- <div class="col-md-4">
164
+ <!-- Right: sidebar -->
165
+ <div class="error-sidebar" style="display: flex; flex-direction: column; gap: var(--space-4); min-width: 0;">
120
166
  <%= render "sidebar_metadata", error: @error, related_errors: @related_errors %>
121
167
 
122
168
  <!-- Baseline Statistics -->
123
169
  <% if RailsErrorDashboard.configuration.enable_baseline_alerts %>
124
170
  <% baselines = @error.baselines %>
125
171
  <% if baselines[:hourly].present? || baselines[:daily].present? || baselines[:weekly].present? %>
126
- <div class="card mb-4">
127
- <div class="card-header bg-white">
128
- <h5 class="mb-0">
129
- <i class="bi bi-graph-up"></i> Baseline Statistics
130
- <small class="text-muted ms-2">Historical patterns</small>
131
- </h5>
132
- </div>
133
- <div class="card-body">
134
- <% anomaly = @error.baseline_anomaly %>
135
- <% if anomaly[:anomaly] %>
136
- <div class="alert alert-<%= anomaly[:level] == :critical ? 'danger' : anomaly[:level] == :high ? 'warning' : 'info' %> mb-3">
137
- <i class="bi bi-exclamation-triangle-fill"></i>
138
- <strong>Anomaly Detected!</strong><br>
139
- <small>
140
- This error is <%= anomaly[:std_devs_above]&.round(1) %>σ above baseline
141
- (<%= anomaly[:level].to_s.upcase %>)
142
- </small>
143
- </div>
144
- <% end %>
145
-
146
- <% [:hourly, :daily, :weekly].each do |type| %>
147
- <% baseline = baselines[type] %>
148
- <% next unless baseline %>
149
-
150
- <div class="mb-3">
151
- <small class="text-muted d-block mb-1 text-capitalize">
152
- <i class="bi bi-<%= type == :hourly ? 'clock' : type == :daily ? 'calendar-day' : 'calendar-week' %>"></i>
153
- <%= type.to_s.capitalize %> Baseline
154
- </small>
155
-
156
- <div class="row g-2">
157
- <div class="col-6">
158
- <div class="text-center p-2 bg-light rounded">
159
- <small class="text-muted d-block">Mean</small>
160
- <strong><%= baseline.mean&.round(1) || 'N/A' %></strong>
161
- </div>
162
- </div>
163
- <div class="col-6">
164
- <div class="text-center p-2 bg-light rounded">
165
- <small class="text-muted d-block">Std Dev</small>
166
- <strong><%= baseline.std_dev&.round(1) || 'N/A' %></strong>
167
- </div>
168
- </div>
169
- </div>
170
-
171
- <div class="row g-2 mt-2">
172
- <div class="col-6">
173
- <div class="text-center p-2 bg-light rounded">
174
- <small class="text-muted d-block">95th %</small>
175
- <strong><%= baseline.percentile_95&.round(1) || 'N/A' %></strong>
176
- </div>
177
- </div>
178
- <div class="col-6">
179
- <div class="text-center p-2 bg-light rounded">
180
- <small class="text-muted d-block">99th %</small>
181
- <strong><%= baseline.percentile_99&.round(1) || 'N/A' %></strong>
182
- </div>
183
- </div>
172
+ <div class="card">
173
+ <div style="display: flex; justify-content: space-between; align-items: center; padding: var(--space-4) var(--space-5); border-bottom: 1px solid var(--border-primary);">
174
+ <span style="font-size: 13px; font-weight: 600;">Baseline Statistics</span>
175
+ </div>
176
+ <div style="padding: var(--space-3) var(--space-5);">
177
+ <% anomaly = @error.baseline_anomaly %>
178
+ <% if anomaly[:anomaly] %>
179
+ <div class="alert alert-<%= anomaly[:level] == :critical ? 'danger' : anomaly[:level] == :high ? 'warning' : 'info' %>" style="padding: var(--space-2) var(--space-3); font-size: 12px; margin-bottom: var(--space-3);">
180
+ <strong>Anomaly:</strong> <%= anomaly[:std_devs_above]&.round(1) %>σ above baseline (<%= anomaly[:level].to_s.upcase %>)
184
181
  </div>
185
-
186
- <% if baseline.respond_to?(:mean) && baseline.mean && baseline.respond_to?(:std_dev) && baseline.std_dev %>
187
- <div class="mt-2">
188
- <small class="text-muted">
189
- Threshold (2σ): <strong><%= baseline.respond_to?(:threshold) ? baseline.threshold(sensitivity: 2)&.round(1) : 'N/A' %></strong>
190
- </small>
182
+ <% end %>
183
+ <% [:hourly, :daily, :weekly].each do |type| %>
184
+ <% baseline = baselines[type] %>
185
+ <% next unless baseline %>
186
+ <div style="padding: var(--space-2) 0; border-bottom: 1px solid var(--border-primary); font-size: 12px;">
187
+ <div style="color: var(--text-tertiary); font-weight: 500; text-transform: capitalize; margin-bottom: 4px;"><%= type %></div>
188
+ <div style="display: flex; gap: var(--space-4);">
189
+ <span>Mean: <strong><%= baseline.mean&.round(1) || 'N/A' %></strong></span>
190
+ <span>σ: <strong><%= baseline.std_dev&.round(1) || 'N/A' %></strong></span>
191
+ <span>P95: <strong><%= baseline.percentile_95&.round(1) || 'N/A' %></strong></span>
191
192
  </div>
192
- <% end %>
193
-
194
- <div class="mt-2">
195
- <small class="text-muted">
196
- Sample: <%= baseline.respond_to?(:sample_size) ? baseline.sample_size : 'N/A' %> periods
197
- <span class="text-muted">|</span>
198
- Updated: <%= baseline.respond_to?(:updated_at) ? local_time(baseline.updated_at, format: :short) : 'N/A' %>
199
- </small>
200
193
  </div>
201
- </div>
202
-
203
- <% unless baseline == baselines.values.compact.last %>
204
- <hr>
205
194
  <% end %>
206
- <% end %>
207
-
208
- <div class="mt-3">
209
- <small class="text-muted">
210
- <i class="bi bi-info-circle"></i>
211
- Baselines establish "normal" error rates. Anomalies are detected when current rates exceed baseline + 2σ.
212
- </small>
213
195
  </div>
214
196
  </div>
215
- </div>
216
197
  <% end %>
217
198
  <% end %>
218
199
 
219
200
  <!-- Quick Actions -->
220
201
  <div class="card">
221
- <div class="card-header bg-white">
222
- <h5 class="mb-0"><i class="bi bi-lightning"></i> Quick Actions</h5>
202
+ <div style="display: flex; justify-content: space-between; align-items: center; padding: var(--space-4) var(--space-5); border-bottom: 1px solid var(--border-primary);">
203
+ <span style="font-size: 13px; font-weight: 600;">Quick Actions</span>
223
204
  </div>
224
- <div class="card-body">
225
- <div class="d-grid gap-2">
226
- <%= link_to errors_path(error_type: @error.error_type), class: "btn btn-outline-primary btn-sm" do %>
227
- <i class="bi bi-filter"></i> View Similar Errors
205
+ <div style="padding: var(--space-3) var(--space-5); display: flex; flex-direction: column; gap: var(--space-2);">
206
+ <%= link_to errors_path(app_context.merge(error_type: @error.error_type)), class: "btn btn-sm" do %>
207
+ <i class="bi bi-filter"></i> View Similar Errors
208
+ <% end %>
209
+ <% if @error.user_id %>
210
+ <%= link_to errors_path(app_context.merge(user_id: @error.user_id)), class: "btn btn-sm" do %>
211
+ <i class="bi bi-person"></i> View User's Errors
228
212
  <% end %>
229
-
230
- <% if @error.user_id %>
231
- <%= link_to errors_path(user_id: @error.user_id), class: "btn btn-outline-primary btn-sm" do %>
232
- <i class="bi bi-person"></i> View User's Errors
233
- <% end %>
234
- <% end %>
235
-
236
- <%= link_to errors_path(platform: @error.platform), class: "btn btn-outline-primary btn-sm" do %>
237
- <i class="bi bi-phone"></i> View <%= @error.platform %> Errors
238
- <% end %>
239
-
240
- <%= link_to analytics_errors_path, class: "btn btn-outline-secondary btn-sm" do %>
241
- <i class="bi bi-graph-up"></i> View Analytics
242
- <% end %>
243
- </div>
213
+ <% end %>
214
+ <%= link_to errors_path(app_context.merge(platform: @error.platform)), class: "btn btn-sm" do %>
215
+ <i class="bi bi-phone"></i> View <%= @error.platform %> Errors
216
+ <% end %>
217
+ <%= link_to analytics_errors_path(app_context), class: "btn btn-sm" do %>
218
+ <i class="bi bi-bar-chart-line"></i> Analytics
219
+ <% end %>
244
220
  </div>
245
221
  </div>
246
222
  </div>
@@ -248,3 +224,24 @@
248
224
  </div>
249
225
 
250
226
  <%= render "modals", error: @error %>
227
+
228
+ <script>
229
+ function switchTab(tabId) {
230
+ // Deactivate all tabs
231
+ document.querySelectorAll('.red-tab').forEach(function(el) {
232
+ el.style.color = 'var(--text-secondary)';
233
+ el.style.fontWeight = '400';
234
+ el.style.borderBottomColor = 'transparent';
235
+ });
236
+ // Activate selected tab
237
+ var tab = document.getElementById('tab-' + tabId);
238
+ if (tab) {
239
+ tab.style.color = 'var(--accent)';
240
+ tab.style.fontWeight = '600';
241
+ tab.style.borderBottomColor = 'var(--accent)';
242
+ }
243
+ // Scroll to selected panel
244
+ var panel = document.getElementById('panel-' + tabId);
245
+ if (panel) { panel.scrollIntoView({ behavior: 'smooth', block: 'start' }); }
246
+ }
247
+ </script>
@@ -1,8 +1,8 @@
1
1
  <% content_for :page_title, "Swallowed Exceptions" %>
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-eye-slash me-2"></i>
7
7
  Swallowed Exceptions
8
8
  </h1>
@@ -21,9 +21,9 @@
21
21
  </div>
22
22
 
23
23
  <% if @unique_count == 0 %>
24
- <div class="text-center py-5">
25
- <i class="bi bi-check-circle display-1 text-success mb-3"></i>
26
- <h4 class="text-muted">No Swallowed Exceptions Detected</h4>
24
+ <div class="red-empty-state">
25
+ <div class="red-empty-state-icon" style="background: var(--status-success-bg); color: var(--status-success);"><i class="bi bi-check-lg"></i></div>
26
+ <div class="red-empty-state-title">No Swallowed Exceptions Detected</div>
27
27
  <p class="text-muted">
28
28
  No exceptions with a rescue ratio above <%= (RailsErrorDashboard.configuration.swallowed_exception_threshold * 100).round %>% were detected in 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-eye-slash text-danger me-2"></i>
74
74
  Swallowed Exception Patterns
@@ -111,7 +111,7 @@
111
111
  </table>
112
112
  </div>
113
113
  </div>
114
- <div class="card-footer bg-white border-top d-flex justify-content-between align-items-center">
114
+ <div class="card-footer border-top d-flex justify-content-between align-items-center">
115
115
  <div>
116
116
  <small class="text-muted">
117
117
  <i class="bi bi-lightbulb text-warning"></i> Swallowed exceptions are raised then silently rescued. They may hide real bugs.
@@ -1,8 +1,8 @@
1
1
  <% content_for :page_title, "User Impact" %>
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-people me-2"></i>
7
7
  User Impact
8
8
  </h1>
@@ -21,9 +21,9 @@
21
21
  </div>
22
22
 
23
23
  <% if @entries.empty? %>
24
- <div class="text-center py-5">
24
+ <div class="red-empty-state">
25
25
  <i class="bi bi-people display-1 text-muted mb-3"></i>
26
- <h4 class="text-muted">No User Impact Data</h4>
26
+ <div class="red-empty-state-title">No User Impact Data</div>
27
27
  <p class="text-muted">
28
28
  No errors with user attribution were found in the last <%= @days %> days.
29
29
  </p>
@@ -77,7 +77,7 @@
77
77
 
78
78
  <!-- User Impact Table -->
79
79
  <div class="card mb-4">
80
- <div class="card-header bg-white d-flex justify-content-between align-items-center">
80
+ <div class="card-header d-flex justify-content-between align-items-center">
81
81
  <h5 class="mb-0">
82
82
  <i class="bi bi-bar-chart text-primary me-2"></i>
83
83
  Errors Ranked by User Impact
@@ -157,7 +157,7 @@
157
157
  </table>
158
158
  </div>
159
159
  </div>
160
- <div class="card-footer bg-white border-top d-flex justify-content-between align-items-center">
160
+ <div class="card-footer border-top d-flex justify-content-between align-items-center">
161
161
  <small class="text-muted">
162
162
  <i class="bi bi-info-circle"></i>
163
163
  Ranked by unique users affected, not occurrence count.
@@ -184,6 +184,9 @@ module RailsErrorDashboard
184
184
  # ActiveStorage event tracking (requires enable_breadcrumbs = true)
185
185
  attr_accessor :enable_activestorage_tracking # Master switch (default: false)
186
186
 
187
+ # Dashboard UI appearance
188
+ attr_accessor :accent_color # :crimson (default), :ruby, :ember, :violet
189
+
187
190
  # Notification callbacks (managed via helper methods, not set directly)
188
191
  attr_reader :notification_callbacks
189
192
 
@@ -353,6 +356,9 @@ module RailsErrorDashboard
353
356
  @enable_internal_logging = false # Opt-in for debugging
354
357
  @log_level = :silent # Silent by default, use :debug, :info, :warn, :error, or :silent
355
358
 
359
+ # Dashboard UI
360
+ @accent_color = :crimson # :crimson, :ruby, :ember, :violet
361
+
356
362
  @notification_callbacks = {
357
363
  error_logged: [],
358
364
  critical_error: [],
@@ -1,3 +1,3 @@
1
1
  module RailsErrorDashboard
2
- VERSION = "0.5.15"
2
+ VERSION = "0.6.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_error_dashboard
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.15
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Anjan Jagirdar
@@ -496,7 +496,7 @@ metadata:
496
496
  funding_uri: https://github.com/sponsors/AnjanJ
497
497
  post_install_message: |
498
498
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
499
- RED (Rails Error Dashboard) v0.5.15
499
+ RED (Rails Error Dashboard) v0.6.0
500
500
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
501
501
 
502
502
  First install: