rails_error_dashboard 0.8.3 → 0.9.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 (111) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +35 -0
  3. data/app/controllers/rails_error_dashboard/application_controller.rb +156 -0
  4. data/app/controllers/rails_error_dashboard/errors_controller.rb +95 -73
  5. data/app/controllers/rails_error_dashboard/locales_controller.rb +87 -0
  6. data/app/helpers/rails_error_dashboard/application_helper.rb +20 -14
  7. data/app/helpers/rails_error_dashboard/backtrace_helper.rb +15 -6
  8. data/app/helpers/rails_error_dashboard/i18n_helper.rb +154 -0
  9. data/app/helpers/rails_error_dashboard/mailer_i18n_helper.rb +69 -0
  10. data/app/helpers/rails_error_dashboard/overview_helper.rb +18 -6
  11. data/app/helpers/rails_error_dashboard/user_agent_helper.rb +25 -11
  12. data/app/jobs/rails_error_dashboard/application_job.rb +5 -0
  13. data/app/jobs/rails_error_dashboard/baseline_alert_job.rb +18 -16
  14. data/app/jobs/rails_error_dashboard/concerns/localized_job.rb +83 -0
  15. data/app/jobs/rails_error_dashboard/discord_error_notification_job.rb +4 -2
  16. data/app/jobs/rails_error_dashboard/email_error_notification_job.rb +4 -2
  17. data/app/jobs/rails_error_dashboard/pagerduty_error_notification_job.rb +6 -2
  18. data/app/jobs/rails_error_dashboard/scheduled_digest_job.rb +8 -3
  19. data/app/jobs/rails_error_dashboard/slack_error_notification_job.rb +6 -4
  20. data/app/jobs/rails_error_dashboard/storm_notification_job.rb +26 -8
  21. data/app/jobs/rails_error_dashboard/webhook_error_notification_job.rb +4 -2
  22. data/app/mailers/rails_error_dashboard/application_mailer.rb +14 -0
  23. data/app/mailers/rails_error_dashboard/digest_mailer.rb +18 -2
  24. data/app/mailers/rails_error_dashboard/error_notification_mailer.rb +19 -2
  25. data/app/views/layouts/rails_error_dashboard.html.erb +255 -92
  26. data/app/views/rails_error_dashboard/digest_mailer/digest_summary.html.erb +23 -22
  27. data/app/views/rails_error_dashboard/digest_mailer/digest_summary.text.erb +30 -19
  28. data/app/views/rails_error_dashboard/error_notification_mailer/error_alert.html.erb +18 -16
  29. data/app/views/rails_error_dashboard/error_notification_mailer/error_alert.text.erb +21 -14
  30. data/app/views/rails_error_dashboard/errors/_ai_help_panel.html.erb +11 -7
  31. data/app/views/rails_error_dashboard/errors/_breadcrumbs_group.html.erb +45 -38
  32. data/app/views/rails_error_dashboard/errors/_co_occurring_errors.html.erb +17 -14
  33. data/app/views/rails_error_dashboard/errors/_discussion.html.erb +27 -9
  34. data/app/views/rails_error_dashboard/errors/_error_cascades.html.erb +25 -21
  35. data/app/views/rails_error_dashboard/errors/_error_info.html.erb +25 -22
  36. data/app/views/rails_error_dashboard/errors/_error_row.html.erb +24 -12
  37. data/app/views/rails_error_dashboard/errors/_instance_variables.html.erb +12 -11
  38. data/app/views/rails_error_dashboard/errors/_issue_section.html.erb +19 -13
  39. data/app/views/rails_error_dashboard/errors/_llm_summary.html.erb +14 -14
  40. data/app/views/rails_error_dashboard/errors/_local_variables.html.erb +8 -8
  41. data/app/views/rails_error_dashboard/errors/_modals.html.erb +60 -52
  42. data/app/views/rails_error_dashboard/errors/_pattern_insights.html.erb +59 -49
  43. data/app/views/rails_error_dashboard/errors/_request_context.html.erb +23 -20
  44. data/app/views/rails_error_dashboard/errors/_show_scripts.html.erb +14 -14
  45. data/app/views/rails_error_dashboard/errors/_sidebar_metadata.html.erb +118 -93
  46. data/app/views/rails_error_dashboard/errors/_similar_errors.html.erb +18 -12
  47. data/app/views/rails_error_dashboard/errors/_source_code.html.erb +20 -5
  48. data/app/views/rails_error_dashboard/errors/_stats.html.erb +5 -5
  49. data/app/views/rails_error_dashboard/errors/_timeline.html.erb +7 -7
  50. data/app/views/rails_error_dashboard/errors/_user_errors_table.html.erb +15 -12
  51. data/app/views/rails_error_dashboard/errors/actioncable_health_summary.html.erb +25 -25
  52. data/app/views/rails_error_dashboard/errors/activestorage_health_summary.html.erb +28 -28
  53. data/app/views/rails_error_dashboard/errors/analytics.html.erb +82 -82
  54. data/app/views/rails_error_dashboard/errors/cache_health_summary.html.erb +27 -27
  55. data/app/views/rails_error_dashboard/errors/correlation.html.erb +61 -57
  56. data/app/views/rails_error_dashboard/errors/database_health_summary.html.erb +64 -65
  57. data/app/views/rails_error_dashboard/errors/deprecations.html.erb +23 -23
  58. data/app/views/rails_error_dashboard/errors/diagnostic_dumps.html.erb +48 -36
  59. data/app/views/rails_error_dashboard/errors/index.html.erb +121 -70
  60. data/app/views/rails_error_dashboard/errors/job_health_summary.html.erb +30 -30
  61. data/app/views/rails_error_dashboard/errors/llm_health_summary.html.erb +38 -39
  62. data/app/views/rails_error_dashboard/errors/n_plus_one_summary.html.erb +28 -25
  63. data/app/views/rails_error_dashboard/errors/overview.html.erb +45 -39
  64. data/app/views/rails_error_dashboard/errors/platform_comparison.html.erb +45 -44
  65. data/app/views/rails_error_dashboard/errors/rack_attack_summary.html.erb +39 -28
  66. data/app/views/rails_error_dashboard/errors/releases.html.erb +46 -47
  67. data/app/views/rails_error_dashboard/errors/settings/_value_badge.html.erb +48 -44
  68. data/app/views/rails_error_dashboard/errors/settings.html.erb +122 -112
  69. data/app/views/rails_error_dashboard/errors/show.html.erb +44 -37
  70. data/app/views/rails_error_dashboard/errors/storms.html.erb +30 -24
  71. data/app/views/rails_error_dashboard/errors/swallowed_exceptions.html.erb +26 -26
  72. data/app/views/rails_error_dashboard/errors/user_impact.html.erb +31 -30
  73. data/config/locales/de.yml +2007 -0
  74. data/config/locales/en.yml +2719 -0
  75. data/config/locales/es.yml +2079 -0
  76. data/config/locales/fr.yml +2082 -0
  77. data/config/locales/it.yml +2060 -0
  78. data/config/locales/ja.yml +1802 -0
  79. data/config/locales/pl.yml +2108 -0
  80. data/config/locales/pt-BR.yml +2064 -0
  81. data/config/locales/ru.yml +2109 -0
  82. data/config/locales/uk.yml +2106 -0
  83. data/config/locales/zh-CN.yml +1791 -0
  84. data/config/routes.rb +5 -0
  85. data/db/migrate/20260325000001_fix_swallowed_exceptions_index_for_mysql.rb +9 -3
  86. data/lib/rails_error_dashboard/commands/batch_delete_errors.rb +3 -1
  87. data/lib/rails_error_dashboard/commands/batch_mute_errors.rb +4 -2
  88. data/lib/rails_error_dashboard/commands/batch_resolve_errors.rb +4 -2
  89. data/lib/rails_error_dashboard/commands/batch_unmute_errors.rb +4 -2
  90. data/lib/rails_error_dashboard/commands/create_issue.rb +5 -3
  91. data/lib/rails_error_dashboard/commands/link_existing_issue.rb +4 -2
  92. data/lib/rails_error_dashboard/commands/log_error.rb +1 -1
  93. data/lib/rails_error_dashboard/configuration.rb +23 -0
  94. data/lib/rails_error_dashboard/current.rb +56 -0
  95. data/lib/rails_error_dashboard/i18n_store.rb +293 -0
  96. data/lib/rails_error_dashboard/private_backend.rb +213 -0
  97. data/lib/rails_error_dashboard/services/baseline_alert_payload_builder.rb +89 -22
  98. data/lib/rails_error_dashboard/services/digest_builder.rb +21 -7
  99. data/lib/rails_error_dashboard/services/discord_payload_builder.rb +27 -15
  100. data/lib/rails_error_dashboard/services/error_notification_dispatcher.rb +10 -5
  101. data/lib/rails_error_dashboard/services/localized_time_formatter.rb +117 -0
  102. data/lib/rails_error_dashboard/services/notification_helpers.rb +87 -4
  103. data/lib/rails_error_dashboard/services/pagerduty_payload_builder.rb +22 -5
  104. data/lib/rails_error_dashboard/services/slack_payload_builder.rb +48 -28
  105. data/lib/rails_error_dashboard/services/storm_protection/gate.rb +2 -1
  106. data/lib/rails_error_dashboard/services/webhook_payload_builder.rb +15 -1
  107. data/lib/rails_error_dashboard/translation.rb +39 -0
  108. data/lib/rails_error_dashboard/version.rb +1 -1
  109. data/lib/rails_error_dashboard.rb +4 -0
  110. data/lib/tasks/error_dashboard.rake +5 -1
  111. metadata +24 -4
