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
@@ -1,229 +1,188 @@
1
1
  <% content_for :page_title, "Dashboard" %>
2
- <div class="container-fluid py-4">
3
- <!-- Page Header -->
2
+ <div style="max-width: 100%;">
4
3
  <div class="d-flex justify-content-between align-items-center mb-4">
5
- <h1 class="h3 mb-0">
6
- <i class="bi bi-speedometer2 me-2"></i>
7
- Dashboard Overview
8
- </h1>
9
- <div class="text-muted">
10
- <small>
11
- Last updated: <%= local_time(Time.current, format: :full) %>
12
- </small>
13
- </div>
4
+ <h1 style="font-size: 20px; font-weight: 700; margin: 0;">Overview</h1>
5
+ <span style="font-size: 12px; color: var(--text-tertiary);">
6
+ Last updated: <%= local_time(Time.current, format: :full) %>
7
+ </span>
14
8
  </div>
15
9
 
16
- <!-- Key Metrics Cards (Mobile-first: stack on mobile, 3 columns on desktop) -->
17
- <div class="row g-3 mb-4">
18
- <!-- Error Rate Card -->
19
- <div class="col-12 col-md-6 col-lg-4">
20
- <div class="card stat-card h-100 <%= error_rate_border_class(@stats[:error_rate]) %>">
21
- <div class="card-body text-center">
22
- <div class="stat-label mb-2">ERROR RATE</div>
23
- <div class="stat-value <%= error_rate_text_class(@stats[:error_rate]) %>">
24
- <%= @stats[:error_rate] %>%
25
- </div>
26
- <small class="text-muted">Errors per hour today</small>
27
- </div>
28
- </div>
10
+ <!-- Spike / Critical Alerts Banner -->
11
+ <% if @critical_alerts.any? %>
12
+ <%
13
+ top_alert = @critical_alerts.first
14
+ alert_count = @critical_alerts.count
15
+ %>
16
+ <div class="alert-danger" style="display: flex; align-items: center; gap: 10px; padding: 10px var(--space-5); background: var(--status-critical-bg); border-radius: var(--radius-md); border: 1px solid var(--status-critical); margin-bottom: var(--space-6); font-size: 13px; color: var(--status-critical);">
17
+ <span style="width: 8px; height: 8px; border-radius: 50%; background: var(--status-critical); animation: pulse 2s infinite; flex-shrink: 0;"></span>
18
+ <strong><%= alert_count %> Critical/High Error<%= alert_count != 1 ? 's' : '' %> in Last Hour</strong>
19
+ <span style="color: var(--text-secondary);">&mdash;</span>
20
+ <span style="color: var(--text-secondary);"><%= link_to error_path(top_alert, **app_context), style: "background: transparent; padding: 0; color: var(--status-critical); text-decoration: none; font-family: var(--font-mono); font-size: 13px;" do %><%= top_alert.error_type %><% end %> &middot; <%= top_alert.occurrence_count %> occurrences</span>
21
+ <%= link_to errors_path(app_context.merge(severity: 'critical')), style: "margin-left: auto; padding: 4px 12px; font-size: 12px; font-weight: 600; border-radius: var(--radius-full); border: 1px solid var(--status-critical); background: transparent; color: var(--status-critical); text-decoration: none; white-space: nowrap;" do %>View<% end %>
29
22
  </div>
23
+ <% end %>
30
24
 
