rails_nexus 2.0.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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +430 -0
- data/Rakefile +6 -0
- data/app/assets/config/rails_nexus_manifest.js +2 -0
- data/app/assets/javascripts/rails_nexus/controllers.js +603 -0
- data/app/assets/stylesheets/rails_nexus/application.css +1964 -0
- data/app/controllers/rails_nexus/analytics_controller.rb +361 -0
- data/app/controllers/rails_nexus/application_controller.rb +15 -0
- data/app/controllers/rails_nexus/backup_controller.rb +91 -0
- data/app/controllers/rails_nexus/cron_jobs_controller.rb +64 -0
- data/app/controllers/rails_nexus/database_health_controller.rb +128 -0
- data/app/controllers/rails_nexus/logged_exceptions_controller.rb +227 -0
- data/app/controllers/rails_nexus/n1_patterns_controller.rb +10 -0
- data/app/controllers/rails_nexus/settings_controller.rb +100 -0
- data/app/controllers/rails_nexus/source_code_controller.rb +39 -0
- data/app/controllers/rails_nexus/stats_controller.rb +494 -0
- data/app/controllers/rails_nexus/workflow_controller.rb +96 -0
- data/app/helpers/rails_nexus/application_helper.rb +5 -0
- data/app/helpers/rails_nexus/logged_exceptions_helper.rb +254 -0
- data/app/helpers/rails_nexus/source_code_helper.rb +107 -0
- data/app/javascript/controllers/rails_nexus_controller.js +380 -0
- data/app/javascript/controllers/rails_nexus_detail_controller.js +165 -0
- data/app/javascript/controllers/rails_nexus_sidebar_controller.js +15 -0
- data/app/javascript/controllers/rails_nexus_theme_controller.js +31 -0
- data/app/javascript/rails_nexus/application.js +17 -0
- data/app/jobs/rails_nexus/application_job.rb +4 -0
- data/app/mailers/rails_nexus/application_mailer.rb +6 -0
- data/app/models/rails_nexus/application_record.rb +5 -0
- data/app/models/rails_nexus/backup.rb +107 -0
- data/app/models/rails_nexus/base_record.rb +26 -0
- data/app/models/rails_nexus/comment.rb +13 -0
- data/app/models/rails_nexus/cron_job.rb +72 -0
- data/app/models/rails_nexus/database_stat.rb +128 -0
- data/app/models/rails_nexus/event.rb +101 -0
- data/app/models/rails_nexus/logged_exception.rb +864 -0
- data/app/models/rails_nexus/logged_exception_n1.rb +87 -0
- data/app/models/rails_nexus/metric.rb +107 -0
- data/app/models/rails_nexus/nginx_metric.rb +112 -0
- data/app/models/rails_nexus/server_metric.rb +168 -0
- data/app/models/rails_nexus/webhook_delivery.rb +64 -0
- data/app/services/rails_nexus/backup_service.rb +275 -0
- data/app/views/layouts/rails_nexus/application.html.erb +185 -0
- data/app/views/rails_nexus/analytics/index.html.erb +363 -0
- data/app/views/rails_nexus/backup/files.html.erb +53 -0
- data/app/views/rails_nexus/backup/index.html.erb +211 -0
- data/app/views/rails_nexus/backup/settings.html.erb +158 -0
- data/app/views/rails_nexus/cron_jobs/index.html.erb +141 -0
- data/app/views/rails_nexus/database_health/index.html.erb +139 -0
- data/app/views/rails_nexus/logged_exceptions/_comments_list.html.erb +50 -0
- data/app/views/rails_nexus/logged_exceptions/_exceptions.html.erb +190 -0
- data/app/views/rails_nexus/logged_exceptions/_feed.html.erb +3 -0
- data/app/views/rails_nexus/logged_exceptions/_filter_group.html.erb +10 -0
- data/app/views/rails_nexus/logged_exceptions/_show.html.erb +554 -0
- data/app/views/rails_nexus/logged_exceptions/_workflow_panel.html.erb +118 -0
- data/app/views/rails_nexus/logged_exceptions/clear.turbo_stream.erb +6 -0
- data/app/views/rails_nexus/logged_exceptions/destroy.turbo_stream.erb +2 -0
- data/app/views/rails_nexus/logged_exceptions/destroy_all.turbo_stream.erb +6 -0
- data/app/views/rails_nexus/logged_exceptions/feed.rss.builder +20 -0
- data/app/views/rails_nexus/logged_exceptions/index.html.erb +323 -0
- data/app/views/rails_nexus/logged_exceptions/query.html.erb +3 -0
- data/app/views/rails_nexus/logged_exceptions/query.turbo_stream.erb +6 -0
- data/app/views/rails_nexus/logged_exceptions/show.html.erb +3 -0
- data/app/views/rails_nexus/logged_exceptions/show.turbo_stream.erb +5 -0
- data/app/views/rails_nexus/n1_patterns/index.html.erb +153 -0
- data/app/views/rails_nexus/settings/index.html.erb +140 -0
- data/app/views/rails_nexus/stats/index.html.erb +569 -0
- data/config/importmap.rb +15 -0
- data/config/initializers/date_formats.rb +5 -0
- data/config/initializers/rails_nexus.rb +18 -0
- data/config/locales/en.yml +47 -0
- data/config/routes.rb +58 -0
- data/db/migrate/20240330122311_create_rails_nexus_logged_exceptions.rb +22 -0
- data/lib/generators/rails_nexus/backup_generator.rb +70 -0
- data/lib/generators/rails_nexus/customize_generator.rb +159 -0
- data/lib/generators/rails_nexus/install_generator.rb +61 -0
- data/lib/generators/rails_nexus/templates/INSTALL.md +9 -0
- data/lib/generators/rails_nexus/templates/backup/backup_helper.sh +152 -0
- data/lib/generators/rails_nexus/templates/backup/config.rb +27 -0
- data/lib/generators/rails_nexus/templates/backup/models/daily_backup.rb +24 -0
- data/lib/generators/rails_nexus/templates/backup/models/full_backup.rb +35 -0
- data/lib/generators/rails_nexus/templates/backup/models/sync_backup.rb +43 -0
- data/lib/generators/rails_nexus/templates/backup/mysql-config/db_config.cnf +15 -0
- data/lib/generators/rails_nexus/templates/backup/schedule.rb +28 -0
- data/lib/generators/rails_nexus/templates/javascript/controllers/rails_nexus_controller.js +310 -0
- data/lib/generators/rails_nexus/templates/javascript/controllers/rails_nexus_detail_controller.js +98 -0
- data/lib/generators/rails_nexus/templates/javascript/controllers/rails_nexus_sidebar_controller.js +15 -0
- data/lib/generators/rails_nexus/templates/javascript/controllers/rails_nexus_theme_controller.js +30 -0
- data/lib/generators/rails_nexus/templates/migration.rb +24 -0
- data/lib/generators/rails_nexus/templates/migration_advanced_features.rb +30 -0
- data/lib/generators/rails_nexus/templates/migration_cron_jobs.rb +22 -0
- data/lib/generators/rails_nexus/templates/migration_new_schema.rb +120 -0
- data/lib/generators/rails_nexus/templates/migration_platform_detection.rb +11 -0
- data/lib/generators/rails_nexus/templates/migration_webhook_deliveries.rb +21 -0
- data/lib/generators/rails_nexus/templates/migration_workflow.rb +29 -0
- data/lib/generators/rails_nexus/templates/rails_nexus.rb +96 -0
- data/lib/generators/rails_nexus/templates/views/layouts/rails_nexus/application.html.erb +129 -0
- data/lib/generators/rails_nexus/templates/views/rails_nexus/logged_exceptions/_exceptions.html.erb +123 -0
- data/lib/generators/rails_nexus/templates/views/rails_nexus/logged_exceptions/_show.html.erb +297 -0
- data/lib/generators/rails_nexus/templates/views/rails_nexus/logged_exceptions/index.html.erb +113 -0
- data/lib/rails_nexus/breadcrumbs.rb +161 -0
- data/lib/rails_nexus/cleanup.rb +32 -0
- data/lib/rails_nexus/configuration.rb +319 -0
- data/lib/rails_nexus/engine.rb +135 -0
- data/lib/rails_nexus/logger.rb +188 -0
- data/lib/rails_nexus/notifications.rb +105 -0
- data/lib/rails_nexus/ransack_config.rb +4 -0
- data/lib/rails_nexus/storm_protection.rb +124 -0
- data/lib/rails_nexus/version.rb +3 -0
- data/lib/rails_nexus.rb +133 -0
- data/lib/tasks/rails_nexus.rake +230 -0
- data/lib/tasks/rails_nexus_tasks.rake +37 -0
- data/rails_nexus.gemspec +36 -0
- metadata +252 -0
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
<div class="rn-detail-panel" style="border-left: none; border-radius: var(--rn-radius-lg); overflow: hidden" data-controller="rails_nexus-detail">
|
|
2
|
+
|
|
3
|
+
<%# ─── Detail Header ───────────────────────────────────────── %>
|
|
4
|
+
<div class="rn-detail-header">
|
|
5
|
+
<div class="min-w-0 flex-1">
|
|
6
|
+
<%# Breadcrumbs %>
|
|
7
|
+
<nav class="rn-breadcrumbs" style="padding: 0; margin-bottom: 0.5rem">
|
|
8
|
+
<a href="<%= logged_exceptions_path %>" class="rn-breadcrumb">Exceptions</a>
|
|
9
|
+
<span class="rn-breadcrumb-sep">/</span>
|
|
10
|
+
<span class="rn-breadcrumb-current"><%= @exception.exception_class %></span>
|
|
11
|
+
</nav>
|
|
12
|
+
|
|
13
|
+
<div class="flex items-center gap-2 flex-wrap">
|
|
14
|
+
<span class="rn-badge rn-badge-<%= severity_for(@exception) %>"><%= @exception.exception_class %></span>
|
|
15
|
+
<span class="rn-mono text-xs" style="color: var(--rn-text-secondary)"><%= @exception.controller_name %>#<%= @exception.action_name %></span>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<p class="mt-1.5 text-xs" style="color: var(--rn-text-muted)">
|
|
19
|
+
<time datetime="<%= @exception.created_at.iso8601 %>"><%= @exception.created_at.strftime("%B %d, %Y at %I:%M:%S %p") %></time>
|
|
20
|
+
<% if @exception.remote_ip.present? %>
|
|
21
|
+
<span style="color: var(--rn-border)">·</span>
|
|
22
|
+
<span class="rn-mono"><%= @exception.remote_ip %></span>
|
|
23
|
+
<% end %>
|
|
24
|
+
</p>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<div class="flex items-center gap-2 shrink-0">
|
|
28
|
+
<%# Copy exception ID %>
|
|
29
|
+
<button type="button"
|
|
30
|
+
class="rn-copy-btn"
|
|
31
|
+
data-action="click->rails_nexus-detail#copyId"
|
|
32
|
+
data-rails_nexus-detail-id-value="<%= @exception.id %>"
|
|
33
|
+
title="Copy exception ID">
|
|
34
|
+
<svg class="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M15.666 3.888A2.25 2.25 0 0013.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 01-.75.75H9.75a.75.75 0 01-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 01-2.25 2.25H6.75A2.25 2.25 0 014.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 011.927-.184" /></svg>
|
|
35
|
+
<span>ID</span>
|
|
36
|
+
</button>
|
|
37
|
+
|
|
38
|
+
<%# Copy message %>
|
|
39
|
+
<button type="button"
|
|
40
|
+
class="rn-copy-btn"
|
|
41
|
+
data-action="click->rails_nexus-detail#copyMessage"
|
|
42
|
+
data-rails_nexus-detail-message-value="<%= @exception.message %>"
|
|
43
|
+
title="Copy message">
|
|
44
|
+
<svg class="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" /></svg>
|
|
45
|
+
<span>Message</span>
|
|
46
|
+
</button>
|
|
47
|
+
|
|
48
|
+
<%# Close %>
|
|
49
|
+
<button type="button"
|
|
50
|
+
class="rn-icon-btn shrink-0"
|
|
51
|
+
data-action="click->rails_nexus-detail#closeDetails"
|
|
52
|
+
title="Close (Esc)">
|
|
53
|
+
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" /></svg>
|
|
54
|
+
</button>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<%# ─── Tabs ─────────────────────────────────────────────────── %>
|
|
59
|
+
<% if @exception_tabs && @exception_tabs.values.count(true) > 1 %>
|
|
60
|
+
<div class="rn-tabs" data-rails_nexus-detail-target="tabs">
|
|
61
|
+
<button type="button" class="rn-tab active" data-action="click->rails_nexus-detail#switchTab" data-rn-tab-param="overview">Overview</button>
|
|
62
|
+
<% if @exception_tabs[:backtrace] %>
|
|
63
|
+
<button type="button" class="rn-tab" data-action="click->rails_nexus-detail#switchTab" data-rn-tab-param="backtrace">
|
|
64
|
+
Backtrace
|
|
65
|
+
<span class="rn-tab-badge"><%= @exception.backtrace.to_s.split("\n").length %></span>
|
|
66
|
+
</button>
|
|
67
|
+
<% end %>
|
|
68
|
+
<% if @exception_tabs[:request] %>
|
|
69
|
+
<button type="button" class="rn-tab" data-action="click->rails_nexus-detail#switchTab" data-rn-tab-param="request">Request</button>
|
|
70
|
+
<% end %>
|
|
71
|
+
<% if @exception_tabs[:environment] %>
|
|
72
|
+
<button type="button" class="rn-tab" data-action="click->rails_nexus-detail#switchTab" data-rn-tab-param="environment">Environment</button>
|
|
73
|
+
<% end %>
|
|
74
|
+
<% if @exception_tabs[:user_info] %>
|
|
75
|
+
<button type="button" class="rn-tab" data-action="click->rails_nexus-detail#switchTab" data-rn-tab-param="user_info">User Info</button>
|
|
76
|
+
<% end %>
|
|
77
|
+
</div>
|
|
78
|
+
<% end %>
|
|
79
|
+
|
|
80
|
+
<%# ─── Detail Body ─────────────────────────────────────────── %>
|
|
81
|
+
<div class="rn-detail-body">
|
|
82
|
+
|
|
83
|
+
<%# ─── Tab: Overview ────────────────────────────────────── %>
|
|
84
|
+
<div data-rails_nexus-detail-target="tabContent" data-rn-tab-content="overview">
|
|
85
|
+
|
|
86
|
+
<%# Message %>
|
|
87
|
+
<div class="rn-detail-section">
|
|
88
|
+
<div class="rn-detail-section-header">
|
|
89
|
+
<h3>Message</h3>
|
|
90
|
+
</div>
|
|
91
|
+
<div class="rn-detail-code"><%= @exception.message %></div>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<%# Metadata Grid %>
|
|
95
|
+
<% if RailsNexus.configuration.show_metadata %>
|
|
96
|
+
<div class="rn-detail-section">
|
|
97
|
+
<h3 style="margin-bottom: 0.5rem">Details</h3>
|
|
98
|
+
<div class="rn-meta">
|
|
99
|
+
<div class="rn-meta-item">
|
|
100
|
+
<div class="rn-meta-label">Exception</div>
|
|
101
|
+
<div class="rn-meta-value"><%= @exception.exception_class %></div>
|
|
102
|
+
</div>
|
|
103
|
+
<div class="rn-meta-item">
|
|
104
|
+
<div class="rn-meta-label">Controller</div>
|
|
105
|
+
<div class="rn-meta-value mono"><%= @exception.controller_name %></div>
|
|
106
|
+
</div>
|
|
107
|
+
<div class="rn-meta-item">
|
|
108
|
+
<div class="rn-meta-label">Action</div>
|
|
109
|
+
<div class="rn-meta-value mono"><%= @exception.action_name %></div>
|
|
110
|
+
</div>
|
|
111
|
+
<div class="rn-meta-item">
|
|
112
|
+
<div class="rn-meta-label">Time</div>
|
|
113
|
+
<div class="rn-meta-value"><time datetime="<%= @exception.created_at.iso8601 %>"><%= @exception.created_at.strftime("%Y-%m-%d %H:%M:%S %Z") %></time></div>
|
|
114
|
+
</div>
|
|
115
|
+
<% if @exception.remote_ip.present? %>
|
|
116
|
+
<div class="rn-meta-item">
|
|
117
|
+
<div class="rn-meta-label">IP Address</div>
|
|
118
|
+
<div class="rn-meta-value mono"><%= @exception.remote_ip %></div>
|
|
119
|
+
</div>
|
|
120
|
+
<% end %>
|
|
121
|
+
<% if @exception.user_agent.present? %>
|
|
122
|
+
<div class="rn-meta-item" style="grid-column: span 2">
|
|
123
|
+
<div class="rn-meta-label">User Agent</div>
|
|
124
|
+
<div class="rn-meta-value" style="font-size: 0.8125rem; word-break: break-all"><%= @exception.user_agent %></div>
|
|
125
|
+
</div>
|
|
126
|
+
<% end %>
|
|
127
|
+
<div class="rn-meta-item">
|
|
128
|
+
<div class="rn-meta-label">Exception ID</div>
|
|
129
|
+
<div class="rn-meta-value mono"><%= @exception.id %></div>
|
|
130
|
+
</div>
|
|
131
|
+
<% if @exception.id_value.present? %>
|
|
132
|
+
<div class="rn-meta-item">
|
|
133
|
+
<div class="rn-meta-label">ID Value</div>
|
|
134
|
+
<div class="rn-meta-value mono"><%= @exception.id_value %></div>
|
|
135
|
+
</div>
|
|
136
|
+
<% end %>
|
|
137
|
+
</div>
|
|
138
|
+
</div>
|
|
139
|
+
<% end %>
|
|
140
|
+
|
|
141
|
+
<%# Request Params %>
|
|
142
|
+
<% if RailsNexus.configuration.show_request && @exception.request.present? %>
|
|
143
|
+
<div class="rn-detail-section">
|
|
144
|
+
<div class="rn-detail-section-header">
|
|
145
|
+
<h3>Request</h3>
|
|
146
|
+
</div>
|
|
147
|
+
<div class="rn-detail-code"><%= pretty_format(@exception.request) %></div>
|
|
148
|
+
</div>
|
|
149
|
+
<% end %>
|
|
150
|
+
|
|
151
|
+
<%# User Info %>
|
|
152
|
+
<% if @exception.user_info.present? %>
|
|
153
|
+
<div class="rn-detail-section">
|
|
154
|
+
<div class="rn-detail-section-header">
|
|
155
|
+
<h3>User Info</h3>
|
|
156
|
+
</div>
|
|
157
|
+
<div class="rn-detail-code"><%= pretty_format(@exception.user_info) %></div>
|
|
158
|
+
</div>
|
|
159
|
+
<% end %>
|
|
160
|
+
</div>
|
|
161
|
+
|
|
162
|
+
<%# ─── Tab: Backtrace ────────────────────────────────────── %>
|
|
163
|
+
<div data-rails_nexus-detail-target="tabContent" data-rn-tab-content="backtrace" style="display: none">
|
|
164
|
+
<% if @exception.backtrace.present? %>
|
|
165
|
+
<% bt = format_backtrace(@exception.backtrace) %>
|
|
166
|
+
<div class="rn-detail-section">
|
|
167
|
+
<div class="rn-detail-section-header">
|
|
168
|
+
<h3>Stack Trace</h3>
|
|
169
|
+
<div class="flex items-center gap-2">
|
|
170
|
+
<button type="button" class="rn-copy-btn" data-action="click->rails_nexus-detail#copyBacktrace"
|
|
171
|
+
data-rails_nexus-detail-backtrace-value="<%= @exception.backtrace %>">
|
|
172
|
+
<svg class="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M15.666 3.888A2.25 2.25 0 0013.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 01-.75.75H9.75a.75.75 0 01-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 01-2.25 2.25H6.75A2.25 2.25 0 014.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 011.927-.184" /></svg>
|
|
173
|
+
Copy
|
|
174
|
+
</button>
|
|
175
|
+
</div>
|
|
176
|
+
</div>
|
|
177
|
+
<div class="rn-detail-pre" data-rails_nexus-detail-target="backtraceContent">
|
|
178
|
+
<%= bt[:visible] %>
|
|
179
|
+
</div>
|
|
180
|
+
<% if bt[:hidden_count] > 0 %>
|
|
181
|
+
<button type="button" class="rn-btn rn-btn-ghost rn-btn-sm mt-2"
|
|
182
|
+
data-action="click->rails_nexus-detail#expandBacktrace">
|
|
183
|
+
Show <%= bt[:hidden_count] %> more frames
|
|
184
|
+
</button>
|
|
185
|
+
<div class="rn-detail-pre mt-2" style="display: none" data-rails_nexus-detail-target="backtraceHidden">
|
|
186
|
+
<%= bt[:hidden] %>
|
|
187
|
+
</div>
|
|
188
|
+
<% end %>
|
|
189
|
+
</div>
|
|
190
|
+
<% else %>
|
|
191
|
+
<div class="text-center py-8">
|
|
192
|
+
<p class="text-sm" style="color: var(--rn-text-muted)">No backtrace available</p>
|
|
193
|
+
</div>
|
|
194
|
+
<% end %>
|
|
195
|
+
</div>
|
|
196
|
+
|
|
197
|
+
<%# ─── Tab: Request ──────────────────────────────────────── %>
|
|
198
|
+
<div data-rails_nexus-detail-target="tabContent" data-rn-tab-content="request" style="display: none">
|
|
199
|
+
<% if @exception.request.present? %>
|
|
200
|
+
<div class="rn-detail-section">
|
|
201
|
+
<div class="rn-detail-section-header">
|
|
202
|
+
<h3>Request Data</h3>
|
|
203
|
+
</div>
|
|
204
|
+
<div class="rn-detail-code" style="max-height: 32rem; overflow: auto"><%= pretty_format(@exception.request) %></div>
|
|
205
|
+
</div>
|
|
206
|
+
<% else %>
|
|
207
|
+
<div class="text-center py-8">
|
|
208
|
+
<p class="text-sm" style="color: var(--rn-text-muted)">No request data available</p>
|
|
209
|
+
</div>
|
|
210
|
+
<% end %>
|
|
211
|
+
</div>
|
|
212
|
+
|
|
213
|
+
<%# ─── Tab: Environment ──────────────────────────────────── %>
|
|
214
|
+
<div data-rails_nexus-detail-target="tabContent" data-rn-tab-content="environment" style="display: none">
|
|
215
|
+
<% if @exception.environment.present? %>
|
|
216
|
+
<div class="rn-detail-section">
|
|
217
|
+
<div class="rn-detail-section-header">
|
|
218
|
+
<h3>Environment Variables</h3>
|
|
219
|
+
</div>
|
|
220
|
+
<div class="rn-detail-code" style="max-height: 32rem; overflow: auto"><%= pretty_format(@exception.environment) %></div>
|
|
221
|
+
</div>
|
|
222
|
+
<% else %>
|
|
223
|
+
<div class="text-center py-8">
|
|
224
|
+
<p class="text-sm" style="color: var(--rn-text-muted)">No environment data available</p>
|
|
225
|
+
</div>
|
|
226
|
+
<% end %>
|
|
227
|
+
</div>
|
|
228
|
+
|
|
229
|
+
<%# ─── Tab: User Info ────────────────────────────────────── %>
|
|
230
|
+
<div data-rails_nexus-detail-target="tabContent" data-rn-tab-content="user_info" style="display: none">
|
|
231
|
+
<% if @exception.user_info.present? %>
|
|
232
|
+
<div class="rn-detail-section">
|
|
233
|
+
<div class="rn-detail-section-header">
|
|
234
|
+
<h3>User Information</h3>
|
|
235
|
+
</div>
|
|
236
|
+
<div class="rn-detail-code"><%= pretty_format(@exception.user_info) %></div>
|
|
237
|
+
</div>
|
|
238
|
+
<% else %>
|
|
239
|
+
<div class="text-center py-8">
|
|
240
|
+
<p class="text-sm" style="color: var(--rn-text-muted)">No user info available</p>
|
|
241
|
+
</div>
|
|
242
|
+
<% end %>
|
|
243
|
+
</div>
|
|
244
|
+
|
|
245
|
+
</div>
|
|
246
|
+
|
|
247
|
+
<%# ─── Actions Footer ──────────────────────────────────────── %>
|
|
248
|
+
<div class="flex items-center justify-between px-5 py-3" style="border-top: 1px solid var(--rn-border-light); background: var(--rn-bg-secondary)">
|
|
249
|
+
<p class="text-xs rn-mono" style="color: var(--rn-text-muted)">
|
|
250
|
+
ID: <%= @exception.id %>
|
|
251
|
+
<span style="color: var(--rn-border)">·</span>
|
|
252
|
+
<time datetime="<%= @exception.created_at.iso8601 %>"><%= @exception.created_at.iso8601 %></time>
|
|
253
|
+
</p>
|
|
254
|
+
<%= link_to logged_exception_path(@exception),
|
|
255
|
+
class: "rn-btn rn-btn-ghost rn-btn-sm",
|
|
256
|
+
style: "color: var(--rn-danger); border-color: var(--rn-danger)",
|
|
257
|
+
data: { turbo_method: :delete, turbo_confirm: t(".confirm_delete"), turbo_stream: true } do %>
|
|
258
|
+
<svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" /></svg>
|
|
259
|
+
Delete
|
|
260
|
+
<% end %>
|
|
261
|
+
</div>
|
|
262
|
+
|
|
263
|
+
<%# ─── Prev/Next Navigation ────────────────────────────────── %>
|
|
264
|
+
<% if RailsNexus.configuration.enable_navigation && (prev_exception || next_exception) %>
|
|
265
|
+
<div class="rn-nav-row">
|
|
266
|
+
<% if prev_exception %>
|
|
267
|
+
<%= link_to logged_exception_path(prev_exception.id, params_filters),
|
|
268
|
+
class: "rn-nav-link",
|
|
269
|
+
data: { turbo_frame: "exception-details", turbo_action: "advance" } do %>
|
|
270
|
+
<svg class="w-4 h-4 shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5L8.25 12l7.5-7.5" /></svg>
|
|
271
|
+
<div class="min-w-0">
|
|
272
|
+
<div class="rn-nav-link-label">Previous</div>
|
|
273
|
+
<div class="rn-nav-link-title"><%= prev_exception.id %></div>
|
|
274
|
+
</div>
|
|
275
|
+
<% end %>
|
|
276
|
+
<% else %>
|
|
277
|
+
<div></div>
|
|
278
|
+
<% end %>
|
|
279
|
+
|
|
280
|
+
<% if next_exception %>
|
|
281
|
+
<%= link_to logged_exception_path(next_exception.id, params_filters),
|
|
282
|
+
class: "rn-nav-link",
|
|
283
|
+
style: "text-align: right",
|
|
284
|
+
data: { turbo_frame: "exception-details", turbo_action: "advance" } do %>
|
|
285
|
+
<div class="min-w-0">
|
|
286
|
+
<div class="rn-nav-link-label">Next</div>
|
|
287
|
+
<div class="rn-nav-link-title"><%= next_exception.id %></div>
|
|
288
|
+
</div>
|
|
289
|
+
<svg class="w-4 h-4 shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" /></svg>
|
|
290
|
+
<% end %>
|
|
291
|
+
<% else %>
|
|
292
|
+
<div></div>
|
|
293
|
+
<% end %>
|
|
294
|
+
</div>
|
|
295
|
+
<% end %>
|
|
296
|
+
|
|
297
|
+
</div>
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
<% page_title t(".title") %>
|
|
2
|
+
|
|
3
|
+
<div class="space-y-4" data-controller="rails_nexus">
|
|
4
|
+
<%# Header with stats %>
|
|
5
|
+
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3">
|
|
6
|
+
<div>
|
|
7
|
+
<h1 class="text-xl font-bold text-gray-900 tracking-tight"><%= t(".title") %></h1>
|
|
8
|
+
<p class="mt-0.5 text-xs text-gray-400"><%= t(".subtitle") %></p>
|
|
9
|
+
</div>
|
|
10
|
+
<div class="flex items-center gap-2">
|
|
11
|
+
<span class="inline-flex items-center gap-1 px-2.5 py-1 text-xs font-medium rounded-full bg-red-50 text-red-700 border border-red-100">
|
|
12
|
+
<span class="w-1.5 h-1.5 rounded-full bg-red-500 animate-pulse"></span>
|
|
13
|
+
<%= RailsNexus::LoggedException.count %> total
|
|
14
|
+
</span>
|
|
15
|
+
<%= button_to t(".clear_history"), clear_logged_exceptions_path, method: :post,
|
|
16
|
+
class: "inline-flex items-center gap-1 px-2.5 py-1 text-xs font-medium text-gray-500 bg-white border border-gray-200 rounded-lg hover:bg-gray-50 hover:text-red-600 transition-colors",
|
|
17
|
+
form: { data: { turbo_stream: true } },
|
|
18
|
+
data: { turbo_confirm: t(".confirm_clear") } %>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<%# Filters Bar %>
|
|
23
|
+
<div class="bg-white rounded-xl border border-gray-200/60 shadow-sm">
|
|
24
|
+
<div class="px-4 py-3 border-b border-gray-100 flex items-center gap-2">
|
|
25
|
+
<svg class="w-4 h-4 text-gray-400" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 3c2.755 0 5.455.232 8.083.678.533.09.917.556.917 1.096v1.044a2.25 2.25 0 01-.659 1.591l-5.432 5.432a2.25 2.25 0 00-.659 1.591v2.927a2.25 2.25 0 01-1.244 2.013L9.75 21v-6.568a2.25 2.25 0 00-.659-1.591L3.659 7.409A2.25 2.25 0 013 5.818V4.774c0-.54.384-1.006.917-1.096A48.32 48.32 0 0112 3z" /></svg>
|
|
26
|
+
<span class="text-xs font-semibold text-gray-500 uppercase tracking-wider">Filters</span>
|
|
27
|
+
<% if filtered? %>
|
|
28
|
+
<%= link_to "Clear", logged_exceptions_path, class: "ml-auto text-xs text-indigo-600 hover:text-indigo-800 font-medium" %>
|
|
29
|
+
<% end %>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<%= search_form_for @q, url: query_logged_exceptions_path, data: { turbo_frame: "exceptions" } do |f| %>
|
|
33
|
+
<div class="px-4 py-3 flex flex-wrap items-end gap-3">
|
|
34
|
+
<%# Exception Class Select %>
|
|
35
|
+
<div class="flex-1 min-w-[160px]">
|
|
36
|
+
<label class="block text-[11px] font-medium text-gray-400 uppercase tracking-wider mb-1">Exception</label>
|
|
37
|
+
<%= f.select :exception_class_cont,
|
|
38
|
+
options_for_select(@exception_names.map { |n| [n, n] }, params.dig(:q, :exception_class_cont)),
|
|
39
|
+
{ include_blank: "All exceptions" },
|
|
40
|
+
class: "block w-full rounded-lg border-gray-200 bg-gray-50/50 text-sm py-2 pl-3 pr-8 focus:border-indigo-500 focus:bg-white focus:ring-1 focus:ring-indigo-500/20 appearance-none cursor-pointer",
|
|
41
|
+
data: { action: "change->rails_nexus#submit" } %>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<%# Controller Select %>
|
|
45
|
+
<div class="flex-1 min-w-[160px]">
|
|
46
|
+
<label class="block text-[11px] font-medium text-gray-400 uppercase tracking-wider mb-1">Controller</label>
|
|
47
|
+
<%= f.select :controller_name_cont,
|
|
48
|
+
options_for_select(@controller_actions.map { |a| [a, a] }, params.dig(:q, :controller_name_cont)),
|
|
49
|
+
{ include_blank: "All controllers" },
|
|
50
|
+
class: "block w-full rounded-lg border-gray-200 bg-gray-50/50 text-sm py-2 pl-3 pr-8 focus:border-indigo-500 focus:bg-white focus:ring-1 focus:ring-indigo-500/20 appearance-none cursor-pointer",
|
|
51
|
+
data: { action: "change->rails_nexus#submit" } %>
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<%# Time Range Select %>
|
|
55
|
+
<div class="flex-1 min-w-[160px]">
|
|
56
|
+
<label class="block text-[11px] font-medium text-gray-400 uppercase tracking-wider mb-1">Time Range</label>
|
|
57
|
+
<%= select_tag :date_ranges_filter,
|
|
58
|
+
options_for_select([
|
|
59
|
+
["All time", ""],
|
|
60
|
+
["Today", "1"],
|
|
61
|
+
["Last 3 days", "3"],
|
|
62
|
+
["Last 7 days", "7"],
|
|
63
|
+
["Last 30 days", "30"]
|
|
64
|
+
], params[:date_ranges_filter]),
|
|
65
|
+
class: "block w-full rounded-lg border-gray-200 bg-gray-50/50 text-sm py-2 pl-3 pr-8 focus:border-indigo-500 focus:bg-white focus:ring-1 focus:ring-indigo-500/20 appearance-none cursor-pointer",
|
|
66
|
+
data: { action: "change->rails_nexus#submit" } %>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<%# Message Search %>
|
|
70
|
+
<div class="flex-1 min-w-[200px]">
|
|
71
|
+
<label class="block text-[11px] font-medium text-gray-400 uppercase tracking-wider mb-1">Search</label>
|
|
72
|
+
<%= f.search_field :message_cont,
|
|
73
|
+
placeholder: "Search messages...",
|
|
74
|
+
class: "block w-full rounded-lg border-gray-200 bg-gray-50/50 text-sm py-2 px-3 focus:border-indigo-500 focus:bg-white focus:ring-1 focus:ring-indigo-500/20",
|
|
75
|
+
data: { action: "input->rails_nexus#debouncedSubmit" } %>
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
<div class="flex gap-2">
|
|
79
|
+
<%= f.submit "Apply", class: "px-4 py-2 text-sm font-medium text-white bg-indigo-600 rounded-lg hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition-colors" %>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
<% end %>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<%# Content %>
|
|
86
|
+
<div class="space-y-4">
|
|
87
|
+
<%# Activity indicator %>
|
|
88
|
+
<div id="activity" data-rails_nexus-target="activity" class="rounded-lg bg-indigo-50 border border-indigo-200 p-3 flex items-center gap-3" role="status" aria-live="polite" hidden>
|
|
89
|
+
<svg class="animate-spin h-4 w-4 text-indigo-500 shrink-0" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
90
|
+
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
91
|
+
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
92
|
+
</svg>
|
|
93
|
+
<p class="text-sm font-medium text-indigo-700"><%= t(".loading") %></p>
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<%# Exception Details Panel %>
|
|
97
|
+
<%= turbo_frame_tag "exception-details", class: "bg-white rounded-xl border border-gray-200/60 shadow-sm" do %>
|
|
98
|
+
<div class="text-center py-12">
|
|
99
|
+
<div class="w-10 h-10 rounded-full bg-gray-50 flex items-center justify-center mx-auto border border-gray-100">
|
|
100
|
+
<svg class="w-5 h-5 text-gray-300" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
|
101
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" />
|
|
102
|
+
</svg>
|
|
103
|
+
</div>
|
|
104
|
+
<p class="mt-2 text-xs text-gray-400">Select an exception to view details</p>
|
|
105
|
+
</div>
|
|
106
|
+
<% end %>
|
|
107
|
+
|
|
108
|
+
<%# Exceptions List %>
|
|
109
|
+
<%= turbo_frame_tag "exceptions" do %>
|
|
110
|
+
<%= render "exceptions" %>
|
|
111
|
+
<% end %>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/notifications"
|
|
4
|
+
|
|
5
|
+
module RailsNexus
|
|
6
|
+
# Breadcrumbs capture activity trail before a crash.
|
|
7
|
+
# Uses ActiveSupport::Notifications to subscribe to SQL, controller,
|
|
8
|
+
# cache, and other instrumentation events.
|
|
9
|
+
module Breadcrumbs
|
|
10
|
+
RING_BUFFER_SIZE = 50
|
|
11
|
+
|
|
12
|
+
class << self
|
|
13
|
+
# Thread-local ring buffer of breadcrumbs
|
|
14
|
+
def buffer
|
|
15
|
+
Thread.current[:rails_nexus_breadcrumbs] ||= []
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def buffer=(val)
|
|
19
|
+
Thread.current[:rails_nexus_breadcrumbs] = val
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Capture a breadcrumb manually
|
|
23
|
+
def add(type:, name:, payload: nil, duration: nil)
|
|
24
|
+
buffer << {
|
|
25
|
+
type: type,
|
|
26
|
+
name: name,
|
|
27
|
+
payload: payload,
|
|
28
|
+
duration: duration,
|
|
29
|
+
timestamp: Time.current.iso8601(3)
|
|
30
|
+
}
|
|
31
|
+
# Keep only last N entries
|
|
32
|
+
buffer.shift if buffer.size > RING_BUFFER_SIZE
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Flush buffer and return captured breadcrumbs
|
|
36
|
+
def flush
|
|
37
|
+
crumbs = buffer.dup
|
|
38
|
+
self.buffer = []
|
|
39
|
+
crumbs
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Start subscribing to instrumentation events
|
|
43
|
+
def subscribe!
|
|
44
|
+
return if @subscribed
|
|
45
|
+
|
|
46
|
+
# SQL queries
|
|
47
|
+
ActiveSupport::Notifications.subscribe("sql.active_record") do |_name, _start, _finish, _id, payload|
|
|
48
|
+
next if payload[:name] == "SCHEMA"
|
|
49
|
+
next if payload[:sql]&.start_with?("SET")
|
|
50
|
+
next if payload[:sql]&.start_with?("SELECT 1")
|
|
51
|
+
|
|
52
|
+
add(
|
|
53
|
+
type: "sql",
|
|
54
|
+
name: payload[:sql]&.truncate(200),
|
|
55
|
+
payload: {
|
|
56
|
+
connection_id: payload[:connection_id],
|
|
57
|
+
cached: payload[:cached]
|
|
58
|
+
},
|
|
59
|
+
duration: payload[:runtime]
|
|
60
|
+
)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Controller action
|
|
64
|
+
ActiveSupport::Notifications.subscribe("process_action.action_controller") do |_name, _start, _finish, _id, payload|
|
|
65
|
+
add(
|
|
66
|
+
type: "controller",
|
|
67
|
+
name: "#{payload[:controller]}##{payload[:action]}",
|
|
68
|
+
payload: {
|
|
69
|
+
format: payload[:format],
|
|
70
|
+
status: payload[:status],
|
|
71
|
+
view_runtime: payload[:view_runtime]&.round(2),
|
|
72
|
+
db_runtime: payload[:db_runtime]&.round(2)
|
|
73
|
+
},
|
|
74
|
+
duration: payload[:runtime]&.round(2)
|
|
75
|
+
)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Cache reads/writes
|
|
79
|
+
ActiveSupport::Notifications.subscribe("cache_read.active_support") do |_name, _start, _finish, _id, payload|
|
|
80
|
+
next unless payload[:hit] # Only log cache hits
|
|
81
|
+
|
|
82
|
+
add(
|
|
83
|
+
type: "cache",
|
|
84
|
+
name: "Cache read: #{payload[:key]}",
|
|
85
|
+
payload: { hit: true, super: payload[:super] }
|
|
86
|
+
)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
ActiveSupport::Notifications.subscribe("cache_write.active_support") do |_name, _start, _finish, _id, payload|
|
|
90
|
+
add(
|
|
91
|
+
type: "cache",
|
|
92
|
+
name: "Cache write: #{payload[:key]}",
|
|
93
|
+
payload: { hit: false }
|
|
94
|
+
)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Active Job
|
|
98
|
+
ActiveSupport::Notifications.subscribe("perform_start.active_job") do |_name, _start, _finish, _id, payload|
|
|
99
|
+
add(
|
|
100
|
+
type: "job",
|
|
101
|
+
name: "Job started: #{payload[:job_class]}",
|
|
102
|
+
payload: {
|
|
103
|
+
job_id: payload[:job_id],
|
|
104
|
+
queue: payload[:queue_name],
|
|
105
|
+
arguments: payload[:arguments]&.first(200)
|
|
106
|
+
}
|
|
107
|
+
)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
ActiveSupport::Notifications.subscribe("perform.active_job") do |_name, _start, _finish, _id, payload|
|
|
111
|
+
add(
|
|
112
|
+
type: "job",
|
|
113
|
+
name: "Job completed: #{payload[:job_class]}",
|
|
114
|
+
payload: {
|
|
115
|
+
job_id: payload[:job_id],
|
|
116
|
+
queue: payload[:queue_name],
|
|
117
|
+
error: payload[:exception_object]&.message
|
|
118
|
+
},
|
|
119
|
+
duration: payload[:runtime]&.round(2)
|
|
120
|
+
)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Action Mailer
|
|
124
|
+
ActiveSupport::Notifications.subscribe("deliver.action_mailer") do |_name, _start, _finish, _id, payload|
|
|
125
|
+
add(
|
|
126
|
+
type: "mailer",
|
|
127
|
+
name: "Mail delivered: #{payload[:mailer]}##{payload[:message_id]}",
|
|
128
|
+
payload: {
|
|
129
|
+
mailer: payload[:mailer],
|
|
130
|
+
message_id: payload[:message_id]
|
|
131
|
+
}
|
|
132
|
+
)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Render partials
|
|
136
|
+
ActiveSupport::Notifications.subscribe("render_partial.action_view") do |_name, _start, _finish, _id, payload|
|
|
137
|
+
add(
|
|
138
|
+
type: "render",
|
|
139
|
+
name: "Render: #{payload[:identifier]}",
|
|
140
|
+
payload: {
|
|
141
|
+
cache_hits: payload[:cache_hit]
|
|
142
|
+
},
|
|
143
|
+
duration: payload[:duration]&.round(2)
|
|
144
|
+
)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
@subscribed = true
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def subscribed?
|
|
151
|
+
@subscribed
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Reset state (for testing)
|
|
155
|
+
def reset!
|
|
156
|
+
self.buffer = []
|
|
157
|
+
@subscribed = false
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsNexus
|
|
4
|
+
module Cleanup
|
|
5
|
+
class << self
|
|
6
|
+
# Delete exceptions older than the configured retention period.
|
|
7
|
+
# Call this from a cron job, Sidekiq scheduler, or Rails runner.
|
|
8
|
+
#
|
|
9
|
+
# Usage:
|
|
10
|
+
# bin/rails runner "RailsNexus::Cleanup.run"
|
|
11
|
+
#
|
|
12
|
+
# Or schedule with sidekiq-cron / whenever / good_job:
|
|
13
|
+
# RailsNexus::Cleanup.run
|
|
14
|
+
def run
|
|
15
|
+
config = RailsNexus.configuration
|
|
16
|
+
return unless config.retention_days.present?
|
|
17
|
+
|
|
18
|
+
cutoff = config.retention_days.days.ago
|
|
19
|
+
count = RailsNexus::LoggedException.where("created_at < ?", cutoff).delete_all
|
|
20
|
+
|
|
21
|
+
Rails.logger.info("[RailsNexus] Cleaned up #{count} exceptions older than #{config.retention_days} days") if count > 0
|
|
22
|
+
count
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Generate a rake task for cleanup.
|
|
26
|
+
# Adds `rake rails_nexus:cleanup` to your app.
|
|
27
|
+
def self.install_rake_task
|
|
28
|
+
# This is called automatically by the engine
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|