@@ -1,21 +1,21 @@
1
- <% content_for :page_title, "Error Correlation" %>
1
+ <% content_for :page_title, red_t("red.analytics.correlation.title") %>
2
2
 
3
3
  <div>
4
4
  <div class="d-flex justify-content-between align-items-center mb-4">
5
5
  <h1 style="font-size: 20px; font-weight: 700; margin: 0;">
6
6
  <i class="bi bi-diagram-3 me-2"></i>
7
- Error Correlation Analysis
7
+ <%= red_t("red.analytics.correlation.title") %>
8
8
  </h1>
9
9
 
10
10
  <div class="btn-group" role="group">
11
11
  <%= link_to correlation_errors_path(days: 7), class: "btn btn-sm #{@days == 7 ? 'btn-primary' : 'btn-outline-primary'}" do %>
12
- 7 Days
12
+ <%= red_t("red.common.days_filter.7") %>
13
13
  <% end %>
14
14
  <%= link_to correlation_errors_path(days: 30), class: "btn btn-sm #{@days == 30 ? 'btn-primary' : 'btn-outline-primary'}" do %>
15
- 30 Days
15
+ <%= red_t("red.common.days_filter.30") %>
16
16
  <% end %>
17
17
  <%= link_to correlation_errors_path(days: 90), class: "btn btn-sm #{@days == 90 ? 'btn-primary' : 'btn-outline-primary'}" do %>
18
- 90 Days
18
+ <%= red_t("red.common.days_filter.90") %>
19
19
  <% end %>
20
20
  </div>
21
21
  </div>
@@ -26,27 +26,31 @@
26
26
  <div class="card-header">
27
27
  <h5 class="mb-0">
28
28
  <i class="bi bi-graph-up-arrow me-2"></i>
29
- Trend Analysis
29
+ <%= red_t("red.analytics.correlation.trend_analysis.title") %>
30
30
  </h5>
31
31
  </div>
32
32
  <div class="card-body">
33
33
  <div class="row">
34
34
  <div class="col-md-4">
35
- <h6 class="text-muted">Current Period</h6>
35
+ <h6 class="text-muted"><%= red_t("red.analytics.correlation.trend_analysis.current_period") %></h6>
36
36
  <div class="display-6"><%= @period_comparison[:current_period][:count] %></div>
37
37
  <small class="text-muted">
38
- <%= @period_comparison[:current_period][:start].strftime("%b %d") %> - <%= @period_comparison[:current_period][:end].strftime("%b %d") %>
38
+ <%= red_t("red.analytics.correlation.trend_analysis.period_range",
39
+ start: @period_comparison[:current_period][:start].strftime("%b %d"),
40
+ end: @period_comparison[:current_period][:end].strftime("%b %d")) %>
39
41
  </small>
