rails_error_dashboard 0.1.0 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +305 -703
- data/app/assets/stylesheets/rails_error_dashboard/_catppuccin_mocha.scss +107 -0
- data/app/assets/stylesheets/rails_error_dashboard/_components.scss +625 -0
- data/app/assets/stylesheets/rails_error_dashboard/_layout.scss +257 -0
- data/app/assets/stylesheets/rails_error_dashboard/_theme_variables.scss +203 -0
- data/app/assets/stylesheets/rails_error_dashboard/application.css +926 -15
- data/app/assets/stylesheets/rails_error_dashboard/application.css.map +7 -0
- data/app/assets/stylesheets/rails_error_dashboard/application.scss +61 -0
- data/app/controllers/rails_error_dashboard/application_controller.rb +18 -0
- data/app/controllers/rails_error_dashboard/errors_controller.rb +140 -4
- data/app/helpers/rails_error_dashboard/application_helper.rb +55 -0
- data/app/helpers/rails_error_dashboard/backtrace_helper.rb +91 -0
- data/app/helpers/rails_error_dashboard/overview_helper.rb +78 -0
- data/app/helpers/rails_error_dashboard/user_agent_helper.rb +118 -0
- data/app/jobs/rails_error_dashboard/application_job.rb +19 -0
- data/app/jobs/rails_error_dashboard/async_error_logging_job.rb +48 -0
- data/app/jobs/rails_error_dashboard/baseline_alert_job.rb +263 -0
- data/app/jobs/rails_error_dashboard/discord_error_notification_job.rb +4 -8
- data/app/jobs/rails_error_dashboard/email_error_notification_job.rb +2 -1
- data/app/jobs/rails_error_dashboard/pagerduty_error_notification_job.rb +5 -5
- data/app/jobs/rails_error_dashboard/slack_error_notification_job.rb +10 -6
- data/app/jobs/rails_error_dashboard/webhook_error_notification_job.rb +5 -6
- data/app/mailers/rails_error_dashboard/application_mailer.rb +1 -1
- data/app/mailers/rails_error_dashboard/error_notification_mailer.rb +1 -1
- data/app/models/rails_error_dashboard/cascade_pattern.rb +74 -0
- data/app/models/rails_error_dashboard/error_baseline.rb +100 -0
- data/app/models/rails_error_dashboard/error_comment.rb +27 -0
- data/app/models/rails_error_dashboard/error_log.rb +471 -3
- data/app/models/rails_error_dashboard/error_occurrence.rb +49 -0
- data/app/views/layouts/rails_error_dashboard.html.erb +816 -178
- data/app/views/layouts/rails_error_dashboard_old_backup.html.erb +383 -0
- data/app/views/rails_error_dashboard/error_notification_mailer/error_alert.html.erb +3 -10
- data/app/views/rails_error_dashboard/error_notification_mailer/error_alert.text.erb +1 -2
- data/app/views/rails_error_dashboard/errors/_error_row.html.erb +78 -0
- data/app/views/rails_error_dashboard/errors/_pattern_insights.html.erb +209 -0
- data/app/views/rails_error_dashboard/errors/_stats.html.erb +34 -0
- data/app/views/rails_error_dashboard/errors/_timeline.html.erb +167 -0
- data/app/views/rails_error_dashboard/errors/analytics.html.erb +152 -56
- data/app/views/rails_error_dashboard/errors/correlation.html.erb +373 -0
- data/app/views/rails_error_dashboard/errors/index.html.erb +294 -138
- data/app/views/rails_error_dashboard/errors/overview.html.erb +253 -0
- data/app/views/rails_error_dashboard/errors/platform_comparison.html.erb +399 -0
- data/app/views/rails_error_dashboard/errors/show.html.erb +781 -65
- data/config/routes.rb +9 -0
- data/db/migrate/20251225071314_add_optimized_indexes_to_error_logs.rb +66 -0
- data/db/migrate/20251225074653_remove_environment_from_error_logs.rb +26 -0
- data/db/migrate/20251225085859_add_enhanced_metrics_to_error_logs.rb +12 -0
- data/db/migrate/20251225093603_add_similarity_tracking_to_error_logs.rb +9 -0
- data/db/migrate/20251225100236_create_error_occurrences.rb +31 -0
- data/db/migrate/20251225101920_create_cascade_patterns.rb +33 -0
- data/db/migrate/20251225102500_create_error_baselines.rb +38 -0
- data/db/migrate/20251226020000_add_workflow_fields_to_error_logs.rb +27 -0
- data/db/migrate/20251226020100_create_error_comments.rb +18 -0
- data/lib/generators/rails_error_dashboard/install/install_generator.rb +276 -1
- data/lib/generators/rails_error_dashboard/install/templates/initializer.rb +272 -37
- data/lib/generators/rails_error_dashboard/solid_queue/solid_queue_generator.rb +36 -0
- data/lib/generators/rails_error_dashboard/solid_queue/templates/queue.yml +55 -0
- data/lib/rails_error_dashboard/commands/batch_delete_errors.rb +1 -1
- data/lib/rails_error_dashboard/commands/batch_resolve_errors.rb +2 -2
- data/lib/rails_error_dashboard/commands/log_error.rb +272 -7
- data/lib/rails_error_dashboard/commands/resolve_error.rb +16 -0
- data/lib/rails_error_dashboard/configuration.rb +90 -5
- data/lib/rails_error_dashboard/error_reporter.rb +15 -7
- data/lib/rails_error_dashboard/logger.rb +105 -0
- data/lib/rails_error_dashboard/middleware/error_catcher.rb +17 -10
- data/lib/rails_error_dashboard/plugin.rb +6 -3
- data/lib/rails_error_dashboard/plugin_registry.rb +2 -2
- data/lib/rails_error_dashboard/plugins/audit_log_plugin.rb +0 -1
- data/lib/rails_error_dashboard/plugins/jira_integration_plugin.rb +3 -4
- data/lib/rails_error_dashboard/plugins/metrics_plugin.rb +1 -3
- data/lib/rails_error_dashboard/queries/analytics_stats.rb +44 -6
- data/lib/rails_error_dashboard/queries/baseline_stats.rb +107 -0
- data/lib/rails_error_dashboard/queries/co_occurring_errors.rb +86 -0
- data/lib/rails_error_dashboard/queries/dashboard_stats.rb +242 -2
- data/lib/rails_error_dashboard/queries/error_cascades.rb +74 -0
- data/lib/rails_error_dashboard/queries/error_correlation.rb +375 -0
- data/lib/rails_error_dashboard/queries/errors_list.rb +106 -10
- data/lib/rails_error_dashboard/queries/filter_options.rb +0 -1
- data/lib/rails_error_dashboard/queries/platform_comparison.rb +254 -0
- data/lib/rails_error_dashboard/queries/similar_errors.rb +93 -0
- data/lib/rails_error_dashboard/services/backtrace_parser.rb +113 -0
- data/lib/rails_error_dashboard/services/baseline_alert_throttler.rb +88 -0
- data/lib/rails_error_dashboard/services/baseline_calculator.rb +269 -0
- data/lib/rails_error_dashboard/services/cascade_detector.rb +95 -0
- data/lib/rails_error_dashboard/services/pattern_detector.rb +268 -0
- data/lib/rails_error_dashboard/services/similarity_calculator.rb +144 -0
- data/lib/rails_error_dashboard/value_objects/error_context.rb +27 -1
- data/lib/rails_error_dashboard/version.rb +1 -1
- data/lib/rails_error_dashboard.rb +57 -7
- metadata +69 -10
- data/app/models/rails_error_dashboard/application_record.rb +0 -5
- data/lib/rails_error_dashboard/queries/developer_insights.rb +0 -277
- data/lib/rails_error_dashboard/queries/errors_list_v2.rb +0 -149
- data/lib/tasks/rails_error_dashboard_tasks.rake +0 -4
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
<div class="container-fluid py-4">
|
|
2
|
+
<!-- Page Header -->
|
|
3
|
+
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
4
|
+
<h1 class="h3 mb-0">
|
|
5
|
+
<i class="bi bi-speedometer2 me-2"></i>
|
|
6
|
+
Dashboard Overview
|
|
7
|
+
</h1>
|
|
8
|
+
<div class="text-muted">
|
|
9
|
+
<small>
|
|
10
|
+
Last updated: <%= Time.current.strftime("%B %d, %Y %I:%M %p") %>
|
|
11
|
+
</small>
|
|
12
|
+
</div>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<!-- Key Metrics Cards (Mobile-first: stack on mobile, 3 columns on desktop) -->
|
|
16
|
+
<div class="row g-3 mb-4">
|
|
17
|
+
<!-- Error Rate Card -->
|
|
18
|
+
<div class="col-12 col-md-6 col-lg-4">
|
|
19
|
+
<div class="card stat-card h-100 <%= error_rate_border_class(@stats[:error_rate]) %>">
|
|
20
|
+
<div class="card-body text-center">
|
|
21
|
+
<div class="stat-label mb-2">ERROR RATE</div>
|
|
22
|
+
<div class="stat-value <%= error_rate_text_class(@stats[:error_rate]) %>">
|
|
23
|
+
<%= @stats[:error_rate] %>%
|
|
24
|
+
</div>
|
|
25
|
+
<small class="text-muted">Errors per hour today</small>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<!-- Affected Users Card -->
|
|
31
|
+
<div class="col-12 col-md-6 col-lg-4">
|
|
32
|
+
<div class="card stat-card h-100">
|
|
33
|
+
<div class="card-body text-center">
|
|
34
|
+
<div class="stat-label mb-2">AFFECTED USERS</div>
|
|
35
|
+
<div class="stat-value text-primary">
|
|
36
|
+
<%= @stats[:affected_users_today] %>
|
|
37
|
+
</div>
|
|
38
|
+
<small class="<%= trend_color_class(@stats[:affected_users_change]) %>">
|
|
39
|
+
<%= trend_arrow(@stats[:affected_users_change]) %>
|
|
40
|
+
<%= @stats[:affected_users_change].abs %>
|
|
41
|
+
from yesterday
|
|
42
|
+
</small>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<!-- Trend Card -->
|
|
48
|
+
<div class="col-12 col-lg-4">
|
|
49
|
+
<div class="card stat-card h-100">
|
|
50
|
+
<div class="card-body text-center">
|
|
51
|
+
<div class="stat-label mb-2">ERROR TREND</div>
|
|
52
|
+
<div class="stat-value <%= trend_color_class(@stats[:trend_percentage]) %>">
|
|
53
|
+
<%= trend_arrow(@stats[:trend_percentage]) %>
|
|
54
|
+
<%= @stats[:trend_percentage].abs %>%
|
|
55
|
+
</div>
|
|
56
|
+
<small class="text-muted">
|
|
57
|
+
<%= trend_text(@stats[:trend_direction]) %>
|
|
58
|
+
</small>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<!-- Top 5 Errors by Impact (Mobile: stack, Desktop: 2 columns) -->
|
|
65
|
+
<% if @stats[:top_errors_by_impact].any? %>
|
|
66
|
+
<div class="card mb-4">
|
|
67
|
+
<div class="card-header bg-white d-flex justify-content-between align-items-center">
|
|
68
|
+
<h5 class="mb-0">
|
|
69
|
+
<i class="bi bi-exclamation-triangle me-2"></i>
|
|
70
|
+
Top 5 Errors by Impact
|
|
71
|
+
</h5>
|
|
72
|
+
<%= link_to "View All Errors →", errors_path, class: "btn btn-sm btn-outline-primary" %>
|
|
73
|
+
</div>
|
|
74
|
+
<div class="card-body">
|
|
75
|
+
<div class="row g-3">
|
|
76
|
+
<% @stats[:top_errors_by_impact].each do |error| %>
|
|
77
|
+
<div class="col-12 col-lg-6">
|
|
78
|
+
<%= link_to error_path(error[:id]), class: "text-decoration-none" do %>
|
|
79
|
+
<div class="card h-100 border-start border-<%= severity_color(error[:severity]) %> border-3">
|
|
80
|
+
<div class="card-body">
|
|
81
|
+
<div class="d-flex justify-content-between align-items-start mb-2">
|
|
82
|
+
<div class="flex-grow-1">
|
|
83
|
+
<h6 class="mb-1">
|
|
84
|
+
<span class="badge bg-<%= severity_color(error[:severity]) %> me-2">
|
|
85
|
+
<%= severity_icon(error[:severity]) %> <%= error[:severity].to_s.titleize %>
|
|
86
|
+
</span>
|
|
87
|
+
<code class="small"><%= error[:error_type] %></code>
|
|
88
|
+
</h6>
|
|
89
|
+
<p class="small text-muted mb-2"><%= error[:message] %></p>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
<div class="d-flex justify-content-between align-items-center small">
|
|
93
|
+
<span class="text-muted">
|
|
94
|
+
<i class="bi bi-people-fill me-1"></i>
|
|
95
|
+
<%= error[:affected_users] %> user<%= error[:affected_users] != 1 ? 's' : '' %>
|
|
96
|
+
</span>
|
|
97
|
+
<span class="text-muted">
|
|
98
|
+
<i class="bi bi-arrow-repeat me-1"></i>
|
|
99
|
+
<%= error[:occurrence_count] %> occurrence<%= error[:occurrence_count] != 1 ? 's' : '' %>
|
|
100
|
+
</span>
|
|
101
|
+
<span class="fw-bold text-danger">
|
|
102
|
+
Impact: <%= error[:impact_score] %>
|
|
103
|
+
</span>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
<% end %>
|
|
108
|
+
</div>
|
|
109
|
+
<% end %>
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
<% end %>
|
|
114
|
+
|
|
115
|
+
<!-- Platform Health Summary (Mobile: stack, Desktop: 3 columns) -->
|
|
116
|
+
<% if @platform_health.any? %>
|
|
117
|
+
<div class="card mb-4">
|
|
118
|
+
<div class="card-header bg-white d-flex justify-content-between align-items-center">
|
|
119
|
+
<h5 class="mb-0">
|
|
120
|
+
<i class="bi bi-phone me-2"></i>
|
|
121
|
+
Platform Health
|
|
122
|
+
</h5>
|
|
123
|
+
<% if RailsErrorDashboard.configuration.enable_platform_comparison %>
|
|
124
|
+
<%= link_to "Full Comparison →", platform_comparison_errors_path, class: "btn btn-sm btn-outline-primary" %>
|
|
125
|
+
<% end %>
|
|
126
|
+
</div>
|
|
127
|
+
<div class="card-body">
|
|
128
|
+
<div class="row g-3">
|
|
129
|
+
<% @platform_health.each do |platform, health| %>
|
|
130
|
+
<div class="col-12 col-md-4">
|
|
131
|
+
<div class="card h-100 border-<%= health_status_color(health[:health_status]) %>">
|
|
132
|
+
<div class="card-body">
|
|
133
|
+
<div class="d-flex justify-content-between align-items-start mb-3">
|
|
134
|
+
<h6 class="text-capitalize mb-0"><%= platform %></h6>
|
|
135
|
+
<span class="badge bg-<%= health_status_color(health[:health_status]) %>">
|
|
136
|
+
<%= health_status_text(health[:health_status]) %>
|
|
137
|
+
</span>
|
|
138
|
+
</div>
|
|
139
|
+
|
|
140
|
+
<div class="mb-2">
|
|
141
|
+
<small class="text-muted">Stability Score</small>
|
|
142
|
+
<div class="d-flex align-items-center">
|
|
143
|
+
<h4 class="mb-0 me-2"><%= health[:stability_score] %>/100</h4>
|
|
144
|
+
<div class="progress flex-grow-1" style="height: 8px;">
|
|
145
|
+
<div class="progress-bar bg-<%= health_status_color(health[:health_status]) %>"
|
|
146
|
+
role="progressbar"
|
|
147
|
+
style="width: <%= health[:stability_score] %>%">
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
|
|
153
|
+
<div class="small">
|
|
154
|
+
<div class="d-flex justify-content-between mb-1">
|
|
155
|
+
<span class="text-muted">Total Errors:</span>
|
|
156
|
+
<strong><%= health[:total_errors] %></strong>
|
|
157
|
+
</div>
|
|
158
|
+
<div class="d-flex justify-content-between mb-1">
|
|
159
|
+
<span class="text-muted">Critical:</span>
|
|
160
|
+
<strong class="text-danger"><%= health[:critical_errors] %></strong>
|
|
161
|
+
</div>
|
|
162
|
+
<div class="d-flex justify-content-between">
|
|
163
|
+
<span class="text-muted">Unresolved:</span>
|
|
164
|
+
<strong><%= health[:unresolved_errors] %></strong>
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
<% end %>
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
<% end %>
|
|
175
|
+
|
|
176
|
+
<!-- Critical Alerts (Last hour) -->
|
|
177
|
+
<% if @critical_alerts.any? %>
|
|
178
|
+
<div class="alert alert-danger border-danger mb-4" role="alert">
|
|
179
|
+
<div class="d-flex align-items-center mb-2">
|
|
180
|
+
<i class="bi bi-exclamation-triangle-fill fs-3 me-3"></i>
|
|
181
|
+
<div>
|
|
182
|
+
<h5 class="alert-heading mb-0">
|
|
183
|
+
🚨 <%= @critical_alerts.count %> Critical/High Error<%= @critical_alerts.count != 1 ? 's' : '' %> in Last Hour
|
|
184
|
+
</h5>
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
<div class="list-group list-group-flush">
|
|
188
|
+
<% @critical_alerts.first(5).each do |error| %>
|
|
189
|
+
<%= link_to error_path(error), class: "list-group-item list-group-item-action" do %>
|
|
190
|
+
<div class="d-flex justify-content-between align-items-start">
|
|
191
|
+
<div class="flex-grow-1">
|
|
192
|
+
<div class="d-flex align-items-center mb-1">
|
|
193
|
+
<span class="badge bg-<%= severity_color(error.severity) %> me-2">
|
|
194
|
+
<%= severity_icon(error.severity) %>
|
|
195
|
+
</span>
|
|
196
|
+
<code class="small"><%= error.error_type %></code>
|
|
197
|
+
</div>
|
|
198
|
+
<p class="mb-1 small"><%= error.message&.truncate(100) %></p>
|
|
199
|
+
<small class="text-muted">
|
|
200
|
+
<i class="bi bi-clock me-1"></i>
|
|
201
|
+
<%= time_ago_in_words(error.occurred_at) %> ago
|
|
202
|
+
•
|
|
203
|
+
<i class="bi bi-arrow-repeat me-1"></i>
|
|
204
|
+
<%= error.occurrence_count %> occurrence<%= error.occurrence_count != 1 ? 's' : '' %>
|
|
205
|
+
</small>
|
|
206
|
+
</div>
|
|
207
|
+
<i class="bi bi-chevron-right"></i>
|
|
208
|
+
</div>
|
|
209
|
+
<% end %>
|
|
210
|
+
<% end %>
|
|
211
|
+
</div>
|
|
212
|
+
<% if @critical_alerts.count > 5 %>
|
|
213
|
+
<div class="mt-2">
|
|
214
|
+
<%= link_to "View all #{@critical_alerts.count} critical errors →",
|
|
215
|
+
errors_path(severity: 'critical'),
|
|
216
|
+
class: "btn btn-sm btn-outline-danger" %>
|
|
217
|
+
</div>
|
|
218
|
+
<% end %>
|
|
219
|
+
</div>
|
|
220
|
+
<% end %>
|
|
221
|
+
|
|
222
|
+
<!-- Quick Actions -->
|
|
223
|
+
<div class="row g-3">
|
|
224
|
+
<div class="col-12 col-md-6 col-lg-3">
|
|
225
|
+
<%= link_to errors_path, class: "btn btn-lg btn-outline-primary w-100" do %>
|
|
226
|
+
<i class="bi bi-list-ul d-block fs-3 mb-2"></i>
|
|
227
|
+
View All Errors
|
|
228
|
+
<% end %>
|
|
229
|
+
</div>
|
|
230
|
+
<div class="col-12 col-md-6 col-lg-3">
|
|
231
|
+
<%= link_to analytics_errors_path, class: "btn btn-lg btn-outline-primary w-100" do %>
|
|
232
|
+
<i class="bi bi-graph-up d-block fs-3 mb-2"></i>
|
|
233
|
+
Analytics
|
|
234
|
+
<% end %>
|
|
235
|
+
</div>
|
|
236
|
+
<% if RailsErrorDashboard.configuration.enable_platform_comparison %>
|
|
237
|
+
<div class="col-12 col-md-6 col-lg-3">
|
|
238
|
+
<%= link_to platform_comparison_errors_path, class: "btn btn-lg btn-outline-primary w-100" do %>
|
|
239
|
+
<i class="bi bi-phone d-block fs-3 mb-2"></i>
|
|
240
|
+
Platform Health
|
|
241
|
+
<% end %>
|
|
242
|
+
</div>
|
|
243
|
+
<% end %>
|
|
244
|
+
<% if RailsErrorDashboard.configuration.enable_error_correlation %>
|
|
245
|
+
<div class="col-12 col-md-6 col-lg-3">
|
|
246
|
+
<%= link_to correlation_errors_path, class: "btn btn-lg btn-outline-primary w-100" do %>
|
|
247
|
+
<i class="bi bi-diagram-3 d-block fs-3 mb-2"></i>
|
|
248
|
+
Correlation
|
|
249
|
+
<% end %>
|
|
250
|
+
</div>
|
|
251
|
+
<% end %>
|
|
252
|
+
</div>
|
|
253
|
+
</div>
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
<div class="container-fluid py-4">
|
|
2
|
+
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
3
|
+
<h1 class="h3 mb-0">
|
|
4
|
+
<i class="bi bi-phone me-2"></i>
|
|
5
|
+
Platform Health Comparison
|
|
6
|
+
</h1>
|
|
7
|
+
|
|
8
|
+
<div class="btn-group" role="group">
|
|
9
|
+
<%= link_to platform_comparison_errors_path(days: 7), class: "btn btn-sm #{@days == 7 ? 'btn-primary' : 'btn-outline-primary'}" do %>
|
|
10
|
+
7 Days
|
|
11
|
+
<% end %>
|
|
12
|
+
<%= link_to platform_comparison_errors_path(days: 14), class: "btn btn-sm #{@days == 14 ? 'btn-primary' : 'btn-outline-primary'}" do %>
|
|
13
|
+
14 Days
|
|
14
|
+
<% end %>
|
|
15
|
+
<%= link_to platform_comparison_errors_path(days: 30), class: "btn btn-sm #{@days == 30 ? 'btn-primary' : 'btn-outline-primary'}" do %>
|
|
16
|
+
30 Days
|
|
17
|
+
<% end %>
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<% if @platform_health.empty? %>
|
|
22
|
+
<div class="alert alert-info">
|
|
23
|
+
<i class="bi bi-info-circle me-2"></i>
|
|
24
|
+
No platform data available for the selected time period.
|
|
25
|
+
</div>
|
|
26
|
+
<% else %>
|
|
27
|
+
<!-- Platform Health Summary Cards -->
|
|
28
|
+
<div class="row mb-4">
|
|
29
|
+
<% @platform_health.each do |platform, health| %>
|
|
30
|
+
<div class="col-md-6 col-lg-3 mb-3">
|
|
31
|
+
<div class="card h-100 <%= health[:health_status] == :healthy ? 'border-success' : health[:health_status] == :warning ? 'border-warning' : 'border-danger' %>">
|
|
32
|
+
<div class="card-body">
|
|
33
|
+
<h5 class="card-title text-capitalize">
|
|
34
|
+
<%= platform || 'Unknown' %>
|
|
35
|
+
<% if health[:health_status] == :healthy %>
|
|
36
|
+
<span class="badge bg-success float-end">Healthy</span>
|
|
37
|
+
<% elsif health[:health_status] == :warning %>
|
|
38
|
+
<span class="badge bg-warning float-end">Warning</span>
|
|
39
|
+
<% else %>
|
|
40
|
+
<span class="badge bg-danger float-end">Critical</span>
|
|
41
|
+
<% end %>
|
|
42
|
+
</h5>
|
|
43
|
+
|
|
44
|
+
<div class="mb-3">
|
|
45
|
+
<div class="d-flex justify-content-between align-items-center mb-1">
|
|
46
|
+
<small class="text-muted">Stability Score</small>
|
|
47
|
+
<strong><%= health[:stability_score] %>/100</strong>
|
|
48
|
+
</div>
|
|
49
|
+
<div class="progress" style="height: 8px;">
|
|
50
|
+
<div class="progress-bar <%= health[:stability_score] >= 80 ? 'bg-success' : health[:stability_score] >= 60 ? 'bg-warning' : 'bg-danger' %>"
|
|
51
|
+
role="progressbar"
|
|
52
|
+
style="width: <%= health[:stability_score] %>%">
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<div class="small">
|
|
58
|
+
<div class="d-flex justify-content-between mb-1">
|
|
59
|
+
<span class="text-muted">Total Errors:</span>
|
|
60
|
+
<strong><%= health[:total_errors] %></strong>
|
|
61
|
+
</div>
|
|
62
|
+
<div class="d-flex justify-content-between mb-1">
|
|
63
|
+
<span class="text-muted">Critical:</span>
|
|
64
|
+
<strong class="text-danger"><%= health[:critical_errors] %></strong>
|
|
65
|
+
</div>
|
|
66
|
+
<div class="d-flex justify-content-between mb-1">
|
|
67
|
+
<span class="text-muted">Unresolved:</span>
|
|
68
|
+
<strong><%= health[:unresolved_errors] %></strong>
|
|
69
|
+
</div>
|
|
70
|
+
<div class="d-flex justify-content-between mb-1">
|
|
71
|
+
<span class="text-muted">Resolution Rate:</span>
|
|
72
|
+
<strong><%= health[:resolution_rate] %>%</strong>
|
|
73
|
+
</div>
|
|
74
|
+
<div class="d-flex justify-content-between">
|
|
75
|
+
<span class="text-muted">Error Velocity:</span>
|
|
76
|
+
<strong class="<%= health[:error_velocity] > 0 ? 'text-danger' : 'text-success' %>">
|
|
77
|
+
<%= health[:error_velocity] > 0 ? '+' : '' %><%= health[:error_velocity] %>%
|
|
78
|
+
</strong>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
<% end %>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
<!-- Error Rate Comparison Chart -->
|
|
88
|
+
<div class="card mb-4">
|
|
89
|
+
<div class="card-header bg-white">
|
|
90
|
+
<h5 class="mb-0">
|
|
91
|
+
<i class="bi bi-bar-chart me-2"></i>
|
|
92
|
+
Error Rate by Platform
|
|
93
|
+
</h5>
|
|
94
|
+
</div>
|
|
95
|
+
<div class="card-body">
|
|
96
|
+
<canvas id="errorRateChart" height="80"></canvas>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
<!-- Daily Trend Charts -->
|
|
101
|
+
<div class="card mb-4">
|
|
102
|
+
<div class="card-header bg-white">
|
|
103
|
+
<h5 class="mb-0">
|
|
104
|
+
<i class="bi bi-graph-up me-2"></i>
|
|
105
|
+
Daily Error Trends
|
|
106
|
+
</h5>
|
|
107
|
+
</div>
|
|
108
|
+
<div class="card-body">
|
|
109
|
+
<canvas id="dailyTrendChart" height="80"></canvas>
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
<div class="row mb-4">
|
|
114
|
+
<!-- Severity Distribution -->
|
|
115
|
+
<div class="col-lg-6 mb-4">
|
|
116
|
+
<div class="card h-100">
|
|
117
|
+
<div class="card-header bg-white">
|
|
118
|
+
<h5 class="mb-0">
|
|
119
|
+
<i class="bi bi-exclamation-triangle me-2"></i>
|
|
120
|
+
Severity Distribution
|
|
121
|
+
</h5>
|
|
122
|
+
</div>
|
|
123
|
+
<div class="card-body">
|
|
124
|
+
<% @severity_distribution.each do |platform, severities| %>
|
|
125
|
+
<div class="mb-3">
|
|
126
|
+
<h6 class="text-capitalize"><%= platform || 'Unknown' %></h6>
|
|
127
|
+
<% total = severities.values.sum %>
|
|
128
|
+
<% if total > 0 %>
|
|
129
|
+
<div class="progress" style="height: 25px;">
|
|
130
|
+
<% if severities[:critical].to_i > 0 %>
|
|
131
|
+
<div class="progress-bar bg-danger"
|
|
132
|
+
role="progressbar"
|
|
133
|
+
style="width: <%= (severities[:critical].to_f / total * 100).round(1) %>%"
|
|
134
|
+
title="Critical: <%= severities[:critical] %>">
|
|
135
|
+
<%= severities[:critical] if (severities[:critical].to_f / total) > 0.1 %>
|
|
136
|
+
</div>
|
|
137
|
+
<% end %>
|
|
138
|
+
<% if severities[:high].to_i > 0 %>
|
|
139
|
+
<div class="progress-bar bg-warning"
|
|
140
|
+
role="progressbar"
|
|
141
|
+
style="width: <%= (severities[:high].to_f / total * 100).round(1) %>%"
|
|
142
|
+
title="High: <%= severities[:high] %>">
|
|
143
|
+
<%= severities[:high] if (severities[:high].to_f / total) > 0.1 %>
|
|
144
|
+
</div>
|
|
145
|
+
<% end %>
|
|
146
|
+
<% if severities[:medium].to_i > 0 %>
|
|
147
|
+
<div class="progress-bar bg-info"
|
|
148
|
+
role="progressbar"
|
|
149
|
+
style="width: <%= (severities[:medium].to_f / total * 100).round(1) %>%"
|
|
150
|
+
title="Medium: <%= severities[:medium] %>">
|
|
151
|
+
<%= severities[:medium] if (severities[:medium].to_f / total) > 0.1 %>
|
|
152
|
+
</div>
|
|
153
|
+
<% end %>
|
|
154
|
+
<% if severities[:low].to_i > 0 %>
|
|
155
|
+
<div class="progress-bar bg-secondary"
|
|
156
|
+
role="progressbar"
|
|
157
|
+
style="width: <%= (severities[:low].to_f / total * 100).round(1) %>%"
|
|
158
|
+
title="Low: <%= severities[:low] %>">
|
|
159
|
+
<%= severities[:low] if (severities[:low].to_f / total) > 0.1 %>
|
|
160
|
+
</div>
|
|
161
|
+
<% end %>
|
|
162
|
+
</div>
|
|
163
|
+
<div class="small text-muted mt-1">
|
|
164
|
+
Critical: <%= severities[:critical] || 0 %> |
|
|
165
|
+
High: <%= severities[:high] || 0 %> |
|
|
166
|
+
Medium: <%= severities[:medium] || 0 %> |
|
|
167
|
+
Low: <%= severities[:low] || 0 %>
|
|
168
|
+
</div>
|
|
169
|
+
<% else %>
|
|
170
|
+
<p class="text-muted small mb-0">No errors</p>
|
|
171
|
+
<% end %>
|
|
172
|
+
</div>
|
|
173
|
+
<% end %>
|
|
174
|
+
</div>
|
|
175
|
+
</div>
|
|
176
|
+
</div>
|
|
177
|
+
|
|
178
|
+
<!-- Resolution Time Comparison -->
|
|
179
|
+
<div class="col-lg-6 mb-4">
|
|
180
|
+
<div class="card h-100">
|
|
181
|
+
<div class="card-header bg-white">
|
|
182
|
+
<h5 class="mb-0">
|
|
183
|
+
<i class="bi bi-clock-history me-2"></i>
|
|
184
|
+
Average Resolution Time
|
|
185
|
+
</h5>
|
|
186
|
+
</div>
|
|
187
|
+
<div class="card-body">
|
|
188
|
+
<canvas id="resolutionTimeChart" height="200"></canvas>
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
</div>
|
|
192
|
+
</div>
|
|
193
|
+
|
|
194
|
+
<!-- Cross-Platform Errors -->
|
|
195
|
+
<% if @cross_platform_errors.any? %>
|
|
196
|
+
<div class="card mb-4">
|
|
197
|
+
<div class="card-header bg-white">
|
|
198
|
+
<h5 class="mb-0">
|
|
199
|
+
<i class="bi bi-layers me-2"></i>
|
|
200
|
+
Cross-Platform Errors
|
|
201
|
+
<span class="badge bg-secondary ms-2"><%= @cross_platform_errors.count %></span>
|
|
202
|
+
</h5>
|
|
203
|
+
</div>
|
|
204
|
+
<div class="card-body">
|
|
205
|
+
<div class="table-responsive">
|
|
206
|
+
<table class="table table-hover">
|
|
207
|
+
<thead>
|
|
208
|
+
<tr>
|
|
209
|
+
<th>Error Type</th>
|
|
210
|
+
<th>Platforms</th>
|
|
211
|
+
<th>Total Occurrences</th>
|
|
212
|
+
<th>Breakdown</th>
|
|
213
|
+
</tr>
|
|
214
|
+
</thead>
|
|
215
|
+
<tbody>
|
|
216
|
+
<% @cross_platform_errors.first(10).each do |error| %>
|
|
217
|
+
<tr>
|
|
218
|
+
<td>
|
|
219
|
+
<code class="small"><%= error[:error_type] %></code>
|
|
220
|
+
</td>
|
|
221
|
+
<td>
|
|
222
|
+
<% error[:platforms].each do |platform| %>
|
|
223
|
+
<span class="badge bg-secondary text-capitalize me-1"><%= platform %></span>
|
|
224
|
+
<% end %>
|
|
225
|
+
</td>
|
|
226
|
+
<td>
|
|
227
|
+
<strong><%= error[:total_occurrences] %></strong>
|
|
228
|
+
</td>
|
|
229
|
+
<td class="small text-muted">
|
|
230
|
+
<% error[:platform_breakdown].each do |platform, count| %>
|
|
231
|
+
<%= platform %>: <%= count %>
|
|
232
|
+
<% end %>
|
|
233
|
+
</td>
|
|
234
|
+
</tr>
|
|
235
|
+
<% end %>
|
|
236
|
+
</tbody>
|
|
237
|
+
</table>
|
|
238
|
+
</div>
|
|
239
|
+
</div>
|
|
240
|
+
</div>
|
|
241
|
+
<% end %>
|
|
242
|
+
|
|
243
|
+
<!-- Top Errors by Platform -->
|
|
244
|
+
<div class="row">
|
|
245
|
+
<% @top_errors_by_platform.each do |platform, errors| %>
|
|
246
|
+
<div class="col-lg-6 mb-4">
|
|
247
|
+
<div class="card h-100">
|
|
248
|
+
<div class="card-header bg-white">
|
|
249
|
+
<h5 class="mb-0 text-capitalize">
|
|
250
|
+
<i class="bi bi-bug me-2"></i>
|
|
251
|
+
Top Errors - <%= platform || 'Unknown' %>
|
|
252
|
+
</h5>
|
|
253
|
+
</div>
|
|
254
|
+
<div class="card-body">
|
|
255
|
+
<% if errors.any? %>
|
|
256
|
+
<div class="list-group list-group-flush">
|
|
257
|
+
<% errors.first(5).each do |error| %>
|
|
258
|
+
<%= link_to error_path(error[:id]), class: "list-group-item list-group-item-action" do %>
|
|
259
|
+
<div class="d-flex justify-content-between align-items-start">
|
|
260
|
+
<div class="flex-grow-1">
|
|
261
|
+
<code class="small"><%= error[:error_type] %></code>
|
|
262
|
+
<p class="mb-1 small text-muted"><%= error[:message] %></p>
|
|
263
|
+
</div>
|
|
264
|
+
<span class="badge bg-primary rounded-pill ms-2">
|
|
265
|
+
<%= error[:occurrence_count] %>
|
|
266
|
+
</span>
|
|
267
|
+
</div>
|
|
268
|
+
<% end %>
|
|
269
|
+
<% end %>
|
|
270
|
+
</div>
|
|
271
|
+
<% else %>
|
|
272
|
+
<p class="text-muted mb-0">No errors found</p>
|
|
273
|
+
<% end %>
|
|
274
|
+
</div>
|
|
275
|
+
</div>
|
|
276
|
+
</div>
|
|
277
|
+
<% end %>
|
|
278
|
+
</div>
|
|
279
|
+
<% end %>
|
|
280
|
+
</div>
|
|
281
|
+
|
|
282
|
+
<script>
|
|
283
|
+
// Error Rate Chart
|
|
284
|
+
const errorRateCtx = document.getElementById('errorRateChart');
|
|
285
|
+
if (errorRateCtx) {
|
|
286
|
+
new Chart(errorRateCtx, {
|
|
287
|
+
type: 'bar',
|
|
288
|
+
data: {
|
|
289
|
+
labels: <%= raw @error_rate_by_platform.keys.map { |k| k.to_s.capitalize }.to_json %>,
|
|
290
|
+
datasets: [{
|
|
291
|
+
label: 'Total Errors',
|
|
292
|
+
data: <%= raw @error_rate_by_platform.values.to_json %>,
|
|
293
|
+
backgroundColor: (function() {
|
|
294
|
+
const isDark = document.body.classList.contains('dark-mode');
|
|
295
|
+
const config = getCatppuccinChartConfig(isDark);
|
|
296
|
+
return config.colors.blue.replace('rgb', 'rgba').replace(')', ', 0.5)');
|
|
297
|
+
})(),
|
|
298
|
+
borderColor: (function() {
|
|
299
|
+
const isDark = document.body.classList.contains('dark-mode');
|
|
300
|
+
const config = getCatppuccinChartConfig(isDark);
|
|
301
|
+
return config.colors.blue;
|
|
302
|
+
})(),
|
|
303
|
+
borderWidth: 1
|
|
304
|
+
}]
|
|
305
|
+
},
|
|
306
|
+
options: {
|
|
307
|
+
responsive: true,
|
|
308
|
+
maintainAspectRatio: true,
|
|
309
|
+
plugins: {
|
|
310
|
+
legend: { display: false }
|
|
311
|
+
},
|
|
312
|
+
scales: {
|
|
313
|
+
y: { beginAtZero: true }
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Daily Trend Chart
|
|
320
|
+
const dailyTrendCtx = document.getElementById('dailyTrendChart');
|
|
321
|
+
if (dailyTrendCtx) {
|
|
322
|
+
// Use Catppuccin colors from global theme config
|
|
323
|
+
const isDark = document.body.classList.contains('dark-mode');
|
|
324
|
+
|
|
325
|
+
const datasets = <%= raw @daily_trends.map { |platform, data|
|
|
326
|
+
{
|
|
327
|
+
label: platform.to_s.capitalize,
|
|
328
|
+
data: data.values,
|
|
329
|
+
platform: platform.to_s.downcase,
|
|
330
|
+
tension: 0.4
|
|
331
|
+
}
|
|
332
|
+
}.to_json %>;
|
|
333
|
+
|
|
334
|
+
// Apply Catppuccin platform colors
|
|
335
|
+
datasets.forEach(ds => {
|
|
336
|
+
const color = getPlatformColor(ds.platform, isDark);
|
|
337
|
+
ds.borderColor = color;
|
|
338
|
+
// Add transparency for background
|
|
339
|
+
ds.backgroundColor = color.replace('rgb', 'rgba').replace(')', ', 0.1)');
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
new Chart(dailyTrendCtx, {
|
|
343
|
+
type: 'line',
|
|
344
|
+
data: {
|
|
345
|
+
labels: <%= raw @daily_trends.values.first&.keys&.map { |d| d.strftime('%b %d') }&.to_json || [].to_json %>,
|
|
346
|
+
datasets: datasets
|
|
347
|
+
},
|
|
348
|
+
options: {
|
|
349
|
+
responsive: true,
|
|
350
|
+
maintainAspectRatio: true,
|
|
351
|
+
plugins: {
|
|
352
|
+
legend: { position: 'top' }
|
|
353
|
+
},
|
|
354
|
+
scales: {
|
|
355
|
+
y: { beginAtZero: true }
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Resolution Time Chart
|
|
362
|
+
const resolutionTimeCtx = document.getElementById('resolutionTimeChart');
|
|
363
|
+
if (resolutionTimeCtx) {
|
|
364
|
+
const platforms = <%= raw @resolution_times.keys.map { |k| k.to_s.capitalize }.to_json %>;
|
|
365
|
+
const times = <%= raw @resolution_times.values.map { |v| v || 0 }.to_json %>;
|
|
366
|
+
|
|
367
|
+
new Chart(resolutionTimeCtx, {
|
|
368
|
+
type: 'horizontalBar',
|
|
369
|
+
data: {
|
|
370
|
+
labels: platforms,
|
|
371
|
+
datasets: [{
|
|
372
|
+
label: 'Hours to Resolve',
|
|
373
|
+
data: times,
|
|
374
|
+
backgroundColor: (function() {
|
|
375
|
+
const isDark = document.body.classList.contains('dark-mode');
|
|
376
|
+
const config = getCatppuccinChartConfig(isDark);
|
|
377
|
+
return config.colors.orange.replace('rgb', 'rgba').replace(')', ', 0.5)');
|
|
378
|
+
})(),
|
|
379
|
+
borderColor: (function() {
|
|
380
|
+
const isDark = document.body.classList.contains('dark-mode');
|
|
381
|
+
const config = getCatppuccinChartConfig(isDark);
|
|
382
|
+
return config.colors.orange;
|
|
383
|
+
})(),
|
|
384
|
+
borderWidth: 1
|
|
385
|
+
}]
|
|
386
|
+
},
|
|
387
|
+
options: {
|
|
388
|
+
responsive: true,
|
|
389
|
+
maintainAspectRatio: false,
|
|
390
|
+
plugins: {
|
|
391
|
+
legend: { display: false }
|
|
392
|
+
},
|
|
393
|
+
scales: {
|
|
394
|
+
x: { beginAtZero: true, title: { display: true, text: 'Hours' } }
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
</script>
|