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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/admin_suite.css +129 -0
  3. data/app/controllers/admin_suite/application_controller.rb +31 -0
  4. data/app/controllers/admin_suite/dashboard_controller.rb +59 -226
  5. data/app/controllers/admin_suite/resources_controller.rb +14 -4
  6. data/app/helpers/admin_suite/base_helper.rb +147 -108
  7. data/app/helpers/admin_suite/panels_helper.rb +1 -1
  8. data/app/javascript/controllers/admin_suite/file_upload_controller.js +9 -9
  9. data/app/javascript/controllers/admin_suite/flash_controller.js +45 -0
  10. data/app/javascript/controllers/admin_suite/json_editor_controller.js +8 -8
  11. data/app/javascript/controllers/admin_suite/searchable_select_controller.js +2 -2
  12. data/app/javascript/controllers/admin_suite/tag_select_controller.js +1 -1
  13. data/app/javascript/controllers/admin_suite/toggle_switch_controller.js +25 -16
  14. data/app/views/admin_suite/dashboard/index.html.erb +6 -15
  15. data/app/views/admin_suite/panels/_cards.html.erb +6 -6
  16. data/app/views/admin_suite/panels/_chart.html.erb +12 -12
  17. data/app/views/admin_suite/panels/_health.html.erb +14 -14
  18. data/app/views/admin_suite/panels/_recent.html.erb +11 -11
  19. data/app/views/admin_suite/panels/_stat.html.erb +24 -24
  20. data/app/views/admin_suite/panels/_table.html.erb +10 -10
  21. data/app/views/admin_suite/portals/show.html.erb +1 -1
  22. data/app/views/admin_suite/resources/_form.html.erb +1 -1
  23. data/app/views/admin_suite/resources/edit.html.erb +4 -4
  24. data/app/views/admin_suite/resources/index.html.erb +23 -23
  25. data/app/views/admin_suite/resources/new.html.erb +4 -4
  26. data/app/views/admin_suite/resources/show.html.erb +17 -17
  27. data/app/views/admin_suite/shared/_flash.html.erb +15 -2
  28. data/app/views/admin_suite/shared/_form.html.erb +8 -8
  29. data/app/views/admin_suite/shared/_json_editor_field.html.erb +4 -4
  30. data/app/views/admin_suite/shared/_sidebar.html.erb +4 -4
  31. data/app/views/admin_suite/shared/_toggle_cell.html.erb +4 -2
  32. data/app/views/admin_suite/shared/_topbar.html.erb +14 -1
  33. data/app/views/layouts/admin_suite/application.html.erb +4 -4
  34. data/docs/configuration.md +55 -0
  35. data/docs/portals.md +42 -0
  36. data/lib/admin_suite/configuration.rb +16 -0
  37. data/lib/admin_suite/engine.rb +9 -0
  38. data/lib/admin_suite/ui/field_renderer_registry.rb +2 -2
  39. data/lib/admin_suite/ui/form_field_renderer.rb +2 -2
  40. data/lib/admin_suite/ui/show_formatter_registry.rb +5 -5
  41. data/lib/admin_suite/ui/show_value_formatter.rb +31 -3
  42. data/lib/admin_suite/version.rb +1 -1
  43. data/lib/admin_suite.rb +31 -0
  44. data/lib/generators/admin_suite/install/templates/admin_suite.rb +5 -0
  45. data/test/integration/dashboard_test.rb +57 -1
  46. metadata +7 -5
  47. data/test/dummy/log/test.log +0 -624
  48. data/test/dummy/tmp/local_secret.txt +0 -1
@@ -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 dark:prose-invert max-w-none")
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 dark:prose-invert max-w-none")
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 dark:text-slate-400")) if field.help.present?
42
- concat(content_tag(:p, resource.errors[field.name].first, class: "mt-1 text-sm text-red-600 dark:text-red-400")) if resource.errors[field.name].any?
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 dark:text-green-400 font-medium"))
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 dark:text-slate-400 text-xs"))
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 dark:text-slate-400 text-xs"))
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 dark:text-indigo-400")
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 dark:bg-slate-700 text-slate-700 dark:text-slate-300"))
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 dark:prose-invert max-w-none")
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 { |c| c.name.to_sym == field_name.to_sym }
44
- if col&.type == :label
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
@@ -2,7 +2,7 @@
2
2
 
3
3
  module AdminSuite
4
4
  module Version
5
- VERSION = "0.2.1"
5
+ VERSION = "0.2.3"
6
6
  end
7
7
 
8
8
  # Backward-compatible constant.
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, "Developer Portal"
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.1
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: 1980-01-02 00:00:00.000000000 Z
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.6.9
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: []