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,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateRailsNexusMetricsTables < ActiveRecord::Migration[8.0]
4
+ def change
5
+ # ─── rails_nexus_metrics ─────────────────────────────────────────
6
+ create_table :rails_nexus_metrics do |t|
7
+ t.string :metric_type, null: false
8
+ t.float :value, null: false
9
+ t.string :unit
10
+ t.json :metadata
11
+ t.datetime :recorded_at, null: false
12
+ end
13
+
14
+ add_index :rails_nexus_metrics, [:metric_type, :recorded_at]
15
+ add_index :rails_nexus_metrics, :recorded_at
16
+
17
+ # ─── rails_nexus_database_stats ──────────────────────────────────
18
+ create_table :rails_nexus_database_stats do |t|
19
+ t.integer :pool_size
20
+ t.integer :busy
21
+ t.integer :idle
22
+ t.integer :dead
23
+ t.integer :waiting
24
+ t.float :utilization
25
+ t.string :table_name
26
+ t.bigint :table_rows
27
+ t.string :table_size
28
+ t.string :index_size
29
+ t.string :data_free
30
+ t.json :n1_patterns
31
+ t.json :slow_queries
32
+ t.datetime :recorded_at, null: false
33
+ end
34
+
35
+ add_index :rails_nexus_database_stats, :recorded_at
36
+ add_index :rails_nexus_database_stats, :table_name
37
+
38
+ # ─── rails_nexus_backups ─────────────────────────────────────────
39
+ create_table :rails_nexus_backups do |t|
40
+ t.string :model_name, null: false
41
+ t.string :status, null: false
42
+ t.text :file_path
43
+ t.bigint :file_size
44
+ t.float :duration
45
+ t.text :error_message
46
+ t.string :triggered_by
47
+ t.datetime :started_at, null: false
48
+ t.datetime :completed_at
49
+ end
50
+
51
+ add_index :rails_nexus_backups, [:model_name, :started_at]
52
+ add_index :rails_nexus_backups, :status
53
+ add_index :rails_nexus_backups, :started_at
54
+
55
+ # ─── rails_nexus_server_metrics ──────────────────────────────────
56
+ create_table :rails_nexus_server_metrics do |t|
57
+ t.string :hostname
58
+ t.string :ruby_version
59
+ t.string :rails_version
60
+ t.string :os_info
61
+ t.float :cpu_usage
62
+ t.integer :cpu_cores
63
+ t.float :load_avg_1m
64
+ t.float :load_avg_5m
65
+ t.float :load_avg_15m
66
+ t.bigint :memory_total
67
+ t.bigint :memory_used
68
+ t.bigint :memory_free
69
+ t.bigint :swap_total
70
+ t.bigint :swap_used
71
+ t.bigint :disk_total
72
+ t.bigint :disk_used
73
+ t.float :disk_usage_percent
74
+ t.integer :puma_workers
75
+ t.integer :puma_threads
76
+ t.integer :sidekiq_workers
77
+ t.integer :sidekiq_processed
78
+ t.integer :sidekiq_failed
79
+ t.integer :sidekiq_enqueued
80
+ t.float :uptime_seconds
81
+ t.datetime :recorded_at, null: false
82
+ end
83
+
84
+ add_index :rails_nexus_server_metrics, :recorded_at
85
+
86
+ # ─── rails_nexus_nginx_metrics ───────────────────────────────────
87
+ create_table :rails_nexus_nginx_metrics do |t|
88
+ t.integer :status_code, null: false
89
+ t.string :request_method
90
+ t.string :request_path
91
+ t.float :response_time
92
+ t.float :upstream_time
93
+ t.string :remote_addr
94
+ t.string :user_agent
95
+ t.string :referer
96
+ t.text :request_body
97
+ t.json :metadata
98
+ t.datetime :recorded_at, null: false
99
+ end
100
+
101
+ add_index :rails_nexus_nginx_metrics, [:status_code, :recorded_at]
102
+ add_index :rails_nexus_nginx_metrics, :recorded_at
103
+ add_index :rails_nexus_nginx_metrics, :request_path
104
+
105
+ # ─── rails_nexus_events ──────────────────────────────────────────
106
+ create_table :rails_nexus_events do |t|
107
+ t.string :event_type, null: false
108
+ t.string :eventable_type
109
+ t.bigint :eventable_id
110
+ t.json :metadata
111
+ t.string :author
112
+ t.text :message
113
+ t.datetime :created_at, null: false
114
+ end
115
+
116
+ add_index :rails_nexus_events, [:eventable_type, :eventable_id]
117
+ add_index :rails_nexus_events, :event_type
118
+ add_index :rails_nexus_events, :created_at
119
+ end
120
+ end
@@ -0,0 +1,11 @@
1
+ class AddPlatformToRailsNexusLoggedExceptions < ActiveRecord::Migration[8.0]
2
+ def change
3
+ add_column :rails_nexus_exceptions, :platform, :string, default: "web"
4
+ add_column :rails_nexus_exceptions, :platform_version, :string
5
+ add_column :rails_nexus_exceptions, :device_type, :string
6
+ add_column :rails_nexus_exceptions, :app_version, :string
7
+
8
+ add_index :rails_nexus_exceptions, :platform
9
+ add_index :rails_nexus_exceptions, [:platform, :exception_class]
10
+ end
11
+ end
@@ -0,0 +1,21 @@
1
+ class CreateRailsNexusWebhookDeliveries < ActiveRecord::Migration[8.0]
2
+ def change
3
+ create_table :rails_nexus_webhook_deliveries do |t|
4
+ t.string :url, null: false
5
+ t.string :status, null: false, default: "pending"
6
+ t.integer :response_code
7
+ t.text :request_body
8
+ t.text :response_body
9
+ t.float :duration
10
+ t.text :error_message
11
+ t.string :event_type
12
+ t.json :metadata
13
+
14
+ t.timestamps
15
+ end
16
+
17
+ add_index :rails_nexus_webhook_deliveries, :status
18
+ add_index :rails_nexus_webhook_deliveries, :created_at
19
+ add_index :rails_nexus_webhook_deliveries, :url
20
+ end
21
+ end
@@ -0,0 +1,29 @@
1
+ # Migration for RailsNexus workflow management
2
+ # Adds priority, assignment, snooze, mute columns and comments table
3
+ class AddWorkflowToRailsNexus < ActiveRecord::Migration[8.0]
4
+ def change
5
+ # Workflow columns on logged_exceptions
6
+ add_column :rails_nexus_exceptions, :priority, :string, default: nil
7
+ add_column :rails_nexus_exceptions, :assigned_to, :string, default: nil
8
+ add_column :rails_nexus_exceptions, :assigned_at, :datetime, default: nil
9
+ add_column :rails_nexus_exceptions, :snoozed_until, :datetime, default: nil
10
+ add_column :rails_nexus_exceptions, :muted, :boolean, default: false
11
+ add_column :rails_nexus_exceptions, :muted_at, :datetime, default: nil
12
+ add_column :rails_nexus_exceptions, :comments_count, :integer, default: 0
13
+
14
+ add_index :rails_nexus_exceptions, :priority
15
+ add_index :rails_nexus_exceptions, :assigned_to
16
+ add_index :rails_nexus_exceptions, :muted
17
+
18
+ # Comments table
19
+ create_table :rails_nexus_comments do |t|
20
+ t.references :logged_exception, null: false, foreign_key: { to_table: :rails_nexus_exceptions }
21
+ t.string :author, null: false
22
+ t.text :body, null: false
23
+ t.string :comment_type, default: "comment" # comment, status_change, assignment, note
24
+ t.timestamps
25
+ end
26
+
27
+ add_index :rails_nexus_comments, [:logged_exception_id, :created_at]
28
+ end
29
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ Rails.application.config.to_prepare do
4
+ RailsNexus.configure do |config|
5
+ # ─── General ─────────────────────────────────────────────
6
+ # Dashboard title
7
+ # config.application_name = "My App"
8
+
9
+ # Number of exceptions per page
10
+ # config.per_page = 30
11
+
12
+ # ─── Authentication ──────────────────────────────────────
13
+ # Protect the dashboard — only authorized users can access
14
+ # config.auth_block = lambda do |controller|
15
+ # controller.current_user&.admin?
16
+ # end
17
+
18
+ # ─── Exception Data ──────────────────────────────────────
19
+ # Attach additional data to each exception record
20
+ # config.exception_data = lambda do |controller|
21
+ # {
22
+ # user_id: controller.current_user&.id,
23
+ # request_id: controller.request.request_id
24
+ # }
25
+ # end
26
+
27
+ # ─── Notifications ───────────────────────────────────────
28
+ # Custom callback after exception is created
29
+ # config.after_create = lambda do |exception|
30
+ # SlackNotifier.alert(exception)
31
+ # end
32
+
33
+ # Webhook URLs for POST notifications
34
+ # config.webhooks = ["https://hooks.slack.com/services/xxx"]
35
+ # config.webhook_timeout = 5
36
+ # config.webhook_headers = { "Authorization" => "Bearer token" }
37
+
38
+ # ─── Cleanup ─────────────────────────────────────────────
39
+ # Auto-delete exceptions older than N days
40
+ # config.retention_days = 90
41
+
42
+ # ─── Appearance ──────────────────────────────────────────
43
+ # Theme: "light", "dark", or "auto"
44
+ # config.theme = "auto"
45
+
46
+ # Body CSS class
47
+ # config.body_class = ""
48
+
49
+ # ─── Advanced UI ─────────────────────────────────────────
50
+
51
+ # Show stats overview cards on the index page
52
+ # config.show_stats = true
53
+
54
+ # Enable keyboard shortcuts (/, j/k, t, ?, Esc, etc.)
55
+ # config.keyboard_shortcuts = true
56
+
57
+ # Show prev/next navigation in detail view
58
+ # config.enable_navigation = true
59
+
60
+ # Number of backtrace frames before collapsing
61
+ # config.backtrace_limit = 30
62
+
63
+ # Show metadata grid in detail view
64
+ # config.show_metadata = true
65
+
66
+ # Show environment variables in detail view
67
+ # config.show_environment = false
68
+
69
+ # Show request params in detail view
70
+ # config.show_request = true
71
+
72
+ # Page size options for the selector
73
+ # config.page_size_options = [25, 50, 100]
74
+
75
+ # ─── Sidebar Links ───────────────────────────────────────
76
+ # Custom links in the sidebar
77
+ # config.sidebar_links = [
78
+ # { label: "GitHub", url: "https://github.com/myorg/myapp" },
79
+ # { label: "Docs", url: "/docs" }
80
+ # ]
81
+ # ─── Database ───────────────────────────────────────────
82
+ # Use a separate database for RailsNexus tables.
83
+ #
84
+ # Option 1: Connection URL
85
+ # config.database_url = "mysql2://user:pass@localhost/rails_nexus"
86
+ #
87
+ # Option 2: Named database (uses config/database.yml entry)
88
+ # config.database_name = "rails_nexus"
89
+ #
90
+ # Option 3: Separate YAML file
91
+ # config.database_yml = "config/rails_nexus_database.yml"
92
+ #
93
+ # Option 4: Table prefix (default: "rails_nexus_")
94
+ # config.table_prefix = "ops_"
95
+ end
96
+ end
@@ -0,0 +1,129 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" class="h-full" data-controller="rails_nexus-theme" data-rails_nexus-theme-theme-value="<%= RailsNexus.configuration.theme %>">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <meta name="color-scheme" content="light dark">
7
+ <title><%= content_for?(:title) ? yield(:title) : RailsNexus.configuration.application_name || "RailsNexus" %></title>
8
+
9
+ <%= stylesheet_link_tag "rails_nexus/application", "data-turbo-track": "reload" %>
10
+ <%= javascript_importmap_tags "rails_nexus/application" %>
11
+
12
+ <% if respond_to?(:turbo_refreshes_with) %>
13
+ <%= turbo_refreshes_with method: :morph, scroll: :preserve %>
14
+ <% end %>
15
+ <%= csrf_meta_tags %>
16
+ <%= csp_meta_tag %>
17
+ </head>
18
+ <body class="h-full <%= RailsNexus.configuration.body_class %>">
19
+ <div class="min-h-full flex">
20
+
21
+ <%# Sidebar %>
22
+ <aside class="rn-sidebar">
23
+ <div class="rn-sidebar-logo">
24
+ <%= link_to root_path, class: "flex items-center gap-2.5" do %>
25
+ <div class="rn-logo-icon">⚡</div>
26
+ <span class="rn-logo-text"><%= RailsNexus.configuration.application_name || "RailsNexus" %></span>
27
+ <% end %>
28
+ </div>
29
+
30
+ <nav class="rn-sidebar-nav">
31
+ <%= link_to root_path,
32
+ class: "rn-nav-item #{request.path == rails_nexus.root_path ? 'active' : ''}" do %>
33
+ <svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6A2.25 2.25 0 016 3.75h2.25A2.25 2.25 0 0110.5 6v2.25a2.25 2.25 0 01-2.25 2.25H6a2.25 2.25 0 01-2.25-2.25V6zM3.75 15.75A2.25 2.25 0 016 13.5h2.25a2.25 2.25 0 012.25 2.25V18a2.25 2.25 0 01-2.25 2.25H6A2.25 2.25 0 013.75 18v-2.25zM13.5 6a2.25 2.25 0 012.25-2.25H18A2.25 2.25 0 0120.25 6v2.25A2.25 2.25 0 0118 10.5h-2.25a2.25 2.25 0 01-2.25-2.25V6zM13.5 15.75a2.25 2.25 0 012.25-2.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-2.25A2.25 2.25 0 0113.5 18v-2.25z" /></svg>
34
+ Dashboard
35
+ <% end %>
36
+
37
+ <%= link_to feed_logged_exceptions_path(format: :rss),
38
+ class: "rn-nav-item" do %>
39
+ <svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12.75 19.5v-.75a7.5 7.5 0 00-7.5-7.5H4.5m0-6.75h.75c7.87 0 14.25 6.38 14.25 14.25v.75M6 18.75a.375.375 0 11-.75 0 .375.375 0 01.75 0z" /></svg>
40
+ RSS Feed
41
+ <% end %>
42
+
43
+ <% RailsNexus.configuration.sidebar_links.each do |link| %>
44
+ <%= link_to link[:url],
45
+ class: "rn-nav-item",
46
+ target: link[:target] || "_self" do %>
47
+ <svg class="w-4 h-4" 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>
48
+ <%= link[:label] %>
49
+ <% end %>
50
+ <% end %>
51
+ </nav>
52
+
53
+ <div class="rn-sidebar-footer">
54
+ v<%= RailsNexus::VERSION %> · <%= RailsNexus::LoggedException.count %> exceptions
55
+ </div>
56
+ </aside>
57
+
58
+ <%# Main Content %>
59
+ <div class="flex-1 flex flex-col min-w-0">
60
+ <header class="rn-topbar">
61
+ <div class="flex items-center gap-3">
62
+ <button type="button" class="lg:hidden rn-icon-btn" data-action="click->rails_nexus-sidebar#toggle">
63
+ <svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" /></svg>
64
+ </button>
65
+ <span class="text-sm font-semibold lg:hidden"><%= RailsNexus.configuration.application_name || "RailsNexus" %></span>
66
+ </div>
67
+
68
+ <div class="flex items-center gap-1.5">
69
+ <button type="button"
70
+ class="rn-icon-btn"
71
+ data-action="click->rails_nexus-theme#toggle"
72
+ title="Toggle theme">
73
+ <svg class="w-4 h-4 block dark:hidden" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M21.752 15.002A9.718 9.718 0 0118 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 003 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 009.002-5.998z" /></svg>
74
+ <svg class="w-4 h-4 hidden dark:block" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 3v2.25m6.364.386l-1.591 1.591M21 12h-2.25m-.386 6.364l-1.591-1.591M12 18.75V21m-4.773-4.227l-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0z" /></svg>
75
+ </button>
76
+
77
+ <% if main_app.respond_to?(:root_path) %>
78
+ <%= link_to main_app.root_path,
79
+ class: "rn-icon-btn",
80
+ title: "Back to app" do %>
81
+ <svg class="w-4 h-4" 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>
82
+ <% end %>
83
+ <% end %>
84
+ </div>
85
+ </header>
86
+
87
+ <% if notice.present? %>
88
+ <div class="mx-4 sm:mx-6 mt-4">
89
+ <div class="rn-alert rn-alert-success">
90
+ <svg class="w-5 h-5 shrink-0" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" /></svg>
91
+ <p class="text-sm font-medium"><%= notice %></p>
92
+ </div>
93
+ </div>
94
+ <% end %>
95
+
96
+ <% if alert.present? %>
97
+ <div class="mx-4 sm:mx-6 mt-4">
98
+ <div class="rn-alert rn-alert-error">
99
+ <svg class="w-5 h-5 shrink-0" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd" /></svg>
100
+ <p class="text-sm font-medium"><%= alert %></p>
101
+ </div>
102
+ </div>
103
+ <% end %>
104
+
105
+ <main class="flex-1 overflow-y-auto p-4 sm:p-6">
106
+ <%= yield %>
107
+ </main>
108
+ </div>
109
+ </div>
110
+
111
+ <div class="rn-overlay" data-rails_nexus-sidebar-target="overlay" data-action="click->rails_nexus-sidebar#close"></div>
112
+ <div class="rn-mobile-sidebar" data-rails_nexus-sidebar-target="drawer">
113
+ <div class="rn-sidebar-logo">
114
+ <span class="rn-logo-text"><%= RailsNexus.configuration.application_name || "RailsNexus" %></span>
115
+ <button type="button" class="rn-icon-btn" data-action="click->rails_nexus-sidebar#close">
116
+ <svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" /></svg>
117
+ </button>
118
+ </div>
119
+ <nav class="rn-sidebar-nav">
120
+ <%= link_to root_path, class: "rn-nav-item" do %>
121
+ <svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6A2.25 2.25 0 016 3.75h2.25A2.25 2.25 0 0110.5 6v2.25a2.25 2.25 0 01-2.25 2.25H6a2.25 2.25 0 01-2.25-2.25V6z" /></svg>
122
+ Dashboard
123
+ <% end %>
124
+ </nav>
125
+ </div>
126
+
127
+ <%= turbo_frame_tag :remote_modal, target: :_top %>
128
+ </body>
129
+ </html>
@@ -0,0 +1,123 @@
1
+ <div id="exceptions-content" class="bg-white rounded-xl border border-gray-200/60 shadow-sm overflow-hidden">
2
+ <% if @exceptions.empty? %>
3
+ <div class="px-6 py-16 text-center">
4
+ <div class="w-10 h-10 rounded-full bg-emerald-50 flex items-center justify-center mx-auto border border-emerald-100">
5
+ <svg class="w-5 h-5 text-emerald-500" 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 text-gray-600"><%= t(".empty") %></p>
10
+ <p class="mt-1 text-xs text-gray-400">No exceptions match your filters.</p>
11
+ </div>
12
+ <% else %>
13
+ <%# Table header with count and bulk actions %>
14
+ <div class="px-4 py-2.5 border-b border-gray-100 flex items-center justify-between bg-gray-50/50">
15
+ <div class="flex items-center gap-3">
16
+ <span class="text-xs font-medium text-gray-500">
17
+ <span class="font-semibold text-gray-700"><%= @pagy.count %></span> exceptions
18
+ <% if filtered? %>
19
+ <span class="text-gray-400">(filtered)</span>
20
+ <% end %>
21
+ </span>
22
+ </div>
23
+ <div class="flex items-center gap-2">
24
+ <%= link_to destroy_all_logged_exceptions_path(params_filters),
25
+ class: "inline-flex items-center gap-1 px-2 py-1 text-[11px] font-medium text-red-600 bg-red-50 border border-red-100 rounded-md hover:bg-red-100 transition-colors",
26
+ data: { turbo_method: :post, turbo_confirm: t(".confirm_delete_visible"), turbo_stream: true } do %>
27
+ <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>
28
+ Delete all
29
+ <% end %>
30
+ </div>
31
+ </div>
32
+
33
+ <div class="overflow-x-auto">
34
+ <table class="min-w-full">
35
+ <thead>
36
+ <tr class="border-b border-gray-100">
37
+ <th class="px-4 py-2 text-left text-[11px] font-semibold text-gray-400 uppercase tracking-wider bg-gray-50/30">
38
+ <%= sort_link @q, :exception_class, "Type", data: { turbo_frame: "exceptions" } %>
39
+ </th>
40
+ <th class="px-4 py-2 text-left text-[11px] font-semibold text-gray-400 uppercase tracking-wider bg-gray-50/30">
41
+ <%= sort_link @q, :controller_name, "Source", data: { turbo_frame: "exceptions" } %>
42
+ </th>
43
+ <th class="px-4 py-2 text-left text-[11px] font-semibold text-gray-400 uppercase tracking-wider bg-gray-50/30">
44
+ <%= sort_link @q, :action_name, "Action", data: { turbo_frame: "exceptions" } %>
45
+ </th>
46
+ <th class="px-4 py-2 text-left text-[11px] font-semibold text-gray-400 uppercase tracking-wider bg-gray-50/30">
47
+ Message
48
+ </th>
49
+ <th class="px-4 py-2 text-left text-[11px] font-semibold text-gray-400 uppercase tracking-wider bg-gray-50/30">
50
+ <%= sort_link @q, :created_at, "Time", data: { turbo_frame: "exceptions" } %>
51
+ </th>
52
+ <th class="px-4 py-2 text-right text-[11px] font-semibold text-gray-400 uppercase tracking-wider bg-gray-50/30">
53
+ <span class="sr-only">Actions</span>
54
+ </th>
55
+ </tr>
56
+ </thead>
57
+ <tbody class="divide-y divide-gray-50">
58
+ <% @exceptions.each do |exception| %>
59
+ <tr id="<%= dom_id(exception) %>" class="group hover:bg-indigo-50/30 transition-colors cursor-pointer"
60
+ data-action="click->rails_nexus#selectRow"
61
+ data-url="<%= logged_exception_path(exception) %>">
62
+ <%# Type badge %>
63
+ <td class="px-4 py-2.5 whitespace-nowrap">
64
+ <span class="inline-flex items-center px-2 py-0.5 rounded text-[11px] font-semibold bg-red-50 text-red-700 border border-red-100/80">
65
+ <%= exception.exception_class %>
66
+ </span>
67
+ </td>
68
+
69
+ <%# Source (controller) %>
70
+ <td class="px-4 py-2.5 whitespace-nowrap">
71
+ <div class="flex items-center gap-1.5">
72
+ <span class="w-1.5 h-1.5 rounded-full bg-gray-300 shrink-0"></span>
73
+ <span class="text-xs font-mono text-gray-600"><%= exception.controller_name %></span>
74
+ </div>
75
+ </td>
76
+
77
+ <%# Action %>
78
+ <td class="px-4 py-2.5 whitespace-nowrap text-xs font-mono text-gray-400">
79
+ <%= exception.action_name %>
80
+ </td>
81
+
82
+ <%# Message %>
83
+ <td class="px-4 py-2.5 text-xs text-gray-500 truncate" style="width: 16rem; max-width: 16rem" title="<%= exception.message %>">
84
+ <%= link_to exception.message, logged_exception_path(exception),
85
+ class: "hover:text-indigo-600 transition-colors",
86
+ data: { turbo_frame: "exception-details" } %>
87
+ </td>
88
+
89
+ <%# Time %>
90
+ <td class="px-4 py-2.5 whitespace-nowrap text-[11px] text-gray-400">
91
+ <%= pretty_exception_date(exception) %>
92
+ </td>
93
+
94
+ <%# Actions %>
95
+ <td class="px-4 py-2.5 whitespace-nowrap text-right">
96
+ <%= link_to logged_exception_path(exception),
97
+ class: "opacity-0 group-hover:opacity-100 text-gray-300 hover:text-red-500 transition-all",
98
+ title: "Delete",
99
+ data: { turbo_method: :delete, turbo_confirm: t(".confirm_delete"), turbo_stream: true, action: "click->rails_nexus#stopPropagation" } do %>
100
+ <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>
101
+ <% end %>
102
+ </td>
103
+ </tr>
104
+ <% end %>
105
+ </tbody>
106
+ </table>
107
+ </div>
108
+
109
+ <%# Pagination %>
110
+ <% if @pagy.pages > 1 %>
111
+ <div class="px-4 py-2.5 border-t border-gray-100 flex items-center justify-between bg-gray-50/30">
112
+ <p class="text-[11px] text-gray-400">
113
+ Showing <span class="font-medium text-gray-600"><%= @pagy.from %></span>
114
+ to <span class="font-medium text-gray-600"><%= @pagy.to %></span>
115
+ of <span class="font-medium text-gray-600"><%= @pagy.count %></span>
116
+ </p>
117
+ <div>
118
+ <%== pagy_nav(@pagy) %>
119
+ </div>
120
+ </div>
121
+ <% end %>
122
+ <% end %>
123
+ </div>