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,4 +1,4 @@
1
- <% content_for :page_title, "Analytics" %>
1
+ <% content_for :page_title, red_t("red.analytics.overview.title") %>
2
2
 
3
3
  <script<%= " nonce=\"#{red_csp_nonce}\"".html_safe if red_csp_nonce %>>
4
4
  // Dynamic chart colors based on theme
@@ -34,28 +34,28 @@
34
34
 
35
35
  <div>
36
36
  <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--space-4);">
37
- <h1 style="font-size: 20px; font-weight: 700; margin: 0;">Analytics</h1>
37
+ <h1 style="font-size: 20px; font-weight: 700; margin: 0;"><%= red_t("red.analytics.overview.title") %></h1>
38
38
  </div>
39
39
 
40
40
  <!-- Time range pills -->
41
41
  <div style="display: flex; gap: 6px; margin-bottom: var(--space-6);">
42
- <% [['7d', 7], ['14d', 14], ['30d', 30], ['60d', 60], ['90d', 90]].each do |label, days| %>
43
- <%= link_to analytics_errors_path(days: days), class: "filter-pill #{@days == days ? 'active' : ''}", style: "text-decoration: none;" do %><%= label %><% end %>
42
+ <% [ 7, 14, 30, 60, 90 ].each do |days| %>
43
+ <%= link_to analytics_errors_path(days: days), class: "filter-pill #{@days == days ? 'active' : ''}", style: "text-decoration: none;" do %><%= red_t("red.analytics.days_pill.#{days}") %><% end %>
44
44
  <% end %>
45
45
  </div>
46
46
 
47
47
  <!-- Summary Stats -->
48
48
  <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-4); margin-bottom: var(--space-6);">
49
49
  <div class="card stat-card" style="border-left: 3px solid var(--accent); padding: var(--space-5) var(--space-6);">
50
- <div class="stat-label" style="margin-bottom: 4px;">Total Errors</div>
50
+ <div class="stat-label" style="margin-bottom: 4px;"><%= red_t("red.analytics.overview.stats.total_errors") %></div>
51
51
  <span class="stat-value"><%= @error_stats[:total] %></span>
52
52
  </div>
53
53
  <div class="card stat-card" style="border-left: 3px solid var(--status-critical); padding: var(--space-5) var(--space-6);">
54
- <div class="stat-label" style="margin-bottom: 4px;">Unresolved</div>
54
+ <div class="stat-label" style="margin-bottom: 4px;"><%= red_t("red.analytics.overview.stats.unresolved") %></div>
55
55
  <span class="stat-value" style="color: var(--status-critical);"><%= @error_stats[:unresolved] %></span>
56
56
  </div>
57
57
  <div class="card stat-card" style="border-left: 3px solid var(--status-success); padding: var(--space-5) var(--space-6);">
58
- <div class="stat-label" style="margin-bottom: 4px;">Resolution Rate</div>
58
+ <div class="stat-label" style="margin-bottom: 4px;"><%= red_t("red.analytics.overview.stats.resolution_rate") %></div>
59
59
  <span class="stat-value" style="color: var(--status-success);"><%= @resolution_rate %>%</span>
60
60
  </div>
61
61
  </div>
@@ -63,7 +63,7 @@
63
63
  <!-- Error Trend Chart -->
64
64
  <div class="card mb-4">
65
65
  <div class="card-header">
66
- <h5 class="mb-0"><i class="bi bi-graph-up"></i> Error Trend (Last <%= @days %> Days)</h5>
66
+ <h5 class="mb-0"><i class="bi bi-graph-up"></i> <%= red_t("red.analytics.overview.trend_chart.title", days: @days) %></h5>
67
67
  </div>
68
68
  <div class="card-body">
69
69
  <div id="errors-over-time-chart"></div>
@@ -81,12 +81,12 @@
81
81
  scales: {
82
82
  x: {
83
83
  ticks: { color: colors.textColor },
84
- title: { color: colors.textColor, display: true, text: 'Date' },
84
+ title: { color: colors.textColor, display: true, text: '<%= red_js_t("red.ui_js.charts.axis_date") %>' },
85
85
  grid: { color: colors.gridColor }
86
86
  },
87
87
  y: {
88
88
  ticks: { color: colors.textColor },
89
- title: { color: colors.textColor, display: true, text: 'Number of Errors' },
89
+ title: { color: colors.textColor, display: true, text: '<%= red_js_t("red.ui_js.charts.axis_number_of_errors") %>' },
90
90
  grid: { color: colors.gridColor }
91
91
  }
92
92
  },