31
- <!-- Affected Users Card -->
32
- <div class="col-12 col-md-6 col-lg-4">
33
- <div class="card stat-card h-100">
34
- <div class="card-body text-center">
35
- <div class="stat-label mb-2">AFFECTED USERS</div>
36
- <div class="stat-value text-primary">
37
- <%= @stats[:affected_users_today] %>
38
- </div>
39
- <small class="<%= trend_color_class(@stats[:affected_users_change]) %>">
40
- <%= trend_arrow(@stats[:affected_users_change]) %>
41
- <%= @stats[:affected_users_change].abs %>
42
- from yesterday
43
- </small>
44
- </div>
25
+ <!-- Hero Stats (3 cards) -->
26
+ <%
27
+ total_errors = @stats[:resolved] + @stats[:unresolved]
28
+ resolution_rate = total_errors > 0 ? ((@stats[:resolved].to_f / total_errors) * 100).round(1) : 0.0
29
+ %>
30
+ <%
31
+ # Build sparkline data from 7-day trend
32
+ trend_values = @stats[:errors_trend_7d].values rescue []
33
+ trend_max = [ trend_values.max || 1, 1 ].max
34
+ %>
35
+ <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-4); margin-bottom: var(--space-6);">
36
+ <!-- Error Rate -->
37
+ <div class="card stat-card" style="border-left: 3px solid var(--status-critical); padding: var(--space-5) var(--space-6);">
38
+ <div class="stat-label" style="margin-bottom: 4px;">Error Rate</div>
39
+ <div style="display: flex; align-items: baseline; gap: 8px;">
40
+ <span class="stat-value" style="color: var(--status-critical);"><%= @stats[:error_rate] %>%</span>
41
+ <% if @stats[:trend_percentage].present? && @stats[:trend_percentage] != 0 %>
42
+ <span style="display: inline-flex; align-items: center; gap: 2px; font-size: 12px; font-weight: 600; color: <%= @stats[:trend_percentage] > 0 ? 'var(--status-critical)' : 'var(--status-success)' %>;">
43
+ <i class="bi <%= @stats[:trend_percentage] > 0 ? 'bi-arrow-up-right' : 'bi-arrow-down-right' %>" style="font-size: 10px;"></i>
44
+ <%= @stats[:trend_percentage].abs %>%
45
+ </span>
46
+ <% end %>
45
47
  </div>
48
+ <% if trend_values.any? %>
49
+ <div style="display: flex; align-items: flex-end; gap: 2px; height: 24px; margin-top: var(--space-2);">
50
+ <% trend_values.each do |v| %>
51
+ <div style="flex: 1; background: var(--status-critical); border-radius: 1px; min-height: 2px; height: <%= (v.to_f / trend_max * 100).round %>%; opacity: <%= v > 0 ? 0.7 : 0.15 %>;"></div>
52
+ <% end %>
53
+ </div>
54
+ <% end %>
55
+ <small style="color: var(--text-tertiary); margin-top: var(--space-1);">Errors per hour today</small>
46
56
  </div>
47
57
 
48
- <!-- Unresolved Errors Card -->
49
- <div class="col-12 col-md-6 col-lg-4">
50
- <div class="card stat-card h-100">
51
- <div class="card-body text-center">
52
- <div class="stat-label mb-2">UNRESOLVED ERRORS</div>
53
- <div class="stat-value text-danger">
54
- <%= @stats[:unresolved] %>
55
- </div>
56
- <small class="text-muted">
57
- Pending resolution
58
- </small>
59
- </div>
58
+ <!-- Unresolved -->
59
+ <div class="card stat-card" style="border-left: 3px solid var(--accent); padding: var(--space-5) var(--space-6);">
60
+ <div class="stat-label" style="margin-bottom: 4px;">Unresolved</div>
61
+ <div style="display: flex; align-items: baseline; gap: 8px;">
62
+ <span class="stat-value"><%= @stats[:unresolved] %></span>
60
63
  </div>
64
+ <% if trend_values.any? %>
65
+ <div style="display: flex; align-items: flex-end; gap: 2px; height: 24px; margin-top: var(--space-2);">
66
+ <% trend_values.each do |v| %>
67
+ <div style="flex: 1; background: var(--accent); border-radius: 1px; min-height: 2px; height: <%= (v.to_f / trend_max * 100).round %>%; opacity: <%= v > 0 ? 0.7 : 0.15 %>;"></div>
68
+ <% end %>
69
+ </div>
70
+ <% end %>
71
+ <small style="color: var(--text-tertiary); margin-top: var(--space-1);">Pending resolution</small>
61
72
  </div>
62
73
 
63
- <!-- Trend Card -->
64
- <div class="col-12 col-md-6 col-lg-4">
65
- <div class="card stat-card h-100">
66
- <div class="card-body text-center">
67
- <div class="stat-label mb-2">ERROR TREND</div>
68
- <div class="stat-value <%= trend_color_class(@stats[:trend_percentage]) %>">
69
- <%= trend_arrow(@stats[:trend_percentage]) %>
70
- <%= @stats[:trend_percentage].abs %>%
71
- </div>
72
- <small class="text-muted">
73
- <%= trend_text(@stats[:trend_direction]) %>
74
- </small>
74
+ <!-- Resolution Rate -->
75
+ <div class="card stat-card" style="border-left: 3px solid var(--status-success); padding: var(--space-5) var(--space-6);">
76
+ <div class="stat-label" style="margin-bottom: 4px;">Resolution Rate</div>
77
+ <div style="display: flex; align-items: baseline; gap: 8px;">
78
+ <span class="stat-value" style="color: var(--status-success);"><%= resolution_rate %>%</span>
79
+ </div>
80
+ <% if trend_values.any? %>
81
+ <div style="display: flex; align-items: flex-end; gap: 2px; height: 24px; margin-top: var(--space-2);">
82
+ <% trend_values.each do |v| %>
83
+ <div style="flex: 1; background: var(--status-success); border-radius: 1px; min-height: 2px; height: <%= (v.to_f / trend_max * 100).round %>%; opacity: <%= v > 0 ? 0.7 : 0.15 %>;"></div>
84
+ <% end %>
75
85
  </div>
