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,26 +1,32 @@
1
1
  <!-- Similar Errors (Fuzzy Matching) -->
2
2
  <% if RailsErrorDashboard.configuration.enable_similar_errors && error.respond_to?(:similar_errors) %>
3
- <% similar = error.similar_errors(threshold: 0.6, limit: 5) %>
3
+ <%# The threshold drives both the query and the hint below, so it is
4
+ named once rather than written as a literal in the prose too. %>
5
+ <% similarity_threshold = 0.6 %>
6
+ <% similar = error.similar_errors(threshold: similarity_threshold, limit: 5) %>
4
7
  <% if similar.any? %>
5
- <% cache [error, 'similar_errors_v1', similar.map { |s| s[:error]&.updated_at }.compact.max] do %>
8
+ <%# The locale is part of the cache key: this fragment contains
9
+ translated text, and without it the first locale to render an
10
+ error would be served to every other one. %>
11
+ <% cache [error, 'similar_errors_v3', red_locale, similar.map { |s| s[:error]&.updated_at }.compact.max] do %>
6
12
  <div class="card mb-4" id="section-similar-errors">
7
13
  <div class="card-header">
8
14
  <h5 class="mb-0">
9
- <i class="bi bi-diagram-3"></i> Similar Errors
10
- <span class="badge bg-info text-dark">Fuzzy Matching</span>
15
+ <i class="bi bi-diagram-3"></i> <%= red_t("red.errors.similar_errors.title") %>
16
+ <span class="badge bg-info text-dark"><%= red_t("red.errors.similar_errors.badge") %></span>
11
17
  </h5>
12
- <small class="text-muted">Errors with similar backtraces and messages (60%+ similarity)</small>
18
+ <small class="text-muted"><%= red_t("red.errors.similar_errors.hint", threshold: (similarity_threshold * 100).round) %></small>
13
19
  </div>
14
20
  <div class="card-body p-0">
15
21
  <div class="table-responsive">
16
22
  <table class="table table-hover mb-0">
17
23
  <thead class="table-light">
18
24
  <tr>
19
- <th>Similarity</th>
20
- <th>Error Type</th>
21
- <th>Message</th>
22
- <th>Platform</th>
23
- <th>Occurrences</th>
25
+ <th><%= red_t("red.errors.similar_errors.column_similarity") %></th>
26
+ <th><%= red_t("red.errors.similar_errors.column_error_type") %></th>
27
+ <th><%= red_t("red.errors.similar_errors.column_message") %></th>
28
+ <th><%= red_t("red.errors.similar_errors.column_platform") %></th>
29
+ <th><%= red_t("red.errors.similar_errors.column_occurrences") %></th>
24
30
  <th></th>
25
31
  </tr>
26
32
  </thead>
@@ -59,9 +65,9 @@
59
65
  <span class="badge badge-api"><i class="bi bi-server"></i> <%= similar_error.platform || 'API' %></span>
60
66
  <% end %>
61
67
  </td>
62
- <td><span class="badge bg-primary"><%= similar_error.occurrence_count %>x</span></td>
68
+ <td><span class="badge bg-primary"><%= red_tp("red.errors.similar_errors.occurrences", count: similar_error.occurrence_count) %></span></td>
63
69
  <td>
64
- <%= link_to "View", error_path(similar_error), class: "btn btn-sm btn-outline-primary" %>
70
+ <%= link_to red_t("red.errors.similar_errors.view"), error_path(similar_error), class: "btn btn-sm btn-outline-primary" %>
65
71
  </td>
66
72
  </tr>
67
73
  <% end %>
@@ -20,10 +20,18 @@
20
20
  </div>
21
21
 
22
22
  <% if repo_link %>
23
+ <%
24
+ # Repository host names are brands and stay English; only the
25
+ # sentence around the name is translated.
26
+ repo_host = if repo_link.include?("github") then "GitHub"
27
+ elsif repo_link.include?("gitlab") then "GitLab"
28
+ else "Bitbucket"
29
+ end
30
+ %>
23
31
  <%= link_to repo_link, target: "_blank", rel: "noopener",
24
32
  class: "btn btn-sm btn-primary",
25
- title: "View on #{repo_link.include?('github') ? 'GitHub' : repo_link.include?('gitlab') ? 'GitLab' : 'Bitbucket'}" do %>
26
- <i class="bi bi-box-arrow-up-right"></i> View Source
33
+ title: red_t("red.errors.source_code.view_on", host: repo_host) do %>
34
+ <i class="bi bi-box-arrow-up-right"></i> <%= red_t("red.errors.source_code.view_source") %>
27
35
  <% end %>
