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,158 @@
|
|
|
1
|
+
<% page_title t(".title", default: "Backup Settings") %>
|
|
2
|
+
|
|
3
|
+
<div class="space-y-6">
|
|
4
|
+
<%# ─── Header ──────────────────────────────────────────────── %>
|
|
5
|
+
<nav class="rn-breadcrumbs" aria-label="Breadcrumb">
|
|
6
|
+
<a href="<%= root_path %>" class="rn-breadcrumb">Dashboard</a>
|
|
7
|
+
<span class="rn-breadcrumb-sep">/</span>
|
|
8
|
+
<a href="<%= backup_path %>" class="rn-breadcrumb">Backups</a>
|
|
9
|
+
<span class="rn-breadcrumb-sep">/</span>
|
|
10
|
+
<span class="rn-breadcrumb-current">Settings</span>
|
|
11
|
+
</nav>
|
|
12
|
+
|
|
13
|
+
<div class="flex items-center gap-3">
|
|
14
|
+
<svg class="w-6 h-6" style="color: var(--rn-primary)" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
|
15
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.24-.438.613-.431.992a6.759 6.759 0 010 .255c-.007.378.138.75.43.99l1.005.828c.424.35.534.954.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.28c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.02-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.992a6.932 6.932 0 010-.255c.007-.378-.138-.75-.43-.99l-1.004-.828a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.087.22-.128.332-.183.582-.495.644-.869l.214-1.281z" />
|
|
16
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
|
17
|
+
</svg>
|
|
18
|
+
<h1 class="text-2xl font-bold" style="color: var(--rn-text)">Backup Settings</h1>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<%# ─── Settings Form ───────────────────────────────────────── %>
|
|
22
|
+
<div class="rn-card">
|
|
23
|
+
<div class="p-4" style="border-bottom: 1px solid var(--rn-border-light)">
|
|
24
|
+
<h2 class="text-sm font-semibold" style="color: var(--rn-text)">Configuration</h2>
|
|
25
|
+
<p class="text-xs mt-1" style="color: var(--rn-text-muted)">Settings are saved to <code>config/rails_nexus_backup.yml</code></p>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<%= form_with url: backup_update_settings_path, method: :patch, class: "p-4 space-y-6" do |f| %>
|
|
29
|
+
<%# General %>
|
|
30
|
+
<div>
|
|
31
|
+
<h3 class="text-xs font-semibold uppercase tracking-wider mb-3" style="color: var(--rn-text-muted)">General</h3>
|
|
32
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
33
|
+
<div>
|
|
34
|
+
<label class="text-xs font-medium" style="color: var(--rn-text)">Backup Enabled</label>
|
|
35
|
+
<%= f.select :enabled, options_for_select([["Yes", true], ["No", false]], @backup_config[:enabled]), {}, class: "rn-input mt-1" %>
|
|
36
|
+
</div>
|
|
37
|
+
<div>
|
|
38
|
+
<label class="text-xs font-medium" style="color: var(--rn-text)">Alert Threshold (hours)</label>
|
|
39
|
+
<%= f.number_field :alert_threshold_hours, value: @backup_config[:alert_threshold_hours] || 24, class: "rn-input mt-1" %>
|
|
40
|
+
<p class="text-xs mt-1" style="color: var(--rn-text-muted)">Alert when no backup in this many hours</p>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<%# Paths %>
|
|
46
|
+
<div>
|
|
47
|
+
<h3 class="text-xs font-semibold uppercase tracking-wider mb-3" style="color: var(--rn-text-muted)">Paths</h3>
|
|
48
|
+
<div class="space-y-4">
|
|
49
|
+
<div>
|
|
50
|
+
<label class="text-xs font-medium" style="color: var(--rn-text)">Backup Config File</label>
|
|
51
|
+
<%= f.text_field :config_path, value: @backup_config[:config_path], placeholder: "~/Backup/config.rb", class: "rn-input mt-1" %>
|
|
52
|
+
<p class="text-xs mt-1" style="color: var(--rn-text-muted)">Path to backup gem's config.rb</p>
|
|
53
|
+
</div>
|
|
54
|
+
<div>
|
|
55
|
+
<label class="text-xs font-medium" style="color: var(--rn-text)">Backup Models Directory</label>
|
|
56
|
+
<%= f.text_field :models_path, value: @backup_config[:models_path], placeholder: "~/Backup/models", class: "rn-input mt-1" %>
|
|
57
|
+
<p class="text-xs mt-1" style="color: var(--rn-text-muted)">Directory containing backup model files</p>
|
|
58
|
+
</div>
|
|
59
|
+
<div>
|
|
60
|
+
<label class="text-xs font-medium" style="color: var(--rn-text)">Dump Directory</label>
|
|
61
|
+
<%= f.text_field :dump_path, value: @backup_config[:dump_path], placeholder: "~/dumps", class: "rn-input mt-1" %>
|
|
62
|
+
<p class="text-xs mt-1" style="color: var(--rn-text-muted)">Where backup files are stored</p>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<%# Notification %>
|
|
68
|
+
<div>
|
|
69
|
+
<h3 class="text-xs font-semibold uppercase tracking-wider mb-3" style="color: var(--rn-text-muted)">Notification</h3>
|
|
70
|
+
<div>
|
|
71
|
+
<label class="text-xs font-medium" style="color: var(--rn-text)">Notify Command</label>
|
|
72
|
+
<%= f.text_field :notify_command, value: @backup_config[:notify_command], placeholder: "bk-send", class: "rn-input mt-1" %>
|
|
73
|
+
<p class="text-xs mt-1" style="color: var(--rn-text-muted)">Shell command to run on backup success/failure</p>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<%# RSync %>
|
|
78
|
+
<div>
|
|
79
|
+
<h3 class="text-xs font-semibold uppercase tracking-wider mb-3" style="color: var(--rn-text-muted)">RSync (Remote Sync)</h3>
|
|
80
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
81
|
+
<div>
|
|
82
|
+
<label class="text-xs font-medium" style="color: var(--rn-text)">Host</label>
|
|
83
|
+
<%= f.text_field :rsync_host, value: @backup_config[:rsync_host], placeholder: "192.168.1.100", class: "rn-input mt-1" %>
|
|
84
|
+
</div>
|
|
85
|
+
<div>
|
|
86
|
+
<label class="text-xs font-medium" style="color: var(--rn-text)">Port</label>
|
|
87
|
+
<%= f.number_field :rsync_port, value: @backup_config[:rsync_port] || 22, class: "rn-input mt-1" %>
|
|
88
|
+
</div>
|
|
89
|
+
<div>
|
|
90
|
+
<label class="text-xs font-medium" style="color: var(--rn-text)">SSH User</label>
|
|
91
|
+
<%= f.text_field :rsync_user, value: @backup_config[:rsync_user], placeholder: "backup", class: "rn-input mt-1" %>
|
|
92
|
+
</div>
|
|
93
|
+
<div>
|
|
94
|
+
<label class="text-xs font-medium" style="color: var(--rn-text)">Remote Path</label>
|
|
95
|
+
<%= f.text_field :rsync_path, value: @backup_config[:rsync_path], placeholder: "backups/", class: "rn-input mt-1" %>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
<%# Encryption %>
|
|
101
|
+
<div>
|
|
102
|
+
<h3 class="text-xs font-semibold uppercase tracking-wider mb-3" style="color: var(--rn-text-muted)">Encryption</h3>
|
|
103
|
+
<div>
|
|
104
|
+
<label class="text-xs font-medium" style="color: var(--rn-text)">Encryption Password</label>
|
|
105
|
+
<%= f.password_field :encrypt_password, value: @backup_config[:encrypt_password], placeholder: "Enter password", class: "rn-input mt-1" %>
|
|
106
|
+
<p class="text-xs mt-1" style="color: var(--rn-text-muted)">Used for OpenSSL encryption of backup files</p>
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
<%# Cleanup %>
|
|
111
|
+
<div>
|
|
112
|
+
<h3 class="text-xs font-semibold uppercase tracking-wider mb-3" style="color: var(--rn-text-muted)">Cleanup</h3>
|
|
113
|
+
<div>
|
|
114
|
+
<label class="text-xs font-medium" style="color: var(--rn-text)">Auto Cleanup (days)</label>
|
|
115
|
+
<%= f.number_field :auto_cleanup_days, value: @backup_config[:auto_cleanup_days] || 30, class: "rn-input mt-1" %>
|
|
116
|
+
<p class="text-xs mt-1" style="color: var(--rn-text-muted)">Delete backups older than this (0 = never)</p>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
<%# Actions %>
|
|
121
|
+
<div class="flex items-center gap-3 pt-4" style="border-top: 1px solid var(--rn-border-light)">
|
|
122
|
+
<%= f.submit "Save Settings", class: "rn-btn rn-btn-primary" %>
|
|
123
|
+
<a href="<%= backup_path %>" class="rn-btn rn-btn-ghost">Cancel</a>
|
|
124
|
+
</div>
|
|
125
|
+
<% end %>
|
|
126
|
+
</div>
|
|
127
|
+
|
|
128
|
+
<%# ─── Current Status ──────────────────────────────────────── %>
|
|
129
|
+
<div class="rn-card">
|
|
130
|
+
<div class="p-4" style="border-bottom: 1px solid var(--rn-border-light)">
|
|
131
|
+
<h2 class="text-sm font-semibold" style="color: var(--rn-text)">Current Status</h2>
|
|
132
|
+
</div>
|
|
133
|
+
<div class="p-4">
|
|
134
|
+
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 text-xs">
|
|
135
|
+
<div>
|
|
136
|
+
<span style="color: var(--rn-text-muted)">Config File</span>
|
|
137
|
+
<p class="font-medium mt-1" style="color: var(--rn-text)"><%= @backup_config[:config_path] || "Not set" %></p>
|
|
138
|
+
<p class="mt-1"><span class="rn-badge <%= File.exist?(@backup_config[:config_path].to_s) ? 'rn-badge-success' : 'rn-badge-danger' %>"><%= File.exist?(@backup_config[:config_path].to_s) ? 'Exists' : 'Missing' %></span></p>
|
|
139
|
+
</div>
|
|
140
|
+
<div>
|
|
141
|
+
<span style="color: var(--rn-text-muted)">Models Dir</span>
|
|
142
|
+
<p class="font-medium mt-1" style="color: var(--rn-text)"><%= @backup_config[:models_path] || "Not set" %></p>
|
|
143
|
+
<p class="mt-1"><span class="rn-badge <%= Dir.exist?(@backup_config[:models_path].to_s) ? 'rn-badge-success' : 'rn-badge-danger' %>"><%= Dir.exist?(@backup_config[:models_path].to_s) ? 'Exists' : 'Missing' %></span></p>
|
|
144
|
+
</div>
|
|
145
|
+
<div>
|
|
146
|
+
<span style="color: var(--rn-text-muted)">Dump Dir</span>
|
|
147
|
+
<p class="font-medium mt-1" style="color: var(--rn-text)"><%= @backup_config[:dump_path] || "Not set" %></p>
|
|
148
|
+
<p class="mt-1"><span class="rn-badge <%= Dir.exist?(@backup_config[:dump_path].to_s) ? 'rn-badge-success' : 'rn-badge-danger' %>"><%= Dir.exist?(@backup_config[:dump_path].to_s) ? 'Exists' : 'Missing' %></span></p>
|
|
149
|
+
</div>
|
|
150
|
+
<div>
|
|
151
|
+
<span style="color: var(--rn-text-muted)">Config File</span>
|
|
152
|
+
<p class="font-medium mt-1" style="color: var(--rn-text)">config/rails_nexus_backup.yml</p>
|
|
153
|
+
<p class="mt-1"><span class="rn-badge <%= File.exist?(Rails.root.join('config', 'rails_nexus_backup.yml')) ? 'rn-badge-success' : 'rn-badge-muted' %>"><%= File.exist?(Rails.root.join('config', 'rails_nexus_backup.yml')) ? 'Saved' : 'Default' %></span></p>
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
<% page_title t(".title", default: "Cron Jobs") %>
|
|
2
|
+
|
|
3
|
+
<div class="space-y-4">
|
|
4
|
+
<%# ─── Header ──────────────────────────────────────────────── %>
|
|
5
|
+
<nav class="rn-breadcrumbs" aria-label="Breadcrumb">
|
|
6
|
+
<a href="<%= root_path %>" class="rn-breadcrumb">Dashboard</a>
|
|
7
|
+
<span class="rn-breadcrumb-sep">/</span>
|
|
8
|
+
<span class="rn-breadcrumb-current">Cron Jobs</span>
|
|
9
|
+
</nav>
|
|
10
|
+
|
|
11
|
+
<div class="flex items-center justify-between">
|
|
12
|
+
<h1 class="text-2xl font-bold" style="color: var(--rn-text)">Cron Jobs</h1>
|
|
13
|
+
<div class="flex items-center gap-2">
|
|
14
|
+
<%= link_to "Clear old", clear_old_cron_jobs_path(days: 30),
|
|
15
|
+
class: "rn-btn rn-btn-ghost rn-btn-sm",
|
|
16
|
+
style: "color: var(--rn-danger)",
|
|
17
|
+
data: { turbo_method: :delete, turbo_confirm: "Delete all cron jobs older than 30 days?" } %>
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<%# ─── Stats Cards ─────────────────────────────────────────── %>
|
|
22
|
+
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
23
|
+
<div class="rn-card p-4">
|
|
24
|
+
<div class="text-xs font-semibold uppercase tracking-wider" style="color: var(--rn-text-muted)">Total Runs</div>
|
|
25
|
+
<div class="text-2xl font-bold mt-1" style="color: var(--rn-text)"><%= @stats[:total] %></div>
|
|
26
|
+
</div>
|
|
27
|
+
<div class="rn-card p-4">
|
|
28
|
+
<div class="text-xs font-semibold uppercase tracking-wider" style="color: var(--rn-text-muted)">Successful</div>
|
|
29
|
+
<div class="text-2xl font-bold mt-1" style="color: var(--rn-success)"><%= @stats[:successful] %></div>
|
|
30
|
+
</div>
|
|
31
|
+
<div class="rn-card p-4">
|
|
32
|
+
<div class="text-xs font-semibold uppercase tracking-wider" style="color: var(--rn-text-muted)">Failed</div>
|
|
33
|
+
<div class="text-2xl font-bold mt-1" style="color: var(--rn-danger)"><%= @stats[:failed] %></div>
|
|
34
|
+
</div>
|
|
35
|
+
<div class="rn-card p-4">
|
|
36
|
+
<div class="text-xs font-semibold uppercase tracking-wider" style="color: var(--rn-text-muted)">Success Rate (7d)</div>
|
|
37
|
+
<div class="text-2xl font-bold mt-1" style="color: var(--rn-text)"><%= RailsNexus::CronJob.success_rate %>%</div>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<%# ─── Filters ─────────────────────────────────────────────── %>
|
|
42
|
+
<div class="rn-card">
|
|
43
|
+
<div class="p-3 flex items-center gap-3 flex-wrap">
|
|
44
|
+
<%= link_to "All", cron_jobs_path, class: "rn-btn rn-btn-xs #{params[:status].blank? ? 'rn-btn-primary' : 'rn-btn-ghost'}" %>
|
|
45
|
+
<%= link_to "Running", cron_jobs_path(status: "running"), class: "rn-btn rn-btn-xs #{params[:status] == 'running' ? 'rn-btn-primary' : 'rn-btn-ghost'}" %>
|
|
46
|
+
<%= link_to "Success", cron_jobs_path(status: "success"), class: "rn-btn rn-btn-xs #{params[:status] == 'success' ? 'rn-btn-primary' : 'rn-btn-ghost'}" %>
|
|
47
|
+
<%= link_to "Failed", cron_jobs_path(status: "failed"), class: "rn-btn rn-btn-xs #{params[:status] == 'failed' ? 'rn-btn-primary' : 'rn-btn-ghost'}" %>
|
|
48
|
+
|
|
49
|
+
<span class="ml-auto text-xs" style="color: var(--rn-text-muted)">
|
|
50
|
+
<%= @pagy.count %> total runs
|
|
51
|
+
</span>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<%# ─── Job Runs Table ──────────────────────────────────────── %>
|
|
56
|
+
<div class="rn-card overflow-hidden">
|
|
57
|
+
<% if @cron_jobs.empty? %>
|
|
58
|
+
<div class="py-16 text-center">
|
|
59
|
+
<div class="w-12 h-12 rounded-full flex items-center justify-center mx-auto" style="background: var(--rn-bg-secondary)">
|
|
60
|
+
<svg class="w-6 h-6" style="color: var(--rn-text-muted)" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
|
61
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
62
|
+
</svg>
|
|
63
|
+
</div>
|
|
64
|
+
<p class="mt-3 text-sm font-medium" style="color: var(--rn-text)">No cron job runs recorded</p>
|
|
65
|
+
<p class="mt-1 text-xs" style="color: var(--rn-text-muted)">Use RailsNexus::CronJob.start! to track job runs</p>
|
|
66
|
+
</div>
|
|
67
|
+
<% else %>
|
|
68
|
+
<div class="rn-table-wrap">
|
|
69
|
+
<table class="rn-table">
|
|
70
|
+
<thead>
|
|
71
|
+
<tr>
|
|
72
|
+
<th style="width: 1rem"></th>
|
|
73
|
+
<th>Job Name</th>
|
|
74
|
+
<th>Status</th>
|
|
75
|
+
<th>Duration</th>
|
|
76
|
+
<th>Started</th>
|
|
77
|
+
<th>Hostname</th>
|
|
78
|
+
<th style="text-align: right"><span class="sr-only">Actions</span></th>
|
|
79
|
+
</tr>
|
|
80
|
+
</thead>
|
|
81
|
+
<tbody>
|
|
82
|
+
<% @cron_jobs.each do |job| %>
|
|
83
|
+
<tr>
|
|
84
|
+
<td>
|
|
85
|
+
<span class="w-2 h-2 rounded-full inline-block"
|
|
86
|
+
style="background: <%= case job.status
|
|
87
|
+
when 'success' then 'var(--rn-success)'
|
|
88
|
+
when 'failed' then 'var(--rn-danger)'
|
|
89
|
+
when 'running' then 'var(--rn-warning)'
|
|
90
|
+
else 'var(--rn-text-muted)'
|
|
91
|
+
end %>">
|
|
92
|
+
</span>
|
|
93
|
+
</td>
|
|
94
|
+
<td>
|
|
95
|
+
<span class="font-medium" style="color: var(--rn-text)"><%= job.name %></span>
|
|
96
|
+
</td>
|
|
97
|
+
<td>
|
|
98
|
+
<span class="rn-badge rn-badge-<%= case job.status
|
|
99
|
+
when 'success' then 'success'
|
|
100
|
+
when 'failed' then 'danger'
|
|
101
|
+
when 'running' then 'warning'
|
|
102
|
+
else 'muted'
|
|
103
|
+
end %>">
|
|
104
|
+
<%= job.status.capitalize %>
|
|
105
|
+
</span>
|
|
106
|
+
</td>
|
|
107
|
+
<td>
|
|
108
|
+
<span class="rn-mono text-xs" style="color: var(--rn-text-secondary)">
|
|
109
|
+
<%= job.duration ? "#{job.duration.round(2)}s" : "—" %>
|
|
110
|
+
</span>
|
|
111
|
+
</td>
|
|
112
|
+
<td>
|
|
113
|
+
<span class="text-xs" style="color: var(--rn-text-muted)">
|
|
114
|
+
<%= job.started_at ? job.started_at.strftime("%b %d, %Y %I:%M %p") : "—" %>
|
|
115
|
+
</span>
|
|
116
|
+
</td>
|
|
117
|
+
<td>
|
|
118
|
+
<span class="rn-mono text-xs" style="color: var(--rn-text-muted)"><%= job.hostname || "—" %></span>
|
|
119
|
+
</td>
|
|
120
|
+
<td style="text-align: right">
|
|
121
|
+
<%= link_to "View", cron_job_path(job), class: "rn-btn rn-btn-ghost rn-btn-xs" %>
|
|
122
|
+
<%= link_to "×", cron_job_path(job),
|
|
123
|
+
class: "rn-icon-btn",
|
|
124
|
+
style: "color: var(--rn-danger)",
|
|
125
|
+
data: { turbo_method: :delete, turbo_confirm: "Delete this run?" } %>
|
|
126
|
+
</td>
|
|
127
|
+
</tr>
|
|
128
|
+
<% end %>
|
|
129
|
+
</tbody>
|
|
130
|
+
</table>
|
|
131
|
+
</div>
|
|
132
|
+
|
|
133
|
+
<%# Pagination %>
|
|
134
|
+
<% if @pagy.pages > 1 %>
|
|
135
|
+
<div class="rn-pagination">
|
|
136
|
+
<%== pagy_nav(@pagy) %>
|
|
137
|
+
</div>
|
|
138
|
+
<% end %>
|
|
139
|
+
<% end %>
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
<% page_title t(".title", default: "Database Health") %>
|
|
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
|
+
<span class="rn-breadcrumb-current">Database Health</span>
|
|
8
|
+
</nav>
|
|
9
|
+
|
|
10
|
+
<div class="flex items-center justify-between">
|
|
11
|
+
<h1 class="text-2xl font-bold" style="color: var(--rn-text)">Database Health</h1>
|
|
12
|
+
<div class="flex items-center gap-2">
|
|
13
|
+
<% [7, 30, 90].each do |days| %>
|
|
14
|
+
<%= link_to "#{days}d", database_health_path(period: days),
|
|
15
|
+
class: "rn-btn rn-btn-xs #{params[:period].to_i == days ? 'rn-btn-primary' : 'rn-btn-ghost'}" %>
|
|
16
|
+
<% end %>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<%# ─── Summary Cards ──────────────────────────────────────── %>
|
|
21
|
+
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
22
|
+
<div class="rn-card p-4">
|
|
23
|
+
<div class="text-xs font-semibold uppercase tracking-wider" style="color: var(--rn-text-muted)">Errors with Pool Data</div>
|
|
24
|
+
<div class="text-2xl font-bold mt-1" style="color: var(--rn-text)"><%= number_with_delimiter(@summary[:errors_with_pool]) %></div>
|
|
25
|
+
<div class="text-xs" style="color: var(--rn-text-muted)">of <%= @summary[:total_errors] %> total</div>
|
|
26
|
+
</div>
|
|
27
|
+
<div class="rn-card p-4">
|
|
28
|
+
<div class="text-xs font-semibold uppercase tracking-wider" style="color: var(--rn-text-muted)">Peak Utilization</div>
|
|
29
|
+
<div class="text-2xl font-bold mt-1" style="color: <%= @peak_utilization > 90 ? 'var(--rn-danger)' : @peak_utilization > 70 ? 'var(--rn-warning)' : 'var(--rn-success)' %>"><%= @peak_utilization %>%</div>
|
|
30
|
+
</div>
|
|
31
|
+
<div class="rn-card p-4">
|
|
32
|
+
<div class="text-xs font-semibold uppercase tracking-wider" style="color: var(--rn-text-muted)">Total Dead</div>
|
|
33
|
+
<div class="text-2xl font-bold mt-1" style="color: var(--rn-danger)"><%= @total_dead %></div>
|
|
34
|
+
</div>
|
|
35
|
+
<div class="rn-card p-4">
|
|
36
|
+
<div class="text-xs font-semibold uppercase tracking-wider" style="color: var(--rn-text-muted)">Total Waiting</div>
|
|
37
|
+
<div class="text-2xl font-bold mt-1" style="color: var(--rn-warning)"><%= @total_waiting %></div>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<%# ─── Live Pool ──────────────────────────────────────────── %>
|
|
42
|
+
<% if @live_pool && !@live_pool[:error] %>
|
|
43
|
+
<div class="rn-card">
|
|
44
|
+
<div class="p-4" style="border-bottom: 1px solid var(--rn-border-light)">
|
|
45
|
+
<h2 class="text-sm font-semibold" style="color: var(--rn-text)">Live Connection Pool</h2>
|
|
46
|
+
</div>
|
|
47
|
+
<div class="p-4">
|
|
48
|
+
<div class="grid grid-cols-2 md:grid-cols-6 gap-4">
|
|
49
|
+
<div>
|
|
50
|
+
<div class="text-xs" style="color: var(--rn-text-muted)">Pool Size</div>
|
|
51
|
+
<div class="text-lg font-bold" style="color: var(--rn-text)"><%= @live_pool[:size] %></div>
|
|
52
|
+
</div>
|
|
53
|
+
<div>
|
|
54
|
+
<div class="text-xs" style="color: var(--rn-text-muted)">Busy</div>
|
|
55
|
+
<div class="text-lg font-bold" style="color: var(--rn-info)"><%= @live_pool[:busy] %></div>
|
|
56
|
+
</div>
|
|
57
|
+
<div>
|
|
58
|
+
<div class="text-xs" style="color: var(--rn-text-muted)">Idle</div>
|
|
59
|
+
<div class="text-lg font-bold" style="color: var(--rn-text-secondary)"><%= @live_pool[:idle] %></div>
|
|
60
|
+
</div>
|
|
61
|
+
<div>
|
|
62
|
+
<div class="text-xs" style="color: var(--rn-text-muted)">Dead</div>
|
|
63
|
+
<div class="text-lg font-bold" style="color: var(--rn-danger)"><%= @live_pool[:dead] %></div>
|
|
64
|
+
</div>
|
|
65
|
+
<div>
|
|
66
|
+
<div class="text-xs" style="color: var(--rn-text-muted)">Waiting</div>
|
|
67
|
+
<div class="text-lg font-bold" style="color: var(--rn-warning)"><%= @live_pool[:waiting] %></div>
|
|
68
|
+
</div>
|
|
69
|
+
<div>
|
|
70
|
+
<div class="text-xs" style="color: var(--rn-text-muted)">Adapter</div>
|
|
71
|
+
<div class="text-lg font-bold" style="color: var(--rn-text)"><%= @live_pool[:adapter] %></div>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
<% elsif @live_pool && @live_pool[:error] %>
|
|
77
|
+
<div class="rn-card p-4">
|
|
78
|
+
<p class="text-sm" style="color: var(--rn-text-muted)">Live pool stats unavailable: <%= @live_pool[:error] %></p>
|
|
79
|
+
</div>
|
|
80
|
+
<% end %>
|
|
81
|
+
|
|
82
|
+
<%# ─── Connection Pool by Error ────────────────────────────── %>
|
|
83
|
+
<% if @pool_by_error&.any? %>
|
|
84
|
+
<div class="rn-card">
|
|
85
|
+
<div class="p-4" style="border-bottom: 1px solid var(--rn-border-light)">
|
|
86
|
+
<h2 class="text-sm font-semibold" style="color: var(--rn-text)">Connection Pool by Error</h2>
|
|
87
|
+
<p class="text-xs mt-1" style="color: var(--rn-text-muted)">High utilization during errors may indicate connection pool exhaustion or long-running queries.</p>
|
|
88
|
+
</div>
|
|
89
|
+
<div class="rn-table-wrap">
|
|
90
|
+
<table class="rn-table">
|
|
91
|
+
<thead>
|
|
92
|
+
<tr>
|
|
93
|
+
<th>Error</th>
|
|
94
|
+
<th>Error Type</th>
|
|
95
|
+
<th>Utilization</th>
|
|
96
|
+
<th>Busy</th>
|
|
97
|
+
<th>Idle</th>
|
|
98
|
+
<th>Dead</th>
|
|
99
|
+
<th>Waiting</th>
|
|
100
|
+
<th>Pool Size</th>
|
|
101
|
+
<th>Last Seen</th>
|
|
102
|
+
</tr>
|
|
103
|
+
</thead>
|
|
104
|
+
<tbody>
|
|
105
|
+
<% @pool_by_error.each do |error| %>
|
|
106
|
+
<tr>
|
|
107
|
+
<td>
|
|
108
|
+
<%= link_to "##{error[:id]}", logged_exception_path(error[:id]),
|
|
109
|
+
class: "text-xs font-medium", style: "color: var(--rn-primary)" %>
|
|
110
|
+
</td>
|
|
111
|
+
<td>
|
|
112
|
+
<span class="rn-badge rn-badge-danger"><%= error[:exception_class] %></span>
|
|
113
|
+
</td>
|
|
114
|
+
<td>
|
|
115
|
+
<div class="flex items-center gap-2">
|
|
116
|
+
<div class="w-16 h-2 rounded-full overflow-hidden" style="background: var(--rn-bg-tertiary)">
|
|
117
|
+
<div class="h-full rounded-full" style="width: <%= error[:utilization] %>% ; background: <%= error[:utilization] > 90 ? 'var(--rn-danger)' : error[:utilization] > 70 ? 'var(--rn-warning)' : 'var(--rn-success)' %>"></div>
|
|
118
|
+
</div>
|
|
119
|
+
<span class="text-xs font-medium" style="color: <%= error[:utilization] > 90 ? 'var(--rn-danger)' : error[:utilization] > 70 ? 'var(--rn-warning)' : 'var(--rn-text)' %>"><%= error[:utilization] %>%</span>
|
|
120
|
+
</div>
|
|
121
|
+
</td>
|
|
122
|
+
<td><span class="text-xs" style="color: var(--rn-text-secondary)"><%= error[:busy] %></span></td>
|
|
123
|
+
<td><span class="text-xs" style="color: var(--rn-text-muted)"><%= error[:idle] %></span></td>
|
|
124
|
+
<td><span class="text-xs font-medium" style="color: <%= error[:dead] > 0 ? 'var(--rn-danger)' : 'var(--rn-text-muted)' %>"><%= error[:dead] %></span></td>
|
|
125
|
+
<td><span class="text-xs font-medium" style="color: <%= error[:waiting] > 0 ? 'var(--rn-warning)' : 'var(--rn-text-muted)' %>"><%= error[:waiting] %></span></td>
|
|
126
|
+
<td><span class="text-xs" style="color: var(--rn-text-muted)"><%= error[:pool_size] %></span></td>
|
|
127
|
+
<td><span class="text-xs" style="color: var(--rn-text-muted)"><%= error[:last_seen]&.strftime("%b %d %H:%M") %></span></td>
|
|
128
|
+
</tr>
|
|
129
|
+
<% end %>
|
|
130
|
+
</tbody>
|
|
131
|
+
</table>
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
<% else %>
|
|
135
|
+
<div class="rn-card p-8 text-center">
|
|
136
|
+
<p class="text-sm" style="color: var(--rn-text-muted)">No connection pool data available yet. Pool stats are captured with each exception.</p>
|
|
137
|
+
</div>
|
|
138
|
+
<% end %>
|
|
139
|
+
</div>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<%# Comments List %>
|
|
2
|
+
<div id="comments-list" class="rn-card">
|
|
3
|
+
<div class="p-4" style="border-bottom: 1px solid var(--rn-border-light)">
|
|
4
|
+
<div class="flex items-center gap-2">
|
|
5
|
+
<svg class="w-4 h-4" style="color: var(--rn-primary)" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
|
6
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M7.5 8.25h9m-9 3H12m-9.75 1.51c0 1.6 1.123 2.994 2.707 3.227 1.129.166 2.27.293 3.423.379.35.026.67.21.865.501L12 21l2.755-4.133a1.14 1.14 0 01.865-.501 48.172 48.172 0 003.423-.379c1.584-.233 2.707-1.626 2.707-3.228V6.741c0-1.602-1.123-2.995-2.707-3.228A48.394 48.394 0 0012 3c-2.392 0-4.744.175-7.043.513C3.373 3.746 2.25 5.14 2.25 6.741v6.018z" />
|
|
7
|
+
</svg>
|
|
8
|
+
<h3 class="text-sm font-semibold" style="color: var(--rn-text)">
|
|
9
|
+
Comments
|
|
10
|
+
<% if exception.comments_count > 0 %>
|
|
11
|
+
<span class="ml-1 text-xs" style="color: var(--rn-text-muted)">(<%= exception.comments_count %>)</span>
|
|
12
|
+
<% end %>
|
|
13
|
+
</h3>
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<div class="p-4">
|
|
18
|
+
<%# Add Comment Form %>
|
|
19
|
+
<%= form_with url: add_comment_logged_exception_path(exception), method: :post, class: "mb-4", data: { turbo_stream: true } do |f| %>
|
|
20
|
+
<div class="flex gap-2">
|
|
21
|
+
<%= f.text_area :body, placeholder: "Add a comment...", class: "rn-input", rows: 2, style: "flex: 1" %>
|
|
22
|
+
</div>
|
|
23
|
+
<div class="flex justify-end mt-2">
|
|
24
|
+
<%= f.submit "Comment", class: "rn-btn rn-btn-primary" %>
|
|
25
|
+
</div>
|
|
26
|
+
<% end %>
|
|
27
|
+
|
|
28
|
+
<%# Comments %>
|
|
29
|
+
<% if exception.comments.any? %>
|
|
30
|
+
<div class="space-y-3">
|
|
31
|
+
<% exception.comments.recent.each do |comment| %>
|
|
32
|
+
<div class="p-3 rounded-lg" style="background: var(--rn-bg-secondary); border-left: 3px solid <%= case comment.comment_type
|
|
33
|
+
when 'assignment' then 'var(--rn-primary)'
|
|
34
|
+
when 'status_change' then 'var(--rn-warning)'
|
|
35
|
+
when 'note' then 'var(--rn-text-muted)'
|
|
36
|
+
else 'var(--rn-border)'
|
|
37
|
+
end %>">
|
|
38
|
+
<div class="flex items-center justify-between mb-1">
|
|
39
|
+
<span class="text-xs font-semibold" style="color: var(--rn-text)"><%= comment.author %></span>
|
|
40
|
+
<span class="text-xs" style="color: var(--rn-text-muted)"><%= time_ago_in_words(comment.created_at) %> ago</span>
|
|
41
|
+
</div>
|
|
42
|
+
<p class="text-sm" style="color: var(--rn-text-secondary)"><%= comment.body %></p>
|
|
43
|
+
</div>
|
|
44
|
+
<% end %>
|
|
45
|
+
</div>
|
|
46
|
+
<% else %>
|
|
47
|
+
<p class="text-sm text-center" style="color: var(--rn-text-muted)">No comments yet.</p>
|
|
48
|
+
<% end %>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|