@@ -108,7 +108,7 @@
108
108
  <div class="col-md-12">
109
109
  <div class="card">
110
110
  <div class="card-header">
111
- <h5 class="mb-0"><i class="bi bi-phone"></i> Errors by Platform</h5>
111
+ <h5 class="mb-0"><i class="bi bi-phone"></i> <%= red_t("red.analytics.overview.platform_chart.title") %></h5>
112
112
  </div>
113
113
  <div class="card-body">
114
114
  <div id="errors-by-platform-chart"></div>
@@ -146,7 +146,7 @@
146
146
  </div>
147
147
  <div class="card-footer border-top-0">
148
148
  <div class="d-flex gap-2 flex-wrap">
149
- <small class="text-muted me-2">Quick Links:</small>
149
+ <small class="text-muted me-2"><%= red_t("red.analytics.overview.platform_chart.quick_links") %></small>
150
150
  <% @errors_by_platform.keys.each do |platform| %>
151
151
  <%= link_to platform, errors_path(platform: platform, unresolved: '0'), class: "btn btn-sm btn-outline-secondary" %>
152
152
  <% end %>
@@ -162,7 +162,7 @@
162
162
  <div class="col-md-12">
163
163
  <div class="card">
164
164
  <div class="card-header">
165
- <h5 class="mb-0"><i class="bi bi-bug"></i> Top 10 Error Types</h5>
165
+ <h5 class="mb-0"><i class="bi bi-bug"></i> <%= red_t("red.analytics.overview.type_chart.title") %></h5>
166
166
  </div>
167
167
  <div class="card-body">
168
168
  <div id="errors-by-type-chart"></div>
@@ -178,12 +178,12 @@
178
178
  scales: {
179
179
  x: {
180
180
  ticks: { color: colors.textColor },
181
- title: { color: colors.textColor, display: true, text: 'Error Type' },
181
+ title: { color: colors.textColor, display: true, text: '<%= red_js_t("red.ui_js.charts.axis_error_type") %>' },
182
182
  grid: { color: colors.gridColor }
183
183
  },
184
184
  y: {
185
185
  ticks: { color: colors.textColor },
186
- title: { color: colors.textColor, display: true, text: 'Count' },
186
+ title: { color: colors.textColor, display: true, text: '<%= red_js_t("red.ui_js.charts.axis_count") %>' },
187
187
  grid: { color: colors.gridColor }
188
188
  }
189
189
  },
@@ -204,7 +204,7 @@
204
204
  <div class="col-md-12">
205
205
  <div class="card">
206
206
  <div class="card-header">
207
- <h5 class="mb-0"><i class="bi bi-clock"></i> Errors by Hour of Day</h5>
207
+ <h5 class="mb-0"><i class="bi bi-clock"></i> <%= red_t("red.analytics.overview.hour_chart.title") %></h5>
208
208
  </div>
209
209
  <div class="card-body">
210
210
  <div id="errors-by-hour-chart"></div>
@@ -220,12 +220,12 @@
220
220
  scales: {
221
221
  x: {
222
222
  ticks: { color: colors.textColor },
223
- title: { color: colors.textColor, display: true, text: 'Hour' },
223
+ title: { color: colors.textColor, display: true, text: '<%= red_js_t("red.ui_js.charts.axis_hour") %>' },
224
224
  grid: { color: colors.gridColor }
225
225
  },
226
226
  y: {
227
227
  ticks: { color: colors.textColor },
228
- title: { color: colors.textColor, display: true, text: 'Number of Errors' },
228
+ title: { color: colors.textColor, display: true, text: '<%= red_js_t("red.ui_js.charts.axis_number_of_errors") %>' },
229
229
  grid: { color: colors.gridColor }
230
230
  }
231
231
  },
@@ -247,7 +247,7 @@
247
247
  <div class="col-md-12">
248
248
  <div class="card">
249
249
  <div class="card-header">
250
- <h5 class="mb-0"><i class="bi bi-people"></i> Top 10 Affected Users</h5>
250
+ <h5 class="mb-0"><i class="bi bi-people"></i> <%= red_t("red.analytics.overview.top_users.title") %></h5>
251
251
  </div>
252
252
  <div class="card-body p-0">
253
253
  <%= render partial: 'user_errors_table',
@@ -270,19 +270,19 @@
270
270
  <div class="col-md-12">
271
271
  <div class="card">
