rails_error_dashboard 0.10.0 → 0.11.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 +16 -3
- data/app/controllers/rails_error_dashboard/errors_controller.rb +3 -0
- data/app/mailers/rails_error_dashboard/error_notification_mailer.rb +8 -1
- data/app/models/rails_error_dashboard/error_log.rb +1 -0
- data/app/views/layouts/rails_error_dashboard.html.erb +30 -0
- data/app/views/rails_error_dashboard/error_notification_mailer/error_alert.html.erb +5 -0
- data/app/views/rails_error_dashboard/error_notification_mailer/error_alert.text.erb +1 -0
- data/app/views/rails_error_dashboard/errors/_error_row.html.erb +8 -0
- data/app/views/rails_error_dashboard/errors/_sidebar_metadata.html.erb +9 -0
- data/app/views/rails_error_dashboard/errors/analytics.html.erb +51 -1
- data/app/views/rails_error_dashboard/errors/database_health_summary.html.erb +1 -1
- data/app/views/rails_error_dashboard/errors/index.html.erb +8 -1
- data/app/views/rails_error_dashboard/errors/overview.html.erb +2 -2
- data/app/views/rails_error_dashboard/errors/settings.html.erb +2 -0
- data/config/locales/de.yml +19 -2
- data/config/locales/en.yml +17 -0
- data/config/locales/es.yml +18 -2
- data/config/locales/fr.yml +19 -2
- data/config/locales/it.yml +27 -3
- data/config/locales/ja.yml +17 -3
- data/config/locales/pl.yml +19 -2
- data/config/locales/pt-BR.yml +18 -2
- data/config/locales/ru.yml +18 -2
- data/config/locales/uk.yml +18 -2
- data/config/locales/zh-CN.yml +15 -3
- data/db/migrate/20260826000001_add_environment_to_error_logs.rb +26 -0
- data/lib/generators/rails_error_dashboard/install/templates/initializer.rb +11 -0
- data/lib/rails_error_dashboard/commands/backfill_environments.rb +58 -0
- data/lib/rails_error_dashboard/commands/find_or_increment_error.rb +55 -29
- data/lib/rails_error_dashboard/commands/flush_storm_counts.rb +40 -9
- data/lib/rails_error_dashboard/commands/log_error.rb +17 -0
- data/lib/rails_error_dashboard/configuration.rb +52 -0
- data/lib/rails_error_dashboard/queries/analytics_stats.rb +9 -0
- data/lib/rails_error_dashboard/queries/errors_list.rb +8 -0
- data/lib/rails_error_dashboard/queries/filter_options.rb +9 -0
- data/lib/rails_error_dashboard/services/discord_payload_builder.rb +8 -0
- data/lib/rails_error_dashboard/services/error_broadcaster.rb +6 -2
- data/lib/rails_error_dashboard/services/issue_body_formatter.rb +1 -0
- data/lib/rails_error_dashboard/services/markdown_error_formatter.rb +1 -0
- data/lib/rails_error_dashboard/services/notification_throttler.rb +23 -0
- data/lib/rails_error_dashboard/services/pagerduty_payload_builder.rb +1 -0
- data/lib/rails_error_dashboard/services/slack_payload_builder.rb +9 -0
- data/lib/rails_error_dashboard/services/storm_protection/gate.rb +2 -0
- data/lib/rails_error_dashboard/services/webhook_payload_builder.rb +1 -0
- data/lib/rails_error_dashboard/version.rb +1 -1
- data/lib/rails_error_dashboard.rb +1 -0
- data/lib/tasks/rails_error_dashboard_tasks.rake +6 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: defed3f836bc0e812c76abfb81b95e5e3f229e5338bfa40cde4604643eb95582
|
|
4
|
+
data.tar.gz: 7657043dbf617694e5318a5b0e2829ecca78ade0c67412ede16fbecabc696c36
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 79992d38956c257a116c1cfcb153429cbf3a082892bc71664c20ba57e801ba33325ae69f43bd0ef8404a3cd08a6d5df9727a989a0f8977826786a179ad70b448
|
|
7
|
+
data.tar.gz: 3bbcc477eff8db567415c727037a58d7e27bd8e01ad602674beb05cd6891f75bb06e665273698fed1b49c20806e4f07dbea586647583f6b7da3052a744d58fae
|
data/README.md
CHANGED
|
@@ -23,7 +23,7 @@ gem 'rails_error_dashboard'
|
|
|
23
23
|
|
|
24
24
|
**[rails-error-dashboard.anjan.dev](https://rails-error-dashboard.anjan.dev)** — Username: `gandalf` · Password: `youshallnotpass`
|
|
25
25
|
|
|
26
|
-
> **Beta Software** — Functional and tested (
|
|
26
|
+
> **Beta Software** — Functional and tested (4,200+ tests passing), but the API may change before v1.0. Supports Rails 7.0-8.1 and Ruby 3.2-4.0.
|
|
27
27
|
|
|
28
28
|
### Screenshots
|
|
29
29
|
|
|
@@ -372,7 +372,7 @@ config.llm_model = "gpt-5"
|
|
|
372
372
|
<details>
|
|
373
373
|
<summary><strong>Notifications — Slack, Discord, PagerDuty, Email, Webhooks</strong></summary>
|
|
374
374
|
|
|
375
|
-
Multi-channel alerting with severity filters, per-error cooldown,
|
|
375
|
+
Multi-channel alerting with severity filters, per-error cooldown, milestone threshold alerts, and a per-environment allowlist (`config.notification_environments = %w[production]`) so a staging deploy never pages anyone.
|
|
376
376
|
|
|
377
377
|
```ruby
|
|
378
378
|
config.enable_slack_notifications = true
|
|
@@ -382,6 +382,19 @@ config.slack_webhook_url = ENV['SLACK_WEBHOOK_URL']
|
|
|
382
382
|
[Notification setup guide →](docs/guides/NOTIFICATIONS.md)
|
|
383
383
|
</details>
|
|
384
384
|
|
|
385
|
+
<details>
|
|
386
|
+
<summary><strong>Environment Awareness — Filter, Badge, Notify per Environment</strong></summary>
|
|
387
|
+
|
|
388
|
+
Every error records the environment it came from — `production`, `staging`, `uat`, `preprod`, any name your deploys use. The errors index filters by it, rows and the detail page carry a badge, the analytics page breaks errors down by environment, and every notification names it. The same error in staging and production is two rows with independent status, so resolving one never hides the other.
|
|
389
|
+
|
|
390
|
+
```ruby
|
|
391
|
+
config.environment = ENV.fetch("ERROR_DASHBOARD_ENVIRONMENT", Rails.env) # free-form, defaults to Rails.env
|
|
392
|
+
config.notification_environments = %w[production] # nil = notify everywhere
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
Errors captured before v0.11.0 show no badge until they recur (the next occurrence claims the row) or you run `rails rails_error_dashboard:backfill_environments`.
|
|
396
|
+
</details>
|
|
397
|
+
|
|
385
398
|
<details>
|
|
386
399
|
<summary><strong>Advanced Analytics</strong></summary>
|
|
387
400
|
|
|
@@ -709,7 +722,7 @@ Built with **CQRS (Command/Query Responsibility Segregation)**:
|
|
|
709
722
|
|
|
710
723
|
## Testing
|
|
711
724
|
|
|
712
|
-
|
|
725
|
+
4,200+ tests covering unit, integration, and browser-based system tests.
|
|
713
726
|
|
|
714
727
|
```bash
|
|
715
728
|
bundle exec rspec # Full suite
|
|
@@ -11,6 +11,7 @@ module RailsErrorDashboard
|
|
|
11
11
|
error_type
|
|
12
12
|
unresolved
|
|
13
13
|
platform
|
|
14
|
+
environment
|
|
14
15
|
application_id
|
|
15
16
|
search
|
|
16
17
|
severity
|
|
@@ -94,6 +95,7 @@ module RailsErrorDashboard
|
|
|
94
95
|
filter_options = Queries::FilterOptions.call(application_id: @current_application_id)
|
|
95
96
|
@error_types = filter_options[:error_types]
|
|
96
97
|
@platforms = filter_options[:platforms]
|
|
98
|
+
@environments = filter_options[:environments]
|
|
97
99
|
@assignees = filter_options[:assignees]
|
|
98
100
|
end
|
|
99
101
|
|
|
@@ -242,6 +244,7 @@ module RailsErrorDashboard
|
|
|
242
244
|
@errors_over_time = analytics[:errors_over_time]
|
|
243
245
|
@errors_by_type = analytics[:errors_by_type]
|
|
244
246
|
@errors_by_platform = analytics[:errors_by_platform]
|
|
247
|
+
@errors_by_environment = analytics[:errors_by_environment]
|
|
245
248
|
@errors_by_hour = analytics[:errors_by_hour]
|
|
246
249
|
@top_users = analytics[:top_users]
|
|
247
250
|
@resolution_rate = analytics[:resolution_rate]
|
|
@@ -20,12 +20,19 @@ module RailsErrorDashboard
|
|
|
20
20
|
# The subject is assembled from a key rather than interpolated inline, so a
|
|
21
21
|
# locale can reorder or drop the emoji. error_type and message are verbatim
|
|
22
22
|
# error content and are never translated.
|
|
23
|
+
#
|
|
24
|
+
# An error that carries an environment uses subject_with_environment; a
|
|
25
|
+
# legacy row keeps the pre-0.11 subject so existing mail filters still match.
|
|
23
26
|
def subject_for(error_log, locale)
|
|
27
|
+
environment = error_log.respond_to?(:environment) ? error_log.environment : nil
|
|
28
|
+
key = environment.present? ? "red.mailers.error_alert.subject_with_environment" : "red.mailers.error_alert.subject"
|
|
29
|
+
|
|
24
30
|
I18nStore.translate(
|
|
25
|
-
|
|
31
|
+
key,
|
|
26
32
|
locale: locale,
|
|
27
33
|
application: error_log.application&.name ||
|
|
28
34
|
I18nStore.translate("red.mailers.shared.unknown_application", locale: locale),
|
|
35
|
+
environment: environment,
|
|
29
36
|
error_type: error_log.error_type,
|
|
30
37
|
message: truncate_subject(error_log.message)
|
|
31
38
|
)
|
|
@@ -61,6 +61,7 @@ module RailsErrorDashboard
|
|
|
61
61
|
scope :by_error_type, ->(type) { where(error_type: type) }
|
|
62
62
|
scope :by_type, ->(type) { where(error_type: type) }
|
|
63
63
|
scope :by_platform, ->(platform) { where(platform: platform) }
|
|
64
|
+
scope :by_environment, ->(environment) { where(environment: environment) }
|
|
64
65
|
scope :last_24_hours, -> { where("occurred_at >= ?", 24.hours.ago) }
|
|
65
66
|
scope :last_week, -> { where("occurred_at >= ?", 1.week.ago) }
|
|
66
67
|
|
|
@@ -660,7 +660,16 @@ dd { color: var(--text-primary); }
|
|
|
660
660
|
height: 48px; flex-shrink: 0;
|
|
661
661
|
background: var(--surface-primary);
|
|
662
662
|
border-bottom: 1px solid var(--border-primary);
|
|
663
|
+
/* A flex item's default min-width is auto, meaning "never shrink below my
|
|
664
|
+
content". Both navbar groups therefore kept their full intrinsic width on a
|
|
665
|
+
narrow screen and pushed the right-hand controls past the viewport — the
|
|
666
|
+
dashboard scrolled sideways at 375px in EVERY locale, English included.
|
|
667
|
+
min-width:0 lets them shrink; overflow:hidden keeps whatever still cannot
|
|
668
|
+
fit from extending the page rather than being clipped inside the bar. */
|
|
669
|
+
min-width: 0;
|
|
670
|
+
overflow: hidden;
|
|
663
671
|
}
|
|
672
|
+
.red-navbar > * { min-width: 0; }
|
|
664
673
|
.red-search-input {
|
|
665
674
|
width: 240px; padding: 5px 10px 5px 30px; font-size: 13px;
|
|
666
675
|
border: 1px solid var(--border-primary); border-radius: var(--radius-sm);
|
|
@@ -720,6 +729,21 @@ dd { color: var(--text-primary); }
|
|
|
720
729
|
.red-sidebar.collapsed { display: none; }
|
|
721
730
|
}
|
|
722
731
|
|
|
732
|
+
/* Stat grids
|
|
733
|
+
These were inline `grid-template-columns: repeat(N, 1fr)`, which cannot carry
|
|
734
|
+
a media query, so three or four cards stayed side by side at 375px and pushed
|
|
735
|
+
the page to 488px — it scrolled sideways on a phone in EVERY locale, English
|
|
736
|
+
worst of all. auto-fit + minmax lets the row reflow to two columns and then
|
|
737
|
+
one, and minmax's 0 floor stops a long word forcing a track wider than the
|
|
738
|
+
screen. */
|
|
739
|
+
.red-stat-grid {
|
|
740
|
+
display: grid;
|
|
741
|
+
grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr));
|
|
742
|
+
gap: var(--space-4);
|
|
743
|
+
margin-bottom: var(--space-6);
|
|
744
|
+
}
|
|
745
|
+
.red-stat-grid > * { min-width: 0; }
|
|
746
|
+
|
|
723
747
|
/* Error detail responsive grid */
|
|
724
748
|
.red-detail-grid { display: grid; grid-template-columns: 1fr 260px; gap: var(--space-4); }
|
|
725
749
|
@media (max-width: 1023.98px) { .red-detail-grid { grid-template-columns: 1fr; } }
|
|
@@ -729,7 +753,13 @@ dd { color: var(--text-primary); }
|
|
|
729
753
|
padding: 3px 10px; font-size: 11px; font-weight: 600;
|
|
730
754
|
border-radius: var(--radius-full);
|
|
731
755
|
text-transform: uppercase; letter-spacing: 0.05em;
|
|
756
|
+
white-space: nowrap;
|
|
732
757
|
}
|
|
758
|
+
/* Below the sidebar breakpoint the navbar has to fit a menu button, search,
|
|
759
|
+
language picker, shortcuts, theme toggle and the env badge. The badge is
|
|
760
|
+
decoration — the environment is also on the overview page — so it is the
|
|
761
|
+
first thing dropped rather than letting it push the page sideways. */
|
|
762
|
+
@media (max-width: 575.98px) { .red-env-badge { display: none; } }
|
|
733
763
|
|
|
734
764
|
/* Kbd shortcut */
|
|
735
765
|
kbd {
|
|
@@ -148,6 +148,11 @@
|
|
|
148
148
|
</div>
|
|
149
149
|
<% end %>
|
|
150
150
|
|
|
151
|
+
<% if @error_log.respond_to?(:environment) && @error_log.environment.present? %>
|
|
152
|
+
<div class="label"><%= red_t("red.mailers.error_alert.labels.environment") %>:</div>
|
|
153
|
+
<div class="value"><strong><%= @error_log.environment %></strong></div>
|
|
154
|
+
<% end %>
|
|
155
|
+
|
|
151
156
|
<div class="label"><%= red_t("red.mailers.error_alert.labels.occurred_at") %>:</div>
|
|
152
157
|
<div class="value"><%= red_mail_time(@error_log.occurred_at) %></div>
|
|
153
158
|
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
<%= red_mail_t("red.mailers.error_alert.labels.application") %>: <%= @error_log.application&.name || red_mail_t("red.mailers.shared.unknown_application") %>
|
|
11
11
|
<%= red_mail_t("red.mailers.error_alert.labels.error_type") %>: <%= @error_log.error_type %>
|
|
12
12
|
<% if @error_log.platform.present? %><%= red_mail_t("red.mailers.error_alert.labels.platform") %>: <%= @error_log.platform %><% end %>
|
|
13
|
+
<% if @error_log.respond_to?(:environment) && @error_log.environment.present? %><%= red_mail_t("red.mailers.error_alert.labels.environment") %>: <%= @error_log.environment %><% end %>
|
|
13
14
|
<%= red_mail_t("red.mailers.error_alert.labels.occurred_at") %>: <%= red_mail_time(@error_log.occurred_at) %>
|
|
14
15
|
<% if @error_log.user_id.present? %><%= red_mail_t("red.mailers.error_alert.labels.user_id") %>: <%= @error_log.user_id %><% end %>
|
|
15
16
|
<% if @error_log.ip_address.present? %><%= red_mail_t("red.mailers.error_alert.labels.ip_address") %>: <%= @error_log.ip_address %><% end %>
|
|
@@ -99,4 +99,12 @@
|
|
|
99
99
|
<% end %>
|
|
100
100
|
</td>
|
|
101
101
|
<% end %>
|
|
102
|
+
<% if local_assigns[:show_environment] %>
|
|
103
|
+
<%# Environment names are free-form data (production, staging, uat...) and are never translated. %>
|
|
104
|
+
<td style="padding: var(--space-3) var(--space-4); font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;" title="<%= error.environment %>">
|
|
105
|
+
<% if error.environment.present? %>
|
|
106
|
+
<span class="badge badge-api" data-environment-badge="<%= error.environment %>"><i class="bi bi-hdd-stack"></i> <%= error.environment %></span>
|
|
107
|
+
<% end %>
|
|
108
|
+
</td>
|
|
109
|
+
<% end %>
|
|
102
110
|
</tr>
|
|
@@ -70,6 +70,15 @@
|
|
|
70
70
|
<% end %>
|
|
71
71
|
</div>
|
|
72
72
|
|
|
73
|
+
<% if error.respond_to?(:environment) && error.environment.present? %>
|
|
74
|
+
<div class="mb-3">
|
|
75
|
+
<small class="metadata-label d-block mb-1"><%= red_t("red.errors.sidebar.environment") %></small>
|
|
76
|
+
<%= link_to errors_path(environment: error.environment, unresolved: '0'), class: "text-decoration-none", title: red_t("red.errors.sidebar.view_all_environment", environment: error.environment) do %>
|
|
77
|
+
<span class="badge badge-api" data-environment-badge="<%= error.environment %>"><i class="bi bi-hdd-stack"></i> <%= error.environment %></span>
|
|
78
|
+
<% end %>
|
|
79
|
+
</div>
|
|
80
|
+
<% end %>
|
|
81
|
+
|
|
73
82
|
<div class="mb-3">
|
|
74
83
|
<small class="metadata-label d-block mb-1"><%= red_t("red.errors.sidebar.user") %></small>
|
|
75
84
|
<% if error.respond_to?(:user) && error.user %>
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
</div>
|
|
46
46
|
|
|
47
47
|
<!-- Summary Stats -->
|
|
48
|
-
<div
|
|
48
|
+
<div class="red-stat-grid">
|
|
49
49
|
<div class="card stat-card" style="border-left: 3px solid var(--accent); padding: var(--space-5) var(--space-6);">
|
|
50
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>
|
|
@@ -155,6 +155,56 @@
|
|
|
155
155
|
</div>
|
|
156
156
|
<% end %>
|
|
157
157
|
|
|
158
|
+
<%# Errors by environment: same shape as the platform chart, shown only when
|
|
159
|
+
there is something to compare. Environment names are data, not text to
|
|
160
|
+
translate; NULL rows (captured before the column existed) are :unknown. %>
|
|
161
|
+
<% environment_counts = (@errors_by_environment || {}).transform_keys { |env| env == :unknown ? red_t("red.analytics.overview.environment_chart.unknown") : env } %>
|
|
162
|
+
<% if environment_counts.size > 1 %>
|
|
163
|
+
<div class="row g-4 mb-4">
|
|
164
|
+
<div class="col-md-12">
|
|
165
|
+
<div class="card">
|
|
166
|
+
<div class="card-header">
|
|
167
|
+
<h5 class="mb-0"><i class="bi bi-hdd-stack"></i> <%= red_t("red.analytics.overview.environment_chart.title") %></h5>
|
|
168
|
+
</div>
|
|
169
|
+
<div class="card-body">
|
|
170
|
+
<div id="errors-by-environment-chart"></div>
|
|
171
|
+
<script<%= " nonce=\"#{red_csp_nonce}\"".html_safe if red_csp_nonce %>>
|
|
172
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
173
|
+
const colors = window.getChartColors();
|
|
174
|
+
const environmentColors = ["#EF4444", "#F59E0B", "#2563EB", "#10B981", "#8B5CF6", "#6B7280"];
|
|
175
|
+
|
|
176
|
+
new Chartkick.PieChart("errors-by-environment-chart", <%= raw environment_counts.to_json %>, {
|
|
177
|
+
colors: environmentColors,
|
|
178
|
+
height: "300px",
|
|
179
|
+
legend: "bottom",
|
|
180
|
+
donut: true,
|
|
181
|
+
library: {
|
|
182
|
+
plugins: {
|
|
183
|
+
legend: {
|
|
184
|
+
labels: {
|
|
185
|
+
color: colors.textColor,
|
|
186
|
+
font: { size: 12, weight: 'bold' }
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
</script>
|
|
194
|
+
</div>
|
|
195
|
+
<div class="card-footer border-top-0">
|
|
196
|
+
<div class="d-flex gap-2 flex-wrap">
|
|
197
|
+
<small class="text-muted me-2"><%= red_t("red.analytics.overview.environment_chart.quick_links") %></small>
|
|
198
|
+
<% (@errors_by_environment || {}).keys.reject { |env| env == :unknown }.each do |environment| %>
|
|
199
|
+
<%= link_to environment, errors_path(environment: environment, unresolved: '0'), class: "btn btn-sm btn-outline-secondary" %>
|
|
200
|
+
<% end %>
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
</div>
|
|
205
|
+
</div>
|
|
206
|
+
<% end %>
|
|
207
|
+
|
|
158
208
|
<!-- Charts Row 2 -->
|
|
159
209
|
<div class="row g-4 mb-4">
|
|
160
210
|
<div class="col-md-12">
|
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
<% if @live_health[:postgresql] %>
|
|
92
92
|
<%# Summary cards for PostgreSQL %>
|
|
93
93
|
<% total_db_size = (@host_tables + @gem_tables).sum { |t| t[:total_bytes] } %>
|
|
94
|
-
<div
|
|
94
|
+
<div class="red-stat-grid" style="margin-bottom: var(--space-4);">
|
|
95
95
|
<div class="card stat-card" style="padding: var(--space-4) var(--space-5); text-align: center;">
|
|
96
96
|
<div class="stat-label" style="margin-bottom: 4px;"><%= red_t("red.health.database.summary.total_db_size") %></div>
|
|
97
97
|
<div class="stat-value"><%= number_to_human_size(total_db_size) %></div>
|
|
@@ -157,6 +157,9 @@
|
|
|
157
157
|
<% if @platforms.size > 1 %>
|
|
158
158
|
<%= select_tag :platform, options_for_select([[red_t("red.errors.index.filters.all_platforms"), '']] + @platforms.map { |p| [p, p] }, params[:platform]), class: "form-select", style: "width: auto; min-width: 120px;" %>
|
|
159
159
|
<% end %>
|
|
160
|
+
<% if @environments.size > 1 %>
|
|
161
|
+
<%= select_tag :environment, options_for_select([[red_t("red.errors.index.filters.all_environments"), '']] + @environments.map { |e| [e, e] }, params[:environment]), class: "form-select", style: "width: auto; min-width: 120px;" %>
|
|
162
|
+
<% end %>
|
|
160
163
|
<%= select_tag :error_type, options_for_select([[red_t("red.errors.index.filters.all_types"), '']] + @error_types.map { |t| [t, t] }, params[:error_type]), class: "form-select", style: "width: auto; min-width: 120px;" %>
|
|
161
164
|
<%
|
|
162
165
|
# Built from the same keys the active chips read, so a label can never
|
|
@@ -217,6 +220,7 @@
|
|
|
217
220
|
active_filters << { label: red_t("red.errors.index.filters.chips.application", value: app_name), param: :application_id } if app_name
|
|
218
221
|
end
|
|
219
222
|
active_filters << { label: red_t("red.errors.index.filters.chips.platform", value: params[:platform]), param: :platform } if params[:platform].present?
|
|
223
|
+
active_filters << { label: red_t("red.errors.index.filters.chips.environment", value: params[:environment]), param: :environment } if params[:environment].present?
|
|
220
224
|
active_filters << { label: red_t("red.errors.index.filters.chips.error_type", value: params[:error_type]), param: :error_type } if params[:error_type].present?
|
|
221
225
|
active_filters << { label: red_t("red.errors.index.filters.chips.severity", value: severity_label.call(params[:severity])), param: :severity } if params[:severity].present?
|
|
222
226
|
active_filters << { label: red_t("red.errors.index.filters.chips.timeframe", value: timeframe_label.call(params[:timeframe])), param: :timeframe } if params[:timeframe].present?
|
|
@@ -287,11 +291,14 @@
|
|
|
287
291
|
<% if @platforms.size > 1 %>
|
|
288
292
|
<th style="padding: var(--space-3) var(--space-4); width: 100px;"><%= red_t("red.errors.index.columns.platform") %></th>
|
|
289
293
|
<% end %>
|
|
294
|
+
<% if @environments.size > 1 %>
|
|
295
|
+
<th style="padding: var(--space-3) var(--space-4); width: 110px;"><%= red_t("red.errors.index.columns.environment") %></th>
|
|
296
|
+
<% end %>
|
|
290
297
|
</tr>
|
|
291
298
|
</thead>
|
|
292
299
|
<tbody id="error_list">
|
|
293
300
|
<% @errors.each do |error| %>
|
|
294
|
-
<%= render "error_row", error: error, show_platform: @platforms.size > 1, show_application: (@applications.size > 1 && params[:application_id].blank?) %>
|
|
301
|
+
<%= render "error_row", error: error, show_platform: @platforms.size > 1, show_environment: @environments.size > 1, show_application: (@applications.size > 1 && params[:application_id].blank?) %>
|
|
295
302
|
<% end %>
|
|
296
303
|
</tbody>
|
|
297
304
|
</table>
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
trend_values = @stats[:errors_trend_7d].values rescue []
|
|
33
33
|
trend_max = [ trend_values.max || 1, 1 ].max
|
|
34
34
|
%>
|
|
35
|
-
<div
|
|
35
|
+
<div class="red-stat-grid">
|
|
36
36
|
<!-- Error Rate -->
|
|
37
37
|
<div class="card stat-card" style="border-left: 3px solid var(--status-critical); padding: var(--space-5) var(--space-6);">
|
|
38
38
|
<div class="stat-label" style="margin-bottom: 4px;"><%= red_t("red.errors.overview_page.error_rate") %></div>
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
</div>
|
|
90
90
|
|
|
91
91
|
<!-- Secondary Stats (3 cards) -->
|
|
92
|
-
<div
|
|
92
|
+
<div class="red-stat-grid">
|
|
93
93
|
<!-- Affected Users -->
|
|
94
94
|
<div class="card stat-card" style="padding: var(--space-5) var(--space-6);">
|
|
95
95
|
<div class="stat-label" style="margin-bottom: 4px;"><%= red_t("red.errors.overview_page.affected_users") %></div>
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
color: "info",
|
|
20
20
|
settings: {
|
|
21
21
|
application_name: { type: :application_name },
|
|
22
|
+
environment: { type: :string },
|
|
22
23
|
database: { type: :database_name },
|
|
23
24
|
use_separate_database: { type: :boolean }
|
|
24
25
|
}
|
|
@@ -45,6 +46,7 @@
|
|
|
45
46
|
icon: "bi-bell",
|
|
46
47
|
color: "info",
|
|
47
48
|
settings: {
|
|
49
|
+
notification_environments: { type: :array },
|
|
48
50
|
enable_slack_notifications: { type: :boolean },
|
|
49
51
|
slack_webhook_url: { type: :url, show_if: :enable_slack_notifications },
|
|
50
52
|
enable_email_notifications: { type: :boolean },
|
data/config/locales/de.yml
CHANGED
|
@@ -144,6 +144,7 @@ de:
|
|
|
144
144
|
any: Beliebiger Schweregrad
|
|
145
145
|
all_apps: Alle Anwendungen
|
|
146
146
|
all_platforms: Alle Plattformen
|
|
147
|
+
all_environments: Alle Umgebungen
|
|
147
148
|
all_types: Alle Typen
|
|
148
149
|
all_timeframes: Gesamter Zeitraum
|
|
149
150
|
all_frequencies: Alle Häufigkeiten
|
|
@@ -172,6 +173,7 @@ de:
|
|
|
172
173
|
search: 'Suche: %{value}'
|
|
173
174
|
application: 'Anwendung: %{value}'
|
|
174
175
|
platform: 'Plattform: %{value}'
|
|
176
|
+
environment: 'Umgebung: %{value}'
|
|
175
177
|
error_type: 'Typ: %{value}'
|
|
176
178
|
severity: 'Schweregrad: %{value}'
|
|
177
179
|
timeframe: 'Zeitraum: %{value}'
|
|
@@ -187,6 +189,7 @@ de:
|
|
|
187
189
|
last_seen: Zuletzt gesehen
|
|
188
190
|
application: Anwendung
|
|
189
191
|
platform: Plattform
|
|
192
|
+
environment: Umgebung
|
|
190
193
|
select_all: Alle Fehler auswählen
|
|
191
194
|
batch:
|
|
192
195
|
selected:
|
|
@@ -625,6 +628,7 @@ de:
|
|
|
625
628
|
low: NIEDRIG
|
|
626
629
|
platform: Plattform
|
|
627
630
|
view_all_platform: Alle %{platform}-Fehler ansehen
|
|
631
|
+
view_all_environment: Alle Fehler aus %{environment} ansehen
|
|
628
632
|
user: Benutzer
|
|
629
633
|
view_user_errors: Alle Fehler dieses Benutzers ansehen
|
|
630
634
|
user_id: 'ID: %{id}'
|
|
@@ -841,8 +845,9 @@ de:
|
|
|
841
845
|
column_ips: IPs
|
|
842
846
|
column_top_path: Häufigster Pfad
|
|
843
847
|
column_top_agent: Häufigster Agent
|
|
844
|
-
overflow_notice_html:
|
|
845
|
-
|
|
848
|
+
overflow_notice_html: "%{count} Ereignisse konnten keiner Regel zugeordnet
|
|
849
|
+
werden, da der Puffer des Trackers voll war. Erhöhen Sie %{option}, um mehr
|
|
850
|
+
zu erfassen."
|
|
846
851
|
match_type:
|
|
847
852
|
blocklist: blocklist
|
|
848
853
|
throttle: throttle
|
|
@@ -1070,6 +1075,10 @@ de:
|
|
|
1070
1075
|
sampling_rate: Anteil der erfassten Fehler
|
|
1071
1076
|
application_name: Name dieser Anwendung (wird automatisch erkannt, wenn nicht
|
|
1072
1077
|
gesetzt)
|
|
1078
|
+
environment: Umgebung, der Fehler zugeordnet werden (Rails.env, falls nicht
|
|
1079
|
+
gesetzt)
|
|
1080
|
+
notification_environments: Nur für diese Umgebungen benachrichtigen (alle,
|
|
1081
|
+
falls nicht gesetzt)
|
|
1073
1082
|
database: Aktive Datenbank für Fehlerprotokolle
|
|
1074
1083
|
use_separate_database: Eigene Datenbank für Fehlerprotokolle verwenden
|
|
1075
1084
|
user_model: Modellname für Benutzerzuordnungen (wird automatisch erkannt,
|
|
@@ -1237,11 +1246,14 @@ de:
|
|
|
1237
1246
|
unknown_application: Unbekannt
|
|
1238
1247
|
error_alert:
|
|
1239
1248
|
subject: "\U0001F6A8 [%{application}] %{error_type}: %{message}"
|
|
1249
|
+
subject_with_environment: "\U0001F6A8 [%{application} · %{environment}] %{error_type}:
|
|
1250
|
+
%{message}"
|
|
1240
1251
|
heading: "\U0001F6A8 Fehlermeldung"
|
|
1241
1252
|
labels:
|
|
1242
1253
|
application: Anwendung
|
|
1243
1254
|
error_type: Fehlertyp
|
|
1244
1255
|
platform: Plattform
|
|
1256
|
+
environment: Umgebung
|
|
1245
1257
|
occurred_at: Aufgetreten am
|
|
1246
1258
|
user_id: Benutzer-ID
|
|
1247
1259
|
ip_address: IP-Adresse
|
|
@@ -1307,6 +1319,7 @@ de:
|
|
|
1307
1319
|
application: Anwendung
|
|
1308
1320
|
error_type: Fehlertyp
|
|
1309
1321
|
platform: Plattform
|
|
1322
|
+
environment: Umgebung
|
|
1310
1323
|
occurred: Aufgetreten
|
|
1311
1324
|
message: Meldung
|
|
1312
1325
|
user: Benutzer
|
|
@@ -1641,6 +1654,10 @@ de:
|
|
|
1641
1654
|
platform_chart:
|
|
1642
1655
|
title: Fehler nach Plattform
|
|
1643
1656
|
quick_links: 'Schnellzugriff:'
|
|
1657
|
+
environment_chart:
|
|
1658
|
+
title: Fehler nach Umgebung
|
|
1659
|
+
quick_links: 'Schnellzugriff:'
|
|
1660
|
+
unknown: Unbekannt
|
|
1644
1661
|
type_chart:
|
|
1645
1662
|
title: Top 10 Fehlertypen
|
|
1646
1663
|
hour_chart:
|
data/config/locales/en.yml
CHANGED
|
@@ -249,6 +249,7 @@ en:
|
|
|
249
249
|
# noun — languages differ on agreement between the two.
|
|
250
250
|
all_apps: "All Apps"
|
|
251
251
|
all_platforms: "All Platforms"
|
|
252
|
+
all_environments: "All Environments"
|
|
252
253
|
all_types: "All Types"
|
|
253
254
|
all_timeframes: "All Time"
|
|
254
255
|
all_frequencies: "All Frequencies"
|
|
@@ -290,6 +291,7 @@ en:
|
|
|
290
291
|
search: "Search: %{value}"
|
|
291
292
|
application: "App: %{value}"
|
|
292
293
|
platform: "Platform: %{value}"
|
|
294
|
+
environment: "Environment: %{value}"
|
|
293
295
|
error_type: "Type: %{value}"
|
|
294
296
|
severity: "Severity: %{value}"
|
|
295
297
|
timeframe: "Timeframe: %{value}"
|
|
@@ -306,6 +308,7 @@ en:
|
|
|
306
308
|
last_seen: "Last seen"
|
|
307
309
|
application: "App"
|
|
308
310
|
platform: "Platform"
|
|
311
|
+
environment: "Environment"
|
|
309
312
|
select_all: "Select all errors"
|
|
310
313
|
|
|
311
314
|
batch:
|
|
@@ -876,6 +879,8 @@ en:
|
|
|
876
879
|
platform: "Platform"
|
|
877
880
|
# %{platform} is a platform name (iOS, Android, Web) and is not translated.
|
|
878
881
|
view_all_platform: "View all %{platform} errors"
|
|
882
|
+
# %{environment} is an environment name (production, staging, uat) and is not translated.
|
|
883
|
+
view_all_environment: "View all %{environment} errors"
|
|
879
884
|
|
|
880
885
|
user: "User"
|
|
881
886
|
view_user_errors: "View all errors for this user"
|
|
@@ -1484,6 +1489,8 @@ en:
|
|
|
1484
1489
|
max_backtrace_lines: "Stack trace depth limit"
|
|
1485
1490
|
sampling_rate: "Percentage of errors captured"
|
|
1486
1491
|
application_name: "Name of this application (auto-detected if not set)"
|
|
1492
|
+
environment: "Environment errors are attributed to (Rails.env if not set)"
|
|
1493
|
+
notification_environments: "Only notify for these environments (all if not set)"
|
|
1487
1494
|
database: "Active database for error logs"
|
|
1488
1495
|
use_separate_database: "Use dedicated DB for error logs"
|
|
1489
1496
|
user_model: "Model name for user associations (auto-detected if not set)"
|
|
@@ -1702,11 +1709,15 @@ en:
|
|
|
1702
1709
|
# an exception class name and %{message} is its message — both are
|
|
1703
1710
|
# verbatim error content and are not translated.
|
|
1704
1711
|
subject: "🚨 [%{application}] %{error_type}: %{message}"
|
|
1712
|
+
# Used instead of `subject` when the error carries an environment.
|
|
1713
|
+
# %{environment} is an environment name and is not translated.
|
|
1714
|
+
subject_with_environment: "🚨 [%{application} · %{environment}] %{error_type}: %{message}"
|
|
1705
1715
|
heading: "🚨 Error Alert"
|
|
1706
1716
|
labels:
|
|
1707
1717
|
application: "Application"
|
|
1708
1718
|
error_type: "Error Type"
|
|
1709
1719
|
platform: "Platform"
|
|
1720
|
+
environment: "Environment"
|
|
1710
1721
|
occurred_at: "Occurred At"
|
|
1711
1722
|
user_id: "User ID"
|
|
1712
1723
|
ip_address: "IP Address"
|
|
@@ -1806,6 +1817,7 @@ en:
|
|
|
1806
1817
|
application: "Application"
|
|
1807
1818
|
error_type: "Error Type"
|
|
1808
1819
|
platform: "Platform"
|
|
1820
|
+
environment: "Environment"
|
|
1809
1821
|
occurred: "Occurred"
|
|
1810
1822
|
message: "Message"
|
|
1811
1823
|
user: "User"
|
|
@@ -2215,6 +2227,11 @@ en:
|
|
|
2215
2227
|
platform_chart:
|
|
2216
2228
|
title: "Errors by Platform"
|
|
2217
2229
|
quick_links: "Quick Links:"
|
|
2230
|
+
environment_chart:
|
|
2231
|
+
title: "Errors by Environment"
|
|
2232
|
+
quick_links: "Quick Links:"
|
|
2233
|
+
# Label for errors captured before the environment column existed.
|
|
2234
|
+
unknown: "Unknown"
|
|
2218
2235
|
|
|
2219
2236
|
type_chart:
|
|
2220
2237
|
title: "Top 10 Error Types"
|
data/config/locales/es.yml
CHANGED
|
@@ -147,6 +147,7 @@ es:
|
|
|
147
147
|
any: Cualquier gravedad
|
|
148
148
|
all_apps: Todas las aplicaciones
|
|
149
149
|
all_platforms: Todas las plataformas
|
|
150
|
+
all_environments: Todos los entornos
|
|
150
151
|
all_types: Todos los tipos
|
|
151
152
|
all_timeframes: Todo el periodo
|
|
152
153
|
all_frequencies: Todas las frecuencias
|
|
@@ -175,6 +176,7 @@ es:
|
|
|
175
176
|
search: 'Búsqueda: %{value}'
|
|
176
177
|
application: 'Aplicación: %{value}'
|
|
177
178
|
platform: 'Plataforma: %{value}'
|
|
179
|
+
environment: 'Entorno: %{value}'
|
|
178
180
|
error_type: 'Tipo: %{value}'
|
|
179
181
|
severity: 'Gravedad: %{value}'
|
|
180
182
|
timeframe: 'Periodo: %{value}'
|
|
@@ -190,6 +192,7 @@ es:
|
|
|
190
192
|
last_seen: Última aparición
|
|
191
193
|
application: Aplicación
|
|
192
194
|
platform: Plataforma
|
|
195
|
+
environment: Entorno
|
|
193
196
|
select_all: Seleccionar todos los errores
|
|
194
197
|
batch:
|
|
195
198
|
selected:
|
|
@@ -652,6 +655,7 @@ es:
|
|
|
652
655
|
low: BAJA
|
|
653
656
|
platform: Plataforma
|
|
654
657
|
view_all_platform: Ver todos los errores de %{platform}
|
|
658
|
+
view_all_environment: Ver todos los errores de %{environment}
|
|
655
659
|
user: Usuario
|
|
656
660
|
view_user_errors: Ver todos los errores de este usuario
|
|
657
661
|
user_id: 'ID: %{id}'
|
|
@@ -870,8 +874,9 @@ es:
|
|
|
870
874
|
column_ips: IP
|
|
871
875
|
column_top_path: Ruta principal
|
|
872
876
|
column_top_agent: Agente principal
|
|
873
|
-
overflow_notice_html:
|
|
874
|
-
|
|
877
|
+
overflow_notice_html: No se pudieron atribuir %{count} eventos a ninguna regla
|
|
878
|
+
porque el búfer del rastreador estaba lleno. Aumente %{option} para capturar
|
|
879
|
+
más.
|
|
875
880
|
match_type:
|
|
876
881
|
blocklist: blocklist
|
|
877
882
|
throttle: throttle
|
|
@@ -1112,6 +1117,9 @@ es:
|
|
|
1112
1117
|
sampling_rate: Porcentaje de errores capturados
|
|
1113
1118
|
application_name: Nombre de esta aplicación (se detecta automáticamente si
|
|
1114
1119
|
no se define)
|
|
1120
|
+
environment: Entorno al que se atribuyen los errores (Rails.env si no se define)
|
|
1121
|
+
notification_environments: Notificar solo para estos entornos (todos si no
|
|
1122
|
+
se define)
|
|
1115
1123
|
database: Base de datos activa para los registros de errores
|
|
1116
1124
|
use_separate_database: Usar una base de datos dedicada para los registros
|
|
1117
1125
|
de errores
|
|
@@ -1287,11 +1295,14 @@ es:
|
|
|
1287
1295
|
unknown_application: Desconocida
|
|
1288
1296
|
error_alert:
|
|
1289
1297
|
subject: "\U0001F6A8 [%{application}] %{error_type}: %{message}"
|
|
1298
|
+
subject_with_environment: "\U0001F6A8 [%{application} · %{environment}] %{error_type}:
|
|
1299
|
+
%{message}"
|
|
1290
1300
|
heading: "\U0001F6A8 Alerta de error"
|
|
1291
1301
|
labels:
|
|
1292
1302
|
application: Aplicación
|
|
1293
1303
|
error_type: Tipo de error
|
|
1294
1304
|
platform: Plataforma
|
|
1305
|
+
environment: Entorno
|
|
1295
1306
|
occurred_at: Ocurrió el
|
|
1296
1307
|
user_id: ID de usuario
|
|
1297
1308
|
ip_address: Dirección IP
|
|
@@ -1360,6 +1371,7 @@ es:
|
|
|
1360
1371
|
application: Aplicación
|
|
1361
1372
|
error_type: Tipo de error
|
|
1362
1373
|
platform: Plataforma
|
|
1374
|
+
environment: Entorno
|
|
1363
1375
|
occurred: Ocurrió
|
|
1364
1376
|
message: Mensaje
|
|
1365
1377
|
user: Usuario
|
|
@@ -1696,6 +1708,10 @@ es:
|
|
|
1696
1708
|
platform_chart:
|
|
1697
1709
|
title: Errores por plataforma
|
|
1698
1710
|
quick_links: 'Enlaces rápidos:'
|
|
1711
|
+
environment_chart:
|
|
1712
|
+
title: Errores por entorno
|
|
1713
|
+
quick_links: 'Enlaces rápidos:'
|
|
1714
|
+
unknown: Desconocido
|
|
1699
1715
|
type_chart:
|
|
1700
1716
|
title: Los 10 tipos de error más frecuentes
|
|
1701
1717
|
hour_chart:
|