ragdoll-rails 0.1.9 → 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/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 +56 -1
- data/lib/ragdoll/rails/engine.rb +32 -1
- data/lib/ragdoll/rails/version.rb +1 -1
- metadata +86 -1
@@ -0,0 +1,208 @@
|
|
1
|
+
<% content_for :title, "Dashboard - Ragdoll Engine" %>
|
2
|
+
|
3
|
+
<%= render Ragdoll::PageHeaderComponent.new(
|
4
|
+
title: "Ragdoll Engine Dashboard",
|
5
|
+
icon: "fas fa-tachometer-alt",
|
6
|
+
subtitle: "Overview of your document processing and search system"
|
7
|
+
) %>
|
8
|
+
|
9
|
+
<div class="row mb-4">
|
10
|
+
<div class="col-md-3">
|
11
|
+
<%= render Ragdoll::StatsCardComponent.new(
|
12
|
+
title: "Documents",
|
13
|
+
value: @stats[:total_documents],
|
14
|
+
icon: "fas fa-file-alt",
|
15
|
+
color: "primary",
|
16
|
+
description: "Total documents"
|
17
|
+
) %>
|
18
|
+
</div>
|
19
|
+
<div class="col-md-3">
|
20
|
+
<%= render Ragdoll::StatsCardComponent.new(
|
21
|
+
title: "Processed",
|
22
|
+
value: @stats[:processed_documents],
|
23
|
+
icon: "fas fa-check-circle",
|
24
|
+
color: "success",
|
25
|
+
description: "Successfully processed"
|
26
|
+
) %>
|
27
|
+
</div>
|
28
|
+
<div class="col-md-3">
|
29
|
+
<%= render Ragdoll::StatsCardComponent.new(
|
30
|
+
title: "Embeddings",
|
31
|
+
value: @stats[:total_embeddings],
|
32
|
+
icon: "fas fa-vector-square",
|
33
|
+
color: "info",
|
34
|
+
description: "Vector embeddings"
|
35
|
+
) %>
|
36
|
+
</div>
|
37
|
+
<div class="col-md-3">
|
38
|
+
<%= render Ragdoll::StatsCardComponent.new(
|
39
|
+
title: "Searches",
|
40
|
+
value: @stats[:total_searches],
|
41
|
+
icon: "fas fa-search",
|
42
|
+
color: "warning",
|
43
|
+
description: "Total searches"
|
44
|
+
) %>
|
45
|
+
</div>
|
46
|
+
</div>
|
47
|
+
|
48
|
+
<div class="row">
|
49
|
+
<div class="col-md-6">
|
50
|
+
<%= render Ragdoll::CardComponent.new(title: "Document Types", icon: "fas fa-chart-pie") do %>
|
51
|
+
<% if @document_types.any? %>
|
52
|
+
<canvas id="documentTypesChart" width="400" height="200"></canvas>
|
53
|
+
<% else %>
|
54
|
+
<p class="text-muted">No documents yet. <%= link_to "Add your first document", ragdoll.new_document_path, class: "btn btn-primary btn-sm" %></p>
|
55
|
+
<% end %>
|
56
|
+
<% end %>
|
57
|
+
</div>
|
58
|
+
|
59
|
+
<div class="col-md-6">
|
60
|
+
<%= render Ragdoll::CardComponent.new(title: "Most Searched Documents", icon: "fas fa-fire") do %>
|
61
|
+
<% if @top_searched_documents.any? %>
|
62
|
+
<% @top_searched_documents.each do |title, count| %>
|
63
|
+
<div class="d-flex justify-content-between align-items-center mb-2">
|
64
|
+
<span class="text-truncate"><%= title %></span>
|
65
|
+
<span class="badge bg-primary"><%= count %></span>
|
66
|
+
</div>
|
67
|
+
<% end %>
|
68
|
+
<% else %>
|
69
|
+
<p class="text-muted">No search data yet. <%= link_to "Try searching", ragdoll.search_index_path, class: "btn btn-primary btn-sm" %></p>
|
70
|
+
<% end %>
|
71
|
+
<% end %>
|
72
|
+
</div>
|
73
|
+
</div>
|
74
|
+
|
75
|
+
<div class="row mt-4">
|
76
|
+
<div class="col-md-6">
|
77
|
+
<div class="card">
|
78
|
+
<div class="card-header">
|
79
|
+
<h5><i class="fas fa-clock"></i> Recent Documents</h5>
|
80
|
+
</div>
|
81
|
+
<div class="card-body">
|
82
|
+
<% if @recent_documents.any? %>
|
83
|
+
<% @recent_documents.each do |document| %>
|
84
|
+
<div class="d-flex justify-content-between align-items-center mb-2">
|
85
|
+
<div>
|
86
|
+
<%= link_to document.title, ragdoll.document_path(document), class: "text-decoration-none" %>
|
87
|
+
<small class="text-muted d-block">
|
88
|
+
<%= document.document_type&.upcase %> •
|
89
|
+
<%= render Ragdoll::StatusBadgeComponent.new(status: document.status) %>
|
90
|
+
</small>
|
91
|
+
</div>
|
92
|
+
<small class="text-muted"><%= time_ago_in_words(document.created_at) %> ago</small>
|
93
|
+
</div>
|
94
|
+
<% end %>
|
95
|
+
<div class="mt-3">
|
96
|
+
<%= link_to "View all documents", ragdoll.documents_path, class: "btn btn-outline-primary btn-sm" %>
|
97
|
+
</div>
|
98
|
+
<% else %>
|
99
|
+
<p class="text-muted">No documents yet. <%= link_to "Add your first document", ragdoll.new_document_path, class: "btn btn-primary btn-sm" %></p>
|
100
|
+
<% end %>
|
101
|
+
</div>
|
102
|
+
</div>
|
103
|
+
</div>
|
104
|
+
|
105
|
+
<div class="col-md-6">
|
106
|
+
<div class="card">
|
107
|
+
<div class="card-header">
|
108
|
+
<h5><i class="fas fa-history"></i> Recent Searches</h5>
|
109
|
+
</div>
|
110
|
+
<div class="card-body">
|
111
|
+
<% if @stats[:recent_searches].any? %>
|
112
|
+
<% @stats[:recent_searches].each do |search| %>
|
113
|
+
<div class="d-flex justify-content-between align-items-center mb-2">
|
114
|
+
<div>
|
115
|
+
<a href="<%= ragdoll.search_index_path %>?search_id=<%= search.id %>" class="text-decoration-none">
|
116
|
+
<i class="fas fa-search me-1"></i><strong><%= search.query %></strong>
|
117
|
+
</a>
|
118
|
+
<small class="text-muted d-block">
|
119
|
+
<%= search.search_type.titleize %>
|
120
|
+
• <%= search.results_count %> results
|
121
|
+
<% if search.execution_time_ms %>
|
122
|
+
• <%= number_with_precision(search.execution_time_ms / 1000.0, precision: 3) %>s
|
123
|
+
<% end %>
|
124
|
+
</small>
|
125
|
+
</div>
|
126
|
+
<small class="text-muted"><%= time_ago_in_words(search.created_at) %> ago</small>
|
127
|
+
</div>
|
128
|
+
<% end %>
|
129
|
+
<div class="mt-3">
|
130
|
+
<%= link_to "View search analytics", ragdoll.analytics_path, class: "btn btn-outline-primary btn-sm" %>
|
131
|
+
</div>
|
132
|
+
<% else %>
|
133
|
+
<p class="text-muted">No searches yet. <%= link_to "Try searching", ragdoll.search_index_path, class: "btn btn-primary btn-sm" %></p>
|
134
|
+
<% end %>
|
135
|
+
</div>
|
136
|
+
</div>
|
137
|
+
</div>
|
138
|
+
</div>
|
139
|
+
|
140
|
+
<div class="row mt-4">
|
141
|
+
<div class="col-12">
|
142
|
+
<div class="card">
|
143
|
+
<div class="card-header">
|
144
|
+
<h5><i class="fas fa-tools"></i> Quick Actions</h5>
|
145
|
+
</div>
|
146
|
+
<div class="card-body">
|
147
|
+
<div class="row">
|
148
|
+
<div class="col-md-3 mb-2">
|
149
|
+
<%= link_to ragdoll.new_document_path, class: "btn btn-primary w-100" do %>
|
150
|
+
<i class="fas fa-plus"></i> Add Document
|
151
|
+
<% end %>
|
152
|
+
</div>
|
153
|
+
<div class="col-md-3 mb-2">
|
154
|
+
<%= link_to ragdoll.search_index_path, class: "btn btn-success w-100" do %>
|
155
|
+
<i class="fas fa-search"></i> Search Documents
|
156
|
+
<% end %>
|
157
|
+
</div>
|
158
|
+
<div class="col-md-3 mb-2">
|
159
|
+
<%= link_to ragdoll.analytics_path, class: "btn btn-info w-100" do %>
|
160
|
+
<i class="fas fa-chart-line"></i> View Analytics
|
161
|
+
<% end %>
|
162
|
+
</div>
|
163
|
+
<div class="col-md-3 mb-2">
|
164
|
+
<%= link_to ragdoll.configuration_path, class: "btn btn-warning w-100" do %>
|
165
|
+
<i class="fas fa-cog"></i> Configuration
|
166
|
+
<% end %>
|
167
|
+
</div>
|
168
|
+
</div>
|
169
|
+
</div>
|
170
|
+
</div>
|
171
|
+
</div>
|
172
|
+
</div>
|
173
|
+
|
174
|
+
<% if @document_types.any? %>
|
175
|
+
<%= content_for :javascript do %>
|
176
|
+
<script>
|
177
|
+
document.addEventListener('DOMContentLoaded', function() {
|
178
|
+
const ctx = document.getElementById('documentTypesChart').getContext('2d');
|
179
|
+
const documentTypesChart = new Chart(ctx, {
|
180
|
+
type: 'doughnut',
|
181
|
+
data: {
|
182
|
+
labels: <%= @document_types.keys.to_json.html_safe %>,
|
183
|
+
datasets: [{
|
184
|
+
data: <%= @document_types.values.to_json.html_safe %>,
|
185
|
+
backgroundColor: [
|
186
|
+
'#FF6384',
|
187
|
+
'#36A2EB',
|
188
|
+
'#FFCE56',
|
189
|
+
'#4BC0C0',
|
190
|
+
'#9966FF',
|
191
|
+
'#FF9F40'
|
192
|
+
]
|
193
|
+
}]
|
194
|
+
},
|
195
|
+
options: {
|
196
|
+
responsive: true,
|
197
|
+
maintainAspectRatio: false,
|
198
|
+
plugins: {
|
199
|
+
legend: {
|
200
|
+
position: 'bottom'
|
201
|
+
}
|
202
|
+
}
|
203
|
+
}
|
204
|
+
});
|
205
|
+
});
|
206
|
+
</script>
|
207
|
+
<% end %>
|
208
|
+
<% end %>
|
@@ -0,0 +1,91 @@
|
|
1
|
+
<% content_for(:title, "Edit Document: #{@document.title}") %>
|
2
|
+
|
3
|
+
<div class="container-fluid mt-4">
|
4
|
+
<div class="row">
|
5
|
+
<div class="col-md-8 mx-auto">
|
6
|
+
<div class="card">
|
7
|
+
<div class="card-header">
|
8
|
+
<h4 class="card-title mb-0">
|
9
|
+
<i class="fas fa-edit"></i> Edit Document
|
10
|
+
</h4>
|
11
|
+
</div>
|
12
|
+
<div class="card-body">
|
13
|
+
<%= form_with model: @document, url: ragdoll.document_path(@document), local: true do |form| %>
|
14
|
+
<% if @document.errors.any? %>
|
15
|
+
<div class="alert alert-danger" role="alert">
|
16
|
+
<h6 class="alert-heading">Please fix the following errors:</h6>
|
17
|
+
<ul class="mb-0">
|
18
|
+
<% @document.errors.full_messages.each do |message| %>
|
19
|
+
<li><%= message %></li>
|
20
|
+
<% end %>
|
21
|
+
</ul>
|
22
|
+
</div>
|
23
|
+
<% end %>
|
24
|
+
|
25
|
+
<div class="mb-3">
|
26
|
+
<%= form.label :title, class: "form-label" %>
|
27
|
+
<%= form.text_field :title, class: "form-control", placeholder: "Enter document title" %>
|
28
|
+
</div>
|
29
|
+
|
30
|
+
<div class="mb-3">
|
31
|
+
<%= form.label :summary, class: "form-label" %>
|
32
|
+
<%= form.text_area :summary, class: "form-control", rows: 3, placeholder: "Enter document summary (optional)" %>
|
33
|
+
</div>
|
34
|
+
|
35
|
+
<div class="mb-3">
|
36
|
+
<%= form.label :keywords, class: "form-label" %>
|
37
|
+
<%= form.text_field :keywords, class: "form-control", placeholder: "Enter keywords separated by commas" %>
|
38
|
+
<small class="form-text text-muted">Separate keywords with commas</small>
|
39
|
+
</div>
|
40
|
+
|
41
|
+
<div class="mb-3">
|
42
|
+
<%= form.label :status, class: "form-label" %>
|
43
|
+
<%= form.select :status, options_for_select([
|
44
|
+
['Pending', 'pending'],
|
45
|
+
['Processing', 'processing'],
|
46
|
+
['Processed', 'processed'],
|
47
|
+
['Failed', 'failed']
|
48
|
+
], @document.status), {}, class: "form-select" %>
|
49
|
+
</div>
|
50
|
+
|
51
|
+
<% if @document.location.present? %>
|
52
|
+
<div class="mb-3">
|
53
|
+
<label class="form-label">File Location</label>
|
54
|
+
<div class="input-group">
|
55
|
+
<input type="text" class="form-control" value="<%= @document.location %>" readonly>
|
56
|
+
<span class="input-group-text"><i class="fas fa-lock"></i></span>
|
57
|
+
</div>
|
58
|
+
<small class="form-text text-muted">File location cannot be changed</small>
|
59
|
+
</div>
|
60
|
+
<% end %>
|
61
|
+
|
62
|
+
<% if @document.metadata.present? %>
|
63
|
+
<div class="mb-3">
|
64
|
+
<label class="form-label">Metadata</label>
|
65
|
+
<div class="card bg-light">
|
66
|
+
<div class="card-body">
|
67
|
+
<% @document.metadata.each do |key, value| %>
|
68
|
+
<div class="row mb-2">
|
69
|
+
<div class="col-sm-4">
|
70
|
+
<small class="text-muted"><%= key.humanize %>:</small>
|
71
|
+
</div>
|
72
|
+
<div class="col-sm-8">
|
73
|
+
<small><%= value %></small>
|
74
|
+
</div>
|
75
|
+
</div>
|
76
|
+
<% end %>
|
77
|
+
</div>
|
78
|
+
</div>
|
79
|
+
</div>
|
80
|
+
<% end %>
|
81
|
+
|
82
|
+
<div class="d-flex justify-content-between">
|
83
|
+
<%= link_to "Cancel", ragdoll.document_path(@document), class: "btn btn-secondary" %>
|
84
|
+
<%= form.submit "Update Document", class: "btn btn-primary" %>
|
85
|
+
</div>
|
86
|
+
<% end %>
|
87
|
+
</div>
|
88
|
+
</div>
|
89
|
+
</div>
|
90
|
+
</div>
|
91
|
+
</div>
|
@@ -0,0 +1,302 @@
|
|
1
|
+
<% content_for :title, "Documents - Ragdoll Engine" %>
|
2
|
+
|
3
|
+
<div class="row">
|
4
|
+
<div class="col-12">
|
5
|
+
<div class="d-flex justify-content-between align-items-center mb-4">
|
6
|
+
<h1><i class="fas fa-file-alt"></i> Documents</h1>
|
7
|
+
<%= link_to "Add Document", ragdoll.new_document_path, class: "btn btn-primary" %>
|
8
|
+
</div>
|
9
|
+
</div>
|
10
|
+
</div>
|
11
|
+
|
12
|
+
<div class="row mb-4">
|
13
|
+
<div class="col-12">
|
14
|
+
<div class="card">
|
15
|
+
<div class="card-header">
|
16
|
+
<h5><i class="fas fa-filter"></i> Filters</h5>
|
17
|
+
</div>
|
18
|
+
<div class="card-body">
|
19
|
+
<%= form_with url: ragdoll.documents_path, method: :get, local: true, class: "row g-3" do |form| %>
|
20
|
+
<div class="col-md-3">
|
21
|
+
<%= form.text_field :search, placeholder: "Search documents...", value: params[:search], class: "form-control" %>
|
22
|
+
</div>
|
23
|
+
<div class="col-md-3">
|
24
|
+
<%= form.select :document_type, options_for_select([["All Types", ""]] + @document_types.map { |type| [type.titleize, type] }, params[:document_type]), {}, { class: "form-select" } %>
|
25
|
+
</div>
|
26
|
+
<div class="col-md-3">
|
27
|
+
<%= form.select :status, options_for_select([["All Statuses", ""]] + @statuses.map { |status| [status.titleize, status] }, params[:status]), {}, { class: "form-select" } %>
|
28
|
+
</div>
|
29
|
+
<div class="col-md-3">
|
30
|
+
<%= form.submit "Filter", class: "btn btn-outline-primary" %>
|
31
|
+
<%= link_to "Clear", ragdoll.documents_path, class: "btn btn-outline-secondary" %>
|
32
|
+
</div>
|
33
|
+
<% end %>
|
34
|
+
</div>
|
35
|
+
</div>
|
36
|
+
</div>
|
37
|
+
</div>
|
38
|
+
|
39
|
+
<% if @documents.any? %>
|
40
|
+
<div class="row">
|
41
|
+
<div class="col-12">
|
42
|
+
<div class="card">
|
43
|
+
<div class="card-header">
|
44
|
+
<div class="d-flex justify-content-between align-items-center">
|
45
|
+
<h5 class="mb-0"><i class="fas fa-list"></i> Document List</h5>
|
46
|
+
<div class="d-flex align-items-center gap-2">
|
47
|
+
<!-- View Toggle Buttons -->
|
48
|
+
<div class="btn-group btn-group-sm me-3" role="group" aria-label="View toggle">
|
49
|
+
<button type="button" class="btn btn-outline-secondary" id="card-view-btn" onclick="switchToCardView()">
|
50
|
+
<i class="fas fa-th-large"></i> Cards
|
51
|
+
</button>
|
52
|
+
<button type="button" class="btn btn-outline-secondary" id="table-view-btn" onclick="switchToTableView()">
|
53
|
+
<i class="fas fa-table"></i> Table
|
54
|
+
</button>
|
55
|
+
</div>
|
56
|
+
|
57
|
+
<!-- Bulk Action Buttons -->
|
58
|
+
<button class="btn btn-outline-danger btn-sm" onclick="bulkDelete()">
|
59
|
+
<i class="fas fa-trash"></i> Delete Selected
|
60
|
+
</button>
|
61
|
+
<button class="btn btn-outline-warning btn-sm" onclick="bulkReprocess()">
|
62
|
+
<i class="fas fa-sync"></i> Reprocess Selected
|
63
|
+
</button>
|
64
|
+
</div>
|
65
|
+
</div>
|
66
|
+
</div>
|
67
|
+
<div class="card-body">
|
68
|
+
<!-- Card View -->
|
69
|
+
<div id="card-view" style="display: block;">
|
70
|
+
<%= render Ragdoll::DocumentListComponent.new(documents: @documents) %>
|
71
|
+
</div>
|
72
|
+
|
73
|
+
<!-- Table View -->
|
74
|
+
<div id="table-view" style="display: none;">
|
75
|
+
<%= render Ragdoll::DocumentTableComponent.new(documents: @documents) %>
|
76
|
+
</div>
|
77
|
+
</div>
|
78
|
+
</div>
|
79
|
+
</div>
|
80
|
+
</div>
|
81
|
+
<% else %>
|
82
|
+
<div class="row">
|
83
|
+
<div class="col-12">
|
84
|
+
<%= render Ragdoll::EmptyStateComponent.new(
|
85
|
+
title: "No documents found",
|
86
|
+
message: "Get started by adding your first document to the system.",
|
87
|
+
icon: "fas fa-file-alt",
|
88
|
+
action_path: ragdoll.new_document_path,
|
89
|
+
action_text: "Add Document"
|
90
|
+
) %>
|
91
|
+
</div>
|
92
|
+
</div>
|
93
|
+
<% end %>
|
94
|
+
|
95
|
+
<%= content_for :javascript do %>
|
96
|
+
<script>
|
97
|
+
// View switching functions
|
98
|
+
function switchToCardView() {
|
99
|
+
document.getElementById('card-view').style.display = 'block';
|
100
|
+
document.getElementById('table-view').style.display = 'none';
|
101
|
+
document.getElementById('card-view-btn').classList.add('active');
|
102
|
+
document.getElementById('table-view-btn').classList.remove('active');
|
103
|
+
localStorage.setItem('ragdoll-document-view', 'card');
|
104
|
+
}
|
105
|
+
|
106
|
+
function switchToTableView() {
|
107
|
+
document.getElementById('card-view').style.display = 'none';
|
108
|
+
document.getElementById('table-view').style.display = 'block';
|
109
|
+
document.getElementById('table-view-btn').classList.add('active');
|
110
|
+
document.getElementById('card-view-btn').classList.remove('active');
|
111
|
+
localStorage.setItem('ragdoll-document-view', 'table');
|
112
|
+
}
|
113
|
+
|
114
|
+
// Attach event listeners to document checkboxes
|
115
|
+
function attachCheckboxListeners() {
|
116
|
+
document.querySelectorAll('.document-checkbox').forEach(checkbox => {
|
117
|
+
checkbox.removeEventListener('change', updateSelectAllState); // Remove first to avoid duplicates
|
118
|
+
checkbox.addEventListener('change', updateSelectAllState);
|
119
|
+
});
|
120
|
+
updateSelectAllState(); // Initialize state
|
121
|
+
}
|
122
|
+
|
123
|
+
// Initialize view based on saved preference
|
124
|
+
document.addEventListener('DOMContentLoaded', function() {
|
125
|
+
const savedView = localStorage.getItem('ragdoll-document-view') || 'card';
|
126
|
+
if (savedView === 'table') {
|
127
|
+
switchToTableView();
|
128
|
+
} else {
|
129
|
+
switchToCardView();
|
130
|
+
}
|
131
|
+
|
132
|
+
// Attach listeners after view is set
|
133
|
+
setTimeout(attachCheckboxListeners, 100);
|
134
|
+
});
|
135
|
+
|
136
|
+
function toggleAll(source) {
|
137
|
+
// Get all document checkboxes
|
138
|
+
const checkboxes = document.querySelectorAll('.document-checkbox');
|
139
|
+
const selectAllCard = document.getElementById('select-all');
|
140
|
+
const selectAllTable = document.getElementById('select-all-table');
|
141
|
+
|
142
|
+
// Set all checkboxes to match the source checkbox state
|
143
|
+
checkboxes.forEach(checkbox => {
|
144
|
+
checkbox.checked = source.checked;
|
145
|
+
});
|
146
|
+
|
147
|
+
// Sync both select-all checkboxes
|
148
|
+
if (selectAllCard) {
|
149
|
+
selectAllCard.checked = source.checked;
|
150
|
+
selectAllCard.indeterminate = false;
|
151
|
+
}
|
152
|
+
if (selectAllTable) {
|
153
|
+
selectAllTable.checked = source.checked;
|
154
|
+
selectAllTable.indeterminate = false;
|
155
|
+
}
|
156
|
+
}
|
157
|
+
|
158
|
+
function updateSelectAllState() {
|
159
|
+
// Get all document checkboxes
|
160
|
+
const checkboxes = document.querySelectorAll('.document-checkbox');
|
161
|
+
const selectAllCard = document.getElementById('select-all');
|
162
|
+
const selectAllTable = document.getElementById('select-all-table');
|
163
|
+
|
164
|
+
if (checkboxes.length === 0) return;
|
165
|
+
|
166
|
+
// Count checked boxes
|
167
|
+
const checkedCount = Array.from(checkboxes).filter(cb => cb.checked).length;
|
168
|
+
|
169
|
+
// Determine state
|
170
|
+
let isChecked = false;
|
171
|
+
let isIndeterminate = false;
|
172
|
+
|
173
|
+
if (checkedCount === 0) {
|
174
|
+
// None checked
|
175
|
+
isChecked = false;
|
176
|
+
isIndeterminate = false;
|
177
|
+
} else if (checkedCount === checkboxes.length) {
|
178
|
+
// All checked
|
179
|
+
isChecked = true;
|
180
|
+
isIndeterminate = false;
|
181
|
+
} else {
|
182
|
+
// Some checked
|
183
|
+
isChecked = false;
|
184
|
+
isIndeterminate = true;
|
185
|
+
}
|
186
|
+
|
187
|
+
// Update both select-all checkboxes
|
188
|
+
if (selectAllCard) {
|
189
|
+
selectAllCard.checked = isChecked;
|
190
|
+
selectAllCard.indeterminate = isIndeterminate;
|
191
|
+
}
|
192
|
+
if (selectAllTable) {
|
193
|
+
selectAllTable.checked = isChecked;
|
194
|
+
selectAllTable.indeterminate = isIndeterminate;
|
195
|
+
}
|
196
|
+
}
|
197
|
+
|
198
|
+
|
199
|
+
function reprocessDocument(documentId) {
|
200
|
+
if (confirm('Are you sure you want to reprocess this document?')) {
|
201
|
+
// Create a form with proper CSRF token for individual reprocess action
|
202
|
+
const form = document.createElement('form');
|
203
|
+
form.method = 'POST';
|
204
|
+
form.action = `<%= ragdoll.documents_path %>/${documentId}/reprocess`;
|
205
|
+
|
206
|
+
// Add CSRF token
|
207
|
+
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
|
208
|
+
const csrfInput = document.createElement('input');
|
209
|
+
csrfInput.type = 'hidden';
|
210
|
+
csrfInput.name = 'authenticity_token';
|
211
|
+
csrfInput.value = csrfToken;
|
212
|
+
form.appendChild(csrfInput);
|
213
|
+
|
214
|
+
document.body.appendChild(form);
|
215
|
+
form.submit();
|
216
|
+
}
|
217
|
+
}
|
218
|
+
|
219
|
+
function bulkDelete() {
|
220
|
+
const checkedBoxes = document.querySelectorAll('.document-checkbox:checked');
|
221
|
+
if (checkedBoxes.length === 0) {
|
222
|
+
alert('Please select at least one document to delete.');
|
223
|
+
return;
|
224
|
+
}
|
225
|
+
|
226
|
+
if (confirm(`Are you sure you want to delete ${checkedBoxes.length} selected documents?`)) {
|
227
|
+
// Create a new form with proper CSRF token for delete action
|
228
|
+
const form = document.createElement('form');
|
229
|
+
form.method = 'POST';
|
230
|
+
form.action = '<%= ragdoll.bulk_delete_documents_path %>';
|
231
|
+
|
232
|
+
// Add CSRF token
|
233
|
+
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
|
234
|
+
const csrfInput = document.createElement('input');
|
235
|
+
csrfInput.type = 'hidden';
|
236
|
+
csrfInput.name = 'authenticity_token';
|
237
|
+
csrfInput.value = csrfToken;
|
238
|
+
form.appendChild(csrfInput);
|
239
|
+
|
240
|
+
// Add selected document IDs
|
241
|
+
checkedBoxes.forEach(checkbox => {
|
242
|
+
const input = document.createElement('input');
|
243
|
+
input.type = 'hidden';
|
244
|
+
input.name = 'document_ids[]';
|
245
|
+
input.value = checkbox.value;
|
246
|
+
form.appendChild(input);
|
247
|
+
});
|
248
|
+
|
249
|
+
document.body.appendChild(form);
|
250
|
+
form.submit();
|
251
|
+
}
|
252
|
+
}
|
253
|
+
|
254
|
+
function bulkReprocess() {
|
255
|
+
const checkedBoxes = document.querySelectorAll('.document-checkbox:checked');
|
256
|
+
if (checkedBoxes.length === 0) {
|
257
|
+
alert('Please select at least one document to reprocess.');
|
258
|
+
return;
|
259
|
+
}
|
260
|
+
|
261
|
+
if (confirm(`Are you sure you want to reprocess ${checkedBoxes.length} selected documents?`)) {
|
262
|
+
// Create a new form with proper CSRF token for reprocess action
|
263
|
+
const form = document.createElement('form');
|
264
|
+
form.method = 'POST';
|
265
|
+
form.action = '<%= ragdoll.bulk_reprocess_documents_path %>';
|
266
|
+
|
267
|
+
// Add CSRF token
|
268
|
+
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
|
269
|
+
const csrfInput = document.createElement('input');
|
270
|
+
csrfInput.type = 'hidden';
|
271
|
+
csrfInput.name = 'authenticity_token';
|
272
|
+
csrfInput.value = csrfToken;
|
273
|
+
form.appendChild(csrfInput);
|
274
|
+
|
275
|
+
// Add selected document IDs
|
276
|
+
checkedBoxes.forEach(checkbox => {
|
277
|
+
const input = document.createElement('input');
|
278
|
+
input.type = 'hidden';
|
279
|
+
input.name = 'document_ids[]';
|
280
|
+
input.value = checkbox.value;
|
281
|
+
form.appendChild(input);
|
282
|
+
});
|
283
|
+
|
284
|
+
document.body.appendChild(form);
|
285
|
+
form.submit();
|
286
|
+
}
|
287
|
+
}
|
288
|
+
|
289
|
+
// Initialize Bootstrap tooltips
|
290
|
+
document.addEventListener('DOMContentLoaded', function () {
|
291
|
+
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
|
292
|
+
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
|
293
|
+
return new bootstrap.Tooltip(tooltipTriggerEl, {
|
294
|
+
delay: { "show": 500, "hide": 100 },
|
295
|
+
placement: 'top',
|
296
|
+
boundary: 'viewport',
|
297
|
+
fallbackPlacements: ['top', 'bottom']
|
298
|
+
});
|
299
|
+
});
|
300
|
+
});
|
301
|
+
</script>
|
302
|
+
<% end %>
|