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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3c7c2bae3dc09def165c1871bf8e5d5636e6a1fe51f3a3c74c076dc40e383aa3
4
+ data.tar.gz: 6b0fdf2d5563fbf5d48848cc8322338bde7e916f3d180357c5b31d551626746e
5
+ SHA512:
6
+ metadata.gz: 3b402f97201e4e87e2c1bc67130f541cb6907222f2e0e271a61aa098540b108d4a986a2186b4ca0bb245997ac9b097a534bf3da473037cb216361033232dd626
7
+ data.tar.gz: e8dd05436410c27994704a8ef3d030ee909d931fa8758416c33ae73eb37e631a7799b0a4a822339069c5205070dc6eeb5e34adb9df233a5f57a455cb4f7da972
data/README.md ADDED
@@ -0,0 +1,91 @@
1
+ # 🚀 Active Storage Dashboard
2
+
3
+ A beautiful Rails engine that provides a sleek, modern dashboard for monitoring and inspecting Active Storage data in your Rails application.
4
+
5
+ ![Active Storage Dashboard Screenshot](https://github.com/giovapanasiti/active_storage_dashboard/blob/main/screenshots/dashboard.png)
6
+
7
+
8
+ ## ✨ Features
9
+
10
+ - 📊 Overview of Active Storage usage statistics
11
+ - 🔍 Browse and inspect blobs, attachments and variant records
12
+ - 📝 View metadata, file details, and relationships
13
+ - 🎨 Modern, responsive UI with animations
14
+ - 🚫 No external dependencies (vanilla JavaScript and CSS)
15
+
16
+ ## 📥 Installation
17
+
18
+ Add this line to your application's Gemfile:
19
+
20
+ ```ruby
21
+ gem 'active_storage_dashboard'
22
+ ```
23
+
24
+ And then execute:
25
+
26
+ ```bash
27
+ $ bundle
28
+ ```
29
+
30
+ ## 🔧 Usage
31
+
32
+ Mount the engine in your `config/routes.rb` file:
33
+
34
+ ```ruby
35
+ Rails.application.routes.draw do
36
+ # IMPORTANT: Make sure the mount path does not contain any special characters
37
+ # Use a simple path like '/active-storage-dashboard' or '/storage-dashboard'
38
+ # This is crucial for proper URL generation
39
+ mount ActiveStorageDashboard::Engine, at: "/active-storage-dashboard"
40
+ end
41
+ ```
42
+
43
+ Then visit `/active-storage-dashboard` in your browser to see the beautiful dashboard.
44
+
45
+ ### 📁 File Downloads
46
+
47
+ The dashboard provides direct file download capabilities from both the list and detail views. Simply click on the download button to get your files.
48
+
49
+ ### 📸 Screenshots
50
+
51
+ <details>
52
+ <summary>Click to see more screenshots</summary>
53
+
54
+ #### Dashboard Overview
55
+ ![Dashboard Overview](https://github.com/giovapanasiti/active_storage_dashboard/blob/main/screenshots/dashboard.png)
56
+
57
+ #### Blob Details
58
+ ![Blob Details](https://github.com/giovapanasiti/active_storage_dashboard/blob/main/screenshots/blob-details.png)
59
+
60
+ #### Files Gallery
61
+ ![Files Gallery](https://github.com/giovapanasiti/active_storage_dashboard/blob/main/screenshots/files-gallery.png)
62
+
63
+ </details>
64
+
65
+ ## 🔒 Security Considerations
66
+
67
+ This dashboard provides access to all Active Storage data. Consider adding authentication before using in production:
68
+
69
+ ```ruby
70
+ # config/routes.rb
71
+ authenticate :user, -> (user) { user.admin? } do
72
+ mount ActiveStorageDashboard::Engine, at: "/active-storage-dashboard"
73
+ end
74
+ ```
75
+
76
+ or with devise:
77
+
78
+ ```ruby
79
+ constraints lambda { |req| req.session[:user_id].present? || (req.env['warden'] && req.env['warden'].user(:user)) } do
80
+ mount ActiveStorageDashboard::Engine, at: "/active-storage-dashboard"
81
+ end
82
+ ```
83
+
84
+
85
+ ## 🤝 Contributing
86
+
87
+ Bug reports and pull requests are welcome on GitHub at https://github.com/giovapanasiti/active-storage-dashboard.
88
+
89
+ ## 📝 License
90
+
91
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,17 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'ActiveStorageDashboard'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.md')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ require 'bundler/gem_tasks'
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveStorageDashboard
4
+ class ApplicationController < ActionController::Base
5
+ protect_from_forgery with: :exception
6
+
7
+ # Simple pagination without external dependencies
8
+ helper_method :paginate
9
+
10
+ def paginate(scope, per_page = 20)
11
+ @page = [params[:page].to_i, 1].max
12
+ scope.limit(per_page).offset((@page - 1) * per_page)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveStorageDashboard
4
+ class AttachmentsController < ApplicationController
5
+ def index
6
+ @attachments = paginate(ActiveStorage::Attachment.order(created_at: :desc))
7
+ @total_count = ActiveStorage::Attachment.count
8
+ end
9
+
10
+ def show
11
+ @attachment = ActiveStorage::Attachment.find(params[:id])
12
+ @blob = @attachment.blob
13
+ end
14
+
15
+ def download
16
+ @attachment = ActiveStorage::Attachment.find(params[:id])
17
+ @blob = @attachment.blob
18
+
19
+ # Pass along the disposition parameter if present
20
+ if params[:disposition].present?
21
+ redirect_to download_blob_path(@blob, disposition: params[:disposition])
22
+ else
23
+ redirect_to download_blob_path(@blob)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveStorageDashboard
4
+ class BlobsController < ApplicationController
5
+ def index
6
+ @blobs = paginate(ActiveStorage::Blob.order(created_at: :desc))
7
+ @total_count = ActiveStorage::Blob.count
8
+ end
9
+
10
+ def show
11
+ @blob = ActiveStorage::Blob.find(params[:id])
12
+ @attachments = @blob.attachments
13
+ @variant_records = defined?(ActiveStorage::VariantRecord) ? @blob.variant_records : []
14
+ end
15
+
16
+ def download
17
+ @blob = ActiveStorage::Blob.find(params[:id])
18
+
19
+ # Determine the disposition (inline for preview, attachment for download)
20
+ # Params can be route params or query params
21
+ disposition = params[:disposition] || 'attachment'
22
+
23
+ # Different approaches depending on Rails version
24
+ if @blob.respond_to?(:open)
25
+ # Rails 6.0+: Use the open method to get the file
26
+ begin
27
+ @blob.open do |file|
28
+ send_data file.read,
29
+ filename: @blob.filename.to_s,
30
+ type: @blob.content_type || 'application/octet-stream',
31
+ disposition: disposition
32
+ end
33
+ rescue => e
34
+ Rails.logger.error("Failed to download blob: #{e.message}")
35
+ redirect_to blob_path(@blob), alert: "Download failed: #{e.message}"
36
+ end
37
+ elsif @blob.respond_to?(:download)
38
+ # Alternative approach: Use the download method
39
+ begin
40
+ send_data @blob.download,
41
+ filename: @blob.filename.to_s,
42
+ type: @blob.content_type || 'application/octet-stream',
43
+ disposition: disposition
44
+ rescue => e
45
+ Rails.logger.error("Failed to download blob: #{e.message}")
46
+ redirect_to blob_path(@blob), alert: "Download failed: #{e.message}"
47
+ end
48
+ else
49
+ # Fallback: Redirect to main app blob path
50
+ disposition_param = disposition == 'inline' ? { disposition: 'inline' } : { disposition: 'attachment' }
51
+ redirect_to main_app.rails_blob_path(@blob, disposition_param)
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveStorageDashboard
4
+ class DashboardController < ApplicationController
5
+ def index
6
+ @blobs_count = ActiveStorage::Blob.count
7
+ @attachments_count = ActiveStorage::Attachment.count
8
+ @variant_records_count = defined?(ActiveStorage::VariantRecord) ? ActiveStorage::VariantRecord.count : 0
9
+
10
+ @total_storage = ActiveStorage::Blob.sum(:byte_size)
11
+ @content_types = ActiveStorage::Blob.group(:content_type).count.sort_by { |_, count| -count }.first(5)
12
+
13
+ @recent_blobs = ActiveStorage::Blob.order(created_at: :desc).limit(5)
14
+
15
+ # Find largest blob
16
+ @largest_blob = ActiveStorage::Blob.order(byte_size: :desc).first
17
+
18
+ # Initialize empty hash for the timeline chart
19
+ @blobs_by_month = {}
20
+
21
+ begin
22
+ load_timeline_data
23
+ rescue => e
24
+ # If everything fails, generate sample data
25
+ Rails.logger.error "Error in timeline chart preparation: #{e.message}"
26
+ generate_sample_timeline_data
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def load_timeline_data
33
+ # Get all blobs created in the last year
34
+ start_date = 1.year.ago.beginning_of_month
35
+
36
+ # First try with ActiveRecord
37
+ begin
38
+ blobs_with_dates = ActiveStorage::Blob
39
+ .where('created_at >= ?', start_date)
40
+ .pluck(:created_at)
41
+
42
+ # Group by year-month manually
43
+ month_counts = Hash.new(0)
44
+ blobs_with_dates.each do |date|
45
+ month_key = date.strftime('%Y-%m')
46
+ month_counts[month_key] += 1
47
+ end
48
+
49
+ # Sort by month
50
+ @blobs_by_month = month_counts.sort.to_h
51
+
52
+ # If we didn't get any data and there are blobs, try the adapter-specific methods
53
+ if @blobs_by_month.empty? && ActiveStorage::Blob.count > 0
54
+ adapter_specific_timeline_data
55
+ end
56
+ rescue => e
57
+ # If ActiveRecord method fails, try adapter-specific methods
58
+ Rails.logger.error "Error using ActiveRecord for timeline: #{e.message}"
59
+ adapter_specific_timeline_data
60
+ end
61
+ end
62
+
63
+ def adapter_specific_timeline_data
64
+ adapter = ActiveRecord::Base.connection.adapter_name.downcase
65
+ table_name = ActiveStorage::Blob.table_name
66
+
67
+ begin
68
+ # Different approach for different database adapters
69
+ if adapter.include?('sqlite')
70
+ # SQLite
71
+ result = ActiveRecord::Base.connection.execute(
72
+ "SELECT strftime('%Y-%m', created_at) as month, COUNT(*) as count " +
73
+ "FROM #{table_name} GROUP BY strftime('%Y-%m', created_at) " +
74
+ "ORDER BY month LIMIT 12"
75
+ )
76
+
77
+ result.each do |row|
78
+ @blobs_by_month[row['month']] = row['count']
79
+ end
80
+ elsif adapter.include?('mysql')
81
+ # MySQL
82
+ result = ActiveRecord::Base.connection.execute(
83
+ "SELECT DATE_FORMAT(created_at, '%Y-%m') as month, COUNT(*) as count " +
84
+ "FROM #{table_name} GROUP BY DATE_FORMAT(created_at, '%Y-%m') " +
85
+ "ORDER BY month LIMIT 12"
86
+ )
87
+
88
+ result.each do |row|
89
+ month_key = row[0] || row['month'] # Handle different result formats
90
+ count = row[1] || row['count']
91
+ @blobs_by_month[month_key] = count
92
+ end
93
+ else
94
+ # PostgreSQL or others
95
+ result = ActiveRecord::Base.connection.execute(
96
+ "SELECT TO_CHAR(created_at, 'YYYY-MM') as month, COUNT(*) as count " +
97
+ "FROM #{table_name} GROUP BY TO_CHAR(created_at, 'YYYY-MM') " +
98
+ "ORDER BY month LIMIT 12"
99
+ )
100
+
101
+ result.each do |row|
102
+ month_key = row['month']
103
+ count = row['count']
104
+ @blobs_by_month[month_key] = count if month_key && count
105
+ end
106
+ end
107
+ rescue => e
108
+ # If database-specific approach fails, use sample data
109
+ Rails.logger.error "Error using #{adapter} for timeline: #{e.message}"
110
+ generate_sample_timeline_data
111
+ end
112
+ end
113
+
114
+ def generate_sample_timeline_data
115
+ # Generate sample data for last 6 months if we can't query the database
116
+ today = Date.today
117
+ 6.times do |i|
118
+ month = (today - i.months).strftime('%Y-%m')
119
+ @blobs_by_month[month] = rand(1..20) # Random count between 1-20
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveStorageDashboard
4
+ class VariantRecordsController < ApplicationController
5
+ def index
6
+ if defined?(ActiveStorage::VariantRecord)
7
+ @variant_records = paginate(ActiveStorage::VariantRecord.order(id: :desc))
8
+ @total_count = ActiveStorage::VariantRecord.count
9
+ else
10
+ @variant_records = []
11
+ @total_count = 0
12
+ end
13
+ end
14
+
15
+ def show
16
+ if defined?(ActiveStorage::VariantRecord)
17
+ @variant_record = ActiveStorage::VariantRecord.find(params[:id])
18
+ @blob = @variant_record.blob
19
+ else
20
+ redirect_to root_path, alert: "Variant Records are not available in this Rails version"
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,289 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveStorageDashboard
4
+ module ApplicationHelper
5
+ # Include Rails URL helpers to easily generate blob URLs
6
+ include Rails.application.routes.url_helpers
7
+
8
+ # Helper to get the main app's routes
9
+ def main_app
10
+ Rails.application.class.routes.url_helpers
11
+ end
12
+
13
+ # Helper to generate a URL for direct blob viewing (for embedding media)
14
+ def rails_blob_url(blob, disposition: nil)
15
+ # For media embedding, we need a reliable URL to the blob
16
+ if defined?(request) && request.present?
17
+ # Get the proper host from the request
18
+ host = request.base_url
19
+
20
+ # Different approaches depending on Rails version
21
+ if Rails.gem_version >= Gem::Version.new('6.1') && Rails.application.routes.url_helpers.respond_to?(:rails_storage_proxy_path)
22
+ # Rails 6.1+ uses proxy approach
23
+ return host + Rails.application.routes.url_helpers.rails_storage_proxy_path(blob, disposition: disposition)
24
+ elsif Rails.application.routes.url_helpers.respond_to?(:rails_service_blob_path)
25
+ # Rails 5.2-6.0
26
+ return host + Rails.application.routes.url_helpers.rails_service_blob_path(blob.key, disposition: disposition)
27
+ elsif Rails.application.routes.url_helpers.respond_to?(:rails_blob_path)
28
+ # Another approach
29
+ return host + Rails.application.routes.url_helpers.rails_blob_path(blob, disposition: disposition)
30
+ end
31
+ end
32
+
33
+ # Fallback to direct service URL
34
+ if blob.respond_to?(:url)
35
+ return blob.url(disposition: disposition)
36
+ elsif blob.respond_to?(:service_url)
37
+ return blob.service_url(disposition: disposition)
38
+ end
39
+
40
+ # Last resort - return path to download action in our engine
41
+ active_storage_dashboard.download_blob_path(blob)
42
+ end
43
+
44
+ def pagination_links(total_count, per_page = 20)
45
+ return if total_count <= per_page
46
+
47
+ total_pages = (total_count.to_f / per_page).ceil
48
+ current_page = [@page.to_i, 1].max
49
+
50
+ content_tag :div, class: 'pagination' do
51
+ html = []
52
+
53
+ if current_page > 1
54
+ html << link_to('« Previous', url_for(page: current_page - 1), class: 'pagination-link')
55
+ else
56
+ html << content_tag(:span, '« Previous', class: 'pagination-link disabled')
57
+ end
58
+
59
+ # Show window of pages
60
+ window_size = 5
61
+ window_start = [1, current_page - (window_size / 2)].max
62
+ window_end = [total_pages, window_start + window_size - 1].min
63
+
64
+ # Adjust window_start if we're at the end
65
+ window_start = [1, window_end - window_size + 1].max
66
+
67
+ # First page
68
+ if window_start > 1
69
+ html << link_to('1', url_for(page: 1), class: 'pagination-link')
70
+ html << content_tag(:span, '...', class: 'pagination-ellipsis') if window_start > 2
71
+ end
72
+
73
+ # Page window
74
+ (window_start..window_end).each do |page|
75
+ if page == current_page
76
+ html << content_tag(:span, page, class: 'pagination-link current')
77
+ else
78
+ html << link_to(page, url_for(page: page), class: 'pagination-link')
79
+ end
80
+ end
81
+
82
+ # Last page
83
+ if window_end < total_pages
84
+ html << content_tag(:span, '...', class: 'pagination-ellipsis') if window_end < total_pages - 1
85
+ html << link_to(total_pages, url_for(page: total_pages), class: 'pagination-link')
86
+ end
87
+
88
+ if current_page < total_pages
89
+ html << link_to('Next »', url_for(page: current_page + 1), class: 'pagination-link')
90
+ else
91
+ html << content_tag(:span, 'Next »', class: 'pagination-link disabled')
92
+ end
93
+
94
+ safe_join(html)
95
+ end
96
+ end
97
+
98
+ def format_bytes(bytes)
99
+ return '0 B' if bytes.nil? || bytes == 0
100
+
101
+ units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']
102
+ exponent = (Math.log(bytes) / Math.log(1024)).to_i
103
+ exponent = [exponent, units.size - 1].min
104
+
105
+ converted = bytes.to_f / (1024 ** exponent)
106
+ "#{format('%.2f', converted)} #{units[exponent]}"
107
+ end
108
+
109
+ # Check if a blob can be previewed
110
+ def previewable_blob?(blob)
111
+ return false unless blob.present?
112
+
113
+ # Check for representable content based on content type
114
+ content_type = blob.content_type
115
+ return false unless content_type.present?
116
+
117
+ image_types = ['image/jpeg', 'image/png', 'image/gif', 'image/webp']
118
+ return true if image_types.include?(content_type)
119
+
120
+ # Check for previewable content in Rails 6+ using Active Storage's previewable method
121
+ if defined?(ActiveStorage::Blob.new.preview) &&
122
+ blob.respond_to?(:previewable?) &&
123
+ blob.previewable?
124
+ return true
125
+ end
126
+
127
+ false
128
+ end
129
+
130
+ # Generate preview HTML for a blob
131
+ def blob_preview(blob)
132
+ return "" unless blob.present?
133
+
134
+ content_type = blob.content_type
135
+
136
+ if content_type&.start_with?('image/')
137
+ url = Rails.application.routes.url_helpers.rails_blob_url(blob, disposition: "inline", only_path: true)
138
+ return image_tag(url, alt: blob.filename)
139
+ elsif defined?(ActiveStorage::Blob.new.preview) &&
140
+ blob.respond_to?(:previewable?) &&
141
+ blob.previewable?
142
+ # This will work in Rails 6+ with proper preview handlers configured
143
+ begin
144
+ url = Rails.application.routes.url_helpers.rails_blob_url(
145
+ blob.preview(resize: "300x300").processed.image,
146
+ disposition: "inline",
147
+ only_path: true
148
+ )
149
+ return image_tag(url, alt: blob.filename, class: "preview-image")
150
+ rescue => e
151
+ # Fallback if preview fails for any reason
152
+ Rails.logger.error("Preview generation failed: #{e.message}")
153
+ return content_tag(:div, "Preview not available", class: "preview-error")
154
+ end
155
+ end
156
+
157
+ return content_tag(:div, "No preview available", class: "no-preview")
158
+ end
159
+
160
+ # Check if an attachment can be previewed
161
+ def previewable_attachment?(attachment)
162
+ return false unless attachment&.blob&.present?
163
+
164
+ previewable_blob?(attachment.blob)
165
+ end
166
+
167
+ # Generate preview HTML for an attachment
168
+ def attachment_preview(attachment)
169
+ return "" unless attachment&.blob&.present?
170
+
171
+ blob_preview(attachment.blob)
172
+ end
173
+
174
+ # Helper to generate a download URL for a blob
175
+ def download_blob_url(blob)
176
+ begin
177
+ # Try simple direct download URL first using the blob's service URL
178
+ # This is often the most reliable method
179
+ if blob.respond_to?(:service_url)
180
+ return blob.service_url(disposition: "attachment", filename: blob.filename.to_s)
181
+ end
182
+
183
+ # If we're in a Rails 6.1+ app, use the direct representation URL
184
+ if Rails.gem_version >= Gem::Version.new('6.1') &&
185
+ blob.respond_to?(:representation) &&
186
+ Rails.application.routes.url_helpers.respond_to?(:rails_blob_representation_url)
187
+
188
+ # Force a download by using the blob directly
189
+ return Rails.application.routes.url_helpers.rails_blob_url(blob, disposition: "attachment")
190
+ end
191
+
192
+ # For Rails 6.0, use standard blob URL approach
193
+ if Rails.application.routes.url_helpers.respond_to?(:rails_blob_url)
194
+ host_options = {}
195
+
196
+ # Make sure we have a host set for URL generation
197
+ if defined?(request) && request.present?
198
+ host_options[:host] = request.host
199
+ host_options[:port] = request.port if request.port != 80 && request.port != 443
200
+ host_options[:protocol] = request.protocol.sub('://', '')
201
+ elsif Rails.application.config.action_controller.default_url_options[:host].present?
202
+ host_options = Rails.application.config.action_controller.default_url_options
203
+ else
204
+ # Fallback to localhost for development
205
+ host_options[:host] = 'localhost'
206
+ host_options[:port] = 3000
207
+ host_options[:protocol] = 'http'
208
+ end
209
+
210
+ # Ensure disposition is set for attachment download
211
+ return Rails.application.routes.url_helpers.rails_blob_url(blob, **host_options, disposition: "attachment")
212
+ end
213
+
214
+ # For Rails 5.2
215
+ if Rails.application.routes.url_helpers.respond_to?(:rails_service_blob_url)
216
+ host_options = {}
217
+ if defined?(request) && request.present?
218
+ host_options[:host] = request.host
219
+ host_options[:port] = request.port if request.port != 80 && request.port != 443
220
+ host_options[:protocol] = request.protocol.sub('://', '')
221
+ end
222
+ return Rails.application.routes.url_helpers.rails_service_blob_url(blob.key, **host_options, disposition: "attachment")
223
+ end
224
+
225
+ # If all else fails, use the direct download path (for development)
226
+ if Rails.application.routes.url_helpers.respond_to?(:rails_blob_path)
227
+ return Rails.application.routes.url_helpers.rails_blob_path(blob, disposition: "attachment")
228
+ end
229
+
230
+ Rails.logger.error("Could not determine download URL for blob #{blob.id}")
231
+ return "#"
232
+ rescue => e
233
+ Rails.logger.error("Error generating download URL: #{e.message}")
234
+ Rails.logger.error(e.backtrace.join("\n"))
235
+ return "#"
236
+ end
237
+ end
238
+
239
+ # Ensure we're properly using the engine routes
240
+ def active_storage_dashboard
241
+ ActiveStorageDashboard::Engine.routes.url_helpers
242
+ end
243
+
244
+ # Fix URL generation by explicitly using the engine routes
245
+ def download_attachment_path(attachment, options = {})
246
+ if options[:disposition].present?
247
+ # For paths with parameters, we need to construct the URL manually
248
+ disposition = options[:disposition]
249
+ active_storage_dashboard.download_attachment_path(attachment, disposition: disposition)
250
+ else
251
+ active_storage_dashboard.download_attachment_path(attachment)
252
+ end
253
+ end
254
+
255
+ def download_blob_path(blob, options = {})
256
+ if options[:disposition].present?
257
+ # For paths with parameters, we need to construct the URL manually
258
+ disposition = options[:disposition]
259
+ active_storage_dashboard.download_blob_path(blob, disposition: disposition)
260
+ else
261
+ active_storage_dashboard.download_blob_path(blob)
262
+ end
263
+ end
264
+
265
+ def attachment_path(attachment)
266
+ active_storage_dashboard.attachment_path(attachment)
267
+ end
268
+
269
+ def blob_path(blob)
270
+ active_storage_dashboard.blob_path(blob)
271
+ end
272
+
273
+ def attachments_path
274
+ active_storage_dashboard.attachments_path
275
+ end
276
+
277
+ def blobs_path
278
+ active_storage_dashboard.blobs_path
279
+ end
280
+
281
+ def variant_records_path
282
+ active_storage_dashboard.variant_records_path
283
+ end
284
+
285
+ def variant_record_path(variant_record)
286
+ active_storage_dashboard.variant_record_path(variant_record)
287
+ end
288
+ end
289
+ end