40
42
  </div>
41
43
  <div class="col-md-4">
42
- <h6 class="text-muted">Previous Period</h6>
44
+ <h6 class="text-muted"><%= red_t("red.analytics.correlation.trend_analysis.previous_period") %></h6>
43
45
  <div class="display-6"><%= @period_comparison[:previous_period][:count] %></div>
44
46
  <small class="text-muted">
45
- <%= @period_comparison[:previous_period][:start].strftime("%b %d") %> - <%= @period_comparison[:previous_period][:end].strftime("%b %d") %>
47
+ <%= red_t("red.analytics.correlation.trend_analysis.period_range",
48
+ start: @period_comparison[:previous_period][:start].strftime("%b %d"),
49
+ end: @period_comparison[:previous_period][:end].strftime("%b %d")) %>
46
50
  </small>
47
51
  </div>
48
52
  <div class="col-md-4">
49
- <h6 class="text-muted">Change</h6>
53
+ <h6 class="text-muted"><%= red_t("red.analytics.correlation.trend_analysis.change") %></h6>
50
54
  <div class="display-6 <%= @period_comparison[:change] > 0 ? 'text-danger' : 'text-success' %>">
51
55
  <%= @period_comparison[:change] > 0 ? '+' : '' %><%= @period_comparison[:change] %>
52
56
  </div>
@@ -54,7 +58,7 @@
54
58
  <span class="badge bg-<%= @period_comparison[:change] > 0 ? 'danger' : 'success' %> fs-6">
55
59
  <%= @period_comparison[:change_percentage] > 0 ? '+' : '' %><%= @period_comparison[:change_percentage] %>%
56
60
  </span>
57
- <span class="ms-2 text-muted text-capitalize"><%= @period_comparison[:trend].to_s.gsub('_', ' ') %></span>
61
+ <span class="ms-2 text-muted"><%= red_t("red.analytics.trend.#{@period_comparison[:trend]}", default: @period_comparison[:trend].to_s) %></span>
58
62
  </div>
59
63
  </div>
60
64
  </div>
@@ -68,23 +72,23 @@
68
72
  <div class="card-header bg-danger text-white">
69
73
  <h5 class="mb-0">
70
74
  <i class="bi bi-exclamation-triangle-fill me-2"></i>
71
- Problematic Releases
75
+ <%= red_t("red.analytics.correlation.problematic_releases.title") %>
72
76
  <span class="badge text-danger"><%= @problematic_releases.count %></span>
73
77
  </h5>
74
- <small>Versions with more than 2x the average error rate</small>
78
+ <small><%= red_t("red.analytics.correlation.problematic_releases.hint") %></small>
75
79
  </div>
76
80
  <div class="card-body">
77
81
  <div class="table-responsive">
78
82
  <table class="table table-hover">
79
83
  <thead>
80
84
  <tr>
81
- <th>Version</th>
82
- <th>Error Count</th>
83
- <th>Deviation from Avg</th>
84
- <th>Critical Errors</th>
85
- <th>Unique Error Types</th>
86
- <th>Platforms</th>
87
- <th>Actions</th>
85
+ <th><%= red_t("red.analytics.correlation.problematic_releases.column_version") %></th>
86
+ <th><%= red_t("red.analytics.correlation.problematic_releases.column_error_count") %></th>
87
+ <th><%= red_t("red.analytics.correlation.problematic_releases.column_deviation") %></th>
88
+ <th><%= red_t("red.analytics.correlation.problematic_releases.column_critical") %></th>
89
+ <th><%= red_t("red.analytics.correlation.problematic_releases.column_unique_types") %></th>
90
+ <th><%= red_t("red.analytics.correlation.problematic_releases.column_platforms") %></th>
91
+ <th><%= red_t("red.analytics.correlation.problematic_releases.column_actions") %></th>
88
92
  </tr>
89
93
  </thead>
90
94
  <tbody>
@@ -109,7 +113,7 @@
109
113
  <% end %>
110
114
  </td>
111
115
  <td>
112
- <%= link_to "View", errors_path(app_version: release[:version], unresolved: '0'), class: "btn btn-sm btn-outline-primary" %>
116
+ <%= link_to red_t("red.analytics.correlation.problematic_releases.view"), errors_path(app_version: release[:version], unresolved: '0'), class: "btn btn-sm btn-outline-primary" %>
113
117
  </td>
114
118
  </tr>
115
119
  <% end %>
@@ -128,7 +132,7 @@
128
132
  <div class="card-header">
129
133
  <h5 class="mb-0">
130
134
  <i class="bi bi-tag me-2"></i>
131
- Errors by App Version
135
+ <%= red_t("red.analytics.correlation.by_version.title") %>
132
136
  </h5>
133
137
  </div>
134
138
  <div class="card-body">
@@ -136,10 +140,10 @@
136
140
  <table class="table table-sm table-hover">
137
141
  <thead>
138
142
  <tr>
139
- <th>Version</th>
140
- <th>Errors</th>
141
- <th>Types</th>
142
- <th>Critical</th>
143
+ <th><%= red_t("red.analytics.correlation.by_version.column_version") %></th>
144
+ <th><%= red_t("red.analytics.correlation.by_version.column_errors") %></th>
145
+ <th><%= red_t("red.analytics.correlation.by_version.column_types") %></th>
146
+ <th><%= red_t("red.analytics.correlation.by_version.column_critical") %></th>
143
147
  </tr>
144
148
  </thead>
145
149
  <tbody>
@@ -176,7 +180,7 @@
176
180
  <div class="card-header">
177
181
  <h5 class="mb-0">
178
182
  <i class="bi bi-git me-2"></i>
179
- Errors by Git SHA
183
+ <%= red_t("red.analytics.correlation.by_git_sha.title") %>
180
184
  </h5>
181
185
  </div>
182
186
  <div class="card-body">
@@ -184,10 +188,10 @@
184
188
  <table class="table table-sm table-hover">