272
272
  <div class="card-header d-flex justify-content-between align-items-center">
273
- <h5 class="mb-0"><i class="bi bi-list-check"></i> Error Type Breakdown</h5>
274
- <small class="text-muted">Last <%= @days %> days</small>
273
+ <h5 class="mb-0"><i class="bi bi-list-check"></i> <%= red_t("red.analytics.overview.type_breakdown.title") %></h5>
274
+ <small class="text-muted"><%= red_t("red.analytics.overview.type_breakdown.subtitle", days: @days) %></small>
275
275
  </div>
276
276
  <div class="card-body p-0">
277
277
  <div class="table-responsive">
278
278
  <table class="table table-hover mb-0">
279
279
  <thead class="table-light">
280
280
  <tr>
281
- <th>Rank</th>
282
- <th>Error Type</th>
283
- <th>Count</th>
284
- <th>Percentage</th>
285
- <th>Actions</th>
281
+ <th><%= red_t("red.analytics.overview.type_breakdown.column_rank") %></th>
282
+ <th><%= red_t("red.analytics.overview.type_breakdown.column_error_type") %></th>
283
+ <th><%= red_t("red.analytics.overview.type_breakdown.column_count") %></th>
284
+ <th><%= red_t("red.analytics.overview.type_breakdown.column_percentage") %></th>
285
+ <th><%= red_t("red.analytics.overview.type_breakdown.column_actions") %></th>
286
286
  </tr>
287
287
  </thead>
288
288
  <tbody>
@@ -304,7 +304,7 @@
304
304
  </div>
305
305
  </td>
306
306
  <td>
307
- <%= link_to "View Errors", errors_path(error_type: error_type, unresolved: '0'), class: "btn btn-sm btn-outline-primary" %>
307
+ <%= link_to red_t("red.analytics.overview.type_breakdown.view_errors"), errors_path(error_type: error_type, unresolved: '0'), class: "btn btn-sm btn-outline-primary" %>
308
308
  </td>
309
309
  </tr>
310
310
  <% end %>
@@ -319,21 +319,21 @@
319
319
  <!-- Recurring Issues Analysis -->
320
320
  <div class="card mb-4">
321
321
  <div class="card-header">
322
- <h5 class="mb-0"><i class="bi bi-arrow-repeat"></i> Recurring Issues</h5>
322
+ <h5 class="mb-0"><i class="bi bi-arrow-repeat"></i> <%= red_t("red.analytics.overview.recurring.title") %></h5>
323
323
  </div>
324
324
  <div class="card-body">
325
325
  <% if @recurring_data[:high_frequency_errors].any? %>
326
- <h6 class="text-muted mb-3">High Frequency Errors</h6>
326
+ <h6 class="text-muted mb-3"><%= red_t("red.analytics.overview.recurring.high_frequency_title") %></h6>
327
327
  <div class="table-responsive mb-4">
328
328
  <table class="table table-sm">
329
329
  <thead>
330
330
  <tr>
331
- <th>Error Type</th>
332
- <th>Total Occurrences</th>
333
- <th>Duration</th>
334
- <th>First Seen</th>
335
- <th>Last Seen</th>
336
- <th>Status</th>
331
+ <th><%= red_t("red.analytics.overview.recurring.column_error_type") %></th>
332
+ <th><%= red_t("red.analytics.overview.recurring.column_total_occurrences") %></th>
333
+ <th><%= red_t("red.analytics.overview.recurring.column_duration") %></th>
334
+ <th><%= red_t("red.analytics.overview.recurring.column_first_seen") %></th>
335
+ <th><%= red_t("red.analytics.overview.recurring.column_last_seen") %></th>
336
+ <th><%= red_t("red.analytics.overview.recurring.column_status") %></th>
337
337
  </tr>
338
338
  </thead>
339
339
  <tbody>
@@ -345,14 +345,14 @@
345
345
  <% end %>
346
346
  </td>
347
347
  <td><span class="badge bg-danger"><%= error[:total_occurrences] %></span></td>
348
- <td><%= error[:duration_days] %> days</td>
348
+ <td><%= red_t("red.analytics.overview.recurring.duration_days", days: error[:duration_days]) %></td>
349
349
  <td><small class="text-muted"><%= local_time(error[:first_seen], format: :date_only) %></small></td>
350
350
  <td><small class="text-muted"><%= local_time(error[:last_seen], format: :datetime) %></small></td>
351
351
  <td>
352
352
  <% if error[:still_active] %>
