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 +7 -0
- data/README.md +91 -0
- data/Rakefile +17 -0
- data/app/controllers/active_storage_dashboard/application_controller.rb +15 -0
- data/app/controllers/active_storage_dashboard/attachments_controller.rb +27 -0
- data/app/controllers/active_storage_dashboard/blobs_controller.rb +55 -0
- data/app/controllers/active_storage_dashboard/dashboard_controller.rb +123 -0
- data/app/controllers/active_storage_dashboard/variant_records_controller.rb +24 -0
- data/app/helpers/active_storage_dashboard/application_helper.rb +289 -0
- data/app/views/active_storage_dashboard/attachments/index.html.erb +269 -0
- data/app/views/active_storage_dashboard/attachments/show.html.erb +163 -0
- data/app/views/active_storage_dashboard/blobs/index.html.erb +262 -0
- data/app/views/active_storage_dashboard/blobs/show.html.erb +203 -0
- data/app/views/active_storage_dashboard/dashboard/index.html.erb +715 -0
- data/app/views/active_storage_dashboard/variant_records/index.html.erb +42 -0
- data/app/views/active_storage_dashboard/variant_records/show.html.erb +59 -0
- data/app/views/layouts/active_storage_dashboard/application.html.erb +302 -0
- data/config/routes.rb +19 -0
- data/lib/active_storage_dashboard/engine.rb +12 -0
- data/lib/active_storage_dashboard/version.rb +5 -0
- data/lib/active_storage_dashboard.rb +8 -0
- data/lib/tasks/active_storage_dashboard_tasks.rake +6 -0
- metadata +79 -0
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
|
+

|
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
|
+

|
56
|
+
|
57
|
+
#### Blob Details
|
58
|
+

|
59
|
+
|
60
|
+
#### Files Gallery
|
61
|
+

|
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
|