rails_error_dashboard 0.8.4 → 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.
- checksums.yaml +4 -4
- data/README.md +35 -0
- data/app/controllers/rails_error_dashboard/application_controller.rb +107 -4
- data/app/controllers/rails_error_dashboard/errors_controller.rb +91 -73
- data/app/controllers/rails_error_dashboard/locales_controller.rb +87 -0
- data/app/helpers/rails_error_dashboard/application_helper.rb +20 -14
- data/app/helpers/rails_error_dashboard/backtrace_helper.rb +15 -6
- data/app/helpers/rails_error_dashboard/i18n_helper.rb +154 -0
- data/app/helpers/rails_error_dashboard/mailer_i18n_helper.rb +69 -0
- data/app/helpers/rails_error_dashboard/overview_helper.rb +18 -6
- data/app/helpers/rails_error_dashboard/user_agent_helper.rb +25 -11
- data/app/jobs/rails_error_dashboard/application_job.rb +5 -0
- data/app/jobs/rails_error_dashboard/baseline_alert_job.rb +18 -16
- data/app/jobs/rails_error_dashboard/concerns/localized_job.rb +83 -0
- data/app/jobs/rails_error_dashboard/discord_error_notification_job.rb +4 -2
- data/app/jobs/rails_error_dashboard/email_error_notification_job.rb +4 -2
- data/app/jobs/rails_error_dashboard/pagerduty_error_notification_job.rb +6 -2
- data/app/jobs/rails_error_dashboard/scheduled_digest_job.rb +8 -3
- data/app/jobs/rails_error_dashboard/slack_error_notification_job.rb +6 -4
- data/app/jobs/rails_error_dashboard/storm_notification_job.rb +26 -8
- data/app/jobs/rails_error_dashboard/webhook_error_notification_job.rb +4 -2
- data/app/mailers/rails_error_dashboard/application_mailer.rb +14 -0
- data/app/mailers/rails_error_dashboard/digest_mailer.rb +18 -2
- data/app/mailers/rails_error_dashboard/error_notification_mailer.rb +19 -2
- data/app/views/layouts/rails_error_dashboard.html.erb +255 -92
- data/app/views/rails_error_dashboard/digest_mailer/digest_summary.html.erb +23 -22
- data/app/views/rails_error_dashboard/digest_mailer/digest_summary.text.erb +30 -19
- data/app/views/rails_error_dashboard/error_notification_mailer/error_alert.html.erb +18 -16
- data/app/views/rails_error_dashboard/error_notification_mailer/error_alert.text.erb +21 -14
- data/app/views/rails_error_dashboard/errors/_ai_help_panel.html.erb +11 -7
- data/app/views/rails_error_dashboard/errors/_breadcrumbs_group.html.erb +45 -38
- data/app/views/rails_error_dashboard/errors/_co_occurring_errors.html.erb +17 -14
- data/app/views/rails_error_dashboard/errors/_discussion.html.erb +27 -9
- data/app/views/rails_error_dashboard/errors/_error_cascades.html.erb +25 -21
- data/app/views/rails_error_dashboard/errors/_error_info.html.erb +25 -22
- data/app/views/rails_error_dashboard/errors/_error_row.html.erb +24 -12
- data/app/views/rails_error_dashboard/errors/_instance_variables.html.erb +12 -11
- data/app/views/rails_error_dashboard/errors/_issue_section.html.erb +19 -13
- data/app/views/rails_error_dashboard/errors/_llm_summary.html.erb +14 -14
- data/app/views/rails_error_dashboard/errors/_local_variables.html.erb +8 -8
- data/app/views/rails_error_dashboard/errors/_modals.html.erb +60 -52
- data/app/views/rails_error_dashboard/errors/_pattern_insights.html.erb +59 -49
- data/app/views/rails_error_dashboard/errors/_request_context.html.erb +23 -20
- data/app/views/rails_error_dashboard/errors/_show_scripts.html.erb +14 -14
- data/app/views/rails_error_dashboard/errors/_sidebar_metadata.html.erb +118 -93
- data/app/views/rails_error_dashboard/errors/_similar_errors.html.erb +18 -12
- data/app/views/rails_error_dashboard/errors/_source_code.html.erb +20 -5
- data/app/views/rails_error_dashboard/errors/_stats.html.erb +5 -5
- data/app/views/rails_error_dashboard/errors/_timeline.html.erb +7 -7
- data/app/views/rails_error_dashboard/errors/_user_errors_table.html.erb +15 -12
- data/app/views/rails_error_dashboard/errors/actioncable_health_summary.html.erb +25 -25
- data/app/views/rails_error_dashboard/errors/activestorage_health_summary.html.erb +28 -28
- data/app/views/rails_error_dashboard/errors/analytics.html.erb +82 -82
- data/app/views/rails_error_dashboard/errors/cache_health_summary.html.erb +27 -27
- data/app/views/rails_error_dashboard/errors/correlation.html.erb +61 -57
- data/app/views/rails_error_dashboard/errors/database_health_summary.html.erb +64 -65
- data/app/views/rails_error_dashboard/errors/deprecations.html.erb +23 -23
- data/app/views/rails_error_dashboard/errors/diagnostic_dumps.html.erb +48 -36
- data/app/views/rails_error_dashboard/errors/index.html.erb +121 -70
- data/app/views/rails_error_dashboard/errors/job_health_summary.html.erb +30 -30
- data/app/views/rails_error_dashboard/errors/llm_health_summary.html.erb +38 -39
- data/app/views/rails_error_dashboard/errors/n_plus_one_summary.html.erb +28 -25
- data/app/views/rails_error_dashboard/errors/overview.html.erb +45 -39
- data/app/views/rails_error_dashboard/errors/platform_comparison.html.erb +45 -44
- data/app/views/rails_error_dashboard/errors/rack_attack_summary.html.erb +30 -29
- data/app/views/rails_error_dashboard/errors/releases.html.erb +46 -47
- data/app/views/rails_error_dashboard/errors/settings/_value_badge.html.erb +48 -44
- data/app/views/rails_error_dashboard/errors/settings.html.erb +122 -112
- data/app/views/rails_error_dashboard/errors/show.html.erb +44 -37
- data/app/views/rails_error_dashboard/errors/storms.html.erb +30 -24
- data/app/views/rails_error_dashboard/errors/swallowed_exceptions.html.erb +26 -26
- data/app/views/rails_error_dashboard/errors/user_impact.html.erb +31 -30
- data/config/locales/de.yml +2007 -0
- data/config/locales/en.yml +2719 -0
- data/config/locales/es.yml +2079 -0
- data/config/locales/fr.yml +2082 -0
- data/config/locales/it.yml +2060 -0
- data/config/locales/ja.yml +1802 -0
- data/config/locales/pl.yml +2108 -0
- data/config/locales/pt-BR.yml +2064 -0
- data/config/locales/ru.yml +2109 -0
- data/config/locales/uk.yml +2106 -0
- data/config/locales/zh-CN.yml +1791 -0
- data/config/routes.rb +5 -0
- data/db/migrate/20260325000001_fix_swallowed_exceptions_index_for_mysql.rb +9 -3
- data/lib/rails_error_dashboard/commands/batch_delete_errors.rb +3 -1
- data/lib/rails_error_dashboard/commands/batch_mute_errors.rb +4 -2
- data/lib/rails_error_dashboard/commands/batch_resolve_errors.rb +4 -2
- data/lib/rails_error_dashboard/commands/batch_unmute_errors.rb +4 -2
- data/lib/rails_error_dashboard/commands/create_issue.rb +5 -3
- data/lib/rails_error_dashboard/commands/link_existing_issue.rb +4 -2
- data/lib/rails_error_dashboard/commands/log_error.rb +1 -1
- data/lib/rails_error_dashboard/configuration.rb +10 -3
- data/lib/rails_error_dashboard/current.rb +56 -0
- data/lib/rails_error_dashboard/i18n_store.rb +293 -0
- data/lib/rails_error_dashboard/private_backend.rb +213 -0
- data/lib/rails_error_dashboard/services/baseline_alert_payload_builder.rb +89 -22
- data/lib/rails_error_dashboard/services/digest_builder.rb +21 -7
- data/lib/rails_error_dashboard/services/discord_payload_builder.rb +27 -15
- data/lib/rails_error_dashboard/services/error_notification_dispatcher.rb +10 -5
- data/lib/rails_error_dashboard/services/localized_time_formatter.rb +117 -0
- data/lib/rails_error_dashboard/services/notification_helpers.rb +87 -4
- data/lib/rails_error_dashboard/services/pagerduty_payload_builder.rb +22 -5
- data/lib/rails_error_dashboard/services/slack_payload_builder.rb +48 -28
- data/lib/rails_error_dashboard/services/storm_protection/gate.rb +2 -1
- data/lib/rails_error_dashboard/services/webhook_payload_builder.rb +15 -1
- data/lib/rails_error_dashboard/translation.rb +39 -0
- data/lib/rails_error_dashboard/version.rb +1 -1
- data/lib/rails_error_dashboard.rb +4 -0
- data/lib/tasks/error_dashboard.rake +5 -1
- metadata +24 -4
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<% content_for :page_title, "
|
|
1
|
+
<% content_for :page_title, red_t("red.errors.show.page_title", id: @error.id) %>
|
|
2
2
|
|
|
3
3
|
<%= render "show_scripts", error: @error %>
|
|
4
4
|
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
<!-- Back + breadcrumb -->
|
|
7
7
|
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: var(--space-4); font-size: 13px; color: var(--text-tertiary);">
|
|
8
8
|
<%= link_to errors_path(app_context), style: "background: none; border: none; color: var(--text-tertiary); display: flex; align-items: center; gap: 4px; text-decoration: none; font-size: 13px;" do %>
|
|
9
|
-
<i class="bi bi-arrow-left"></i>
|
|
9
|
+
<i class="bi bi-arrow-left"></i> <%= red_t("red.errors.show.breadcrumb_errors") %>
|
|
10
10
|
<% end %>
|
|
11
11
|
<span>/</span>
|
|
12
12
|
<span style="color: var(--text-secondary);"><%= @error.error_type %></span>
|
|
@@ -18,74 +18,81 @@
|
|
|
18
18
|
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 6px;">
|
|
19
19
|
<span class="badge bg-<%= severity_color(@error.severity) %>" style="display: inline-flex; align-items: center; gap: 4px;">
|
|
20
20
|
<span style="width: 6px; height: 6px; border-radius: 50%; background: currentColor;"></span>
|
|
21
|
-
<%= @error.severity %>
|
|
21
|
+
<%= red_t("red.common.severity.#{@error.severity}", default: @error.severity.to_s) %>
|
|
22
22
|
</span>
|
|
23
23
|
<%
|
|
24
|
+
# Same treatment as _error_row: status_key stays the machine value and
|
|
25
|
+
# drives both the colour lookup and the translation key; only the
|
|
26
|
+
# label is translated. The two views read the same red.errors.row.status.*
|
|
27
|
+
# keys rather than keeping parallel copies that would drift.
|
|
24
28
|
raw_status = @error.respond_to?(:status) ? @error.status : nil
|
|
25
|
-
|
|
29
|
+
status_key = if @error.respond_to?(:muted?) && @error.muted?
|
|
26
30
|
'muted'
|
|
27
31
|
elsif @error.respond_to?(:snoozed?) && @error.snoozed?
|
|
28
32
|
'snoozed'
|
|
29
33
|
elsif raw_status.present? && raw_status != 'new'
|
|
30
|
-
raw_status
|
|
34
|
+
raw_status
|
|
31
35
|
elsif @error.resolved?
|
|
32
36
|
'resolved'
|
|
33
37
|
else
|
|
34
38
|
'unresolved'
|
|
35
39
|
end
|
|
40
|
+
status_text = red_t("red.errors.row.status.#{status_key}", default: status_key.tr('_', ' '))
|
|
36
41
|
status_colors = {
|
|
37
42
|
'unresolved' => { bg: 'var(--status-critical-bg)', color: 'var(--status-critical)' },
|
|
38
43
|
'resolved' => { bg: 'var(--status-success-bg)', color: 'var(--status-success)' },
|
|
39
|
-
'
|
|
44
|
+
'in_progress' => { bg: 'var(--status-info-bg)', color: 'var(--status-info)' },
|
|
40
45
|
'investigating' => { bg: 'var(--status-caution-bg)', color: 'var(--status-caution)' },
|
|
41
|
-
'
|
|
46
|
+
'wont_fix' => { bg: 'var(--surface-tertiary)', color: 'var(--text-tertiary)' },
|
|
42
47
|
'assigned' => { bg: 'var(--accent-subtle)', color: 'var(--accent)' },
|
|
43
48
|
'snoozed' => { bg: 'var(--status-caution-bg)', color: 'var(--status-caution)' },
|
|
44
49
|
'muted' => { bg: 'var(--surface-tertiary)', color: 'var(--text-tertiary)' },
|
|
45
50
|
}
|
|
46
|
-
sc = status_colors[
|
|
51
|
+
sc = status_colors[status_key] || status_colors['unresolved']
|
|
47
52
|
%>
|
|
48
53
|
<span style="display: inline-flex; align-items: center; gap: 4px; padding: 3px 10px; font-size: 11px; font-weight: 600; border-radius: var(--radius-full); background: <%= sc[:bg] %>; color: <%= sc[:color] %>; text-transform: capitalize;">
|
|
49
54
|
<%= status_text %>
|
|
50
55
|
</span>
|
|
51
56
|
<% if @error.reopened? %>
|
|
52
57
|
<span style="display: inline-flex; align-items: center; gap: 4px; padding: 3px 10px; font-size: 11px; font-weight: 600; border-radius: var(--radius-full); background: var(--status-warning-bg); color: var(--status-warning);">
|
|
53
|
-
<i class="bi bi-arrow-counterclockwise" style="font-size: 10px;"></i>
|
|
58
|
+
<i class="bi bi-arrow-counterclockwise" style="font-size: 10px;"></i> <%= red_t("red.errors.show.hero.reopened") %>
|
|
54
59
|
</span>
|
|
55
60
|
<% end %>
|
|
56
61
|
</div>
|
|
57
62
|
<h2 style="font-size: 20px; font-weight: 700; color: var(--text-primary); font-family: var(--font-mono); margin: 0 0 4px; word-break: break-word;"><%= @error.error_type %></h2>
|
|
58
63
|
<p style="font-size: 14px; color: var(--text-secondary); margin: 0; font-family: var(--font-mono);"><%= @error.message&.truncate(200) %></p>
|
|
59
64
|
<div style="display: flex; gap: var(--space-6); margin-top: var(--space-4); font-size: 12px; color: var(--text-tertiary); flex-wrap: wrap;">
|
|
60
|
-
<span
|
|
65
|
+
<span><%= red_tp("red.errors.show.hero.occurrences_html", count: @error.occurrence_count,
|
|
66
|
+
value: tag.strong(@error.occurrence_count, style: "color: var(--text-primary);")) %></span>
|
|
61
67
|
<% if @error.application&.name.present? %>
|
|
62
68
|
<span><i class="bi bi-layers" style="font-size: 10px;"></i> <strong style="color: var(--text-primary);"><%= @error.application.name %></strong></span>
|
|
63
69
|
<% end %>
|
|
64
70
|
<% if @error.user_id %>
|
|
65
|
-
<span
|
|
71
|
+
<span><%= red_t("red.errors.show.hero.user_html",
|
|
72
|
+
value: tag.strong("##{@error.user_id}", style: "color: var(--text-primary);")) %></span>
|
|
66
73
|
<% end %>
|
|
67
|
-
<span
|
|
68
|
-
<span
|
|
74
|
+
<span><%= red_t("red.errors.show.hero.first_seen_html", time: local_time_ago(@error.first_seen_at)) %></span>
|
|
75
|
+
<span><%= red_t("red.errors.show.hero.last_seen_html", time: local_time_ago(@error.last_seen_at)) %></span>
|
|
69
76
|
</div>
|
|
70
77
|
</div>
|
|
71
78
|
<div style="display: flex; gap: 6px; flex-shrink: 0;">
|
|
72
79
|
<% unless @error.resolved? %>
|
|
73
80
|
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#resolveModal" style="font-weight: 600;">
|
|
74
|
-
|
|
81
|
+
<%= red_t("red.errors.show.actions.resolve") %>
|
|
75
82
|
</button>
|
|
76
83
|
<% end %>
|
|
77
84
|
<button type="button" class="btn" data-bs-toggle="modal" data-bs-target="#assignModal" id="hero-assign-btn">
|
|
78
|
-
|
|
85
|
+
<%= red_t("red.errors.show.actions.assign") %>
|
|
79
86
|
</button>
|
|
80
87
|
<button type="button" class="btn" data-red-action="copy-markdown" data-markdown="<%= j @error_markdown %>">
|
|
81
|
-
<i class="bi bi-clipboard" style="margin-right: 4px;"></i
|
|
88
|
+
<i class="bi bi-clipboard" style="margin-right: 4px;"></i><%= red_t("red.errors.show.actions.copy_for_llm") %>
|
|
82
89
|
</button>
|
|
83
90
|
<% if RailsErrorDashboard.configuration.llm_configured? %>
|
|
84
91
|
<button type="button" class="btn" data-red-action="open-ai-help">
|
|
85
|
-
<i class="bi bi-stars" style="margin-right: 4px;"></i
|
|
92
|
+
<i class="bi bi-stars" style="margin-right: 4px;"></i><%= red_t("red.errors.show.actions.ai_help") %>
|
|
86
93
|
</button>
|
|
87
94
|
<% end %>
|
|
88
|
-
<button type="button" class="btn" data-red-action="download-json" title="
|
|
95
|
+
<button type="button" class="btn" data-red-action="download-json" title="<%= red_t("red.errors.show.actions.export_json") %>">
|
|
89
96
|
<i class="bi bi-download"></i>
|
|
90
97
|
</button>
|
|
91
98
|
</div>
|
|
@@ -98,17 +105,17 @@
|
|
|
98
105
|
<!-- Tab bar -->
|
|
99
106
|
<div style="display: flex; gap: 0; border-bottom: 1px solid var(--border-primary); margin-bottom: var(--space-4);">
|
|
100
107
|
<button type="button" data-red-action="switch-tab" data-red-tab="details" class="red-tab active" id="tab-details" style="padding: 10px 16px; font-size: 13px; font-weight: 600; color: var(--accent); background: none; border: none; border-bottom: 2px solid var(--accent); margin-bottom: -1px; cursor: pointer;">
|
|
101
|
-
<i class="bi bi-code-slash" style="margin-right: 6px;"></i
|
|
108
|
+
<i class="bi bi-code-slash" style="margin-right: 6px;"></i><%= red_t("red.errors.show.tabs.details") %>
|
|
102
109
|
</button>
|
|
103
110
|
<button type="button" data-red-action="switch-tab" data-red-tab="context" class="red-tab" id="tab-context" style="padding: 10px 16px; font-size: 13px; font-weight: 400; color: var(--text-secondary); background: none; border: none; border-bottom: 2px solid transparent; margin-bottom: -1px; cursor: pointer;">
|
|
104
|
-
<i class="bi bi-layers" style="margin-right: 6px;"></i
|
|
111
|
+
<i class="bi bi-layers" style="margin-right: 6px;"></i><%= red_t("red.errors.show.tabs.context") %>
|
|
105
112
|
</button>
|
|
106
113
|
<button type="button" data-red-action="switch-tab" data-red-tab="history" class="red-tab" id="tab-history" style="padding: 10px 16px; font-size: 13px; font-weight: 400; color: var(--text-secondary); background: none; border: none; border-bottom: 2px solid transparent; margin-bottom: -1px; cursor: pointer;">
|
|
107
|
-
<i class="bi bi-clock-history" style="margin-right: 6px;"></i
|
|
114
|
+
<i class="bi bi-clock-history" style="margin-right: 6px;"></i><%= red_t("red.errors.show.tabs.history") %>
|
|
108
115
|
</button>
|
|
109
116
|
<% if RailsErrorDashboard.configuration.enable_issue_tracking %>
|
|
110
117
|
<button type="button" data-red-action="switch-tab" data-red-tab="issues" class="red-tab" id="tab-issues" style="padding: 10px 16px; font-size: 13px; font-weight: 400; color: var(--text-secondary); background: none; border: none; border-bottom: 2px solid transparent; margin-bottom: -1px; cursor: pointer;">
|
|
111
|
-
<i class="bi bi-github" style="margin-right: 6px;"></i
|
|
118
|
+
<i class="bi bi-github" style="margin-right: 6px;"></i><%= red_t("red.errors.show.tabs.issues") %>
|
|
112
119
|
</button>
|
|
113
120
|
<% end %>
|
|
114
121
|
</div>
|
|
@@ -119,16 +126,16 @@
|
|
|
119
126
|
<div class="alert alert-info mb-3" style="display: flex; justify-content: space-between; align-items: center; padding: var(--space-3) var(--space-4);">
|
|
120
127
|
<div style="font-size: 12px;">
|
|
121
128
|
<% if RailsErrorDashboard::Services::CoverageTracker.active? %>
|
|
122
|
-
<i class="bi bi-broadcast"></i> <strong
|
|
129
|
+
<i class="bi bi-broadcast"></i> <strong><%= red_t("red.errors.show.coverage.active_label") %></strong> — <%= red_t("red.errors.show.coverage.active_body") %>
|
|
123
130
|
<% else %>
|
|
124
|
-
<i class="bi bi-code-square"></i> <strong
|
|
131
|
+
<i class="bi bi-code-square"></i> <strong><%= red_t("red.errors.show.coverage.inactive_label") %></strong> — <%= red_t("red.errors.show.coverage.inactive_body") %>
|
|
125
132
|
<% end %>
|
|
126
133
|
</div>
|
|
127
134
|
<div>
|
|
128
135
|
<% if RailsErrorDashboard::Services::CoverageTracker.active? %>
|
|
129
|
-
<%= button_to "
|
|
136
|
+
<%= button_to red_t("red.errors.show.coverage.disable"), disable_coverage_errors_path, method: :post, class: "btn btn-sm" %>
|
|
130
137
|
<% else %>
|
|
131
|
-
<%= button_to "
|
|
138
|
+
<%= button_to red_t("red.errors.show.coverage.enable"), enable_coverage_errors_path, method: :post, class: "btn btn-sm btn-primary" %>
|
|
132
139
|
<% end %>
|
|
133
140
|
</div>
|
|
134
141
|
</div>
|
|
@@ -178,24 +185,24 @@
|
|
|
178
185
|
<% if baselines[:hourly].present? || baselines[:daily].present? || baselines[:weekly].present? %>
|
|
179
186
|
<div class="card">
|
|
180
187
|
<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);">
|
|
181
|
-
<span style="font-size: 13px; font-weight: 600;"
|
|
188
|
+
<span style="font-size: 13px; font-weight: 600;"><%= red_t("red.errors.show.baselines.title") %></span>
|
|
182
189
|
</div>
|
|
183
190
|
<div style="padding: var(--space-3) var(--space-5);">
|
|
184
191
|
<% anomaly = @error.baseline_anomaly %>
|
|
185
192
|
<% if anomaly[:anomaly] %>
|
|
186
193
|
<div class="alert alert-<%= anomaly[:level] == :critical ? 'danger' : anomaly[:level] == :high ? 'warning' : 'info' %>" style="padding: var(--space-2) var(--space-3); font-size: 12px; margin-bottom: var(--space-3);">
|
|
187
|
-
<strong
|
|
194
|
+
<strong><%= red_t("red.errors.show.baselines.anomaly_label") %></strong> <%= red_t("red.errors.show.baselines.anomaly_body", sigma: anomaly[:std_devs_above]&.round(1), level: red_t("red.errors.show.baselines.levels.#{anomaly[:level]}", default: anomaly[:level].to_s.upcase)) %>
|
|
188
195
|
</div>
|
|
189
196
|
<% end %>
|
|
190
197
|
<% [:hourly, :daily, :weekly].each do |type| %>
|
|
191
198
|
<% baseline = baselines[type] %>
|
|
192
199
|
<% next unless baseline %>
|
|
193
200
|
<div style="padding: var(--space-2) 0; border-bottom: 1px solid var(--border-primary); font-size: 12px;">
|
|
194
|
-
<div style="color: var(--text-tertiary); font-weight: 500; text-transform: capitalize; margin-bottom: 4px;"><%= type %></div>
|
|
201
|
+
<div style="color: var(--text-tertiary); font-weight: 500; text-transform: capitalize; margin-bottom: 4px;"><%= red_t("red.errors.show.baselines.#{type}", default: type.to_s) %></div>
|
|
195
202
|
<div style="display: flex; gap: var(--space-4);">
|
|
196
|
-
<span
|
|
197
|
-
<span
|
|
198
|
-
<span
|
|
203
|
+
<span><%= red_t("red.errors.show.baselines.mean") %> <strong><%= baseline.mean&.round(1) || red_t("red.common.not_available") %></strong></span>
|
|
204
|
+
<span><%= red_t("red.errors.show.baselines.std_dev") %> <strong><%= baseline.std_dev&.round(1) || red_t("red.common.not_available") %></strong></span>
|
|
205
|
+
<span><%= red_t("red.errors.show.baselines.percentile_95") %> <strong><%= baseline.percentile_95&.round(1) || red_t("red.common.not_available") %></strong></span>
|
|
199
206
|
</div>
|
|
200
207
|
</div>
|
|
201
208
|
<% end %>
|
|
@@ -207,24 +214,24 @@
|
|
|
207
214
|
<!-- Quick Actions -->
|
|
208
215
|
<div class="card">
|
|
209
216
|
<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);">
|
|
210
|
-
<span style="font-size: 13px; font-weight: 600;"
|
|
217
|
+
<span style="font-size: 13px; font-weight: 600;"><%= red_t("red.errors.show.quick_actions.title") %></span>
|
|
211
218
|
</div>
|
|
212
219
|
<div style="padding: var(--space-3) var(--space-5); display: flex; flex-direction: column; gap: var(--space-2);">
|
|
213
220
|
<%= link_to errors_path(app_context.merge(error_type: @error.error_type, unresolved: '0')), class: "btn btn-sm" do %>
|
|
214
|
-
<i class="bi bi-filter"></i>
|
|
221
|
+
<i class="bi bi-filter"></i> <%= red_t("red.errors.show.quick_actions.view_similar") %>
|
|
215
222
|
<% end %>
|
|
216
223
|
<% if @error.user_id %>
|
|
217
224
|
<%= link_to errors_path(app_context.merge(user_id: @error.user_id, unresolved: '0')), class: "btn btn-sm" do %>
|
|
218
|
-
<i class="bi bi-person"></i>
|
|
225
|
+
<i class="bi bi-person"></i> <%= red_t("red.errors.show.quick_actions.view_user_errors") %>
|
|
219
226
|
<% end %>
|
|
220
227
|
<% end %>
|
|
221
228
|
<% if @error.platform.present? %>
|
|
222
229
|
<%= link_to errors_path(app_context.merge(platform: @error.platform, unresolved: '0')), class: "btn btn-sm" do %>
|
|
223
|
-
<i class="bi bi-phone"></i>
|
|
230
|
+
<i class="bi bi-phone"></i> <%= red_t("red.errors.show.quick_actions.view_platform_errors", platform: @error.platform) %>
|
|
224
231
|
<% end %>
|
|
225
232
|
<% end %>
|
|
226
233
|
<%= link_to analytics_errors_path(app_context), class: "btn btn-sm" do %>
|
|
227
|
-
<i class="bi bi-bar-chart-line"></i>
|
|
234
|
+
<i class="bi bi-bar-chart-line"></i> <%= red_t("red.errors.show.quick_actions.analytics") %>
|
|
228
235
|
<% end %>
|
|
229
236
|
</div>
|
|
230
237
|
</div>
|
|
@@ -1,35 +1,35 @@
|
|
|
1
|
-
<% content_for :page_title, "
|
|
1
|
+
<% content_for :page_title, red_t("red.errors.storms_page.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-lightning-rain me-2"></i>
|
|
7
|
-
|
|
7
|
+
<%= red_t("red.errors.storms_page.title") %>
|
|
8
8
|
</h1>
|
|
9
9
|
</div>
|
|
10
10
|
|
|
11
11
|
<p class="text-muted" style="font-size: 13px; margin-bottom: var(--space-4);">
|
|
12
|
-
|
|
13
|
-
limits the gem's own database writes so it never amplifies the incident.
|
|
14
|
-
Occurrences are always counted exactly — only per-event detail is sampled.
|
|
15
|
-
Thresholds are configurable per process via <code>storm_*</code> options.
|
|
12
|
+
<%= red_t("red.errors.storms_page.intro_html", options: content_tag(:code, "storm_*")) %>
|
|
16
13
|
</p>
|
|
17
14
|
|
|
18
15
|
<% if @active_storm %>
|
|
19
16
|
<div class="alert alert-danger" style="border-left: 4px solid var(--status-critical);">
|
|
20
17
|
<i class="bi bi-cloud-lightning-rain-fill me-1"></i>
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
<%# One sentence rather than a <strong> label with fragments trailing
|
|
19
|
+
after it: the clause order does not hold in every language. %>
|
|
20
|
+
<%= red_t("red.errors.storms_page.active_html",
|
|
21
|
+
label: content_tag(:strong, red_t("red.errors.storms_page.active_label")),
|
|
22
|
+
time: @active_storm.started_at.strftime(red_time_format(:datetime)),
|
|
23
|
+
rate: red_t("red.errors.storms_page.rate_per_minute", value: number_with_delimiter(@active_storm.peak_rate_per_minute))) %>
|
|
23
24
|
</div>
|
|
24
25
|
<% end %>
|
|
25
26
|
|
|
26
27
|
<% if @storm_events.empty? %>
|
|
27
28
|
<div class="red-empty-state">
|
|
28
29
|
<i class="bi bi-cloud-sun display-1 text-muted mb-3"></i>
|
|
29
|
-
<div class="red-empty-state-title"
|
|
30
|
+
<div class="red-empty-state-title"><%= red_t("red.errors.storms_page.empty_title") %></div>
|
|
30
31
|
<p class="text-muted">
|
|
31
|
-
|
|
32
|
-
episodes will appear here with exact counts of what was shed.
|
|
32
|
+
<%= red_t("red.errors.storms_page.empty_body") %>
|
|
33
33
|
</p>
|
|
34
34
|
</div>
|
|
35
35
|
<% else %>
|
|
@@ -38,37 +38,43 @@
|
|
|
38
38
|
<table class="table" style="margin: 0;">
|
|
39
39
|
<thead>
|
|
40
40
|
<tr>
|
|
41
|
-
<th
|
|
42
|
-
<th
|
|
43
|
-
<th
|
|
44
|
-
<th
|
|
45
|
-
<th class="text-end"
|
|
46
|
-
<th class="text-end"
|
|
47
|
-
<th
|
|
41
|
+
<th><%= red_t("red.errors.storms_page.column_started") %></th>
|
|
42
|
+
<th><%= red_t("red.errors.storms_page.column_duration") %></th>
|
|
43
|
+
<th><%= red_t("red.errors.storms_page.column_peak_rate") %></th>
|
|
44
|
+
<th><%= red_t("red.errors.storms_page.column_mode") %></th>
|
|
45
|
+
<th class="text-end"><%= red_t("red.errors.storms_page.column_counted") %></th>
|
|
46
|
+
<th class="text-end"><%= red_t("red.errors.storms_page.column_overflow") %></th>
|
|
47
|
+
<th><%= red_t("red.errors.storms_page.column_top_errors") %></th>
|
|
48
48
|
</tr>
|
|
49
49
|
</thead>
|
|
50
50
|
<tbody>
|
|
51
51
|
<% @storm_events.each do |event| %>
|
|
52
52
|
<tr>
|
|
53
53
|
<td style="white-space: nowrap;">
|
|
54
|
-
<%= event.started_at.strftime(
|
|
54
|
+
<%= event.started_at.strftime(red_time_format(:datetime)) %>
|
|
55
55
|
<% if event.active? %>
|
|
56
|
-
<span class="badge bg-danger ms-1"
|
|
56
|
+
<span class="badge bg-danger ms-1"><%= red_t("red.errors.storms_page.active_badge") %></span>
|
|
57
57
|
<% end %>
|
|
58
58
|
</td>
|
|
59
59
|
<td>
|
|
60
60
|
<% if event.duration_seconds %>
|
|
61
|
+
<%# Duration#inspect emits English ("1 hour and 3 minutes").
|
|
62
|
+
Rails' distance_of_time_in_words would translate, but
|
|
63
|
+
through the HOST's I18n, which RED must not read — the
|
|
64
|
+
same limitation local_time_ago documents in
|
|
65
|
+
application_helper. Left English until RED owns a
|
|
66
|
+
duration formatter of its own. %>
|
|
61
67
|
<%= ActiveSupport::Duration.build(event.duration_seconds).inspect %>
|
|
62
68
|
<% else %>
|
|
63
69
|
—
|
|
64
70
|
<% end %>
|
|
65
71
|
</td>
|
|
66
|
-
<td><%= number_with_delimiter(event.peak_rate_per_minute)
|
|
72
|
+
<td><%= red_t("red.errors.storms_page.rate_per_minute", value: number_with_delimiter(event.peak_rate_per_minute)) %></td>
|
|
67
73
|
<td>
|
|
68
74
|
<% if event.reached_open %>
|
|
69
|
-
<span class="badge bg-danger"
|
|
75
|
+
<span class="badge bg-danger"><%= red_t("red.errors.storms_page.mode_count_only") %></span>
|
|
70
76
|
<% else %>
|
|
71
|
-
<span class="badge bg-warning text-dark"
|
|
77
|
+
<span class="badge bg-warning text-dark"><%= red_t("red.errors.storms_page.mode_shedding") %></span>
|
|
72
78
|
<% end %>
|
|
73
79
|
</td>
|
|
74
80
|
<td class="text-end"><%= number_with_delimiter(event.events_counted_only) %></td>
|
|
@@ -77,7 +83,7 @@
|
|
|
77
83
|
<% event.top_fingerprints_list.first(3).each do |fp| %>
|
|
78
84
|
<div style="font-size: 12px; font-family: var(--font-mono); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;" title="<%= fp["message"] %>">
|
|
79
85
|
<strong><%= fp["class"] %></strong>
|
|
80
|
-
<span class="text-muted"
|
|
86
|
+
<span class="text-muted"><%= red_t("red.errors.storms_page.occurrence_multiplier", count: number_with_delimiter(fp["count"])) %></span>
|
|
81
87
|
</div>
|
|
82
88
|
<% end %>
|
|
83
89
|
</td>
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
<% content_for :page_title, "
|
|
1
|
+
<% content_for :page_title, red_t("red.errors.swallowed_page.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-eye-slash me-2"></i>
|
|
7
|
-
|
|
7
|
+
<%= red_t("red.errors.swallowed_page.title") %>
|
|
8
8
|
</h1>
|
|
9
9
|
|
|
10
10
|
<div class="btn-group" role="group">
|
|
11
11
|
<%= link_to swallowed_exceptions_errors_path(days: 7), class: "btn btn-sm #{@days == 7 ? 'btn-primary' : 'btn-outline-primary'}" do %>
|
|
12
|
-
7
|
|
12
|
+
<%= red_t("red.common.days_filter.7") %>
|
|
13
13
|
<% end %>
|
|
14
14
|
<%= link_to swallowed_exceptions_errors_path(days: 30), class: "btn btn-sm #{@days == 30 ? 'btn-primary' : 'btn-outline-primary'}" do %>
|
|
15
|
-
30
|
|
15
|
+
<%= red_t("red.common.days_filter.30") %>
|
|
16
16
|
<% end %>
|
|
17
17
|
<%= link_to swallowed_exceptions_errors_path(days: 90), class: "btn btn-sm #{@days == 90 ? 'btn-primary' : 'btn-outline-primary'}" do %>
|
|
18
|
-
90
|
|
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_count == 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"
|
|
26
|
+
<div class="red-empty-state-title"><%= red_t("red.errors.swallowed_page.empty_title") %></div>
|
|
27
27
|
<p class="text-muted">
|
|
28
|
-
|
|
28
|
+
<%= red_t("red.errors.swallowed_page.empty_body", threshold: (RailsErrorDashboard.configuration.swallowed_exception_threshold * 100).round, 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
|
|
32
|
+
<h6><%= red_t("red.errors.swallowed_page.how_title") %></h6>
|
|
33
33
|
<ul class="mb-0">
|
|
34
|
-
<li
|
|
35
|
-
<li
|
|
36
|
-
<li
|
|
37
|
-
<li
|
|
34
|
+
<li><%= red_t("red.errors.swallowed_page.how_raise") %></li>
|
|
35
|
+
<li><%= red_t("red.errors.swallowed_page.how_rescue") %></li>
|
|
36
|
+
<li><%= red_t("red.errors.swallowed_page.how_ratio") %></li>
|
|
37
|
+
<li><%= red_t("red.errors.swallowed_page.how_requires_html", option: content_tag(:code, "detect_swallowed_exceptions = true")) %></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-danger"><%= @unique_count %></div>
|
|
48
|
-
<small class="text-muted"
|
|
48
|
+
<small class="text-muted"><%= red_t("red.errors.swallowed_page.swallowed_patterns") %></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-warning"><%= number_with_delimiter(@total_rescue_count) %></div>
|
|
56
|
-
<small class="text-muted"
|
|
56
|
+
<small class="text-muted"><%= red_t("red.errors.swallowed_page.total_rescues") %></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-info"><%= number_with_delimiter(@total_raise_count) %></div>
|
|
64
|
-
<small class="text-muted"
|
|
64
|
+
<small class="text-muted"><%= red_t("red.errors.swallowed_page.total_raises") %></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-eye-slash text-danger me-2"></i>
|
|
74
|
-
|
|
74
|
+
<%= red_t("red.errors.swallowed_page.all_title") %>
|
|
75
75
|
<span class="badge bg-danger"><%= @unique_count %></span>
|
|
76
76
|
</h5>
|
|
77
77
|
<small class="text-muted"><%== @pagy.info_tag %></small>
|
|
@@ -81,25 +81,25 @@
|
|
|
81
81
|
<table class="table table-hover mb-0">
|
|
82
82
|
<thead>
|
|
83
83
|
<tr style="border-bottom: 1px solid var(--border-primary);">
|
|
84
|
-
<th style="padding: var(--space-3) var(--space-4); color: var(--text-secondary); font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em;"
|
|
85
|
-
<th style="padding: var(--space-3) var(--space-4); color: var(--text-secondary); font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em;" width="250"
|
|
86
|
-
<th style="padding: var(--space-3) var(--space-4); color: var(--text-secondary); font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em;" width="250"
|
|
87
|
-
<th style="padding: var(--space-3) var(--space-4); color: var(--text-secondary); font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em;" width="100"
|
|
88
|
-
<th style="padding: var(--space-3) var(--space-4); color: var(--text-secondary); font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em;" width="100"
|
|
89
|
-
<th style="padding: var(--space-3) var(--space-4); color: var(--text-secondary); font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em;" width="100"
|
|
90
|
-
<th style="padding: var(--space-3) var(--space-4); color: var(--text-secondary); font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em;" width="140"
|
|
84
|
+
<th style="padding: var(--space-3) var(--space-4); color: var(--text-secondary); font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em;"><%= red_t("red.errors.swallowed_page.column_exception_class") %></th>
|
|
85
|
+
<th style="padding: var(--space-3) var(--space-4); color: var(--text-secondary); font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em;" width="250"><%= red_t("red.errors.swallowed_page.column_raise_location") %></th>
|
|
86
|
+
<th style="padding: var(--space-3) var(--space-4); color: var(--text-secondary); font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em;" width="250"><%= red_t("red.errors.swallowed_page.column_rescue_location") %></th>
|
|
87
|
+
<th style="padding: var(--space-3) var(--space-4); color: var(--text-secondary); font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em;" width="100"><%= red_t("red.errors.swallowed_page.column_raises") %></th>
|
|
88
|
+
<th style="padding: var(--space-3) var(--space-4); color: var(--text-secondary); font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em;" width="100"><%= red_t("red.errors.swallowed_page.column_rescues") %></th>
|
|
89
|
+
<th style="padding: var(--space-3) var(--space-4); color: var(--text-secondary); font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em;" width="100"><%= red_t("red.errors.swallowed_page.column_ratio") %></th>
|
|
90
|
+
<th style="padding: var(--space-3) var(--space-4); color: var(--text-secondary); font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em;" width="140"><%= red_t("red.errors.pages.column_last_seen") %></th>
|
|
91
91
|
</tr>
|
|
92
92
|
</thead>
|
|
93
93
|
<tbody>
|
|
94
94
|
<% @entries.each do |entry| %>
|
|
95
95
|
<tr>
|
|
96
96
|
<td>
|
|
97
|
-
<%= link_to errors_path(error_type: entry[:exception_class], unresolved: '0'), class: "text-decoration-none", title: "
|
|
97
|
+
<%= link_to errors_path(error_type: entry[:exception_class], unresolved: '0'), class: "text-decoration-none", title: red_t("red.errors.swallowed_page.view_unrescued", exception: entry[:exception_class]) do %>
|
|
98
98
|
<code style="font-size: 0.85em;"><%= entry[:exception_class] %></code>
|
|
99
99
|
<% end %>
|
|
100
100
|
</td>
|
|
101
101
|
<td style="color: var(--text-secondary); font-size: 12px; word-break: break-all;"><%= entry[:raise_location] %></td>
|
|
102
|
-
<td style="color: var(--text-secondary); font-size: 12px; word-break: break-all;"><%= entry[:rescue_location] || "
|
|
102
|
+
<td style="color: var(--text-secondary); font-size: 12px; word-break: break-all;"><%= entry[:rescue_location] || red_t("red.common.unknown") %></td>
|
|
103
103
|
<td><span class="badge bg-info text-dark"><%= number_with_delimiter(entry[:raise_count]) %></span></td>
|
|
104
104
|
<td><span class="badge bg-warning text-dark"><%= number_with_delimiter(entry[:rescue_count]) %></span></td>
|
|
105
105
|
<td>
|
|
@@ -118,7 +118,7 @@
|
|
|
118
118
|
<div class="card-footer border-top d-flex justify-content-between align-items-center">
|
|
119
119
|
<div>
|
|
120
120
|
<small class="text-muted">
|
|
121
|
-
<i class="bi bi-lightbulb text-warning"></i>
|
|
121
|
+
<i class="bi bi-lightbulb text-warning"></i> <%= red_t("red.errors.swallowed_page.footer_tip") %>
|
|
122
122
|
</small>
|
|
123
123
|
</div>
|
|
124
124
|
<div>
|