rails_nexus 2.0.0 → 2.0.2
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/app/controllers/rails_nexus/backup_controller.rb +74 -53
- data/app/models/rails_nexus/backup_config.rb +90 -0
- data/app/services/rails_nexus/backup_runner.rb +62 -0
- data/app/services/rails_nexus/backup_service.rb +215 -214
- data/app/views/layouts/rails_nexus/application.html.erb +8 -0
- data/app/views/rails_nexus/backup/_form.html.erb +185 -0
- data/app/views/rails_nexus/backup/config_history.html.erb +67 -0
- data/app/views/rails_nexus/backup/edit.html.erb +15 -0
- data/app/views/rails_nexus/backup/history.html.erb +60 -0
- data/app/views/rails_nexus/backup/index.html.erb +91 -147
- data/app/views/rails_nexus/backup/new.html.erb +15 -0
- data/config/routes.rb +9 -6
- data/lib/generators/rails_nexus/install_generator.rb +1 -13
- data/lib/generators/rails_nexus/templates/migration.rb +119 -11
- data/lib/generators/rails_nexus/templates/rails_nexus.rb +1 -1
- data/lib/rails_nexus/version.rb +1 -1
- data/lib/tasks/rails_nexus.rake +71 -0
- metadata +8 -16
- data/app/models/rails_nexus/database_stat.rb +0 -128
- data/app/models/rails_nexus/event.rb +0 -101
- data/app/models/rails_nexus/metric.rb +0 -107
- data/app/models/rails_nexus/nginx_metric.rb +0 -112
- data/app/models/rails_nexus/server_metric.rb +0 -168
- data/app/views/rails_nexus/backup/files.html.erb +0 -53
- data/app/views/rails_nexus/backup/settings.html.erb +0 -158
- data/config/initializers/rails_nexus.rb +0 -18
- data/db/migrate/20240330122311_create_rails_nexus_logged_exceptions.rb +0 -22
- data/lib/generators/rails_nexus/templates/migration_advanced_features.rb +0 -30
- data/lib/generators/rails_nexus/templates/migration_cron_jobs.rb +0 -22
- data/lib/generators/rails_nexus/templates/migration_new_schema.rb +0 -120
- data/lib/generators/rails_nexus/templates/migration_platform_detection.rb +0 -11
- data/lib/generators/rails_nexus/templates/migration_webhook_deliveries.rb +0 -21
- data/lib/generators/rails_nexus/templates/migration_workflow.rb +0 -29
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<% page_title "New Backup Config" %>
|
|
2
|
+
|
|
3
|
+
<div class="space-y-6">
|
|
4
|
+
<nav class="rn-breadcrumbs" aria-label="Breadcrumb">
|
|
5
|
+
<a href="<%= root_path %>" class="rn-breadcrumb">Dashboard</a>
|
|
6
|
+
<span class="rn-breadcrumb-sep">/</span>
|
|
7
|
+
<a href="<%= backup_path %>" class="rn-breadcrumb">Backups</a>
|
|
8
|
+
<span class="rn-breadcrumb-sep">/</span>
|
|
9
|
+
<span class="rn-breadcrumb-current">New Config</span>
|
|
10
|
+
</nav>
|
|
11
|
+
|
|
12
|
+
<h1 class="text-2xl font-bold" style="color: var(--rn-text)">New Backup Configuration</h1>
|
|
13
|
+
|
|
14
|
+
<%= render "form" %>
|
|
15
|
+
</div>
|
data/config/routes.rb
CHANGED
|
@@ -42,12 +42,15 @@ RailsNexus::Engine.routes.draw do
|
|
|
42
42
|
get "n1_patterns", to: "n1_patterns#index", as: :n1_patterns
|
|
43
43
|
|
|
44
44
|
# Backup management
|
|
45
|
-
get
|
|
46
|
-
get
|
|
47
|
-
post "backup
|
|
48
|
-
get
|
|
49
|
-
get
|
|
50
|
-
patch "backup
|
|
45
|
+
get "backup", to: "backup#index", as: :backup
|
|
46
|
+
get "backup/new", to: "backup#new", as: :new_backup
|
|
47
|
+
post "backup", to: "backup#create"
|
|
48
|
+
get "backup/history", to: "backup#all_history", as: :backup_history
|
|
49
|
+
get "backup/:id/edit", to: "backup#edit", as: :edit_backup
|
|
50
|
+
patch "backup/:id", to: "backup#update"
|
|
51
|
+
delete "backup/:id", to: "backup#destroy", as: :backup_destroy
|
|
52
|
+
post "backup/:id/trigger", to: "backup#trigger", as: :backup_trigger
|
|
53
|
+
get "backup/:id/history", to: "backup#history", as: :backup_config_history
|
|
51
54
|
|
|
52
55
|
# Settings and webhook management
|
|
53
56
|
get "settings", to: "settings#index", as: :settings
|
|
@@ -8,16 +8,6 @@ module RailsNexus
|
|
|
8
8
|
class InstallGenerator < Rails::Generators::Base
|
|
9
9
|
include Rails::Generators::Migration
|
|
10
10
|
|
|
11
|
-
MIGRATIONS = {
|
|
12
|
-
"migration.rb" => "create_rails_nexus_exceptions.rb",
|
|
13
|
-
"migration_advanced_features.rb" => "add_advanced_features_to_rails_nexus_exceptions.rb",
|
|
14
|
-
"migration_platform_detection.rb" => "add_platform_to_rails_nexus_exceptions.rb",
|
|
15
|
-
"migration_workflow.rb" => "add_workflow_to_rails_nexus.rb",
|
|
16
|
-
"migration_cron_jobs.rb" => "create_rails_nexus_cron_jobs.rb",
|
|
17
|
-
"migration_webhook_deliveries.rb" => "create_rails_nexus_webhook_deliveries.rb",
|
|
18
|
-
"migration_new_schema.rb" => "create_rails_nexus_metrics_tables.rb"
|
|
19
|
-
}.freeze
|
|
20
|
-
|
|
21
11
|
source_root File.expand_path("templates", __dir__)
|
|
22
12
|
desc "Install RailsNexus into your Rails application"
|
|
23
13
|
|
|
@@ -26,9 +16,7 @@ module RailsNexus
|
|
|
26
16
|
end
|
|
27
17
|
|
|
28
18
|
def create_migration_files
|
|
29
|
-
|
|
30
|
-
migration_template source, "db/migrate/#{destination}"
|
|
31
|
-
end
|
|
19
|
+
migration_template "migration.rb", "db/migrate/create_rails_nexus_tables.rb"
|
|
32
20
|
end
|
|
33
21
|
|
|
34
22
|
def create_initializer
|
|
@@ -1,18 +1,50 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
class
|
|
3
|
+
class CreateRailsNexusTables < ActiveRecord::Migration[<%= "[#{ActiveRecord::Migration.current_version}]" %>]
|
|
4
4
|
def change
|
|
5
|
+
# ─── rails_nexus_exceptions ──────────────────────────────────────
|
|
5
6
|
create_table :rails_nexus_exceptions do |t|
|
|
6
|
-
|
|
7
|
-
t.string
|
|
8
|
-
t.string
|
|
9
|
-
t.
|
|
10
|
-
t.text
|
|
11
|
-
t.text
|
|
12
|
-
t.text
|
|
13
|
-
t.
|
|
14
|
-
t.string
|
|
15
|
-
t.string
|
|
7
|
+
# Core exception data
|
|
8
|
+
t.string :exception_class
|
|
9
|
+
t.string :controller_name
|
|
10
|
+
t.string :action_name
|
|
11
|
+
t.text :message
|
|
12
|
+
t.text :backtrace
|
|
13
|
+
t.text :environment
|
|
14
|
+
t.text :request
|
|
15
|
+
t.string :user_info
|
|
16
|
+
t.string :user_agent
|
|
17
|
+
t.string :remote_ip
|
|
18
|
+
|
|
19
|
+
# Fingerprinting & deduplication
|
|
20
|
+
t.string :fingerprint
|
|
21
|
+
t.integer :occurrence_count, default: 1
|
|
22
|
+
|
|
23
|
+
# Platform detection
|
|
24
|
+
t.string :platform, default: "web"
|
|
25
|
+
t.string :platform_version
|
|
26
|
+
t.string :device_type
|
|
27
|
+
t.string :app_version
|
|
28
|
+
|
|
29
|
+
# Advanced features
|
|
30
|
+
t.text :cause_chain
|
|
31
|
+
t.text :breadcrumbs
|
|
32
|
+
t.text :system_health
|
|
33
|
+
t.text :local_variables
|
|
34
|
+
t.text :instance_variables
|
|
35
|
+
|
|
36
|
+
# User impact tracking
|
|
37
|
+
t.string :user_id
|
|
38
|
+
t.string :user_type
|
|
39
|
+
|
|
40
|
+
# Workflow management
|
|
41
|
+
t.string :priority
|
|
42
|
+
t.string :assigned_to
|
|
43
|
+
t.datetime :assigned_at
|
|
44
|
+
t.datetime :snoozed_until
|
|
45
|
+
t.boolean :muted, default: false
|
|
46
|
+
t.datetime :muted_at
|
|
47
|
+
t.integer :comments_count, default: 0
|
|
16
48
|
|
|
17
49
|
t.timestamps
|
|
18
50
|
end
|
|
@@ -20,5 +52,81 @@ class CreateRailsNexusLoggedExceptions < ActiveRecord::Migration<%= "[#{ActiveRe
|
|
|
20
52
|
add_index :rails_nexus_exceptions, :created_at
|
|
21
53
|
add_index :rails_nexus_exceptions, :exception_class
|
|
22
54
|
add_index :rails_nexus_exceptions, [:controller_name, :action_name]
|
|
55
|
+
add_index :rails_nexus_exceptions, :fingerprint
|
|
56
|
+
add_index :rails_nexus_exceptions, :occurrence_count
|
|
57
|
+
add_index :rails_nexus_exceptions, :platform
|
|
58
|
+
add_index :rails_nexus_exceptions, [:platform, :exception_class]
|
|
59
|
+
add_index :rails_nexus_exceptions, :priority
|
|
60
|
+
add_index :rails_nexus_exceptions, :assigned_to
|
|
61
|
+
add_index :rails_nexus_exceptions, :muted
|
|
62
|
+
add_index :rails_nexus_exceptions, :user_id
|
|
63
|
+
|
|
64
|
+
# ─── rails_nexus_comments ────────────────────────────────────────
|
|
65
|
+
create_table :rails_nexus_comments do |t|
|
|
66
|
+
t.references :logged_exception, null: false, foreign_key: { to_table: :rails_nexus_exceptions }
|
|
67
|
+
t.string :author, null: false
|
|
68
|
+
t.text :body, null: false
|
|
69
|
+
t.string :comment_type, default: "comment"
|
|
70
|
+
|
|
71
|
+
t.timestamps
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
add_index :rails_nexus_comments, [:logged_exception_id, :created_at]
|
|
75
|
+
|
|
76
|
+
# ─── rails_nexus_cron_jobs ───────────────────────────────────────
|
|
77
|
+
create_table :rails_nexus_cron_jobs do |t|
|
|
78
|
+
t.string :name, null: false
|
|
79
|
+
t.string :status, null: false, default: "pending"
|
|
80
|
+
t.text :output
|
|
81
|
+
t.text :error_message
|
|
82
|
+
t.float :duration
|
|
83
|
+
t.string :hostname
|
|
84
|
+
t.json :metadata
|
|
85
|
+
t.datetime :started_at
|
|
86
|
+
t.datetime :finished_at
|
|
87
|
+
|
|
88
|
+
t.timestamps
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
add_index :rails_nexus_cron_jobs, :name
|
|
92
|
+
add_index :rails_nexus_cron_jobs, :status
|
|
93
|
+
add_index :rails_nexus_cron_jobs, :created_at
|
|
94
|
+
add_index :rails_nexus_cron_jobs, :started_at
|
|
95
|
+
|
|
96
|
+
# ─── rails_nexus_webhook_deliveries ──────────────────────────────
|
|
97
|
+
create_table :rails_nexus_webhook_deliveries do |t|
|
|
98
|
+
t.string :url, null: false
|
|
99
|
+
t.string :status, null: false, default: "pending"
|
|
100
|
+
t.integer :response_code
|
|
101
|
+
t.text :request_body
|
|
102
|
+
t.text :response_body
|
|
103
|
+
t.float :duration
|
|
104
|
+
t.text :error_message
|
|
105
|
+
t.string :event_type
|
|
106
|
+
t.json :metadata
|
|
107
|
+
|
|
108
|
+
t.timestamps
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
add_index :rails_nexus_webhook_deliveries, :status
|
|
112
|
+
add_index :rails_nexus_webhook_deliveries, :created_at
|
|
113
|
+
add_index :rails_nexus_webhook_deliveries, :url
|
|
114
|
+
|
|
115
|
+
# ─── rails_nexus_backups ─────────────────────────────────────────
|
|
116
|
+
create_table :rails_nexus_backups do |t|
|
|
117
|
+
t.string :model_name, null: false
|
|
118
|
+
t.string :status, null: false
|
|
119
|
+
t.text :file_path
|
|
120
|
+
t.bigint :file_size
|
|
121
|
+
t.float :duration
|
|
122
|
+
t.text :error_message
|
|
123
|
+
t.string :triggered_by
|
|
124
|
+
t.datetime :started_at, null: false
|
|
125
|
+
t.datetime :completed_at
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
add_index :rails_nexus_backups, [:model_name, :started_at]
|
|
129
|
+
add_index :rails_nexus_backups, :status
|
|
130
|
+
add_index :rails_nexus_backups, :started_at
|
|
23
131
|
end
|
|
24
132
|
end
|
|
@@ -12,7 +12,7 @@ Rails.application.config.to_prepare do
|
|
|
12
12
|
# ─── Authentication ──────────────────────────────────────
|
|
13
13
|
# Protect the dashboard — only authorized users can access
|
|
14
14
|
# config.auth_block = lambda do |controller|
|
|
15
|
-
# controller.current_user&.admin?
|
|
15
|
+
# controller.current_user&.admin? # adjust to your auth model
|
|
16
16
|
# end
|
|
17
17
|
|
|
18
18
|
# ─── Exception Data ──────────────────────────────────────
|
data/lib/rails_nexus/version.rb
CHANGED
data/lib/tasks/rails_nexus.rake
CHANGED
|
@@ -227,4 +227,75 @@ namespace :rails_nexus do
|
|
|
227
227
|
count = RailsNexus::WebhookDelivery.where("created_at < ?", days.days.ago).delete_all
|
|
228
228
|
puts "✓ Deleted #{count} webhook delivery records older than #{days} days"
|
|
229
229
|
end
|
|
230
|
+
|
|
231
|
+
# ─── Backup Tasks ──────────────────────────────────────────────
|
|
232
|
+
|
|
233
|
+
desc "Run a specific backup by name: rails_nexus:backup[daily_backup]"
|
|
234
|
+
task :backup, [:name] => :environment do |_t, args|
|
|
235
|
+
name = args[:name]
|
|
236
|
+
if name.blank?
|
|
237
|
+
puts "Usage: rake rails_nexus:backup[config_name]"
|
|
238
|
+
puts "Available configs:"
|
|
239
|
+
RailsNexus::BackupConfig.enabled.pluck(:name).each { |n| puts " - #{n}" }
|
|
240
|
+
next
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
result = RailsNexus::BackupRunner.run(name)
|
|
244
|
+
if result[:success]
|
|
245
|
+
puts "✓ Backup "#{name}" completed successfully (#{result[:record].duration_human})"
|
|
246
|
+
else
|
|
247
|
+
puts "✗ Backup "#{name}" failed: #{result[:error]}"
|
|
248
|
+
exit 1
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
desc "Run all enabled backup configs"
|
|
253
|
+
task backup_all: :environment do
|
|
254
|
+
results = RailsNexus::BackupRunner.run_all
|
|
255
|
+
results.each do |r|
|
|
256
|
+
if r[:success]
|
|
257
|
+
puts "✓ #{r[:record].model_name} completed"
|
|
258
|
+
else
|
|
259
|
+
puts "✗ #{r[:record]&.model_name || '?'} failed: #{r[:error]}"
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
desc "Run backups matching current cron schedule"
|
|
265
|
+
task backup_scheduled: :environment do
|
|
266
|
+
results = RailsNexus::BackupRunner.run_scheduled
|
|
267
|
+
if results.empty?
|
|
268
|
+
puts "No backups scheduled for this time"
|
|
269
|
+
else
|
|
270
|
+
results.each do |r|
|
|
271
|
+
if r[:success]
|
|
272
|
+
puts "✓ #{r[:record].model_name} completed"
|
|
273
|
+
else
|
|
274
|
+
puts "✗ #{r[:record]&.model_name || '?'} failed: #{r[:error]}"
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
desc "Clean up old backup records and files (default: 30 days)"
|
|
281
|
+
task :backup_cleanup, [:days] => :environment do |_t, args|
|
|
282
|
+
days = (args[:days] || 30).to_i
|
|
283
|
+
count = RailsNexus::Backup.where("started_at < ?", days.days.ago).delete_all
|
|
284
|
+
puts "✓ Deleted #{count} backup records older than #{days} days"
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
desc "List all backup configs and their status"
|
|
288
|
+
task backup_list: :environment do
|
|
289
|
+
configs = RailsNexus::BackupConfig.order(:name)
|
|
290
|
+
if configs.empty?
|
|
291
|
+
puts "No backup configs found"
|
|
292
|
+
else
|
|
293
|
+
configs.each do |c|
|
|
294
|
+
status = c.enabled? ? "✓" : "✗"
|
|
295
|
+
last = c.recent_backups(limit: 1).first
|
|
296
|
+
last_str = last ? "#{last.status} #{last.started_at&.strftime('%b %d %H:%M')}" : "never"
|
|
297
|
+
puts "#{status} #{c.name.ljust(20)} #{c.adapter.ljust(12)} #{c.database_name.ljust(20)} last: #{last_str}"
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
end
|
|
230
301
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rails_nexus
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.0.
|
|
4
|
+
version: 2.0.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tamiru Hailu
|
|
@@ -139,23 +139,23 @@ files:
|
|
|
139
139
|
- app/mailers/rails_nexus/application_mailer.rb
|
|
140
140
|
- app/models/rails_nexus/application_record.rb
|
|
141
141
|
- app/models/rails_nexus/backup.rb
|
|
142
|
+
- app/models/rails_nexus/backup_config.rb
|
|
142
143
|
- app/models/rails_nexus/base_record.rb
|
|
143
144
|
- app/models/rails_nexus/comment.rb
|
|
144
145
|
- app/models/rails_nexus/cron_job.rb
|
|
145
|
-
- app/models/rails_nexus/database_stat.rb
|
|
146
|
-
- app/models/rails_nexus/event.rb
|
|
147
146
|
- app/models/rails_nexus/logged_exception.rb
|
|
148
147
|
- app/models/rails_nexus/logged_exception_n1.rb
|
|
149
|
-
- app/models/rails_nexus/metric.rb
|
|
150
|
-
- app/models/rails_nexus/nginx_metric.rb
|
|
151
|
-
- app/models/rails_nexus/server_metric.rb
|
|
152
148
|
- app/models/rails_nexus/webhook_delivery.rb
|
|
149
|
+
- app/services/rails_nexus/backup_runner.rb
|
|
153
150
|
- app/services/rails_nexus/backup_service.rb
|
|
154
151
|
- app/views/layouts/rails_nexus/application.html.erb
|
|
155
152
|
- app/views/rails_nexus/analytics/index.html.erb
|
|
156
|
-
- app/views/rails_nexus/backup/
|
|
153
|
+
- app/views/rails_nexus/backup/_form.html.erb
|
|
154
|
+
- app/views/rails_nexus/backup/config_history.html.erb
|
|
155
|
+
- app/views/rails_nexus/backup/edit.html.erb
|
|
156
|
+
- app/views/rails_nexus/backup/history.html.erb
|
|
157
157
|
- app/views/rails_nexus/backup/index.html.erb
|
|
158
|
-
- app/views/rails_nexus/backup/
|
|
158
|
+
- app/views/rails_nexus/backup/new.html.erb
|
|
159
159
|
- app/views/rails_nexus/cron_jobs/index.html.erb
|
|
160
160
|
- app/views/rails_nexus/database_health/index.html.erb
|
|
161
161
|
- app/views/rails_nexus/logged_exceptions/_comments_list.html.erb
|
|
@@ -178,10 +178,8 @@ files:
|
|
|
178
178
|
- app/views/rails_nexus/stats/index.html.erb
|
|
179
179
|
- config/importmap.rb
|
|
180
180
|
- config/initializers/date_formats.rb
|
|
181
|
-
- config/initializers/rails_nexus.rb
|
|
182
181
|
- config/locales/en.yml
|
|
183
182
|
- config/routes.rb
|
|
184
|
-
- db/migrate/20240330122311_create_rails_nexus_logged_exceptions.rb
|
|
185
183
|
- lib/generators/rails_nexus/backup_generator.rb
|
|
186
184
|
- lib/generators/rails_nexus/customize_generator.rb
|
|
187
185
|
- lib/generators/rails_nexus/install_generator.rb
|
|
@@ -198,12 +196,6 @@ files:
|
|
|
198
196
|
- lib/generators/rails_nexus/templates/javascript/controllers/rails_nexus_sidebar_controller.js
|
|
199
197
|
- lib/generators/rails_nexus/templates/javascript/controllers/rails_nexus_theme_controller.js
|
|
200
198
|
- lib/generators/rails_nexus/templates/migration.rb
|
|
201
|
-
- lib/generators/rails_nexus/templates/migration_advanced_features.rb
|
|
202
|
-
- lib/generators/rails_nexus/templates/migration_cron_jobs.rb
|
|
203
|
-
- lib/generators/rails_nexus/templates/migration_new_schema.rb
|
|
204
|
-
- lib/generators/rails_nexus/templates/migration_platform_detection.rb
|
|
205
|
-
- lib/generators/rails_nexus/templates/migration_webhook_deliveries.rb
|
|
206
|
-
- lib/generators/rails_nexus/templates/migration_workflow.rb
|
|
207
199
|
- lib/generators/rails_nexus/templates/rails_nexus.rb
|
|
208
200
|
- lib/generators/rails_nexus/templates/views/layouts/rails_nexus/application.html.erb
|
|
209
201
|
- lib/generators/rails_nexus/templates/views/rails_nexus/logged_exceptions/_exceptions.html.erb
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module RailsNexus
|
|
4
|
-
class DatabaseStat < BaseRecord
|
|
5
|
-
self.table_name = "rails_nexus_database_stats"
|
|
6
|
-
|
|
7
|
-
validates :recorded_at, presence: true
|
|
8
|
-
|
|
9
|
-
scope :recent, ->(hours = 24) { where("recorded_at >= ?", hours.hours.ago) }
|
|
10
|
-
scope :with_tables, -> { where.not(table_name: nil) }
|
|
11
|
-
scope :pool_stats, -> { where(table_name: nil) }
|
|
12
|
-
|
|
13
|
-
# Collect database health snapshot
|
|
14
|
-
def self.collect!
|
|
15
|
-
now = Time.current
|
|
16
|
-
adapter = ActiveRecord::Base.connection.adapter_name.downcase
|
|
17
|
-
|
|
18
|
-
# Connection pool stats
|
|
19
|
-
pool = ActiveRecord::Base.connection_pool
|
|
20
|
-
stats = pool_stats(pool)
|
|
21
|
-
create!(stats.merge(recorded_at: now))
|
|
22
|
-
|
|
23
|
-
# Table stats (MySQL only)
|
|
24
|
-
if adapter.include?("mysql")
|
|
25
|
-
collect_mysql_stats(now)
|
|
26
|
-
elsif adapter.include?("postgresql")
|
|
27
|
-
collect_postgresql_stats(now)
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
# Get pool utilization over time
|
|
32
|
-
def self.pool_utilization(hours: 24)
|
|
33
|
-
pool_stats
|
|
34
|
-
.where("recorded_at >= ?", hours.hours.ago)
|
|
35
|
-
.order(:recorded_at)
|
|
36
|
-
.map { |s| { time: s.recorded_at, utilization: s.utilization, busy: s.busy, idle: s.idle } }
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
# Get slow queries
|
|
40
|
-
def self.slow_queries(limit: 10)
|
|
41
|
-
where.not(slow_queries: nil)
|
|
42
|
-
.order(recorded_at: :desc)
|
|
43
|
-
.limit(1)
|
|
44
|
-
.flat_map(&:slow_queries)
|
|
45
|
-
.first(limit)
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
# Get N+1 patterns
|
|
49
|
-
def self.n1_patterns
|
|
50
|
-
where.not(n1_patterns: nil)
|
|
51
|
-
.order(recorded_at: :desc)
|
|
52
|
-
.limit(1)
|
|
53
|
-
.flat_map(&:n1_patterns)
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
private
|
|
57
|
-
|
|
58
|
-
def self.pool_stats(pool)
|
|
59
|
-
{
|
|
60
|
-
pool_size: pool.size,
|
|
61
|
-
busy: pool.connections.size,
|
|
62
|
-
idle: pool.size - pool.connections.size,
|
|
63
|
-
dead: 0,
|
|
64
|
-
waiting: pool.stat[:waiting] || 0,
|
|
65
|
-
utilization: pool.size > 0 ? (pool.connections.size.to_f / pool.size * 100).round(1) : 0
|
|
66
|
-
}
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
def self.collect_mysql_stats(now)
|
|
70
|
-
conn = ActiveRecord::Base.connection
|
|
71
|
-
tables = conn.execute(<<~SQL)
|
|
72
|
-
SELECT table_name, table_rows, data_length, index_length, data_free
|
|
73
|
-
FROM information_schema.tables
|
|
74
|
-
WHERE table_schema = DATABASE()
|
|
75
|
-
ORDER BY data_length + index_length DESC
|
|
76
|
-
LIMIT 50
|
|
77
|
-
SQL
|
|
78
|
-
|
|
79
|
-
tables.each do |row|
|
|
80
|
-
create!(
|
|
81
|
-
table_name: row["TABLE_NAME"],
|
|
82
|
-
table_rows: row["TABLE_ROWS"],
|
|
83
|
-
table_size: format_size(row["DATA_LENGTH"]),
|
|
84
|
-
index_size: format_size(row["INDEX_LENGTH"]),
|
|
85
|
-
data_free: format_size(row["DATA_FREE"]),
|
|
86
|
-
recorded_at: now
|
|
87
|
-
)
|
|
88
|
-
end
|
|
89
|
-
rescue StandardError => e
|
|
90
|
-
Rails.logger.error("[RailsNexus] Failed to collect MySQL stats: #{e.message}") if defined?(Rails)
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
def self.collect_postgresql_stats(now)
|
|
94
|
-
conn = ActiveRecord::Base.connection
|
|
95
|
-
tables = conn.execute(<<~SQL)
|
|
96
|
-
SELECT schemaname, relname, n_tup_ins, n_tup_upd, n_tup_del,
|
|
97
|
-
pg_size_pretty(pg_total_relation_size(relid)) as total_size,
|
|
98
|
-
pg_size_pretty(pg_indexes_size(relid)) as index_size
|
|
99
|
-
FROM pg_stat_user_tables
|
|
100
|
-
ORDER BY pg_total_relation_size(relid) DESC
|
|
101
|
-
LIMIT 50
|
|
102
|
-
SQL
|
|
103
|
-
|
|
104
|
-
tables.each do |row|
|
|
105
|
-
create!(
|
|
106
|
-
table_name: row["relname"],
|
|
107
|
-
table_rows: (row["n_tup_ins"].to_i + row["n_tup_upd"].to_i - row["n_tup_del"].to_i),
|
|
108
|
-
table_size: row["total_size"],
|
|
109
|
-
index_size: row["index_size"],
|
|
110
|
-
recorded_at: now
|
|
111
|
-
)
|
|
112
|
-
end
|
|
113
|
-
rescue StandardError => e
|
|
114
|
-
Rails.logger.error("[RailsNexus] Failed to collect PostgreSQL stats: #{e.message}") if defined?(Rails)
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
def self.format_size(bytes)
|
|
118
|
-
return "0 B" unless bytes
|
|
119
|
-
units = ["B", "KB", "MB", "GB", "TB"]
|
|
120
|
-
size = bytes.to_f
|
|
121
|
-
units.each do |unit|
|
|
122
|
-
return "#{size.round(1)} #{unit}" if size < 1024
|
|
123
|
-
size /= 1024
|
|
124
|
-
end
|
|
125
|
-
"#{size.round(1)} PB"
|
|
126
|
-
end
|
|
127
|
-
end
|
|
128
|
-
end
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module RailsNexus
|
|
4
|
-
class Event < BaseRecord
|
|
5
|
-
self.table_name = "rails_nexus_events"
|
|
6
|
-
|
|
7
|
-
belongs_to :eventable, polymorphic: true, optional: true
|
|
8
|
-
|
|
9
|
-
validates :event_type, presence: true, inclusion: { in: %w[
|
|
10
|
-
breadcrumb storm_start storm_end
|
|
11
|
-
assignment priority_change snooze unsnooze
|
|
12
|
-
mute unmute comment note status_change
|
|
13
|
-
backup_start backup_complete backup_failed
|
|
14
|
-
exception_created exception_resolved
|
|
15
|
-
] }
|
|
16
|
-
|
|
17
|
-
validates :created_at, presence: true
|
|
18
|
-
|
|
19
|
-
scope :recent, ->(hours = 24) { where("created_at >= ?", hours.hours.ago) }
|
|
20
|
-
scope :by_type, ->(type) { where(event_type: type) }
|
|
21
|
-
scope :breadcrumbs, -> { by_type("breadcrumb") }
|
|
22
|
-
scope :workflow_events, -> { where(event_type: %w[assignment priority_change snooze unsnooze mute unmute comment note]) }
|
|
23
|
-
scope :storm_events, -> { where(event_type: %w[storm_start storm_end]) }
|
|
24
|
-
scope :audit_trail, ->(eventable) { where(eventable: eventable).order(created_at: :desc) }
|
|
25
|
-
|
|
26
|
-
# Record a breadcrumb
|
|
27
|
-
def self.breadcrumb!(message:, metadata: {}, author: nil)
|
|
28
|
-
create!(
|
|
29
|
-
event_type: "breadcrumb",
|
|
30
|
-
message: message,
|
|
31
|
-
metadata: metadata,
|
|
32
|
-
author: author,
|
|
33
|
-
created_at: Time.current
|
|
34
|
-
)
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
# Record a storm event
|
|
38
|
-
def self.storm_start!(threshold:, current_rate:)
|
|
39
|
-
create!(
|
|
40
|
-
event_type: "storm_start",
|
|
41
|
-
message: "Error storm detected: #{current_rate} errors/sec (threshold: #{threshold}/sec)",
|
|
42
|
-
metadata: { threshold: threshold, current_rate: current_rate },
|
|
43
|
-
created_at: Time.current
|
|
44
|
-
)
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def self.storm_end!(duration_seconds:)
|
|
48
|
-
create!(
|
|
49
|
-
event_type: "storm_end",
|
|
50
|
-
message: "Error storm ended after #{duration_seconds.round(1)}s",
|
|
51
|
-
metadata: { duration_seconds: duration_seconds },
|
|
52
|
-
created_at: Time.current
|
|
53
|
-
)
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
# Record a workflow event
|
|
57
|
-
def self.workflow!(event_type:, eventable:, message:, author: nil, metadata: {})
|
|
58
|
-
create!(
|
|
59
|
-
event_type: event_type,
|
|
60
|
-
eventable: eventable,
|
|
61
|
-
message: message,
|
|
62
|
-
author: author,
|
|
63
|
-
metadata: metadata,
|
|
64
|
-
created_at: Time.current
|
|
65
|
-
)
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
# Get event timeline for an exception
|
|
69
|
-
def self.timeline_for(exception)
|
|
70
|
-
where(eventable: exception).order(created_at: :asc)
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
# Cleanup old events
|
|
74
|
-
def self.cleanup!(retention_days: 30)
|
|
75
|
-
where("created_at < ?", retention_days.days.ago).delete_all
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
# Human-readable event type
|
|
79
|
-
def type_label
|
|
80
|
-
case event_type
|
|
81
|
-
when "breadcrumb" then "🔍 Breadcrumb"
|
|
82
|
-
when "storm_start" then "🌪️ Storm Start"
|
|
83
|
-
when "storm_end" then "☀️ Storm End"
|
|
84
|
-
when "assignment" then "👤 Assigned"
|
|
85
|
-
when "priority_change" then " priority Changed"
|
|
86
|
-
when "snooze" then "😴 Snoozed"
|
|
87
|
-
when "unsnooze" then "⏰ Unsnoozed"
|
|
88
|
-
when "mute" then "🔇 Muted"
|
|
89
|
-
when "unmute" then "🔊 Unmuted"
|
|
90
|
-
when "comment" then "💬 Comment"
|
|
91
|
-
when "note" then "📝 Note"
|
|
92
|
-
when "backup_start" then "💾 Backup Started"
|
|
93
|
-
when "backup_complete" then "✅ Backup Complete"
|
|
94
|
-
when "backup_failed" then "❌ Backup Failed"
|
|
95
|
-
when "exception_created" then "🚨 Exception Created"
|
|
96
|
-
when "exception_resolved" then "✓ Exception Resolved"
|
|
97
|
-
else event_type.humanize
|
|
98
|
-
end
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
end
|