active_storage_dashboard 0.1.1

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.
@@ -0,0 +1,269 @@
1
+ <h1 class="page-title">Active Storage Attachments</h1>
2
+
3
+ <div class="view-options">
4
+ <button id="table-view-btn" class="view-toggle-btn active">Table View</button>
5
+ <button id="card-view-btn" class="view-toggle-btn">Card View</button>
6
+ </div>
7
+
8
+ <div class="card">
9
+ <div class="card-body">
10
+ <% if @attachments.any? %>
11
+ <div id="table-view">
12
+ <table>
13
+ <thead>
14
+ <tr>
15
+ <th>ID</th>
16
+ <th>Name</th>
17
+ <th>Record Type</th>
18
+ <th>Record ID</th>
19
+ <th>Created At</th>
20
+ <th>Actions</th>
21
+ </tr>
22
+ </thead>
23
+ <tbody>
24
+ <% @attachments.each do |attachment| %>
25
+ <tr>
26
+ <td><%= attachment.id %></td>
27
+ <td><%= attachment.name %></td>
28
+ <td><%= attachment.record_type %></td>
29
+ <td><%= attachment.record_id %></td>
30
+ <td><%= attachment.created_at.strftime('%Y-%m-%d %H:%M') %></td>
31
+ <td>
32
+ <a href="<%= attachment_path(attachment) %>">View Details</a>
33
+ </td>
34
+ </tr>
35
+ <% end %>
36
+ </tbody>
37
+ </table>
38
+ </div>
39
+
40
+ <div id="card-view" style="display: none;">
41
+ <div class="attachment-cards">
42
+ <% @attachments.each do |attachment| %>
43
+ <div class="attachment-card">
44
+ <div class="attachment-preview">
45
+ <% if previewable_attachment?(attachment) %>
46
+ <%= attachment_preview(attachment) %>
47
+ <% else %>
48
+ <div class="file-icon">
49
+ <% if attachment.blob.content_type&.start_with?('image/') %>
50
+ <div class="file-type-icon image-icon">IMG</div>
51
+ <% elsif attachment.blob.content_type&.start_with?('video/') %>
52
+ <div class="file-type-icon video-icon">VID</div>
53
+ <% elsif attachment.blob.content_type&.start_with?('audio/') %>
54
+ <div class="file-type-icon audio-icon">AUD</div>
55
+ <% elsif attachment.blob.content_type&.start_with?('application/pdf') %>
56
+ <div class="file-type-icon pdf-icon">PDF</div>
57
+ <% else %>
58
+ <div class="file-type-icon">
59
+ <%= attachment.blob.content_type&.split('/')&.last&.upcase || 'FILE' %>
60
+ </div>
61
+ <% end %>
62
+ </div>
63
+ <% end %>
64
+ </div>
65
+ <div class="attachment-info">
66
+ <h3 class="filename"><%= attachment.blob.filename %></h3>
67
+ <p class="attachment-name"><strong><%= attachment.name %></strong></p>
68
+ <p class="record-info"><%= attachment.record_type %> #<%= attachment.record_id %></p>
69
+ <p class="file-info">
70
+ <%= format_bytes(attachment.blob.byte_size) %> •
71
+ <%= attachment.blob.content_type || 'Unknown type' %>
72
+ </p>
73
+ <div class="card-actions">
74
+ <a href="<%= attachment_path(attachment) %>" class="view-details-btn">View Details</a>
75
+ <a href="<%= download_attachment_path(attachment) %>" class="download-btn">Download</a>
76
+ </div>
77
+ </div>
78
+ </div>
79
+ <% end %>
80
+ </div>
81
+ </div>
82
+
83
+ <%= pagination_links(@total_count) %>
84
+ <% else %>
85
+ <p>No attachments found.</p>
86
+ <% end %>
87
+ </div>
88
+ </div>
89
+
90
+ <style>
91
+ .view-options {
92
+ margin-bottom: 20px;
93
+ text-align: right;
94
+ }
95
+
96
+ .view-toggle-btn {
97
+ background-color: var(--light-color);
98
+ border: 1px solid var(--border-color);
99
+ padding: 8px 16px;
100
+ border-radius: 4px;
101
+ cursor: pointer;
102
+ margin-left: 8px;
103
+ }
104
+
105
+ .view-toggle-btn.active {
106
+ background-color: var(--primary-color);
107
+ color: white;
108
+ border-color: var(--primary-color);
109
+ }
110
+
111
+ .attachment-cards {
112
+ display: grid;
113
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
114
+ gap: 20px;
115
+ }
116
+
117
+ .attachment-card {
118
+ border: 1px solid var(--border-color);
119
+ border-radius: 8px;
120
+ overflow: hidden;
121
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
122
+ background-color: white;
123
+ display: flex;
124
+ flex-direction: column;
125
+ }
126
+
127
+ .attachment-preview {
128
+ height: 180px;
129
+ background-color: #f8f9fa;
130
+ display: flex;
131
+ align-items: center;
132
+ justify-content: center;
133
+ overflow: hidden;
134
+ }
135
+
136
+ .attachment-preview img {
137
+ max-width: 100%;
138
+ max-height: 100%;
139
+ object-fit: contain;
140
+ }
141
+
142
+ .file-icon {
143
+ width: 80px;
144
+ height: 100px;
145
+ background-color: #e9ecef;
146
+ display: flex;
147
+ align-items: center;
148
+ justify-content: center;
149
+ border-radius: 4px;
150
+ font-size: 0.9rem;
151
+ font-weight: bold;
152
+ }
153
+
154
+ .file-type-icon {
155
+ width: 60px;
156
+ height: 60px;
157
+ display: flex;
158
+ align-items: center;
159
+ justify-content: center;
160
+ border-radius: 4px;
161
+ font-weight: bold;
162
+ color: white;
163
+ background-color: #6c757d;
164
+ }
165
+
166
+ .file-type-icon.image-icon { background-color: #28a745; }
167
+ .file-type-icon.video-icon { background-color: #dc3545; }
168
+ .file-type-icon.audio-icon { background-color: #fd7e14; }
169
+ .file-type-icon.pdf-icon { background-color: #e83e8c; }
170
+
171
+ .attachment-info {
172
+ padding: 16px;
173
+ flex: 1;
174
+ }
175
+
176
+ .attachment-info h3 {
177
+ margin: 0 0 8px 0;
178
+ font-size: 1.1rem;
179
+ }
180
+
181
+ .attachment-info h3.filename {
182
+ margin: 0 0 8px 0;
183
+ font-size: 1.1rem;
184
+ white-space: nowrap;
185
+ overflow: hidden;
186
+ text-overflow: ellipsis;
187
+ }
188
+
189
+ .attachment-info p {
190
+ margin: 0 0 8px 0;
191
+ font-size: 0.9rem;
192
+ color: #6c757d;
193
+ }
194
+
195
+ .attachment-name {
196
+ color: var(--dark-color) !important;
197
+ }
198
+
199
+ .view-details-btn {
200
+ display: inline-block;
201
+ margin-top: 8px;
202
+ padding: 6px 12px;
203
+ background-color: var(--primary-color);
204
+ color: white;
205
+ text-decoration: none;
206
+ border-radius: 4px;
207
+ font-size: 0.9rem;
208
+ }
209
+
210
+ .download-btn {
211
+ display: inline-block;
212
+ margin-top: 8px;
213
+ margin-left: 8px;
214
+ padding: 6px 12px;
215
+ background-color: var(--success-color);
216
+ color: white;
217
+ text-decoration: none;
218
+ border-radius: 4px;
219
+ font-size: 0.9rem;
220
+ }
221
+
222
+ .view-details-btn:hover,
223
+ .download-btn:hover {
224
+ text-decoration: none;
225
+ opacity: 0.9;
226
+ color: white;
227
+ }
228
+
229
+ .card-actions {
230
+ margin-top: 8px;
231
+ }
232
+ </style>
233
+
234
+ <script>
235
+ document.addEventListener('DOMContentLoaded', function() {
236
+ const tableViewBtn = document.getElementById('table-view-btn');
237
+ const cardViewBtn = document.getElementById('card-view-btn');
238
+ const tableView = document.getElementById('table-view');
239
+ const cardView = document.getElementById('card-view');
240
+
241
+ // Set the initial view based on localStorage or default to table
242
+ const currentView = localStorage.getItem('attachmentsViewMode') || 'table';
243
+ if (currentView === 'card') {
244
+ setCardView();
245
+ } else {
246
+ setTableView();
247
+ }
248
+
249
+ // Add click handlers
250
+ tableViewBtn.addEventListener('click', setTableView);
251
+ cardViewBtn.addEventListener('click', setCardView);
252
+
253
+ function setTableView() {
254
+ tableView.style.display = 'block';
255
+ cardView.style.display = 'none';
256
+ tableViewBtn.classList.add('active');
257
+ cardViewBtn.classList.remove('active');
258
+ localStorage.setItem('attachmentsViewMode', 'table');
259
+ }
260
+
261
+ function setCardView() {
262
+ tableView.style.display = 'none';
263
+ cardView.style.display = 'block';
264
+ tableViewBtn.classList.remove('active');
265
+ cardViewBtn.classList.add('active');
266
+ localStorage.setItem('attachmentsViewMode', 'card');
267
+ }
268
+ });
269
+ </script>
@@ -0,0 +1,163 @@
1
+ <h1 class="page-title">Attachment Details</h1>
2
+
3
+ <% if previewable_blob?(@blob) %>
4
+ <div class="card">
5
+ <div class="card-header">Media Preview</div>
6
+ <div class="card-body media-preview">
7
+ <% if @blob.content_type&.start_with?('image/') %>
8
+ <div class="image-preview">
9
+ <img src="<%= download_blob_path(@blob, disposition: 'inline') %>" alt="<%= @blob.filename %>" />
10
+ </div>
11
+ <% elsif @blob.content_type&.start_with?('video/') %>
12
+ <div class="video-preview">
13
+ <video controls width="100%">
14
+ <source src="<%= download_blob_path(@blob, disposition: 'inline') %>" type="<%= @blob.content_type %>">
15
+ Your browser does not support the video tag.
16
+ </video>
17
+ </div>
18
+ <% elsif @blob.content_type&.start_with?('audio/') %>
19
+ <div class="audio-preview">
20
+ <audio controls>
21
+ <source src="<%= download_blob_path(@blob, disposition: 'inline') %>" type="<%= @blob.content_type %>">
22
+ Your browser does not support the audio tag.
23
+ </audio>
24
+ </div>
25
+ <% elsif @blob.content_type == 'application/pdf' %>
26
+ <div class="pdf-preview">
27
+ <object data="<%= download_blob_path(@blob, disposition: 'inline') %>" type="application/pdf" width="100%" height="600px">
28
+ <p>It appears your browser doesn't support embedded PDFs.
29
+ <a href="<%= download_blob_path(@blob, disposition: 'inline') %>">Click here to download the PDF</a>.</p>
30
+ </object>
31
+ </div>
32
+ <% elsif @blob.respond_to?(:preview) && @blob.previewable? %>
33
+ <div class="preview-image">
34
+ <% if Rails.gem_version >= Gem::Version.new('6.1') %>
35
+ <%= blob_preview(@blob) %>
36
+ <% else %>
37
+ <p>Preview not available. <a href="<%= download_blob_path(@blob, disposition: 'inline') %>">Download to view</a>.</p>
38
+ <% end %>
39
+ </div>
40
+ <% end %>
41
+ </div>
42
+ </div>
43
+ <% end %>
44
+
45
+ <div class="card">
46
+ <div class="card-header">Attachment Information</div>
47
+ <div class="card-body">
48
+ <table>
49
+ <tr>
50
+ <th>ID</th>
51
+ <td><%= @attachment.id %></td>
52
+ </tr>
53
+ <tr>
54
+ <th>Name</th>
55
+ <td><%= @attachment.name %></td>
56
+ </tr>
57
+ <tr>
58
+ <th>Record Type</th>
59
+ <td><%= @attachment.record_type %></td>
60
+ </tr>
61
+ <tr>
62
+ <th>Record ID</th>
63
+ <td><%= @attachment.record_id %></td>
64
+ </tr>
65
+ <tr>
66
+ <th>Record</th>
67
+ <td>
68
+ <% record = @attachment.record rescue nil %>
69
+ <% if record %>
70
+ <%= record.to_s %> (ID: <%= record.id %>)
71
+ <% else %>
72
+ Record not found or inaccessible
73
+ <% end %>
74
+ </td>
75
+ </tr>
76
+ <tr>
77
+ <th>Created At</th>
78
+ <td><%= @attachment.created_at.strftime('%Y-%m-%d %H:%M:%S') %></td>
79
+ </tr>
80
+ </table>
81
+ </div>
82
+ </div>
83
+
84
+ <div class="card">
85
+ <div class="card-header">Associated Blob</div>
86
+ <div class="card-body">
87
+ <table>
88
+ <tr>
89
+ <th>ID</th>
90
+ <td><%= @blob.id %></td>
91
+ </tr>
92
+ <tr>
93
+ <th>Filename</th>
94
+ <td><%= @blob.filename %></td>
95
+ </tr>
96
+ <tr>
97
+ <th>Content Type</th>
98
+ <td><%= @blob.content_type || 'Unknown' %></td>
99
+ </tr>
100
+ <tr>
101
+ <th>Size</th>
102
+ <td><%= format_bytes(@blob.byte_size) %></td>
103
+ </tr>
104
+ <tr>
105
+ <th>Created At</th>
106
+ <td><%= @blob.created_at.strftime('%Y-%m-%d %H:%M:%S') %></td>
107
+ </tr>
108
+ <tr>
109
+ <th>Actions</th>
110
+ <td>
111
+ <a href="<%= blob_path(@blob) %>">View Blob Details</a>
112
+ </td>
113
+ </tr>
114
+ </table>
115
+ </div>
116
+ </div>
117
+
118
+ <div class="card-body">
119
+ <a href="<%= attachments_path %>" class="back-link">← Back to Attachments</a>
120
+ <a href="<%= download_attachment_path(@attachment) %>" class="download-btn">Download File</a>
121
+ </div>
122
+
123
+ <style>
124
+ .media-preview {
125
+ display: flex;
126
+ justify-content: center;
127
+ align-items: center;
128
+ padding: 20px;
129
+ background: #f8f9fa;
130
+ border-radius: 4px;
131
+ overflow: hidden;
132
+ }
133
+
134
+ .image-preview {
135
+ max-width: 100%;
136
+ text-align: center;
137
+ }
138
+
139
+ .image-preview img {
140
+ max-width: 100%;
141
+ max-height: 600px;
142
+ border-radius: 4px;
143
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
144
+ }
145
+
146
+ .video-preview, .audio-preview, .pdf-preview {
147
+ width: 100%;
148
+ }
149
+
150
+ .audio-preview {
151
+ padding: 20px 0;
152
+ }
153
+
154
+ audio {
155
+ width: 100%;
156
+ }
157
+
158
+ .preview-image img {
159
+ max-width: 100%;
160
+ max-height: 600px;
161
+ border-radius: 4px;
162
+ }
163
+ </style>
@@ -0,0 +1,262 @@
1
+ <h1 class="page-title">Active Storage Blobs</h1>
2
+
3
+ <div class="view-options">
4
+ <button id="table-view-btn" class="view-toggle-btn active">Table View</button>
5
+ <button id="card-view-btn" class="view-toggle-btn">Card View</button>
6
+ </div>
7
+
8
+ <div class="card">
9
+ <div class="card-body">
10
+ <% if @blobs.any? %>
11
+ <div id="table-view">
12
+ <table>
13
+ <thead>
14
+ <tr>
15
+ <th>ID</th>
16
+ <th>Filename</th>
17
+ <th>Content Type</th>
18
+ <th>Size</th>
19
+ <th>Created</th>
20
+ <th>Actions</th>
21
+ </tr>
22
+ </thead>
23
+ <tbody>
24
+ <% @blobs.each do |blob| %>
25
+ <tr>
26
+ <td><%= blob.id %></td>
27
+ <td><%= blob.filename %></td>
28
+ <td><%= blob.content_type || 'Unknown' %></td>
29
+ <td><%= format_bytes(blob.byte_size) %></td>
30
+ <td><%= blob.created_at.strftime('%Y-%m-%d %H:%M') %></td>
31
+ <td>
32
+ <a href="<%= blob_path(blob) %>">View Details</a>
33
+ </td>
34
+ </tr>
35
+ <% end %>
36
+ </tbody>
37
+ </table>
38
+ </div>
39
+
40
+ <div id="card-view" style="display: none;">
41
+ <div class="blob-cards">
42
+ <% @blobs.each do |blob| %>
43
+ <div class="blob-card">
44
+ <div class="blob-preview">
45
+ <% if previewable_blob?(blob) %>
46
+ <%= blob_preview(blob) %>
47
+ <% else %>
48
+ <div class="file-icon">
49
+ <% if blob.content_type&.start_with?('image/') %>
50
+ <div class="file-type-icon image-icon">IMG</div>
51
+ <% elsif blob.content_type&.start_with?('video/') %>
52
+ <div class="file-type-icon video-icon">VID</div>
53
+ <% elsif blob.content_type&.start_with?('audio/') %>
54
+ <div class="file-type-icon audio-icon">AUD</div>
55
+ <% elsif blob.content_type&.start_with?('application/pdf') %>
56
+ <div class="file-type-icon pdf-icon">PDF</div>
57
+ <% else %>
58
+ <div class="file-type-icon">
59
+ <%= blob.content_type&.split('/')&.last&.upcase || 'FILE' %>
60
+ </div>
61
+ <% end %>
62
+ </div>
63
+ <% end %>
64
+ </div>
65
+ <div class="blob-info">
66
+ <h3 class="filename"><%= blob.filename %></h3>
67
+ <p class="record-info">ID: <%= blob.id %></p>
68
+ <p class="file-info">
69
+ <%= format_bytes(blob.byte_size) %> •
70
+ <%= blob.content_type || 'Unknown type' %>
71
+ </p>
72
+ <p class="created-at">
73
+ Created: <%= blob.created_at.strftime('%Y-%m-%d %H:%M') %>
74
+ </p>
75
+ <div class="card-actions">
76
+ <a href="<%= blob_path(blob) %>" class="view-details-btn">View Details</a>
77
+ <a href="<%= download_blob_path(blob) %>" class="download-btn">Download</a>
78
+ </div>
79
+ </div>
80
+ </div>
81
+ <% end %>
82
+ </div>
83
+ </div>
84
+
85
+ <%= pagination_links(@total_count) %>
86
+ <% else %>
87
+ <p>No blobs found.</p>
88
+ <% end %>
89
+ </div>
90
+ </div>
91
+
92
+ <style>
93
+ .view-options {
94
+ margin-bottom: 20px;
95
+ text-align: right;
96
+ }
97
+
98
+ .view-toggle-btn {
99
+ background-color: var(--light-color);
100
+ border: 1px solid var(--border-color);
101
+ padding: 8px 16px;
102
+ border-radius: 4px;
103
+ cursor: pointer;
104
+ margin-left: 8px;
105
+ }
106
+
107
+ .view-toggle-btn.active {
108
+ background-color: var(--primary-color);
109
+ color: white;
110
+ border-color: var(--primary-color);
111
+ }
112
+
113
+ .blob-cards {
114
+ display: grid;
115
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
116
+ gap: 20px;
117
+ }
118
+
119
+ .blob-card {
120
+ border: 1px solid var(--border-color);
121
+ border-radius: 8px;
122
+ overflow: hidden;
123
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
124
+ background-color: white;
125
+ display: flex;
126
+ flex-direction: column;
127
+ }
128
+
129
+ .blob-preview {
130
+ height: 180px;
131
+ background-color: #f8f9fa;
132
+ display: flex;
133
+ align-items: center;
134
+ justify-content: center;
135
+ overflow: hidden;
136
+ }
137
+
138
+ .blob-preview img {
139
+ max-width: 100%;
140
+ max-height: 100%;
141
+ object-fit: contain;
142
+ }
143
+
144
+ .file-icon {
145
+ width: 80px;
146
+ height: 100px;
147
+ background-color: #e9ecef;
148
+ display: flex;
149
+ align-items: center;
150
+ justify-content: center;
151
+ border-radius: 4px;
152
+ font-size: 0.9rem;
153
+ font-weight: bold;
154
+ }
155
+
156
+ .file-type-icon {
157
+ width: 60px;
158
+ height: 60px;
159
+ display: flex;
160
+ align-items: center;
161
+ justify-content: center;
162
+ border-radius: 4px;
163
+ font-weight: bold;
164
+ color: white;
165
+ background-color: #6c757d;
166
+ }
167
+
168
+ .file-type-icon.image-icon { background-color: #28a745; }
169
+ .file-type-icon.video-icon { background-color: #dc3545; }
170
+ .file-type-icon.audio-icon { background-color: #fd7e14; }
171
+ .file-type-icon.pdf-icon { background-color: #e83e8c; }
172
+
173
+ .blob-info {
174
+ padding: 16px;
175
+ flex: 1;
176
+ }
177
+
178
+ .blob-info h3.filename {
179
+ margin: 0 0 8px 0;
180
+ font-size: 1.1rem;
181
+ white-space: nowrap;
182
+ overflow: hidden;
183
+ text-overflow: ellipsis;
184
+ }
185
+
186
+ .blob-info p {
187
+ margin: 0 0 8px 0;
188
+ font-size: 0.9rem;
189
+ color: #6c757d;
190
+ }
191
+
192
+ .view-details-btn {
193
+ display: inline-block;
194
+ margin-top: 8px;
195
+ padding: 6px 12px;
196
+ background-color: var(--primary-color);
197
+ color: white;
198
+ text-decoration: none;
199
+ border-radius: 4px;
200
+ font-size: 0.9rem;
201
+ }
202
+
203
+ .download-btn {
204
+ display: inline-block;
205
+ margin-top: 8px;
206
+ margin-left: 8px;
207
+ padding: 6px 12px;
208
+ background-color: var(--success-color);
209
+ color: white;
210
+ text-decoration: none;
211
+ border-radius: 4px;
212
+ font-size: 0.9rem;
213
+ }
214
+
215
+ .view-details-btn:hover,
216
+ .download-btn:hover {
217
+ text-decoration: none;
218
+ opacity: 0.9;
219
+ color: white;
220
+ }
221
+
222
+ .card-actions {
223
+ margin-top: 8px;
224
+ }
225
+ </style>
226
+
227
+ <script>
228
+ document.addEventListener('DOMContentLoaded', function() {
229
+ const tableViewBtn = document.getElementById('table-view-btn');
230
+ const cardViewBtn = document.getElementById('card-view-btn');
231
+ const tableView = document.getElementById('table-view');
232
+ const cardView = document.getElementById('card-view');
233
+
234
+ // Set the initial view based on localStorage or default to table
235
+ const currentView = localStorage.getItem('blobsViewMode') || 'table';
236
+ if (currentView === 'card') {
237
+ setCardView();
238
+ } else {
239
+ setTableView();
240
+ }
241
+
242
+ // Add click handlers
243
+ tableViewBtn.addEventListener('click', setTableView);
244
+ cardViewBtn.addEventListener('click', setCardView);
245
+
246
+ function setTableView() {
247
+ tableView.style.display = 'block';
248
+ cardView.style.display = 'none';
249
+ tableViewBtn.classList.add('active');
250
+ cardViewBtn.classList.remove('active');
251
+ localStorage.setItem('blobsViewMode', 'table');
252
+ }
253
+
254
+ function setCardView() {
255
+ tableView.style.display = 'none';
256
+ cardView.style.display = 'block';
257
+ tableViewBtn.classList.remove('active');
258
+ cardViewBtn.classList.add('active');
259
+ localStorage.setItem('blobsViewMode', 'card');
260
+ }
261
+ });
262
+ </script>