86
+ <% end %>
87
+ <small style="color: var(--text-tertiary); margin-top: var(--space-1);"><%= @stats[:resolved] %> of <%= total_errors %> resolved</small>
88
+ </div>
89
+ </div>
90
+
91
+ <!-- Secondary Stats (3 cards) -->
92
+ <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-4); margin-bottom: var(--space-6);">
93
+ <!-- Affected Users -->
94
+ <div class="card stat-card" style="padding: var(--space-5) var(--space-6);">
95
+ <div class="stat-label" style="margin-bottom: 4px;">Affected Users</div>
96
+ <div style="display: flex; align-items: baseline; gap: 8px;">
97
+ <span class="stat-value"><%= @stats[:affected_users_today] %></span>
98
+ <% if @stats[:affected_users_change] != 0 %>
99
+ <span style="display: inline-flex; align-items: center; gap: 2px; font-size: 12px; font-weight: 600; color: <%= @stats[:affected_users_change] > 0 ? 'var(--status-critical)' : 'var(--status-success)' %>;">
100
+ <i class="bi <%= @stats[:affected_users_change] > 0 ? 'bi-arrow-up-right' : 'bi-arrow-down-right' %>" style="font-size: 10px;"></i>
101
+ <%= @stats[:affected_users_change].abs %> from yesterday
102
+ </span>
103
+ <% end %>
76
104
  </div>
77
105
  </div>
78
106
 
79
- <!-- Resolution Rate Card -->
80
- <div class="col-12 col-md-6 col-lg-4">
81
- <div class="card stat-card h-100">
82
- <div class="card-body text-center">
83
- <div class="stat-label mb-2">RESOLUTION RATE</div>
84
- <%
85
- total_errors = @stats[:resolved] + @stats[:unresolved]
86
- resolution_rate = total_errors > 0 ? ((@stats[:resolved].to_f / total_errors) * 100).round(1) : 0.0
87
- %>
88
- <div class="stat-value <%= resolution_rate >= 80 ? 'text-success' : resolution_rate >= 50 ? 'text-warning' : 'text-danger' %>">
89
- <%= resolution_rate %>%
90
- </div>
91
- <small class="text-muted">
92
- <%= @stats[:resolved] %> of <%= total_errors %> resolved
93
- </small>
94
- </div>
107
+ <!-- Avg Resolution Time -->
108
+ <div class="card stat-card" style="padding: var(--space-5) var(--space-6);">
109
+ <div class="stat-label" style="margin-bottom: 4px;">Avg Resolution</div>
110
+ <div style="display: flex; align-items: baseline; gap: 8px;">
111
+ <% if @stats[:average_resolution_time].present? %>
112
+ <span class="stat-value"><%= @stats[:average_resolution_time] %>h</span>
113
+ <% else %>
114
+ <span class="stat-value" style="color: var(--text-tertiary);">&mdash;</span>
115
+ <% end %>
95
116
  </div>
117
+ <small style="color: var(--text-tertiary);">Mean time to resolution</small>
96
118
  </div>
97
119
 
98
- <!-- Average Resolution Time Card -->
99
- <div class="col-12 col-md-6 col-lg-4">
100
- <div class="card stat-card h-100">
101
- <div class="card-body text-center">
102
- <div class="stat-label mb-2">AVG RESOLUTION TIME</div>
103
- <% if @stats[:average_resolution_time].present? %>
104
- <div class="stat-value text-warning">
105
- <%= @stats[:average_resolution_time] %>h
106
- </div>
107
- <small class="text-muted">
108
- Mean time to resolution
109
- </small>
110
- <% else %>
111
- <div class="stat-value text-muted">
112
- --
113
- </div>
114
- <small class="text-muted">
115
- No resolved errors yet
116
- </small>
117
- <% end %>
118
- </div>
120
+ <!-- Error Trend -->
121
+ <div class="card stat-card" style="padding: var(--space-5) var(--space-6);">
122
+ <div class="stat-label" style="margin-bottom: 4px;">Error Trend</div>
123
+ <div style="display: flex; align-items: baseline; gap: 8px;">
124
+ <span class="stat-value"><%= trend_arrow(@stats[:trend_percentage]) %> <%= @stats[:trend_percentage].abs %>%</span>
119
125
  </div>
