ragdoll-rails 0.1.8 → 0.1.11
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/README.md +18 -21
- data/app/assets/javascripts/ragdoll/application.js +129 -0
- data/app/assets/javascripts/ragdoll/bulk_upload_status.js +454 -0
- data/app/assets/stylesheets/ragdoll/application.css +84 -0
- data/app/assets/stylesheets/ragdoll/bulk_upload_status.css +379 -0
- data/app/channels/application_cable/channel.rb +6 -0
- data/app/channels/application_cable/connection.rb +6 -0
- data/app/channels/ragdoll/bulk_upload_status_channel.rb +27 -0
- data/app/channels/ragdoll/file_processing_channel.rb +26 -0
- data/app/components/ragdoll/alert_component.html.erb +4 -0
- data/app/components/ragdoll/alert_component.rb +32 -0
- data/app/components/ragdoll/application_component.rb +6 -0
- data/app/components/ragdoll/card_component.html.erb +15 -0
- data/app/components/ragdoll/card_component.rb +21 -0
- data/app/components/ragdoll/document_list_component.html.erb +41 -0
- data/app/components/ragdoll/document_list_component.rb +13 -0
- data/app/components/ragdoll/document_table_component.html.erb +76 -0
- data/app/components/ragdoll/document_table_component.rb +13 -0
- data/app/components/ragdoll/empty_state_component.html.erb +12 -0
- data/app/components/ragdoll/empty_state_component.rb +17 -0
- data/app/components/ragdoll/flash_messages_component.html.erb +3 -0
- data/app/components/ragdoll/flash_messages_component.rb +37 -0
- data/app/components/ragdoll/navbar_component.html.erb +24 -0
- data/app/components/ragdoll/navbar_component.rb +31 -0
- data/app/components/ragdoll/page_header_component.html.erb +13 -0
- data/app/components/ragdoll/page_header_component.rb +15 -0
- data/app/components/ragdoll/stats_card_component.html.erb +11 -0
- data/app/components/ragdoll/stats_card_component.rb +17 -0
- data/app/components/ragdoll/status_badge_component.html.erb +3 -0
- data/app/components/ragdoll/status_badge_component.rb +30 -0
- data/app/controllers/ragdoll/api/v1/analytics_controller.rb +72 -0
- data/app/controllers/ragdoll/api/v1/base_controller.rb +29 -0
- data/app/controllers/ragdoll/api/v1/documents_controller.rb +148 -0
- data/app/controllers/ragdoll/api/v1/search_controller.rb +87 -0
- data/app/controllers/ragdoll/api/v1/system_controller.rb +97 -0
- data/app/controllers/ragdoll/application_controller.rb +17 -0
- data/app/controllers/ragdoll/configuration_controller.rb +82 -0
- data/app/controllers/ragdoll/dashboard_controller.rb +98 -0
- data/app/controllers/ragdoll/documents_controller.rb +460 -0
- data/app/controllers/ragdoll/documents_controller_backup.rb +68 -0
- data/app/controllers/ragdoll/jobs_controller.rb +116 -0
- data/app/controllers/ragdoll/search_controller.rb +368 -0
- data/app/jobs/application_job.rb +9 -0
- data/app/jobs/ragdoll/bulk_document_processing_job.rb +280 -0
- data/app/jobs/ragdoll/process_file_job.rb +166 -0
- data/app/services/ragdoll/worker_health_service.rb +111 -0
- data/app/views/layouts/ragdoll/application.html.erb +162 -0
- data/app/views/ragdoll/dashboard/analytics.html.erb +333 -0
- data/app/views/ragdoll/dashboard/index.html.erb +208 -0
- data/app/views/ragdoll/documents/edit.html.erb +91 -0
- data/app/views/ragdoll/documents/index.html.erb +302 -0
- data/app/views/ragdoll/documents/new.html.erb +1518 -0
- data/app/views/ragdoll/documents/show.html.erb +188 -0
- data/app/views/ragdoll/documents/upload_results.html.erb +248 -0
- data/app/views/ragdoll/jobs/index.html.erb +669 -0
- data/app/views/ragdoll/jobs/show.html.erb +129 -0
- data/app/views/ragdoll/search/index.html.erb +324 -0
- data/config/cable.yml +12 -0
- data/config/routes.rb +57 -2
- data/lib/generators/ragdoll/init/templates/INSTALL +3 -2
- data/lib/generators/ragdoll/init_generator.rb +68 -0
- data/lib/ragdoll/rails/engine.rb +48 -0
- data/lib/ragdoll/rails/version.rb +1 -1
- metadata +231 -6
- data/lib/generators/ragdoll/init/init_generator.rb +0 -26
@@ -0,0 +1,166 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ragdoll
|
4
|
+
class ProcessFileJob < ApplicationJob
|
5
|
+
queue_as :default
|
6
|
+
|
7
|
+
def perform(file_id, session_id, filename, temp_path)
|
8
|
+
::Rails.logger.info "🚀 Ragdoll::ProcessFileJob starting: file_id=#{file_id}, session_id=#{session_id}, filename=#{filename}"
|
9
|
+
::Rails.logger.info "📁 Temp file path: #{temp_path}"
|
10
|
+
::Rails.logger.info "📊 Temp file exists: #{File.exist?(temp_path)}"
|
11
|
+
::Rails.logger.info "📏 Temp file size: #{File.exist?(temp_path) ? File.size(temp_path) : 'N/A'} bytes"
|
12
|
+
|
13
|
+
begin
|
14
|
+
# Verify temp file exists before processing
|
15
|
+
unless File.exist?(temp_path)
|
16
|
+
raise "Temporary file not found: #{temp_path}"
|
17
|
+
end
|
18
|
+
|
19
|
+
# Broadcast start
|
20
|
+
broadcast_data = {
|
21
|
+
file_id: file_id,
|
22
|
+
filename: filename,
|
23
|
+
status: 'started',
|
24
|
+
progress: 0,
|
25
|
+
message: 'Starting file processing...'
|
26
|
+
}
|
27
|
+
|
28
|
+
::Rails.logger.info "📡 Broadcasting start: #{broadcast_data}"
|
29
|
+
begin
|
30
|
+
ActionCable.server.broadcast("ragdoll_file_processing_#{session_id}", broadcast_data)
|
31
|
+
::Rails.logger.info "✅ ActionCable broadcast sent successfully"
|
32
|
+
|
33
|
+
# Track job start in monitoring system
|
34
|
+
track_job_progress(session_id, file_id, filename, 0, 'started')
|
35
|
+
rescue => e
|
36
|
+
::Rails.logger.error "❌ ActionCable broadcast failed: #{e.message}"
|
37
|
+
::Rails.logger.error e.backtrace.first(3)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Simulate progress updates during processing
|
41
|
+
broadcast_progress(session_id, file_id, filename, 25, 'Reading file...')
|
42
|
+
track_job_progress(session_id, file_id, filename, 25, 'processing')
|
43
|
+
|
44
|
+
# Use Ragdoll to add document
|
45
|
+
result = ::Ragdoll.add_document(path: temp_path)
|
46
|
+
|
47
|
+
broadcast_progress(session_id, file_id, filename, 75, 'Generating embeddings...')
|
48
|
+
track_job_progress(session_id, file_id, filename, 75, 'processing')
|
49
|
+
|
50
|
+
if result[:success] && result[:document_id]
|
51
|
+
document = ::Ragdoll::Document.find(result[:document_id])
|
52
|
+
|
53
|
+
# Broadcast completion
|
54
|
+
completion_data = {
|
55
|
+
file_id: file_id,
|
56
|
+
filename: filename,
|
57
|
+
status: 'completed',
|
58
|
+
progress: 100,
|
59
|
+
message: 'Processing completed successfully',
|
60
|
+
document_id: document.id
|
61
|
+
}
|
62
|
+
|
63
|
+
::Rails.logger.info "🎉 Broadcasting completion: #{completion_data}"
|
64
|
+
begin
|
65
|
+
ActionCable.server.broadcast("ragdoll_file_processing_#{session_id}", completion_data)
|
66
|
+
::Rails.logger.info "✅ Completion broadcast sent successfully"
|
67
|
+
|
68
|
+
# Mark job as completed in monitoring system
|
69
|
+
mark_job_completed(session_id, file_id)
|
70
|
+
rescue => e
|
71
|
+
::Rails.logger.error "❌ Completion broadcast failed: #{e.message}"
|
72
|
+
end
|
73
|
+
else
|
74
|
+
raise "Processing failed: #{result[:error] || 'Unknown error'}"
|
75
|
+
end
|
76
|
+
|
77
|
+
rescue => e
|
78
|
+
::Rails.logger.error "💥 Ragdoll::ProcessFileJob error: #{e.message}"
|
79
|
+
::Rails.logger.error e.backtrace.first(5)
|
80
|
+
|
81
|
+
# Broadcast error
|
82
|
+
error_data = {
|
83
|
+
file_id: file_id,
|
84
|
+
filename: filename,
|
85
|
+
status: 'error',
|
86
|
+
progress: 0,
|
87
|
+
message: "Error: #{e.message}"
|
88
|
+
}
|
89
|
+
|
90
|
+
::Rails.logger.info "📡 Broadcasting error: #{error_data}"
|
91
|
+
begin
|
92
|
+
ActionCable.server.broadcast("ragdoll_file_processing_#{session_id}", error_data)
|
93
|
+
::Rails.logger.info "✅ Error broadcast sent successfully"
|
94
|
+
|
95
|
+
# Mark job as failed in monitoring system
|
96
|
+
mark_job_failed(session_id, file_id)
|
97
|
+
rescue => e
|
98
|
+
::Rails.logger.error "❌ Error broadcast failed: #{e.message}"
|
99
|
+
end
|
100
|
+
|
101
|
+
# Re-raise the error to mark job as failed
|
102
|
+
raise e
|
103
|
+
ensure
|
104
|
+
# ALWAYS clean up temp file in ensure block
|
105
|
+
if temp_path && File.exist?(temp_path)
|
106
|
+
::Rails.logger.info "🧹 Cleaning up temp file: #{temp_path}"
|
107
|
+
begin
|
108
|
+
File.delete(temp_path)
|
109
|
+
::Rails.logger.info "✅ Temp file deleted successfully"
|
110
|
+
rescue => e
|
111
|
+
::Rails.logger.error "❌ Failed to delete temp file: #{e.message}"
|
112
|
+
end
|
113
|
+
else
|
114
|
+
::Rails.logger.info "📝 Temp file already cleaned up or doesn't exist: #{temp_path}"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
private
|
120
|
+
|
121
|
+
def broadcast_progress(session_id, file_id, filename, progress, message)
|
122
|
+
broadcast_data = {
|
123
|
+
file_id: file_id,
|
124
|
+
filename: filename,
|
125
|
+
status: 'processing',
|
126
|
+
progress: progress,
|
127
|
+
message: message
|
128
|
+
}
|
129
|
+
|
130
|
+
::Rails.logger.info "📡 Broadcasting progress: #{broadcast_data}"
|
131
|
+
begin
|
132
|
+
ActionCable.server.broadcast("ragdoll_file_processing_#{session_id}", broadcast_data)
|
133
|
+
::Rails.logger.info "✅ Progress broadcast sent successfully"
|
134
|
+
rescue => e
|
135
|
+
::Rails.logger.error "❌ Progress broadcast failed: #{e.message}"
|
136
|
+
end
|
137
|
+
|
138
|
+
# Small delay to simulate processing time
|
139
|
+
sleep(0.5)
|
140
|
+
end
|
141
|
+
|
142
|
+
def track_job_progress(session_id, file_id, filename, progress, status)
|
143
|
+
if defined?(JobFailureMonitorService)
|
144
|
+
JobFailureMonitorService.track_job_progress(session_id, file_id, filename, progress, status)
|
145
|
+
end
|
146
|
+
rescue => e
|
147
|
+
::Rails.logger.error "❌ Failed to track job progress: #{e.message}"
|
148
|
+
end
|
149
|
+
|
150
|
+
def mark_job_completed(session_id, file_id)
|
151
|
+
if defined?(JobFailureMonitorService)
|
152
|
+
JobFailureMonitorService.mark_job_completed(session_id, file_id)
|
153
|
+
end
|
154
|
+
rescue => e
|
155
|
+
::Rails.logger.error "❌ Failed to mark job as completed: #{e.message}"
|
156
|
+
end
|
157
|
+
|
158
|
+
def mark_job_failed(session_id, file_id)
|
159
|
+
if defined?(JobFailureMonitorService)
|
160
|
+
JobFailureMonitorService.mark_job_failed(session_id, file_id)
|
161
|
+
end
|
162
|
+
rescue => e
|
163
|
+
::Rails.logger.error "❌ Failed to mark job as failed: #{e.message}"
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ragdoll
|
4
|
+
class WorkerHealthService
|
5
|
+
class << self
|
6
|
+
def check_worker_health
|
7
|
+
{
|
8
|
+
status: 'healthy',
|
9
|
+
workers: worker_status,
|
10
|
+
queues: queue_status,
|
11
|
+
timestamp: Time.current
|
12
|
+
}
|
13
|
+
rescue => e
|
14
|
+
{
|
15
|
+
status: 'error',
|
16
|
+
error: e.message,
|
17
|
+
timestamp: Time.current
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
def needs_restart?
|
22
|
+
# Check if there are stuck jobs or workers
|
23
|
+
stuck_jobs_count > 5 || !workers_running?
|
24
|
+
rescue
|
25
|
+
false
|
26
|
+
end
|
27
|
+
|
28
|
+
def process_stuck_jobs!(limit = 10)
|
29
|
+
processed = 0
|
30
|
+
|
31
|
+
# Find jobs that have been processing for too long (e.g., > 1 hour)
|
32
|
+
if defined?(SolidQueue::Job)
|
33
|
+
stuck_jobs = SolidQueue::Job
|
34
|
+
.where(finished_at: nil)
|
35
|
+
.where('created_at < ?', 1.hour.ago)
|
36
|
+
.limit(limit)
|
37
|
+
|
38
|
+
stuck_jobs.each do |job|
|
39
|
+
job.update(finished_at: Time.current)
|
40
|
+
processed += 1
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
processed
|
45
|
+
rescue => e
|
46
|
+
::Rails.logger.error "Failed to process stuck jobs: #{e.message}"
|
47
|
+
0
|
48
|
+
end
|
49
|
+
|
50
|
+
def restart_workers!
|
51
|
+
# In development, we typically don't restart workers
|
52
|
+
# This would be implemented differently in production
|
53
|
+
::Rails.logger.info "Worker restart requested (no-op in development)"
|
54
|
+
true
|
55
|
+
rescue => e
|
56
|
+
::Rails.logger.error "Failed to restart workers: #{e.message}"
|
57
|
+
false
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def worker_status
|
63
|
+
if defined?(SolidQueue::Worker)
|
64
|
+
{
|
65
|
+
count: SolidQueue::Worker.count,
|
66
|
+
active: SolidQueue::Worker.where('last_heartbeat_at > ?', 5.minutes.ago).count
|
67
|
+
}
|
68
|
+
else
|
69
|
+
{ count: 0, active: 0 }
|
70
|
+
end
|
71
|
+
rescue
|
72
|
+
{ count: 0, active: 0, error: 'Unable to check worker status' }
|
73
|
+
end
|
74
|
+
|
75
|
+
def queue_status
|
76
|
+
if defined?(SolidQueue::Job)
|
77
|
+
{
|
78
|
+
pending: SolidQueue::Job.where(finished_at: nil).count,
|
79
|
+
completed: SolidQueue::Job.where.not(finished_at: nil).count,
|
80
|
+
failed: defined?(SolidQueue::FailedExecution) ? SolidQueue::FailedExecution.count : 0
|
81
|
+
}
|
82
|
+
else
|
83
|
+
{ pending: 0, completed: 0, failed: 0 }
|
84
|
+
end
|
85
|
+
rescue
|
86
|
+
{ pending: 0, completed: 0, failed: 0, error: 'Unable to check queue status' }
|
87
|
+
end
|
88
|
+
|
89
|
+
def stuck_jobs_count
|
90
|
+
return 0 unless defined?(SolidQueue::Job)
|
91
|
+
|
92
|
+
SolidQueue::Job
|
93
|
+
.where(finished_at: nil)
|
94
|
+
.where('created_at < ?', 1.hour.ago)
|
95
|
+
.count
|
96
|
+
rescue
|
97
|
+
0
|
98
|
+
end
|
99
|
+
|
100
|
+
def workers_running?
|
101
|
+
return false unless defined?(SolidQueue::Worker)
|
102
|
+
|
103
|
+
SolidQueue::Worker
|
104
|
+
.where('last_heartbeat_at > ?', 5.minutes.ago)
|
105
|
+
.exists?
|
106
|
+
rescue
|
107
|
+
true # Assume workers are running if we can't check
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title><%= content_for(:title) || "Ragdoll Dashboard" %></title>
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
6
|
+
<meta name="apple-mobile-web-app-capable" content="yes">
|
7
|
+
<meta name="mobile-web-app-capable" content="yes">
|
8
|
+
<%= csrf_meta_tags %>
|
9
|
+
<%= csp_meta_tag %>
|
10
|
+
<meta name="csrf-token" content="<%= form_authenticity_token %>">
|
11
|
+
|
12
|
+
<%= yield :head %>
|
13
|
+
|
14
|
+
<%= favicon_link_tag "ragdoll.png", type: "image/png" %>
|
15
|
+
<%= favicon_link_tag "ragdoll.png", rel: "apple-touch-icon" %>
|
16
|
+
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
|
17
|
+
<%= stylesheet_link_tag "ragdoll/bulk_upload_status", "data-turbo-track": "reload" %>
|
18
|
+
|
19
|
+
<!-- Bootstrap CSS from CDN -->
|
20
|
+
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
21
|
+
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
22
|
+
|
23
|
+
<!-- Rails UJS for handling method: :delete and data-confirm -->
|
24
|
+
<script src="https://cdn.jsdelivr.net/npm/@rails/ujs@7.0.0/lib/assets/compiled/rails-ujs.js"></script>
|
25
|
+
|
26
|
+
<!-- ActionCable from CDN with fallback handling -->
|
27
|
+
<script src="https://cdn.jsdelivr.net/gh/rails/rails@7-1-stable/actioncable/app/assets/javascripts/action_cable.js"></script>
|
28
|
+
|
29
|
+
<!-- Fallback CSS for when CDN is blocked -->
|
30
|
+
<style>
|
31
|
+
.container { max-width: 1200px; margin: 0 auto; padding: 0 15px; }
|
32
|
+
.row { display: flex; flex-wrap: wrap; margin: 0 -15px; }
|
33
|
+
.col-md-8 { flex: 0 0 66.666667%; max-width: 66.666667%; padding: 0 15px; }
|
34
|
+
.col-md-4 { flex: 0 0 33.333333%; max-width: 33.333333%; padding: 0 15px; }
|
35
|
+
.col-12 { flex: 0 0 100%; max-width: 100%; padding: 0 15px; }
|
36
|
+
.card { border: 1px solid #dee2e6; border-radius: 0.375rem; margin-bottom: 1rem; }
|
37
|
+
.card-header { padding: 0.75rem 1.25rem; background-color: #f8f9fa; border-bottom: 1px solid #dee2e6; }
|
38
|
+
.card-body { padding: 1.25rem; }
|
39
|
+
.btn { display: inline-block; padding: 0.375rem 0.75rem; font-size: 1rem; line-height: 1.5; border-radius: 0.375rem; border: 1px solid; text-decoration: none; cursor: pointer; }
|
40
|
+
.btn-primary { color: #fff; background-color: #007bff; border-color: #007bff; }
|
41
|
+
.navbar { padding: 0.5rem 0; background-color: #007bff !important; }
|
42
|
+
.navbar-brand { color: #fff !important; text-decoration: none; font-weight: bold; }
|
43
|
+
.nav-link { color: #fff !important; text-decoration: none; padding: 0.5rem 1rem; }
|
44
|
+
.alert { padding: 0.75rem 1.25rem; margin-bottom: 1rem; border: 1px solid; border-radius: 0.375rem; }
|
45
|
+
.alert-success { color: #155724; background-color: #d4edda; border-color: #c3e6cb; }
|
46
|
+
.alert-danger { color: #721c24; background-color: #f8d7da; border-color: #f5c6cb; }
|
47
|
+
.alert-info { color: #0c5460; background-color: #d1ecf1; border-color: #bee5eb; }
|
48
|
+
</style>
|
49
|
+
</head>
|
50
|
+
|
51
|
+
<body>
|
52
|
+
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
53
|
+
<div class="container">
|
54
|
+
<%= link_to "Ragdoll Dashboard", ragdoll.root_path, class: "navbar-brand" %>
|
55
|
+
|
56
|
+
<div class="navbar-nav ms-auto">
|
57
|
+
<%= link_to "Dashboard", ragdoll.dashboard_index_path, class: "nav-link" %>
|
58
|
+
<%= link_to "Documents", ragdoll.documents_path, class: "nav-link" %>
|
59
|
+
<%= link_to "Jobs", ragdoll.jobs_path, class: "nav-link" %>
|
60
|
+
<%= link_to "Search", ragdoll.search_index_path, class: "nav-link" %>
|
61
|
+
<%= link_to "Analytics", ragdoll.analytics_path, class: "nav-link" %>
|
62
|
+
<%= link_to "Back to Demo", main_app.root_path, class: "nav-link" %>
|
63
|
+
</div>
|
64
|
+
</div>
|
65
|
+
</nav>
|
66
|
+
|
67
|
+
<% if flash.any? %>
|
68
|
+
<div class="container mt-3">
|
69
|
+
<% flash.each do |type, message| %>
|
70
|
+
<div class="alert alert-<%= type == 'notice' ? 'success' : type == 'alert' ? 'danger' : 'info' %> alert-dismissible fade show" role="alert">
|
71
|
+
<%= message %>
|
72
|
+
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
73
|
+
</div>
|
74
|
+
<% end %>
|
75
|
+
</div>
|
76
|
+
<% end %>
|
77
|
+
|
78
|
+
<main>
|
79
|
+
<%= yield %>
|
80
|
+
</main>
|
81
|
+
|
82
|
+
<!-- JavaScript with fallback support -->
|
83
|
+
<script>
|
84
|
+
// Check if CDN resources loaded, provide fallbacks if not
|
85
|
+
console.log('🔧 Checking JavaScript dependencies...');
|
86
|
+
|
87
|
+
// ActionCable fallback and App global setup
|
88
|
+
window.App = window.App || {};
|
89
|
+
|
90
|
+
if (typeof ActionCable === 'undefined') {
|
91
|
+
console.warn('ActionCable not loaded from CDN, using minimal fallback');
|
92
|
+
window.ActionCable = {
|
93
|
+
createConsumer: function(url) {
|
94
|
+
console.log('ActionCable fallback: WebSocket functionality disabled');
|
95
|
+
return {
|
96
|
+
subscriptions: {
|
97
|
+
create: function(channel, callbacks) {
|
98
|
+
console.log('ActionCable fallback: Channel subscription disabled', channel);
|
99
|
+
if (callbacks && callbacks.connected) {
|
100
|
+
setTimeout(() => callbacks.connected(), 100);
|
101
|
+
}
|
102
|
+
return {
|
103
|
+
unsubscribe: function() { console.log('ActionCable fallback: Unsubscribe'); }
|
104
|
+
};
|
105
|
+
}
|
106
|
+
},
|
107
|
+
disconnect: function() { console.log('ActionCable fallback: Disconnect'); }
|
108
|
+
};
|
109
|
+
}
|
110
|
+
};
|
111
|
+
} else {
|
112
|
+
console.log('✅ ActionCable loaded from CDN');
|
113
|
+
}
|
114
|
+
|
115
|
+
// Ensure App.cable is available regardless of loading method
|
116
|
+
if (!window.App) {
|
117
|
+
window.App = {};
|
118
|
+
}
|
119
|
+
|
120
|
+
if (typeof ActionCable !== 'undefined' && !window.App.cable) {
|
121
|
+
try {
|
122
|
+
window.App.cable = ActionCable.createConsumer('/cable');
|
123
|
+
console.log('✅ App.cable initialized from CDN ActionCable');
|
124
|
+
console.log('✅ App.cable consumer ready:', window.App.cable);
|
125
|
+
} catch (e) {
|
126
|
+
console.error('❌ Failed to initialize App.cable:', e);
|
127
|
+
}
|
128
|
+
} else if (window.App.cable) {
|
129
|
+
console.log('✅ App.cable already initialized');
|
130
|
+
}
|
131
|
+
|
132
|
+
console.log('🚀 JavaScript dependencies check complete');
|
133
|
+
|
134
|
+
// Additional CDN loading diagnostics
|
135
|
+
console.log('📊 CDN Resource Status:');
|
136
|
+
console.log('- ActionCable CDN loaded:', typeof ActionCable !== 'undefined');
|
137
|
+
console.log('- Bootstrap CDN loaded:', typeof bootstrap !== 'undefined');
|
138
|
+
console.log('- Chart.js CDN loaded:', typeof Chart !== 'undefined');
|
139
|
+
|
140
|
+
console.log('🔧 Fallback system status:');
|
141
|
+
console.log('- ActionCable fallback:', window.ActionCable ? 'active' : 'inactive');
|
142
|
+
|
143
|
+
// Initialize Rails UJS
|
144
|
+
if (typeof Rails !== 'undefined') {
|
145
|
+
Rails.start();
|
146
|
+
console.log('✅ Rails UJS initialized');
|
147
|
+
} else {
|
148
|
+
console.warn('⚠️ Rails UJS not available');
|
149
|
+
}
|
150
|
+
</script>
|
151
|
+
|
152
|
+
<!-- JavaScript from CDN -->
|
153
|
+
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
154
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
155
|
+
|
156
|
+
<%= javascript_importmap_tags %>
|
157
|
+
<%= javascript_include_tag 'ragdoll/application', 'data-turbo-track': 'reload' %>
|
158
|
+
<%= javascript_include_tag 'ragdoll/bulk_upload_status', 'data-turbo-track': 'reload' %>
|
159
|
+
|
160
|
+
<%= yield :javascript %>
|
161
|
+
</body>
|
162
|
+
</html>
|