353
- <span class="badge bg-warning">Active</span>
353
+ <span class="badge bg-warning"><%= red_t("red.analytics.overview.recurring.active") %></span>
354
354
  <% else %>
355
- <span class="badge bg-secondary">Inactive</span>
355
+ <span class="badge bg-secondary"><%= red_t("red.analytics.overview.recurring.inactive") %></span>
356
356
  <% end %>
357
357
  </td>
358
358
  </tr>
@@ -361,24 +361,24 @@
361
361
  </table>
362
362
  </div>
363
363
  <% else %>
364
- <p class="text-muted">No high-frequency errors found in the last <%= @days %> days.</p>
364
+ <p class="text-muted"><%= red_t("red.analytics.overview.recurring.empty_high_frequency", days: @days) %></p>
365
365
  <% end %>
366
366
 
367
367
  <% if @recurring_data[:persistent_errors].any? %>
368
- <h6 class="text-muted mb-3 mt-4">Persistent Unresolved Errors</h6>
368
+ <h6 class="text-muted mb-3 mt-4"><%= red_t("red.analytics.overview.recurring.persistent_title") %></h6>
369
369
  <div class="alert alert-info">
370
- <i class="bi bi-info-circle"></i> These errors have been unresolved for more than 7 days.
370
+ <i class="bi bi-info-circle"></i> <%= red_t("red.analytics.overview.recurring.persistent_hint") %>
371
371
  </div>
372
372
  <div class="table-responsive">
373
373
  <table class="table table-sm">
374
374
  <thead>
375
375
  <tr>
376
- <th>Error Type</th>
377
- <th>Message</th>
378
- <th>Platform</th>
379
- <th>Count</th>
380
- <th>Age</th>
381
- <th>Actions</th>
376
+ <th><%= red_t("red.analytics.overview.recurring.column_error_type") %></th>
377
+ <th><%= red_t("red.analytics.overview.recurring.column_message") %></th>
378
+ <th><%= red_t("red.analytics.overview.recurring.column_platform") %></th>
379
+ <th><%= red_t("red.analytics.overview.recurring.column_count") %></th>
380
+ <th><%= red_t("red.analytics.overview.recurring.column_age") %></th>
381
+ <th><%= red_t("red.analytics.overview.recurring.column_actions") %></th>
382
382
  </tr>
383
383
  </thead>
384
384
  <tbody>
@@ -389,10 +389,10 @@
389
389
  <td><%= error[:platform] %></td>
390
390
  <td><span class="badge bg-secondary"><%= error[:occurrence_count] %>x</span></td>
391
391
  <td>
392
- <span class="badge bg-warning"><%= error[:age_days] %> days</span>
392
+ <span class="badge bg-warning"><%= red_t("red.analytics.overview.recurring.age_days", days: error[:age_days]) %></span>
393
393
  </td>
394
394
  <td>
395
- <%= link_to "View", error_path(error[:id]), class: "btn btn-sm btn-outline-primary" %>
395
+ <%= link_to red_t("red.analytics.overview.recurring.view"), error_path(error[:id]), class: "btn btn-sm btn-outline-primary" %>
396
396
  </td>
397
397
  </tr>
398
398
  <% end %>
@@ -410,7 +410,7 @@
410
410
  <div class="col-md-8">
411
411
  <div class="card">
412
412
  <div class="card-header">
413
- <h5 class="mb-0"><i class="bi bi-git"></i> Errors by Release Version</h5>
413
+ <h5 class="mb-0"><i class="bi bi-git"></i> <%= red_t("red.analytics.overview.releases.version_chart_title") %></h5>
414
414
  </div>
415
415
  <div class="card-body">
416
416
  <div id="errors-by-version-chart"></div>
@@ -436,40 +436,40 @@
436
436
  <div class="col-md-4">
437
437
  <div class="card">
438
438
  <div class="card-header">
439
- <h5 class="mb-0">Latest vs Previous</h5>
439
+ <h5 class="mb-0"><%= red_t("red.analytics.overview.releases.comparison_title") %></h5>
440
440
  </div>
441
441
  <div class="card-body">
442
442
  <% if @release_comparison.present? %>
443
443
  <div class="mb-3">
444
- <small class="text-muted">Latest Version</small>
444
+ <small class="text-muted"><%= red_t("red.analytics.overview.releases.latest_version") %></small>
445
445
  <h4><%= @release_comparison[:latest_version] %></h4>
446
446
  <p class="mb-0">
