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.
Files changed (114) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +430 -0
  4. data/Rakefile +6 -0
  5. data/app/assets/config/rails_nexus_manifest.js +2 -0
  6. data/app/assets/javascripts/rails_nexus/controllers.js +603 -0
  7. data/app/assets/stylesheets/rails_nexus/application.css +1964 -0
  8. data/app/controllers/rails_nexus/analytics_controller.rb +361 -0
  9. data/app/controllers/rails_nexus/application_controller.rb +15 -0
  10. data/app/controllers/rails_nexus/backup_controller.rb +91 -0
  11. data/app/controllers/rails_nexus/cron_jobs_controller.rb +64 -0
  12. data/app/controllers/rails_nexus/database_health_controller.rb +128 -0
  13. data/app/controllers/rails_nexus/logged_exceptions_controller.rb +227 -0
  14. data/app/controllers/rails_nexus/n1_patterns_controller.rb +10 -0
  15. data/app/controllers/rails_nexus/settings_controller.rb +100 -0
  16. data/app/controllers/rails_nexus/source_code_controller.rb +39 -0
  17. data/app/controllers/rails_nexus/stats_controller.rb +494 -0
  18. data/app/controllers/rails_nexus/workflow_controller.rb +96 -0
  19. data/app/helpers/rails_nexus/application_helper.rb +5 -0
  20. data/app/helpers/rails_nexus/logged_exceptions_helper.rb +254 -0
  21. data/app/helpers/rails_nexus/source_code_helper.rb +107 -0
  22. data/app/javascript/controllers/rails_nexus_controller.js +380 -0
  23. data/app/javascript/controllers/rails_nexus_detail_controller.js +165 -0
  24. data/app/javascript/controllers/rails_nexus_sidebar_controller.js +15 -0
  25. data/app/javascript/controllers/rails_nexus_theme_controller.js +31 -0
  26. data/app/javascript/rails_nexus/application.js +17 -0
  27. data/app/jobs/rails_nexus/application_job.rb +4 -0
  28. data/app/mailers/rails_nexus/application_mailer.rb +6 -0
  29. data/app/models/rails_nexus/application_record.rb +5 -0
  30. data/app/models/rails_nexus/backup.rb +107 -0
  31. data/app/models/rails_nexus/base_record.rb +26 -0
  32. data/app/models/rails_nexus/comment.rb +13 -0
  33. data/app/models/rails_nexus/cron_job.rb +72 -0
  34. data/app/models/rails_nexus/database_stat.rb +128 -0
  35. data/app/models/rails_nexus/event.rb +101 -0
  36. data/app/models/rails_nexus/logged_exception.rb +864 -0
  37. data/app/models/rails_nexus/logged_exception_n1.rb +87 -0
  38. data/app/models/rails_nexus/metric.rb +107 -0
  39. data/app/models/rails_nexus/nginx_metric.rb +112 -0
  40. data/app/models/rails_nexus/server_metric.rb +168 -0
  41. data/app/models/rails_nexus/webhook_delivery.rb +64 -0
  42. data/app/services/rails_nexus/backup_service.rb +275 -0
  43. data/app/views/layouts/rails_nexus/application.html.erb +185 -0
  44. data/app/views/rails_nexus/analytics/index.html.erb +363 -0
  45. data/app/views/rails_nexus/backup/files.html.erb +53 -0
  46. data/app/views/rails_nexus/backup/index.html.erb +211 -0
  47. data/app/views/rails_nexus/backup/settings.html.erb +158 -0
  48. data/app/views/rails_nexus/cron_jobs/index.html.erb +141 -0
  49. data/app/views/rails_nexus/database_health/index.html.erb +139 -0
  50. data/app/views/rails_nexus/logged_exceptions/_comments_list.html.erb +50 -0
  51. data/app/views/rails_nexus/logged_exceptions/_exceptions.html.erb +190 -0
  52. data/app/views/rails_nexus/logged_exceptions/_feed.html.erb +3 -0
  53. data/app/views/rails_nexus/logged_exceptions/_filter_group.html.erb +10 -0
  54. data/app/views/rails_nexus/logged_exceptions/_show.html.erb +554 -0
  55. data/app/views/rails_nexus/logged_exceptions/_workflow_panel.html.erb +118 -0
  56. data/app/views/rails_nexus/logged_exceptions/clear.turbo_stream.erb +6 -0
  57. data/app/views/rails_nexus/logged_exceptions/destroy.turbo_stream.erb +2 -0
  58. data/app/views/rails_nexus/logged_exceptions/destroy_all.turbo_stream.erb +6 -0
  59. data/app/views/rails_nexus/logged_exceptions/feed.rss.builder +20 -0
  60. data/app/views/rails_nexus/logged_exceptions/index.html.erb +323 -0
  61. data/app/views/rails_nexus/logged_exceptions/query.html.erb +3 -0
  62. data/app/views/rails_nexus/logged_exceptions/query.turbo_stream.erb +6 -0
  63. data/app/views/rails_nexus/logged_exceptions/show.html.erb +3 -0
  64. data/app/views/rails_nexus/logged_exceptions/show.turbo_stream.erb +5 -0
  65. data/app/views/rails_nexus/n1_patterns/index.html.erb +153 -0
  66. data/app/views/rails_nexus/settings/index.html.erb +140 -0
  67. data/app/views/rails_nexus/stats/index.html.erb +569 -0
  68. data/config/importmap.rb +15 -0
  69. data/config/initializers/date_formats.rb +5 -0
  70. data/config/initializers/rails_nexus.rb +18 -0
  71. data/config/locales/en.yml +47 -0
  72. data/config/routes.rb +58 -0
  73. data/db/migrate/20240330122311_create_rails_nexus_logged_exceptions.rb +22 -0
  74. data/lib/generators/rails_nexus/backup_generator.rb +70 -0
  75. data/lib/generators/rails_nexus/customize_generator.rb +159 -0
  76. data/lib/generators/rails_nexus/install_generator.rb +61 -0
  77. data/lib/generators/rails_nexus/templates/INSTALL.md +9 -0
  78. data/lib/generators/rails_nexus/templates/backup/backup_helper.sh +152 -0
  79. data/lib/generators/rails_nexus/templates/backup/config.rb +27 -0
  80. data/lib/generators/rails_nexus/templates/backup/models/daily_backup.rb +24 -0
  81. data/lib/generators/rails_nexus/templates/backup/models/full_backup.rb +35 -0
  82. data/lib/generators/rails_nexus/templates/backup/models/sync_backup.rb +43 -0
  83. data/lib/generators/rails_nexus/templates/backup/mysql-config/db_config.cnf +15 -0
  84. data/lib/generators/rails_nexus/templates/backup/schedule.rb +28 -0
  85. data/lib/generators/rails_nexus/templates/javascript/controllers/rails_nexus_controller.js +310 -0
  86. data/lib/generators/rails_nexus/templates/javascript/controllers/rails_nexus_detail_controller.js +98 -0
  87. data/lib/generators/rails_nexus/templates/javascript/controllers/rails_nexus_sidebar_controller.js +15 -0
  88. data/lib/generators/rails_nexus/templates/javascript/controllers/rails_nexus_theme_controller.js +30 -0
  89. data/lib/generators/rails_nexus/templates/migration.rb +24 -0
  90. data/lib/generators/rails_nexus/templates/migration_advanced_features.rb +30 -0
  91. data/lib/generators/rails_nexus/templates/migration_cron_jobs.rb +22 -0
  92. data/lib/generators/rails_nexus/templates/migration_new_schema.rb +120 -0
  93. data/lib/generators/rails_nexus/templates/migration_platform_detection.rb +11 -0
  94. data/lib/generators/rails_nexus/templates/migration_webhook_deliveries.rb +21 -0
  95. data/lib/generators/rails_nexus/templates/migration_workflow.rb +29 -0
  96. data/lib/generators/rails_nexus/templates/rails_nexus.rb +96 -0
  97. data/lib/generators/rails_nexus/templates/views/layouts/rails_nexus/application.html.erb +129 -0
  98. data/lib/generators/rails_nexus/templates/views/rails_nexus/logged_exceptions/_exceptions.html.erb +123 -0
  99. data/lib/generators/rails_nexus/templates/views/rails_nexus/logged_exceptions/_show.html.erb +297 -0
  100. data/lib/generators/rails_nexus/templates/views/rails_nexus/logged_exceptions/index.html.erb +113 -0
  101. data/lib/rails_nexus/breadcrumbs.rb +161 -0
  102. data/lib/rails_nexus/cleanup.rb +32 -0
  103. data/lib/rails_nexus/configuration.rb +319 -0
  104. data/lib/rails_nexus/engine.rb +135 -0
  105. data/lib/rails_nexus/logger.rb +188 -0
  106. data/lib/rails_nexus/notifications.rb +105 -0
  107. data/lib/rails_nexus/ransack_config.rb +4 -0
  108. data/lib/rails_nexus/storm_protection.rb +124 -0
  109. data/lib/rails_nexus/version.rb +3 -0
  110. data/lib/rails_nexus.rb +133 -0
  111. data/lib/tasks/rails_nexus.rake +230 -0
  112. data/lib/tasks/rails_nexus_tasks.rake +37 -0
  113. data/rails_nexus.gemspec +36 -0
  114. metadata +252 -0