126
+ <small style="color: var(--text-tertiary);"><%= trend_text(@stats[:trend_direction]) %></small>
120
127
  </div>
121
128
  </div>
122
129
 
123
- <!-- Top 6 Errors by Impact (Mobile: stack, Desktop: 2 columns) -->
130
+ <!-- Top Errors by Impact -->
124
131
  <% if @stats[:top_errors_by_impact].any? %>
125
- <div class="card mb-4">
126
- <div class="card-header bg-white d-flex justify-content-between align-items-center">
127
- <h5 class="mb-0">
128
- <i class="bi bi-exclamation-triangle me-2"></i>
129
- Top 6 Errors by Impact
130
- </h5>
131
- <%= link_to "View All Errors →", errors_path, class: "btn btn-sm btn-outline-primary" %>
132
+ <div class="card" style="margin-bottom: var(--space-6);">
133
+ <div style="display: flex; justify-content: space-between; align-items: center; padding: var(--space-4) var(--space-6); border-bottom: 1px solid var(--border-primary);">
134
+ <span style="font-size: 13px; font-weight: 600; color: var(--text-primary);">Top Errors by Impact</span>
135
+ <%= link_to errors_path(app_context), style: "font-size: 12px; color: var(--accent); text-decoration: none; font-weight: 500;" do %>View all &rarr;<% end %>
132
136
  </div>
133
- <div class="card-body">
134
- <div class="row g-3">
135
- <% @stats[:top_errors_by_impact].each do |error| %>
136
- <div class="col-12 col-lg-6">
137
- <%= link_to error_path(error[:id]), class: "text-decoration-none" do %>
138
- <div class="card h-100 border-start border-<%= severity_color(error[:severity]) %> border-3">
139
- <div class="card-body">
140
- <div class="d-flex justify-content-between align-items-start mb-2">
141
- <div class="flex-grow-1">
142
- <h6 class="mb-1">
143
- <span class="badge bg-<%= severity_color(error[:severity]) %> me-2">
144
- <%= severity_icon(error[:severity]) %> <%= error[:severity].to_s.titleize %>
145
- </span>
146
- <code class="small"><%= error[:error_type] %></code>
147
- </h6>
148
- <p class="small text-muted mb-2"><%= error[:message] %></p>
149
- </div>
150
- </div>
151
- <div class="d-flex justify-content-between align-items-center small">
152
- <span class="text-muted">
153
- <i class="bi bi-people-fill me-1"></i>
154
- <%= error[:affected_users] %> user<%= error[:affected_users] != 1 ? 's' : '' %>
155
- </span>
156
- <span class="text-muted">
157
- <i class="bi bi-arrow-repeat me-1"></i>
158
- <%= error[:occurrence_count] %> occurrence<%= error[:occurrence_count] != 1 ? 's' : '' %>
159
- </span>
160
- <span class="fw-bold text-danger">
161
- Impact: <%= error[:impact_score] %>
162
- </span>
163
- </div>
164
- </div>
165
- </div>
166
- <% end %>
137
+ <div>
138
+ <% @stats[:top_errors_by_impact].each_with_index do |error, i| %>
139
+ <%= link_to error_path(error[:id], **app_context), style: "display: flex; align-items: center; gap: var(--space-4); padding: var(--space-4) var(--space-6); border-bottom: #{i < @stats[:top_errors_by_impact].length - 1 ? '1px solid var(--border-primary)' : 'none'}; cursor: pointer; transition: background 0.1s ease; text-decoration: none; color: inherit;", class: "top-error-row" do %>
140
+ <span style="width: 24px; text-align: center; font-size: 12px; font-weight: 600; color: var(--text-tertiary);"><%= i + 1 %></span>
141
+ <span class="badge bg-<%= severity_color(error[:severity]) %>" style="display: inline-flex; align-items: center; gap: 4px;">
142
+ <span style="width: 6px; height: 6px; border-radius: 50%; background: currentColor;"></span>
143
+ <%= error[:severity] %>
144
+ </span>
145
+ <div style="flex: 1; min-width: 0;">
146
+ <div style="font-size: 13px; font-weight: 600; color: var(--text-primary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-family: var(--font-mono); font-size: 12px;">
147
+ <%= error[:error_type] %>
148
+ </div>
149
+ <div style="font-size: 12px; color: var(--text-tertiary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;"><%= error[:message]&.truncate(80) %></div>
150
+ </div>
151
+ <div style="text-align: right; flex-shrink: 0;">
152
+ <div style="font-size: 14px; font-weight: 600; color: var(--text-primary); font-variant-numeric: tabular-nums;"><%= error[:occurrence_count]&.to_s(:delimited) rescue error[:occurrence_count] %></div>
153
+ <div style="font-size: 11px; color: var(--text-tertiary);"><%= error[:affected_users] %> users</div>
167
154
  </div>
