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,84 @@
|
|
1
|
+
/*
|
2
|
+
* Ragdoll Rails Engine Stylesheet
|
3
|
+
* This file provides custom styling for the Ragdoll Engine interface
|
4
|
+
*/
|
5
|
+
|
6
|
+
/* Custom styling for Ragdoll components */
|
7
|
+
.ragdoll-brand {
|
8
|
+
font-weight: bold;
|
9
|
+
}
|
10
|
+
|
11
|
+
.ragdoll-stats-card {
|
12
|
+
transition: transform 0.2s ease-in-out;
|
13
|
+
}
|
14
|
+
|
15
|
+
.ragdoll-stats-card:hover {
|
16
|
+
transform: translateY(-2px);
|
17
|
+
}
|
18
|
+
|
19
|
+
.ragdoll-search-form {
|
20
|
+
background-color: #f8f9fa;
|
21
|
+
border-radius: 8px;
|
22
|
+
padding: 1rem;
|
23
|
+
}
|
24
|
+
|
25
|
+
.ragdoll-document-list {
|
26
|
+
max-height: 500px;
|
27
|
+
overflow-y: auto;
|
28
|
+
}
|
29
|
+
|
30
|
+
.ragdoll-similarity-score {
|
31
|
+
font-family: 'Courier New', monospace;
|
32
|
+
font-weight: bold;
|
33
|
+
}
|
34
|
+
|
35
|
+
/* Loading states */
|
36
|
+
.ragdoll-loading {
|
37
|
+
opacity: 0.6;
|
38
|
+
pointer-events: none;
|
39
|
+
}
|
40
|
+
|
41
|
+
.ragdoll-loading::after {
|
42
|
+
content: '';
|
43
|
+
position: absolute;
|
44
|
+
top: 50%;
|
45
|
+
left: 50%;
|
46
|
+
width: 20px;
|
47
|
+
height: 20px;
|
48
|
+
margin: -10px 0 0 -10px;
|
49
|
+
border: 2px solid #007bff;
|
50
|
+
border-radius: 50%;
|
51
|
+
border-top-color: transparent;
|
52
|
+
animation: ragdoll-spin 1s linear infinite;
|
53
|
+
}
|
54
|
+
|
55
|
+
@keyframes ragdoll-spin {
|
56
|
+
to {
|
57
|
+
transform: rotate(360deg);
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
/* Component specific styles */
|
62
|
+
.navbar.ragdoll-navbar {
|
63
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
64
|
+
}
|
65
|
+
|
66
|
+
.card.ragdoll-card {
|
67
|
+
border: 1px solid #e9ecef;
|
68
|
+
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
|
69
|
+
}
|
70
|
+
|
71
|
+
.alert.ragdoll-alert {
|
72
|
+
border-radius: 6px;
|
73
|
+
}
|
74
|
+
|
75
|
+
/* Responsive adjustments */
|
76
|
+
@media (max-width: 768px) {
|
77
|
+
.ragdoll-stats-card {
|
78
|
+
margin-bottom: 1rem;
|
79
|
+
}
|
80
|
+
|
81
|
+
.ragdoll-search-form {
|
82
|
+
padding: 0.75rem;
|
83
|
+
}
|
84
|
+
}
|
@@ -0,0 +1,379 @@
|
|
1
|
+
/* Bulk Upload Status Popup Styles */
|
2
|
+
.bulk-upload-status-container {
|
3
|
+
position: fixed;
|
4
|
+
top: 20px;
|
5
|
+
right: 20px;
|
6
|
+
width: 400px;
|
7
|
+
max-width: 90vw;
|
8
|
+
max-height: 80vh;
|
9
|
+
background: #ffffff;
|
10
|
+
border: 1px solid #e1e5e9;
|
11
|
+
border-radius: 12px;
|
12
|
+
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
|
13
|
+
z-index: 10000;
|
14
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
15
|
+
font-size: 14px;
|
16
|
+
opacity: 0;
|
17
|
+
transform: translateY(-20px) scale(0.95);
|
18
|
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
19
|
+
pointer-events: none;
|
20
|
+
overflow: hidden;
|
21
|
+
}
|
22
|
+
|
23
|
+
.bulk-upload-status-container.visible {
|
24
|
+
opacity: 1;
|
25
|
+
transform: translateY(0) scale(1);
|
26
|
+
pointer-events: auto;
|
27
|
+
}
|
28
|
+
|
29
|
+
.bulk-upload-status-container.minimized .bulk-upload-status-content {
|
30
|
+
display: none;
|
31
|
+
}
|
32
|
+
|
33
|
+
.bulk-upload-status-container.minimized {
|
34
|
+
height: auto;
|
35
|
+
}
|
36
|
+
|
37
|
+
/* Header */
|
38
|
+
.bulk-upload-status-header {
|
39
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
40
|
+
color: white;
|
41
|
+
padding: 16px 20px;
|
42
|
+
display: flex;
|
43
|
+
justify-content: space-between;
|
44
|
+
align-items: center;
|
45
|
+
cursor: grab;
|
46
|
+
user-select: none;
|
47
|
+
}
|
48
|
+
|
49
|
+
.bulk-upload-status-header:active {
|
50
|
+
cursor: grabbing;
|
51
|
+
}
|
52
|
+
|
53
|
+
.bulk-upload-status-title {
|
54
|
+
display: flex;
|
55
|
+
align-items: center;
|
56
|
+
gap: 8px;
|
57
|
+
font-weight: 600;
|
58
|
+
font-size: 16px;
|
59
|
+
}
|
60
|
+
|
61
|
+
.bulk-upload-status-title i {
|
62
|
+
font-size: 18px;
|
63
|
+
}
|
64
|
+
|
65
|
+
.bulk-upload-status-controls {
|
66
|
+
display: flex;
|
67
|
+
gap: 8px;
|
68
|
+
}
|
69
|
+
|
70
|
+
.bulk-upload-status-controls button {
|
71
|
+
background: rgba(255, 255, 255, 0.2);
|
72
|
+
border: none;
|
73
|
+
color: white;
|
74
|
+
width: 32px;
|
75
|
+
height: 32px;
|
76
|
+
border-radius: 6px;
|
77
|
+
display: flex;
|
78
|
+
align-items: center;
|
79
|
+
justify-content: center;
|
80
|
+
cursor: pointer;
|
81
|
+
transition: background-color 0.2s ease;
|
82
|
+
}
|
83
|
+
|
84
|
+
.bulk-upload-status-controls button:hover {
|
85
|
+
background: rgba(255, 255, 255, 0.3);
|
86
|
+
}
|
87
|
+
|
88
|
+
.bulk-upload-status-controls button:active {
|
89
|
+
background: rgba(255, 255, 255, 0.4);
|
90
|
+
}
|
91
|
+
|
92
|
+
/* Content */
|
93
|
+
.bulk-upload-status-content {
|
94
|
+
max-height: 500px;
|
95
|
+
overflow-y: auto;
|
96
|
+
padding: 0;
|
97
|
+
}
|
98
|
+
|
99
|
+
.no-uploads-message {
|
100
|
+
padding: 24px 20px;
|
101
|
+
text-align: center;
|
102
|
+
color: #6b7280;
|
103
|
+
font-style: italic;
|
104
|
+
}
|
105
|
+
|
106
|
+
/* Upload Items */
|
107
|
+
.upload-item {
|
108
|
+
border-bottom: 1px solid #f3f4f6;
|
109
|
+
padding: 20px;
|
110
|
+
background: #ffffff;
|
111
|
+
transition: background-color 0.2s ease;
|
112
|
+
}
|
113
|
+
|
114
|
+
.upload-item:last-child {
|
115
|
+
border-bottom: none;
|
116
|
+
}
|
117
|
+
|
118
|
+
.upload-item:hover {
|
119
|
+
background: #f9fafb;
|
120
|
+
}
|
121
|
+
|
122
|
+
.upload-item.status-starting {
|
123
|
+
background: #fef3c7;
|
124
|
+
}
|
125
|
+
|
126
|
+
.upload-item.status-processing {
|
127
|
+
background: #dbeafe;
|
128
|
+
}
|
129
|
+
|
130
|
+
.upload-item.status-completed {
|
131
|
+
background: #d1fae5;
|
132
|
+
}
|
133
|
+
|
134
|
+
.upload-item.status-failed {
|
135
|
+
background: #fee2e2;
|
136
|
+
}
|
137
|
+
|
138
|
+
/* Upload Header */
|
139
|
+
.upload-header {
|
140
|
+
display: flex;
|
141
|
+
justify-content: space-between;
|
142
|
+
align-items: center;
|
143
|
+
margin-bottom: 12px;
|
144
|
+
}
|
145
|
+
|
146
|
+
.upload-title {
|
147
|
+
display: flex;
|
148
|
+
align-items: center;
|
149
|
+
gap: 8px;
|
150
|
+
font-weight: 600;
|
151
|
+
color: #374151;
|
152
|
+
}
|
153
|
+
|
154
|
+
.upload-percentage {
|
155
|
+
font-weight: 700;
|
156
|
+
color: #667eea;
|
157
|
+
font-size: 16px;
|
158
|
+
}
|
159
|
+
|
160
|
+
/* Progress Bar */
|
161
|
+
.progress-bar {
|
162
|
+
width: 100%;
|
163
|
+
height: 8px;
|
164
|
+
background: #e5e7eb;
|
165
|
+
border-radius: 4px;
|
166
|
+
overflow: hidden;
|
167
|
+
margin-bottom: 16px;
|
168
|
+
}
|
169
|
+
|
170
|
+
.progress-fill {
|
171
|
+
height: 100%;
|
172
|
+
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
|
173
|
+
transition: width 0.3s ease;
|
174
|
+
border-radius: 4px;
|
175
|
+
}
|
176
|
+
|
177
|
+
.status-failed .progress-fill {
|
178
|
+
background: linear-gradient(90deg, #ef4444 0%, #dc2626 100%);
|
179
|
+
}
|
180
|
+
|
181
|
+
.status-completed .progress-fill {
|
182
|
+
background: linear-gradient(90deg, #10b981 0%, #059669 100%);
|
183
|
+
}
|
184
|
+
|
185
|
+
/* Upload Details */
|
186
|
+
.upload-details {
|
187
|
+
space-y: 12px;
|
188
|
+
}
|
189
|
+
|
190
|
+
.upload-stats {
|
191
|
+
display: flex;
|
192
|
+
gap: 16px;
|
193
|
+
flex-wrap: wrap;
|
194
|
+
margin-bottom: 12px;
|
195
|
+
}
|
196
|
+
|
197
|
+
.upload-stats > span {
|
198
|
+
display: flex;
|
199
|
+
align-items: center;
|
200
|
+
gap: 4px;
|
201
|
+
font-size: 13px;
|
202
|
+
color: #6b7280;
|
203
|
+
}
|
204
|
+
|
205
|
+
.processed-count {
|
206
|
+
color: #059669 !important;
|
207
|
+
font-weight: 600;
|
208
|
+
}
|
209
|
+
|
210
|
+
.failed-count {
|
211
|
+
color: #dc2626 !important;
|
212
|
+
font-weight: 600;
|
213
|
+
}
|
214
|
+
|
215
|
+
.eta {
|
216
|
+
color: #667eea !important;
|
217
|
+
font-weight: 500;
|
218
|
+
}
|
219
|
+
|
220
|
+
/* Current File */
|
221
|
+
.current-file {
|
222
|
+
display: flex;
|
223
|
+
align-items: center;
|
224
|
+
gap: 8px;
|
225
|
+
padding: 8px 12px;
|
226
|
+
background: rgba(102, 126, 234, 0.1);
|
227
|
+
border-radius: 6px;
|
228
|
+
margin-bottom: 12px;
|
229
|
+
font-size: 13px;
|
230
|
+
color: #4338ca;
|
231
|
+
}
|
232
|
+
|
233
|
+
.current-file i {
|
234
|
+
color: #667eea;
|
235
|
+
}
|
236
|
+
|
237
|
+
/* Messages */
|
238
|
+
.completion-message,
|
239
|
+
.error-message {
|
240
|
+
display: flex;
|
241
|
+
align-items: center;
|
242
|
+
gap: 8px;
|
243
|
+
padding: 12px;
|
244
|
+
border-radius: 6px;
|
245
|
+
font-size: 13px;
|
246
|
+
font-weight: 500;
|
247
|
+
margin-bottom: 12px;
|
248
|
+
}
|
249
|
+
|
250
|
+
.completion-message {
|
251
|
+
background: #d1fae5;
|
252
|
+
color: #047857;
|
253
|
+
border: 1px solid #a7f3d0;
|
254
|
+
}
|
255
|
+
|
256
|
+
.error-message {
|
257
|
+
background: #fee2e2;
|
258
|
+
color: #dc2626;
|
259
|
+
border: 1px solid #fca5a5;
|
260
|
+
}
|
261
|
+
|
262
|
+
/* Error List */
|
263
|
+
.error-list {
|
264
|
+
margin-top: 12px;
|
265
|
+
}
|
266
|
+
|
267
|
+
.error-list details {
|
268
|
+
background: #fef2f2;
|
269
|
+
border: 1px solid #fecaca;
|
270
|
+
border-radius: 6px;
|
271
|
+
padding: 8px 12px;
|
272
|
+
}
|
273
|
+
|
274
|
+
.error-list summary {
|
275
|
+
font-weight: 600;
|
276
|
+
color: #dc2626;
|
277
|
+
cursor: pointer;
|
278
|
+
font-size: 13px;
|
279
|
+
}
|
280
|
+
|
281
|
+
.error-list summary:hover {
|
282
|
+
color: #b91c1c;
|
283
|
+
}
|
284
|
+
|
285
|
+
.error-list ul {
|
286
|
+
margin: 8px 0 0 0;
|
287
|
+
padding-left: 16px;
|
288
|
+
list-style-type: disc;
|
289
|
+
}
|
290
|
+
|
291
|
+
.error-list li {
|
292
|
+
color: #7f1d1d;
|
293
|
+
font-size: 12px;
|
294
|
+
margin: 4px 0;
|
295
|
+
word-break: break-word;
|
296
|
+
}
|
297
|
+
|
298
|
+
/* Animations */
|
299
|
+
@keyframes pulse {
|
300
|
+
0%, 100% {
|
301
|
+
opacity: 1;
|
302
|
+
}
|
303
|
+
50% {
|
304
|
+
opacity: 0.7;
|
305
|
+
}
|
306
|
+
}
|
307
|
+
|
308
|
+
.status-processing .upload-title i {
|
309
|
+
animation: pulse 2s infinite;
|
310
|
+
}
|
311
|
+
|
312
|
+
/* Responsive Design */
|
313
|
+
@media (max-width: 768px) {
|
314
|
+
.bulk-upload-status-container {
|
315
|
+
width: 320px;
|
316
|
+
right: 10px;
|
317
|
+
top: 10px;
|
318
|
+
}
|
319
|
+
|
320
|
+
.upload-stats {
|
321
|
+
flex-direction: column;
|
322
|
+
gap: 8px;
|
323
|
+
}
|
324
|
+
|
325
|
+
.bulk-upload-status-header {
|
326
|
+
padding: 12px 16px;
|
327
|
+
}
|
328
|
+
|
329
|
+
.upload-item {
|
330
|
+
padding: 16px;
|
331
|
+
}
|
332
|
+
}
|
333
|
+
|
334
|
+
@media (max-width: 480px) {
|
335
|
+
.bulk-upload-status-container {
|
336
|
+
width: calc(100vw - 20px);
|
337
|
+
right: 10px;
|
338
|
+
left: 10px;
|
339
|
+
}
|
340
|
+
}
|
341
|
+
|
342
|
+
/* Dark mode support */
|
343
|
+
@media (prefers-color-scheme: dark) {
|
344
|
+
.bulk-upload-status-container {
|
345
|
+
background: #1f2937;
|
346
|
+
border-color: #374151;
|
347
|
+
color: #f9fafb;
|
348
|
+
}
|
349
|
+
|
350
|
+
.upload-item {
|
351
|
+
background: #1f2937;
|
352
|
+
border-color: #374151;
|
353
|
+
}
|
354
|
+
|
355
|
+
.upload-item:hover {
|
356
|
+
background: #374151;
|
357
|
+
}
|
358
|
+
|
359
|
+
.upload-title {
|
360
|
+
color: #f9fafb;
|
361
|
+
}
|
362
|
+
|
363
|
+
.no-uploads-message {
|
364
|
+
color: #9ca3af;
|
365
|
+
}
|
366
|
+
|
367
|
+
.progress-bar {
|
368
|
+
background: #374151;
|
369
|
+
}
|
370
|
+
|
371
|
+
.upload-stats > span {
|
372
|
+
color: #9ca3af;
|
373
|
+
}
|
374
|
+
|
375
|
+
.current-file {
|
376
|
+
background: rgba(102, 126, 234, 0.2);
|
377
|
+
color: #93c5fd;
|
378
|
+
}
|
379
|
+
}
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Ragdoll
|
2
|
+
class BulkUploadStatusChannel < ApplicationCable::Channel
|
3
|
+
def subscribed
|
4
|
+
session_id = params[:session_id]
|
5
|
+
|
6
|
+
if session_id.present?
|
7
|
+
stream_from "bulk_upload_status_#{session_id}"
|
8
|
+
logger.info "📡 Client subscribed to bulk upload status for session: #{session_id}"
|
9
|
+
else
|
10
|
+
reject
|
11
|
+
logger.warn "⚠️ Bulk upload status subscription rejected: missing session_id"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def unsubscribed
|
16
|
+
logger.info "📡 Client unsubscribed from bulk upload status"
|
17
|
+
end
|
18
|
+
|
19
|
+
def ping(data)
|
20
|
+
# Respond to client ping to maintain connection
|
21
|
+
transmit({
|
22
|
+
type: 'pong',
|
23
|
+
timestamp: Time.current.iso8601
|
24
|
+
})
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ragdoll
|
4
|
+
class FileProcessingChannel < ApplicationCable::Channel
|
5
|
+
def subscribed
|
6
|
+
stream_from "ragdoll_file_processing_#{params[:session_id]}"
|
7
|
+
puts "📡 Ragdoll::FileProcessingChannel subscribed to ragdoll_file_processing_#{params[:session_id]}"
|
8
|
+
logger.info "📡 Ragdoll::FileProcessingChannel subscribed to ragdoll_file_processing_#{params[:session_id]}"
|
9
|
+
end
|
10
|
+
|
11
|
+
def unsubscribed
|
12
|
+
puts "📡 Ragdoll::FileProcessingChannel unsubscribed from ragdoll_file_processing_#{params[:session_id]}"
|
13
|
+
logger.info "📡 Ragdoll::FileProcessingChannel unsubscribed from ragdoll_file_processing_#{params[:session_id]}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_connection
|
17
|
+
puts "🏓 Received test_connection ping from session: #{params[:session_id]}"
|
18
|
+
logger.info "🏓 Received test_connection ping from session: #{params[:session_id]}"
|
19
|
+
ActionCable.server.broadcast("ragdoll_file_processing_#{params[:session_id]}", {
|
20
|
+
type: 'ping',
|
21
|
+
message: 'Connection test successful',
|
22
|
+
timestamp: Time.current.to_f
|
23
|
+
})
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ragdoll
|
4
|
+
class AlertComponent < ApplicationComponent
|
5
|
+
def initialize(message:, type: 'info', dismissible: true, **options)
|
6
|
+
@message = message
|
7
|
+
@type = type
|
8
|
+
@dismissible = dismissible
|
9
|
+
@options = options
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
attr_reader :message, :type, :dismissible, :options
|
15
|
+
|
16
|
+
def alert_classes
|
17
|
+
classes = ['alert', "alert-#{type}"]
|
18
|
+
classes << 'alert-dismissible' if dismissible
|
19
|
+
classes << 'fade show' if dismissible
|
20
|
+
classes << options[:class] if options[:class]
|
21
|
+
classes.join(' ')
|
22
|
+
end
|
23
|
+
|
24
|
+
def dismiss_button
|
25
|
+
return unless dismissible
|
26
|
+
|
27
|
+
content_tag :button, type: 'button', class: 'btn-close', data: { bs_dismiss: 'alert' } do
|
28
|
+
''
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
<div class="<%= card_classes %>">
|
2
|
+
<% if title %>
|
3
|
+
<div class="card-header">
|
4
|
+
<h5>
|
5
|
+
<% if icon %>
|
6
|
+
<i class="<%= icon %>"></i>
|
7
|
+
<% end %>
|
8
|
+
<%= title %>
|
9
|
+
</h5>
|
10
|
+
</div>
|
11
|
+
<% end %>
|
12
|
+
<div class="card-body">
|
13
|
+
<%= content %>
|
14
|
+
</div>
|
15
|
+
</div>
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ragdoll
|
4
|
+
class CardComponent < ApplicationComponent
|
5
|
+
def initialize(title: nil, icon: nil, **options)
|
6
|
+
@title = title
|
7
|
+
@icon = icon
|
8
|
+
@options = options
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
attr_reader :title, :icon, :options
|
14
|
+
|
15
|
+
def card_classes
|
16
|
+
classes = ['card']
|
17
|
+
classes << options[:class] if options[:class]
|
18
|
+
classes.join(' ')
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
<div class="mb-3 d-flex align-items-center">
|
2
|
+
<input type="checkbox" id="select-all" class="form-check-input me-2" onclick="toggleAll(this)">
|
3
|
+
<label class="form-check-label text-muted">Select All</label>
|
4
|
+
</div>
|
5
|
+
<div class="row">
|
6
|
+
<% documents.each do |document| %>
|
7
|
+
<div class="col-md-6 col-lg-4 mb-4">
|
8
|
+
<div class="card h-100">
|
9
|
+
<div class="card-header d-flex justify-content-between align-items-center">
|
10
|
+
<div class="d-flex align-items-center">
|
11
|
+
<input type="checkbox" class="document-checkbox form-check-input me-2" value="<%= document.id %>">
|
12
|
+
<h6 class="card-title mb-0"><%= document.title %></h6>
|
13
|
+
</div>
|
14
|
+
<span class="badge bg-<%= document.status == 'processed' ? 'success' : document.status == 'failed' ? 'danger' : 'warning' %>">
|
15
|
+
<%= document.status.humanize %>
|
16
|
+
</span>
|
17
|
+
</div>
|
18
|
+
<div class="card-body">
|
19
|
+
<p class="card-text text-muted small mb-2">
|
20
|
+
<i class="fas fa-file"></i> <%= document.document_type %>
|
21
|
+
</p>
|
22
|
+
<p class="card-text text-muted small mb-2">
|
23
|
+
<i class="fas fa-weight"></i> <%= number_to_human_size(document.metadata['file_size']) if document.metadata && document.metadata['file_size'] %>
|
24
|
+
</p>
|
25
|
+
<p class="card-text text-muted small">
|
26
|
+
<i class="fas fa-clock"></i> <%= time_ago_in_words(document.created_at) %> ago
|
27
|
+
</p>
|
28
|
+
</div>
|
29
|
+
<div class="card-footer bg-transparent">
|
30
|
+
<div class="btn-group w-100" role="group">
|
31
|
+
<%= link_to "View", helpers.ragdoll.document_path(document), class: "btn btn-outline-primary btn-sm" %>
|
32
|
+
<%= link_to "Edit", helpers.ragdoll.edit_document_path(document), class: "btn btn-outline-secondary btn-sm" %>
|
33
|
+
<%= link_to "Delete", helpers.ragdoll.document_path(document), method: :delete,
|
34
|
+
class: "btn btn-outline-danger btn-sm",
|
35
|
+
data: { confirm: "Are you sure you want to delete '#{document.title}'?", turbo_method: :delete } %>
|
36
|
+
</div>
|
37
|
+
</div>
|
38
|
+
</div>
|
39
|
+
</div>
|
40
|
+
<% end %>
|
41
|
+
</div>
|