447
- <span class="badge bg-danger"><%= @release_comparison[:latest_count] %> errors</span>
447
+ <span class="badge bg-danger"><%= red_tp("red.analytics.overview.releases.latest_errors", count: @release_comparison[:latest_count]) %></span>
448
448
  <% if @release_comparison[:latest_critical] > 0 %>
449
- <span class="badge bg-dark ms-1"><%= @release_comparison[:latest_critical] %> critical</span>
449
+ <span class="badge bg-dark ms-1"><%= red_tp("red.analytics.overview.releases.latest_critical", count: @release_comparison[:latest_critical]) %></span>
450
450
  <% end %>
451
451
  </p>
452
452
  </div>
453
453
  <div class="mb-3">
454
- <small class="text-muted">Previous Version</small>
454
+ <small class="text-muted"><%= red_t("red.analytics.overview.releases.previous_version") %></small>
455
455
  <h4><%= @release_comparison[:previous_version] %></h4>
456
456
  <p class="mb-0">
457
- <span class="badge bg-secondary"><%= @release_comparison[:previous_count] %> errors</span>
457
+ <span class="badge bg-secondary"><%= red_tp("red.analytics.overview.releases.previous_errors", count: @release_comparison[:previous_count]) %></span>
458
458
  <% if @release_comparison[:previous_critical] > 0 %>
459
- <span class="badge bg-secondary ms-1"><%= @release_comparison[:previous_critical] %> critical</span>
459
+ <span class="badge bg-secondary ms-1"><%= red_tp("red.analytics.overview.releases.previous_critical", count: @release_comparison[:previous_critical]) %></span>
460
460
  <% end %>
461
461
  </p>
462
462
  </div>
463
463
  <hr>
464
464
  <div>
465
- <small class="text-muted">Change</small>
465
+ <small class="text-muted"><%= red_t("red.analytics.overview.releases.change") %></small>
466
466
  <h4 class="<%= @release_comparison[:change_percentage] > 0 ? 'text-danger' : 'text-success' %>">
467
467
  <%= @release_comparison[:change_percentage] > 0 ? '↑' : '↓' %>
468
468
  <%= @release_comparison[:change_percentage].abs %>%
469
469
  </h4>
470
470
  </div>
471
471
  <% else %>
472
- <p class="text-muted">Not enough release data for comparison.</p>
472
+ <p class="text-muted"><%= red_t("red.analytics.overview.releases.empty") %></p>
473
473
  <% end %>
474
474
  </div>
475
475
  </div>
@@ -479,15 +479,15 @@
479
479
  <!-- Problematic Releases Alert -->
480
480
  <% if @problematic_releases.any? %>
481
481
  <div class="alert alert-warning mb-4">
482
- <h5 class="alert-heading"><i class="bi bi-exclamation-triangle"></i> Problematic Releases Detected</h5>
483
- <p>The following releases have significantly higher error rates (&gt;2x average):</p>
482
+ <h5 class="alert-heading"><i class="bi bi-exclamation-triangle"></i> <%= red_t("red.analytics.overview.releases.problematic_title") %></h5>
483
+ <p><%= red_t("red.analytics.overview.releases.problematic_hint_html", multiplier: 2) %></p>
484
484
  <ul class="mb-0">
485
485
  <% @problematic_releases.each do |release| %>
486
486
  <li>
487
487
  <strong><%= release[:version] %></strong>:
488
- <%= release[:error_count] %> errors
489
- (<%= release[:critical_count] %> critical,
490
- +<%= release[:deviation_from_avg] %>% from average)
488
+ <%= red_t("red.analytics.overview.releases.problematic_entry",
489
+ count: release[:error_count], critical: release[:critical_count],
490
+ deviation: release[:deviation_from_avg]) %>
491
491
  </li>
492
492
  <% end %>
493
493
  </ul>
@@ -499,7 +499,7 @@
499
499
  <% if @mttr_stats[:total_resolved] > 0 %>
500
500
  <div class="card mb-4">
501
501
  <div class="card-header">
502
- <h5 class="mb-0"><i class="bi bi-clock-history"></i> Resolution Performance (MTTR)</h5>
502
+ <h5 class="mb-0"><i class="bi bi-clock-history"></i> <%= red_t("red.analytics.overview.mttr.title") %></h5>
503
503
  </div>
504
504
  <div class="card-body">
505
505
  <div class="row g-3 mb-4">
@@ -507,9 +507,9 @@
507
507
  <div class="col-md-3">
508
508
  <div class="card border-info">
509
509
  <div class="card-body text-center">