168
155
  <% end %>
169
- </div>
156
+ <% end %>
170
157
  </div>
171
158
  </div>
172
159
  <% end %>
173
160
 
174
- <!-- Platform Health Summary (Mobile: stack, Desktop: 3 columns) -->
161
+ <!-- Platform Health Summary -->
175
162
  <% if @platform_health.any? %>
176
- <div class="card mb-4">
177
- <div class="card-header bg-white d-flex justify-content-between align-items-center">
178
- <h5 class="mb-0">
179
- <i class="bi bi-phone me-2"></i>
180
- Platform Health
181
- </h5>
163
+ <div class="card" style="margin-bottom: var(--space-6);">
164
+ <div style="display: flex; justify-content: space-between; align-items: center; padding: var(--space-4) var(--space-6); border-bottom: 1px solid var(--border-primary);">
165
+ <span style="font-size: 13px; font-weight: 600; color: var(--text-primary);">Platform Health</span>
182
166
  <% if RailsErrorDashboard.configuration.enable_platform_comparison %>
183
- <%= link_to "Full Comparison ", platform_comparison_errors_path, class: "btn btn-sm btn-outline-primary" %>
167
+ <%= link_to platform_comparison_errors_path(app_context), style: "font-size: 12px; color: var(--accent); text-decoration: none; font-weight: 500;" do %>Full comparison &rarr;<% end %>
184
168
  <% end %>
185
169
  </div>
186
- <div class="card-body">
187
- <div class="row g-3">
170
+ <div style="padding: var(--space-5) var(--space-6);">
171
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: var(--space-4);">
188
172
  <% @platform_health.each do |platform, health| %>
189
- <div class="col-12 col-md-4">
190
- <div class="card h-100 border-<%= health_status_color(health[:health_status]) %>">
191
- <div class="card-body">
192
- <div class="d-flex justify-content-between align-items-start mb-3">
193
- <h6 class="text-capitalize mb-0"><%= platform %></h6>
194
- <span class="badge bg-<%= health_status_color(health[:health_status]) %>">
195
- <%= health_status_text(health[:health_status]) %>
196
- </span>
197
- </div>
198
-
199
- <div class="mb-2">
200
- <small class="text-muted">Stability Score</small>
201
- <div class="d-flex align-items-center">
202
- <h4 class="mb-0 me-2"><%= health[:stability_score] %>/100</h4>
203
- <div class="progress flex-grow-1" style="height: 8px;">
204
- <div class="progress-bar bg-<%= health_status_color(health[:health_status]) %>"
205
- role="progressbar"
206
- style="width: <%= health[:stability_score] %>%">
207
- </div>
208
- </div>
209
- </div>
210
- </div>
211
-
212
- <div class="small">
213
- <div class="d-flex justify-content-between mb-1">
214
- <span class="text-muted">Total Errors:</span>
215
- <strong><%= health[:total_errors] %></strong>
216
- </div>
217
- <div class="d-flex justify-content-between mb-1">
218
- <span class="text-muted">Critical:</span>
219
- <strong class="text-danger"><%= health[:critical_errors] %></strong>
220
- </div>
221
- <div class="d-flex justify-content-between">
222
- <span class="text-muted">Unresolved:</span>
223
- <strong><%= health[:unresolved_errors] %></strong>
224
- </div>
225
- </div>
226
- </div>
173
+ <div style="padding: var(--space-4); border: 1px solid var(--border-primary); border-radius: var(--radius-md);">
174
+ <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--space-3);">
175
+ <span style="font-size: 13px; font-weight: 600; text-transform: capitalize;"><%= platform %></span>
176
+ <span class="badge bg-<%= health_status_color(health[:health_status]) %>"><%= health_status_text(health[:health_status]) %></span>
177
+ </div>
178
+ <div style="margin-bottom: var(--space-2);">
179
+ <div style="font-size: 20px; font-weight: 700; font-variant-numeric: tabular-nums;"><%= health[:stability_score] %><span style="font-size: 12px; color: var(--text-tertiary); font-weight: 400;">/100</span></div>
180
+ </div>
181
+ <div class="progress" style="height: 4px; margin-bottom: var(--space-3);">
182
+ <div class="progress-bar" style="width: <%= health[:stability_score] %>%; background: var(--status-<%= health[:health_status] == :healthy ? 'success' : health[:health_status] == :warning ? 'warning' : 'critical' %>);"></div>
183
+ </div>
184
+ <div style="font-size: 12px; color: var(--text-tertiary);">
185
+ <%= health[:total_errors] %> errors &middot; <%= health[:critical_errors] %> critical &middot; <%= health[:unresolved_errors] %> open
227
186
  </div>