185
189
  <thead>
186
190
  <tr>
187
- <th>SHA</th>
188
- <th>Errors</th>
189
- <th>Types</th>
190
- <th>Versions</th>
191
+ <th><%= red_t("red.analytics.correlation.by_git_sha.column_sha") %></th>
192
+ <th><%= red_t("red.analytics.correlation.by_git_sha.column_errors") %></th>
193
+ <th><%= red_t("red.analytics.correlation.by_git_sha.column_types") %></th>
194
+ <th><%= red_t("red.analytics.correlation.by_git_sha.column_versions") %></th>
191
195
  </tr>
192
196
  </thead>
193
197
  <tbody>
@@ -222,10 +226,10 @@
222
226
  <div class="card-header">
223
227
  <h5 class="mb-0">
224
228
  <i class="bi bi-people-fill me-2"></i>
225
- Users Affected by Multiple Error Types
229
+ <%= red_t("red.analytics.correlation.multi_error_users.title") %>
226
230
  <span class="badge bg-info"><%= @multi_error_users.count %></span>
227
231
  </h5>
228
- <small class="text-muted">Users experiencing 2+ different error types</small>
232
+ <small class="text-muted"><%= red_t("red.analytics.correlation.multi_error_users.hint") %></small>
229
233
  </div>
230
234
  <div class="card-body">
231
235
  <%= render partial: 'user_errors_table',
@@ -241,7 +245,7 @@
241
245
  <div class="card-body pt-0 border-top">
242
246
  <% if @multi_error_users.count > 20 %>
243
247
  <p class="text-muted small mb-0">
244
- Showing 20 of <%= @multi_error_users.count %> users
248
+ <%= red_t("red.analytics.correlation.multi_error_users.truncated", shown: 20, total: @multi_error_users.count) %>
245
249
  </p>
246
250
  <% end %>
247
251
  </div>
@@ -254,21 +258,21 @@
254
258
  <div class="card-header">
255
259
  <h5 class="mb-0">
256
260
  <i class="bi bi-clock-history me-2"></i>
257
- Time-Correlated Errors
261
+ <%= red_t("red.analytics.correlation.time_correlated.title") %>
258
262
  <span class="badge bg-info"><%= @time_correlated_errors.count %></span>
259
263
  </h5>
260
- <small class="text-muted">Error types that occur at similar times of day</small>
264
+ <small class="text-muted"><%= red_t("red.analytics.correlation.time_correlated.hint") %></small>
261
265
  </div>
262
266
  <div class="card-body">
263
267
  <div class="table-responsive">
264
268
  <table class="table table-hover">
265
269
  <thead>
266
270
  <tr>
267
- <th>Error Type A</th>
271
+ <th><%= red_t("red.analytics.correlation.time_correlated.column_error_type_a") %></th>
268
272
  <th></th>
269
- <th>Error Type B</th>
270
- <th>Correlation</th>
271
- <th>Strength</th>
273
+ <th><%= red_t("red.analytics.correlation.time_correlated.column_error_type_b") %></th>
274
+ <th><%= red_t("red.analytics.correlation.time_correlated.column_correlation") %></th>
275
+ <th><%= red_t("red.analytics.correlation.time_correlated.column_strength") %></th>
272
276
  </tr>
273
277
  </thead>
274
278
  <tbody>
@@ -288,11 +292,11 @@
288
292
  <td>
289
293
  <% case data[:strength] %>
290
294
  <% when :strong %>
291
- <span class="badge bg-success">Strong</span>
295
+ <span class="badge bg-success"><%= red_t("red.analytics.correlation_strength.strong") %></span>
292
296
  <% when :moderate %>
293
- <span class="badge bg-info">Moderate</span>
297
+ <span class="badge bg-info"><%= red_t("red.analytics.correlation_strength.moderate") %></span>
294
298
  <% else %>
295
- <span class="badge bg-secondary">Weak</span>
299
+ <span class="badge bg-secondary"><%= red_t("red.analytics.correlation_strength.weak") %></span>
296
300
  <% end %>
297
301
  </td>
298
302
  </tr>
@@ -302,7 +306,7 @@
302
306
  </div>
303
307
  <% if @time_correlated_errors.count > 15 %>
304
308
  <p class="text-muted small mb-0">
305
- Showing 15 of <%= @time_correlated_errors.count %> correlated error pairs
309
+ <%= red_t("red.analytics.correlation.time_correlated.truncated", shown: 15, total: @time_correlated_errors.count) %>
306
310
  </p>
307
311
  <% end %>
308
312
  </div>
@@ -315,7 +319,7 @@
315
319
  <div class="card-header">
316
320
  <h5 class="mb-0">
317
321
  <i class="bi bi-phone me-2"></i>
318
- Platform-Specific vs Cross-Platform Errors
322
+ <%= red_t("red.analytics.correlation.platform_specific.title") %>
319
323
  </h5>
320
324
  </div>
321
325
  <div class="card-body">
@@ -330,16 +334,16 @@
330
334
  <div>
331
335
  <code class="small"><%= error_data[:error_type] %></code>
332
336
  <div class="small text-muted mt-1">
333
- <%= error_data[:count] %> occurrences
337
+ <%= red_tp("red.analytics.correlation.platform_specific.occurrences", count: error_data[:count]) %>
334
338
  </div>
335
339
  </div>
336
340
  <div>
337
341
  <% if error_data[:platform_specific] %>
338
- <span class="badge bg-primary">Platform-Specific</span>
342
+ <span class="badge bg-primary"><%= red_t("red.analytics.correlation.platform_specific.platform_specific") %></span>
339
343
  <% else %>
340
- <span class="badge bg-secondary">Cross-Platform</span>
344
+ <span class="badge bg-secondary"><%= red_t("red.analytics.correlation.platform_specific.cross_platform") %></span>
341
345
  <div class="small text-muted mt-1">
342
- Also on: <%= error_data[:also_on].join(', ') %>
346
+ <%= red_t("red.analytics.correlation.platform_specific.also_on", platforms: error_data[:also_on].join(', ')) %>
343
347
  </div>
344
348
  <% end %>
345
349
  </div>
