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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +18 -21
  3. data/app/assets/javascripts/ragdoll/application.js +129 -0
  4. data/app/assets/javascripts/ragdoll/bulk_upload_status.js +454 -0
  5. data/app/assets/stylesheets/ragdoll/application.css +84 -0
  6. data/app/assets/stylesheets/ragdoll/bulk_upload_status.css +379 -0
  7. data/app/channels/application_cable/channel.rb +6 -0
  8. data/app/channels/application_cable/connection.rb +6 -0
  9. data/app/channels/ragdoll/bulk_upload_status_channel.rb +27 -0
  10. data/app/channels/ragdoll/file_processing_channel.rb +26 -0
  11. data/app/components/ragdoll/alert_component.html.erb +4 -0
  12. data/app/components/ragdoll/alert_component.rb +32 -0
  13. data/app/components/ragdoll/application_component.rb +6 -0
  14. data/app/components/ragdoll/card_component.html.erb +15 -0
  15. data/app/components/ragdoll/card_component.rb +21 -0
  16. data/app/components/ragdoll/document_list_component.html.erb +41 -0
  17. data/app/components/ragdoll/document_list_component.rb +13 -0
  18. data/app/components/ragdoll/document_table_component.html.erb +76 -0
  19. data/app/components/ragdoll/document_table_component.rb +13 -0
  20. data/app/components/ragdoll/empty_state_component.html.erb +12 -0
  21. data/app/components/ragdoll/empty_state_component.rb +17 -0
  22. data/app/components/ragdoll/flash_messages_component.html.erb +3 -0
  23. data/app/components/ragdoll/flash_messages_component.rb +37 -0
  24. data/app/components/ragdoll/navbar_component.html.erb +24 -0
  25. data/app/components/ragdoll/navbar_component.rb +31 -0
  26. data/app/components/ragdoll/page_header_component.html.erb +13 -0
  27. data/app/components/ragdoll/page_header_component.rb +15 -0
  28. data/app/components/ragdoll/stats_card_component.html.erb +11 -0
  29. data/app/components/ragdoll/stats_card_component.rb +17 -0
  30. data/app/components/ragdoll/status_badge_component.html.erb +3 -0
  31. data/app/components/ragdoll/status_badge_component.rb +30 -0
  32. data/app/controllers/ragdoll/api/v1/analytics_controller.rb +72 -0
  33. data/app/controllers/ragdoll/api/v1/base_controller.rb +29 -0
  34. data/app/controllers/ragdoll/api/v1/documents_controller.rb +148 -0
  35. data/app/controllers/ragdoll/api/v1/search_controller.rb +87 -0
  36. data/app/controllers/ragdoll/api/v1/system_controller.rb +97 -0
  37. data/app/controllers/ragdoll/application_controller.rb +17 -0
  38. data/app/controllers/ragdoll/configuration_controller.rb +82 -0
  39. data/app/controllers/ragdoll/dashboard_controller.rb +98 -0
  40. data/app/controllers/ragdoll/documents_controller.rb +460 -0
  41. data/app/controllers/ragdoll/documents_controller_backup.rb +68 -0
  42. data/app/controllers/ragdoll/jobs_controller.rb +116 -0
  43. data/app/controllers/ragdoll/search_controller.rb +368 -0
  44. data/app/jobs/application_job.rb +9 -0
  45. data/app/jobs/ragdoll/bulk_document_processing_job.rb +280 -0
  46. data/app/jobs/ragdoll/process_file_job.rb +166 -0
  47. data/app/services/ragdoll/worker_health_service.rb +111 -0
  48. data/app/views/layouts/ragdoll/application.html.erb +162 -0
  49. data/app/views/ragdoll/dashboard/analytics.html.erb +333 -0
  50. data/app/views/ragdoll/dashboard/index.html.erb +208 -0
  51. data/app/views/ragdoll/documents/edit.html.erb +91 -0
  52. data/app/views/ragdoll/documents/index.html.erb +302 -0
  53. data/app/views/ragdoll/documents/new.html.erb +1518 -0
  54. data/app/views/ragdoll/documents/show.html.erb +188 -0
  55. data/app/views/ragdoll/documents/upload_results.html.erb +248 -0
  56. data/app/views/ragdoll/jobs/index.html.erb +669 -0
  57. data/app/views/ragdoll/jobs/show.html.erb +129 -0
  58. data/app/views/ragdoll/search/index.html.erb +324 -0
  59. data/config/cable.yml +12 -0
  60. data/config/routes.rb +57 -2
  61. data/lib/generators/ragdoll/init/templates/INSTALL +3 -2
  62. data/lib/generators/ragdoll/init_generator.rb +68 -0
  63. data/lib/ragdoll/rails/engine.rb +48 -0
  64. data/lib/ragdoll/rails/version.rb +1 -1
  65. metadata +231 -6
  66. data/lib/generators/ragdoll/init/init_generator.rb +0 -26
@@ -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 %>