228
187
  </div>
229
188
  <% end %>
@@ -232,198 +191,68 @@
232
191
  </div>
233
192
  <% end %>
234
193
 
235
- <!-- Correlation Summary (Last 7 days) -->
194
+ <!-- Correlation Insights -->
236
195
  <% if RailsErrorDashboard.configuration.enable_error_correlation && (@problematic_releases.any? || @time_correlated_errors.any? || @multi_error_users.any?) %>
237
- <%
238
- # Calculate column width based on number of cards present
239
- cards_present = [@problematic_releases.any?, @time_correlated_errors.any?, @multi_error_users.any?].count(true)
240
- col_class = case cards_present
241
- when 1 then "col-12"
242
- when 2 then "col-12 col-lg-6"
243
- else "col-12 col-lg-4"
244
- end
245
- %>
246
- <div class="card mb-4">
247
- <div class="card-header bg-white d-flex justify-content-between align-items-center">
248
- <h5 class="mb-0">
249
- <i class="bi bi-diagram-3 me-2"></i>
250
- Error Correlation Insights
251
- </h5>
252
- <%= link_to "Full Analysis →", correlation_errors_path, class: "btn btn-sm btn-outline-primary" %>
196
+ <div class="card" style="margin-bottom: var(--space-6);">
197
+ <div style="display: flex; justify-content: space-between; align-items: center; padding: var(--space-4) var(--space-6); border-bottom: 1px solid var(--border-primary);">
198
+ <span style="font-size: 13px; font-weight: 600; color: var(--text-primary);">Correlation Insights</span>
199
+ <%= link_to correlation_errors_path(app_context), style: "font-size: 12px; color: var(--accent); text-decoration: none; font-weight: 500;" do %>Full analysis &rarr;<% end %>
253
200
  </div>
254
- <div class="card-body">
255
- <div class="row g-3">
256
- <!-- Problematic Releases -->
201
+ <div style="padding: var(--space-5) var(--space-6);">
202
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: var(--space-4);">
257
203
  <% if @problematic_releases.any? %>
258
- <div class="<%= col_class %>">
259
- <div class="card h-100 border-warning">
260
- <div class="card-body">
261
- <h6 class="mb-3">
262
- <i class="bi bi-tag me-2"></i>
263
- Problematic Releases
264
- </h6>
265
- <div class="list-group list-group-flush">
266
- <% @problematic_releases.each do |release| %>
267
- <div class="list-group-item px-0 py-2">
268
- <div class="d-flex justify-content-between align-items-start">
269
- <div class="flex-grow-1">
270
- <code class="small"><%= release[:version] || release[:git_sha]&.truncate(8, omission: '') %></code>
271
- <div class="small text-muted">
272
- <%= release[:error_count] %> error<%= release[:error_count] != 1 ? 's' : '' %> •
273
- <%= release[:unique_types] %> type<%= release[:unique_types] != 1 ? 's' : '' %>
274
- </div>
275
- </div>
276
- <span class="badge bg-danger"><%= release[:severity_score] %></span>
277
- </div>
278
- </div>
279
- <% end %>
280
- </div>
281
- </div>
204
+ <div style="padding: var(--space-4); border: 1px solid var(--border-primary); border-radius: var(--radius-md); border-left: 3px solid var(--status-warning);">
205
+ <div style="font-size: 12px; font-weight: 600; color: var(--text-secondary); margin-bottom: var(--space-3);">
206
+ <i class="bi bi-tag me-1"></i> Problematic Releases
282
207
  </div>