@@ -358,22 +362,22 @@
358
362
  <% if @errors_by_version.blank? && @multi_error_users.blank? && @time_correlated_errors.blank? %>
359
363
  <div class="red-empty-state">
360
364
  <i class="bi bi-diagram-3 display-1 text-muted mb-3"></i>
361
- <div class="red-empty-state-title">Not Enough Data for Correlation Analysis</div>
365
+ <div class="red-empty-state-title"><%= red_t("red.analytics.correlation.empty.title") %></div>
362
366
  <p class="text-muted">
363
- Correlation analysis requires more error data to identify patterns.
367
+ <%= red_t("red.analytics.correlation.empty.message") %>
364
368
  </p>
365
369
  <div class="card mx-auto" style="max-width: 500px;">
366
370
  <div class="card-body text-start">
367
- <h6>What's needed:</h6>
371
+ <h6><%= red_t("red.analytics.correlation.empty.needed_title") %></h6>
368
372
  <ul class="mb-0">
369
- <li>App version tracking for release correlation</li>
370
- <li>User tracking for user correlation</li>
371
- <li>Multiple error types for time correlation</li>
373
+ <li><%= red_t("red.analytics.correlation.empty.needed_version") %></li>
374
+ <li><%= red_t("red.analytics.correlation.empty.needed_user") %></li>
375
+ <li><%= red_t("red.analytics.correlation.empty.needed_types") %></li>
372
376
  </ul>
373
377
  </div>
374
378
  </div>
375
379
  <small class="text-muted d-block mt-3">
376
- <i class="bi bi-lightbulb"></i> Check back once more errors have been logged.
380
+ <i class="bi bi-lightbulb"></i> <%= red_t("red.analytics.correlation.empty.hint") %>
377
381
  </small>
378
382
  </div>
379
383
  <% end %>
@@ -1,39 +1,39 @@
1
- <% content_for :page_title, "DB Health" %>
1
+ <% content_for :page_title, red_t("red.health.database.title") %>
2
2
 
3
3
  <div>
4
4
  <div class="d-flex justify-content-between align-items-center mb-4">
5
5
  <h1 style="font-size: 20px; font-weight: 700; margin: 0;">
6
6
  <i class="bi bi-database me-2"></i>
7
- Database Health
7
+ <%= red_t("red.health.database.title") %>
8
8
  </h1>
9
9
 
10
10
  <div style="display: flex; gap: 4px;">
11
- <%= link_to database_health_summary_errors_path(days: 7), class: "btn btn-sm #{@days == 7 ? 'btn-primary' : 'btn-outline-primary'}", style: "text-decoration: none;" do %>7 Days<% end %>
12
- <%= link_to database_health_summary_errors_path(days: 30), class: "btn btn-sm #{@days == 30 ? 'btn-primary' : 'btn-outline-primary'}", style: "text-decoration: none;" do %>30 Days<% end %>
13
- <%= link_to database_health_summary_errors_path(days: 90), class: "btn btn-sm #{@days == 90 ? 'btn-primary' : 'btn-outline-primary'}", style: "text-decoration: none;" do %>90 Days<% end %>
11
+ <%= link_to database_health_summary_errors_path(days: 7), class: "btn btn-sm #{@days == 7 ? 'btn-primary' : 'btn-outline-primary'}", style: "text-decoration: none;" do %><%= red_t("red.common.days_filter.7") %><% end %>
12
+ <%= link_to database_health_summary_errors_path(days: 30), class: "btn btn-sm #{@days == 30 ? 'btn-primary' : 'btn-outline-primary'}", style: "text-decoration: none;" do %><%= red_t("red.common.days_filter.30") %><% end %>
13
+ <%= link_to database_health_summary_errors_path(days: 90), class: "btn btn-sm #{@days == 90 ? 'btn-primary' : 'btn-outline-primary'}", style: "text-decoration: none;" do %><%= red_t("red.common.days_filter.90") %><% end %>
14
14
  </div>
15
15
  </div>
16
16
 
17
17
  <%# ===== SECTION A: Summary Stats ===== %>
18
18
  <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: var(--space-4); margin-bottom: var(--space-6);">
19
19
  <div class="card stat-card" style="border-left: 3px solid var(--accent); padding: var(--space-4) var(--space-5); text-align: center;">
20
- <div class="stat-label" style="margin-bottom: 4px;">Errors with Pool Data</div>
20
+ <div class="stat-label" style="margin-bottom: 4px;"><%= red_t("red.health.database.stats.errors_with_pool") %></div>
21
21
  <div class="stat-value"><%= @errors_with_pool %></div>
22
22
  </div>
23
23
  <% if @live_health[:connection_pool] %>
24
24
  <% pool = @live_health[:connection_pool] %>
25
25
  <div class="card stat-card" style="padding: var(--space-4) var(--space-5); text-align: center;">
26
- <div class="stat-label" style="margin-bottom: 4px;">Peak Utilization</div>
26
+ <div class="stat-label" style="margin-bottom: 4px;"><%= red_t("red.health.database.stats.peak_utilization") %></div>
27
27
  <% util_color = @max_utilization >= 80 ? 'var(--status-critical)' : (@max_utilization >= 60 ? 'var(--status-caution)' : 'var(--status-success)') %>
28
28
  <div class="stat-value" style="color: <%= util_color %>;"><%= @max_utilization %>%</div>
29
29
  </div>
30
30
  <div class="card stat-card" style="padding: var(--space-4) var(--space-5); text-align: center;">
31
- <div class="stat-label" style="margin-bottom: 4px;">Total Dead</div>
31
+ <div class="stat-label" style="margin-bottom: 4px;"><%= red_t("red.health.database.stats.total_dead") %></div>
32
32
  <% dead_color = @total_dead > 0 ? 'var(--status-critical)' : 'var(--status-success)' %>
33
33
  <div class="stat-value" style="color: <%= dead_color %>;"><%= @total_dead %></div>
34
34
  </div>
35
35
  <div class="card stat-card" style="padding: var(--space-4) var(--space-5); text-align: center;">
