active_storage_dashboard 0.1.4 → 0.1.6
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/controllers/active_storage_dashboard/attachments_controller.rb +33 -2
- data/app/controllers/active_storage_dashboard/blobs_controller.rb +42 -2
- data/app/controllers/active_storage_dashboard/dashboard_controller.rb +3 -0
- data/app/views/active_storage_dashboard/attachments/index.html.erb +208 -1
- data/app/views/active_storage_dashboard/attachments/show.html.erb +9 -1
- data/app/views/active_storage_dashboard/blobs/index.html.erb +212 -2
- data/app/views/active_storage_dashboard/blobs/show.html.erb +9 -1
- data/app/views/active_storage_dashboard/dashboard/index.html.erb +182 -6
- data/app/views/active_storage_dashboard/variant_records/show.html.erb +12 -2
- data/app/views/layouts/active_storage_dashboard/application.html.erb +254 -5
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 37a9ff508b9a3249ca7bda98dbfca0f7aa2629e35168958b0eca00d82d2cd27f
|
4
|
+
data.tar.gz: 6eac31451e7615696babb0f89de47d31f881d6ab392d9600907ed26b5733574e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 86378de269473f43fdc742d95f2dce3197f22ddc4726a5e9e91f56ae4a77a05df3fa2094ac0789d20734edbc99235050db81ffecddce74117422113ab43f13df
|
7
|
+
data.tar.gz: 466081bfa71b093cfbed38febfb5810a020cf8a40922f6098e41848a0a90a0bc6d6968302a0b381dc8b163851f6034a5bf0bf13a39c43b54dda64bd296c78ee7
|
@@ -3,8 +3,20 @@
|
|
3
3
|
module ActiveStorageDashboard
|
4
4
|
class AttachmentsController < ApplicationController
|
5
5
|
def index
|
6
|
-
@attachments =
|
7
|
-
|
6
|
+
@attachments = ActiveStorage::Attachment.order(created_at: :desc)
|
7
|
+
|
8
|
+
# Get record types for filter dropdown
|
9
|
+
@record_types = ActiveStorage::Attachment.distinct.pluck(:record_type).compact.sort
|
10
|
+
|
11
|
+
# Get content types for filter dropdown
|
12
|
+
@content_types = ActiveStorage::Blob.joins(:attachments).distinct.pluck(:content_type).compact.sort
|
13
|
+
|
14
|
+
# Apply filters
|
15
|
+
apply_filters
|
16
|
+
|
17
|
+
# Pagination after filters
|
18
|
+
@total_count = @attachments.count
|
19
|
+
@attachments = paginate(@attachments)
|
8
20
|
end
|
9
21
|
|
10
22
|
def show
|
@@ -23,5 +35,24 @@ module ActiveStorageDashboard
|
|
23
35
|
redirect_to download_blob_path(@blob)
|
24
36
|
end
|
25
37
|
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def apply_filters
|
42
|
+
# Filter by attachment name
|
43
|
+
if params[:name].present?
|
44
|
+
@attachments = @attachments.where('name LIKE ?', "%#{params[:name]}%")
|
45
|
+
end
|
46
|
+
|
47
|
+
# Filter by record type
|
48
|
+
if params[:record_type].present?
|
49
|
+
@attachments = @attachments.where(record_type: params[:record_type])
|
50
|
+
end
|
51
|
+
|
52
|
+
# Filter by content type
|
53
|
+
if params[:content_type].present?
|
54
|
+
@attachments = @attachments.joins(:blob).where(active_storage_blobs: { content_type: params[:content_type] })
|
55
|
+
end
|
56
|
+
end
|
26
57
|
end
|
27
58
|
end
|
@@ -3,8 +3,17 @@
|
|
3
3
|
module ActiveStorageDashboard
|
4
4
|
class BlobsController < ApplicationController
|
5
5
|
def index
|
6
|
-
@blobs =
|
7
|
-
|
6
|
+
@blobs = ActiveStorage::Blob.order(created_at: :desc)
|
7
|
+
|
8
|
+
# Get content types for filter dropdown
|
9
|
+
@content_types = ActiveStorage::Blob.distinct.pluck(:content_type).compact.sort
|
10
|
+
|
11
|
+
# Apply filters
|
12
|
+
apply_filters
|
13
|
+
|
14
|
+
# Pagination after filters
|
15
|
+
@total_count = @blobs.count
|
16
|
+
@blobs = paginate(@blobs)
|
8
17
|
end
|
9
18
|
|
10
19
|
def show
|
@@ -51,5 +60,36 @@ module ActiveStorageDashboard
|
|
51
60
|
redirect_to main_app.rails_blob_path(@blob, disposition_param)
|
52
61
|
end
|
53
62
|
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def apply_filters
|
67
|
+
# Filter by content type
|
68
|
+
if params[:content_type].present?
|
69
|
+
@blobs = @blobs.where(content_type: params[:content_type])
|
70
|
+
end
|
71
|
+
|
72
|
+
# Filter by size
|
73
|
+
if params[:size].present?
|
74
|
+
case params[:size]
|
75
|
+
when 'small'
|
76
|
+
@blobs = @blobs.where('byte_size < ?', 1.megabyte)
|
77
|
+
when 'medium'
|
78
|
+
@blobs = @blobs.where('byte_size >= ? AND byte_size <= ?', 1.megabyte, 10.megabytes)
|
79
|
+
when 'large'
|
80
|
+
@blobs = @blobs.where('byte_size > ?', 10.megabytes)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Filter by status
|
85
|
+
if params[:status].present?
|
86
|
+
case params[:status]
|
87
|
+
when 'purgable'
|
88
|
+
@blobs = @blobs.left_outer_joins(:attachments).where(active_storage_attachments: { id: nil })
|
89
|
+
when 'attached'
|
90
|
+
@blobs = @blobs.joins(:attachments).distinct
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
54
94
|
end
|
55
95
|
end
|
@@ -11,6 +11,9 @@ module ActiveStorageDashboard
|
|
11
11
|
@content_types = ActiveStorage::Blob.group(:content_type).count.sort_by { |_, count| -count }.first(5)
|
12
12
|
|
13
13
|
@recent_blobs = ActiveStorage::Blob.order(created_at: :desc).limit(5)
|
14
|
+
|
15
|
+
@purgable_blobs = ActiveStorage::Blob.left_outer_joins(:attachments)
|
16
|
+
.where(active_storage_attachments: { id: nil })
|
14
17
|
|
15
18
|
# Find largest blob
|
16
19
|
@largest_blob = ActiveStorage::Blob.order(byte_size: :desc).first
|
@@ -1,5 +1,60 @@
|
|
1
1
|
<h1 class="page-title">Active Storage Attachments</h1>
|
2
2
|
|
3
|
+
<div class="filter-panel">
|
4
|
+
<div class="filter-header">
|
5
|
+
<h3 class="filter-title">Filter Attachments</h3>
|
6
|
+
<button id="toggle-filters" class="filter-toggle">
|
7
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="filter-icon">
|
8
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M12 3c2.755 0 5.455.232 8.083.678.533.09.917.556.917 1.096v1.044a2.25 2.25 0 01-.659 1.591l-5.432 5.432a2.25 2.25 0 00-.659 1.591v2.927a2.25 2.25 0 01-1.244 2.013L9.75 21v-6.568a2.25 2.25 0 00-.659-1.591L3.659 7.409A2.25 2.25 0 013 5.818V4.774c0-.54.384-1.006.917-1.096A48.32 48.32 0 0112 3z" />
|
9
|
+
</svg>
|
10
|
+
</button>
|
11
|
+
</div>
|
12
|
+
|
13
|
+
<form action="<%= attachments_path %>" method="get" class="filters-form" id="filters-form">
|
14
|
+
<div class="filter-row">
|
15
|
+
<div class="filter-group">
|
16
|
+
<label for="filter_name">Attachment Name:</label>
|
17
|
+
<input type="text" name="name" id="filter_name" class="filter-input" value="<%= params[:name] %>" placeholder="Search by name">
|
18
|
+
</div>
|
19
|
+
|
20
|
+
<div class="filter-group">
|
21
|
+
<label for="filter_record_type">Record Type:</label>
|
22
|
+
<select name="record_type" id="filter_record_type" class="filter-select">
|
23
|
+
<option value="">All Record Types</option>
|
24
|
+
<% @record_types&.each do |record_type| %>
|
25
|
+
<option value="<%= record_type %>" <%= params[:record_type] == record_type ? 'selected' : '' %>><%= record_type %></option>
|
26
|
+
<% end %>
|
27
|
+
</select>
|
28
|
+
</div>
|
29
|
+
|
30
|
+
<div class="filter-group">
|
31
|
+
<label for="filter_content_type">Content Type:</label>
|
32
|
+
<select name="content_type" id="filter_content_type" class="filter-select">
|
33
|
+
<option value="">All Content Types</option>
|
34
|
+
<% @content_types&.each do |content_type| %>
|
35
|
+
<option value="<%= content_type %>" <%= params[:content_type] == content_type ? 'selected' : '' %>><%= content_type || 'Unknown' %></option>
|
36
|
+
<% end %>
|
37
|
+
</select>
|
38
|
+
</div>
|
39
|
+
</div>
|
40
|
+
|
41
|
+
<div class="filter-actions">
|
42
|
+
<button type="submit" class="btn btn-primary">
|
43
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="action-icon">
|
44
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M3 4.5h14.25M3 9h9.75M3 13.5h9.75m4.5-4.5v12m0 0l-3.75-3.75M17.25 21L21 17.25" />
|
45
|
+
</svg>
|
46
|
+
Apply Filters
|
47
|
+
</button>
|
48
|
+
<a href="<%= attachments_path %>" class="btn btn-outline">
|
49
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="action-icon">
|
50
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99" />
|
51
|
+
</svg>
|
52
|
+
Clear
|
53
|
+
</a>
|
54
|
+
</div>
|
55
|
+
</form>
|
56
|
+
</div>
|
57
|
+
|
3
58
|
<div class="view-options">
|
4
59
|
<button id="table-view-btn" class="view-toggle-btn active">Table View</button>
|
5
60
|
<button id="card-view-btn" class="view-toggle-btn">Card View</button>
|
@@ -63,7 +118,7 @@
|
|
63
118
|
<% end %>
|
64
119
|
</div>
|
65
120
|
<div class="attachment-info">
|
66
|
-
<h3 class="filename" title="<%= attachment.blob.filename %>"><%=
|
121
|
+
<h3 class="filename" title="<%= attachment.blob.filename %>"><%= attachment.blob.filename %></h3>
|
67
122
|
<p class="attachment-name"><strong><%= attachment.name %></strong></p>
|
68
123
|
<p class="record-info"><%= attachment.record_type %> #<%= attachment.record_id %></p>
|
69
124
|
<p class="file-info">
|
@@ -122,6 +177,7 @@
|
|
122
177
|
background-color: white;
|
123
178
|
display: flex;
|
124
179
|
flex-direction: column;
|
180
|
+
width: 100%;
|
125
181
|
}
|
126
182
|
|
127
183
|
.attachment-preview {
|
@@ -171,11 +227,14 @@
|
|
171
227
|
.attachment-info {
|
172
228
|
padding: 16px;
|
173
229
|
flex: 1;
|
230
|
+
min-width: 0;
|
231
|
+
width: 100%;
|
174
232
|
}
|
175
233
|
|
176
234
|
.attachment-info h3 {
|
177
235
|
margin: 0 0 8px 0;
|
178
236
|
font-size: 1.1rem;
|
237
|
+
width: 100%;
|
179
238
|
}
|
180
239
|
|
181
240
|
.attachment-info h3.filename {
|
@@ -184,12 +243,15 @@
|
|
184
243
|
white-space: nowrap;
|
185
244
|
overflow: hidden;
|
186
245
|
text-overflow: ellipsis;
|
246
|
+
width: 100%;
|
247
|
+
display: block;
|
187
248
|
}
|
188
249
|
|
189
250
|
.attachment-info p {
|
190
251
|
margin: 0 0 8px 0;
|
191
252
|
font-size: 0.9rem;
|
192
253
|
color: #6c757d;
|
254
|
+
width: 100%;
|
193
255
|
}
|
194
256
|
|
195
257
|
.attachment-name {
|
@@ -229,6 +291,129 @@
|
|
229
291
|
.card-actions {
|
230
292
|
margin-top: 8px;
|
231
293
|
}
|
294
|
+
|
295
|
+
.filename-truncate {
|
296
|
+
white-space: nowrap;
|
297
|
+
overflow: hidden;
|
298
|
+
text-overflow: ellipsis;
|
299
|
+
max-width: 100%;
|
300
|
+
display: block;
|
301
|
+
}
|
302
|
+
|
303
|
+
/* Enhanced Filter Panel */
|
304
|
+
.filter-panel {
|
305
|
+
background-color: var(--card-bg);
|
306
|
+
padding: 0;
|
307
|
+
border-radius: var(--border-radius);
|
308
|
+
margin-bottom: 1.5rem;
|
309
|
+
box-shadow: var(--box-shadow);
|
310
|
+
overflow: hidden;
|
311
|
+
border: 1px solid rgba(0,0,0,0.05);
|
312
|
+
}
|
313
|
+
|
314
|
+
.filter-header {
|
315
|
+
display: flex;
|
316
|
+
justify-content: space-between;
|
317
|
+
align-items: center;
|
318
|
+
padding: 0.75rem 1.25rem;
|
319
|
+
background-color: rgba(0,0,0,0.02);
|
320
|
+
border-bottom: 1px solid rgba(0,0,0,0.05);
|
321
|
+
}
|
322
|
+
|
323
|
+
.filter-title {
|
324
|
+
margin: 0;
|
325
|
+
font-size: 1.1rem;
|
326
|
+
font-weight: 600;
|
327
|
+
color: var(--dark-color);
|
328
|
+
}
|
329
|
+
|
330
|
+
.filter-toggle {
|
331
|
+
background: transparent;
|
332
|
+
border: none;
|
333
|
+
cursor: pointer;
|
334
|
+
display: flex;
|
335
|
+
align-items: center;
|
336
|
+
color: var(--secondary-color);
|
337
|
+
padding: 0.25rem;
|
338
|
+
}
|
339
|
+
|
340
|
+
.filter-toggle:hover {
|
341
|
+
color: var(--primary-color);
|
342
|
+
}
|
343
|
+
|
344
|
+
.filter-icon, .action-icon {
|
345
|
+
width: 1.25rem;
|
346
|
+
height: 1.25rem;
|
347
|
+
margin-right: 0.25rem;
|
348
|
+
}
|
349
|
+
|
350
|
+
.filters-form {
|
351
|
+
padding: 1.25rem;
|
352
|
+
}
|
353
|
+
|
354
|
+
.filter-row {
|
355
|
+
display: flex;
|
356
|
+
flex-wrap: wrap;
|
357
|
+
gap: 1.25rem;
|
358
|
+
margin-bottom: 1.25rem;
|
359
|
+
}
|
360
|
+
|
361
|
+
.filter-group {
|
362
|
+
display: flex;
|
363
|
+
flex-direction: column;
|
364
|
+
gap: 0.5rem;
|
365
|
+
flex: 1;
|
366
|
+
min-width: 180px;
|
367
|
+
}
|
368
|
+
|
369
|
+
.filter-group label {
|
370
|
+
font-weight: 500;
|
371
|
+
font-size: 0.875rem;
|
372
|
+
color: var(--dark-color);
|
373
|
+
}
|
374
|
+
|
375
|
+
.filter-select, .filter-input {
|
376
|
+
padding: 0.5rem 0.75rem;
|
377
|
+
border: 1px solid rgba(0,0,0,0.1);
|
378
|
+
border-radius: 0.375rem;
|
379
|
+
font-size: 0.875rem;
|
380
|
+
background-color: white;
|
381
|
+
transition: border-color 0.2s, box-shadow 0.2s;
|
382
|
+
}
|
383
|
+
|
384
|
+
.filter-select:focus, .filter-input:focus {
|
385
|
+
border-color: var(--primary-color);
|
386
|
+
box-shadow: 0 0 0 0.2rem rgba(67, 97, 238, 0.25);
|
387
|
+
outline: none;
|
388
|
+
}
|
389
|
+
|
390
|
+
.filter-actions {
|
391
|
+
display: flex;
|
392
|
+
gap: 0.75rem;
|
393
|
+
justify-content: flex-end;
|
394
|
+
}
|
395
|
+
|
396
|
+
.btn-primary {
|
397
|
+
background-color: var(--primary-color);
|
398
|
+
display: inline-flex;
|
399
|
+
align-items: center;
|
400
|
+
}
|
401
|
+
|
402
|
+
@media (max-width: 768px) {
|
403
|
+
.filter-row {
|
404
|
+
flex-direction: column;
|
405
|
+
}
|
406
|
+
|
407
|
+
.filter-actions {
|
408
|
+
flex-direction: column;
|
409
|
+
width: 100%;
|
410
|
+
}
|
411
|
+
|
412
|
+
.filter-actions .btn {
|
413
|
+
width: 100%;
|
414
|
+
justify-content: center;
|
415
|
+
}
|
416
|
+
}
|
232
417
|
</style>
|
233
418
|
|
234
419
|
<script>
|
@@ -265,5 +450,27 @@
|
|
265
450
|
cardViewBtn.classList.add('active');
|
266
451
|
localStorage.setItem('attachmentsViewMode', 'card');
|
267
452
|
}
|
453
|
+
|
454
|
+
// Toggle filters visibility
|
455
|
+
const toggleFiltersBtn = document.getElementById('toggle-filters');
|
456
|
+
const filtersForm = document.getElementById('filters-form');
|
457
|
+
|
458
|
+
// Check localStorage for saved state
|
459
|
+
const filtersVisible = localStorage.getItem('attachmentFiltersVisible') !== 'false';
|
460
|
+
|
461
|
+
// Set initial state
|
462
|
+
if (!filtersVisible) {
|
463
|
+
filtersForm.style.display = 'none';
|
464
|
+
}
|
465
|
+
|
466
|
+
toggleFiltersBtn.addEventListener('click', function() {
|
467
|
+
if (filtersForm.style.display === 'none') {
|
468
|
+
filtersForm.style.display = 'block';
|
469
|
+
localStorage.setItem('attachmentFiltersVisible', 'true');
|
470
|
+
} else {
|
471
|
+
filtersForm.style.display = 'none';
|
472
|
+
localStorage.setItem('attachmentFiltersVisible', 'false');
|
473
|
+
}
|
474
|
+
});
|
268
475
|
});
|
269
476
|
</script>
|
@@ -91,7 +91,7 @@
|
|
91
91
|
</tr>
|
92
92
|
<tr>
|
93
93
|
<th>Filename</th>
|
94
|
-
<td title="<%= @blob.filename %>"><%=
|
94
|
+
<td title="<%= @blob.filename %>" class="filename-truncate"><%= @blob.filename %></td>
|
95
95
|
</tr>
|
96
96
|
<tr>
|
97
97
|
<th>Content Type</th>
|
@@ -160,4 +160,12 @@
|
|
160
160
|
max-height: 600px;
|
161
161
|
border-radius: 4px;
|
162
162
|
}
|
163
|
+
|
164
|
+
.filename-truncate {
|
165
|
+
white-space: nowrap;
|
166
|
+
overflow: hidden;
|
167
|
+
text-overflow: ellipsis;
|
168
|
+
max-width: 100%;
|
169
|
+
display: inline-block;
|
170
|
+
}
|
163
171
|
</style>
|
@@ -1,5 +1,64 @@
|
|
1
1
|
<h1 class="page-title">Active Storage Blobs</h1>
|
2
2
|
|
3
|
+
<div class="filter-panel">
|
4
|
+
<div class="filter-header">
|
5
|
+
<h3 class="filter-title">Filter Blobs</h3>
|
6
|
+
<button id="toggle-filters" class="filter-toggle">
|
7
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="filter-icon">
|
8
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M12 3c2.755 0 5.455.232 8.083.678.533.09.917.556.917 1.096v1.044a2.25 2.25 0 01-.659 1.591l-5.432 5.432a2.25 2.25 0 00-.659 1.591v2.927a2.25 2.25 0 01-1.244 2.013L9.75 21v-6.568a2.25 2.25 0 00-.659-1.591L3.659 7.409A2.25 2.25 0 013 5.818V4.774c0-.54.384-1.006.917-1.096A48.32 48.32 0 0112 3z" />
|
9
|
+
</svg>
|
10
|
+
</button>
|
11
|
+
</div>
|
12
|
+
|
13
|
+
<form action="<%= blobs_path %>" method="get" class="filters-form" id="filters-form">
|
14
|
+
<div class="filter-row">
|
15
|
+
<div class="filter-group">
|
16
|
+
<label for="filter_type">Content Type:</label>
|
17
|
+
<select name="content_type" id="filter_type" class="filter-select">
|
18
|
+
<option value="">All Types</option>
|
19
|
+
<% @content_types&.each do |content_type| %>
|
20
|
+
<option value="<%= content_type %>" <%= params[:content_type] == content_type ? 'selected' : '' %>><%= content_type || 'Unknown' %></option>
|
21
|
+
<% end %>
|
22
|
+
</select>
|
23
|
+
</div>
|
24
|
+
|
25
|
+
<div class="filter-group">
|
26
|
+
<label for="filter_size">Size:</label>
|
27
|
+
<select name="size" id="filter_size" class="filter-select">
|
28
|
+
<option value="">Any Size</option>
|
29
|
+
<option value="small" <%= params[:size] == 'small' ? 'selected' : '' %>>Small (< 1MB)</option>
|
30
|
+
<option value="medium" <%= params[:size] == 'medium' ? 'selected' : '' %>>Medium (1MB - 10MB)</option>
|
31
|
+
<option value="large" <%= params[:size] == 'large' ? 'selected' : '' %>>Large (> 10MB)</option>
|
32
|
+
</select>
|
33
|
+
</div>
|
34
|
+
|
35
|
+
<div class="filter-group">
|
36
|
+
<label for="filter_status">Status:</label>
|
37
|
+
<select name="status" id="filter_status" class="filter-select">
|
38
|
+
<option value="">All Status</option>
|
39
|
+
<option value="purgable" <%= params[:status] == 'purgable' ? 'selected' : '' %>>Purgable Blobs</option>
|
40
|
+
<option value="attached" <%= params[:status] == 'attached' ? 'selected' : '' %>>Attached Blobs</option>
|
41
|
+
</select>
|
42
|
+
</div>
|
43
|
+
</div>
|
44
|
+
|
45
|
+
<div class="filter-actions">
|
46
|
+
<button type="submit" class="btn btn-primary">
|
47
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="action-icon">
|
48
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M3 4.5h14.25M3 9h9.75M3 13.5h9.75m4.5-4.5v12m0 0l-3.75-3.75M17.25 21L21 17.25" />
|
49
|
+
</svg>
|
50
|
+
Apply Filters
|
51
|
+
</button>
|
52
|
+
<a href="<%= blobs_path %>" class="btn btn-outline">
|
53
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="action-icon">
|
54
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99" />
|
55
|
+
</svg>
|
56
|
+
Clear
|
57
|
+
</a>
|
58
|
+
</div>
|
59
|
+
</form>
|
60
|
+
</div>
|
61
|
+
|
3
62
|
<div class="view-options">
|
4
63
|
<button id="table-view-btn" class="view-toggle-btn active">Table View</button>
|
5
64
|
<button id="card-view-btn" class="view-toggle-btn">Card View</button>
|
@@ -24,7 +83,7 @@
|
|
24
83
|
<% @blobs.each do |blob| %>
|
25
84
|
<tr>
|
26
85
|
<td><%= blob.id %></td>
|
27
|
-
<td title="<%= blob.filename %>"><%= truncate(blob.filename.to_s, length:
|
86
|
+
<td title="<%= blob.filename %>" class="filename-truncate"><%= truncate(blob.filename.to_s, length: 40) %></td>
|
28
87
|
<td><%= blob.content_type || 'Unknown' %></td>
|
29
88
|
<td><%= format_bytes(blob.byte_size) %></td>
|
30
89
|
<td><%= blob.created_at.strftime('%Y-%m-%d %H:%M') %></td>
|
@@ -63,7 +122,7 @@
|
|
63
122
|
<% end %>
|
64
123
|
</div>
|
65
124
|
<div class="blob-info">
|
66
|
-
<h3 class="filename" title="<%= blob.filename %>"><%=
|
125
|
+
<h3 class="filename" title="<%= blob.filename %>"><%= blob.filename %></h3>
|
67
126
|
<p class="record-info">ID: <%= blob.id %></p>
|
68
127
|
<p class="file-info">
|
69
128
|
<%= format_bytes(blob.byte_size) %> •
|
@@ -124,6 +183,7 @@
|
|
124
183
|
background-color: white;
|
125
184
|
display: flex;
|
126
185
|
flex-direction: column;
|
186
|
+
width: 100%;
|
127
187
|
}
|
128
188
|
|
129
189
|
.blob-preview {
|
@@ -173,6 +233,8 @@
|
|
173
233
|
.blob-info {
|
174
234
|
padding: 16px;
|
175
235
|
flex: 1;
|
236
|
+
min-width: 0;
|
237
|
+
width: 100%;
|
176
238
|
}
|
177
239
|
|
178
240
|
.blob-info h3.filename {
|
@@ -181,12 +243,15 @@
|
|
181
243
|
white-space: nowrap;
|
182
244
|
overflow: hidden;
|
183
245
|
text-overflow: ellipsis;
|
246
|
+
width: 100%;
|
247
|
+
display: block;
|
184
248
|
}
|
185
249
|
|
186
250
|
.blob-info p {
|
187
251
|
margin: 0 0 8px 0;
|
188
252
|
font-size: 0.9rem;
|
189
253
|
color: #6c757d;
|
254
|
+
width: 100%;
|
190
255
|
}
|
191
256
|
|
192
257
|
.view-details-btn {
|
@@ -222,6 +287,129 @@
|
|
222
287
|
.card-actions {
|
223
288
|
margin-top: 8px;
|
224
289
|
}
|
290
|
+
|
291
|
+
.filename-truncate {
|
292
|
+
white-space: nowrap;
|
293
|
+
overflow: hidden;
|
294
|
+
text-overflow: ellipsis;
|
295
|
+
max-width: 100%;
|
296
|
+
display: block;
|
297
|
+
}
|
298
|
+
|
299
|
+
/* Enhanced Filter Panel */
|
300
|
+
.filter-panel {
|
301
|
+
background-color: var(--card-bg);
|
302
|
+
padding: 0;
|
303
|
+
border-radius: var(--border-radius);
|
304
|
+
margin-bottom: 1.5rem;
|
305
|
+
box-shadow: var(--box-shadow);
|
306
|
+
overflow: hidden;
|
307
|
+
border: 1px solid rgba(0,0,0,0.05);
|
308
|
+
}
|
309
|
+
|
310
|
+
.filter-header {
|
311
|
+
display: flex;
|
312
|
+
justify-content: space-between;
|
313
|
+
align-items: center;
|
314
|
+
padding: 0.75rem 1.25rem;
|
315
|
+
background-color: rgba(0,0,0,0.02);
|
316
|
+
border-bottom: 1px solid rgba(0,0,0,0.05);
|
317
|
+
}
|
318
|
+
|
319
|
+
.filter-title {
|
320
|
+
margin: 0;
|
321
|
+
font-size: 1.1rem;
|
322
|
+
font-weight: 600;
|
323
|
+
color: var(--dark-color);
|
324
|
+
}
|
325
|
+
|
326
|
+
.filter-toggle {
|
327
|
+
background: transparent;
|
328
|
+
border: none;
|
329
|
+
cursor: pointer;
|
330
|
+
display: flex;
|
331
|
+
align-items: center;
|
332
|
+
color: var(--secondary-color);
|
333
|
+
padding: 0.25rem;
|
334
|
+
}
|
335
|
+
|
336
|
+
.filter-toggle:hover {
|
337
|
+
color: var(--primary-color);
|
338
|
+
}
|
339
|
+
|
340
|
+
.filter-icon, .action-icon {
|
341
|
+
width: 1.25rem;
|
342
|
+
height: 1.25rem;
|
343
|
+
margin-right: 0.25rem;
|
344
|
+
}
|
345
|
+
|
346
|
+
.filters-form {
|
347
|
+
padding: 1.25rem;
|
348
|
+
}
|
349
|
+
|
350
|
+
.filter-row {
|
351
|
+
display: flex;
|
352
|
+
flex-wrap: wrap;
|
353
|
+
gap: 1.25rem;
|
354
|
+
margin-bottom: 1.25rem;
|
355
|
+
}
|
356
|
+
|
357
|
+
.filter-group {
|
358
|
+
display: flex;
|
359
|
+
flex-direction: column;
|
360
|
+
gap: 0.5rem;
|
361
|
+
flex: 1;
|
362
|
+
min-width: 180px;
|
363
|
+
}
|
364
|
+
|
365
|
+
.filter-group label {
|
366
|
+
font-weight: 500;
|
367
|
+
font-size: 0.875rem;
|
368
|
+
color: var(--dark-color);
|
369
|
+
}
|
370
|
+
|
371
|
+
.filter-select, .filter-input {
|
372
|
+
padding: 0.5rem 0.75rem;
|
373
|
+
border: 1px solid rgba(0,0,0,0.1);
|
374
|
+
border-radius: 0.375rem;
|
375
|
+
font-size: 0.875rem;
|
376
|
+
background-color: white;
|
377
|
+
transition: border-color 0.2s, box-shadow 0.2s;
|
378
|
+
}
|
379
|
+
|
380
|
+
.filter-select:focus, .filter-input:focus {
|
381
|
+
border-color: var(--primary-color);
|
382
|
+
box-shadow: 0 0 0 0.2rem rgba(67, 97, 238, 0.25);
|
383
|
+
outline: none;
|
384
|
+
}
|
385
|
+
|
386
|
+
.filter-actions {
|
387
|
+
display: flex;
|
388
|
+
gap: 0.75rem;
|
389
|
+
justify-content: flex-end;
|
390
|
+
}
|
391
|
+
|
392
|
+
.btn-primary {
|
393
|
+
background-color: var(--primary-color);
|
394
|
+
display: inline-flex;
|
395
|
+
align-items: center;
|
396
|
+
}
|
397
|
+
|
398
|
+
@media (max-width: 768px) {
|
399
|
+
.filter-row {
|
400
|
+
flex-direction: column;
|
401
|
+
}
|
402
|
+
|
403
|
+
.filter-actions {
|
404
|
+
flex-direction: column;
|
405
|
+
width: 100%;
|
406
|
+
}
|
407
|
+
|
408
|
+
.filter-actions .btn {
|
409
|
+
width: 100%;
|
410
|
+
justify-content: center;
|
411
|
+
}
|
412
|
+
}
|
225
413
|
</style>
|
226
414
|
|
227
415
|
<script>
|
@@ -258,5 +446,27 @@
|
|
258
446
|
cardViewBtn.classList.add('active');
|
259
447
|
localStorage.setItem('blobsViewMode', 'card');
|
260
448
|
}
|
449
|
+
|
450
|
+
// Toggle filters visibility
|
451
|
+
const toggleFiltersBtn = document.getElementById('toggle-filters');
|
452
|
+
const filtersForm = document.getElementById('filters-form');
|
453
|
+
|
454
|
+
// Check localStorage for saved state
|
455
|
+
const filtersVisible = localStorage.getItem('filtersVisible') !== 'false';
|
456
|
+
|
457
|
+
// Set initial state
|
458
|
+
if (!filtersVisible) {
|
459
|
+
filtersForm.style.display = 'none';
|
460
|
+
}
|
461
|
+
|
462
|
+
toggleFiltersBtn.addEventListener('click', function() {
|
463
|
+
if (filtersForm.style.display === 'none') {
|
464
|
+
filtersForm.style.display = 'block';
|
465
|
+
localStorage.setItem('filtersVisible', 'true');
|
466
|
+
} else {
|
467
|
+
filtersForm.style.display = 'none';
|
468
|
+
localStorage.setItem('filtersVisible', 'false');
|
469
|
+
}
|
470
|
+
});
|
261
471
|
});
|
262
472
|
</script>
|
@@ -56,7 +56,7 @@
|
|
56
56
|
</tr>
|
57
57
|
<tr>
|
58
58
|
<th>Filename</th>
|
59
|
-
<td title="<%= @blob.filename %>"><%=
|
59
|
+
<td title="<%= @blob.filename %>" class="filename-truncate"><%= @blob.filename %></td>
|
60
60
|
</tr>
|
61
61
|
<tr>
|
62
62
|
<th>Content Type</th>
|
@@ -200,4 +200,12 @@
|
|
200
200
|
max-height: 600px;
|
201
201
|
border-radius: 4px;
|
202
202
|
}
|
203
|
+
|
204
|
+
.filename-truncate {
|
205
|
+
white-space: nowrap;
|
206
|
+
overflow: hidden;
|
207
|
+
text-overflow: ellipsis;
|
208
|
+
max-width: 100%;
|
209
|
+
display: inline-block;
|
210
|
+
}
|
203
211
|
</style>
|
@@ -10,7 +10,7 @@
|
|
10
10
|
|
11
11
|
|
12
12
|
</div>
|
13
|
-
<div class="stat-card-value count-up" data-value="<%= @blobs_count %>"
|
13
|
+
<div class="stat-card-value count-up" data-value="<%= @blobs_count %>"><%= @blobs_count %></div>
|
14
14
|
<div class="stat-card-label">Total Blobs</div>
|
15
15
|
</div>
|
16
16
|
|
@@ -21,7 +21,7 @@
|
|
21
21
|
</svg>
|
22
22
|
|
23
23
|
</div>
|
24
|
-
<div class="stat-card-value count-up" data-value="<%= @attachments_count %>"
|
24
|
+
<div class="stat-card-value count-up" data-value="<%= @attachments_count %>"><%= @attachments_count %></div>
|
25
25
|
<div class="stat-card-label">Total Attachments</div>
|
26
26
|
</div>
|
27
27
|
|
@@ -32,9 +32,20 @@
|
|
32
32
|
</svg>
|
33
33
|
|
34
34
|
</div>
|
35
|
-
<div class="stat-card-value count-up" data-value="<%= @variant_records_count %>"
|
35
|
+
<div class="stat-card-value count-up" data-value="<%= @variant_records_count %>"><%= @variant_records_count %></div>
|
36
36
|
<div class="stat-card-label">Variant Records</div>
|
37
37
|
</div>
|
38
|
+
|
39
|
+
<div class="stat-card">
|
40
|
+
<div class="stat-card-icon purge-icon">
|
41
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
42
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9-.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 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
|
43
|
+
</svg>
|
44
|
+
|
45
|
+
</div>
|
46
|
+
<div class="stat-card-value count-up" data-value="<%= @purgable_blobs.count %>"><%= @purgable_blobs.count %></div>
|
47
|
+
<div class="stat-card-label">Purgable Blobs</div>
|
48
|
+
</div>
|
38
49
|
|
39
50
|
<div class="stat-card">
|
40
51
|
<div class="stat-card-icon storage-icon">
|
@@ -104,7 +115,7 @@
|
|
104
115
|
<% end %>
|
105
116
|
</div>
|
106
117
|
<div class="activity-content">
|
107
|
-
<div class="activity-title" title="<%= blob.filename %>"><%= truncate(blob.filename.to_s, length:
|
118
|
+
<div class="activity-title filename-truncate" title="<%= blob.filename %>"><%= truncate(blob.filename.to_s, length: 40) %></div>
|
108
119
|
<div class="activity-meta">
|
109
120
|
<span class="activity-time"><%= time_ago_in_words(blob.created_at, locale: :en) %> ago</span>
|
110
121
|
<span class="activity-size"><%= format_bytes(blob.byte_size) %></span>
|
@@ -179,7 +190,7 @@
|
|
179
190
|
<% end %>
|
180
191
|
</div>
|
181
192
|
<div class="file-info">
|
182
|
-
<h3 class="file-name" title="<%= blob.filename %>"><%=
|
193
|
+
<h3 class="file-name filename-truncate" title="<%= blob.filename %>"><%= blob.filename %></h3>
|
183
194
|
<div class="file-meta">
|
184
195
|
<span class="file-size"><%= format_bytes(blob.byte_size) %></span>
|
185
196
|
<span class="file-date"><%= blob.created_at.strftime('%Y-%m-%d %H:%M') %></span>
|
@@ -299,6 +310,10 @@
|
|
299
310
|
.storage-icon {
|
300
311
|
background-color: var(--info-color);
|
301
312
|
}
|
313
|
+
|
314
|
+
.purge-icon {
|
315
|
+
background-color: var(--danger-color);
|
316
|
+
}
|
302
317
|
|
303
318
|
.stat-card-value {
|
304
319
|
font-size: 2.5rem;
|
@@ -426,12 +441,15 @@
|
|
426
441
|
.activity-timeline {
|
427
442
|
display: flex;
|
428
443
|
flex-direction: column;
|
444
|
+
width: 100%;
|
429
445
|
}
|
430
446
|
|
431
447
|
.activity-item {
|
432
448
|
display: flex;
|
433
449
|
gap: 1rem;
|
434
450
|
padding: 0.75rem 0;
|
451
|
+
width: 100%;
|
452
|
+
max-width: 100%;
|
435
453
|
}
|
436
454
|
|
437
455
|
.activity-divider {
|
@@ -446,15 +464,27 @@
|
|
446
464
|
}
|
447
465
|
|
448
466
|
.activity-content {
|
449
|
-
flex
|
467
|
+
flex: 1;
|
468
|
+
min-width: 0; /* Critical for text truncation */
|
469
|
+
max-width: calc(100% - 60px); /* Account for icon width + gap */
|
470
|
+
width: 100%;
|
471
|
+
overflow: hidden;
|
450
472
|
}
|
451
473
|
|
452
474
|
.activity-title {
|
453
475
|
font-weight: 500;
|
454
476
|
margin-bottom: 0.25rem;
|
477
|
+
width: 100%;
|
478
|
+
max-width: 100%;
|
479
|
+
}
|
480
|
+
|
481
|
+
.filename-truncate {
|
455
482
|
white-space: nowrap;
|
456
483
|
overflow: hidden;
|
457
484
|
text-overflow: ellipsis;
|
485
|
+
max-width: 100%;
|
486
|
+
display: block;
|
487
|
+
width: 100%;
|
458
488
|
}
|
459
489
|
|
460
490
|
.activity-meta {
|
@@ -463,11 +493,15 @@
|
|
463
493
|
display: flex;
|
464
494
|
gap: 1rem;
|
465
495
|
margin-bottom: 0.5rem;
|
496
|
+
max-width: 100%;
|
497
|
+
flex-wrap: wrap;
|
466
498
|
}
|
467
499
|
|
468
500
|
.activity-actions {
|
469
501
|
display: flex;
|
470
502
|
gap: 0.5rem;
|
503
|
+
flex-wrap: wrap;
|
504
|
+
max-width: 100%;
|
471
505
|
}
|
472
506
|
|
473
507
|
/* Storage summary */
|
@@ -535,6 +569,7 @@
|
|
535
569
|
display: flex;
|
536
570
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
537
571
|
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
572
|
+
width: 100%;
|
538
573
|
}
|
539
574
|
|
540
575
|
.recent-file-card:hover {
|
@@ -589,6 +624,8 @@
|
|
589
624
|
.file-info {
|
590
625
|
padding: 0.75rem;
|
591
626
|
flex: 1;
|
627
|
+
min-width: 0; /* Important for flexbox to respect text-overflow */
|
628
|
+
width: 100%;
|
592
629
|
}
|
593
630
|
|
594
631
|
.file-name {
|
@@ -597,6 +634,8 @@
|
|
597
634
|
white-space: nowrap;
|
598
635
|
overflow: hidden;
|
599
636
|
text-overflow: ellipsis;
|
637
|
+
width: 100%;
|
638
|
+
display: block;
|
600
639
|
}
|
601
640
|
|
602
641
|
.file-meta {
|
@@ -605,6 +644,7 @@
|
|
605
644
|
color: var(--secondary-color);
|
606
645
|
font-size: 0.75rem;
|
607
646
|
margin-bottom: 0.5rem;
|
647
|
+
width: 100%;
|
608
648
|
}
|
609
649
|
|
610
650
|
.view-details-btn {
|
@@ -664,6 +704,142 @@
|
|
664
704
|
font-style: italic;
|
665
705
|
padding: 2rem 0;
|
666
706
|
}
|
707
|
+
|
708
|
+
.filename-truncate {
|
709
|
+
white-space: nowrap;
|
710
|
+
overflow: hidden;
|
711
|
+
text-overflow: ellipsis;
|
712
|
+
max-width: 100%;
|
713
|
+
display: block;
|
714
|
+
}
|
715
|
+
</style>
|
716
|
+
|
717
|
+
<footer class="dashboard-footer">
|
718
|
+
<div class="footer-content">
|
719
|
+
<div class="love-message">
|
720
|
+
Made with <span class="heart">❤</span> by Giovanni Panasiti
|
721
|
+
</div>
|
722
|
+
<div class="opensource-message">
|
723
|
+
Embracing the open source spirit; because great software, like great ideas, deserves to be shared freely.
|
724
|
+
</div>
|
725
|
+
<div class="github-link">
|
726
|
+
<a href="https://github.com/giovapanasiti/active_storage_dashboard" target="_blank" class="github-button">
|
727
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="github-icon">
|
728
|
+
<path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path>
|
729
|
+
</svg>
|
730
|
+
Star on GitHub
|
731
|
+
</a>
|
732
|
+
</div>
|
733
|
+
</div>
|
734
|
+
</footer>
|
735
|
+
|
736
|
+
<style>
|
737
|
+
/* Base styles */
|
738
|
+
:root {
|
739
|
+
--primary-color: #4361ee;
|
740
|
+
--primary-dark-color: #3a0ca3;
|
741
|
+
--secondary-color: #6c757d;
|
742
|
+
--success-color: #2ecc71;
|
743
|
+
--info-color: #3498db;
|
744
|
+
--warning-color: #f39c12;
|
745
|
+
--danger-color: #e74c3c;
|
746
|
+
--light-color: #f8f9fa;
|
747
|
+
--dark-color: #343a40;
|
748
|
+
--card-bg: #ffffff;
|
749
|
+
--border-radius: 0.5rem;
|
750
|
+
--transition-speed: 0.3s;
|
751
|
+
--box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
752
|
+
}
|
753
|
+
|
754
|
+
/* Footer styles */
|
755
|
+
.dashboard-footer {
|
756
|
+
margin-top: 4rem;
|
757
|
+
padding: 2rem 0;
|
758
|
+
text-align: center;
|
759
|
+
border-top: 1px solid var(--border-color);
|
760
|
+
background: linear-gradient(180deg, transparent, rgba(67, 97, 238, 0.05));
|
761
|
+
}
|
762
|
+
|
763
|
+
.footer-content {
|
764
|
+
max-width: 800px;
|
765
|
+
margin: 0 auto;
|
766
|
+
display: flex;
|
767
|
+
flex-direction: column;
|
768
|
+
gap: 1rem;
|
769
|
+
align-items: center;
|
770
|
+
justify-content: center;
|
771
|
+
}
|
772
|
+
|
773
|
+
.love-message {
|
774
|
+
font-size: 1.1rem;
|
775
|
+
font-weight: 500;
|
776
|
+
color: var(--dark-color);
|
777
|
+
}
|
778
|
+
|
779
|
+
.heart {
|
780
|
+
color: var(--danger-color);
|
781
|
+
display: inline-block;
|
782
|
+
animation: heartbeat 1.5s ease infinite;
|
783
|
+
}
|
784
|
+
|
785
|
+
@keyframes heartbeat {
|
786
|
+
0%, 100% { transform: scale(1); }
|
787
|
+
50% { transform: scale(1.2); }
|
788
|
+
}
|
789
|
+
|
790
|
+
.opensource-message {
|
791
|
+
color: var(--secondary-color);
|
792
|
+
font-style: italic;
|
793
|
+
max-width: 600px;
|
794
|
+
line-height: 1.6;
|
795
|
+
}
|
796
|
+
|
797
|
+
.github-link {
|
798
|
+
margin-top: 1rem;
|
799
|
+
}
|
800
|
+
|
801
|
+
.github-button {
|
802
|
+
display: inline-flex;
|
803
|
+
align-items: center;
|
804
|
+
gap: 0.5rem;
|
805
|
+
padding: 0.5rem 1rem;
|
806
|
+
background-color: var(--dark-color);
|
807
|
+
color: white;
|
808
|
+
border-radius: 2rem;
|
809
|
+
text-decoration: none;
|
810
|
+
font-size: 0.9rem;
|
811
|
+
transition: all 0.3s ease;
|
812
|
+
}
|
813
|
+
|
814
|
+
.github-button:hover {
|
815
|
+
background-color: #000;
|
816
|
+
transform: translateY(-2px);
|
817
|
+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
818
|
+
text-decoration: none;
|
819
|
+
color: white;
|
820
|
+
}
|
821
|
+
|
822
|
+
.github-icon {
|
823
|
+
transition: transform 0.3s ease;
|
824
|
+
}
|
825
|
+
|
826
|
+
.github-button:hover .github-icon {
|
827
|
+
transform: rotate(360deg);
|
828
|
+
}
|
829
|
+
|
830
|
+
@media (max-width: 576px) {
|
831
|
+
.dashboard-footer {
|
832
|
+
padding: 1.5rem 1rem;
|
833
|
+
}
|
834
|
+
|
835
|
+
.love-message {
|
836
|
+
font-size: 1rem;
|
837
|
+
}
|
838
|
+
|
839
|
+
.opensource-message {
|
840
|
+
font-size: 0.9rem;
|
841
|
+
}
|
842
|
+
}
|
667
843
|
</style>
|
668
844
|
|
669
845
|
<script>
|
@@ -30,7 +30,7 @@
|
|
30
30
|
</tr>
|
31
31
|
<tr>
|
32
32
|
<th>Filename</th>
|
33
|
-
<td title="<%= @blob.filename %>"><%=
|
33
|
+
<td title="<%= @blob.filename %>" class="filename-truncate"><%= @blob.filename %></td>
|
34
34
|
</tr>
|
35
35
|
<tr>
|
36
36
|
<th>Content Type</th>
|
@@ -56,4 +56,14 @@
|
|
56
56
|
|
57
57
|
<div class="card-body">
|
58
58
|
<a href="<%= variant_records_path %>" class="back-link">← Back to Variant Records</a>
|
59
|
-
</div>
|
59
|
+
</div>
|
60
|
+
|
61
|
+
<style>
|
62
|
+
.filename-truncate {
|
63
|
+
white-space: nowrap;
|
64
|
+
overflow: hidden;
|
65
|
+
text-overflow: ellipsis;
|
66
|
+
max-width: 100%;
|
67
|
+
display: inline-block;
|
68
|
+
}
|
69
|
+
</style>
|
@@ -118,6 +118,14 @@
|
|
118
118
|
gap: 1.5rem;
|
119
119
|
}
|
120
120
|
|
121
|
+
.filename-truncate {
|
122
|
+
white-space: nowrap;
|
123
|
+
overflow: hidden;
|
124
|
+
text-overflow: ellipsis;
|
125
|
+
max-width: 100%;
|
126
|
+
display: inline-block;
|
127
|
+
}
|
128
|
+
|
121
129
|
.stat-card {
|
122
130
|
padding: 1.25rem;
|
123
131
|
text-align: center;
|
@@ -228,6 +236,211 @@
|
|
228
236
|
color: white;
|
229
237
|
}
|
230
238
|
|
239
|
+
/* Enhanced Button Styles */
|
240
|
+
.btn {
|
241
|
+
display: inline-flex;
|
242
|
+
align-items: center;
|
243
|
+
justify-content: center;
|
244
|
+
padding: 0.375rem 0.75rem;
|
245
|
+
font-size: 0.875rem;
|
246
|
+
font-weight: 500;
|
247
|
+
line-height: 1.5;
|
248
|
+
text-align: center;
|
249
|
+
white-space: nowrap;
|
250
|
+
vertical-align: middle;
|
251
|
+
cursor: pointer;
|
252
|
+
user-select: none;
|
253
|
+
border: 1px solid transparent;
|
254
|
+
border-radius: 0.25rem;
|
255
|
+
transition: color 0.15s ease-in-out,
|
256
|
+
background-color 0.15s ease-in-out,
|
257
|
+
border-color 0.15s ease-in-out,
|
258
|
+
box-shadow 0.15s ease-in-out;
|
259
|
+
}
|
260
|
+
|
261
|
+
.btn:hover {
|
262
|
+
text-decoration: none;
|
263
|
+
}
|
264
|
+
|
265
|
+
.btn:focus {
|
266
|
+
outline: 0;
|
267
|
+
box-shadow: 0 0 0 0.2rem rgba(74, 107, 239, 0.25);
|
268
|
+
}
|
269
|
+
|
270
|
+
.btn-sm {
|
271
|
+
padding: 0.25rem 0.5rem;
|
272
|
+
font-size: 0.765625rem;
|
273
|
+
line-height: 1.5;
|
274
|
+
border-radius: 0.2rem;
|
275
|
+
}
|
276
|
+
|
277
|
+
.btn-primary {
|
278
|
+
color: #fff;
|
279
|
+
background-color: var(--primary-color);
|
280
|
+
border-color: var(--primary-color);
|
281
|
+
}
|
282
|
+
|
283
|
+
.btn-primary:hover {
|
284
|
+
color: #fff;
|
285
|
+
background-color: #3b5bd9;
|
286
|
+
border-color: #2e4ecf;
|
287
|
+
}
|
288
|
+
|
289
|
+
.btn-primary:focus {
|
290
|
+
box-shadow: 0 0 0 0.2rem rgba(74, 107, 239, 0.5);
|
291
|
+
}
|
292
|
+
|
293
|
+
.btn-outline {
|
294
|
+
color: var(--primary-color);
|
295
|
+
background-color: transparent;
|
296
|
+
border-color: var(--primary-color);
|
297
|
+
}
|
298
|
+
|
299
|
+
.btn-outline:hover {
|
300
|
+
color: #fff;
|
301
|
+
background-color: var(--primary-color);
|
302
|
+
border-color: var(--primary-color);
|
303
|
+
}
|
304
|
+
|
305
|
+
.btn-outline:focus {
|
306
|
+
box-shadow: 0 0 0 0.2rem rgba(74, 107, 239, 0.25);
|
307
|
+
}
|
308
|
+
|
309
|
+
.btn-success {
|
310
|
+
color: #fff;
|
311
|
+
background-color: var(--success-color);
|
312
|
+
border-color: var(--success-color);
|
313
|
+
}
|
314
|
+
|
315
|
+
.btn-success:hover {
|
316
|
+
color: #fff;
|
317
|
+
background-color: #218838;
|
318
|
+
border-color: #1e7e34;
|
319
|
+
}
|
320
|
+
|
321
|
+
.btn-danger {
|
322
|
+
color: #fff;
|
323
|
+
background-color: var(--danger-color);
|
324
|
+
border-color: var(--danger-color);
|
325
|
+
}
|
326
|
+
|
327
|
+
.btn-danger:hover {
|
328
|
+
color: #fff;
|
329
|
+
background-color: #c82333;
|
330
|
+
border-color: #bd2130;
|
331
|
+
}
|
332
|
+
|
333
|
+
.action-icon {
|
334
|
+
width: 1rem;
|
335
|
+
height: 1rem;
|
336
|
+
margin-right: 0.25rem;
|
337
|
+
}
|
338
|
+
|
339
|
+
.filter-panel {
|
340
|
+
background-color: var(--card-bg);
|
341
|
+
padding: 0;
|
342
|
+
border-radius: 0.25rem;
|
343
|
+
margin-bottom: 1.5rem;
|
344
|
+
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
|
345
|
+
border: 1px solid var(--border-color);
|
346
|
+
}
|
347
|
+
|
348
|
+
.filter-header {
|
349
|
+
display: flex;
|
350
|
+
justify-content: space-between;
|
351
|
+
align-items: center;
|
352
|
+
padding: 0.75rem 1.25rem;
|
353
|
+
background-color: rgba(0, 0, 0, 0.03);
|
354
|
+
border-bottom: 1px solid var(--border-color);
|
355
|
+
}
|
356
|
+
|
357
|
+
.filter-title {
|
358
|
+
margin: 0;
|
359
|
+
font-size: 1rem;
|
360
|
+
font-weight: 500;
|
361
|
+
}
|
362
|
+
|
363
|
+
.filter-toggle {
|
364
|
+
background: transparent;
|
365
|
+
border: none;
|
366
|
+
cursor: pointer;
|
367
|
+
display: flex;
|
368
|
+
align-items: center;
|
369
|
+
color: var(--secondary-color);
|
370
|
+
padding: 0.25rem;
|
371
|
+
}
|
372
|
+
|
373
|
+
.filter-toggle:hover {
|
374
|
+
color: var(--primary-color);
|
375
|
+
}
|
376
|
+
|
377
|
+
.filter-icon {
|
378
|
+
width: 1.25rem;
|
379
|
+
height: 1.25rem;
|
380
|
+
}
|
381
|
+
|
382
|
+
.filters-form {
|
383
|
+
padding: 1.25rem;
|
384
|
+
}
|
385
|
+
|
386
|
+
.filter-row {
|
387
|
+
display: flex;
|
388
|
+
flex-wrap: wrap;
|
389
|
+
gap: 1rem;
|
390
|
+
margin-bottom: 1rem;
|
391
|
+
}
|
392
|
+
|
393
|
+
.filter-group {
|
394
|
+
display: flex;
|
395
|
+
flex-direction: column;
|
396
|
+
gap: 0.375rem;
|
397
|
+
flex: 1;
|
398
|
+
min-width: 180px;
|
399
|
+
}
|
400
|
+
|
401
|
+
.filter-group label {
|
402
|
+
font-weight: 500;
|
403
|
+
font-size: 0.875rem;
|
404
|
+
}
|
405
|
+
|
406
|
+
.filter-select,
|
407
|
+
.filter-input {
|
408
|
+
padding: 0.375rem 0.75rem;
|
409
|
+
border: 1px solid var(--border-color);
|
410
|
+
border-radius: 0.25rem;
|
411
|
+
font-size: 0.875rem;
|
412
|
+
background-color: #fff;
|
413
|
+
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
|
414
|
+
}
|
415
|
+
|
416
|
+
.filter-select:focus,
|
417
|
+
.filter-input:focus {
|
418
|
+
border-color: var(--primary-color);
|
419
|
+
outline: 0;
|
420
|
+
box-shadow: 0 0 0 0.2rem rgba(74, 107, 239, 0.25);
|
421
|
+
}
|
422
|
+
|
423
|
+
.filter-actions {
|
424
|
+
display: flex;
|
425
|
+
gap: 0.75rem;
|
426
|
+
justify-content: flex-end;
|
427
|
+
}
|
428
|
+
|
429
|
+
@media (max-width: 768px) {
|
430
|
+
.filter-row {
|
431
|
+
flex-direction: column;
|
432
|
+
}
|
433
|
+
|
434
|
+
.filter-actions {
|
435
|
+
flex-direction: column;
|
436
|
+
width: 100%;
|
437
|
+
}
|
438
|
+
|
439
|
+
.filter-actions .btn {
|
440
|
+
width: 100%;
|
441
|
+
}
|
442
|
+
}
|
443
|
+
|
231
444
|
.flash {
|
232
445
|
padding: 0.75rem 1.25rem;
|
233
446
|
margin-bottom: 1rem;
|
@@ -251,18 +464,54 @@
|
|
251
464
|
}
|
252
465
|
|
253
466
|
.download-btn {
|
254
|
-
display: inline-
|
255
|
-
|
467
|
+
display: inline-flex;
|
468
|
+
align-items: center;
|
469
|
+
justify-content: center;
|
470
|
+
padding: 0.375rem 0.75rem;
|
256
471
|
background-color: var(--success-color);
|
257
472
|
color: white;
|
258
473
|
text-decoration: none;
|
259
|
-
border-radius:
|
260
|
-
font-size: 0.
|
474
|
+
border-radius: 0.25rem;
|
475
|
+
font-size: 0.875rem;
|
476
|
+
font-weight: 500;
|
477
|
+
border: 1px solid var(--success-color);
|
478
|
+
transition: color 0.15s ease-in-out,
|
479
|
+
background-color 0.15s ease-in-out,
|
480
|
+
border-color 0.15s ease-in-out,
|
481
|
+
box-shadow 0.15s ease-in-out;
|
261
482
|
}
|
262
483
|
|
263
484
|
.download-btn:hover {
|
264
485
|
text-decoration: none;
|
265
|
-
|
486
|
+
background-color: #218838;
|
487
|
+
border-color: #1e7e34;
|
488
|
+
color: white;
|
489
|
+
}
|
490
|
+
|
491
|
+
.download-btn:focus {
|
492
|
+
outline: 0;
|
493
|
+
box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5);
|
494
|
+
}
|
495
|
+
|
496
|
+
.view-details-btn {
|
497
|
+
display: inline-flex;
|
498
|
+
align-items: center;
|
499
|
+
justify-content: center;
|
500
|
+
padding: 0.375rem 0.75rem;
|
501
|
+
background-color: var(--primary-color);
|
502
|
+
color: white;
|
503
|
+
text-decoration: none;
|
504
|
+
border-radius: 0.25rem;
|
505
|
+
font-size: 0.875rem;
|
506
|
+
font-weight: 500;
|
507
|
+
border: 1px solid var(--primary-color);
|
508
|
+
transition: all 0.15s ease-in-out;
|
509
|
+
}
|
510
|
+
|
511
|
+
.view-details-btn:hover {
|
512
|
+
text-decoration: none;
|
513
|
+
background-color: #3b5bd9;
|
514
|
+
border-color: #2e4ecf;
|
266
515
|
color: white;
|
267
516
|
}
|
268
517
|
</style>
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_storage_dashboard
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Giovanni Panasiti
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-05-
|
10
|
+
date: 2025-05-17 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: rails
|