admin_suite 0.2.1 → 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/assets/admin_suite.css +129 -0
- data/app/controllers/admin_suite/application_controller.rb +31 -0
- data/app/controllers/admin_suite/dashboard_controller.rb +59 -226
- data/app/controllers/admin_suite/resources_controller.rb +14 -4
- data/app/helpers/admin_suite/base_helper.rb +147 -108
- data/app/helpers/admin_suite/panels_helper.rb +1 -1
- data/app/javascript/controllers/admin_suite/file_upload_controller.js +9 -9
- data/app/javascript/controllers/admin_suite/flash_controller.js +45 -0
- data/app/javascript/controllers/admin_suite/json_editor_controller.js +8 -8
- data/app/javascript/controllers/admin_suite/searchable_select_controller.js +2 -2
- data/app/javascript/controllers/admin_suite/tag_select_controller.js +1 -1
- data/app/javascript/controllers/admin_suite/toggle_switch_controller.js +25 -16
- data/app/views/admin_suite/dashboard/index.html.erb +6 -15
- data/app/views/admin_suite/panels/_cards.html.erb +6 -6
- data/app/views/admin_suite/panels/_chart.html.erb +12 -12
- data/app/views/admin_suite/panels/_health.html.erb +14 -14
- data/app/views/admin_suite/panels/_recent.html.erb +11 -11
- data/app/views/admin_suite/panels/_stat.html.erb +24 -24
- data/app/views/admin_suite/panels/_table.html.erb +10 -10
- data/app/views/admin_suite/portals/show.html.erb +1 -1
- data/app/views/admin_suite/resources/_form.html.erb +1 -1
- data/app/views/admin_suite/resources/edit.html.erb +4 -4
- data/app/views/admin_suite/resources/index.html.erb +23 -23
- data/app/views/admin_suite/resources/new.html.erb +4 -4
- data/app/views/admin_suite/resources/show.html.erb +17 -17
- data/app/views/admin_suite/shared/_flash.html.erb +15 -2
- data/app/views/admin_suite/shared/_form.html.erb +8 -8
- data/app/views/admin_suite/shared/_json_editor_field.html.erb +4 -4
- data/app/views/admin_suite/shared/_sidebar.html.erb +4 -4
- data/app/views/admin_suite/shared/_toggle_cell.html.erb +4 -2
- data/app/views/admin_suite/shared/_topbar.html.erb +14 -1
- data/app/views/layouts/admin_suite/application.html.erb +4 -4
- data/docs/configuration.md +55 -0
- data/docs/portals.md +42 -0
- data/lib/admin_suite/configuration.rb +16 -0
- data/lib/admin_suite/engine.rb +9 -0
- data/lib/admin_suite/ui/field_renderer_registry.rb +2 -2
- data/lib/admin_suite/ui/form_field_renderer.rb +2 -2
- data/lib/admin_suite/ui/show_formatter_registry.rb +5 -5
- data/lib/admin_suite/ui/show_value_formatter.rb +31 -3
- data/lib/admin_suite/version.rb +1 -1
- data/lib/admin_suite.rb +31 -0
- data/lib/generators/admin_suite/install/templates/admin_suite.rb +5 -0
- data/test/integration/dashboard_test.rb +57 -1
- metadata +7 -5
- data/test/dummy/log/test.log +0 -624
- data/test/dummy/tmp/local_secret.txt +0 -1
data/lib/admin_suite/engine.rb
CHANGED
|
@@ -141,6 +141,15 @@ module AdminSuite
|
|
|
141
141
|
]
|
|
142
142
|
end
|
|
143
143
|
|
|
144
|
+
if config.dashboard_globs.blank?
|
|
145
|
+
config.dashboard_globs = [
|
|
146
|
+
Rails.root.join("config/admin_suite/dashboard.rb").to_s,
|
|
147
|
+
Rails.root.join("config/admin_suite/dashboard/*.rb").to_s,
|
|
148
|
+
Rails.root.join("app/admin_suite/dashboard.rb").to_s,
|
|
149
|
+
Rails.root.join("app/admin_suite/dashboard/*.rb").to_s
|
|
150
|
+
]
|
|
151
|
+
end
|
|
152
|
+
|
|
144
153
|
config.portals = {
|
|
145
154
|
ops: { label: "Ops Portal", icon: "settings", color: :amber, order: 10 },
|
|
146
155
|
email: { label: "Email Portal", icon: "inbox", color: :emerald, order: 20 },
|
|
@@ -75,11 +75,11 @@ AdminSuite::UI::FieldRendererRegistry.register(:attachment) do |view, f, field,
|
|
|
75
75
|
end
|
|
76
76
|
|
|
77
77
|
AdminSuite::UI::FieldRendererRegistry.register(:trix) do |_view, f, field, resource, _field_class|
|
|
78
|
-
f.rich_text_area(field.name, class: "prose
|
|
78
|
+
f.rich_text_area(field.name, class: "prose max-w-none")
|
|
79
79
|
end
|
|
80
80
|
|
|
81
81
|
AdminSuite::UI::FieldRendererRegistry.register(:rich_text) do |_view, f, field, resource, _field_class|
|
|
82
|
-
f.rich_text_area(field.name, class: "prose
|
|
82
|
+
f.rich_text_area(field.name, class: "prose max-w-none")
|
|
83
83
|
end
|
|
84
84
|
|
|
85
85
|
AdminSuite::UI::FieldRendererRegistry.register(:markdown) do |_view, f, field, resource, field_class|
|
|
@@ -38,8 +38,8 @@ module AdminSuite
|
|
|
38
38
|
|
|
39
39
|
concat(field_html)
|
|
40
40
|
|
|
41
|
-
concat(content_tag(:p, field.help, class: "mt-1 text-sm text-slate-500
|
|
42
|
-
concat(content_tag(:p, resource.errors[field.name].first, class: "mt-1 text-sm text-red-600
|
|
41
|
+
concat(content_tag(:p, field.help, class: "mt-1 text-sm text-slate-500")) if field.help.present?
|
|
42
|
+
concat(content_tag(:p, resource.errors[field.name].first, class: "mt-1 text-sm text-red-600")) if resource.errors[field.name].any?
|
|
43
43
|
end)
|
|
44
44
|
end
|
|
45
45
|
end
|
|
@@ -40,7 +40,7 @@ end
|
|
|
40
40
|
AdminSuite::UI::ShowFormatterRegistry.register_class(TrueClass) do |_value, view, _record, _field|
|
|
41
41
|
view.content_tag(:span, class: "inline-flex items-center gap-1") do
|
|
42
42
|
view.concat(view.admin_suite_icon("check-circle-2", class: "w-4 h-4 text-green-500"))
|
|
43
|
-
view.concat(view.content_tag(:span, "Yes", class: "text-green-600
|
|
43
|
+
view.concat(view.content_tag(:span, "Yes", class: "text-green-600 font-medium"))
|
|
44
44
|
end
|
|
45
45
|
end
|
|
46
46
|
|
|
@@ -54,14 +54,14 @@ end
|
|
|
54
54
|
AdminSuite::UI::ShowFormatterRegistry.register_class(Time) do |value, view, _record, _field|
|
|
55
55
|
view.content_tag(:span, class: "inline-flex items-center gap-2") do
|
|
56
56
|
view.concat(view.content_tag(:span, value.strftime("%B %d, %Y at %H:%M"), class: "font-medium"))
|
|
57
|
-
view.concat(view.content_tag(:span, "(#{view.time_ago_in_words(value)} ago)", class: "text-slate-500
|
|
57
|
+
view.concat(view.content_tag(:span, "(#{view.time_ago_in_words(value)} ago)", class: "text-slate-500 text-xs"))
|
|
58
58
|
end
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
AdminSuite::UI::ShowFormatterRegistry.register_class(DateTime) do |value, view, _record, _field|
|
|
62
62
|
view.content_tag(:span, class: "inline-flex items-center gap-2") do
|
|
63
63
|
view.concat(view.content_tag(:span, value.strftime("%B %d, %Y at %H:%M"), class: "font-medium"))
|
|
64
|
-
view.concat(view.content_tag(:span, "(#{view.time_ago_in_words(value)} ago)", class: "text-slate-500
|
|
64
|
+
view.concat(view.content_tag(:span, "(#{view.time_ago_in_words(value)} ago)", class: "text-slate-500 text-xs"))
|
|
65
65
|
end
|
|
66
66
|
end
|
|
67
67
|
|
|
@@ -72,7 +72,7 @@ end
|
|
|
72
72
|
if defined?(ActiveRecord::Base)
|
|
73
73
|
AdminSuite::UI::ShowFormatterRegistry.register_class(ActiveRecord::Base) do |value, view, _record, _field|
|
|
74
74
|
link_text = value.respond_to?(:name) ? value.name : "#{value.class.name} ##{value.id}"
|
|
75
|
-
view.content_tag(:span, link_text, class: "text-indigo-600
|
|
75
|
+
view.content_tag(:span, link_text, class: "text-indigo-600")
|
|
76
76
|
end
|
|
77
77
|
end
|
|
78
78
|
|
|
@@ -88,7 +88,7 @@ AdminSuite::UI::ShowFormatterRegistry.register_class(Array) do |value, view, _re
|
|
|
88
88
|
else
|
|
89
89
|
view.content_tag(:div, class: "flex flex-wrap gap-1") do
|
|
90
90
|
value.each do |item|
|
|
91
|
-
view.concat(view.content_tag(:span, item.to_s, class: "inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-slate-100
|
|
91
|
+
view.concat(view.content_tag(:span, item.to_s, class: "inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-slate-100 text-slate-700"))
|
|
92
92
|
end
|
|
93
93
|
end
|
|
94
94
|
end
|
|
@@ -12,6 +12,8 @@ module AdminSuite
|
|
|
12
12
|
|
|
13
13
|
if (field_def = admin_suite_field_definition(field_name))
|
|
14
14
|
case field_def.type
|
|
15
|
+
when :toggle
|
|
16
|
+
return render_show_toggle(record, field_def.name)
|
|
15
17
|
when :markdown
|
|
16
18
|
rendered =
|
|
17
19
|
if defined?(::MarkdownRenderer)
|
|
@@ -19,7 +21,7 @@ module AdminSuite
|
|
|
19
21
|
else
|
|
20
22
|
simple_format(value.to_s)
|
|
21
23
|
end
|
|
22
|
-
return content_tag(:div, rendered, class: "prose
|
|
24
|
+
return content_tag(:div, rendered, class: "prose max-w-none")
|
|
23
25
|
when :json
|
|
24
26
|
begin
|
|
25
27
|
parsed =
|
|
@@ -40,8 +42,13 @@ module AdminSuite
|
|
|
40
42
|
# If the field isn't in the form config, fall back to index column config
|
|
41
43
|
# so show pages can still render labels consistently.
|
|
42
44
|
if respond_to?(:resource_config, true) && (rc = resource_config) && rc.index_config&.columns_list
|
|
43
|
-
col = rc.index_config.columns_list.find
|
|
44
|
-
|
|
45
|
+
col = rc.index_config.columns_list.find do |c|
|
|
46
|
+
c.name.to_sym == field_name.to_sym || c.toggle_field&.to_sym == field_name.to_sym
|
|
47
|
+
end
|
|
48
|
+
if col&.type == :toggle
|
|
49
|
+
toggle_field = (col.toggle_field || col.name).to_sym
|
|
50
|
+
return render_show_toggle(record, toggle_field)
|
|
51
|
+
elsif col&.type == :label
|
|
45
52
|
label_value = col.content.is_a?(Proc) ? col.content.call(record) : value
|
|
46
53
|
return render_label_badge(label_value, color: col.label_color, size: col.label_size, record: record)
|
|
47
54
|
end
|
|
@@ -65,6 +72,27 @@ module AdminSuite
|
|
|
65
72
|
|
|
66
73
|
super
|
|
67
74
|
end
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
def render_show_toggle(record, field)
|
|
79
|
+
toggle_url =
|
|
80
|
+
begin
|
|
81
|
+
resource_toggle_path(
|
|
82
|
+
portal: current_portal,
|
|
83
|
+
resource_name: resource_name,
|
|
84
|
+
id: record.to_param,
|
|
85
|
+
field: field
|
|
86
|
+
)
|
|
87
|
+
rescue StandardError
|
|
88
|
+
nil
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
render(
|
|
92
|
+
partial: "admin_suite/shared/toggle_cell",
|
|
93
|
+
locals: { record: record, field: field, toggle_url: toggle_url }
|
|
94
|
+
)
|
|
95
|
+
end
|
|
68
96
|
end
|
|
69
97
|
end
|
|
70
98
|
end
|
data/lib/admin_suite/version.rb
CHANGED
data/lib/admin_suite.rb
CHANGED
|
@@ -53,5 +53,36 @@ module AdminSuite
|
|
|
53
53
|
def portal_definitions
|
|
54
54
|
PortalRegistry.all
|
|
55
55
|
end
|
|
56
|
+
|
|
57
|
+
# Defines (or updates) the root dashboard shown at the engine root (`/`).
|
|
58
|
+
#
|
|
59
|
+
# This uses the same dashboard DSL as portal dashboards.
|
|
60
|
+
#
|
|
61
|
+
# Host apps typically place these in:
|
|
62
|
+
# - `config/admin_suite/dashboard.rb` (recommended; not a Zeitwerk autoload path)
|
|
63
|
+
# - `app/admin_suite/dashboard.rb` (supported; AdminSuite ignores for Zeitwerk)
|
|
64
|
+
#
|
|
65
|
+
# @yield Dashboard definition DSL
|
|
66
|
+
# @return [AdminSuite::UI::DashboardDefinition]
|
|
67
|
+
def root_dashboard(&block)
|
|
68
|
+
config.root_dashboard_definition ||= UI::DashboardDefinition.new
|
|
69
|
+
UI::DashboardDSL.new(config.root_dashboard_definition).instance_eval(&block) if block_given?
|
|
70
|
+
config.root_dashboard_definition
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# @return [AdminSuite::UI::DashboardDefinition, nil]
|
|
74
|
+
def root_dashboard_definition
|
|
75
|
+
config.root_dashboard_definition
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Clears the root dashboard definition (useful for development reloads).
|
|
79
|
+
#
|
|
80
|
+
# @return [void]
|
|
81
|
+
def reset_root_dashboard!
|
|
82
|
+
config.root_dashboard_definition = nil
|
|
83
|
+
config.root_dashboard_loaded = false
|
|
84
|
+
rescue StandardError
|
|
85
|
+
# best-effort
|
|
86
|
+
end
|
|
56
87
|
end
|
|
57
88
|
end
|
|
@@ -14,6 +14,11 @@ AdminSuite.configure do |config|
|
|
|
14
14
|
# config.authorize = ->(actor, action:, subject:, resource:, controller:) { true }
|
|
15
15
|
config.authorize = nil
|
|
16
16
|
|
|
17
|
+
# Optional sign-out action in the topbar.
|
|
18
|
+
# config.logout_path = ->(view) { view.main_app.logout_path }
|
|
19
|
+
# config.logout_method = :delete
|
|
20
|
+
# config.logout_label = "Log out"
|
|
21
|
+
|
|
17
22
|
# Resource definition file globs (host app can override).
|
|
18
23
|
config.resource_globs = [
|
|
19
24
|
Rails.root.join("config/admin_suite/resources/*.rb").to_s,
|
|
@@ -1,13 +1,69 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "test_helper"
|
|
4
|
+
require "tmpdir"
|
|
4
5
|
|
|
5
6
|
module AdminSuite
|
|
6
7
|
class DashboardTest < ActionDispatch::IntegrationTest
|
|
7
8
|
test "GET /internal/admin_suite renders dashboard" do
|
|
8
9
|
get "/internal/admin_suite"
|
|
9
10
|
assert_response :success
|
|
10
|
-
assert_includes response.body, "
|
|
11
|
+
assert_includes response.body, "Admin Suite"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
test "loads custom root dashboard definition from dashboard_globs" do
|
|
15
|
+
old_globs = AdminSuite.config.dashboard_globs
|
|
16
|
+
old_title = AdminSuite.config.root_dashboard_title
|
|
17
|
+
old_description = AdminSuite.config.root_dashboard_description
|
|
18
|
+
|
|
19
|
+
Dir.mktmpdir("admin-suite-dashboard") do |dir|
|
|
20
|
+
dashboard_rb = File.join(dir, "dashboard.rb")
|
|
21
|
+
|
|
22
|
+
File.write(dashboard_rb, <<~'RUBY')
|
|
23
|
+
# frozen_string_literal: true
|
|
24
|
+
|
|
25
|
+
AdminSuite.configure do |config|
|
|
26
|
+
config.root_dashboard_title = ->(controller) { "Custom Root #{controller.request.path}" }
|
|
27
|
+
config.root_dashboard_description = ->(_controller) { "Custom root description" }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
AdminSuite.root_dashboard do
|
|
31
|
+
row do
|
|
32
|
+
stat_panel "Custom A", -> { 11 }, span: 6, variant: :mini, color: :slate
|
|
33
|
+
stat_panel "Custom B", 22, span: 6, variant: :mini, color: :slate
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
RUBY
|
|
37
|
+
|
|
38
|
+
AdminSuite.reset_root_dashboard!
|
|
39
|
+
AdminSuite.config.dashboard_globs = [File.join(dir, "*.rb")]
|
|
40
|
+
|
|
41
|
+
get "/internal/admin_suite"
|
|
42
|
+
assert_response :success
|
|
43
|
+
|
|
44
|
+
# Title + description should come from the dashboard definition file.
|
|
45
|
+
assert_includes response.body, "Custom Root /internal/admin_suite"
|
|
46
|
+
assert_includes response.body, "Custom root description"
|
|
47
|
+
|
|
48
|
+
# DSL-driven panels should render.
|
|
49
|
+
assert_includes response.body, "Custom A"
|
|
50
|
+
assert_includes response.body, ">11<"
|
|
51
|
+
assert_includes response.body, "Custom B"
|
|
52
|
+
assert_includes response.body, ">22<"
|
|
53
|
+
|
|
54
|
+
# Ensure spans are emitted (used to drive column layout).
|
|
55
|
+
assert_includes response.body, "admin-suite-dashboard-row"
|
|
56
|
+
assert_equal 2, response.body.scan("grid-column: span 6 / span 6;").size
|
|
57
|
+
|
|
58
|
+
# Loader should mark the dashboard as loaded in non-dev envs (test).
|
|
59
|
+
assert AdminSuite.config.root_dashboard_loaded
|
|
60
|
+
assert AdminSuite.root_dashboard_definition.present?
|
|
61
|
+
end
|
|
62
|
+
ensure
|
|
63
|
+
AdminSuite.config.dashboard_globs = old_globs
|
|
64
|
+
AdminSuite.config.root_dashboard_title = old_title
|
|
65
|
+
AdminSuite.config.root_dashboard_description = old_description
|
|
66
|
+
AdminSuite.reset_root_dashboard!
|
|
11
67
|
end
|
|
12
68
|
end
|
|
13
69
|
end
|
metadata
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: admin_suite
|
|
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
|
- TechWright Labs
|
|
8
|
+
autorequire:
|
|
8
9
|
bindir: bin
|
|
9
10
|
cert_chain: []
|
|
10
|
-
date:
|
|
11
|
+
date: 2026-02-14 00:00:00.000000000 Z
|
|
11
12
|
dependencies:
|
|
12
13
|
- !ruby/object:Gem::Dependency
|
|
13
14
|
name: rails
|
|
@@ -139,6 +140,7 @@ files:
|
|
|
139
140
|
- app/javascript/controllers/admin_suite/clipboard_controller.js
|
|
140
141
|
- app/javascript/controllers/admin_suite/code_editor_controller.js
|
|
141
142
|
- app/javascript/controllers/admin_suite/file_upload_controller.js
|
|
143
|
+
- app/javascript/controllers/admin_suite/flash_controller.js
|
|
142
144
|
- app/javascript/controllers/admin_suite/json_editor_controller.js
|
|
143
145
|
- app/javascript/controllers/admin_suite/live_filter_controller.js
|
|
144
146
|
- app/javascript/controllers/admin_suite/markdown_editor_controller.js
|
|
@@ -237,7 +239,6 @@ files:
|
|
|
237
239
|
- test/dummy/config/puma.rb
|
|
238
240
|
- test/dummy/config/routes.rb
|
|
239
241
|
- test/dummy/db/seeds.rb
|
|
240
|
-
- test/dummy/log/test.log
|
|
241
242
|
- test/dummy/public/400.html
|
|
242
243
|
- test/dummy/public/404.html
|
|
243
244
|
- test/dummy/public/406-unsupported-browser.html
|
|
@@ -247,7 +248,6 @@ files:
|
|
|
247
248
|
- test/dummy/public/icon.svg
|
|
248
249
|
- test/dummy/public/robots.txt
|
|
249
250
|
- test/dummy/test/test_helper.rb
|
|
250
|
-
- test/dummy/tmp/local_secret.txt
|
|
251
251
|
- test/fixtures/docs/progress/PROGRESS_REPORT.md
|
|
252
252
|
- test/integration/dashboard_test.rb
|
|
253
253
|
- test/integration/docs_test.rb
|
|
@@ -264,6 +264,7 @@ metadata:
|
|
|
264
264
|
rubygems_mfa_required: 'true'
|
|
265
265
|
source_code_uri: https://github.com/techwright-lab/admin_suite
|
|
266
266
|
changelog_uri: https://github.com/techwright-lab/admin_suite/blob/main/CHANGELOG.md
|
|
267
|
+
post_install_message:
|
|
267
268
|
rdoc_options: []
|
|
268
269
|
require_paths:
|
|
269
270
|
- lib
|
|
@@ -278,7 +279,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
278
279
|
- !ruby/object:Gem::Version
|
|
279
280
|
version: '0'
|
|
280
281
|
requirements: []
|
|
281
|
-
rubygems_version: 3.
|
|
282
|
+
rubygems_version: 3.5.22
|
|
283
|
+
signing_key:
|
|
282
284
|
specification_version: 4
|
|
283
285
|
summary: Reusable admin suite engine
|
|
284
286
|
test_files: []
|