From 2a0dfcefb14b30ac4d1ab52160fc082098fd71b5 Mon Sep 17 00:00:00 2001 From: Christopher Nilsson Date: Sun, 2 Feb 2025 15:32:39 +0100 Subject: [PATCH 1/3] feature: Add grouping for Domains - Allow a domain to have a :group. - When defining ash_admin routes, allow to filter on a domain group. - Test: For the ash_admin_test.exs to ensure the group is accessible. - Test: For verifying the behaviour on page_live. - Test/refactor: DomainA and DomainB, to easier test one against the other. --- config/config.exs | 3 +- lib/ash_admin/domain.ex | 46 ++++++++++++ lib/ash_admin/pages/page_live.ex | 11 ++- lib/ash_admin/router.ex | 24 +++++- test/ash_admin_test.exs | 74 +++++++++++++++++++ .../top_nav/helpers/dropdown_helper_test.exs | 12 +-- test/page_live_test.exs | 23 ++++++ test/support/{domain.ex => domain_a.ex} | 2 +- test/support/domain_b.ex | 11 +++ test/support/resources/blog.ex | 2 +- test/support/resources/comment.ex | 2 +- test/support/resources/post.ex | 2 +- test/support/router.ex | 6 ++ 13 files changed, 203 insertions(+), 15 deletions(-) rename test/support/{domain.ex => domain_a.ex} (90%) create mode 100644 test/support/domain_b.ex diff --git a/config/config.exs b/config/config.exs index 53cd51f1..a2d31109 100644 --- a/config/config.exs +++ b/config/config.exs @@ -77,6 +77,7 @@ if config_env() == :test do config :ash_admin, ash_domains: [ - AshAdmin.Test.Domain + AshAdmin.Test.DomainA, + AshAdmin.Test.DomainB ] end diff --git a/lib/ash_admin/domain.ex b/lib/ash_admin/domain.ex index a01f4d40..0d158bf9 100644 --- a/lib/ash_admin/domain.ex +++ b/lib/ash_admin/domain.ex @@ -29,6 +29,18 @@ defmodule AshAdmin.Domain do default: [], doc: "Humanized names for each resource group to appear in the admin area. These will be used as labels in the top navigation dropdown and will be shown sorted as given. If a key for a group does not appear in this mapping, the label will not be rendered." + ], + group: [ + type: :atom, + default: nil, + doc: """ + The group for filtering multiple admin dashboards. When set, this domain will only appear + in admin routes that specify a matching group option. If not set (nil), the domain will + only appear in admin routes without group filtering. + + Example: + group :sub_app # This domain will only show up in routes with group: :sub_app + """ ] ] } @@ -39,6 +51,36 @@ defmodule AshAdmin.Domain do @moduledoc """ A domain extension to alter the behavior of a domain in the admin UI. + + ## Group-based Filtering + + Domains can be assigned to groups using the `group` option in the admin configuration. + This allows you to create multiple admin dashboards, each showing only the domains that belong + to a specific group. + + ### Example + + ```elixir + defmodule MyApp.SomeFeatureDomain do + use Ash.Domain, + extensions: [AshAdmin.Domain] + + admin do + show? true + group :sub_app # This domain will only appear in admin routes with group: :sub_app + end + + # ... rest of domain configuration + end + ``` + + Then in your router: + ```elixir + ash_admin "/sub_app/admin", group: :sub_app # Will only show domains with group: :sub_app + ``` + + Note: If you add a group filter to your admin route but haven't set the corresponding group + in your domains' admin configuration, those domains won't appear in the admin interface. """ def name(domain) do @@ -61,6 +103,10 @@ defmodule AshAdmin.Domain do Spark.Dsl.Extension.get_opt(domain, [:admin], :resource_group_labels, [], true) end + def group(domain) do + Spark.Dsl.Extension.get_opt(domain, [:admin], :group, nil, true) + end + defp default_name(domain) do split = domain |> Module.split() diff --git a/lib/ash_admin/pages/page_live.ex b/lib/ash_admin/pages/page_live.ex index d9cf0179..52ede47b 100644 --- a/lib/ash_admin/pages/page_live.ex +++ b/lib/ash_admin/pages/page_live.ex @@ -25,7 +25,7 @@ defmodule AshAdmin.PageLive do socket ) do otp_app = socket.endpoint.config(:otp_app) - + group = session["group"] prefix = case prefix do "/" -> @@ -39,7 +39,7 @@ defmodule AshAdmin.PageLive do socket = assign(socket, :prefix, prefix) - domains = domains(otp_app) + domains = domains(otp_app) |> filter_domains_by_group(group) {:ok, socket @@ -113,6 +113,13 @@ defmodule AshAdmin.PageLive do |> Enum.filter(&AshAdmin.Domain.show?/1) end + defp filter_domains_by_group(domains, nil), do: domains + defp filter_domains_by_group(domains, group) do + Enum.filter(domains, fn domain -> + AshAdmin.Domain.group(domain) == group + end) + end + defp assign_domain(socket, domain) do domain = Enum.find(socket.assigns.domains, fn shown_domain -> diff --git a/lib/ash_admin/router.ex b/lib/ash_admin/router.ex index 077a3569..d4f6a1f5 100644 --- a/lib/ash_admin/router.ex +++ b/lib/ash_admin/router.ex @@ -60,6 +60,18 @@ defmodule AshAdmin.Router do * `:live_session_name` - Optional atom to name the `live_session`. Defaults to `:ash_admin`. + * `:group` - Optional atom to filter domains by group. Only domains with a matching group will be shown. + For example: `group: :sub_app` will only show domains with `group: :sub_app` in their admin configuration. + Note: If you specify a group here but haven't set that group in any domain's admin configuration, + the admin interface will appear empty. Make sure to configure the group in your domains: + ```elixir + # In your domain: + admin do + show? true + group :sub_app + end + ``` + ## Examples defmodule MyAppWeb.Router do use Phoenix.Router @@ -71,7 +83,9 @@ defmodule AshAdmin.Router do # If you don't have one, see `admin_browser_pipeline/1` pipe_through [:browser] - ash_admin "/admin" + # Default route - shows all domains that don't have a group set + ash_admin "/admin" # Shows all domains with no group filter + ash_admin "/sub_app/admin", group: :sub_app # Only shows domains with group: :sub_app ash_admin "/csp/admin", live_session_name: :ash_admin_csp, csp_nonce_assign_key: :csp_nonce_value end end @@ -100,7 +114,13 @@ defmodule AshAdmin.Router do live_session opts[:live_session_name] || :ash_admin, on_mount: List.wrap(opts[:on_mount]), session: - {AshAdmin.Router, :__session__, [%{"prefix" => path}, List.wrap(opts[:session])]}, + {AshAdmin.Router, :__session__, [ + Map.merge( + %{"prefix" => path}, + if(opts[:group], do: %{"group" => opts[:group]}, else: %{}) + ), + List.wrap(opts[:session]) + ]}, root_layout: {AshAdmin.Layouts, :root} do live( "#{path}/*route", diff --git a/test/ash_admin_test.exs b/test/ash_admin_test.exs index a126b81f..ee383f3b 100644 --- a/test/ash_admin_test.exs +++ b/test/ash_admin_test.exs @@ -92,4 +92,78 @@ defmodule AshAdmin.Test.AshAdminTest do end ) end + + describe "domain grouping" do + test "domains without group return nil" do + defmodule DomainNoGroup do + @moduledoc false + use Ash.Domain, + extensions: [AshAdmin.Domain] + + admin do + show? true + end + + resources do + resource(AshAdmin.Test.Post) + end + end + + assert AshAdmin.Domain.group(DomainNoGroup) == nil + end + + test "domains with group return their group value" do + defmodule DomainWithGroup do + @moduledoc false + use Ash.Domain, + extensions: [AshAdmin.Domain] + + admin do + show? true + group :sub_app + end + + resources do + resource(AshAdmin.Test.Post) + end + end + + assert AshAdmin.Domain.group(DomainWithGroup) == :sub_app + end + + test "multiple domains with same group are all visible" do + defmodule FirstGroupedDomain do + @moduledoc false + use Ash.Domain, + extensions: [AshAdmin.Domain] + + admin do + show? true + group :sub_app + end + + resources do + resource(AshAdmin.Test.Post) + end + end + + defmodule SecondGroupedDomain do + @moduledoc false + use Ash.Domain, + extensions: [AshAdmin.Domain] + + admin do + show? true + group :sub_app + end + + resources do + resource(AshAdmin.Test.Comment) + end + end + + assert AshAdmin.Domain.group(FirstGroupedDomain) == :sub_app + assert AshAdmin.Domain.group(SecondGroupedDomain) == :sub_app + end + end end diff --git a/test/components/top_nav/helpers/dropdown_helper_test.exs b/test/components/top_nav/helpers/dropdown_helper_test.exs index ff5d3ca5..8ff94c0d 100644 --- a/test/components/top_nav/helpers/dropdown_helper_test.exs +++ b/test/components/top_nav/helpers/dropdown_helper_test.exs @@ -7,27 +7,27 @@ defmodule AshAdmin.Test.Components.TopNav.Helpers.DropdownHelperTest do test "groups resources" do prefix = "/admin" current_resource = AshAdmin.Test.Post - domain = AshAdmin.Test.Domain + domain = AshAdmin.Test.DomainA blog_link = %{ active: false, group: :group_b, text: "Blog", - to: "/admin?domain=Test&resource=Blog" + to: "/admin?domain=DomainA&resource=Blog" } post_link = %{ active: true, group: :group_a, text: "Post", - to: "/admin?domain=Test&resource=Post" + to: "/admin?domain=DomainA&resource=Post" } comment_link = %{ active: false, group: nil, text: "Comment", - to: "/admin?domain=Test&resource=Comment" + to: "/admin?domain=DomainA&resource=Comment" } assert_unordered( @@ -39,7 +39,7 @@ defmodule AshAdmin.Test.Components.TopNav.Helpers.DropdownHelperTest do test "groups resources by given order from the domain" do prefix = "/admin" current_resource = AshAdmin.Test.Post - domain = AshAdmin.Test.Domain + domain = AshAdmin.Test.DomainA assert [ [%{group: :group_b, text: "Blog"} = _blog_link], @@ -51,7 +51,7 @@ defmodule AshAdmin.Test.Components.TopNav.Helpers.DropdownHelperTest do describe "dropdown_group_labels/3" do test "returns groups" do - domain = AshAdmin.Test.Domain + domain = AshAdmin.Test.DomainA assert [group_b: "Group B", group_a: "Group A", group_c: "Group C"] = DropdownHelper.dropdown_group_labels(domain) diff --git a/test/page_live_test.exs b/test/page_live_test.exs index 303eca55..63cb16b8 100644 --- a/test/page_live_test.exs +++ b/test/page_live_test.exs @@ -59,4 +59,27 @@ defmodule AshAdmin.Test.PageLiveTest do assert html =~ ~s| Plug.Test.init_test_session(%{}) + |> fetch_session() + |> put_session(:group, :group_b) + |> live("/api/sub_app/admin") + + # Should show only domains with group_b + assert html =~ "DomainB" + refute html =~ "DomainA" + + {:ok, _view, html} = + conn + |> live("/api/admin") + + # Should show only ungrouped domains + assert html =~ "DomainA" + assert html =~ "DomainB" # DomainB has group_b, so it shouldn't show up in ungrouped view + end + end end diff --git a/test/support/domain.ex b/test/support/domain_a.ex similarity index 90% rename from test/support/domain.ex rename to test/support/domain_a.ex index 912afe00..b2c224f5 100644 --- a/test/support/domain.ex +++ b/test/support/domain_a.ex @@ -1,4 +1,4 @@ -defmodule AshAdmin.Test.Domain do +defmodule AshAdmin.Test.DomainA do @moduledoc false use Ash.Domain, extensions: [AshAdmin.Domain] diff --git a/test/support/domain_b.ex b/test/support/domain_b.ex new file mode 100644 index 00000000..e26a95b3 --- /dev/null +++ b/test/support/domain_b.ex @@ -0,0 +1,11 @@ +defmodule AshAdmin.Test.DomainB do + @moduledoc false + use Ash.Domain, + extensions: [AshAdmin.Domain] + + admin do + show? true + group :group_b + end + +end diff --git a/test/support/resources/blog.ex b/test/support/resources/blog.ex index 35063a2b..a11e589c 100644 --- a/test/support/resources/blog.ex +++ b/test/support/resources/blog.ex @@ -1,7 +1,7 @@ defmodule AshAdmin.Test.Blog do @moduledoc false use Ash.Resource, - domain: AshAdmin.Test.Domain, + domain: AshAdmin.Test.DomainA, data_layer: Ash.DataLayer.Ets, extensions: [AshAdmin.Resource] diff --git a/test/support/resources/comment.ex b/test/support/resources/comment.ex index 55db028c..2c4193af 100644 --- a/test/support/resources/comment.ex +++ b/test/support/resources/comment.ex @@ -1,7 +1,7 @@ defmodule AshAdmin.Test.Comment do @moduledoc false use Ash.Resource, - domain: AshAdmin.Test.Domain, + domain: AshAdmin.Test.DomainA, data_layer: Ash.DataLayer.Ets attributes do diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 79a33c08..a7fbf1bf 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -1,7 +1,7 @@ defmodule AshAdmin.Test.Post do @moduledoc false use Ash.Resource, - domain: AshAdmin.Test.Domain, + domain: AshAdmin.Test.DomainA, data_layer: Ash.DataLayer.Ets, extensions: [AshAdmin.Resource] diff --git a/test/support/router.ex b/test/support/router.ex index 0c213750..bc7e596f 100644 --- a/test/support/router.ex +++ b/test/support/router.ex @@ -27,5 +27,11 @@ defmodule AshAdmin.Test.Router do live_session_name: :ash_admin_csp_full, csp_nonce_assign_key: csp_full ) + + # Test route for group-based admin panel + ash_admin("/sub_app/admin", + live_session_name: :ash_admin_sub_app, + group: :group_b + ) end end From 73caae51a8442cb5548c64ee92d7b856741d09b2 Mon Sep 17 00:00:00 2001 From: Christopher Nilsson Date: Thu, 29 May 2025 15:32:43 +0200 Subject: [PATCH 2/3] wip: Start adding cross domain tests --- test/cross_domain_relationships_test.exs | 61 ++++++++++++++++++++++++ test/support/domain_b.ex | 5 +- test/support/resources/author.ex | 28 +++++++++++ test/support/resources/post.ex | 6 +++ 4 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 test/cross_domain_relationships_test.exs create mode 100644 test/support/resources/author.ex diff --git a/test/cross_domain_relationships_test.exs b/test/cross_domain_relationships_test.exs new file mode 100644 index 00000000..73bc0aed --- /dev/null +++ b/test/cross_domain_relationships_test.exs @@ -0,0 +1,61 @@ +defmodule AshAdmin.Test.CrossDomainRelationshipsTest do + use ExUnit.Case, async: true + use Phoenix.ConnTest + + import Phoenix.LiveViewTest + + @endpoint AshAdmin.Test.Endpoint + + setup do + # Configure the domains in the application environment + Application.put_env(:ash_admin, :ash_domains, [ + AshAdmin.Test.DomainA, + AshAdmin.Test.DomainB + ]) + + # Create an author in DomainB + {:ok, author} = + AshAdmin.Test.Author + |> Ash.Changeset.for_create(:create, %{name: "Test Author"}) + |> Ash.create() + + # Create a post in DomainA that references the author + {:ok, post} = + AshAdmin.Test.Post + |> Ash.Changeset.for_create(:create, %{ + body: "Test Post", + author_id: author.id + }) + |> Ash.create() + + %{author: author, post: post} + end + + describe "cross-domain relationships" do + test "viewing a post shows its author from another domain", %{post: post} do + {:ok, view, _html} = + live( + build_conn(), + "/api/admin?domain=#{AshAdmin.Domain.name(AshAdmin.Test.DomainA)}&resource=Post&action_type=read&primary_key=#{post.id}" + ) + + # The view should show the post's details + assert has_element?(view, "[data-test-id='post-body']", post.body) + + # The view should show the relationship to the author + assert has_element?(view, "[data-test-id='relationship-author']") + end + + test "viewing relationships across domains respects domain boundaries", %{post: post} do + {:ok, view, _html} = + live( + build_conn(), + "/api/admin?domain=#{AshAdmin.Domain.name(AshAdmin.Test.DomainA)}&resource=Post&action_type=read&primary_key=#{post.id}" + ) + + # Verify that cross-domain relationship actions are properly handled + # This might mean certain actions are hidden or disabled + refute has_element?(view, "[data-test-id='edit-author-button']") + end + end +end diff --git a/test/support/domain_b.ex b/test/support/domain_b.ex index e26a95b3..020deb84 100644 --- a/test/support/domain_b.ex +++ b/test/support/domain_b.ex @@ -5,7 +5,10 @@ defmodule AshAdmin.Test.DomainB do admin do show? true - group :group_b + group(:group_b) end + resources do + resource(AshAdmin.Test.Author) + end end diff --git a/test/support/resources/author.ex b/test/support/resources/author.ex new file mode 100644 index 00000000..74cf3ede --- /dev/null +++ b/test/support/resources/author.ex @@ -0,0 +1,28 @@ +defmodule AshAdmin.Test.Author do + @moduledoc false + use Ash.Resource, + domain: AshAdmin.Test.DomainB, + data_layer: Ash.DataLayer.Ets, + extensions: [AshAdmin.Resource] + + attributes do + uuid_primary_key(:id) + + attribute :name, :string do + allow_nil?(false) + public?(true) + end + end + + actions do + defaults([:read, :update, :destroy]) + + create :create do + accept([:name]) + end + end + + admin do + resource_group(:group_b) + end +end diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index a7fbf1bf..938fff10 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -18,6 +18,12 @@ defmodule AshAdmin.Test.Post do end end + relationships do + belongs_to :author, AshAdmin.Test.Author do + public?(true) + end + end + actions do default_accept(:*) defaults(create: :*) From 2823d17cdf85b43c79357fb7184d6f92a619c0fd Mon Sep 17 00:00:00 2001 From: Christopher Nilsson Date: Fri, 30 May 2025 22:37:05 +0200 Subject: [PATCH 3/3] ensure the UI hides relationships from other groups on show --- lib/ash_admin/components/resource/info.ex | 8 ++++- .../components/resource/metadata_table.ex | 26 +++++++++++++-- lib/ash_admin/components/resource/resource.ex | 10 +++++- lib/ash_admin/components/resource/show.ex | 23 ++++++++++--- lib/ash_admin/components/resource/table.ex | 1 + lib/ash_admin/domain.ex | 32 +++++++++++++++++++ lib/ash_admin/pages/page_live.ex | 2 ++ 7 files changed, 93 insertions(+), 9 deletions(-) diff --git a/lib/ash_admin/components/resource/info.ex b/lib/ash_admin/components/resource/info.ex index 30e9b0e7..2373c278 100644 --- a/lib/ash_admin/components/resource/info.ex +++ b/lib/ash_admin/components/resource/info.ex @@ -7,12 +7,18 @@ defmodule AshAdmin.Components.Resource.Info do attr :resource, :any, required: true attr :domain, :any, required: true attr :prefix, :any, required: true + attr :current_group, :any, default: nil def info(assigns) do ~H"""
- +
""" end diff --git a/lib/ash_admin/components/resource/metadata_table.ex b/lib/ash_admin/components/resource/metadata_table.ex index 644f35ba..66bc27a8 100644 --- a/lib/ash_admin/components/resource/metadata_table.ex +++ b/lib/ash_admin/components/resource/metadata_table.ex @@ -71,6 +71,7 @@ defmodule AshAdmin.Components.Resource.MetadataTable do attr :resource, :any, required: true attr :domain, :any, required: true attr :prefix, :any, required: true + attr :current_group, :any, default: nil def relationship_table(assigns) do ~H""" @@ -98,9 +99,16 @@ defmodule AshAdmin.Components.Resource.MetadataTable do {relationship.type} <.td> - <.link navigate={"#{@prefix}?domain=#{AshAdmin.Domain.name(@domain)}&resource=#{AshAdmin.Resource.name(relationship.destination)}"}> - {AshAdmin.Resource.name(relationship.destination)} - + <%= %> + <%= if destination_domain_accessible?(relationship, @domain, @current_group) do %> + <.link navigate={"#{@prefix}?domain=#{AshAdmin.Domain.name(destination_domain(relationship, @domain))}&resource=#{AshAdmin.Resource.name(relationship.destination)}"}> + {AshAdmin.Resource.name(relationship.destination)} + + <% else %> + + {AshAdmin.Resource.name(relationship.destination)} (not accessible) + + <% end %> <.td class="max-w-sm min-w-sm text-gray-500"> {relationship.description} @@ -175,4 +183,16 @@ defmodule AshAdmin.Components.Resource.MetadataTable do |> Ash.Resource.Info.relationships() |> Enum.sort_by(&(not &1.public?)) end + + defp destination_domain(relationship, fallback \\ nil) do + Ash.Resource.Info.domain(relationship.destination) || fallback + end + + defp destination_domain_accessible?(relationship, domain, current_group) do + # Get the destination domain of the relationship + destination_domain = destination_domain(relationship, domain) + + # Use the helper function from AshAdmin.Domain to check accessibility + AshAdmin.Domain.domain_accessible_in_group?(destination_domain, current_group) + end end diff --git a/lib/ash_admin/components/resource/resource.ex b/lib/ash_admin/components/resource/resource.ex index ff54cb3e..d7eeb085 100644 --- a/lib/ash_admin/components/resource/resource.ex +++ b/lib/ash_admin/components/resource/resource.ex @@ -22,6 +22,7 @@ defmodule AshAdmin.Components.Resource do attr :prefix, :any, default: nil attr :action_type, :atom attr :polymorphic_actions, :any + attr :current_group, :any, default: nil def render(assigns) do ~H""" @@ -90,8 +91,15 @@ defmodule AshAdmin.Components.Resource do tenant={@tenant} table={@table} prefix={@prefix} + current_group={@current_group} + /> + - <.live_component :if={@action_type == :create} module={Form} diff --git a/lib/ash_admin/components/resource/show.ex b/lib/ash_admin/components/resource/show.ex index 3b07ac3f..da418844 100644 --- a/lib/ash_admin/components/resource/show.ex +++ b/lib/ash_admin/components/resource/show.ex @@ -17,6 +17,7 @@ defmodule AshAdmin.Components.Resource.Show do attr :tenant, :any attr :table, :any, required: true attr :prefix, :any, required: true + attr :current_group, :any, default: nil def render(assigns) do ~H""" @@ -142,11 +143,23 @@ defmodule AshAdmin.Components.Resource.Show do end defp render_relationships(assigns, _record, resource) do - assigns = assign(assigns, resource: resource) + # Filter relationships to only include those with accessible domains + accessible_relationships = + resource + |> AshAdmin.Components.Resource.Form.relationships(:show) + |> Enum.filter(fn relationship -> + AshAdmin.Domain.domain_accessible_in_group?( + Ash.Resource.Info.domain(relationship.destination), + assigns[:current_group] + ) + end) + + assigns = + assign(assigns, resource: resource, accessible_relationships: accessible_relationships) ~H"""
@@ -221,9 +234,10 @@ defmodule AshAdmin.Components.Resource.Show do cardinality: :one, name: name, destination: destination, - context: context, - domain: destination_domain + context: context }) do + destination_domain = Ash.Resource.Info.domain(destination) + case Map.get(record, name) do nil -> "None" @@ -283,6 +297,7 @@ defmodule AshAdmin.Components.Resource.Show do prefix={@prefix} skip={[@destination_attribute]} relationship_name={@relationship_name} + current_group={@current_group} />
""" diff --git a/lib/ash_admin/components/resource/table.ex b/lib/ash_admin/components/resource/table.ex index a822701f..f3ab91c6 100644 --- a/lib/ash_admin/components/resource/table.ex +++ b/lib/ash_admin/components/resource/table.ex @@ -20,6 +20,7 @@ defmodule AshAdmin.Components.Resource.Table do attr :show_sensitive_fields, :list, default: [] attr :actor, :any, default: nil attr :relationship_name, :atom, default: nil + attr :current_group, :any, default: nil def table(assigns) do ~H""" diff --git a/lib/ash_admin/domain.ex b/lib/ash_admin/domain.ex index 0d158bf9..db665717 100644 --- a/lib/ash_admin/domain.ex +++ b/lib/ash_admin/domain.ex @@ -79,6 +79,13 @@ defmodule AshAdmin.Domain do ash_admin "/sub_app/admin", group: :sub_app # Will only show domains with group: :sub_app ``` + You might need to define different `live_session_name` for the admin dashboards in your + router, depending on the group. For example: + + ```elixir + ash_admin "/sub_app/admin", group: :sub_app, live_session_name: :sub_app_admin + ``` + Note: If you add a group filter to your admin route but haven't set the corresponding group in your domains' admin configuration, those domains won't appear in the admin interface. """ @@ -107,6 +114,31 @@ defmodule AshAdmin.Domain do Spark.Dsl.Extension.get_opt(domain, [:admin], :group, nil, true) end + @doc """ + Checks if a destination domain is accessible from the current group context. + + Returns true if: + - No group filtering is active (current_group is nil) + - The destination domain belongs to the same group as current_group + - The destination domain has no group (nil) and current_group is also nil + """ + def domain_accessible_in_group?(destination_domain, current_group) do + destination_group = group(destination_domain) + + case {current_group, destination_group} do + # No group filtering, ungrouped domain + {nil, nil} -> true + # No group filtering, but domain has a group + {nil, _} -> false + # Same group + {group, group} -> true + # Group filtering active, but domain has no group + {_, nil} -> false + # Different groups + {_, _} -> false + end + end + defp default_name(domain) do split = domain |> Module.split() diff --git a/lib/ash_admin/pages/page_live.ex b/lib/ash_admin/pages/page_live.ex index 52ede47b..3aaeac7d 100644 --- a/lib/ash_admin/pages/page_live.ex +++ b/lib/ash_admin/pages/page_live.ex @@ -47,6 +47,7 @@ defmodule AshAdmin.PageLive do |> assign(:primary_key, nil) |> assign(:record, nil) |> assign(:domains, domains) + |> assign(:current_group, group) |> assign(:tenant, session["tenant"]) |> assign(:editing_tenant, false) |> then(fn socket -> @@ -103,6 +104,7 @@ defmodule AshAdmin.PageLive do tables={@tables} polymorphic_actions={@polymorphic_actions} prefix={@prefix} + current_group={@current_group} /> """ end