36
- <div class="stat-label" style="margin-bottom: 4px;">Total Waiting</div>
36
+ <div class="stat-label" style="margin-bottom: 4px;"><%= red_t("red.health.database.stats.total_waiting") %></div>
37
37
  <% waiting_color = @total_waiting > 0 ? 'var(--status-caution)' : 'var(--status-success)' %>
38
38
  <div class="stat-value" style="color: <%= waiting_color %>;"><%= @total_waiting %></div>
39
39
  </div>
@@ -44,15 +44,15 @@
44
44
  <div class="card" style="margin-bottom: var(--space-4);">
45
45
  <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);">
46
46
  <span style="font-size: 13px; font-weight: 600;">
47
- <i class="bi bi-activity me-2"></i>Live Database Health
47
+ <i class="bi bi-activity me-2"></i><%= red_t("red.health.database.live.title") %>
48
48
  </span>
49
49
  </div>
50
50
 
51
51
  <% unless @live_health[:postgresql] %>
52
52
  <div class="alert alert-info" style="margin: var(--space-3) var(--space-5); padding: var(--space-3) var(--space-4); font-size: 12px;">
53
53
  <i class="bi bi-info-circle me-1"></i>
54
- <strong>Non-PostgreSQL adapter detected (<%= @live_health[:adapter] || "unknown" %>).</strong>
55
- Live table, index, and activity stats require PostgreSQL. Connection pool and historical data are still available below.
54
+ <strong><%= red_t("red.health.database.live.non_postgresql", adapter: @live_health[:adapter] || "unknown") %></strong>
55
+ <%= red_t("red.health.database.live.non_postgresql_hint") %>
56
56
  </div>
57
57
  <% end %>
58
58
 
@@ -62,26 +62,26 @@
62
62
  <div style="display: grid; grid-template-columns: repeat(5, 1fr); gap: var(--space-3); margin-bottom: var(--space-4);">
63
63
  <div style="text-align: center; padding: var(--space-3); background: var(--surface-base); border-radius: var(--radius-md);">
64
64
  <div style="font-size: 24px; font-weight: 700; color: var(--accent);"><%= pool[:size] %></div>
65
- <small style="color: var(--text-tertiary);">Pool Size</small>
65
+ <small style="color: var(--text-tertiary);"><%= red_t("red.health.database.live.pool_size") %></small>
66
66
  </div>
67
67
  <div style="text-align: center; padding: var(--space-3); background: var(--surface-base); border-radius: var(--radius-md);">
68
68
  <% busy_color = pool[:size] > 0 && (pool[:busy].to_f / pool[:size]) >= 0.8 ? 'var(--status-critical)' : 'var(--status-info)' %>
69
69
  <div style="font-size: 24px; font-weight: 700; color: <%= busy_color %>;"><%= pool[:busy] %></div>
70
- <small style="color: var(--text-tertiary);">Busy</small>
70
+ <small style="color: var(--text-tertiary);"><%= red_t("red.health.database.live.busy") %></small>
71
71
  </div>
72
72
  <div style="text-align: center; padding: var(--space-3); background: var(--surface-base); border-radius: var(--radius-md);">
73
73
  <div style="font-size: 24px; font-weight: 700; color: var(--status-success);"><%= pool[:idle] %></div>
74
- <small style="color: var(--text-tertiary);">Idle</small>
74
+ <small style="color: var(--text-tertiary);"><%= red_t("red.health.database.live.idle") %></small>
75
75
  </div>
76
76
  <div style="text-align: center; padding: var(--space-3); background: var(--surface-base); border-radius: var(--radius-md);">
77
77
  <% dead_color = pool[:dead] > 0 ? 'var(--status-critical)' : 'var(--status-success)' %>
78
78
  <div style="font-size: 24px; font-weight: 700; color: <%= dead_color %>;"><%= pool[:dead] %></div>
79
- <small style="color: var(--text-tertiary);">Dead</small>
79
+ <small style="color: var(--text-tertiary);"><%= red_t("red.health.database.live.dead") %></small>
80
80
  </div>
81
81
  <div style="text-align: center; padding: var(--space-3); background: var(--surface-base); border-radius: var(--radius-md);">
82
82
  <% waiting_color = pool[:waiting] > 0 ? 'var(--status-caution)' : 'var(--status-success)' %>
83
83
  <div style="font-size: 24px; font-weight: 700; color: <%= waiting_color %>;"><%= pool[:waiting] %></div>
84
- <small style="color: var(--text-tertiary);">Waiting</small>
84
+ <small style="color: var(--text-tertiary);"><%= red_t("red.health.database.live.waiting") %></small>
85
85
  </div>
86
86
  </div>
87
87
  </div>
@@ -93,21 +93,21 @@
93
93
  <% total_db_size = (@host_tables + @gem_tables).sum { |t| t[:total_bytes] } %>
94
94
  <div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--space-4); margin-bottom: var(--space-4);">
95
95
  <div class="card stat-card" style="padding: var(--space-4) var(--space-5); text-align: center;">
96
- <div class="stat-label" style="margin-bottom: 4px;">Total DB Size</div>
96
+ <div class="stat-label" style="margin-bottom: 4px;"><%= red_t("red.health.database.summary.total_db_size") %></div>
97
97
  <div class="stat-value"><%= number_to_human_size(total_db_size) %></div>
98
98
  </div>
99
99
  <div class="card stat-card" style="padding: var(--space-4) var(--space-5); text-align: center;">
100
- <div class="stat-label" style="margin-bottom: 4px;">Tables</div>
100
+ <div class="stat-label" style="margin-bottom: 4px;"><%= red_t("red.health.database.summary.tables") %></div>
101
101
  <div class="stat-value"><%= @host_tables.size + @gem_tables.size %></div>
102
102
  </div>
103
103
  <div class="card stat-card" style="padding: var(--space-4) var(--space-5); text-align: center;">
104
- <div class="stat-label" style="margin-bottom: 4px;">Unused Indexes</div>
104
+ <div class="stat-label" style="margin-bottom: 4px;"><%= red_t("red.health.database.summary.unused_indexes") %></div>
105
105
  <% unused_color = @live_health[:unused_indexes]&.any? ? 'var(--status-caution)' : 'var(--status-success)' %>
