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,168 +1,168 @@
1
- <% content_for :page_title, "Settings" %>
1
+ <% content_for :page_title, red_t("red.settings.title") %>
2
2
 
3
3
  <%
4
4
  # Define setting groups with metadata
5
5
  setting_groups = {
6
- "Core Features" => {
6
+ :core_features => {
7
7
  icon: "bi-shield-check",
8
8
  color: "primary",
9
9
  settings: {
10
- enable_middleware: { label: "Error Middleware", description: "Catches unhandled exceptions", type: :boolean },
11
- enable_error_subscriber: { label: "Rails.error Subscriber", description: "Subscribes to Rails error events", type: :boolean },
12
- retention_days: { label: "Data Retention", description: "Manual cleanup only (run: rails error_dashboard:cleanup_resolved DAYS=90)", type: :retention_days },
13
- max_backtrace_lines: { label: "Max Backtrace Lines", description: "Stack trace depth limit", type: :integer, unit: "lines" },
14
- sampling_rate: { label: "Sampling Rate", description: "Percentage of errors captured", type: :percentage }
10
+ enable_middleware: { type: :boolean },
11
+ enable_error_subscriber: { type: :boolean },
12
+ retention_days: { type: :retention_days },
13
+ max_backtrace_lines: { type: :integer, unit: :lines },
14
+ sampling_rate: { type: :percentage }
15
15
  }
16
16
  },
17
- "Multi-App Support" => {
17
+ :multi_app => {
18
18
  icon: "bi-collection",
19
19
  color: "info",
20
20
  settings: {
21
- application_name: { label: "Application Name", description: "Name of this application (auto-detected if not set)", type: :application_name },
22
- database: { label: "Database Connection", description: "Active database for error logs", type: :database_name },
23
- use_separate_database: { label: "Separate Database", description: "Use dedicated DB for error logs", type: :boolean }
21
+ application_name: { type: :application_name },
22
+ database: { type: :database_name },
23
+ use_separate_database: { type: :boolean }
24
24
  }
25
25
  },
26
- "User Integration" => {
26
+ :user_integration => {
27
27
  icon: "bi-people",
28
28
  color: "secondary",
29
29
  settings: {
30
- user_model: { label: "User Model", description: "Model name for user associations (auto-detected if not set)", type: :user_model },
31
- total_users_for_impact: { label: "Total Users", description: "For impact % calculation (auto-detected if not set)", type: :total_users }
30
+ user_model: { type: :user_model },
31
+ total_users_for_impact: { type: :total_users }
32
32
  }
33
33
  },
34
- "Performance Settings" => {
34
+ :performance => {
35
35
  icon: "bi-lightning-charge",
36
36
  color: "dark",
37
37
  settings: {
38
- async_logging: { label: "Async Logging", description: "Background job processing", type: :boolean, extra: :async_adapter },
39
- async_adapter: { label: "Async Adapter", description: "Job queue adapter", type: :symbol, show_if: :async_logging },
40
- enable_rate_limiting: { label: "Rate Limiting", description: "Throttle API requests", type: :boolean },
41
- rate_limit_per_minute: { label: "Rate Limit Threshold", description: "Requests per minute per IP", type: :integer, show_if: :enable_rate_limiting }
38
+ async_logging: { type: :boolean, extra: :async_adapter },
39
+ async_adapter: { type: :symbol, show_if: :async_logging },
40
+ enable_rate_limiting: { type: :boolean },
41
+ rate_limit_per_minute: { type: :integer, show_if: :enable_rate_limiting }
42
42
  }
43
43
  },
44
- "Notification Channels" => {
44
+ :notifications => {
45
45
  icon: "bi-bell",
46
46
  color: "info",
47
47
  settings: {
48
- enable_slack_notifications: { label: "Slack", description: "Real-time notifications", type: :boolean },
49
- slack_webhook_url: { label: "Slack Webhook URL", description: "Slack webhook endpoint", type: :url, show_if: :enable_slack_notifications },
50
- enable_email_notifications: { label: "Email", description: "Email alerts", type: :boolean },
51
- notification_email_recipients: { label: "Email Recipients", description: "Recipient addresses", type: :array, show_if: :enable_email_notifications },
52
- notification_email_from: { label: "Email From", description: "Sender address", type: :string, show_if: :enable_email_notifications },
53
- enable_discord_notifications: { label: "Discord", description: "Discord webhook notifications", type: :boolean },
54
- discord_webhook_url: { label: "Discord Webhook URL", description: "Discord webhook endpoint", type: :url, show_if: :enable_discord_notifications },
55
- enable_pagerduty_notifications: { label: "PagerDuty", description: "Critical error escalation", type: :boolean },
56
- pagerduty_integration_key: { label: "PagerDuty Integration Key", description: "PagerDuty API key", type: :string, show_if: :enable_pagerduty_notifications },
57
- enable_webhook_notifications: { label: "Custom Webhooks", description: "Generic webhook endpoints", type: :boolean },
58
- webhook_urls: { label: "Webhook URLs", description: "Custom webhook endpoints", type: :array, show_if: :enable_webhook_notifications },
59
- enable_scheduled_digests: { label: "Scheduled Digests", description: "Daily/weekly summary emails. Schedule via SolidQueue, Sidekiq, or cron", type: :boolean },
60
- digest_frequency: { label: "Digest Frequency", description: "How often to send digest emails", type: :symbol, show_if: :enable_scheduled_digests },
61
- digest_recipients: { label: "Digest Recipients", description: "Email addresses (falls back to notification recipients)", type: :array, show_if: :enable_scheduled_digests }
48
+ enable_slack_notifications: { type: :boolean },
49
+ slack_webhook_url: { type: :url, show_if: :enable_slack_notifications },
50
+ enable_email_notifications: { type: :boolean },
51
+ notification_email_recipients: { type: :array, show_if: :enable_email_notifications },
52
+ notification_email_from: { type: :string, show_if: :enable_email_notifications },
53
+ enable_discord_notifications: { type: :boolean },
54
+ discord_webhook_url: { type: :url, show_if: :enable_discord_notifications },
55
+ enable_pagerduty_notifications: { type: :boolean },
56
+ pagerduty_integration_key: { type: :string, show_if: :enable_pagerduty_notifications },
57
+ enable_webhook_notifications: { type: :boolean },
58
+ webhook_urls: { type: :array, show_if: :enable_webhook_notifications },
59
+ enable_scheduled_digests: { type: :boolean },
60
+ digest_frequency: { type: :symbol, show_if: :enable_scheduled_digests },
61
+ digest_recipients: { type: :array, show_if: :enable_scheduled_digests }
62
62
  }
63
63
  },
64
- "Advanced Analytics Features" => {
64
+ :analytics => {
65
65
  icon: "bi-graph-up",
66
66
  color: "success",
67
67
  settings: {
68
- enable_similar_errors: { label: "Similar Errors (Fuzzy Matching)", description: "Find related errors by similarity", type: :boolean },
69
- enable_co_occurring_errors: { label: "Co-occurring Errors", description: "Errors happening together", type: :boolean },
70
- enable_error_cascades: { label: "Error Cascades", description: "Parent→child error chains", type: :boolean },
71
- enable_error_correlation: { label: "Error Correlation", description: "Version/user/time analysis", type: :boolean },
72
- enable_platform_comparison: { label: "Platform Comparison", description: "iOS vs Android analytics", type: :boolean },
73
- enable_occurrence_patterns: { label: "Occurrence Patterns", description: "Cyclical/burst detection", type: :boolean },
74
- enable_baseline_alerts: { label: "Baseline Alerts", description: "Anomaly detection", type: :boolean },
75
- baseline_alert_threshold_std_devs: { label: "Alert Threshold", description: "Standard deviations to trigger alert", type: :float, show_if: :enable_baseline_alerts, unit: "σ" },
76
- baseline_alert_severities: { label: "Alert Severities", description: "Severities to alert on", type: :array, show_if: :enable_baseline_alerts },
77
- baseline_alert_cooldown_minutes: { label: "Alert Cooldown", description: "Minutes between alerts for same error type", type: :integer, show_if: :enable_baseline_alerts, unit: "minutes" }
68
+ enable_similar_errors: { type: :boolean },
69
+ enable_co_occurring_errors: { type: :boolean },
70
+ enable_error_cascades: { type: :boolean },
71
+ enable_error_correlation: { type: :boolean },
72
+ enable_platform_comparison: { type: :boolean },
73
+ enable_occurrence_patterns: { type: :boolean },
74
+ enable_baseline_alerts: { type: :boolean },
75
+ baseline_alert_threshold_std_devs: { type: :float, show_if: :enable_baseline_alerts, unit: :std_devs },
76
+ baseline_alert_severities: { type: :array, show_if: :enable_baseline_alerts },
77
+ baseline_alert_cooldown_minutes: { type: :integer, show_if: :enable_baseline_alerts, unit: :minutes }
78
78
  }
79
79
  },
80
- "Source Code Integration" => {
80
+ :source_code => {
81
81
  icon: "bi-code-square",
82
82
  color: "dark",
83
83
  settings: {
84
- enable_source_code_integration: { label: "Source Code Integration", description: "Show code in backtrace", type: :boolean },
85
- source_code_context_lines: { label: "Context Lines", description: "Lines before/after target line", type: :integer, show_if: :enable_source_code_integration },
86
- enable_git_blame: { label: "Git Blame", description: "Show git blame info", type: :boolean, show_if: :enable_source_code_integration },
87
- source_code_cache_ttl: { label: "Cache TTL", description: "Source code cache duration", type: :integer, show_if: :enable_source_code_integration, unit: "seconds" },
88
- only_show_app_code_source: { label: "Only App Code", description: "Hide gems/stdlib for security", type: :boolean, show_if: :enable_source_code_integration },
89
- git_branch_strategy: { label: "Git Branch Strategy", description: "Branch resolution strategy", type: :symbol, show_if: :enable_source_code_integration }
84
+ enable_source_code_integration: { type: :boolean },
85
+ source_code_context_lines: { type: :integer, show_if: :enable_source_code_integration },
86
+ enable_git_blame: { type: :boolean, show_if: :enable_source_code_integration },
87
+ source_code_cache_ttl: { type: :integer, show_if: :enable_source_code_integration, unit: :seconds },
88
+ only_show_app_code_source: { type: :boolean, show_if: :enable_source_code_integration },
89
+ git_branch_strategy: { type: :symbol, show_if: :enable_source_code_integration }
90
90
  }
91
91
  },
92
- "Breadcrumbs" => {
92
+ :breadcrumbs => {
93
93
  icon: "bi-signpost-2",
94
94
  color: "warning",
95
95
  settings: {
96
- enable_breadcrumbs: { label: "Breadcrumbs", description: "Capture request activity trail", type: :boolean },
97
- breadcrumb_buffer_size: { label: "Buffer Size", description: "Max breadcrumbs per request", type: :integer, show_if: :enable_breadcrumbs, unit: "events" },
98
- enable_n_plus_one_detection: { label: "N+1 Detection", description: "Detect repeated query patterns in breadcrumbs", type: :boolean, show_if: :enable_breadcrumbs },
99
- n_plus_one_threshold: { label: "N+1 Threshold", description: "Min repetitions to flag as N+1", type: :integer, show_if: :enable_n_plus_one_detection, unit: "queries" }
96
+ enable_breadcrumbs: { type: :boolean },
97
+ breadcrumb_buffer_size: { type: :integer, show_if: :enable_breadcrumbs, unit: :events },
98
+ enable_n_plus_one_detection: { type: :boolean, show_if: :enable_breadcrumbs },
99
+ n_plus_one_threshold: { type: :integer, show_if: :enable_n_plus_one_detection, unit: :queries }
100
100
  }
101
101
  },
102
- "System Health" => {
102
+ :system_health => {
103
103
  icon: "bi-heart-pulse",
104
104
  color: "danger",
105
105
  settings: {
106
- enable_system_health: { label: "System Health Snapshot", description: "Capture GC, memory, threads, connection pool at error time", type: :boolean }
106
+ enable_system_health: { type: :boolean }
107
107
  }
108
108
  },
109
- "Enhanced Metrics" => {
109
+ :enhanced_metrics => {
110
110
  icon: "bi-speedometer",
111
111
  color: "secondary",
112
112
  settings: {
113
- app_version: { label: "App Version", description: "Current application version", type: :string },
114
- git_sha: { label: "Git SHA", description: "Deployed commit", type: :git_sha },
115
- git_repository_url: { label: "Git Repository URL", description: "GitHub/GitLab repo URL", type: :url },
116
- dashboard_base_url: { label: "Dashboard Base URL", description: "Base URL for dashboard links", type: :url }
113
+ app_version: { type: :string },
114
+ git_sha: { type: :git_sha },
115
+ git_repository_url: { type: :url },
116
+ dashboard_base_url: { type: :url }
117
117
  }
118
118
  },
119
- "Issue Tracking" => {
119
+ :issue_tracking => {
120
120
  icon: "bi-link-45deg",
121
121
  color: "primary",
122
122
  settings: {
123
- enable_issue_tracking: { label: "Issue Tracking", description: "GitHub/GitLab/Codeberg integration — one switch enables all: issue creation, auto-create, lifecycle sync, platform state mirroring, comment display. Replaces workflow controls with platform state.", type: :boolean },
124
- issue_tracker_token: { label: "API Token", description: "RED bot token (set RED_BOT_TOKEN env var)", type: :secret, show_if: :enable_issue_tracking },
125
- issue_tracker_provider: { label: "Provider", description: "Auto-detected from git_repository_url", type: :auto_detect, show_if: :enable_issue_tracking, method: :effective_issue_tracker_provider },
126
- issue_tracker_repo: { label: "Repository", description: "Auto-detected from git_repository_url", type: :auto_detect, show_if: :enable_issue_tracking, method: :effective_issue_tracker_repo },
127
- issue_tracker_labels: { label: "Labels", description: "Labels added to new issues", type: :array, show_if: :enable_issue_tracking },
128
- issue_webhook_secret: { label: "Webhook Secret", description: "Set to enable two-way sync (HMAC verification)", type: :secret, show_if: :enable_issue_tracking }
123
+ enable_issue_tracking: { type: :boolean },
124
+ issue_tracker_token: { type: :secret, show_if: :enable_issue_tracking },
125
+ issue_tracker_provider: { type: :auto_detect, show_if: :enable_issue_tracking, method: :effective_issue_tracker_provider },
126
+ issue_tracker_repo: { type: :auto_detect, show_if: :enable_issue_tracking, method: :effective_issue_tracker_repo },
127
+ issue_tracker_labels: { type: :array, show_if: :enable_issue_tracking },
128
+ issue_webhook_secret: { type: :secret, show_if: :enable_issue_tracking }
129
129
  }
130
130
  },
131
- "Deep Debugging" => {
131
+ :deep_debugging => {
132
132
  icon: "bi-search",
133
133
  color: "danger",
134
134
  settings: {
135
- enable_local_variables: { label: "Local Variable Capture", description: "TracePoint(:raise) — capture locals at exception time", type: :boolean },
136
- enable_instance_variables: { label: "Instance Variable Capture", description: "Capture self's ivars at exception time", type: :boolean },
137
- detect_swallowed_exceptions: { label: "Swallowed Exception Detection", description: "TracePoint(:raise) + (:rescue) — detect silently rescued exceptions (Ruby 3.3+)", type: :boolean },
138
- enable_diagnostic_dump: { label: "Diagnostic Dump", description: "On-demand system state snapshots", type: :boolean },
139
- enable_crash_capture: { label: "Process Crash Capture", description: "at_exit hook for unhandled crashes", type: :boolean },
140
- enable_coverage_tracking: { label: "Code Path Coverage", description: "Diagnostic mode — enable via dashboard to see executed lines in source view (Ruby 3.2+)", type: :boolean }
135
+ enable_local_variables: { type: :boolean },
136
+ enable_instance_variables: { type: :boolean },
137
+ detect_swallowed_exceptions: { type: :boolean },
138
+ enable_diagnostic_dump: { type: :boolean },
139
+ enable_crash_capture: { type: :boolean },
140
+ enable_coverage_tracking: { type: :boolean }
141
141
  }
142
142
  },
143
- "Event Tracking" => {
143
+ :event_tracking => {
144
144
  icon: "bi-broadcast",
145
145
  color: "info",
146
146
  settings: {
147
- enable_rack_attack_tracking: { label: "Rack Attack Tracking", description: "Track throttle/blocklist events as breadcrumbs", type: :boolean },
148
- enable_actioncable_tracking: { label: "ActionCable Tracking", description: "Track WebSocket channel events as breadcrumbs", type: :boolean },
149
- enable_activestorage_tracking: { label: "ActiveStorage Tracking", description: "Track uploads/downloads/deletes as breadcrumbs", type: :boolean }
147
+ enable_rack_attack_tracking: { type: :boolean },
148
+ enable_actioncable_tracking: { type: :boolean },
149
+ enable_activestorage_tracking: { type: :boolean }
150
150
  }
151
151
  },
152
- "Advanced Configuration" => {
152
+ :advanced => {
153
153
  icon: "bi-sliders",
154
154
  color: "secondary",
155
155
  settings: {
156
- custom_severity_rules: { label: "Custom Severity Rules", description: "Error type → severity mapping", type: :hash },
157
- ignored_exceptions: { label: "Ignored Exceptions", description: "Exceptions to ignore", type: :array }
156
+ custom_severity_rules: { type: :hash },
157
+ ignored_exceptions: { type: :array }
158
158
  }
159
159
  },
160
- "Internal Logging" => {
160
+ :internal_logging => {
161
161
  icon: "bi-terminal",
162
162
  color: "light",
163
163
  settings: {
164
- enable_internal_logging: { label: "Internal Logging", description: "Gem debugging output", type: :boolean },
165
- log_level: { label: "Log Level", description: "Verbosity level", type: :symbol, show_if: :enable_internal_logging }
164
+ enable_internal_logging: { type: :boolean },
165
+ log_level: { type: :symbol, show_if: :enable_internal_logging }
166
166
  }
167
167
  }
168
168
  }
@@ -170,21 +170,24 @@
170
170
 
171
171
  <div>
172
172
  <div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--space-4);">
173
- <h1 style="font-size: 20px; font-weight: 700; margin: 0;">Settings</h1>
174
- <span style="font-family: var(--font-mono); font-size: 12px; font-weight: 500; color: var(--text-tertiary); padding: 2px 10px; border: 1px solid var(--border-primary); border-radius: var(--radius-full);">v<%= RailsErrorDashboard::VERSION %></span>
173
+ <h1 style="font-size: 20px; font-weight: 700; margin: 0;"><%= red_t("red.settings.title") %></h1>
174
+ <span style="font-family: var(--font-mono); font-size: 12px; font-weight: 500; color: var(--text-tertiary); padding: 2px 10px; border: 1px solid var(--border-primary); border-radius: var(--radius-full);"><%= red_t("red.settings.version", version: RailsErrorDashboard::VERSION) %></span>
175
175
  </div>
176
176
 
177
+ <%# The initializer path is built here rather than interpolated into the key
178
+ as raw markup: content_tag escapes it, so the _html key never receives an
179
+ unescaped value. %>
177
180
  <p style="font-size: 13px; color: var(--text-tertiary); margin-bottom: var(--space-6);">
178
- Read-only view of current configuration. Edit via <code>config/initializers/rails_error_dashboard.rb</code> or environment variables.
181
+ <%= red_t("red.settings.intro_html", file: content_tag(:code, "config/initializers/rails_error_dashboard.rb")) %>
179
182
  </p>
180
183
 
181
184
  <!-- Dynamically render setting groups -->
182
- <% setting_groups.each do |group_name, group_config| %>
185
+ <% setting_groups.each do |group_slug, group_config| %>
183
186
  <div class="card" style="margin-bottom: var(--space-4);">
184
187
  <button type="button" data-red-action="toggle-next" data-red-display="block" style="display: flex; align-items: center; justify-content: space-between; width: 100%; padding: var(--space-4) var(--space-6); background: none; border: none; cursor: pointer; border-bottom: 1px solid var(--border-primary);">
185
- <span style="font-size: 14px; font-weight: 600; color: var(--text-primary);"><i class="<%= group_config[:icon] %>" style="margin-right: 6px;"></i> <%= group_name %></span>
188
+ <span style="font-size: 14px; font-weight: 600; color: var(--text-primary);"><i class="<%= group_config[:icon] %>" style="margin-right: 6px;"></i> <%= red_t("red.settings.groups.#{group_slug}") %></span>
186
189
  <div style="display: flex; align-items: center; gap: 8px;">
187
- <span style="font-size: 11px; color: var(--text-tertiary);"><%= group_config[:settings].size %> options</span>
190
+ <span style="font-size: 11px; color: var(--text-tertiary);"><%= red_tp("red.settings.option_count", count: group_config[:settings].size) %></span>
188
191
  <i class="bi bi-chevron-up chev" style="font-size: 12px; color: var(--text-tertiary);"></i>
189
192
  </div>
190
193
  </button>
@@ -202,7 +205,9 @@
202
205
  <%= render partial: 'rails_error_dashboard/errors/settings/value_badge',
203
206
  locals: { value: value, type: setting_meta[:type], unit: setting_meta[:unit] } %>
204
207
  </span>
205
- <span style="color: var(--text-tertiary); font-size: 12px;"><%= setting_meta[:description] %></span>
208
+ <%# Keyed by the config option's own name, so a key is greppable
209
+ from the initializer that sets it. %>
210
+ <span style="color: var(--text-tertiary); font-size: 12px;"><%= red_t("red.settings.options.#{setting_key}") %></span>
206
211
  </div>
207
212
  <% end %>
208
213
  </div>
@@ -212,7 +217,7 @@
212
217
  <!-- Active Plugins -->
213
218
  <div class="card" style="margin-bottom: var(--space-4);">
214
219
  <div style="padding: var(--space-4) var(--space-6); border-bottom: 1px solid var(--border-primary);">
215
- <span style="font-size: 14px; font-weight: 600; color: var(--text-primary);"><i class="bi bi-puzzle" style="margin-right: 6px;"></i> Active Plugins</span>
220
+ <span style="font-size: 14px; font-weight: 600; color: var(--text-primary);"><i class="bi bi-puzzle" style="margin-right: 6px;"></i> <%= red_t("red.settings.plugins.title") %></span>
216
221
  </div>
217
222
  <div class="card-body">
218
223
  <% if RailsErrorDashboard::PluginRegistry.any? %>
@@ -220,10 +225,10 @@
220
225
  <table class="table table-hover">
221
226
  <thead>
222
227
  <tr>
223
- <th>Plugin Name</th>
224
- <th>Version</th>
225
- <th>Description</th>
226
- <th>Status</th>
228
+ <th><%= red_t("red.settings.plugins.column_name") %></th>
229
+ <th><%= red_t("red.settings.plugins.column_version") %></th>
230
+ <th><%= red_t("red.settings.plugins.column_description") %></th>
231
+ <th><%= red_t("red.settings.plugins.column_status") %></th>
227
232
  </tr>
228
233
  </thead>
229
234
  <tbody>
@@ -235,11 +240,11 @@
235
240
  <td>
236
241
  <% if plugin[:enabled] %>
237
242
  <span class="badge bg-success">
238
- <i class="bi bi-check-circle"></i> Active
243
+ <i class="bi bi-check-circle"></i> <%= red_t("red.settings.plugins.active") %>
239
244
  </span>
240
245
  <% else %>
241
246
  <span class="badge bg-secondary">
242
- <i class="bi bi-pause-circle"></i> Inactive
247
+ <i class="bi bi-pause-circle"></i> <%= red_t("red.settings.plugins.inactive") %>
243
248
  </span>
244
249
  <% end %>
245
250
  </td>
@@ -251,7 +256,7 @@
251
256
  <% else %>
252
257
  <div class="alert alert-secondary mb-0">
253
258
  <i class="bi bi-info-circle me-2"></i>
254
- No plugins are currently registered. You can create custom plugins to extend functionality.
259
+ <%= red_t("red.settings.plugins.empty") %>
255
260
  </div>
256
261
  <% end %>
257
262
  </div>
@@ -260,17 +265,15 @@
260
265
  <!-- Test Error -->
261
266
  <div class="card" style="margin-bottom: var(--space-4);">
262
267
  <div style="padding: var(--space-4) var(--space-6); border-bottom: 1px solid var(--border-primary);">
263
- <span style="font-size: 14px; font-weight: 600; color: var(--text-primary);"><i class="bi bi-send-check" style="margin-right: 6px;"></i> Test Notifications</span>
268
+ <span style="font-size: 14px; font-weight: 600; color: var(--text-primary);"><i class="bi bi-send-check" style="margin-right: 6px;"></i> <%= red_t("red.settings.test_error.title") %></span>
264
269
  </div>
265
270
  <div style="padding: var(--space-5) var(--space-6);">
266
271
  <p style="font-size: 13px; color: var(--text-secondary); margin-bottom: var(--space-4);">
267
- Send a test error through the full capture and notification pipeline. This creates a <code>RailsErrorDashboard::TestError</code> in the
268
- error log and dispatches notifications to all configured channels (Slack, Discord, PagerDuty, email, webhooks).
269
- The test error is clearly marked and safe to resolve or delete.
272
+ <%= red_t("red.settings.test_error.description_html", error_class: content_tag(:code, "RailsErrorDashboard::TestError")) %>
270
273
  </p>
271
274
  <%= form_tag test_error_errors_path(application_id: @current_application_id.presence), method: :post, style: "display: inline;" do %>
272
- <button type="submit" class="btn btn-outline" data-red-action="confirm-submit" data-red-confirm-message="This will create a test error and send notifications to all configured channels. Continue?">
273
- <i class="bi bi-send" style="margin-right: 4px;"></i> Send Test Error
275
+ <button type="submit" class="btn btn-outline" data-red-action="confirm-submit" data-red-confirm-message="<%= red_t("red.settings.test_error.confirm") %>">
276
+ <i class="bi bi-send" style="margin-right: 4px;"></i> <%= red_t("red.settings.test_error.submit") %>
274
277
  </button>
275
278
  <% end %>
276
279
  </div>
@@ -280,12 +283,19 @@
280
283
  <div class="alert alert-light border">
281
284
  <h6 class="alert-heading">
282
285
  <i class="bi bi-question-circle me-2"></i>
283
- Need to change these settings?
286
+ <%= red_t("red.settings.help.title") %>
284
287
  </h6>
288
+ <%# Two whole sentences, each with its element interpolated rather than
289
+ prose fragments assembled around the markup — clause order is not
290
+ universal. The elements are built here so their contents are escaped. %>
285
291
  <p class="mb-0">
286
- Edit your configuration in <code>config/initializers/rails_error_dashboard.rb</code> and restart your application.
292
+ <%= red_t("red.settings.help.edit_html",
293
+ file: content_tag(:code, "config/initializers/rails_error_dashboard.rb")) %>
287
294
  <br>
288
- See the <a href="https://github.com/AnjanJ/rails_error_dashboard" target="_blank" class="alert-link">documentation</a> for more information.
295
+ <%= red_t("red.settings.help.docs_html",
296
+ docs: link_to(red_t("red.settings.help.docs_link"),
297
+ "https://github.com/AnjanJ/rails_error_dashboard",
298
+ target: "_blank", class: "alert-link")) %>
289
299
  </p>
290
300
  </div>
291
301
  </div>