@@ -0,0 +1,190 @@
1
+ <div id="exceptions-content" class="rn-card overflow-hidden">
2
+ <% if @exceptions.empty? %>
3
+ <div class="py-16 text-center">
4
+ <div class="w-12 h-12 rounded-full flex items-center justify-center mx-auto" style="background: var(--rn-success-light)">
5
+ <svg class="w-6 h-6" style="color: var(--rn-success)" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
6
+ <path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
7
+ </svg>
8
+ </div>
9
+ <p class="mt-3 text-sm font-medium" style="color: var(--rn-text)"><%= t(".empty") %></p>
10
+ <p class="mt-1 text-xs" style="color: var(--rn-text-muted)">No exceptions match your current filters.</p>
11
+ <% if filtered? %>
12
+ <%= link_to "Clear filters", logged_exceptions_path, class: "mt-3 inline-block text-xs font-medium", style: "color: var(--rn-primary)" %>
13
+ <% end %>
14
+ </div>
15
+ <% else %>
16
+ <%# ─── Table Header Bar ───────────────────────────────────── %>
17
+ <div class="px-4 py-2.5 flex items-center justify-between" style="background: var(--rn-bg-secondary); border-bottom: 1px solid var(--rn-border-light)">
18
+ <div class="flex items-center gap-3">
19
+ <span class="text-xs font-medium" style="color: var(--rn-text-secondary)">
20
+ <span class="font-semibold" style="color: var(--rn-text)"><%= number_with_delimiter(@pagy.count) %></span> exceptions
21
+ <% if filtered? %>
22
+ <span style="color: var(--rn-text-muted)">(filtered)</span>
23
+ <% end %>
24
+ </span>
25
+
26
+ <%# Page size selector %>
27
+ <div class="flex items-center gap-1">
28
+ <span class="text-xs" style="color: var(--rn-text-muted)">Show</span>
29
+ <% RailsNexus.configuration.page_size_options.each do |size| %>
30
+ <%= link_to size.to_s,
31
+ logged_exceptions_path(params_filters.merge(per_page: size)),
32
+ class: "rn-btn rn-btn-xs #{params[:per_page].to_i == size || (!params[:per_page] && size == RailsNexus.configuration.per_page) ? 'rn-btn-primary' : 'rn-btn-ghost'}",
33
+ data: { turbo_frame: "exceptions" } %>
34
+ <% end %>
35
+ </div>
36
+ </div>
37
+
38
+ <div class="flex items-center gap-2">
39
+ <%# Keyboard hint %>
40
+ <span class="text-xs flex items-center gap-1" style="color: var(--rn-text-muted)">
41
+ <span class="rn-kbd">?</span> shortcuts
42
+ </span>
43
+
44
+ <%= link_to destroy_all_logged_exceptions_path(params_filters),
45
+ class: "rn-btn rn-btn-ghost rn-btn-sm",
46
+ style: "color: var(--rn-danger); border-color: var(--rn-danger)",
47
+ data: { turbo_method: :post, turbo_confirm: t(".confirm_delete_visible"), turbo_stream: true } do %>
48
+ <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="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>
49
+ Delete all
50
+ <% end %>
51
+ </div>
52
+ </div>
53
+
54
+ <%# ─── Exception Table ────────────────────────────────────── %>
55
+ <div class="rn-table-wrap">
56
+ <table class="rn-table rn-exceptions-table">
57
+ <thead>
58
+ <tr>
59
+ <th class="rn-col-severity"><span class="sr-only">Severity</span></th>
60
+ <th class="rn-col-exception <%= sort_header_class(:exception_class) %>"><%= sort_link @q, :exception_class, "Exception" %></th>
61
+ <% if exception_feature?(:platform) %>
62
+ <th class="rn-col-platform <%= sort_header_class(:platform) %>"><%= sort_link @q, :platform, "Platform" %></th>
63
+ <% end %>
64
+ <th class="rn-col-source <%= sort_header_class(:controller_name) %>"><%= sort_link @q, :controller_name, "Source" %></th>
65
+ <th class="rn-col-message <%= sort_header_class(:message) %>"><%= sort_link @q, :message, "Message" %></th>
66
+ <% if exception_feature?(:occurrence_count) %>
67
+ <th class="rn-col-count <%= sort_header_class(:occurrence_count) %>"><%= sort_link @q, :occurrence_count, "Count" %></th>
68
+ <% else %>
69
+ <th class="rn-col-count">Count</th>
70
+ <% end %>
71
+ <th class="rn-col-workflow <%= sort_header_class(:priority) if exception_feature?(:priority) && exception_searchable?(:priority) %>">
72
+ <%= exception_feature?(:priority) && exception_searchable?(:priority) ? sort_link(@q, :priority, "Workflow") : "Workflow" %>
73
+ </th>
74
+ <th class="rn-col-time <%= sort_header_class(:created_at) %>"><%= sort_link @q, :created_at, "Last Seen" %></th>
75
+ <th class="rn-col-actions"><span class="sr-only">Actions</span></th>
76
+ </tr>
77
+ </thead>
78
+ <tbody data-rails_nexus-target="exceptionList">
79
+ <% @exceptions.each_with_index do |exception, index| %>
80
+ <tr id="<%= dom_id(exception) %>"
81
+ class="group"
82
+ style="cursor: pointer; position: relative"
83
+ data-action="click->rails_nexus#selectRow"
84
+ data-url="<%= logged_exception_path(exception) %>"
85
+ data-index="<%= index %>"
86
+ data-rails_nexus-index-param="<%= index %>">
87
+
88
+ <%# Severity indicator dot %>
89
+ <td>
90
+ <span class="rn-count-indicator rn-badge-<%= severity_for(exception) %>"
91
+ style="background: var(--rn-<%= severity_for(exception) %>-light); color: var(--rn-<%= severity_for(exception) %>); width: 0.5rem; height: 0.5rem; min-width: 0; padding: 0; border-radius: 50%">
92
+ </span>
93
+ </td>
94
+
95
+ <%# Exception class with severity badge %>
96
+ <td class="rn-col-exception">
97
+ <span class="rn-badge rn-badge-<%= severity_for(exception) %> rn-exception-name" title="<%= exception.exception_class %>"><%= exception.exception_class %></span>
98
+ <% if exception.respond_to?(:fingerprint) && exception.fingerprint.present? %>
99
+ <span class="rn-row-meta rn-mono" title="Fingerprint"><%= exception.fingerprint.first(10) %></span>
100
+ <% end %>
101
+ </td>
102
+
103
+ <%# Platform badge %>
104
+ <% if exception_feature?(:platform) %>
105
+ <td class="rn-col-platform">
106
+ <span class="rn-badge rn-badge-<%= exception.platform == 'ios' ? 'success' : exception.platform == 'android' ? 'warning' : exception.platform == 'api' ? 'info' : exception.platform == 'bot' ? 'muted' : 'primary' %>">
107
+ <%= exception.platform&.upcase || 'WEB' %>
108
+ </span>
109
+ </td>
110
+ <% end %>
111
+
112
+ <%# Source (controller + action) %>
113
+ <td class="rn-col-source">
114
+ <div class="rn-source" title="<%= [exception.controller_name, exception.action_name].compact.join('#') %>">
115
+ <span class="rn-mono text-xs" style="color: var(--rn-text-secondary)"><%= exception.controller_name.presence || "—" %></span>
116
+ <% if exception.action_name.present? %>
117
+ <span class="rn-mono text-xs" style="color: var(--rn-text-muted)">#<%= exception.action_name %></span>
118
+ <% end %>
119
+ </div>
120
+ </td>
121
+
122
+ <%# Message (truncated) %>
123
+ <td class="rn-col-message">
124
+ <div class="rn-msg" title="<%= exception.message %>">
125
+ <%= exception.message.presence || "No message" %>
126
+ </div>
127
+ </td>
128
+
129
+ <%# Occurrence count %>
130
+ <td class="rn-col-count">
131
+ <span class="rn-badge rn-badge-muted"><%= number_with_delimiter(exception_occurrence_count(exception)) %></span>
132
+ </td>
133
+
134
+ <%# Workflow indicators %>
135
+ <td class="rn-col-workflow">
136
+ <% status_label, status_style = exception_status(exception) %>
137
+ <div class="rn-workflow-state">
138
+ <span class="rn-badge rn-badge-xs rn-badge-<%= status_style %>"><%= status_label %></span>
139
+ <% if exception.respond_to?(:priority) && exception.priority.present? %>
140
+ <span class="rn-badge rn-badge-xs rn-badge-<%= exception.priority == 'critical' ? 'danger' : exception.priority == 'high' ? 'warning' : exception.priority == 'medium' ? 'info' : 'muted' %>"><%= exception.priority.upcase %></span>
141
+ <% end %>
142
+ <% if exception.respond_to?(:assigned_to) && exception.assigned_to.present? %>
143
+ <span class="rn-row-meta" title="Assigned to <%= exception.assigned_to %>">👤 <%= exception.assigned_to %></span>
144
+ <% elsif exception.respond_to?(:comments_count) && exception.comments_count.to_i > 0 %>
145
+ <span class="rn-row-meta"><%= exception.comments_count %> comments</span>
146
+ <% end %>
147
+ </div>
148
+ </td>
149
+
150
+ <%# Time (relative) %>
151
+ <td class="rn-date"><%= pretty_exception_date(exception) %></td>
152
+
153
+ <%# Actions (delete on hover) %>
154
+ <td class="rn-col-actions" style="text-align: right">
155
+ <div class="flex items-center justify-end gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
156
+ <%= link_to logged_exception_path(exception),
157
+ class: "rn-icon-btn",
158
+ style: "color: var(--rn-text-muted)",
159
+ title: "View details",
160
+ data: { turbo_frame: "exception-details", action: "click->rails_nexus#stopPropagation" } do %>
161
+ <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="M13.5 6H5.25A2.25 2.25 0 003 8.25v10.5A2.25 2.25 0 005.25 21h10.5A2.25 2.25 0 0018 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25" /></svg>
162
+ <% end %>
163
+ <%= link_to logged_exception_path(exception),
164
+ class: "rn-icon-btn",
165
+ style: "color: var(--rn-danger)",
166
+ title: "Delete",
167
+ data: { turbo_method: :delete, turbo_confirm: t(".confirm_delete"), turbo_stream: true, action: "click->rails_nexus#stopPropagation" } do %>
168
+ <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>
169
+ <% end %>
170
+ </div>
171
+ </td>
172
+ </tr>
173
+ <% end %>
174
+ </tbody>
175
+ </table>
176
+ </div>
177
+
178
+ <%# ─── Pagination ─────────────────────────────────────────── %>
179
+ <% if @pagy.pages > 1 %>
180
+ <div class="rn-pagination">
181
+ <p class="text-xs" style="color: var(--rn-text-muted)">
182
+ Showing <span class="font-medium" style="color: var(--rn-text-secondary)"><%= @pagy.from %></span>
183
+ to <span class="font-medium" style="color: var(--rn-text-secondary)"><%= @pagy.to %></span>
184
+ of <span class="font-medium" style="color: var(--rn-text-secondary)"><%= @pagy.count %></span>
185
+ </p>
186
+ <div><%== pagy_nav(@pagy) %></div>
187
+ </div>
188
+ <% end %>
189
+ <% end %>
190
+ </div>
@@ -0,0 +1,3 @@
1
+ <div class="rn-feed">
2
+ <%= link_to t(".rss_feed"), feed_logged_exceptions_path(format: :rss) %>
3
+ </div>
@@ -0,0 +1,10 @@
1
+ <div>
2
+ <h3 class="text-xs font-semibold text-gray-500 uppercase tracking-wider"><%= title %></h3>
3
+ <nav class="mt-2 space-y-1 max-h-48 overflow-y-auto">
4
+ <% values.each do |value| %>
5
+ <%= link_to value, query_logged_exceptions_path(parameter => value),
6
+ class: "block px-3 py-2 text-sm rounded-md text-gray-700 hover:bg-gray-100 hover:text-gray-900",
7
+ data: { turbo_frame: "exceptions" } %>
8
+ <% end %>
9
+ </nav>
10
+ </div>