106
106
  <div class="stat-value" style="color: <%= unused_color %>;"><%= @live_health[:unused_indexes]&.size || 0 %></div>
107
107
  </div>
108
108
  <div class="card stat-card" style="padding: var(--space-4) var(--space-5); text-align: center;">
109
- <div class="stat-label" style="margin-bottom: 4px;">Active Connections</div>
110
- <div class="stat-value"><%= @live_health[:activity] ? @live_health[:activity][:total] : "N/A" %></div>
109
+ <div class="stat-label" style="margin-bottom: 4px;"><%= red_t("red.health.database.summary.active_connections") %></div>
110
+ <div class="stat-value"><%= @live_health[:activity] ? @live_health[:activity][:total] : red_t("red.common.not_available") %></div>
111
111
  </div>
112
112
  </div>
113
113
 
@@ -117,7 +117,7 @@
117
117
  <div class="card-header">
118
118
  <h5 class="mb-0">
119
119
  <i class="bi bi-table text-primary me-2"></i>
120
- Host App Tables
120
+ <%= red_t("red.health.database.host_tables.title") %>
121
121
  <span class="badge bg-primary"><%= @host_tables.size %></span>
122
122
  </h5>
123
123
  </div>
@@ -126,13 +126,13 @@
126
126
  <table class="table table-hover mb-0">
127
127
  <thead class="table-light">
128
128
  <tr>
129
- <th>Table</th>
130
- <th width="100">Est. Rows</th>
131
- <th width="100">Size</th>
132
- <th width="80">Seq Scans</th>
133
- <th width="80">Idx Scans</th>
134
- <th width="100">Dead Tuples</th>
135
- <th width="140">Last Vacuum</th>
129
+ <th><%= red_t("red.health.database.table_columns.table") %></th>
130
+ <th width="100"><%= red_t("red.health.database.table_columns.est_rows") %></th>
131
+ <th width="100"><%= red_t("red.health.database.table_columns.size") %></th>
132
+ <th width="80"><%= red_t("red.health.database.table_columns.seq_scans") %></th>
133
+ <th width="80"><%= red_t("red.health.database.table_columns.idx_scans") %></th>
134
+ <th width="100"><%= red_t("red.health.database.table_columns.dead_tuples") %></th>
135
+ <th width="140"><%= red_t("red.health.database.table_columns.last_vacuum") %></th>
136
136
  </tr>
137
137
  </thead>
138
138
  <tbody>
@@ -150,7 +150,7 @@
150
150
  <%= number_with_delimiter(table[:dead_tuples]) %>
151
151
  <% end %>
152
152
  </td>
153
- <td><%= table[:last_autovacuum] ? local_time_ago(parse_pg_timestamp(table[:last_autovacuum])) : "Never" %></td>
153
+ <td><%= table[:last_autovacuum] ? local_time_ago(parse_pg_timestamp(table[:last_autovacuum])) : red_t("red.health.database.never_vacuumed") %></td>
154
154
  </tr>
155
155
  <% end %>
156
156
  </tbody>
@@ -167,7 +167,7 @@
167
167
  <a class="text-decoration-none" data-bs-toggle="collapse" href="#gemTablesCollapse" role="button" aria-expanded="false" aria-controls="gemTablesCollapse">
168
168
  <h5 class="mb-0">
169
169
  <i class="bi bi-table text-secondary me-2"></i>
170
- Error Dashboard Tables
170
+ <%= red_t("red.health.database.gem_tables.title") %>
171
171
  <span class="badge bg-secondary"><%= @gem_tables.size %></span>
172
172
  <i class="bi bi-chevron-down ms-2" style="font-size: 0.8em;"></i>
173
173
  </h5>
@@ -179,13 +179,13 @@
179
179
  <table class="table table-hover mb-0">
180
180
  <thead class="table-light">
181
181
  <tr>
182
- <th>Table</th>
183
- <th width="100">Est. Rows</th>
184
- <th width="100">Size</th>
185
- <th width="80">Seq Scans</th>
186
- <th width="80">Idx Scans</th>
187
- <th width="100">Dead Tuples</th>
188
- <th width="140">Last Vacuum</th>
182
+ <th><%= red_t("red.health.database.table_columns.table") %></th>
183
+ <th width="100"><%= red_t("red.health.database.table_columns.est_rows") %></th>
184
+ <th width="100"><%= red_t("red.health.database.table_columns.size") %></th>
185
+ <th width="80"><%= red_t("red.health.database.table_columns.seq_scans") %></th>
186
+ <th width="80"><%= red_t("red.health.database.table_columns.idx_scans") %></th>
187
+ <th width="100"><%= red_t("red.health.database.table_columns.dead_tuples") %></th>
188
+ <th width="140"><%= red_t("red.health.database.table_columns.last_vacuum") %></th>
189
189
  </tr>
190
190
  </thead>
191
191
  <tbody>
@@ -203,7 +203,7 @@
203
203
  <%= number_with_delimiter(table[:dead_tuples]) %>
204
204
  <% end %>
205
205
  </td>
206
- <td><%= table[:last_autovacuum] ? local_time_ago(parse_pg_timestamp(table[:last_autovacuum])) : "Never" %></td>
206
+ <td><%= table[:last_autovacuum] ? local_time_ago(parse_pg_timestamp(table[:last_autovacuum])) : red_t("red.health.database.never_vacuumed") %></td>
207
207
  </tr>
208
208
  <% end %>
209
209
  </tbody>
@@ -220,7 +220,7 @@
220
220
  <div class="card-header">
221
221
  <h5 class="mb-0">
222
222
  <i class="bi bi-exclamation-triangle text-warning me-2"></i>
223
- Unused Indexes
223
+ <%= red_t("red.health.database.unused_indexes.title") %>
224
224
  <span class="badge bg-warning text-dark"><%= @live_health[:unused_indexes].size %></span>
225
225
  </h5>
226
226
  </div>
@@ -229,9 +229,9 @@
229
229
  <table class="table table-hover mb-0">
230
230
  <thead class="table-light">
231
231
  <tr>
