super_admin 0.2.0
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 +216 -0
- data/Rakefile +30 -0
- data/app/assets/stylesheets/super_admin/application.css +15 -0
- data/app/assets/stylesheets/super_admin/tailwind.css +1 -0
- data/app/assets/stylesheets/super_admin/tailwind.source.css +25 -0
- data/app/controllers/super_admin/application_controller.rb +89 -0
- data/app/controllers/super_admin/associations_controller.rb +136 -0
- data/app/controllers/super_admin/audit_logs_controller.rb +39 -0
- data/app/controllers/super_admin/base_controller.rb +133 -0
- data/app/controllers/super_admin/dashboard_controller.rb +29 -0
- data/app/controllers/super_admin/exports_controller.rb +109 -0
- data/app/controllers/super_admin/resources_controller.rb +201 -0
- data/app/dashboards/super_admin/base_dashboard.rb +200 -0
- data/app/errors/super_admin/configuration_error.rb +6 -0
- data/app/helpers/super_admin/application_helper.rb +84 -0
- data/app/helpers/super_admin/exports_helper.rb +16 -0
- data/app/helpers/super_admin/resources_helper.rb +204 -0
- data/app/helpers/super_admin/route_helper.rb +7 -0
- data/app/javascript/super_admin/application.js +263 -0
- data/app/jobs/super_admin/application_job.rb +4 -0
- data/app/jobs/super_admin/generate_super_admin_csv_export_job.rb +100 -0
- data/app/mailers/super_admin/application_mailer.rb +6 -0
- data/app/models/super_admin/application_record.rb +5 -0
- data/app/models/super_admin/audit_log.rb +35 -0
- data/app/models/super_admin/csv_export.rb +67 -0
- data/app/services/super_admin/auditing.rb +74 -0
- data/app/services/super_admin/authorization.rb +113 -0
- data/app/services/super_admin/authorization_adapters/base_adapter.rb +100 -0
- data/app/services/super_admin/authorization_adapters/default_adapter.rb +77 -0
- data/app/services/super_admin/authorization_adapters/proc_adapter.rb +65 -0
- data/app/services/super_admin/authorization_adapters/pundit_adapter.rb +81 -0
- data/app/services/super_admin/csv_export_creator.rb +45 -0
- data/app/services/super_admin/dashboard_registry.rb +90 -0
- data/app/services/super_admin/dashboard_resolver.rb +100 -0
- data/app/services/super_admin/filter_builder.rb +185 -0
- data/app/services/super_admin/form_builder.rb +59 -0
- data/app/services/super_admin/form_fields/array_field.rb +35 -0
- data/app/services/super_admin/form_fields/association_field.rb +146 -0
- data/app/services/super_admin/form_fields/base_field.rb +53 -0
- data/app/services/super_admin/form_fields/boolean_field.rb +29 -0
- data/app/services/super_admin/form_fields/date_field.rb +15 -0
- data/app/services/super_admin/form_fields/date_time_field.rb +15 -0
- data/app/services/super_admin/form_fields/enum_field.rb +27 -0
- data/app/services/super_admin/form_fields/factory.rb +102 -0
- data/app/services/super_admin/form_fields/nested_field.rb +120 -0
- data/app/services/super_admin/form_fields/number_field.rb +29 -0
- data/app/services/super_admin/form_fields/text_area_field.rb +19 -0
- data/app/services/super_admin/model_inspector.rb +182 -0
- data/app/services/super_admin/queries/base_query.rb +45 -0
- data/app/services/super_admin/queries/filter_query.rb +188 -0
- data/app/services/super_admin/queries/resource_scope_query.rb +74 -0
- data/app/services/super_admin/queries/search_query.rb +146 -0
- data/app/services/super_admin/queries/sort_query.rb +41 -0
- data/app/services/super_admin/resource_configuration.rb +63 -0
- data/app/services/super_admin/resource_exporter.rb +78 -0
- data/app/services/super_admin/resource_query.rb +40 -0
- data/app/services/super_admin/resources/association_inspector.rb +112 -0
- data/app/services/super_admin/resources/collection_presenter.rb +63 -0
- data/app/services/super_admin/resources/context.rb +63 -0
- data/app/services/super_admin/resources/filter_params.rb +29 -0
- data/app/services/super_admin/resources/permitted_attributes.rb +104 -0
- data/app/services/super_admin/resources/value_normalizer.rb +121 -0
- data/app/services/super_admin/sensitive_attributes.rb +166 -0
- data/app/views/layouts/super_admin.html.erb +74 -0
- data/app/views/super_admin/audit_logs/index.html.erb +143 -0
- data/app/views/super_admin/dashboard/index.html.erb +79 -0
- data/app/views/super_admin/exports/index.html.erb +84 -0
- data/app/views/super_admin/exports/show.html.erb +57 -0
- data/app/views/super_admin/resources/_form.html.erb +42 -0
- data/app/views/super_admin/resources/destroy.turbo_stream.erb +17 -0
- data/app/views/super_admin/resources/edit.html.erb +37 -0
- data/app/views/super_admin/resources/index.html.erb +189 -0
- data/app/views/super_admin/resources/new.html.erb +31 -0
- data/app/views/super_admin/resources/show.html.erb +106 -0
- data/app/views/super_admin/shared/_breadcrumbs.html.erb +12 -0
- data/app/views/super_admin/shared/_custom_styles.html.erb +132 -0
- data/app/views/super_admin/shared/_flash.html.erb +55 -0
- data/app/views/super_admin/shared/_form_field.html.erb +35 -0
- data/app/views/super_admin/shared/_navigation.html.erb +92 -0
- data/app/views/super_admin/shared/_nested_fields.html.erb +59 -0
- data/app/views/super_admin/shared/_nested_record_fields.html.erb +45 -0
- data/config/importmap.rb +4 -0
- data/config/initializers/rack_attack.rb +134 -0
- data/config/initializers/super_admin.rb +117 -0
- data/config/locales/super_admin.en.yml +197 -0
- data/config/locales/super_admin.fr.yml +197 -0
- data/config/routes.rb +22 -0
- data/lib/generators/super_admin/dashboard_generator.rb +50 -0
- data/lib/generators/super_admin/install_generator.rb +58 -0
- data/lib/generators/super_admin/templates/20240101000001_create_super_admin_audit_logs.rb +24 -0
- data/lib/generators/super_admin/templates/20240101000002_create_super_admin_csv_exports.rb +33 -0
- data/lib/generators/super_admin/templates/super_admin.rb +58 -0
- data/lib/super_admin/dashboard_creator.rb +256 -0
- data/lib/super_admin/engine.rb +53 -0
- data/lib/super_admin/install_task.rb +96 -0
- data/lib/super_admin/version.rb +3 -0
- data/lib/super_admin.rb +7 -0
- data/lib/tasks/super_admin_tasks.rake +38 -0
- metadata +239 -0
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "pathname"
|
|
5
|
+
require "active_support/inflector"
|
|
6
|
+
|
|
7
|
+
module SuperAdmin
|
|
8
|
+
# Utility object responsible for creating dashboard skeleton files.
|
|
9
|
+
class DashboardCreator
|
|
10
|
+
def self.call(model_name: nil, stdout: $stdout)
|
|
11
|
+
new(model_name:, stdout:).call
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def initialize(model_name:, stdout:)
|
|
15
|
+
@model_name = model_name
|
|
16
|
+
@stdout = stdout
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def call
|
|
20
|
+
models = determine_models
|
|
21
|
+
|
|
22
|
+
if models.empty?
|
|
23
|
+
stdout.puts "No models found to generate dashboards for."
|
|
24
|
+
return { generated: [], skipped: [] }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
generated = []
|
|
28
|
+
skipped = []
|
|
29
|
+
|
|
30
|
+
models.each do |model|
|
|
31
|
+
unless constantizable_model?(model)
|
|
32
|
+
stdout.puts "Skipping #{model.inspect}: model is not constantizable."
|
|
33
|
+
next
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
result = generate_dashboard(model)
|
|
37
|
+
next unless result
|
|
38
|
+
|
|
39
|
+
case result[:status]
|
|
40
|
+
when :generated
|
|
41
|
+
generated << result[:path]
|
|
42
|
+
when :skipped
|
|
43
|
+
skipped << result[:path]
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
print_summary(generated, skipped)
|
|
48
|
+
|
|
49
|
+
{ generated:, skipped: }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
attr_reader :model_name, :stdout
|
|
55
|
+
|
|
56
|
+
def determine_models
|
|
57
|
+
models = if model_name && !model_name.to_s.strip.empty?
|
|
58
|
+
[ resolve_model(model_name) ]
|
|
59
|
+
else
|
|
60
|
+
SuperAdmin::ModelInspector.discoverable_models
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
Array(models).compact.each_with_object([]) do |model, list|
|
|
64
|
+
if engine_owned_model?(model)
|
|
65
|
+
stdout.puts "Skipping #{model.name}: managed internally by SuperAdmin."
|
|
66
|
+
next
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
list << model
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def constantizable_model?(model)
|
|
74
|
+
return false unless model.respond_to?(:name)
|
|
75
|
+
name = model.name
|
|
76
|
+
return false if name.nil? || name.empty?
|
|
77
|
+
|
|
78
|
+
constant = name.safe_constantize
|
|
79
|
+
constant == model
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def resolve_model(name)
|
|
83
|
+
return name if name.is_a?(Class) && name < ActiveRecord::Base
|
|
84
|
+
|
|
85
|
+
constant_name = normalize_model_name(name)
|
|
86
|
+
klass = constant_name.constantize
|
|
87
|
+
if engine_owned_model?(klass)
|
|
88
|
+
raise ArgumentError, "#{constant_name} is managed internally by SuperAdmin and should not receive a dashboard."
|
|
89
|
+
end
|
|
90
|
+
return klass if klass < ActiveRecord::Base
|
|
91
|
+
|
|
92
|
+
raise ArgumentError, "#{constant_name} is not an ActiveRecord model"
|
|
93
|
+
rescue NameError
|
|
94
|
+
raise ArgumentError, "Unable to find model for '#{name}'"
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def normalize_model_name(name)
|
|
98
|
+
raw = name.to_s.tr("/", "::").split("::").reject(&:empty?)
|
|
99
|
+
segments = raw.map { |segment| segment.underscore.singularize.camelize }
|
|
100
|
+
segments.join("::")
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def generate_dashboard(model_class)
|
|
104
|
+
if dashboard_exists?(model_class)
|
|
105
|
+
stdout.puts "Skipping #{model_class.name}: dashboard already exists."
|
|
106
|
+
return { status: :skipped, path: relative_dashboard_path(model_class).to_s }
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
path = absolute_dashboard_path(model_class)
|
|
110
|
+
FileUtils.mkdir_p(path.dirname)
|
|
111
|
+
File.write(path, dashboard_template(model_class))
|
|
112
|
+
stdout.puts "Created #{relative_dashboard_path(model_class)}"
|
|
113
|
+
|
|
114
|
+
{ status: :generated, path: relative_dashboard_path(model_class).to_s }
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def dashboard_exists?(model_class)
|
|
118
|
+
registry = SuperAdmin::DashboardRegistry.instance
|
|
119
|
+
return true if registry.dashboard_for(model_class)
|
|
120
|
+
|
|
121
|
+
absolute_dashboard_path(model_class).exist?
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def absolute_dashboard_path(model_class)
|
|
125
|
+
Rails.root.join(relative_dashboard_path(model_class))
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def relative_dashboard_path(model_class)
|
|
129
|
+
Pathname.new(File.join("app", "dashboards", "super_admin", *namespace_segments(model_class), "#{model_basename(model_class)}_dashboard.rb"))
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def namespace_segments(model_class)
|
|
133
|
+
relative_namespace_constants(model_class).map { |segment| segment.underscore }
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def namespace_constants(model_class)
|
|
137
|
+
namespace = model_class.name.deconstantize
|
|
138
|
+
return [] if namespace.empty?
|
|
139
|
+
|
|
140
|
+
namespace.split("::")
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def relative_namespace_constants(model_class)
|
|
144
|
+
segments = namespace_constants(model_class)
|
|
145
|
+
segments.shift if segments.first == root_namespace
|
|
146
|
+
segments
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def model_basename(model_class)
|
|
150
|
+
model_class.name.demodulize.underscore
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def dashboard_template(model_class)
|
|
154
|
+
collection_attrs = default_collection_attributes(model_class)
|
|
155
|
+
show_attrs = default_show_attributes(model_class)
|
|
156
|
+
form_attrs = default_form_attributes(model_class)
|
|
157
|
+
|
|
158
|
+
lines = []
|
|
159
|
+
lines << "# frozen_string_literal: true"
|
|
160
|
+
lines << ""
|
|
161
|
+
|
|
162
|
+
indent = 0
|
|
163
|
+
append(lines, indent, "module SuperAdmin")
|
|
164
|
+
indent += 1
|
|
165
|
+
|
|
166
|
+
relative_namespace_constants(model_class).each do |namespace|
|
|
167
|
+
append(lines, indent, "module #{namespace}")
|
|
168
|
+
indent += 1
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
append(lines, indent, "class #{model_class.name.demodulize}Dashboard < SuperAdmin::BaseDashboard")
|
|
172
|
+
indent += 1
|
|
173
|
+
append(lines, indent, "resource #{model_class.name}")
|
|
174
|
+
append_attribute_declaration(lines, indent, "collection_attributes", collection_attrs)
|
|
175
|
+
append_attribute_declaration(lines, indent, "show_attributes", show_attrs)
|
|
176
|
+
append_attribute_declaration(lines, indent, "form_attributes", form_attrs)
|
|
177
|
+
indent -= 1
|
|
178
|
+
append(lines, indent, "end")
|
|
179
|
+
|
|
180
|
+
relative_namespace_constants(model_class).reverse_each do |_namespace|
|
|
181
|
+
indent -= 1
|
|
182
|
+
append(lines, indent, "end")
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
indent -= 1
|
|
186
|
+
append(lines, indent, "end")
|
|
187
|
+
|
|
188
|
+
lines.join("\n") + "\n"
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def append(lines, indent, text)
|
|
192
|
+
lines << (" " * indent + text)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def append_attribute_declaration(lines, indent, macro, attributes)
|
|
196
|
+
return if attributes.empty?
|
|
197
|
+
|
|
198
|
+
append(lines, indent, "#{macro} #{format_attribute_list(attributes, indent)}")
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def default_collection_attributes(model_class)
|
|
202
|
+
SuperAdmin::ResourceConfiguration
|
|
203
|
+
.displayable_attributes(model_class)
|
|
204
|
+
.map { |attr| normalize_attribute_name(attr) }
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def default_show_attributes(model_class)
|
|
208
|
+
SuperAdmin::ResourceConfiguration
|
|
209
|
+
.displayable_attributes(model_class)
|
|
210
|
+
.map { |attr| normalize_attribute_name(attr) }
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def default_form_attributes(model_class)
|
|
214
|
+
SuperAdmin::ResourceConfiguration
|
|
215
|
+
.editable_attributes(model_class)
|
|
216
|
+
.map { |attr| normalize_attribute_name(attr) }
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def normalize_attribute_name(attribute)
|
|
220
|
+
attribute.is_a?(Symbol) ? attribute : attribute.to_s.to_sym
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def format_attribute_list(attributes, indent)
|
|
224
|
+
tokens = attributes.map { |attr| ":#{attr}" }
|
|
225
|
+
single_line = tokens.join(", ")
|
|
226
|
+
return single_line if single_line.length <= 80
|
|
227
|
+
|
|
228
|
+
continuation_indent = "\n" + (" " * indent) + " "
|
|
229
|
+
tokens.join(",#{continuation_indent}")
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def root_namespace
|
|
233
|
+
"SuperAdmin"
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def engine_owned_model?(model)
|
|
237
|
+
model.name.start_with?("#{root_namespace}::")
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def print_summary(generated, skipped)
|
|
241
|
+
stdout.puts
|
|
242
|
+
stdout.puts "Dashboards generated: #{generated.count}"
|
|
243
|
+
stdout.puts " - #{generated.join("\n - ")}" if generated.any?
|
|
244
|
+
stdout.puts "Dashboards skipped: #{skipped.count}"
|
|
245
|
+
stdout.puts " - #{skipped.join("\n - ")}" if skipped.any?
|
|
246
|
+
|
|
247
|
+
if generated.empty?
|
|
248
|
+
if skipped.any?
|
|
249
|
+
stdout.puts "No dashboards created; all models already have dashboards."
|
|
250
|
+
else
|
|
251
|
+
stdout.puts "No dashboards created."
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
module SuperAdmin
|
|
2
|
+
# :nocov:
|
|
3
|
+
class Engine < ::Rails::Engine
|
|
4
|
+
isolate_namespace SuperAdmin
|
|
5
|
+
|
|
6
|
+
initializer "super_admin.assets" do |app|
|
|
7
|
+
# Add engine's JavaScript to asset paths so they can be served
|
|
8
|
+
app.config.assets.paths << root.join("app/javascript") if app.config.respond_to?(:assets)
|
|
9
|
+
# Add engine's stylesheets to asset paths for propshaft/sprockets
|
|
10
|
+
app.config.assets.paths << root.join("app/assets/stylesheets") if app.config.respond_to?(:assets)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
initializer "super_admin.importmap", before: "importmap" do |app|
|
|
14
|
+
# Register SuperAdmin importmap if importmap-rails is available
|
|
15
|
+
if app.config.respond_to?(:importmap)
|
|
16
|
+
app.config.importmap.paths << root.join("config/importmap.rb")
|
|
17
|
+
app.config.importmap.cache_sweepers << root.join("app/javascript")
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
initializer "super_admin.route_helpers" do
|
|
22
|
+
require_relative "../../app/helpers/super_admin/route_helper"
|
|
23
|
+
|
|
24
|
+
ActiveSupport.on_load(:action_view) do
|
|
25
|
+
include ::SuperAdmin::RouteHelper
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
ActiveSupport.on_load(:action_controller) do
|
|
29
|
+
helper ::SuperAdmin::RouteHelper
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
config.to_prepare do
|
|
33
|
+
if defined?(ViewComponent::Base) && !ViewComponent::Base.included_modules.include?(::SuperAdmin::RouteHelper)
|
|
34
|
+
ViewComponent::Base.include(::SuperAdmin::RouteHelper)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
if defined?(ActionView::Component::Base) && !ActionView::Component::Base.included_modules.include?(::SuperAdmin::RouteHelper)
|
|
38
|
+
ActionView::Component::Base.include(::SuperAdmin::RouteHelper)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
initializer "super_admin.rack_attack", after: :load_config_initializers do
|
|
44
|
+
# Load Rack::Attack configuration for SuperAdmin endpoints
|
|
45
|
+
if defined?(Rack::Attack)
|
|
46
|
+
require "rack-attack"
|
|
47
|
+
config_file = root.join("config/initializers/rack_attack.rb")
|
|
48
|
+
load(config_file) if config_file.exist?
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
# :nocov:
|
|
53
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "pathname"
|
|
5
|
+
|
|
6
|
+
module SuperAdmin
|
|
7
|
+
# Handles bootstrapping host applications with required SuperAdmin assets.
|
|
8
|
+
class InstallTask
|
|
9
|
+
INITIALIZER_PATH = "config/initializers/super_admin.rb"
|
|
10
|
+
|
|
11
|
+
def self.call(stdout: $stdout)
|
|
12
|
+
new(stdout:).call
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def initialize(stdout: $stdout)
|
|
16
|
+
@stdout = stdout
|
|
17
|
+
@engine_root = Pathname.new(SuperAdmin::Engine.root)
|
|
18
|
+
@app_root = Pathname.new(Rails.root)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def call
|
|
22
|
+
copy_initializer
|
|
23
|
+
copy_migrations
|
|
24
|
+
stdout.puts "SuperAdmin installation complete."
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Public method for copying migrations (used by rake task)
|
|
28
|
+
def copy_migrations
|
|
29
|
+
migration_files.each do |source_path|
|
|
30
|
+
copy_migration(source_path)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
attr_reader :stdout, :engine_root, :app_root
|
|
37
|
+
|
|
38
|
+
def copy_initializer
|
|
39
|
+
target = app_root.join(INITIALIZER_PATH)
|
|
40
|
+
if target.exist?
|
|
41
|
+
stdout.puts "Initializer already exists at #{relative_to_app(target)} (skipping)."
|
|
42
|
+
return
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
source = engine_root.join(INITIALIZER_PATH)
|
|
46
|
+
FileUtils.mkdir_p(target.dirname)
|
|
47
|
+
FileUtils.cp(source, target)
|
|
48
|
+
stdout.puts "Initializer copied to #{relative_to_app(target)}."
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def migration_files
|
|
52
|
+
# Dir[] does not guarantee order, so we must sort to preserve migration sequence.
|
|
53
|
+
# We filter to only include files starting with a timestamp (e.g., 20240101000001_)
|
|
54
|
+
# to avoid copying non-migration files like super_admin.rb
|
|
55
|
+
Dir[engine_root.join("lib", "generators", "super_admin", "templates", "*.rb")]
|
|
56
|
+
.select { |f| File.basename(f) =~ /^\d+_/ }
|
|
57
|
+
.sort
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def copy_migration(source_path)
|
|
61
|
+
basename = File.basename(source_path).sub(/^\d+_/, "")
|
|
62
|
+
|
|
63
|
+
if migration_exists?(basename)
|
|
64
|
+
stdout.puts "Migration #{basename} already present (skipping)."
|
|
65
|
+
return
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
timestamp = next_migration_timestamp
|
|
69
|
+
target = app_root.join("db", "migrate", "#{timestamp}_#{basename}")
|
|
70
|
+
FileUtils.mkdir_p(target.dirname)
|
|
71
|
+
FileUtils.cp(source_path, target)
|
|
72
|
+
stdout.puts "Migration created at #{relative_to_app(target)}."
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def migration_exists?(basename)
|
|
76
|
+
Dir[app_root.join("db", "migrate", "*_#{basename}").to_s].any?
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def next_migration_timestamp
|
|
80
|
+
now = Time.now.utc.strftime("%Y%m%d%H%M%S").to_i
|
|
81
|
+
@migration_counter ||= begin
|
|
82
|
+
existing = Dir[app_root.join("db", "migrate", "*.rb").to_s]
|
|
83
|
+
existing.map { |path| File.basename(path).split("_", 2).first.to_i }.max || 0
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
@migration_counter = [ @migration_counter + 1, now ].max
|
|
87
|
+
format("%014d", @migration_counter)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def relative_to_app(path)
|
|
91
|
+
path.relative_path_from(app_root)
|
|
92
|
+
rescue ArgumentError
|
|
93
|
+
path
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
data/lib/super_admin.rb
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
require "super_admin/dashboard_creator"
|
|
2
|
+
require "super_admin/install_task"
|
|
3
|
+
|
|
4
|
+
namespace :super_admin do
|
|
5
|
+
desc "Install SuperAdmin (use 'rails generate super_admin:install' instead)"
|
|
6
|
+
task install: :environment do
|
|
7
|
+
puts "⚠️ This rake task is deprecated. Please use the generator instead:"
|
|
8
|
+
puts " rails generate super_admin:install"
|
|
9
|
+
puts "\nRunning generator..."
|
|
10
|
+
Rails::Generators.invoke("super_admin:install")
|
|
11
|
+
rescue StandardError => e
|
|
12
|
+
abort "SuperAdmin install failed: #{e.message}"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
namespace :install do
|
|
16
|
+
desc "Copy migrations from SuperAdmin to application"
|
|
17
|
+
task :migrations do
|
|
18
|
+
SuperAdmin::InstallTask.new.send(:copy_migrations)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
namespace :dashboards do
|
|
23
|
+
desc "Generate SuperAdmin dashboards (use 'rails generate super_admin:dashboard' instead)"
|
|
24
|
+
task :generate, [ :model_name ] => :environment do |_task, args|
|
|
25
|
+
model_name = args[:model_name] || ENV["MODEL"]
|
|
26
|
+
puts "⚠️ This rake task is deprecated. Please use the generator instead:"
|
|
27
|
+
if model_name
|
|
28
|
+
puts " rails generate super_admin:dashboard #{model_name}"
|
|
29
|
+
else
|
|
30
|
+
puts " rails generate super_admin:dashboard"
|
|
31
|
+
end
|
|
32
|
+
puts "\nRunning generator..."
|
|
33
|
+
Rails::Generators.invoke("super_admin:dashboard", [ model_name ].compact)
|
|
34
|
+
rescue ArgumentError => e
|
|
35
|
+
abort e.message
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: super_admin
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.2.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Thibaut Baissac
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2025-11-07 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: rails
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '7.1'
|
|
20
|
+
- - "<"
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: '9.0'
|
|
23
|
+
type: :runtime
|
|
24
|
+
prerelease: false
|
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
26
|
+
requirements:
|
|
27
|
+
- - ">="
|
|
28
|
+
- !ruby/object:Gem::Version
|
|
29
|
+
version: '7.1'
|
|
30
|
+
- - "<"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '9.0'
|
|
33
|
+
- !ruby/object:Gem::Dependency
|
|
34
|
+
name: pagy
|
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '9.0'
|
|
40
|
+
type: :runtime
|
|
41
|
+
prerelease: false
|
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '9.0'
|
|
47
|
+
- !ruby/object:Gem::Dependency
|
|
48
|
+
name: turbo-rails
|
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '2.0'
|
|
54
|
+
type: :runtime
|
|
55
|
+
prerelease: false
|
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '2.0'
|
|
61
|
+
- !ruby/object:Gem::Dependency
|
|
62
|
+
name: stimulus-rails
|
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '1.3'
|
|
68
|
+
type: :runtime
|
|
69
|
+
prerelease: false
|
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - ">="
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '1.3'
|
|
75
|
+
- !ruby/object:Gem::Dependency
|
|
76
|
+
name: rack-attack
|
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '6.0'
|
|
82
|
+
type: :runtime
|
|
83
|
+
prerelease: false
|
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - ">="
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '6.0'
|
|
89
|
+
- !ruby/object:Gem::Dependency
|
|
90
|
+
name: csv
|
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - ">="
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '0'
|
|
96
|
+
type: :runtime
|
|
97
|
+
prerelease: false
|
|
98
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - ">="
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: '0'
|
|
103
|
+
description: SuperAdmin is a mountable Rails engine that provides a full-featured
|
|
104
|
+
administration interface inspired by Administrate and ActiveAdmin, built for modern
|
|
105
|
+
Rails 7+ applications.
|
|
106
|
+
email:
|
|
107
|
+
- tbaissac@gmail.com
|
|
108
|
+
executables: []
|
|
109
|
+
extensions: []
|
|
110
|
+
extra_rdoc_files: []
|
|
111
|
+
files:
|
|
112
|
+
- README.md
|
|
113
|
+
- Rakefile
|
|
114
|
+
- app/assets/stylesheets/super_admin/application.css
|
|
115
|
+
- app/assets/stylesheets/super_admin/tailwind.css
|
|
116
|
+
- app/assets/stylesheets/super_admin/tailwind.source.css
|
|
117
|
+
- app/controllers/super_admin/application_controller.rb
|
|
118
|
+
- app/controllers/super_admin/associations_controller.rb
|
|
119
|
+
- app/controllers/super_admin/audit_logs_controller.rb
|
|
120
|
+
- app/controllers/super_admin/base_controller.rb
|
|
121
|
+
- app/controllers/super_admin/dashboard_controller.rb
|
|
122
|
+
- app/controllers/super_admin/exports_controller.rb
|
|
123
|
+
- app/controllers/super_admin/resources_controller.rb
|
|
124
|
+
- app/dashboards/super_admin/base_dashboard.rb
|
|
125
|
+
- app/errors/super_admin/configuration_error.rb
|
|
126
|
+
- app/helpers/super_admin/application_helper.rb
|
|
127
|
+
- app/helpers/super_admin/exports_helper.rb
|
|
128
|
+
- app/helpers/super_admin/resources_helper.rb
|
|
129
|
+
- app/helpers/super_admin/route_helper.rb
|
|
130
|
+
- app/javascript/super_admin/application.js
|
|
131
|
+
- app/jobs/super_admin/application_job.rb
|
|
132
|
+
- app/jobs/super_admin/generate_super_admin_csv_export_job.rb
|
|
133
|
+
- app/mailers/super_admin/application_mailer.rb
|
|
134
|
+
- app/models/super_admin/application_record.rb
|
|
135
|
+
- app/models/super_admin/audit_log.rb
|
|
136
|
+
- app/models/super_admin/csv_export.rb
|
|
137
|
+
- app/services/super_admin/auditing.rb
|
|
138
|
+
- app/services/super_admin/authorization.rb
|
|
139
|
+
- app/services/super_admin/authorization_adapters/base_adapter.rb
|
|
140
|
+
- app/services/super_admin/authorization_adapters/default_adapter.rb
|
|
141
|
+
- app/services/super_admin/authorization_adapters/proc_adapter.rb
|
|
142
|
+
- app/services/super_admin/authorization_adapters/pundit_adapter.rb
|
|
143
|
+
- app/services/super_admin/csv_export_creator.rb
|
|
144
|
+
- app/services/super_admin/dashboard_registry.rb
|
|
145
|
+
- app/services/super_admin/dashboard_resolver.rb
|
|
146
|
+
- app/services/super_admin/filter_builder.rb
|
|
147
|
+
- app/services/super_admin/form_builder.rb
|
|
148
|
+
- app/services/super_admin/form_fields/array_field.rb
|
|
149
|
+
- app/services/super_admin/form_fields/association_field.rb
|
|
150
|
+
- app/services/super_admin/form_fields/base_field.rb
|
|
151
|
+
- app/services/super_admin/form_fields/boolean_field.rb
|
|
152
|
+
- app/services/super_admin/form_fields/date_field.rb
|
|
153
|
+
- app/services/super_admin/form_fields/date_time_field.rb
|
|
154
|
+
- app/services/super_admin/form_fields/enum_field.rb
|
|
155
|
+
- app/services/super_admin/form_fields/factory.rb
|
|
156
|
+
- app/services/super_admin/form_fields/nested_field.rb
|
|
157
|
+
- app/services/super_admin/form_fields/number_field.rb
|
|
158
|
+
- app/services/super_admin/form_fields/text_area_field.rb
|
|
159
|
+
- app/services/super_admin/model_inspector.rb
|
|
160
|
+
- app/services/super_admin/queries/base_query.rb
|
|
161
|
+
- app/services/super_admin/queries/filter_query.rb
|
|
162
|
+
- app/services/super_admin/queries/resource_scope_query.rb
|
|
163
|
+
- app/services/super_admin/queries/search_query.rb
|
|
164
|
+
- app/services/super_admin/queries/sort_query.rb
|
|
165
|
+
- app/services/super_admin/resource_configuration.rb
|
|
166
|
+
- app/services/super_admin/resource_exporter.rb
|
|
167
|
+
- app/services/super_admin/resource_query.rb
|
|
168
|
+
- app/services/super_admin/resources/association_inspector.rb
|
|
169
|
+
- app/services/super_admin/resources/collection_presenter.rb
|
|
170
|
+
- app/services/super_admin/resources/context.rb
|
|
171
|
+
- app/services/super_admin/resources/filter_params.rb
|
|
172
|
+
- app/services/super_admin/resources/permitted_attributes.rb
|
|
173
|
+
- app/services/super_admin/resources/value_normalizer.rb
|
|
174
|
+
- app/services/super_admin/sensitive_attributes.rb
|
|
175
|
+
- app/views/layouts/super_admin.html.erb
|
|
176
|
+
- app/views/super_admin/audit_logs/index.html.erb
|
|
177
|
+
- app/views/super_admin/dashboard/index.html.erb
|
|
178
|
+
- app/views/super_admin/exports/index.html.erb
|
|
179
|
+
- app/views/super_admin/exports/show.html.erb
|
|
180
|
+
- app/views/super_admin/resources/_form.html.erb
|
|
181
|
+
- app/views/super_admin/resources/destroy.turbo_stream.erb
|
|
182
|
+
- app/views/super_admin/resources/edit.html.erb
|
|
183
|
+
- app/views/super_admin/resources/index.html.erb
|
|
184
|
+
- app/views/super_admin/resources/new.html.erb
|
|
185
|
+
- app/views/super_admin/resources/show.html.erb
|
|
186
|
+
- app/views/super_admin/shared/_breadcrumbs.html.erb
|
|
187
|
+
- app/views/super_admin/shared/_custom_styles.html.erb
|
|
188
|
+
- app/views/super_admin/shared/_flash.html.erb
|
|
189
|
+
- app/views/super_admin/shared/_form_field.html.erb
|
|
190
|
+
- app/views/super_admin/shared/_navigation.html.erb
|
|
191
|
+
- app/views/super_admin/shared/_nested_fields.html.erb
|
|
192
|
+
- app/views/super_admin/shared/_nested_record_fields.html.erb
|
|
193
|
+
- config/importmap.rb
|
|
194
|
+
- config/initializers/rack_attack.rb
|
|
195
|
+
- config/initializers/super_admin.rb
|
|
196
|
+
- config/locales/super_admin.en.yml
|
|
197
|
+
- config/locales/super_admin.fr.yml
|
|
198
|
+
- config/routes.rb
|
|
199
|
+
- lib/generators/super_admin/dashboard_generator.rb
|
|
200
|
+
- lib/generators/super_admin/install_generator.rb
|
|
201
|
+
- lib/generators/super_admin/templates/20240101000001_create_super_admin_audit_logs.rb
|
|
202
|
+
- lib/generators/super_admin/templates/20240101000002_create_super_admin_csv_exports.rb
|
|
203
|
+
- lib/generators/super_admin/templates/super_admin.rb
|
|
204
|
+
- lib/super_admin.rb
|
|
205
|
+
- lib/super_admin/dashboard_creator.rb
|
|
206
|
+
- lib/super_admin/engine.rb
|
|
207
|
+
- lib/super_admin/install_task.rb
|
|
208
|
+
- lib/super_admin/version.rb
|
|
209
|
+
- lib/tasks/super_admin_tasks.rake
|
|
210
|
+
homepage: https://github.com/ThibautBaissac/super_admin
|
|
211
|
+
licenses:
|
|
212
|
+
- MIT
|
|
213
|
+
metadata:
|
|
214
|
+
homepage_uri: https://github.com/ThibautBaissac/super_admin
|
|
215
|
+
source_code_uri: https://github.com/ThibautBaissac/super_admin
|
|
216
|
+
changelog_uri: https://github.com/ThibautBaissac/super_admin/blob/main/CHANGELOG.md
|
|
217
|
+
bug_tracker_uri: https://github.com/ThibautBaissac/super_admin/issues
|
|
218
|
+
documentation_uri: https://github.com/ThibautBaissac/super_admin#readme
|
|
219
|
+
rubygems_mfa_required: 'true'
|
|
220
|
+
post_install_message:
|
|
221
|
+
rdoc_options: []
|
|
222
|
+
require_paths:
|
|
223
|
+
- lib
|
|
224
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
225
|
+
requirements:
|
|
226
|
+
- - ">="
|
|
227
|
+
- !ruby/object:Gem::Version
|
|
228
|
+
version: '3.2'
|
|
229
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
230
|
+
requirements:
|
|
231
|
+
- - ">="
|
|
232
|
+
- !ruby/object:Gem::Version
|
|
233
|
+
version: '0'
|
|
234
|
+
requirements: []
|
|
235
|
+
rubygems_version: 3.5.22
|
|
236
|
+
signing_key:
|
|
237
|
+
specification_version: 4
|
|
238
|
+
summary: A modern, flexible administration engine for Rails applications
|
|
239
|
+
test_files: []
|