28
36
  <% end %>
29
37
  </div>
@@ -38,7 +46,9 @@
38
46
  </div>
39
47
  <div class="d-flex align-items-center gap-1">
40
48
  <i class="bi bi-clock"></i>
41
- <span><%= time_ago_in_words(blame_data[:date]) %> ago</span>
49
+ <%# One key, not a duration concatenated with " ago": several
50
+ languages put the equivalent of "ago" before the duration. %>
51
+ <span><%= red_t("red.time.ago", duration: time_ago_in_words(blame_data[:date])) %></span>
42
52
  </div>
43
53
  <% if blame_data[:commit_message] %>
44
54
  <div class="d-flex align-items-center gap-1 flex-grow-1">
@@ -66,7 +76,12 @@
66
76
  <% if coverage_data %>
67
77
  <div class="px-3 py-1 bg-info bg-opacity-10 border-bottom">
68
78
  <small class="text-info">
69
- <i class="bi bi-broadcast"></i> Coverage active <span class="text-success">green</span> = executed, <span class="text-secondary">gray</span> = not executed
79
+ <%# One sentence with the colours interpolated, not a sentence cut
80
+ into pieces by two <span>s — the clause order is not the same
81
+ in every language. %>
82
+ <i class="bi bi-broadcast"></i> <%= red_t("red.errors.source_code.coverage_legend_html",
83
+ executed: tag.span(red_t("red.errors.source_code.coverage_green"), class: "text-success"),
84
+ not_executed: tag.span(red_t("red.errors.source_code.coverage_gray"), class: "text-secondary")) %>
70
85
  </small>
71
86
  </div>
72
87
  <% end %>
@@ -83,7 +98,7 @@
83
98
  <!-- Error message if source couldn't be read -->
84
99
  <div class="alert alert-warning mt-2 mb-2 py-2">
85
100
  <i class="bi bi-exclamation-triangle"></i>
86
- <small>Could not read source: <%= source_data[:error] %></small>
101
+ <small><%= red_t("red.errors.source_code.read_failed", message: source_data[:error]) %></small>
87
102
  </div>
88
103
  <% end %>
89
104
  <% end %>
@@ -2,7 +2,7 @@
2
2
  <div class="col">
3
3
  <div class="card stat-card">
4
4
  <div class="card-body">
5
- <div class="stat-label mb-2">Today</div>
5
+ <div class="stat-label mb-2"><%= red_t("red.errors.stats.today") %></div>
6
6
  <div class="stat-value text-info"><%= stats[:total_today] %></div>
7
7
  </div>
8
8
  </div>
@@ -10,7 +10,7 @@
10
10
  <div class="col">
11
11
  <div class="card stat-card">
12
12
  <div class="card-body">
13
- <div class="stat-label mb-2">This Week</div>
13
+ <div class="stat-label mb-2"><%= red_t("red.errors.stats.this_week") %></div>
14
14
  <div class="stat-value text-primary"><%= stats[:total_week] %></div>
15
15
  </div>
16
16
  </div>
@@ -18,7 +18,7 @@
18
18
  <div class="col">
19
19
  <div class="card stat-card">
20
20
  <div class="card-body">
21
- <div class="stat-label mb-2">Unresolved</div>
21
+ <div class="stat-label mb-2"><%= red_t("red.errors.stats.unresolved") %></div>
22
22
  <div class="stat-value text-danger"><%= stats[:unresolved] %></div>
23
23
  </div>
24
24
  </div>
@@ -26,7 +26,7 @@
26
26
  <div class="col">
27
27
  <div class="card stat-card">
28
28
  <div class="card-body">
29
- <div class="stat-label mb-2">Resolved</div>
29
+ <div class="stat-label mb-2"><%= red_t("red.errors.stats.resolved") %></div>
30
30
  <div class="stat-value text-success"><%= stats[:resolved] %></div>
31
31
  </div>
32
32
  </div>
@@ -34,7 +34,7 @@
34
34
  <div class="col">
35
35
  <div class="card stat-card">
36
36
  <div class="card-body">
37
- <div class="stat-label mb-2">Reopened</div>
37
+ <div class="stat-label mb-2"><%= red_t("red.errors.stats.reopened") %></div>
38
38
  <div class="stat-value text-warning"><%= stats[:reopened] %></div>
39
39
  </div>
40
40
  </div>
