Module: RubyCms
- Extended by:
- CommandsRegistry
- Defined in:
- lib/generators/ruby_cms/templates/lib/ruby_cms/dashboard_blocks.rb,
lib/ruby_cms.rb,
lib/ruby_cms/cli.rb,
lib/ruby_cms/syncer.rb,
lib/ruby_cms/updater.rb,
lib/ruby_cms/version.rb,
lib/ruby_cms/excludes.rb,
lib/ruby_cms/lockfile.rb,
lib/ruby_cms/manifest.rb,
lib/ruby_cms/path_map.rb,
lib/ruby_cms/gem_setup.rb,
lib/ruby_cms/installer.rb,
lib/ruby_cms/app_wiring.rb,
lib/ruby_cms/auth_wiring.rb,
lib/ruby_cms/helper_wiring.rb,
lib/ruby_cms/manifest_data.rb,
lib/ruby_cms/nav_assembler.rb,
lib/ruby_cms/file_installer.rb,
lib/ruby_cms/passkey_wiring.rb,
lib/ruby_cms/importmap_wiring.rb,
lib/ruby_cms/routes_assembler.rb,
lib/ruby_cms/migration_helpers.rb,
lib/ruby_cms/migration_installer.rb,
lib/ruby_cms/migration_reconciler.rb,
lib/generators/ruby_cms/admin_page_generator.rb,
lib/generators/ruby_cms/templates/lib/ruby_cms.rb,
lib/generators/ruby_cms/templates/lib/ruby_cms/cli.rb,
lib/generators/ruby_cms/templates/lib/ruby_cms/icons.rb,
lib/generators/ruby_cms/templates/lib/ruby_cms/settings.rb,
lib/generators/ruby_cms/templates/lib/ruby_cms/commands_registry.rb,
lib/generators/ruby_cms/templates/lib/ruby_cms/settings_registry.rb,
lib/generators/ruby_cms/templates/lib/ruby_cms/content_blocks_sync.rb,
lib/generators/ruby_cms/templates/lib/ruby_cms/content_blocks_grouping.rb,
sig/ruby_cms.rbs
Overview
Loaded from lib/ruby_cms.rb and from lib/ruby_cms/engine.rb so dashboard API exists even when
the host only requires "ruby_cms/engine" (without loading lib/ruby_cms.rb first).
Defined Under Namespace
Modules: CommandsRegistry, ContentBlocksGrouping, Excludes, Generators, Icons, MigrationHelpers, Nav, PathMap, Settings, SettingsRegistry
Classes: AppWiring, AuthWiring, CLI, ContentBlocksSync, Error, FileInstaller, GemSetup, HelperWiring, ImportmapWiring, Installer, Lockfile, Manifest, MigrationInstaller, MigrationReconciler, NavAssembler, PasskeyWiring, RoutesAssembler, RunSetupAdmin, Syncer, Updater
Constant Summary
collapse
- VERSION =
"1.1.1"
- DEFAULT_PERMISSION_KEYS =
%w[
manage_admin
manage_permissions
manage_content_blocks
manage_visitor_errors
manage_analytics
].freeze
- NAV_SECTION_MAIN =
"main"
- NAV_SECTION_BOTTOM =
"Settings"
- VALID_PAGE_SECTIONS =
%i[main settings].freeze
CommandsRegistry::CATEGORIES, CommandsRegistry::PARAM_TYPES
Class Method Summary
collapse
-
.configure {|config.ruby_cms| ... } ⇒ Object
-
.dashboard_register(key:, label:, section:, order:, partial: nil, render: nil, permission: nil, enabled: true, default_visible: true, span: :single, data: nil) ⇒ Object
Register a dashboard block (stats row or main row).
-
.manifest ⇒ Object
-
.nav_group(key:, label:, children:, path: nil, icon: nil, section: nil, order: nil, permission: nil, default_visible: true, default_open: true, **options) ⇒ Object
-
.nav_register(key:, label:, path:, icon: nil, section: nil, order: nil, permission: nil, default_visible: true, **options) ⇒ Object
-
.nav_request_cache_key(user) ⇒ Object
-
.register_page(key:, label:, path:, icon: nil, section: :main, order: nil, permission: nil, default_visible: true) ⇒ Object
-
.register_permission_keys(*keys) ⇒ Object
-
.register_permission_template(name, label:, keys:, description: nil) ⇒ Object
-
.reset_visible_nav_cache ⇒ Object
-
.ruby_cms_options?(config) ⇒ Boolean
True when config.ruby_cms is already an OrderedOptions.
-
.setting(key, default: nil) ⇒ Object
-
.templates_root ⇒ Object
-
.visible_dashboard_blocks(user: nil) ⇒ Object
-
.visible_nav_registry(view_context: nil, user: nil) ⇒ Object
-
.visible_nav_sidebar_rows(section:, view_context: nil, user: nil) ⇒ Object
find_command, register_command, registered_commands, registered_commands=
Class Method Details
43
44
45
46
47
|
# File 'lib/generators/ruby_cms/templates/lib/ruby_cms.rb', line 43
def self.configure
config = Rails.application.config
config.ruby_cms = ActiveSupport::OrderedOptions.new unless ruby_cms_options?(config)
yield(config.ruby_cms)
end
|
.dashboard_register(key:, label:, section:, order:, partial: nil, render: nil, permission: nil, enabled: true, default_visible: true, span: :single, data: nil) ⇒ Object
Register a dashboard block (stats row or main row). Host apps can add blocks or replace defaults by key.
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
# File 'lib/generators/ruby_cms/templates/lib/ruby_cms/dashboard_blocks.rb', line 10
def self.dashboard_register(
key:, label:, section:, order:, partial: nil, render: nil, permission: nil,
enabled: true, default_visible: true, span: :single, data: nil
)
normalized_key = key.to_sym
normalized_section = section.to_sym
raise ArgumentError, "section must be :stats or :main" unless %i[stats main].include?(normalized_section)
raise ArgumentError, "partial or render is required" if partial.blank? && !render.respond_to?(:call)
entry = {
key: normalized_key,
label: label.to_s,
section: normalized_section,
order: order.to_i,
partial: partial,
render: render,
permission: permission&.to_sym,
enabled: enabled ? true : false,
default_visible: default_visible ? true : false,
span: span.to_sym == :double ? :double : :single,
data: data
}
self.dashboard_registry = dashboard_registry.reject { |e| e[:key] == normalized_key }
self.dashboard_registry += [ entry ]
register_dashboard_setting!(entry)
entry
end
|
.manifest ⇒ Object
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
|
# File 'lib/ruby_cms/manifest_data.rb', line 6
def self.manifest
@manifest ||= Manifest.build do
module_def :core, base: true do
description "Admin shell: dashboard, layout, nav, settings"
files %w[
admin.html.erb
views/layouts/admin/**/*
views/shared/_maintenance_banner.html.erb
views/admin/shared/**/*
views/admin/dashboard/**/*
views/admin/settings/**/*
helpers/**/*
javascript/**/*
components/**/*
assets/**/*
config/locales/**/*
config/initializers/admin.rb
config/initializers/admin_dashboard.rb
config/initializers/pagy.rb
config/initializers/ruby_cms_core.rb
config/initializers/ruby_cms_custom_settings.rb
lib/**/*
models/concerns/**/*
models/permittable.rb
models/preference.rb
controllers/admin/application_controller.rb
controllers/admin/dashboard_controller.rb
controllers/admin/locale_controller.rb
controllers/admin/settings_controller.rb
controllers/admin/notifications_controller.rb
controllers/concerns/**/*
controllers/errors_controller.rb
services/admin/**/*
]
routes "routes/core.rb"
nav "nav/core.rb"
permissions %i[manage_admin]
migrations %w[
create_ruby_cms_preferences add_category_to_ruby_cms_preferences
add_discarded_at_to_cms_tables use_unprefixed_cms_tables
]
end
module_def :auth, base: true do
description "Sign-in wiring + user invitations"
files %w[
models/invitation.rb
controllers/admin/invitations_controller.rb
]
routes "routes/auth.rb"
permissions %i[manage_permissions]
migrations %w[create_invitations]
end
module_def :permissions, base: true do
description "Roles & permission management"
files %w[
models/permission.rb models/user_permission.rb
controllers/admin/permissions_controller.rb
controllers/admin/user_permissions_controller.rb
views/admin/permissions/**/* views/admin/user_permissions/**/*
]
routes "routes/permissions.rb"
nav "nav/permissions.rb"
permissions %i[manage_permissions]
migrations %w[
create_ruby_cms_permissions create_ruby_cms_user_permissions
add_indexes_to_ruby_cms_tables
]
end
module_def :users, base: true do
description "User management"
files %w[controllers/admin/users_controller.rb views/admin/users/**/*]
routes "routes/users.rb"
nav "nav/users.rb"
permissions %i[manage_permissions]
end
module_def :content_blocks, base: true do
description "Editable content blocks with versions"
files %w[
models/content_block.rb models/content_block_version.rb
controllers/admin/content_blocks_controller.rb
controllers/admin/content_block_versions_controller.rb
views/admin/content_blocks/**/* views/admin/content_block_versions/**/*
]
routes "routes/content_blocks.rb"
nav "nav/content_blocks.rb"
permissions %i[manage_content_blocks]
migrations %w[
create_ruby_cms_content_blocks add_locale_to_ruby_cms_content_blocks
create_content_block_versions
]
end
module_def :visual_editor, base: true do
description "In-page visual content editor"
files %w[controllers/admin/visual_editor_controller.rb views/admin/visual_editor/**/*]
routes "routes/visual_editor.rb"
nav "nav/visual_editor.rb"
permissions %i[manage_content_blocks]
end
module_def :analytics, base: false do
description "Visitor & page analytics (Ahoy)"
files %w[
services/analytics/**/* services/analytics_service.rb
controllers/admin/analytics_controller.rb views/admin/analytics/**/*
]
routes "routes/analytics.rb"
nav "nav/analytics.rb"
permissions %i[manage_analytics]
migrations %w[
add_analytics_indices add_ruby_cms_analytics_fields_to_ahoy_events
add_analytics_performance_indexes backfill_security_fields_in_ahoy_events
]
gems "ahoy_matey" => "~> 5.2"
end
module_def :media, base: false do
description "Media library & uploads"
files %w[
models/media_asset.rb controllers/admin/media_assets_controller.rb
views/admin/media_assets/**/*
javascript/controllers/admin/media_selection_controller.js
javascript/controllers/admin/media_uploader_controller.js
]
routes "routes/media.rb"
nav "nav/media.rb"
permissions %i[manage_media]
migrations %w[create_media_assets]
end
module_def :redirects, base: false do
description "URL redirect management"
files %w[models/redirect.rb controllers/admin/redirects_controller.rb views/admin/redirects/**/*]
routes "routes/redirects.rb"
nav "nav/redirects.rb"
permissions %i[manage_redirects]
migrations %w[create_redirects]
gems "csv" => nil end
module_def :audit_log, base: false do
description "Audit trail of admin actions"
files %w[
models/audit_log_entry.rb controllers/admin/audit_log_entries_controller.rb
views/admin/audit_log_entries/**/* services/audit_log.rb
]
routes "routes/audit_log.rb"
nav "nav/audit_log.rb"
permissions %i[manage_audit_log]
migrations %w[create_audit_log_entries]
end
module_def :trash, base: false do
description "Soft-delete recycle bin"
files %w[controllers/admin/trash_controller.rb views/admin/trash/**/*]
routes "routes/trash.rb"
nav "nav/trash.rb"
permissions %i[manage_admin]
end
module_def :security, base: false, depends_on: %i[analytics] do
description "Visitor errors, IP/email blocklist, security dashboard"
files %w[
models/ip_blocklist.rb models/email_blocklist.rb models/visitor_error.rb
controllers/admin/security_controller.rb controllers/admin/visitor_errors_controller.rb
views/admin/security/**/* views/admin/visitor_errors/**/*
services/security_service.rb services/security_tracker.rb
]
routes "routes/security.rb"
nav "nav/security.rb"
permissions %i[manage_security manage_visitor_errors]
migrations %w[
create_email_blocklists create_ip_blocklists create_ruby_cms_visitor_errors
add_referer_and_query_to_ruby_cms_visitor_errors
add_state_to_visitor_errors add_discarded_at_to_visitor_errors
]
end
module_def :system_health, base: false do
description "System health checks & status"
files %w[controllers/admin/system_health_controller.rb views/admin/system_health/**/*]
routes "routes/system_health.rb"
nav "nav/system_health.rb"
permissions %i[manage_system_health]
end
module_def :passkeys, base: false, depends_on: %i[auth] do
description "Passkey / WebAuthn passwordless login"
files %w[
models/passkey_credential.rb
controllers/admin/passkey_registrations_controller.rb
controllers/admin/passkey_credentials_controller.rb
controllers/passkey_sessions_controller.rb
controllers/concerns/sudo_mode.rb
views/admin/passkey_registrations/**/*
views/admin/passkey_credentials/**/*
javascript/controllers/webauthn_controller.js
config/initializers/webauthn.rb
]
routes "routes/passkeys.rb"
permissions %i[manage_admin]
migrations %w[
create_passkey_credentials add_webauthn_id_to_users
add_authenticated_at_to_sessions
]
gems "webauthn" => "~> 3.1"
end
module_def :commands, base: false do
description "Run whitelisted maintenance commands"
files %w[
models/command_run.rb controllers/admin/commands_controller.rb
views/admin/commands/**/* services/command_runner.rb
javascript/controllers/admin/commands_controller.js
javascript/controllers/admin/admin_commands_controller.js
]
routes "routes/commands.rb"
nav "nav/commands.rb"
permissions %i[manage_admin]
migrations %w[create_command_runs]
end
end
end
|
.nav_group(key:, label:, children:, path: nil, icon: nil, section: nil, order: nil, permission: nil, default_visible: true, default_open: true, **options) ⇒ Object
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
|
# File 'lib/generators/ruby_cms/templates/lib/ruby_cms.rb', line 85
def self.nav_group(
key:, label:, children:,
path: nil, icon: nil, section: nil, order: nil,
permission: nil, default_visible: true, default_open: true, **options
)
normalized_key = key.to_sym
normalized_section = section.to_s == "settings" ? NAV_SECTION_BOTTOM : NAV_SECTION_MAIN
resolved_path = if path.nil?
nil
else
(path.kind_of?(Symbol) ? ->(v) { v.main_app.send(path) } : path)
end
resolved_icon = icon.nil? ? nil : RubyCms::Icons.resolve(icon)
normalized_children = Array(children).map(&:to_s).map(&:strip).reject(&:blank?).map(&:to_sym)
entry = {
type: :group,
key: normalized_key,
label: label.to_s,
path: resolved_path,
icon: resolved_icon,
section: normalized_section,
order: order,
permission: permission&.to_s,
default_visible: default_visible ? true : false,
default_open: default_open ? true : false,
children: normalized_children,
if: options[:if]
}
self.nav_registry = nav_registry.reject { |e| e[:key] == normalized_key }
self.nav_registry += [ entry ]
register_navigation_setting!(entry)
entry
end
|
.nav_register(key:, label:, path:, icon: nil, section: nil, order: nil, permission: nil, default_visible: true, **options) ⇒ Object
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
|
# File 'lib/generators/ruby_cms/templates/lib/ruby_cms.rb', line 61
def self.nav_register(key:, label:, path:, icon: nil, section: nil, order: nil, permission: nil, default_visible: true, **options)
normalized_key = key.to_sym
normalized_section = section.to_s == "settings" ? NAV_SECTION_BOTTOM : NAV_SECTION_MAIN
resolved_path = path.kind_of?(Symbol) ? ->(v) { v.main_app.send(path) } : path
resolved_icon = icon.nil? ? nil : RubyCms::Icons.resolve(icon)
entry = {
type: :link,
key: normalized_key,
label: label.to_s,
path: resolved_path,
icon: resolved_icon,
section: normalized_section,
order: order,
permission: permission&.to_s,
default_visible: default_visible ? true : false,
if: options[:if]
}
self.nav_registry = nav_registry.reject { |e| e[:key] == normalized_key }
self.nav_registry += [ entry ]
register_navigation_setting!(entry)
entry
end
|
.nav_request_cache_key(user) ⇒ Object
158
159
160
161
162
163
164
165
|
# File 'lib/generators/ruby_cms/templates/lib/ruby_cms.rb', line 158
def self.nav_request_cache_key(user)
locale = I18n.locale.to_s
if user.respond_to?(:id) && user.id
"u:#{user.id}:#{locale}"
elsif user
"obj:#{user.object_id}:#{locale}"
end
end
|
.register_page(key:, label:, path:, icon: nil, section: :main, order: nil, permission: nil, default_visible: true) ⇒ Object
123
124
125
126
127
128
129
130
131
|
# File 'lib/generators/ruby_cms/templates/lib/ruby_cms.rb', line 123
def self.register_page(
key:, label:, path:, icon: nil, section: :main, order: nil,
permission: nil, default_visible: true, **
)
raise ArgumentError, "register_page section must be :main or :settings, got #{section.inspect}" unless VALID_PAGE_SECTIONS.include?(section.to_s.to_sym)
register_permission_keys(permission) if permission.present?
nav_register(key:, label:, path:, icon:, section:, order:, permission:, default_visible:, **)
end
|
.register_permission_keys(*keys) ⇒ Object
28
29
30
|
# File 'lib/generators/ruby_cms/templates/lib/ruby_cms.rb', line 28
def self.register_permission_keys(*keys)
self. = ( + keys.flatten.map(&:to_s)).uniq
end
|
.register_permission_template(name, label:, keys:, description: nil) ⇒ Object
32
33
34
35
36
37
38
|
# File 'lib/generators/ruby_cms/templates/lib/ruby_cms.rb', line 32
def self.register_permission_template(name, label:, keys:, description: nil)
permission_templates[name.to_sym] = {
label: label,
keys: keys.map(&:to_s),
description: description
}
end
|
.reset_visible_nav_cache ⇒ Object
154
155
156
|
# File 'lib/generators/ruby_cms/templates/lib/ruby_cms.rb', line 154
def self.reset_visible_nav_cache
Thread.current[:ruby_cms_visible_nav] = nil
end
|
.ruby_cms_options?(config) ⇒ Boolean
True when config.ruby_cms is already an OrderedOptions. Reading an unset custom
config key raises NoMethodError on Rails::Application::Configuration, so guard it.
51
52
53
54
55
|
# File 'lib/generators/ruby_cms/templates/lib/ruby_cms.rb', line 51
def self.ruby_cms_options?(config)
config.ruby_cms.is_a?(ActiveSupport::OrderedOptions)
rescue NoMethodError
false
end
|
.setting(key, default: nil) ⇒ Object
57
58
59
|
# File 'lib/generators/ruby_cms/templates/lib/ruby_cms.rb', line 57
def self.setting(key, default: nil)
RubyCms::Settings.get(key, default:)
end
|
.templates_root ⇒ Object
6
7
8
|
# File 'lib/ruby_cms.rb', line 6
def self.templates_root
File.expand_path("generators/ruby_cms/templates", __dir__)
end
|
.visible_dashboard_blocks(user: nil) ⇒ Object
41
42
43
44
45
46
47
48
49
|
# File 'lib/generators/ruby_cms/templates/lib/ruby_cms/dashboard_blocks.rb', line 41
def self.visible_dashboard_blocks(user: nil)
dashboard_registry
.select { |e| e[:enabled] }
.select { |e| dashboard_block_visible?(e, user:) }
.sort_by { |e| [ e[:section] == :stats ? 0 : 1, e[:order], e[:label] ] }
rescue StandardError => e
Rails.logger.error("[RubyCMS] Error filtering dashboard blocks: #{e.message}") if defined?(Rails.logger)
[]
end
|
.visible_nav_registry(view_context: nil, user: nil) ⇒ Object
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
|
# File 'lib/generators/ruby_cms/templates/lib/ruby_cms.rb', line 133
def self.visible_nav_registry(view_context: nil, user: nil)
cache_key = nav_request_cache_key(user)
if cache_key && (cached = Thread.current[:ruby_cms_visible_nav]&.dig(cache_key))
return cached
end
list = nav_registry
.select { |item| nav_entry_visible?(item, view_context:, user:) }
.sort_by { |item| nav_sort_tuple(item) }
result = localize_nav_labels(apply_nav_order(list))
if cache_key
Thread.current[:ruby_cms_visible_nav] ||= {}
Thread.current[:ruby_cms_visible_nav][cache_key] = result
end
result
rescue StandardError => e
Rails.logger.error("[RubyCMS] Error filtering navigation: #{e.message}") if defined?(Rails.logger)
[]
end
|
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
|
# File 'lib/generators/ruby_cms/templates/lib/ruby_cms.rb', line 167
def self.(section:, view_context: nil, user: nil)
visible = visible_nav_registry(view_context:, user:)
links = visible.select { |e| e[:type].to_s == "link" }
groups = visible.select { |e| e[:type].to_s == "group" }
links_by_key = links.index_by { |e| e[:key].to_sym }
nested_keys = groups.flat_map { |g| Array(g[:children]) }.map(&:to_sym).to_set
position = visible.each_with_index.to_h { |e, i| [ e[:key], i ] }
section_name = section.to_s == "settings" ? NAV_SECTION_BOTTOM : NAV_SECTION_MAIN
group_rows = groups
.select { |g| g[:section].to_s == section_name }
.filter_map do |g|
child_entries = Array(g[:children]).map { |k| links_by_key[k.to_sym] }.compact
next if child_entries.empty? && g[:path].blank?
[ { type: :group, group: g, children: child_entries }, position.fetch(g[:key], 9999) ]
end
link_rows = links
.select { |e| e[:section].to_s == section_name }
.reject { |e| nested_keys.include?(e[:key].to_sym) }
.map { |e| [ { type: :link, entry: e }, position.fetch(e[:key], 9999) ] }
(group_rows + link_rows).sort_by { |(_, pos)| pos }.map(&:first)
end
|