208
+ <% @problematic_releases.each do |release| %>
209
+ <div style="display: flex; justify-content: space-between; align-items: center; padding: var(--space-2) 0; border-bottom: 1px solid var(--border-primary); font-size: 12px;">
210
+ <code style="font-size: 11px;"><%= release[:version] || release[:git_sha]&.truncate(8, omission: '') %></code>
211
+ <span style="font-weight: 600;"><%= release[:error_count] %> errors</span>
212
+ </div>
213
+ <% end %>
283
214
  </div>
284
215
  <% end %>
285
216
 
286
- <!-- Time-Correlated Errors -->
287
217
  <% if @time_correlated_errors.any? %>
288
- <div class="<%= col_class %>">
289
- <div class="card h-100 border-info">
290
- <div class="card-body">
291
- <h6 class="mb-3">
292
- <i class="bi bi-clock-history me-2"></i>
293
- Time-Correlated Errors
294
- </h6>
295
- <div class="list-group list-group-flush">
296
- <% @time_correlated_errors.each do |_, data| %>
297
- <div class="list-group-item px-0 py-2">
298
- <div class="small">
299
- <div class="d-flex align-items-center gap-1">
300
- <code class="text-truncate"><%= data[:error_type_a] %></code>
301
- <i class="bi bi-arrow-left-right text-muted"></i>
302
- <code class="text-truncate"><%= data[:error_type_b] %></code>
303
- </div>
304
- <div class="text-muted mt-1">
305
- Correlation: <strong><%= (data[:correlation] * 100).round(0) %>%</strong>
306
- (<%= data[:strength] %>)
307
- </div>
308
- </div>
309
- </div>
310
- <% end %>
218
+ <div style="padding: var(--space-4); border: 1px solid var(--border-primary); border-radius: var(--radius-md); border-left: 3px solid var(--status-info);">
219
+ <div style="font-size: 12px; font-weight: 600; color: var(--text-secondary); margin-bottom: var(--space-3);">
220
+ <i class="bi bi-clock-history me-1"></i> Time-Correlated
221
+ </div>
222
+ <% @time_correlated_errors.each do |_, data| %>
223
+ <div style="padding: var(--space-2) 0; border-bottom: 1px solid var(--border-primary); font-size: 12px;">
224
+ <div style="display: flex; align-items: center; gap: 4px;">
225
+ <code style="font-size: 10px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100px;"><%= data[:error_type_a] %></code>
226
+ <i class="bi bi-arrow-left-right" style="color: var(--text-tertiary); font-size: 10px;"></i>
227
+ <code style="font-size: 10px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100px;"><%= data[:error_type_b] %></code>
228
+ </div>
229
+ <div style="color: var(--text-tertiary); margin-top: 2px;">
230
+ <strong><%= (data[:correlation] * 100).round(0) %>%</strong> correlation (<%= data[:strength] %>)
311
231
  </div>
312
232
  </div>
313
- </div>
233
+ <% end %>
314
234
  </div>
315
235
  <% end %>
316
236
 
317
- <!-- Multi-Error Users -->
318
237
  <% if @multi_error_users.any? %>
319
- <div class="<%= col_class %>">
320
- <div class="card h-100 border-danger">
321
- <div class="card-body">
322
- <h6 class="mb-3">
323
- <i class="bi bi-people-fill me-2"></i>
324
- Users with Multiple Errors
325
- </h6>
326
- <div class="list-group list-group-flush">
327
- <% @multi_error_users.each do |user| %>
328
- <div class="list-group-item px-0 py-2">
329
- <div class="d-flex justify-content-between align-items-start">
330
- <div class="flex-grow-1">
331
- <div class="small">
332
- User ID: <strong><%= user[:user_id] %></strong>
333
- </div>
334
- <div class="small text-muted">
335
- <%= user[:error_types].size %> different error type<%= user[:error_types].size != 1 ? 's' : '' %>
336
- </div>
337
- </div>
338
- <span class="badge bg-danger"><%= user[:total_errors] %></span>
339
- </div>
340
- </div>
341
- <% end %>
342
- </div>
343
- </div>
238
+ <div style="padding: var(--space-4); border: 1px solid var(--border-primary); border-radius: var(--radius-md); border-left: 3px solid var(--status-critical);">
239
+ <div style="font-size: 12px; font-weight: 600; color: var(--text-secondary); margin-bottom: var(--space-3);">
240
+ <i class="bi bi-people-fill me-1"></i> Multi-Error Users
344
241
  </div>
