active_canvas 0.0.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/MIT-LICENSE +20 -0
- data/README.md +318 -0
- data/Rakefile +6 -0
- data/app/assets/javascripts/active_canvas/editor/ai_panel.js +1607 -0
- data/app/assets/javascripts/active_canvas/editor/asset_manager.js +498 -0
- data/app/assets/javascripts/active_canvas/editor/blocks.js +1083 -0
- data/app/assets/javascripts/active_canvas/editor/code_panel.js +572 -0
- data/app/assets/javascripts/active_canvas/editor/component_toolbar.js +394 -0
- data/app/assets/javascripts/active_canvas/editor/panels.js +460 -0
- data/app/assets/javascripts/active_canvas/editor/utils.js +56 -0
- data/app/assets/javascripts/active_canvas/editor.js +295 -0
- data/app/assets/stylesheets/active_canvas/application.css +15 -0
- data/app/assets/stylesheets/active_canvas/editor.css +2929 -0
- data/app/controllers/active_canvas/admin/ai_controller.rb +181 -0
- data/app/controllers/active_canvas/admin/application_controller.rb +56 -0
- data/app/controllers/active_canvas/admin/media_controller.rb +61 -0
- data/app/controllers/active_canvas/admin/page_types_controller.rb +57 -0
- data/app/controllers/active_canvas/admin/page_versions_controller.rb +23 -0
- data/app/controllers/active_canvas/admin/pages_controller.rb +133 -0
- data/app/controllers/active_canvas/admin/partials_controller.rb +88 -0
- data/app/controllers/active_canvas/admin/settings_controller.rb +256 -0
- data/app/controllers/active_canvas/application_controller.rb +20 -0
- data/app/controllers/active_canvas/pages_controller.rb +18 -0
- data/app/controllers/concerns/active_canvas/current_user.rb +12 -0
- data/app/controllers/concerns/active_canvas/rate_limitable.rb +75 -0
- data/app/controllers/concerns/active_canvas/tailwind_compilation.rb +39 -0
- data/app/helpers/active_canvas/application_helper.rb +4 -0
- data/app/jobs/active_canvas/application_job.rb +4 -0
- data/app/jobs/active_canvas/compile_tailwind_job.rb +64 -0
- data/app/mailers/active_canvas/application_mailer.rb +6 -0
- data/app/models/active_canvas/ai_model.rb +136 -0
- data/app/models/active_canvas/application_record.rb +5 -0
- data/app/models/active_canvas/media.rb +141 -0
- data/app/models/active_canvas/page.rb +85 -0
- data/app/models/active_canvas/page_type.rb +22 -0
- data/app/models/active_canvas/page_version.rb +80 -0
- data/app/models/active_canvas/partial.rb +73 -0
- data/app/models/active_canvas/setting.rb +292 -0
- data/app/services/active_canvas/ai_configuration.rb +40 -0
- data/app/services/active_canvas/ai_models.rb +128 -0
- data/app/services/active_canvas/ai_service.rb +289 -0
- data/app/services/active_canvas/content_sanitizer.rb +112 -0
- data/app/services/active_canvas/tailwind_compiler.rb +156 -0
- data/app/views/active_canvas/admin/media/index.html.erb +401 -0
- data/app/views/active_canvas/admin/media/show.html.erb +297 -0
- data/app/views/active_canvas/admin/page_types/_form.html.erb +25 -0
- data/app/views/active_canvas/admin/page_types/edit.html.erb +13 -0
- data/app/views/active_canvas/admin/page_types/index.html.erb +29 -0
- data/app/views/active_canvas/admin/page_types/new.html.erb +9 -0
- data/app/views/active_canvas/admin/page_types/show.html.erb +18 -0
- data/app/views/active_canvas/admin/page_versions/show.html.erb +469 -0
- data/app/views/active_canvas/admin/pages/_form.html.erb +62 -0
- data/app/views/active_canvas/admin/pages/content.html.erb +139 -0
- data/app/views/active_canvas/admin/pages/edit.html.erb +335 -0
- data/app/views/active_canvas/admin/pages/editor.html.erb +710 -0
- data/app/views/active_canvas/admin/pages/index.html.erb +149 -0
- data/app/views/active_canvas/admin/pages/new.html.erb +19 -0
- data/app/views/active_canvas/admin/pages/show.html.erb +258 -0
- data/app/views/active_canvas/admin/pages/versions.html.erb +333 -0
- data/app/views/active_canvas/admin/partials/edit.html.erb +182 -0
- data/app/views/active_canvas/admin/partials/editor.html.erb +703 -0
- data/app/views/active_canvas/admin/partials/index.html.erb +131 -0
- data/app/views/active_canvas/admin/settings/show.html.erb +1864 -0
- data/app/views/active_canvas/pages/no_homepage.html.erb +45 -0
- data/app/views/active_canvas/pages/show.html.erb +113 -0
- data/app/views/layouts/active_canvas/admin/application.html.erb +960 -0
- data/app/views/layouts/active_canvas/admin/editor.html.erb +826 -0
- data/app/views/layouts/active_canvas/application.html.erb +55 -0
- data/config/routes.rb +48 -0
- data/db/migrate/20260202000001_create_active_canvas_tables.rb +113 -0
- data/db/migrate/20260202000002_create_active_canvas_ai_models.rb +26 -0
- data/lib/active_canvas/configuration.rb +232 -0
- data/lib/active_canvas/engine.rb +44 -0
- data/lib/active_canvas/version.rb +3 -0
- data/lib/active_canvas.rb +26 -0
- data/lib/generators/active_canvas/install/install_generator.rb +263 -0
- data/lib/generators/active_canvas/install/templates/initializer.rb.tt +163 -0
- data/lib/tasks/active_canvas_tasks.rake +69 -0
- metadata +150 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
|
|
7
|
+
<title><%= yield(:page_title).presence || "Active Canvas" %></title>
|
|
8
|
+
|
|
9
|
+
<%= csrf_meta_tags %>
|
|
10
|
+
<%= csp_meta_tag %>
|
|
11
|
+
|
|
12
|
+
<!-- SEO Meta Tags -->
|
|
13
|
+
<%= yield :seo_meta %>
|
|
14
|
+
|
|
15
|
+
<!-- Open Graph / Social -->
|
|
16
|
+
<%= yield :og_meta %>
|
|
17
|
+
|
|
18
|
+
<!-- Twitter Card -->
|
|
19
|
+
<%= yield :twitter_meta %>
|
|
20
|
+
|
|
21
|
+
<!-- Canonical URL -->
|
|
22
|
+
<%= yield :canonical %>
|
|
23
|
+
|
|
24
|
+
<!-- Structured Data -->
|
|
25
|
+
<%= yield :structured_data %>
|
|
26
|
+
|
|
27
|
+
<%= yield :head %>
|
|
28
|
+
|
|
29
|
+
<!-- ActiveCanvas: framework=<%= ActiveCanvas::Setting.css_framework %> compiled_mode=<%= ActiveCanvas::Setting.tailwind_compiled_mode? %> available=<%= ActiveCanvas::TailwindCompiler.available? %> env=<%= Rails.env %> -->
|
|
30
|
+
<% if ActiveCanvas::Setting.tailwind_compiled_mode? %>
|
|
31
|
+
<!-- ActiveCanvas: Using compiled Tailwind CSS (no CDN) -->
|
|
32
|
+
<% elsif ActiveCanvas::Setting.css_framework_url.present? %>
|
|
33
|
+
<!-- ActiveCanvas: Loading CSS framework from CDN -->
|
|
34
|
+
<% if ActiveCanvas::Setting.css_framework_type == :script %>
|
|
35
|
+
<script src="<%= ActiveCanvas::Setting.css_framework_url %>"></script>
|
|
36
|
+
<% else %>
|
|
37
|
+
<link rel="stylesheet" href="<%= ActiveCanvas::Setting.css_framework_url %>">
|
|
38
|
+
<% end %>
|
|
39
|
+
<% end %>
|
|
40
|
+
|
|
41
|
+
<%= stylesheet_link_tag "active_canvas/application", media: "all" %>
|
|
42
|
+
|
|
43
|
+
<% if ActiveCanvas::Setting.global_css.present? %>
|
|
44
|
+
<style><%= ActiveCanvas::Setting.global_css.html_safe %></style>
|
|
45
|
+
<% end %>
|
|
46
|
+
</head>
|
|
47
|
+
<body>
|
|
48
|
+
|
|
49
|
+
<%= yield %>
|
|
50
|
+
|
|
51
|
+
<% if ActiveCanvas::Setting.global_js.present? %>
|
|
52
|
+
<script><%= ActiveCanvas::Setting.global_js.html_safe %></script>
|
|
53
|
+
<% end %>
|
|
54
|
+
</body>
|
|
55
|
+
</html>
|
data/config/routes.rb
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
ActiveCanvas::Engine.routes.draw do
|
|
2
|
+
namespace :admin do
|
|
3
|
+
resources :pages do
|
|
4
|
+
member do
|
|
5
|
+
get :content
|
|
6
|
+
patch :update_content
|
|
7
|
+
get :editor
|
|
8
|
+
patch :save_editor
|
|
9
|
+
get :versions
|
|
10
|
+
end
|
|
11
|
+
resources :versions, only: [:show], controller: "page_versions"
|
|
12
|
+
end
|
|
13
|
+
resources :page_types
|
|
14
|
+
resources :partials, only: [:index, :edit, :update] do
|
|
15
|
+
member do
|
|
16
|
+
get :editor
|
|
17
|
+
patch :save_editor
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
resources :media, only: [:index, :show, :create, :destroy]
|
|
21
|
+
resource :settings, only: [:show, :update] do
|
|
22
|
+
patch :update_global_css
|
|
23
|
+
patch :update_global_js
|
|
24
|
+
patch :update_ai
|
|
25
|
+
post :sync_ai_models
|
|
26
|
+
patch :toggle_ai_model
|
|
27
|
+
patch :bulk_toggle_ai_models
|
|
28
|
+
post :create_ai_model
|
|
29
|
+
delete :destroy_ai_model
|
|
30
|
+
patch :update_tailwind_config
|
|
31
|
+
post :recompile_tailwind
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
namespace :ai do
|
|
35
|
+
post :chat
|
|
36
|
+
post :image
|
|
37
|
+
post :screenshot_to_code
|
|
38
|
+
get :models
|
|
39
|
+
get :status
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
root to: "pages#index"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
root to: "pages#home"
|
|
46
|
+
get ":slug", to: "pages#show", as: :public_page,
|
|
47
|
+
constraints: ->(req) { ActiveCanvas::Page.published.exists?(slug: req.params[:slug]) }
|
|
48
|
+
end
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
class CreateActiveCanvasTables < ActiveRecord::Migration[8.0]
|
|
2
|
+
def change
|
|
3
|
+
create_table :active_canvas_page_types, if_not_exists: true do |t|
|
|
4
|
+
t.string :name, null: false
|
|
5
|
+
t.string :key, null: false
|
|
6
|
+
|
|
7
|
+
t.timestamps
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
add_index :active_canvas_page_types, :key, unique: true, if_not_exists: true
|
|
11
|
+
|
|
12
|
+
create_table :active_canvas_pages, if_not_exists: true do |t|
|
|
13
|
+
t.string :title, null: false
|
|
14
|
+
t.string :slug
|
|
15
|
+
t.text :content
|
|
16
|
+
t.references :page_type, null: false, foreign_key: { to_table: :active_canvas_page_types }
|
|
17
|
+
t.boolean :published, default: false, null: false
|
|
18
|
+
|
|
19
|
+
# Editor data
|
|
20
|
+
t.text :content_css
|
|
21
|
+
t.text :content_js
|
|
22
|
+
t.text :content_components
|
|
23
|
+
|
|
24
|
+
# Tailwind compilation
|
|
25
|
+
t.text :compiled_tailwind_css
|
|
26
|
+
t.datetime :tailwind_compiled_at
|
|
27
|
+
|
|
28
|
+
# Header/Footer
|
|
29
|
+
t.boolean :show_header, default: true, null: false
|
|
30
|
+
t.boolean :show_footer, default: true, null: false
|
|
31
|
+
|
|
32
|
+
# SEO
|
|
33
|
+
t.string :meta_title
|
|
34
|
+
t.text :meta_description
|
|
35
|
+
t.string :canonical_url
|
|
36
|
+
t.string :meta_robots
|
|
37
|
+
|
|
38
|
+
# Open Graph
|
|
39
|
+
t.string :og_title
|
|
40
|
+
t.text :og_description
|
|
41
|
+
t.string :og_image
|
|
42
|
+
|
|
43
|
+
# Twitter
|
|
44
|
+
t.string :twitter_card
|
|
45
|
+
t.string :twitter_title
|
|
46
|
+
t.text :twitter_description
|
|
47
|
+
t.string :twitter_image
|
|
48
|
+
|
|
49
|
+
# Structured data
|
|
50
|
+
t.text :structured_data
|
|
51
|
+
|
|
52
|
+
t.timestamps
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
add_index :active_canvas_pages, :slug, unique: true, if_not_exists: true
|
|
56
|
+
|
|
57
|
+
create_table :active_canvas_page_versions, if_not_exists: true do |t|
|
|
58
|
+
t.references :page, null: false, foreign_key: { to_table: :active_canvas_pages }
|
|
59
|
+
t.integer :version_number, null: false
|
|
60
|
+
t.text :content_before
|
|
61
|
+
t.text :content_after
|
|
62
|
+
t.text :content_diff
|
|
63
|
+
t.text :css_before
|
|
64
|
+
t.text :css_after
|
|
65
|
+
t.string :change_summary
|
|
66
|
+
t.string :changed_by
|
|
67
|
+
t.integer :content_size_before
|
|
68
|
+
t.integer :content_size_after
|
|
69
|
+
|
|
70
|
+
t.timestamps
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
add_index :active_canvas_page_versions, [:page_id, :version_number], unique: true, if_not_exists: true
|
|
74
|
+
add_index :active_canvas_page_versions, :created_at, if_not_exists: true
|
|
75
|
+
|
|
76
|
+
create_table :active_canvas_partials, if_not_exists: true do |t|
|
|
77
|
+
t.string :name, null: false
|
|
78
|
+
t.string :partial_type, null: false
|
|
79
|
+
t.text :content
|
|
80
|
+
t.text :content_css
|
|
81
|
+
t.text :content_js
|
|
82
|
+
t.text :content_components
|
|
83
|
+
t.text :compiled_css
|
|
84
|
+
t.boolean :active, default: true, null: false
|
|
85
|
+
|
|
86
|
+
t.timestamps
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
add_index :active_canvas_partials, :partial_type, unique: true, if_not_exists: true
|
|
90
|
+
|
|
91
|
+
create_table :active_canvas_media, if_not_exists: true do |t|
|
|
92
|
+
t.string :filename, null: false
|
|
93
|
+
t.string :content_type
|
|
94
|
+
t.integer :byte_size
|
|
95
|
+
t.text :metadata
|
|
96
|
+
|
|
97
|
+
t.timestamps
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
add_index :active_canvas_media, :content_type, if_not_exists: true
|
|
101
|
+
add_index :active_canvas_media, :created_at, if_not_exists: true
|
|
102
|
+
|
|
103
|
+
create_table :active_canvas_settings, if_not_exists: true do |t|
|
|
104
|
+
t.string :key, null: false
|
|
105
|
+
t.text :value
|
|
106
|
+
t.text :encrypted_value
|
|
107
|
+
|
|
108
|
+
t.timestamps
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
add_index :active_canvas_settings, :key, unique: true, if_not_exists: true
|
|
112
|
+
end
|
|
113
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
class CreateActiveCanvasAiModels < ActiveRecord::Migration[8.0]
|
|
2
|
+
def change
|
|
3
|
+
create_table :active_canvas_ai_models, if_not_exists: true do |t|
|
|
4
|
+
t.string :model_id, null: false
|
|
5
|
+
t.string :provider, null: false
|
|
6
|
+
t.string :model_type
|
|
7
|
+
t.string :name
|
|
8
|
+
t.string :family
|
|
9
|
+
t.integer :context_window
|
|
10
|
+
t.integer :max_tokens
|
|
11
|
+
t.boolean :supports_functions, default: false
|
|
12
|
+
t.decimal :input_price_per_million, precision: 10, scale: 4
|
|
13
|
+
t.decimal :output_price_per_million, precision: 10, scale: 4
|
|
14
|
+
t.boolean :active, default: true
|
|
15
|
+
t.text :input_modalities
|
|
16
|
+
t.text :output_modalities
|
|
17
|
+
|
|
18
|
+
t.timestamps
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
add_index :active_canvas_ai_models, :model_id, unique: true, if_not_exists: true
|
|
22
|
+
add_index :active_canvas_ai_models, :provider, if_not_exists: true
|
|
23
|
+
add_index :active_canvas_ai_models, :model_type, if_not_exists: true
|
|
24
|
+
add_index :active_canvas_ai_models, :active, if_not_exists: true
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
module ActiveCanvas
|
|
2
|
+
class Configuration
|
|
3
|
+
# ==> Authentication
|
|
4
|
+
# Authentication callback for public pages
|
|
5
|
+
# Set to a proc/lambda that will be called as a before_action
|
|
6
|
+
# Example: config.authenticate_public = -> { redirect_to login_path unless current_user }
|
|
7
|
+
attr_accessor :authenticate_public
|
|
8
|
+
|
|
9
|
+
# Authentication callback for admin pages
|
|
10
|
+
# Set to a proc/lambda or method name symbol
|
|
11
|
+
# Example: config.authenticate_admin = :authenticate_admin_user!
|
|
12
|
+
# Example: config.authenticate_admin = -> { redirect_to login_path unless current_user&.admin? }
|
|
13
|
+
attr_accessor :authenticate_admin
|
|
14
|
+
|
|
15
|
+
# HTTP Basic Auth credentials (used when authenticate_admin = :http_basic_auth)
|
|
16
|
+
attr_accessor :http_basic_user
|
|
17
|
+
attr_accessor :http_basic_password
|
|
18
|
+
|
|
19
|
+
# Parent controller class for admin controllers
|
|
20
|
+
# Set to a string like "Admin::ApplicationController" to inherit authentication
|
|
21
|
+
# Example: config.admin_parent_controller = "Admin::ApplicationController"
|
|
22
|
+
attr_accessor :admin_parent_controller
|
|
23
|
+
attr_accessor :public_parent_controller
|
|
24
|
+
|
|
25
|
+
# Current user method name (used by AI features, version tracking, etc.)
|
|
26
|
+
attr_accessor :current_user_method
|
|
27
|
+
|
|
28
|
+
# ==> CSS Framework
|
|
29
|
+
# Default CSS framework: :tailwind, :bootstrap5, :none
|
|
30
|
+
# Can be overridden in admin settings
|
|
31
|
+
attr_accessor :css_framework
|
|
32
|
+
|
|
33
|
+
# ==> Media Uploads
|
|
34
|
+
# Enable/disable file uploads
|
|
35
|
+
attr_accessor :enable_uploads
|
|
36
|
+
|
|
37
|
+
# Maximum upload size in bytes
|
|
38
|
+
attr_accessor :max_upload_size
|
|
39
|
+
|
|
40
|
+
# Allowed MIME types for uploads
|
|
41
|
+
attr_accessor :allowed_content_types
|
|
42
|
+
|
|
43
|
+
# Allow SVG uploads (disabled by default due to XSS risks)
|
|
44
|
+
attr_accessor :allow_svg_uploads
|
|
45
|
+
|
|
46
|
+
# Active Storage service name (nil = default service)
|
|
47
|
+
attr_accessor :storage_service
|
|
48
|
+
|
|
49
|
+
# Make uploads publicly accessible (false = use signed URLs)
|
|
50
|
+
attr_accessor :public_uploads
|
|
51
|
+
|
|
52
|
+
# ==> Editor Settings
|
|
53
|
+
# Default blocks available in the editor
|
|
54
|
+
attr_accessor :editor_blocks
|
|
55
|
+
|
|
56
|
+
# Enable/disable specific editor features
|
|
57
|
+
attr_accessor :enable_ai_features
|
|
58
|
+
attr_accessor :enable_code_editor
|
|
59
|
+
attr_accessor :enable_asset_manager
|
|
60
|
+
|
|
61
|
+
# ==> Page Settings
|
|
62
|
+
# Auto-save interval in seconds (0 = disabled)
|
|
63
|
+
attr_accessor :autosave_interval
|
|
64
|
+
|
|
65
|
+
# Maximum versions to keep per page (0 = unlimited)
|
|
66
|
+
attr_accessor :max_versions_per_page
|
|
67
|
+
|
|
68
|
+
# ==> Security
|
|
69
|
+
# Sanitize HTML content on save
|
|
70
|
+
attr_accessor :sanitize_content
|
|
71
|
+
|
|
72
|
+
# Allowed HTML tags (when sanitize_content is true)
|
|
73
|
+
attr_accessor :allowed_html_tags
|
|
74
|
+
|
|
75
|
+
# Allowed HTML attributes (when sanitize_content is true)
|
|
76
|
+
attr_accessor :allowed_html_attributes
|
|
77
|
+
|
|
78
|
+
# ==> AI Security
|
|
79
|
+
# Rate limit for AI requests (per minute per IP)
|
|
80
|
+
attr_accessor :ai_rate_limit_per_minute
|
|
81
|
+
|
|
82
|
+
# Maximum stream timeout for AI chat
|
|
83
|
+
attr_accessor :ai_stream_timeout
|
|
84
|
+
|
|
85
|
+
# Idle timeout for AI streaming (no data received)
|
|
86
|
+
attr_accessor :ai_stream_idle_timeout
|
|
87
|
+
|
|
88
|
+
# Maximum response size for AI streaming
|
|
89
|
+
attr_accessor :ai_max_response_size
|
|
90
|
+
|
|
91
|
+
# Maximum screenshot size (base64 encoded)
|
|
92
|
+
attr_accessor :max_screenshot_size
|
|
93
|
+
|
|
94
|
+
# Allowed hosts for AI-generated image downloads
|
|
95
|
+
attr_accessor :allowed_ai_image_hosts
|
|
96
|
+
|
|
97
|
+
# Dangerous content types that are always blocked
|
|
98
|
+
DANGEROUS_CONTENT_TYPES = %w[
|
|
99
|
+
application/x-executable
|
|
100
|
+
application/x-sharedlib
|
|
101
|
+
application/x-mach-binary
|
|
102
|
+
text/html
|
|
103
|
+
application/javascript
|
|
104
|
+
text/javascript
|
|
105
|
+
application/x-httpd-php
|
|
106
|
+
].freeze
|
|
107
|
+
|
|
108
|
+
def initialize
|
|
109
|
+
# Authentication - open by default (configure in initializer!)
|
|
110
|
+
@authenticate_public = nil
|
|
111
|
+
@authenticate_admin = nil
|
|
112
|
+
@http_basic_user = nil
|
|
113
|
+
@http_basic_password = nil
|
|
114
|
+
@admin_parent_controller = "ActionController::Base"
|
|
115
|
+
@public_parent_controller = "ActionController::Base"
|
|
116
|
+
@current_user_method = :current_user
|
|
117
|
+
|
|
118
|
+
# CSS Framework
|
|
119
|
+
@css_framework = :tailwind
|
|
120
|
+
|
|
121
|
+
# Media Uploads
|
|
122
|
+
@enable_uploads = true
|
|
123
|
+
@max_upload_size = 10.megabytes
|
|
124
|
+
@allowed_content_types = %w[
|
|
125
|
+
image/jpeg
|
|
126
|
+
image/png
|
|
127
|
+
image/gif
|
|
128
|
+
image/webp
|
|
129
|
+
image/avif
|
|
130
|
+
application/pdf
|
|
131
|
+
]
|
|
132
|
+
@allow_svg_uploads = false
|
|
133
|
+
@storage_service = nil
|
|
134
|
+
@public_uploads = false
|
|
135
|
+
|
|
136
|
+
# Editor Settings
|
|
137
|
+
@editor_blocks = :all
|
|
138
|
+
@enable_ai_features = true
|
|
139
|
+
@enable_code_editor = true
|
|
140
|
+
@enable_asset_manager = true
|
|
141
|
+
|
|
142
|
+
# Page Settings
|
|
143
|
+
@autosave_interval = 60
|
|
144
|
+
@max_versions_per_page = 50
|
|
145
|
+
|
|
146
|
+
# Security
|
|
147
|
+
@sanitize_content = true
|
|
148
|
+
@allowed_html_tags = %w[
|
|
149
|
+
h1 h2 h3 h4 h5 h6 p div span a img ul ol li
|
|
150
|
+
table thead tbody tr th td
|
|
151
|
+
section article header footer nav main aside
|
|
152
|
+
figure figcaption blockquote pre code
|
|
153
|
+
strong em b i u s mark small sub sup
|
|
154
|
+
br hr
|
|
155
|
+
form input button label select option textarea
|
|
156
|
+
iframe video audio source
|
|
157
|
+
]
|
|
158
|
+
@allowed_html_attributes = %w[
|
|
159
|
+
class id style href src alt title target rel
|
|
160
|
+
width height loading name type value placeholder
|
|
161
|
+
disabled readonly checked selected multiple
|
|
162
|
+
action method enctype
|
|
163
|
+
controls autoplay loop muted poster
|
|
164
|
+
frameborder allowfullscreen allow
|
|
165
|
+
]
|
|
166
|
+
|
|
167
|
+
# AI Security
|
|
168
|
+
@ai_rate_limit_per_minute = 30
|
|
169
|
+
@ai_stream_timeout = 5.minutes
|
|
170
|
+
@ai_stream_idle_timeout = 30.seconds
|
|
171
|
+
@ai_max_response_size = 1.megabyte
|
|
172
|
+
@max_screenshot_size = 10.megabytes
|
|
173
|
+
@allowed_ai_image_hosts = %w[
|
|
174
|
+
oaidalleapiprodscus.blob.core.windows.net
|
|
175
|
+
dalleprodsec.blob.core.windows.net
|
|
176
|
+
]
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Get effective allowed content types (includes SVG if enabled, excludes dangerous types)
|
|
180
|
+
def effective_allowed_content_types
|
|
181
|
+
types = allowed_content_types.dup
|
|
182
|
+
types << "image/svg+xml" if allow_svg_uploads
|
|
183
|
+
types - DANGEROUS_CONTENT_TYPES
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Check if authentication is properly configured for production
|
|
187
|
+
def enforce_authentication!
|
|
188
|
+
return unless defined?(Rails) && Rails.env.production?
|
|
189
|
+
return if authenticate_admin.present?
|
|
190
|
+
return if admin_parent_controller != "ActionController::Base"
|
|
191
|
+
|
|
192
|
+
raise SecurityError, <<~MSG
|
|
193
|
+
[ActiveCanvas] Admin authentication is not configured!
|
|
194
|
+
|
|
195
|
+
Your admin interface is currently open to anyone. Configure authentication in your initializer:
|
|
196
|
+
|
|
197
|
+
ActiveCanvas.configure do |config|
|
|
198
|
+
# Option 1: Use your app's authentication method (recommended)
|
|
199
|
+
config.authenticate_admin = :authenticate_user!
|
|
200
|
+
|
|
201
|
+
# Option 2: Inherit from your admin base controller
|
|
202
|
+
config.admin_parent_controller = "Admin::ApplicationController"
|
|
203
|
+
|
|
204
|
+
# Option 3: Use HTTP Basic Auth
|
|
205
|
+
config.authenticate_admin = :http_basic_auth
|
|
206
|
+
config.http_basic_user = "admin"
|
|
207
|
+
config.http_basic_password = Rails.application.credentials.active_canvas_password
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
For development, you can use HTTP Basic Auth with default credentials,
|
|
211
|
+
but ALWAYS configure proper authentication for production.
|
|
212
|
+
MSG
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Check if HTTP Basic Auth is configured
|
|
216
|
+
def http_basic_auth_configured?
|
|
217
|
+
authenticate_admin == :http_basic_auth &&
|
|
218
|
+
http_basic_user.present? &&
|
|
219
|
+
http_basic_password.present?
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# Helper to check if AI features are enabled
|
|
223
|
+
def ai_available?
|
|
224
|
+
@enable_ai_features
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# Helper to check if Tailwind compilation is available
|
|
228
|
+
def tailwind_compilation_available?
|
|
229
|
+
@css_framework == :tailwind && defined?(Tailwindcss::Ruby)
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module ActiveCanvas
|
|
2
|
+
class Engine < ::Rails::Engine
|
|
3
|
+
isolate_namespace ActiveCanvas
|
|
4
|
+
|
|
5
|
+
# Prevent engine migrations from auto-running in the host app.
|
|
6
|
+
# Host apps copy migrations via: rails active_canvas:install:migrations
|
|
7
|
+
initializer "active_canvas.migrations", before: :append_migrations do
|
|
8
|
+
config.paths["db/migrate"] = []
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Ensure engine assets are precompiled
|
|
12
|
+
initializer "active_canvas.assets.precompile" do |app|
|
|
13
|
+
app.config.assets.precompile += %w[
|
|
14
|
+
active_canvas/editor.js
|
|
15
|
+
active_canvas/editor.css
|
|
16
|
+
]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Filter sensitive parameters from logs
|
|
20
|
+
initializer "active_canvas.filter_parameters" do |app|
|
|
21
|
+
app.config.filter_parameters += [
|
|
22
|
+
:ai_openai_api_key,
|
|
23
|
+
:ai_anthropic_api_key,
|
|
24
|
+
:ai_openrouter_api_key,
|
|
25
|
+
:http_basic_password,
|
|
26
|
+
/active_canvas.*api.*key/i,
|
|
27
|
+
/active_canvas.*password/i
|
|
28
|
+
]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Warn about authentication configuration
|
|
32
|
+
config.after_initialize do
|
|
33
|
+
if defined?(Rails::Server) && Rails.env.production?
|
|
34
|
+
unless ActiveCanvas.config.authenticate_admin.present?
|
|
35
|
+
Rails.logger.warn <<~MSG
|
|
36
|
+
[ActiveCanvas] WARNING: Admin authentication is not configured!
|
|
37
|
+
Your admin interface will be inaccessible until you configure authentication.
|
|
38
|
+
See the ActiveCanvas documentation for setup instructions.
|
|
39
|
+
MSG
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
require "active_canvas/version"
|
|
2
|
+
require "active_canvas/configuration"
|
|
3
|
+
require "active_canvas/engine"
|
|
4
|
+
|
|
5
|
+
# Load RubyLLM if available for AI features
|
|
6
|
+
begin
|
|
7
|
+
require "ruby_llm"
|
|
8
|
+
rescue LoadError
|
|
9
|
+
# RubyLLM is optional - AI features will be disabled if not available
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
module ActiveCanvas
|
|
13
|
+
class << self
|
|
14
|
+
def configuration
|
|
15
|
+
@configuration ||= Configuration.new
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def configure
|
|
19
|
+
yield(configuration)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def config
|
|
23
|
+
configuration
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|