510
- <small class="text-muted d-block">Overall MTTR</small>
510
+ <small class="text-muted d-block"><%= red_t("red.analytics.overview.mttr.overall_title") %></small>
511
511
  <h3 class="text-info mb-0"><%= @mttr_stats[:overall_mttr] %>h</h3>
512
- <small class="text-muted">Average resolution time</small>
512
+ <small class="text-muted"><%= red_t("red.analytics.overview.mttr.overall_hint") %></small>
513
513
  </div>
514
514
  </div>
515
515
  </div>
@@ -518,9 +518,9 @@
518
518
  <div class="col-md-3">
519
519
  <div class="card border-success">
520
520
  <div class="card-body text-center">
521
- <small class="text-muted d-block">Fastest</small>
521
+ <small class="text-muted d-block"><%= red_t("red.analytics.overview.mttr.fastest_title") %></small>
522
522
  <h3 class="text-success mb-0"><%= @mttr_stats[:fastest_resolution] || 0 %>m</h3>
523
- <small class="text-muted">Best resolution time</small>
523
+ <small class="text-muted"><%= red_t("red.analytics.overview.mttr.fastest_hint") %></small>
524
524
  </div>
525
525
  </div>
526
526
  </div>
@@ -529,9 +529,9 @@
529
529
  <div class="col-md-3">
530
530
  <div class="card border-danger">
531
531
  <div class="card-body text-center">
532
- <small class="text-muted d-block">Slowest</small>
532
+ <small class="text-muted d-block"><%= red_t("red.analytics.overview.mttr.slowest_title") %></small>
533
533
  <h3 class="text-danger mb-0"><%= @mttr_stats[:slowest_resolution] || 0 %>h</h3>
534
- <small class="text-muted">Longest resolution time</small>
534
+ <small class="text-muted"><%= red_t("red.analytics.overview.mttr.slowest_hint") %></small>
535
535
  </div>
536
536
  </div>
537
537
  </div>
@@ -540,9 +540,9 @@
540
540
  <div class="col-md-3">
541
541
  <div class="card border-secondary">
542
542
  <div class="card-body text-center">
543
- <small class="text-muted d-block">Total Resolved</small>
543
+ <small class="text-muted d-block"><%= red_t("red.analytics.overview.mttr.total_resolved_title") %></small>
544
544
  <h3 class="text-secondary mb-0"><%= @mttr_stats[:total_resolved] %></h3>
545
- <small class="text-muted">In last <%= @days %> days</small>
545
+ <small class="text-muted"><%= red_t("red.analytics.overview.mttr.total_resolved_hint", days: @days) %></small>
546
546
  </div>
547
547
  </div>
548
548
  </div>
@@ -551,7 +551,7 @@
551
551
  <!-- MTTR by Platform -->
552
552
  <% if @mttr_by_platform.present? && @mttr_by_platform.any? %>
553
553
  <div class="mt-4">
554
- <h6 class="text-muted mb-3">MTTR by Platform</h6>
554
+ <h6 class="text-muted mb-3"><%= red_t("red.analytics.overview.mttr.by_platform_title") %></h6>
555
555
  <div id="mttr-by-platform-chart"></div>
556
556
  <script<%= " nonce=\"#{red_csp_nonce}\"".html_safe if red_csp_nonce %>>