232
- <th>Index</th>
233
- <th>Table</th>
234
- <th width="100">Size</th>
232
+ <th><%= red_t("red.health.database.unused_indexes.column_index") %></th>
233
+ <th><%= red_t("red.health.database.unused_indexes.column_table") %></th>
234
+ <th width="100"><%= red_t("red.health.database.unused_indexes.column_size") %></th>
235
235
  </tr>
236
236
  </thead>
237
237
  <tbody>
@@ -255,10 +255,10 @@
255
255
  <div class="card-header">
256
256
  <h5 class="mb-0">
257
257
  <i class="bi bi-people text-info me-2"></i>
258
- Connection Activity
259
- <span class="badge bg-info"><%= @live_health[:activity][:total] %> total</span>
258
+ <%= red_t("red.health.database.activity.title") %>
259
+ <span class="badge bg-info"><%= red_t("red.health.database.activity.total_badge", count: @live_health[:activity][:total]) %></span>
260
260
  <% if @live_health[:activity][:total_waiting] > 0 %>
261
- <span class="badge bg-warning text-dark"><%= @live_health[:activity][:total_waiting] %> waiting</span>
261
+ <span class="badge bg-warning text-dark"><%= red_t("red.health.database.activity.waiting_badge", count: @live_health[:activity][:total_waiting]) %></span>
262
262
  <% end %>
263
263
  </h5>
264
264
  </div>
@@ -267,9 +267,9 @@
267
267
  <table class="table table-hover mb-0">
268
268
  <thead class="table-light">
269
269
  <tr>
270
- <th>State</th>
271
- <th width="100">Count</th>
272
- <th width="100">Waiting</th>
270
+ <th><%= red_t("red.health.database.activity.column_state") %></th>
271
+ <th width="100"><%= red_t("red.health.database.activity.column_count") %></th>
272
+ <th width="100"><%= red_t("red.health.database.activity.column_waiting") %></th>
273
273
  </tr>
274
274
  </thead>
275
275
  <tbody>
@@ -298,7 +298,7 @@
298
298
  <div class="card" style="margin-bottom: var(--space-4);">
299
299
  <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);">
300
300
  <span style="font-size: 13px; font-weight: 600;">
301
- <i class="bi bi-clock-history me-2"></i>Connection Pool by Error
301
+ <i class="bi bi-clock-history me-2"></i><%= red_t("red.health.database.pool_history.title") %>
302
302
  <% if @errors_with_pool > 0 %>
303
303
  <span class="badge bg-info"><%= @errors_with_pool %></span>
304
304
  <% end %>
@@ -311,10 +311,9 @@
311
311
  <% if @errors_with_pool == 0 %>
312
312
  <div class="red-empty-state" style="padding: var(--space-6);">
313
313
  <div class="red-empty-state-icon" style="background: var(--status-success-bg); color: var(--status-success);"><i class="bi bi-check-lg"></i></div>
314
- <div class="red-empty-state-title">No Connection Pool Data Found</div>
314
+ <div class="red-empty-state-title"><%= red_t("red.health.database.pool_history.empty_title") %></div>
315
315
  <p style="color: var(--text-tertiary); font-size: 13px; max-width: 400px; margin: 0 auto;">
316
- No connection pool stats were detected in system health snapshots over the last <%= @days %> days.
317
- Enable <code>enable_system_health = true</code> to capture pool stats at error time.
316
+ <%= red_t("red.health.database.pool_history.empty_message_html", days: @days) %>
318
317
  </p>
319
318
  </div>
320
319
  <% else %>
@@ -323,15 +322,15 @@
323
322
  <table class="table table-hover mb-0">
324
323
  <thead class="table-light">
325
324
  <tr>
326
- <th width="80">Error</th>
327
- <th>Error Type</th>
328
- <th width="110">Utilization</th>
329
- <th width="60">Busy</th>
330
- <th width="60">Idle</th>
331
- <th width="60">Dead</th>
332
- <th width="70">Waiting</th>
333
- <th width="70">Pool Size</th>
334
- <th width="140">Last Seen</th>
325
+ <th width="80"><%= red_t("red.health.database.pool_history.column_error") %></th>
326
+ <th><%= red_t("red.health.database.pool_history.column_error_type") %></th>
327
+ <th width="110"><%= red_t("red.health.database.pool_history.column_utilization") %></th>
328
+ <th width="60"><%= red_t("red.health.database.pool_history.column_busy") %></th>
329
+ <th width="60"><%= red_t("red.health.database.pool_history.column_idle") %></th>
330
+ <th width="60"><%= red_t("red.health.database.pool_history.column_dead") %></th>
331
+ <th width="70"><%= red_t("red.health.database.pool_history.column_waiting") %></th>
332
+ <th width="70"><%= red_t("red.health.database.pool_history.column_pool_size") %></th>
333
+ <th width="140"><%= red_t("red.health.database.pool_history.column_last_seen") %></th>
335
334
  </tr>
336
335
  </thead>
337
336
  <tbody>
@@ -379,11 +378,11 @@
379
378
  <div style="display: flex; justify-content: space-between; align-items: center; padding: var(--space-3) var(--space-5); border-top: 1px solid var(--border-primary); font-size: 12px;">
380
379
  <div>
381
380
  <small style="color: var(--text-tertiary);">
382
- <i class="bi bi-lightbulb" style="color: var(--status-caution);"></i> High utilization during errors may indicate connection pool exhaustion or long-running queries.
381
+ <i class="bi bi-lightbulb" style="color: var(--status-caution);"></i> <%= red_t("red.health.database.pool_history.tip") %>
383
382
  </small>
384
383
  <small class="ms-3">
385
384
  <a href="https://guides.rubyonrails.org/configuring.html#configuring-a-database" target="_blank" rel="noopener" style="color: var(--accent); text-decoration: none;">
386
- <i class="bi bi-book"></i> Database Guide <i class="bi bi-box-arrow-up-right" style="font-size: 0.7em;"></i>
385
+ <i class="bi bi-book"></i> <%= red_t("red.health.database.pool_history.guide_link") %> <i class="bi bi-box-arrow-up-right" style="font-size: 0.7em;"></i>
387
386
  </a>
388
387
  </small>
389
388
  </div>