@@ -3,8 +3,8 @@
3
3
  <div class="card" id="section-timeline">
4
4
  <div class="card-header">
5
5
  <h5 class="mb-0">
6
- <i class="bi bi-clock-history"></i> Timeline
7
- <small class="text-muted ms-2">Recent related errors</small>
6
+ <i class="bi bi-clock-history"></i> <%= red_t("red.errors.timeline.title") %>
7
+ <small class="text-muted ms-2"><%= red_t("red.errors.timeline.hint") %></small>
8
8
  </h5>
9
9
  </div>
10
10
  <div class="card-body">
@@ -26,7 +26,7 @@
26
26
  <div class="flex-grow-1">
27
27
  <% if error.id == @error.id %>
28
28
  <strong class="text-danger">
29
- <i class="bi bi-arrow-right-circle"></i> Current Error
29
+ <i class="bi bi-arrow-right-circle"></i> <%= red_t("red.errors.timeline.current_error") %>
30
30
  </strong>
31
31
  <% else %>
32
32
  <%= link_to error_path(error), class: "text-decoration-none" do %>
@@ -62,7 +62,7 @@
62
62
  <% if error.user_id %>
63
63
  <span class="text-muted">
64
64
  <i class="bi bi-person"></i>
65
- User: <%= error.user_id %>
65
+ <%= red_t("red.errors.timeline.user", id: error.user_id) %>
66
66
  </span>
67
67
  <% end %>
68
68
 
@@ -78,9 +78,9 @@
78
78
  <% if error.resolution_comment.present? %>
79
79
  data-bs-toggle="tooltip"
80
80
  data-bs-placement="top"
81
- title="Resolution Notes: <%= error.resolution_comment.truncate(150) %>"
81
+ title="<%= red_t("red.errors.timeline.notes_tooltip", notes: error.resolution_comment.truncate(150)) %>"
82
82
  <% end %>>
83
- <i class="bi bi-check-circle"></i> Resolved
83
+ <i class="bi bi-check-circle"></i> <%= red_t("red.errors.timeline.resolved") %>
84
84
  </span>
85
85
  <% end %>
86
86
  </div>
@@ -88,7 +88,7 @@
88
88
  <% if error.resolved? && error.resolution_comment.present? %>
89
89
  <div style="margin-top: var(--space-2); padding: var(--space-2) var(--space-3); background: var(--status-success-bg); border: 1px solid var(--status-success); border-radius: var(--radius-sm);">
90
90
  <small style="color: var(--status-success); font-weight: 600;">
91
- <i class="bi bi-journal-text"></i> Resolution Notes:
91
+ <i class="bi bi-journal-text"></i> <%= red_t("red.errors.timeline.notes_label") %>
92
92
  </small>
93
93
  <div style="font-size: 13px; color: var(--text-primary); margin-top: 4px;">
94
94
  <%= auto_link_urls(truncate(error.resolution_comment, length: 200), error: error).html_safe %>
@@ -3,20 +3,20 @@
3
3
  <thead class="table-light">
4
4
  <tr>
5
5
  <% if show_rank %>
6
- <th>Rank</th>
6
+ <th><%= red_t("red.errors.user_errors_table.column_rank") %></th>
7
7
  <% end %>
8
- <th>User</th>
8
+ <th><%= red_t("red.errors.user_errors_table.column_user") %></th>
9
9
  <% if show_error_type_count %>
10
- <th>Different Error Types</th>
10
+ <th><%= red_t("red.errors.user_errors_table.column_error_type_count") %></th>
11
11
  <% end %>
12
- <th>Error Count</th>
12
+ <th><%= red_t("red.errors.user_errors_table.column_error_count") %></th>
13
13
  <% if show_percentage %>
14
- <th>Percentage</th>
14
+ <th><%= red_t("red.errors.user_errors_table.column_percentage") %></th>
15
15
  <% end %>
16
16
  <% if show_error_types %>
17
- <th>Error Types</th>
17
+ <th><%= red_t("red.errors.user_errors_table.column_error_types") %></th>
18
18
  <% end %>
19
- <th>Actions</th>
19
+ <th><%= red_t("red.errors.user_errors_table.column_actions") %></th>
20
20
  </tr>
21
21
  </thead>
22
22
  <tbody>
@@ -29,7 +29,7 @@
29
29
  <% if show_error_type_count %>
30
30
  <td>
31
31
  <span class="badge bg-warning text-dark">
32
- <%= user_data[:error_type_count] %> types
32
+ <%= red_tp("red.errors.user_errors_table.types", count: user_data[:error_type_count]) %>
33
33
  </span>