345
- </div>
346
- <% end %>
347
- </div>
348
- </div>
349
- </div>
350
- <% end %>
351
-
352
- <!-- Critical Alerts (Last hour) -->
353
- <% if @critical_alerts.any? %>
354
- <div class="alert alert-danger border-danger mb-4" role="alert">
355
- <div class="d-flex align-items-center mb-2">
356
- <i class="bi bi-exclamation-triangle-fill fs-3 me-3"></i>
357
- <div>
358
- <h5 class="alert-heading mb-0">
359
- 🚨 <%= @critical_alerts.count %> Critical/High Error<%= @critical_alerts.count != 1 ? 's' : '' %> in Last Hour
360
- </h5>
361
- </div>
362
- </div>
363
- <div class="list-group list-group-flush">
364
- <% @critical_alerts.first(5).each do |error| %>
365
- <%= link_to error_path(error), class: "list-group-item list-group-item-action" do %>
366
- <div class="d-flex justify-content-between align-items-start">
367
- <div class="flex-grow-1">
368
- <div class="d-flex align-items-center mb-1">
369
- <span class="badge bg-<%= severity_color(error.severity) %> me-2">
370
- <%= severity_icon(error.severity) %>
371
- </span>
372
- <code class="small"><%= error.error_type %></code>
242
+ <% @multi_error_users.each do |user| %>
243
+ <div style="display: flex; justify-content: space-between; align-items: center; padding: var(--space-2) 0; border-bottom: 1px solid var(--border-primary); font-size: 12px;">
244
+ <span>User <strong><%= user[:user_id] %></strong> &middot; <%= user[:error_types].size %> types</span>
245
+ <span style="font-weight: 600; color: var(--status-critical);"><%= user[:total_errors] %></span>
373
246
  </div>
374
- <p class="mb-1 small"><%= error.message&.truncate(100) %></p>
375
- <small class="text-muted">
376
- <i class="bi bi-clock me-1"></i>
377
- <%= local_time_ago(error.occurred_at) %>
378
-
379
- <i class="bi bi-arrow-repeat me-1"></i>
380
- <%= error.occurrence_count %> occurrence<%= error.occurrence_count != 1 ? 's' : '' %>
381
- </small>
382
- </div>
383
- <i class="bi bi-chevron-right"></i>
247
+ <% end %>
384
248
  </div>
385
249
  <% end %>
386
- <% end %>
387
- </div>
388
- <% if @critical_alerts.count > 5 %>
389
- <div class="mt-2">
390
- <%= link_to "View all #{@critical_alerts.count} critical errors →",
391
- errors_path(severity: 'critical'),
392
- class: "btn btn-sm btn-outline-danger" %>
393
250
  </div>
394
- <% end %>
251
+ </div>
395
252
  </div>
396
253
  <% end %>
397
-
398
- <!-- Quick Actions -->
399
- <div class="row g-3">
400
- <div class="col-12 col-md-6 col-lg-3">
401
- <%= link_to errors_path, class: "btn btn-lg btn-outline-primary w-100" do %>
402
- <i class="bi bi-list-ul d-block fs-3 mb-2"></i>
403
- View All Errors
404
- <% end %>
405
- </div>
406
- <div class="col-12 col-md-6 col-lg-3">
407
- <%= link_to analytics_errors_path, class: "btn btn-lg btn-outline-primary w-100" do %>
408
- <i class="bi bi-graph-up d-block fs-3 mb-2"></i>
409
- Analytics
410
- <% end %>
411
- </div>
412
- <% if RailsErrorDashboard.configuration.enable_platform_comparison %>
413
- <div class="col-12 col-md-6 col-lg-3">
414
- <%= link_to platform_comparison_errors_path, class: "btn btn-lg btn-outline-primary w-100" do %>
415
- <i class="bi bi-phone d-block fs-3 mb-2"></i>
416
- Platform Health
417
- <% end %>
418
- </div>
419
- <% end %>
420
- <% if RailsErrorDashboard.configuration.enable_error_correlation %>
421
- <div class="col-12 col-md-6 col-lg-3">
422
- <%= link_to correlation_errors_path, class: "btn btn-lg btn-outline-primary w-100" do %>
423
- <i class="bi bi-diagram-3 d-block fs-3 mb-2"></i>
424
- Correlation
425
- <% end %>
426
- </div>
427
- <% end %>
428
- </div>
429
254
  </div>
255
+
256
+ <style>
257
+ .top-error-row:hover { background: var(--surface-hover); }
258
+ </style>