admin_resources 0.2.2 → 0.2.3
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 +4 -4
- data/app/controllers/admin_resources/application_controller.rb +5 -1
- data/app/controllers/admin_resources/resources_controller.rb +104 -8
- data/app/views/admin_resources/resources/_form.html.erb +29 -1
- data/app/views/admin_resources/resources/index.html.erb +44 -1
- data/app/views/admin_resources/resources/show.html.erb +32 -0
- data/app/views/layouts/admin_resources/admin.html.erb +23 -2
- data/lib/admin_resources/configuration.rb +16 -3
- data/lib/admin_resources/version.rb +1 -1
- data/lib/admin_resources.rb +1 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c4fb7ecf52ff124948a11973d5b63b3cbe3737158da8fec91073798240e0590b
|
|
4
|
+
data.tar.gz: af1ff3a8d57925be7120991d2a9fc1b79562c17fd93f02bac6800dbdcc95defb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1296c290116af677ce5d57988eddf05938d18180065a4b2a3652853193e644bf056d78a5d97bdd60ec7714abe3f1392d3c788349c65a4f883d4aa55a8b76d005
|
|
7
|
+
data.tar.gz: 48558c73eb234283b8bcfc5c5e7d621b765c4d746c9d1799392b055c53427cf7ba8b43b90884f7da66f1326ba29abafd137fc476ec9089843f870f9e259d301b
|
|
@@ -11,7 +11,11 @@ module AdminResources
|
|
|
11
11
|
admin_resources.new_admin_user_session_path
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
helper_method :admin_models, :admin_path_for
|
|
14
|
+
helper_method :admin_models, :admin_path_for, :admin_custom_pages
|
|
15
|
+
|
|
16
|
+
def admin_custom_pages
|
|
17
|
+
AdminResources.configuration.custom_pages
|
|
18
|
+
end
|
|
15
19
|
|
|
16
20
|
def admin_models
|
|
17
21
|
AdminResources.model_names
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
module AdminResources
|
|
2
2
|
class ResourcesController < ApplicationController
|
|
3
3
|
before_action :set_model_class
|
|
4
|
-
before_action :set_resource, only: %i[show edit update destroy]
|
|
4
|
+
before_action :set_resource, only: %i[show edit update destroy custom_action]
|
|
5
5
|
|
|
6
|
-
helper_method :model_class, :model_name, :index_columns, :form_columns, :admin_value_display
|
|
6
|
+
helper_method :model_class, :model_name, :index_columns, :form_columns, :admin_value_display, :join_associations, :custom_actions, :filter_columns
|
|
7
7
|
|
|
8
8
|
def index
|
|
9
9
|
puts "[AdminResources::ResourcesController] index for #{model_name}"
|
|
10
|
-
@resources = model_class.all.order(id: :desc)
|
|
10
|
+
@resources = filter_resources(model_class.all).order(id: :desc)
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def show
|
|
@@ -23,6 +23,8 @@ module AdminResources
|
|
|
23
23
|
puts "[AdminResources::ResourcesController] create #{model_name}"
|
|
24
24
|
@resource = model_class.new(resource_params)
|
|
25
25
|
if @resource.save
|
|
26
|
+
sync_join_associations
|
|
27
|
+
flash[:new_url] = admin_path_for(model_name, :new)
|
|
26
28
|
redirect_to admin_path_for(model_name, :show, @resource), notice: "#{model_name} was successfully created."
|
|
27
29
|
else
|
|
28
30
|
render :new, status: :unprocessable_entity
|
|
@@ -36,6 +38,7 @@ module AdminResources
|
|
|
36
38
|
def update
|
|
37
39
|
puts "[AdminResources::ResourcesController] update #{model_name}##{@resource.id}"
|
|
38
40
|
if @resource.update(resource_params)
|
|
41
|
+
sync_join_associations
|
|
39
42
|
redirect_to admin_path_for(model_name, :show, @resource), notice: "#{model_name} was successfully updated."
|
|
40
43
|
else
|
|
41
44
|
render :edit, status: :unprocessable_entity
|
|
@@ -48,6 +51,33 @@ module AdminResources
|
|
|
48
51
|
redirect_to admin_path_for(model_name, :index), notice: "#{model_name} was successfully deleted."
|
|
49
52
|
end
|
|
50
53
|
|
|
54
|
+
# Dispatches to a host-app controller action via a configured custom action.
|
|
55
|
+
# The host app must define a route that this proxies to, specified as `handler`.
|
|
56
|
+
# If no handler is configured, falls back to calling a same-named method on the resource.
|
|
57
|
+
def custom_action
|
|
58
|
+
action_name_param = params[:custom_action]
|
|
59
|
+
action_config = custom_actions.find { |a| a[:name].to_s == action_name_param }
|
|
60
|
+
unless action_config
|
|
61
|
+
redirect_to admin_path_for(model_name, :show, @resource), alert: "Unknown action."
|
|
62
|
+
return
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
handler = action_config[:handler]
|
|
66
|
+
if handler
|
|
67
|
+
# handler is a callable (proc/lambda) that receives (resource, controller)
|
|
68
|
+
handler.call(@resource, self)
|
|
69
|
+
else
|
|
70
|
+
# Default: call a method by the action name on the resource
|
|
71
|
+
if @resource.respond_to?(action_name_param)
|
|
72
|
+
@resource.public_send(action_name_param)
|
|
73
|
+
redirect_to admin_path_for(model_name, :show, @resource),
|
|
74
|
+
notice: "#{action_config[:label] || action_name_param} completed."
|
|
75
|
+
else
|
|
76
|
+
redirect_to admin_path_for(model_name, :show, @resource), alert: "Action not implemented."
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
51
81
|
private
|
|
52
82
|
|
|
53
83
|
def set_model_class
|
|
@@ -92,17 +122,14 @@ module AdminResources
|
|
|
92
122
|
assoc_name = column.sub(/_id$/, "")
|
|
93
123
|
association = resource.class.reflect_on_association(assoc_name.to_sym)
|
|
94
124
|
|
|
95
|
-
if association && association.macro == :belongs_to
|
|
125
|
+
if association && association.macro == :belongs_to && !association.options[:polymorphic]
|
|
96
126
|
assoc_class = association.klass
|
|
97
127
|
assoc_model = assoc_class.name
|
|
98
128
|
|
|
99
129
|
if AdminResources.model_names.include?(assoc_model)
|
|
100
130
|
associated_record = assoc_class.find_by(id: value)
|
|
101
131
|
if associated_record
|
|
102
|
-
display =
|
|
103
|
-
display = associated_record.name if associated_record.respond_to?(:name) && associated_record.name.present?
|
|
104
|
-
display = associated_record.version if associated_record.respond_to?(:version) && associated_record.version.present?
|
|
105
|
-
display = associated_record.email if associated_record.respond_to?(:email) && associated_record.email.present?
|
|
132
|
+
display = associated_record.to_s
|
|
106
133
|
return [display, admin_path_for(assoc_model, :show, associated_record)]
|
|
107
134
|
end
|
|
108
135
|
end
|
|
@@ -123,5 +150,74 @@ module AdminResources
|
|
|
123
150
|
end
|
|
124
151
|
params.require(model_class.model_name.param_key).permit(*permitted)
|
|
125
152
|
end
|
|
153
|
+
|
|
154
|
+
def join_associations
|
|
155
|
+
AdminResources.models[model_name]&.dig(:has_many_through) || []
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def custom_actions
|
|
159
|
+
AdminResources.models[model_name]&.dig(:custom_actions) || []
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Returns only the declared index columns that actually exist as DB columns (excludes virtual/through cols)
|
|
163
|
+
def filter_columns
|
|
164
|
+
index_columns.select { |col| model_class.columns_hash[col] }
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def filter_resources(scope)
|
|
168
|
+
return scope unless params[:q].is_a?(Hash)
|
|
169
|
+
|
|
170
|
+
params[:q].each do |col, value|
|
|
171
|
+
next if value.blank?
|
|
172
|
+
next unless filter_columns.include?(col)
|
|
173
|
+
|
|
174
|
+
column = model_class.columns_hash[col]
|
|
175
|
+
next unless column
|
|
176
|
+
|
|
177
|
+
case column.type
|
|
178
|
+
when :string, :text, :citext
|
|
179
|
+
scope = scope.where("#{model_class.quoted_table_name}.#{connection.quote_column_name(col)} ILIKE ?", "%#{value}%")
|
|
180
|
+
when :integer, :bigint
|
|
181
|
+
scope = scope.where(col => value.to_i) if value.match?(/\A-?\d+\z/)
|
|
182
|
+
when :boolean
|
|
183
|
+
scope = scope.where(col => ActiveModel::Type::Boolean.new.cast(value)) unless value == "any"
|
|
184
|
+
when :date, :datetime
|
|
185
|
+
scope = scope.where(col => value) if value.present?
|
|
186
|
+
else
|
|
187
|
+
scope = scope.where(col => value)
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
scope
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def connection
|
|
195
|
+
model_class.connection
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def sync_join_associations
|
|
199
|
+
join_associations.each do |jdef|
|
|
200
|
+
join_model_class = jdef[:join_model].safe_constantize
|
|
201
|
+
next unless join_model_class
|
|
202
|
+
|
|
203
|
+
foreign_key = jdef[:foreign_key]
|
|
204
|
+
through_key = jdef[:through_key]
|
|
205
|
+
param_key = "#{jdef[:association]}_ids"
|
|
206
|
+
submitted_ids = (params[model_class.model_name.param_key] || {})[param_key]
|
|
207
|
+
|
|
208
|
+
next if submitted_ids.nil?
|
|
209
|
+
|
|
210
|
+
new_ids = Array(submitted_ids).map(&:to_i).reject(&:zero?)
|
|
211
|
+
|
|
212
|
+
existing = join_model_class.where(foreign_key => @resource.id)
|
|
213
|
+
existing_ids = existing.pluck(through_key)
|
|
214
|
+
|
|
215
|
+
to_add = new_ids - existing_ids
|
|
216
|
+
to_remove = existing_ids - new_ids
|
|
217
|
+
|
|
218
|
+
join_model_class.where(foreign_key => @resource.id, through_key => to_remove).destroy_all if to_remove.any?
|
|
219
|
+
to_add.each { |tid| join_model_class.create!(foreign_key => @resource.id, through_key => tid) }
|
|
220
|
+
end
|
|
221
|
+
end
|
|
126
222
|
end
|
|
127
223
|
end
|
|
@@ -20,7 +20,19 @@
|
|
|
20
20
|
<% when :text %>
|
|
21
21
|
<%= form.text_area col, rows: 4 %>
|
|
22
22
|
<% when :integer, :decimal, :float %>
|
|
23
|
-
|
|
23
|
+
<% if model_class.defined_enums.key?(col) %>
|
|
24
|
+
<%= form.select col, model_class.defined_enums[col].keys, include_blank: true %>
|
|
25
|
+
<% elsif col.end_with?('_id') %>
|
|
26
|
+
<% assoc_name = col.sub(/_id$/, '').classify %>
|
|
27
|
+
<% assoc_class = assoc_name.safe_constantize %>
|
|
28
|
+
<% if assoc_class && assoc_class < ActiveRecord::Base %>
|
|
29
|
+
<%= form.collection_select col, assoc_class.all, :id, :to_s, { include_blank: true }, {} %>
|
|
30
|
+
<% else %>
|
|
31
|
+
<%= form.number_field col, step: 1 %>
|
|
32
|
+
<% end %>
|
|
33
|
+
<% else %>
|
|
34
|
+
<%= form.number_field col, step: (column.type == :integer ? 1 : 'any') %>
|
|
35
|
+
<% end %>
|
|
24
36
|
<% when :date %>
|
|
25
37
|
<%= form.date_field col %>
|
|
26
38
|
<% when :datetime %>
|
|
@@ -47,6 +59,22 @@
|
|
|
47
59
|
</div>
|
|
48
60
|
<% end %>
|
|
49
61
|
|
|
62
|
+
<% join_associations.each do |jdef| %>
|
|
63
|
+
<% assoc_name = jdef[:association] %>
|
|
64
|
+
<% param_key = "#{assoc_name}_ids" %>
|
|
65
|
+
<% target_class = assoc_name.to_s.classify.safe_constantize %>
|
|
66
|
+
<% if target_class && target_class < ActiveRecord::Base %>
|
|
67
|
+
<div class="field">
|
|
68
|
+
<%= form.label param_key, assoc_name.to_s.humanize %>
|
|
69
|
+
<% current_ids = @resource.persisted? ? @resource.send(assoc_name).pluck(:id) : [] %>
|
|
70
|
+
<%= form.collection_select param_key, target_class.all, :id, :to_s,
|
|
71
|
+
{ selected: current_ids, include_blank: false },
|
|
72
|
+
{ multiple: true, size: 6 } %>
|
|
73
|
+
<small>Hold Ctrl / Cmd to select multiple</small>
|
|
74
|
+
</div>
|
|
75
|
+
<% end %>
|
|
76
|
+
<% end %>
|
|
77
|
+
|
|
50
78
|
<div class="field">
|
|
51
79
|
<%= form.submit class: "admin-btn admin-btn-primary" %>
|
|
52
80
|
<%= link_to "Cancel", (@resource.new_record? ? admin_path_for(model_name) : admin_path_for(model_name, :show, @resource)), class: "admin-btn admin-btn-secondary" %>
|
|
@@ -3,6 +3,41 @@
|
|
|
3
3
|
<%= link_to "New #{model_name}", admin_path_for(model_name, :new), class: "admin-btn admin-btn-primary" %>
|
|
4
4
|
</div>
|
|
5
5
|
|
|
6
|
+
<% if filter_columns.any? %>
|
|
7
|
+
<form method="get" style="display:flex;flex-wrap:wrap;gap:0.5rem;align-items:flex-end;margin-bottom:1.25rem">
|
|
8
|
+
<% filter_columns.each do |col| %>
|
|
9
|
+
<% column = model_class.columns_hash[col] %>
|
|
10
|
+
<div style="display:flex;flex-direction:column;gap:0.25rem">
|
|
11
|
+
<label style="font-size:0.75rem;color:#666"><%= col.titleize %></label>
|
|
12
|
+
<% case column.type %>
|
|
13
|
+
<% when :boolean %>
|
|
14
|
+
<select name="q[<%= col %>]" class="admin-input" style="height:32px;padding:0 0.5rem;font-size:0.875rem">
|
|
15
|
+
<option value="any" <%= "selected" if params.dig(:q, col) == "any" || params.dig(:q, col).blank? %>>Any</option>
|
|
16
|
+
<option value="true" <%= "selected" if params.dig(:q, col) == "true" %>>Yes</option>
|
|
17
|
+
<option value="false" <%= "selected" if params.dig(:q, col) == "false" %>>No</option>
|
|
18
|
+
</select>
|
|
19
|
+
<% when :integer, :bigint %>
|
|
20
|
+
<input type="number" step="1" name="q[<%= col %>]" value="<%= params.dig(:q, col) %>"
|
|
21
|
+
class="admin-input" style="width:100px;height:32px;padding:0 0.5rem;font-size:0.875rem">
|
|
22
|
+
<% when :date %>
|
|
23
|
+
<input type="date" name="q[<%= col %>]" value="<%= params.dig(:q, col) %>"
|
|
24
|
+
class="admin-input" style="height:32px;padding:0 0.5rem;font-size:0.875rem">
|
|
25
|
+
<% when :datetime %>
|
|
26
|
+
<input type="date" name="q[<%= col %>]" value="<%= params.dig(:q, col) %>"
|
|
27
|
+
class="admin-input" style="height:32px;padding:0 0.5rem;font-size:0.875rem">
|
|
28
|
+
<% else %>
|
|
29
|
+
<input type="text" name="q[<%= col %>]" value="<%= params.dig(:q, col) %>"
|
|
30
|
+
placeholder="Filter…" class="admin-input" style="height:32px;padding:0 0.5rem;font-size:0.875rem">
|
|
31
|
+
<% end %>
|
|
32
|
+
</div>
|
|
33
|
+
<% end %>
|
|
34
|
+
<div style="display:flex;gap:0.5rem">
|
|
35
|
+
<button type="submit" class="admin-btn admin-btn-primary" style="height:32px">Filter</button>
|
|
36
|
+
<%= link_to "Clear", request.path, class: "admin-btn admin-btn-secondary", style: "height:32px;line-height:32px;padding:0 0.75rem" %>
|
|
37
|
+
</div>
|
|
38
|
+
</form>
|
|
39
|
+
<% end %>
|
|
40
|
+
|
|
6
41
|
<table class="admin-table">
|
|
7
42
|
<thead>
|
|
8
43
|
<tr>
|
|
@@ -14,7 +49,7 @@
|
|
|
14
49
|
</thead>
|
|
15
50
|
<tbody>
|
|
16
51
|
<% @resources.each do |resource| %>
|
|
17
|
-
<tr>
|
|
52
|
+
<tr data-href="<%= admin_path_for(model_name, :show, resource) %>" style="cursor:pointer">
|
|
18
53
|
<% index_columns.each do |col| %>
|
|
19
54
|
<td>
|
|
20
55
|
<% display, link = admin_value_display(resource, col) %>
|
|
@@ -31,6 +66,14 @@
|
|
|
31
66
|
<td class="admin-actions">
|
|
32
67
|
<%= link_to "View", admin_path_for(model_name, :show, resource), class: "admin-btn admin-btn-secondary" %>
|
|
33
68
|
<%= link_to "Edit", admin_path_for(model_name, :edit, resource), class: "admin-btn admin-btn-secondary" %>
|
|
69
|
+
<% custom_actions.each do |action| %>
|
|
70
|
+
<% action_path = "/admin/#{model_name.underscore.pluralize}/#{resource.id}/#{action[:name]}" %>
|
|
71
|
+
<%= button_to action[:label] || action[:name].to_s.titleize,
|
|
72
|
+
action_path,
|
|
73
|
+
method: (action[:method] || :post),
|
|
74
|
+
form: { data: { turbo_confirm: action[:confirm] } },
|
|
75
|
+
class: "admin-btn #{action[:class] || 'admin-btn-secondary'}" %>
|
|
76
|
+
<% end %>
|
|
34
77
|
</td>
|
|
35
78
|
</tr>
|
|
36
79
|
<% end %>
|
|
@@ -35,6 +35,38 @@
|
|
|
35
35
|
<% end %>
|
|
36
36
|
</div>
|
|
37
37
|
|
|
38
|
+
<%# Display Active Storage attachments %>
|
|
39
|
+
<% model_class.reflect_on_all_associations(:has_many).select { |a| a.options[:class_name] == "ActiveStorage::Attachment" }.each do |assoc| %>
|
|
40
|
+
<% attachments = @resource.send(assoc.name) %>
|
|
41
|
+
<% next if attachments.empty? %>
|
|
42
|
+
<div style="margin-top:2rem">
|
|
43
|
+
<div class="admin-header">
|
|
44
|
+
<h2><%= assoc.name.to_s.sub(/_attachments$/, '').titleize %></h2>
|
|
45
|
+
</div>
|
|
46
|
+
<div class="admin-card" style="display:flex;flex-wrap:wrap;gap:1rem">
|
|
47
|
+
<% attachments.each do |att| %>
|
|
48
|
+
<div style="border:1px solid #e5e5e5;border-radius:6px;overflow:hidden;width:160px">
|
|
49
|
+
<% blob_url = Rails.application.routes.url_helpers.rails_blob_url(att.blob, only_path: true) %>
|
|
50
|
+
<% if att.image? %>
|
|
51
|
+
<%= image_tag blob_url, style: "width:160px;height:120px;object-fit:cover;display:block" %>
|
|
52
|
+
<% elsif att.video? %>
|
|
53
|
+
<video style="width:160px;height:120px;object-fit:cover;display:block" controls preload="none">
|
|
54
|
+
<source src="<%= blob_url %>" type="<%= att.content_type %>">
|
|
55
|
+
</video>
|
|
56
|
+
<% else %>
|
|
57
|
+
<div style="width:160px;height:120px;display:flex;align-items:center;justify-content:center;background:#f5f5f5;font-size:0.75rem;color:#666;text-align:center;padding:0.5rem">
|
|
58
|
+
<%= att.filename %>
|
|
59
|
+
</div>
|
|
60
|
+
<% end %>
|
|
61
|
+
<div style="padding:0.35rem 0.5rem;font-size:0.7rem;color:#666;border-top:1px solid #e5e5e5">
|
|
62
|
+
<%= att.filename %><br><%= number_to_human_size(att.byte_size) %>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
<% end %>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
<% end %>
|
|
69
|
+
|
|
38
70
|
<%# Display has_one associations %>
|
|
39
71
|
<% model_class.reflect_on_all_associations(:has_one).each do |assoc| %>
|
|
40
72
|
<% record = @resource.send(assoc.name) %>
|
|
@@ -56,15 +56,28 @@
|
|
|
56
56
|
class: ('active' if params[:model] == model_name) %>
|
|
57
57
|
<% end %>
|
|
58
58
|
|
|
59
|
+
<% if admin_custom_pages.any? %>
|
|
60
|
+
<h3>Tools</h3>
|
|
61
|
+
<% admin_custom_pages.each do |page| %>
|
|
62
|
+
<%= link_to "#{page[:icon]} #{page[:label]}".strip, page[:path],
|
|
63
|
+
class: (request.path.start_with?(page[:path]) ? 'active' : '') %>
|
|
64
|
+
<% end %>
|
|
65
|
+
<% end %>
|
|
66
|
+
|
|
59
67
|
<h3>Account</h3>
|
|
60
68
|
<%= link_to "Admin Users", admin_resources.admin_users_path,
|
|
61
69
|
class: (controller_path == 'admin_resources/admin_users' ? 'active' : '') %>
|
|
62
|
-
<%=
|
|
70
|
+
<%= button_to "Sign Out", admin_resources.destroy_admin_user_session_path, method: :delete, class: "admin-btn admin-btn-secondary", style: "width:100%;text-align:left;background:none;color:#eee;padding:0.5rem;border-radius:4px;cursor:pointer;" %>
|
|
63
71
|
</nav>
|
|
64
72
|
|
|
65
73
|
<main class="admin-main">
|
|
66
74
|
<% if notice %>
|
|
67
|
-
<div class="admin-flash notice"
|
|
75
|
+
<div class="admin-flash notice">
|
|
76
|
+
<%= notice %>
|
|
77
|
+
<% if flash[:new_url] %>
|
|
78
|
+
· <%= link_to "Add another →", flash[:new_url] %>
|
|
79
|
+
<% end %>
|
|
80
|
+
</div>
|
|
68
81
|
<% end %>
|
|
69
82
|
<% if alert %>
|
|
70
83
|
<div class="admin-flash alert"><%= alert %></div>
|
|
@@ -73,5 +86,13 @@
|
|
|
73
86
|
<%= yield %>
|
|
74
87
|
</main>
|
|
75
88
|
</div>
|
|
89
|
+
<script>
|
|
90
|
+
document.addEventListener('click', function(e) {
|
|
91
|
+
var row = e.target.closest('tr[data-href]');
|
|
92
|
+
if (!row) return;
|
|
93
|
+
if (e.target.closest('a, button, form')) return;
|
|
94
|
+
window.location = row.dataset.href;
|
|
95
|
+
});
|
|
96
|
+
</script>
|
|
76
97
|
</body>
|
|
77
98
|
</html>
|
|
@@ -2,22 +2,35 @@
|
|
|
2
2
|
|
|
3
3
|
module AdminResources
|
|
4
4
|
class Configuration
|
|
5
|
-
attr_reader :models
|
|
5
|
+
attr_reader :models, :custom_pages
|
|
6
6
|
|
|
7
7
|
def initialize
|
|
8
8
|
@models = {}
|
|
9
|
+
@custom_pages = []
|
|
9
10
|
end
|
|
10
11
|
|
|
11
12
|
# Register a model for admin management
|
|
12
13
|
# Usage: config.register "User", columns: %w[id email created_at]
|
|
13
14
|
# Usage: config.register "User" (defaults to first 6 columns)
|
|
14
|
-
|
|
15
|
+
# Usage: config.register "Product", has_many_through: [{ association: :desk_buddy_versions, join_model: "ProductVersion", foreign_key: :product_id, through_key: :desk_buddy_version_id }]
|
|
16
|
+
# Usage: config.register "Order", custom_actions: [{ name: :ship, label: "Mark Shipped", method: :patch, confirm: "Mark this order as shipped?" }]
|
|
17
|
+
def register(model_name, columns: nil, has_many_through: nil, custom_actions: nil)
|
|
15
18
|
name = model_name.to_s.classify
|
|
16
|
-
@models[name] = {
|
|
19
|
+
@models[name] = {
|
|
20
|
+
columns: columns&.map(&:to_s),
|
|
21
|
+
has_many_through: has_many_through || [],
|
|
22
|
+
custom_actions: custom_actions || []
|
|
23
|
+
}
|
|
17
24
|
end
|
|
18
25
|
|
|
19
26
|
def model_names
|
|
20
27
|
@models.keys
|
|
21
28
|
end
|
|
29
|
+
|
|
30
|
+
# Register a custom sidebar page that routes to the host app
|
|
31
|
+
# Usage: config.add_page "STL Models", path: "/admin/stl_models", icon: "📦"
|
|
32
|
+
def add_page(label, path:, icon: nil)
|
|
33
|
+
@custom_pages << { label: label, path: path, icon: icon }
|
|
34
|
+
end
|
|
22
35
|
end
|
|
23
36
|
end
|
data/lib/admin_resources.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: admin_resources
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- mark rosenberg
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-04-08 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|