34
34
  </td>
35
35
  <% end %>
@@ -52,16 +52,19 @@
52
52
  <% end %>
53
53
  <% if show_error_types && user_data[:error_types].present? %>
54
54
  <td>
55
- <% user_data[:error_types].first(3).each do |error_type| %>
55
+ <%# The cut-off is named once: it bounds the list and the
56
+ "+N more" count, which must agree. %>
57
+ <% shown_error_types = 3 %>
58
+ <% user_data[:error_types].first(shown_error_types).each do |error_type| %>
56
59
  <code class="small me-1"><%= error_type %></code>
57
60
  <% end %>
58
- <% if user_data[:error_types].count > 3 %>
59
- <span class="text-muted small">+<%= user_data[:error_types].count - 3 %> more</span>
61
+ <% if user_data[:error_types].count > shown_error_types %>
62
+ <span class="text-muted small"><%= red_tp("red.errors.user_errors_table.more", count: user_data[:error_types].count - shown_error_types) %></span>
60
63
  <% end %>
61
64
  </td>
62
65
  <% end %>
63
66
  <td>
64
- <%= link_to "View Errors", errors_path(user_id: user_data[:user_id], unresolved: '0'), class: "btn btn-sm btn-outline-primary" %>
67
+ <%= link_to red_t("red.errors.user_errors_table.view_errors"), errors_path(user_id: user_data[:user_id], unresolved: '0'), class: "btn btn-sm btn-outline-primary" %>
65
68
  </td>
66
69
  </tr>
67
70
  <% end %>