557
557
  document.addEventListener('DOMContentLoaded', function() {
@@ -571,14 +571,14 @@
571
571
  <!-- MTTR by Severity -->
572
572
  <% if @mttr_stats[:mttr_by_severity].present? && @mttr_stats[:mttr_by_severity].any? %>
573
573
  <div class="mt-4">
574
- <h6 class="text-muted mb-3">MTTR by Severity</h6>
574
+ <h6 class="text-muted mb-3"><%= red_t("red.analytics.overview.mttr.by_severity_title") %></h6>
575
575
  <div class="table-responsive">
576
576
  <table class="table table-sm">
577
577
  <thead>
578
578
  <tr>
579
- <th>Severity</th>
580
- <th>Average Time to Resolution</th>
581
- <th>Actions</th>
579
+ <th><%= red_t("red.analytics.overview.mttr.column_severity") %></th>
580
+ <th><%= red_t("red.analytics.overview.mttr.column_avg_time") %></th>
581
+ <th><%= red_t("red.analytics.overview.mttr.column_actions") %></th>
582
582
  </tr>
583
583
  </thead>
584
584
  <tbody>
@@ -586,12 +586,12 @@
586
586
  <tr>
587
587
  <td>
588
588
  <span class="badge bg-<%= severity_color(severity) %>">
589
- <%= severity.to_s.capitalize %>
589
+ <%= red_t("red.common.severity.#{severity}", default: severity.to_s) %>
590
590
  </span>
591
591
  </td>
592
- <td><strong><%= hours %> hours</strong></td>
592
+ <td><strong><%= red_t("red.analytics.overview.mttr.hours", hours: hours) %></strong></td>
593
593
  <td>
594
- <%= link_to "View", errors_path(severity: severity, unresolved: '0'), class: "btn btn-sm btn-outline-primary" %>
594
+ <%= link_to red_t("red.analytics.overview.mttr.view"), errors_path(severity: severity, unresolved: '0'), class: "btn btn-sm btn-outline-primary" %>
595
595
  </td>
596
596
  </tr>
597
597
  <% end %>
@@ -604,7 +604,7 @@
604
604
  <!-- MTTR Trend -->
605
605
  <% if @mttr_stats[:mttr_trend].present? && @mttr_stats[:mttr_trend].any? %>
606
606
  <div class="mt-4">
607
- <h6 class="text-muted mb-3">MTTR Trend (Weekly)</h6>
607
+ <h6 class="text-muted mb-3"><%= red_t("red.analytics.overview.mttr.trend_title") %></h6>
608
608
  <div id="mttr-trend-chart"></div>
609
609
  <script<%= " nonce=\"#{red_csp_nonce}\"".html_safe if red_csp_nonce %>>
610
610
  document.addEventListener('DOMContentLoaded', function() {
@@ -1,21 +1,21 @@
1
- <% content_for :page_title, "Cache Health" %>
1
+ <% content_for :page_title, red_t("red.health.cache.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-lightning-charge me-2"></i>
7
- Cache Health
7
+ <%= red_t("red.health.cache.title") %>
8
8
  </h1>
9
9
 
10
10
  <div class="btn-group" role="group">
11
11
  <%= link_to cache_health_summary_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 cache_health_summary_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 cache_health_summary_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>
@@ -23,18 +23,18 @@
23
23
  <% if @errors_with_cache == 0 %>
24
24
  <div class="red-empty-state">
25
25
  <div class="red-empty-state-icon" style="background: var(--status-success-bg); color: var(--status-success);"><i class="bi bi-check-lg"></i></div>
26
- <div class="red-empty-state-title">No Cache Activity Found</div>
26
+ <div class="red-empty-state-title"><%= red_t("red.health.cache.empty.title") %></div>
27
27
  <p class="text-muted">
28
- No cache operations were detected in error breadcrumbs over the last <%= @days %> days.
28
+ <%= red_t("red.health.cache.empty.message", days: @days) %>
29
29
  </p>
30
30
  <div class="card mx-auto" style="max-width: 500px;">
31
31
  <div class="card-body text-start">
32
- <h6>How cache tracking works:</h6>
32
+ <h6><%= red_t("red.health.cache.empty.how_it_works") %></h6>
33
33
  <ul class="mb-0">
34
- <li>Breadcrumbs must be enabled (<code>enable_breadcrumbs = true</code>)</li>
35
- <li>Cache reads and writes are captured as breadcrumbs during requests that produce errors</li>
36
- <li>Hit/miss status is tracked for read operations</li>
37
- <li>This page shows cache performance per-error, sorted worst-first</li>
34
+ <li><%= red_t("red.health.cache.empty.step_breadcrumbs_html") %></li>
35
+ <li><%= red_t("red.health.cache.empty.step_capture") %></li>
36
+ <li><%= red_t("red.health.cache.empty.step_hit_status") %></li>
37
+ <li><%= red_t("red.health.cache.empty.step_sort") %></li>
38
38
  </ul>
39
39
  </div>
40
40
  </div>
@@ -45,7 +45,7 @@
45
45
  <div class="card text-center">
46
46
  <div class="card-body">
47
47
  <div class="display-6 text-info"><%= @errors_with_cache %></div>
48
- <small class="text-muted">Errors with Cache</small>
48
+ <small class="text-muted"><%= red_t("red.health.cache.stats.errors_with_cache") %></small>
49
49
  </div>
50
50
  </div>
51
51
  </div>
@@ -53,12 +53,12 @@
53
53
  <div class="card text-center">
54
54
  <div class="card-body">
55
55
  <% if @avg_hit_rate.nil? %>
56
- <div class="display-6 text-secondary">N/A</div>
56
+ <div class="display-6 text-secondary"><%= red_t("red.common.not_available") %></div>
57
57
  <% else %>
58
58
  <% hit_color = @avg_hit_rate >= 80 ? "success" : (@avg_hit_rate >= 50 ? "warning" : "danger") %>
59
59
  <div class="display-6 text-<%= hit_color %>"><%= @avg_hit_rate %>%</div>
60
60
  <% end %>
61
- <small class="text-muted">Avg Hit Rate</small>
61
+ <small class="text-muted"><%= red_t("red.health.cache.stats.avg_hit_rate") %></small>
62
62
  </div>
63
63
  </div>
64
64
  </div>
@@ -66,7 +66,7 @@
66
66
  <div class="card text-center">
67
67
  <div class="card-body">
68
68
  <div class="display-6 text-secondary"><%= @total_cache_ops %></div>
69
- <small class="text-muted">Total Cache Ops</small>
69
+ <small class="text-muted"><%= red_t("red.health.cache.stats.total_ops") %></small>
70
70
  </div>
71
71
  </div>
72
72
  </div>
@@ -76,7 +76,7 @@
76
76
  <div class="card-header d-flex justify-content-between align-items-center">
77
77
  <h5 class="mb-0">
78
78
  <i class="bi bi-lightning-charge text-info me-2"></i>
79
- Cache Performance by Error
79
+ <%= red_t("red.health.cache.table.title") %>
80
80
  <span class="badge bg-info"><%= @errors_with_cache %></span>
81
81
  </h5>
82
82
  <small class="text-muted"><%== @pagy.info_tag %></small>
@@ -86,13 +86,13 @@
86
86
  <table class="table table-hover mb-0">
87
87
  <thead class="table-light">
88
88
  <tr>
89
- <th width="100">Error</th>
90
- <th width="80">Reads</th>
91
- <th width="80">Writes</th>
92
- <th width="120">Hit Rate</th>
93
- <th width="120">Total Time</th>
94
- <th>Slowest Op</th>
95
- <th width="140">Last Seen</th>
89
+ <th width="100"><%= red_t("red.health.cache.table.column_error") %></th>
90
+ <th width="80"><%= red_t("red.health.cache.table.column_reads") %></th>
91
+ <th width="80"><%= red_t("red.health.cache.table.column_writes") %></th>
92
+ <th width="120"><%= red_t("red.health.cache.table.column_hit_rate") %></th>
93
+ <th width="120"><%= red_t("red.health.cache.table.column_total_time") %></th>
94
+ <th><%= red_t("red.health.cache.table.column_slowest_op") %></th>
95
+ <th width="140"><%= red_t("red.health.cache.table.column_last_seen") %></th>
96
96
  </tr>
97
97
  </thead>
98
98
  <tbody>
@@ -103,7 +103,7 @@
103
103
  <td><%= entry[:writes] %></td>
104
104
  <td>
105
105
  <% if entry[:hit_rate].nil? %>
106
- <span class="badge bg-secondary">N/A</span>
106
+ <span class="badge bg-secondary"><%= red_t("red.common.not_available") %></span>
107
107
  <% else %>
108
108
  <% rate_color = entry[:hit_rate] >= 80 ? "success" : (entry[:hit_rate] >= 50 ? "warning" : "danger") %>
109
109
  <span class="badge bg-<%= rate_color %>"><%= entry[:hit_rate] %>%</span>
@@ -126,11 +126,11 @@
126
126
  <div class="card-footer border-top d-flex justify-content-between align-items-center">
127
127
  <div>
128
128
  <small class="text-muted">
129
- <i class="bi bi-lightbulb text-warning"></i> Low hit rates may indicate cache keys that expire too quickly or aren't being set properly.
129
+ <i class="bi bi-lightbulb text-warning"></i> <%= red_t("red.health.cache.table.tip") %>
130
130
  </small>
131
131
  <small class="ms-3">
132
132
  <a href="https://guides.rubyonrails.org/caching_with_rails.html" target="_blank" rel="noopener" class="text-decoration-none">
133
- <i class="bi bi-book"></i> Rails Caching Guide <i class="bi bi-box-arrow-up-right" style="font-size: 0.7em;"></i>
133
+ <i class="bi bi-book"></i> <%= red_t("red.health.cache.table.guide_link") %> <i class="bi bi-box-arrow-up-right" style="font-size: 0.7em;"></i>
134
134
  </a>
135
135
  </small>
136
136
  </div>