@@ -1,21 +1,21 @@
1
- <% content_for :page_title, "ActionCable Health" %>
1
+ <% content_for :page_title, red_t("red.health.actioncable.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-broadcast me-2"></i>
7
- ActionCable Health
7
+ <%= red_t("red.health.actioncable.title") %>
8
8
  </h1>
9
9
 
10
10
  <div class="btn-group" role="group">
11
11
  <%= link_to actioncable_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 actioncable_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 actioncable_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 @unique_channels == 0 %>
24
24
  <div class="red-empty-state">
25
25
  <i class="bi bi-broadcast display-1 text-success mb-3"></i>
26
- <div class="red-empty-state-title">No ActionCable Events Found</div>
26
+ <div class="red-empty-state-title"><%= red_t("red.health.actioncable.empty.title") %></div>
27
27
  <p class="text-muted">
28
- No ActionCable channel actions, transmissions, or subscription events were detected in error breadcrumbs over the last <%= @days %> days.
28
+ <%= red_t("red.health.actioncable.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 ActionCable tracking works:</h6>
32
+ <h6><%= red_t("red.health.actioncable.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>ActionCable tracking must be enabled (<code>enable_actioncable_tracking = true</code>)</li>
36
- <li>ActionCable must be configured in your app</li>
37
- <li>Channel actions, transmissions, and subscription events are captured as breadcrumbs during requests that produce errors</li>
34
+ <li><%= red_t("red.health.actioncable.empty.step_breadcrumbs_html") %></li>
35
+ <li><%= red_t("red.health.actioncable.empty.step_tracking_html") %></li>
36
+ <li><%= red_t("red.health.actioncable.empty.step_configured") %></li>
37
+ <li><%= red_t("red.health.actioncable.empty.step_capture") %></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-primary"><%= @unique_channels %></div>
48
- <small class="text-muted">Active Channels</small>
48
+ <small class="text-muted"><%= red_t("red.health.actioncable.stats.active_channels") %></small>
49
49
  </div>
50
50
  </div>
51
51
  </div>
@@ -53,7 +53,7 @@
53
53
  <div class="card text-center">
54
54
  <div class="card-body">
55
55
  <div class="display-6 text-info"><%= @total_events %></div>
56
- <small class="text-muted">Total Events</small>
56
+ <small class="text-muted"><%= red_t("red.health.actioncable.stats.total_events") %></small>
57
57
  </div>
58
58
  </div>
59
59
  </div>
@@ -61,7 +61,7 @@
61
61
  <div class="card text-center">
62
62
  <div class="card-body">
63
63
  <div class="display-6 <%= @total_rejections > 0 ? 'text-danger' : 'text-success' %>"><%= @total_rejections %></div>
64
- <small class="text-muted">Subscription Rejections</small>
64
+ <small class="text-muted"><%= red_t("red.health.actioncable.stats.subscription_rejections") %></small>
65
65
  </div>
66
66
  </div>
67
67
  </div>
@@ -71,7 +71,7 @@
71
71
  <div class="card-header d-flex justify-content-between align-items-center">
72
72
  <h5 class="mb-0">
73
73
  <i class="bi bi-broadcast text-primary me-2"></i>
74
- ActionCable Events by Channel
74
+ <%= red_t("red.health.actioncable.table.title") %>
75
75
  <span class="badge bg-primary"><%= @unique_channels %></span>
76
76
  </h5>
77
77
  <small class="text-muted"><%== @pagy.info_tag %></small>
@@ -81,13 +81,13 @@
81
81
  <table class="table table-hover mb-0">
82
82
  <thead class="table-light">
83
83
  <tr>
84
- <th>Channel</th>
85
- <th width="100">Actions</th>
86
- <th width="120">Transmissions</th>
87
- <th width="120">Subscriptions</th>
88
- <th width="100">Rejections</th>
89
- <th width="80">Errors</th>
90
- <th width="140">Last Seen</th>
84
+ <th><%= red_t("red.health.actioncable.table.column_channel") %></th>
85
+ <th width="100"><%= red_t("red.health.actioncable.table.column_actions") %></th>
86
+ <th width="120"><%= red_t("red.health.actioncable.table.column_transmissions") %></th>
87
+ <th width="120"><%= red_t("red.health.actioncable.table.column_subscriptions") %></th>
88
+ <th width="100"><%= red_t("red.health.actioncable.table.column_rejections") %></th>
89
+ <th width="80"><%= red_t("red.health.actioncable.table.column_errors") %></th>
90
+ <th width="140"><%= red_t("red.health.actioncable.table.column_last_seen") %></th>
91
91
  </tr>
92
92
  </thead>
93
93
  <tbody>
@@ -115,11 +115,11 @@
115
115
  <div class="card-footer border-top d-flex justify-content-between align-items-center">
116
116
  <div>
117
117
  <small class="text-muted">
118
- <i class="bi bi-lightbulb text-warning"></i> ActionCable events are captured when they coincide with errors. High rejection counts may indicate authentication or authorization issues.
118
+ <i class="bi bi-lightbulb text-warning"></i> <%= red_t("red.health.actioncable.table.tip") %>
119
119
  </small>
120
120
  <small class="ms-3">
121
121
  <a href="https://guides.rubyonrails.org/action_cable_overview.html" target="_blank" rel="noopener" class="text-decoration-none">
122
- <i class="bi bi-book"></i> ActionCable Guide <i class="bi bi-box-arrow-up-right" style="font-size: 0.7em;"></i>
122
+ <i class="bi bi-book"></i> <%= red_t("red.health.actioncable.table.guide_link") %> <i class="bi bi-box-arrow-up-right" style="font-size: 0.7em;"></i>
123
123
  </a>
124
124
  </small>
125
125
  </div>
@@ -1,21 +1,21 @@
1
- <% content_for :page_title, "ActiveStorage Health" %>
1
+ <% content_for :page_title, red_t("red.health.activestorage.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-cloud-arrow-up me-2"></i>
7
- ActiveStorage Health
7
+ <%= red_t("red.health.activestorage.title") %>
8
8
  </h1>
9
9
 
10
10
  <div class="btn-group" role="group">
11
11
  <%= link_to activestorage_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 activestorage_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 activestorage_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 @unique_services == 0 %>
24
24
  <div class="red-empty-state">
25
25
  <i class="bi bi-cloud-arrow-up display-1 text-success mb-3"></i>
26
- <div class="red-empty-state-title">No ActiveStorage Events Found</div>
26
+ <div class="red-empty-state-title"><%= red_t("red.health.activestorage.empty.title") %></div>
27
27
  <p class="text-muted">
28
- No ActiveStorage service operations were detected in error breadcrumbs over the last <%= @days %> days.
28
+ <%= red_t("red.health.activestorage.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 ActiveStorage tracking works:</h6>
32
+ <h6><%= red_t("red.health.activestorage.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>ActiveStorage tracking must be enabled (<code>enable_activestorage_tracking = true</code>)</li>
36
- <li>ActiveStorage must be configured in your app</li>
37
- <li>Uploads, downloads, and deletes are captured as breadcrumbs during requests that produce errors</li>
34
+ <li><%= red_t("red.health.activestorage.empty.step_breadcrumbs_html") %></li>
35
+ <li><%= red_t("red.health.activestorage.empty.step_tracking_html") %></li>
36
+ <li><%= red_t("red.health.activestorage.empty.step_configured") %></li>
37
+ <li><%= red_t("red.health.activestorage.empty.step_capture") %></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-primary"><%= @unique_services %></div>
48
- <small class="text-muted">Storage Services</small>
48
+ <small class="text-muted"><%= red_t("red.health.activestorage.stats.storage_services") %></small>
49
49
  </div>
50
50
  </div>
51
51
  </div>
@@ -53,7 +53,7 @@
53
53
  <div class="card text-center">
54
54
  <div class="card-body">
55
55
  <div class="display-6 text-info"><%= @total_operations %></div>
56
- <small class="text-muted">Total Operations</small>
56
+ <small class="text-muted"><%= red_t("red.health.activestorage.stats.total_operations") %></small>
57
57
  </div>
58
58
  </div>
59
59
  </div>
@@ -61,7 +61,7 @@
61
61
  <div class="card text-center">
62
62
  <div class="card-body">
63
63
  <div class="display-6 text-warning"><%= @errors_with_storage %></div>
64
- <small class="text-muted">Errors with Storage Ops</small>
64
+ <small class="text-muted"><%= red_t("red.health.activestorage.stats.errors_with_storage") %></small>
65
65
  </div>
66
66
  </div>
67
67
  </div>
@@ -71,7 +71,7 @@
71
71
  <div class="card-header d-flex justify-content-between align-items-center">
72
72
  <h5 class="mb-0">
73
73
  <i class="bi bi-cloud-arrow-up text-primary me-2"></i>
74
- Storage Operations by Service
74
+ <%= red_t("red.health.activestorage.table.title") %>
75
75
  <span class="badge bg-primary"><%= @unique_services %></span>
76
76
  </h5>
77
77
  <small class="text-muted"><%== @pagy.info_tag %></small>
@@ -81,16 +81,16 @@
81
81
  <table class="table table-hover mb-0">
82
82
  <thead class="table-light">
83
83
  <tr>
84
- <th>Service</th>
85
- <th width="90">Uploads</th>
86
- <th width="100">Downloads</th>
87
- <th width="90">Deletes</th>
88
- <th width="90">Exists</th>
89
- <th width="80">Total</th>
90
- <th width="100">Avg Duration</th>
91
- <th width="100">Slowest</th>
92
- <th width="80">Errors</th>
93
- <th width="140">Last Seen</th>
84
+ <th><%= red_t("red.health.activestorage.table.column_service") %></th>
85
+ <th width="90"><%= red_t("red.health.activestorage.table.column_uploads") %></th>
86
+ <th width="100"><%= red_t("red.health.activestorage.table.column_downloads") %></th>
87
+ <th width="90"><%= red_t("red.health.activestorage.table.column_deletes") %></th>
88
+ <th width="90"><%= red_t("red.health.activestorage.table.column_exists") %></th>
89
+ <th width="80"><%= red_t("red.health.activestorage.table.column_total") %></th>
90
+ <th width="100"><%= red_t("red.health.activestorage.table.column_avg_duration") %></th>
91
+ <th width="100"><%= red_t("red.health.activestorage.table.column_slowest") %></th>
92
+ <th width="80"><%= red_t("red.health.activestorage.table.column_errors") %></th>
93
+ <th width="140"><%= red_t("red.health.activestorage.table.column_last_seen") %></th>
94
94
  </tr>
95
95
  </thead>
96
96
  <tbody>
@@ -131,11 +131,11 @@
131
131
  <div class="card-footer border-top d-flex justify-content-between align-items-center">
132
132
  <div>
133
133
  <small class="text-muted">
134
- <i class="bi bi-lightbulb text-warning"></i> Slow storage operations may indicate network issues, large file sizes, or service provider throttling.
134
+ <i class="bi bi-lightbulb text-warning"></i> <%= red_t("red.health.activestorage.table.tip") %>
135
135
  </small>
136
136
  <small class="ms-3">
137
137
  <a href="https://guides.rubyonrails.org/active_storage_overview.html" target="_blank" rel="noopener" class="text-decoration-none">
138
- <i class="bi bi-book"></i> ActiveStorage Guide <i class="bi bi-box-arrow-up-right" style="font-size: 0.7em;"></i>
138
+ <i class="bi bi-book"></i> <%= red_t("red.health.activestorage.table.guide_link") %> <i class="bi bi-box-arrow-up-right" style="font-size: 0.7em;"></i>
139
139
  </a>
140
140
  </small>
141
141
  </div>