Coder Social home page Coder Social logo

tfwright / live_admin Goto Github PK

View Code? Open in Web Editor NEW
223.0 11.0 22.0 3.24 MB

Low-config admin UI for Phoenix apps, built on LiveView

License: MIT License

Elixir 85.27% Dockerfile 0.28% JavaScript 4.44% HTML 0.71% CSS 9.30%
phoenix-framework liveview

live_admin's People

Contributors

christiantovar avatar dependabot[bot] avatar harmon25 avatar tfwright avatar tmaszk avatar vasspilka avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

live_admin's Issues

Error when not using prefix - function nil.all/2 is undefined

After generating an empty Phoenix app, generating a single resource, and adding live_admin, I get the following error.

[error] GenServer #PID<0.768.0> terminating
** (UndefinedFunctionError) function nil.all/2 is undefined
    nil.all(#Ecto.Query<from p0 in LiveAdminIssue.Posts.Post, order_by: [asc: p0.id], limit: 10, offset: ^0>, [prefix: nil])
    (live_admin 0.10.1) lib/live_admin/resource.ex:233: LiveAdmin.Resource.build_list/3
    (live_admin 0.10.1) lib/live_admin/components/resource/index.ex:22: LiveAdmin.Components.Container.Index.update/2
    (phoenix_live_view 0.19.5) lib/phoenix_live_view/utils.ex:487: Phoenix.LiveView.Utils.maybe_call_update!/3
    (phoenix_live_view 0.19.5) lib/phoenix_live_view/diff.ex:653: anonymous fn/5 in Phoenix.LiveView.Diff.render_pending_components/6
    (elixir 1.14.4) lib/enum.ex:2468: Enum."-reduce/3-lists^foldl/2-0-"/3
    (stdlib 4.3) maps.erl:411: :maps.fold_1/3
    (phoenix_live_view 0.19.5) lib/phoenix_live_view/diff.ex:629: Phoenix.LiveView.Diff.render_pending_components/6
    (phoenix_live_view 0.19.5) lib/phoenix_live_view/diff.ex:143: Phoenix.LiveView.Diff.render/3
    (phoenix_live_view 0.19.5) lib/phoenix_live_view/channel.ex:833: Phoenix.LiveView.Channel.render_diff/3
    (phoenix_live_view 0.19.5) lib/phoenix_live_view/channel.ex:469: Phoenix.LiveView.Channel.mount_handle_params_result/3
    (phoenix_live_view 0.19.5) lib/phoenix_live_view/channel.ex:1043: Phoenix.LiveView.Channel.verified_mount/8
    (phoenix_live_view 0.19.5) lib/phoenix_live_view/channel.ex:59: Phoenix.LiveView.Channel.handle_info/2
    (stdlib 4.3) gen_server.erl:1123: :gen_server.try_dispatch/4
    (stdlib 4.3) gen_server.erl:1200: :gen_server.handle_msg/6
    (stdlib 4.3) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
Last message: {Phoenix.Channel, %{"params" => %{"_csrf_token" => "USg1JhFiWSNWbxAnBnc2QyZCdQYtPEMFfOMmB7ksc_FmaAoti88Bl_zT", "_mounts" => 0}, "session" => "SFMyNTY.g2gDaAJhBXQAAAAIZAACaWRtAAAAFHBoeC1GM3Zmbk90MGwzQW9FUUxEZAAMbGl2ZV9zZXNzaW9uaAJkABRsaXZlX2FkbWluXy9teV9hZG1pbm4IAK_Omak333sXZAAKcGFyZW50X3BpZGQAA25pbGQACHJvb3RfcGlkZAADbmlsZAAJcm9vdF92aWV3ZAAlRWxpeGlyLkxpdmVBZG1pbi5Db21wb25lbnRzLkNvbnRhaW5lcmQABnJvdXRlcmQAH0VsaXhpci5MaXZlQWRtaW5Jc3N1ZVdlYi5Sb3V0ZXJkAAdzZXNzaW9udAAAAAZtAAAACWJhc2VfcGF0aG0AAAAJL215X2FkbWlubQAAAApjb21wb25lbnRzdAAAAAdkAARlZGl0ZAAqRWxpeGlyLkxpdmVBZG1pbi5Db21wb25lbnRzLkNvbnRhaW5lci5Gb3JtZAAEaG9tZWQAKEVsaXhpci5MaXZlQWRtaW4uQ29tcG9uZW50cy5Ib21lLkNvbnRlbnRkAARsaXN0ZAArRWxpeGlyLkxpdmVBZG1pbi5Db21wb25lbnRzLkNvbnRhaW5lci5JbmRleGQAA25hdmQAH0VsaXhpci5MaXZlQWRtaW4uQ29tcG9uZW50cy5OYXZkAANuZXdkACpFbGl4aXIuTGl2ZUFkbWluLkNvbXBvbmVudHMuQ29udGFpbmVyLkZvcm1kAAdzZXNzaW9uZAArRWxpeGlyLkxpdmVBZG1pbi5Db21wb25lbnRzLlNlc3Npb24uQ29udGVudGQABHZpZXdkACpFbGl4aXIuTGl2ZUFkbWluLkNvbXBvbmVudHMuQ29udGFpbmVyLlZpZXdtAAAACG9uX21vdW50ZAADbmlsbQAAAARyZXBvZAADbmlsbQAAAApzZXNzaW9uX2lkbQAAACQ5OWExZTk4Mi03ODgzLTQwMWYtOTA4MS0xYzYxNmIzOGIyZDFtAAAABXRpdGxlbQAAAAlMaXZlQWRtaW5kAAR2aWV3ZAAlRWxpeGlyLkxpdmVBZG1pbi5Db21wb25lbnRzLkNvbnRhaW5lcm4GAPEEgP6JAWIAAVGA.N94LdIa2duq87LJ1yga9XaB1d90IuFA9jROC8oWcKq8", "static" => "SFMyNTY.g2gDaAJhBXQAAAADZAAKYXNzaWduX25ld2pkAAVmbGFzaHQAAAAAZAACaWRtAAAAFHBoeC1GM3Zmbk90MGwzQW9FUUxEbgYA8gSA_okBYgABUYA.uDF11JrVTwNu5b3-QixRmXUnRCL9WumVkRpVQT5CW1Y", "url" => "http://localhost:4000/my_admin/posts"}, {#PID<0.761.0>, #Reference<0.2831961241.2145386497.34039>}, %Phoenix.Socket{assigns: %{}, channel: Phoenix.LiveView.Channel, channel_pid: nil, endpoint: LiveAdminIssueWeb.Endpoint, handler: Phoenix.LiveView.Socket, id: "users_sessions:yQS76IvwFkSKDoIUfXMVNSYYGantJAK7jA2jp2T0vSw=", joined: false, join_ref: "4", private: %{connect_info: %{session: %{"_csrf_token" => "7gxKSU2P50VJg6Y7OzMDAc9Q", "live_socket_id" => "users_sessions:yQS76IvwFkSKDoIUfXMVNSYYGantJAK7jA2jp2T0vSw=", "user_token" => <<201, 4, 187, 232, 139, 240, 22, 68, 138, 14, 130, 20, 125, 115, 21, 53, 38, 24, 25, 169, 237, 36, 2, 187, 140, 13, 163, 167, 100, 244, 189, 44>>}}}, pubsub_server: LiveAdminIssue.PubSub, ref: nil, serializer: Phoenix.Socket.V2.JSONSerializer, topic: "lv:phx-F3vfnOt0l3AoEQLD", transport: :websocket, transport_pid: #PID<0.761.0>}}
State: #Reference<0.2831961241.2145386497.34041>
[error] an exception was raised:
    ** (UndefinedFunctionError) function nil.all/2 is undefined
        nil.all(#Ecto.Query<from p0 in LiveAdminIssue.Posts.Post, order_by: [asc: p0.id], limit: 10, offset: ^0>, [prefix: nil])
        (live_admin 0.10.1) lib/live_admin/resource.ex:233: LiveAdmin.Resource.build_list/3
        (live_admin 0.10.1) lib/live_admin/components/resource/index.ex:22: LiveAdmin.Components.Container.Index.update/2
        (phoenix_live_view 0.19.5) lib/phoenix_live_view/utils.ex:487: Phoenix.LiveView.Utils.maybe_call_update!/3
        (phoenix_live_view 0.19.5) lib/phoenix_live_view/diff.ex:653: anonymous fn/5 in Phoenix.LiveView.Diff.render_pending_components/6
        (elixir 1.14.4) lib/enum.ex:2468: Enum."-reduce/3-lists^foldl/2-0-"/3
        (stdlib 4.3) maps.erl:411: :maps.fold_1/3
        (phoenix_live_view 0.19.5) lib/phoenix_live_view/diff.ex:629: Phoenix.LiveView.Diff.render_pending_components/6
        (phoenix_live_view 0.19.5) lib/phoenix_live_view/diff.ex:143: Phoenix.LiveView.Diff.render/3
        (phoenix_live_view 0.19.5) lib/phoenix_live_view/channel.ex:833: Phoenix.LiveView.Channel.render_diff/3
        (phoenix_live_view 0.19.5) lib/phoenix_live_view/channel.ex:469: Phoenix.LiveView.Channel.mount_handle_params_result/3
        (phoenix_live_view 0.19.5) lib/phoenix_live_view/channel.ex:1043: Phoenix.LiveView.Channel.verified_mount/8
        (phoenix_live_view 0.19.5) lib/phoenix_live_view/channel.ex:59: Phoenix.LiveView.Channel.handle_info/2
        (stdlib 4.3) gen_server.erl:1123: :gen_server.try_dispatch/4
        (stdlib 4.3) gen_server.erl:1200: :gen_server.handle_msg/6
        (stdlib 4.3) proc_lib.erl:240: :proc_lib.init_p_do_apply/3

You can see the example app here and how I installed live_admin in the associated git commit.

I was setting this up to investigate #63.

it's requiring phoenix_view

I am getting

** (CompileError) lib/live_admin/view.ex:4: module Phoenix.View is not loaded and could not be found

is'nt this supposed to be fully liveview?

[Bug] ** (UndefinedFunctionError) function nil.all/2 is undefined

Describe the bug

The code:

defmodule DocumentStore.UsersAdmin do
  use LiveAdmin.Resource, schema: DocumentStore.Accounts.User
end

The bug:

[error] GenServer #PID<0.843.0> terminating
** (UndefinedFunctionError) function nil.all/2 is undefined
    nil.all(#Ecto.Query<from c0 in DocumentStore.Entities.Client, order_by: [asc: c0.id], limit: 10, offset: ^0, preload: [:user]>, [prefix: nil])
    (live_admin 0.11.4) lib/live_admin/resource.ex:235: LiveAdmin.Resource.build_list/4
    (live_admin 0.11.4) lib/live_admin/components/resource/index.ex:25: LiveAdmin.Components.Container.Index.update/2
    (phoenix_live_view 0.20.4) lib/phoenix_live_view/utils.ex:498: Phoenix.LiveView.Utils.maybe_call_update!/3
    (elixir 1.16.0) lib/enum.ex:1700: Enum."-map/2-lists^map/1-1-"/2
    (phoenix_live_view 0.20.4) lib/phoenix_live_view/diff.ex:681: anonymous fn/4 in Phoenix.LiveView.Diff.render_pending_components/6
    (telemetry 1.2.1) /Users/danieljaouen/src/document_store/deps/telemetry/src/telemetry.erl:321: :telemetry.span/3
    (phoenix_live_view 0.20.4) lib/phoenix_live_view/diff.ex:676: anonymous fn/4 in Phoenix.LiveView.Diff.render_pending_components/6
    (stdlib 5.2) maps.erl:416: :maps.fold_1/4
    (phoenix_live_view 0.20.4) lib/phoenix_live_view/diff.ex:635: Phoenix.LiveView.Diff.render_pending_components/6
    (phoenix_live_view 0.20.4) lib/phoenix_live_view/diff.ex:143: Phoenix.LiveView.Diff.render/3
    (phoenix_live_view 0.20.4) lib/phoenix_live_view/channel.ex:953: anonymous fn/4 in Phoenix.LiveView.Channel.render_diff/3
    (telemetry 1.2.1) /Users/danieljaouen/src/document_store/deps/telemetry/src/telemetry.erl:321: :telemetry.span/3
    (phoenix_live_view 0.20.4) lib/phoenix_live_view/channel.ex:948: Phoenix.LiveView.Channel.render_diff/3
    (phoenix_live_view 0.20.4) lib/phoenix_live_view/channel.ex:575: Phoenix.LiveView.Channel.mount_handle_params_result/3
    (phoenix_live_view 0.20.4) lib/phoenix_live_view/channel.ex:1169: Phoenix.LiveView.Channel.verified_mount/8
    (phoenix_live_view 0.20.4) lib/phoenix_live_view/channel.ex:84: Phoenix.LiveView.Channel.handle_info/2
    (stdlib 5.2) gen_server.erl:1095: :gen_server.try_handle_info/3
    (stdlib 5.2) gen_server.erl:1183: :gen_server.handle_msg/6
    (stdlib 5.2) proc_lib.erl:241: :proc_lib.init_p_do_apply/3
Last message: {Phoenix.Channel, %{"params" => %{"_csrf_token" => "clip", "_mounts" => 0}, "session" => "clip", "static" => "clip", "url" => "http://localhost:4000/admin/clients"}, {#PID<0.830.0>, #Reference<clip>}, %Phoenix.Socket{assigns: %{}, channel: Phoenix.LiveView.Channel, channel_pid: nil, endpoint: DocumentStoreWeb.Endpoint, handler: Phoenix.LiveView.Socket, id: "users_sessions:clip=", joined: false, join_ref: "4", private: %{connect_info: %{session: %{"_csrf_token" => "clip-clip", "live_socket_id" => "users_sessions:clip=", "user_token" => <<clip>>}}}, pubsub_server: DocumentStore.PubSub, ref: nil, serializer: Phoenix.Socket.V2.JSONSerializer, topic: "lv:phx-clip", transport: :websocket, transport_pid: #PID<clip>}}
State: #Reference<clip>
[error] an exception was raised:
    ** (UndefinedFunctionError) function nil.all/2 is undefined
        nil.all(#Ecto.Query<from c0 in DocumentStore.Entities.Client, order_by: [asc: c0.id], limit: 10, offset: ^0, preload: [:user]>, [prefix: nil])
        (live_admin 0.11.4) lib/live_admin/resource.ex:235: LiveAdmin.Resource.build_list/4
        (live_admin 0.11.4) lib/live_admin/components/resource/index.ex:25: LiveAdmin.Components.Container.Index.update/2
        (phoenix_live_view 0.20.4) lib/phoenix_live_view/utils.ex:498: Phoenix.LiveView.Utils.maybe_call_update!/3
        (elixir 1.16.0) lib/enum.ex:1700: Enum."-map/2-lists^map/1-1-"/2
        (phoenix_live_view 0.20.4) lib/phoenix_live_view/diff.ex:681: anonymous fn/4 in Phoenix.LiveView.Diff.render_pending_components/6
        (telemetry 1.2.1) /Users/danieljaouen/src/document_store/deps/telemetry/src/telemetry.erl:321: :telemetry.span/3
        (phoenix_live_view 0.20.4) lib/phoenix_live_view/diff.ex:676: anonymous fn/4 in Phoenix.LiveView.Diff.render_pending_components/6
        (stdlib 5.2) maps.erl:416: :maps.fold_1/4
        (phoenix_live_view 0.20.4) lib/phoenix_live_view/diff.ex:635: Phoenix.LiveView.Diff.render_pending_components/6
        (phoenix_live_view 0.20.4) lib/phoenix_live_view/diff.ex:143: Phoenix.LiveView.Diff.render/3
        (phoenix_live_view 0.20.4) lib/phoenix_live_view/channel.ex:953: anonymous fn/4 in Phoenix.LiveView.Channel.render_diff/3
        (telemetry 1.2.1) /Users/danieljaouen/src/document_store/deps/telemetry/src/telemetry.erl:321: :telemetry.span/3
        (phoenix_live_view 0.20.4) lib/phoenix_live_view/channel.ex:948: Phoenix.LiveView.Channel.render_diff/3
        (phoenix_live_view 0.20.4) lib/phoenix_live_view/channel.ex:575: Phoenix.LiveView.Channel.mount_handle_params_result/3
        (phoenix_live_view 0.20.4) lib/phoenix_live_view/channel.ex:1169: Phoenix.LiveView.Channel.verified_mount/8
        (phoenix_live_view 0.20.4) lib/phoenix_live_view/channel.ex:84: Phoenix.LiveView.Channel.handle_info/2
        (stdlib 5.2) gen_server.erl:1095: :gen_server.try_handle_info/3
        (stdlib 5.2) gen_server.erl:1183: :gen_server.handle_msg/6
        (stdlib 5.2) proc_lib.erl:241: :proc_lib.init_p_do_apply/3
[debug] ** (Phoenix.Router.NoRouteError) no route found for GET /manifest.json (DocumentStoreWeb.Router)
    (document_store 0.1.0) deps/phoenix/lib/phoenix/router.ex:541: DocumentStoreWeb.Router.call/2
    (document_store 0.1.0) lib/document_store_web/endpoint.ex:1: DocumentStoreWeb.Endpoint.plug_builder_call/2
    (document_store 0.1.0) deps/plug/lib/plug/debugger.ex:136: DocumentStoreWeb.Endpoint."call (overridable 3)"/2
    (document_store 0.1.0) lib/document_store_web/endpoint.ex:1: DocumentStoreWeb.Endpoint.call/2
    (phoenix 1.7.11) lib/phoenix/endpoint/sync_code_reload_plug.ex:22: Phoenix.Endpoint.SyncCodeReloadPlug.do_call/4
    (bandit 1.2.0) lib/bandit/pipeline.ex:101: Bandit.Pipeline.call_plug/2
    (bandit 1.2.0) lib/bandit/pipeline.ex:22: Bandit.Pipeline.run/6
    (bandit 1.2.0) lib/bandit/http1/handler.ex:33: Bandit.HTTP1.Handler.handle_data/3
    (bandit 1.2.0) lib/bandit/delegating_handler.ex:18: Bandit.DelegatingHandler.handle_data/3
    (bandit 1.2.0) /Users/danieljaouen/src/document_store/deps/thousand_island/lib/thousand_island/handler.ex:379: Bandit.DelegatingHandler.handle_info/2
    (stdlib 5.2) gen_server.erl:1095: :gen_server.try_handle_info/3
    (stdlib 5.2) gen_server.erl:1183: :gen_server.handle_msg/6
    (stdlib 5.2) proc_lib.erl:241: :proc_lib.init_p_do_apply/3

Not sure what the problem is here. Any help?

live_admin route throws "invalid resource"

Describe the bug

Great library! I'm using the latest version 0.8.2.
I'm having a problem following the setup guide.
The live_admin route in the router from the README is not working. The lib is not picking up
the resource, giving me an "invalid resource" error, although it is an Ecto schema.

Using the syntax from the hex docs live_admin "/admin", resources: [MyApp.SomeEctoSchema] works instead.

To Reproduce
Follow the small setup in the README in a new project

Environment:

  • OS: ubuntu 23
  • Browser: firefox
  • LiveAdmin: 0.8.2
  • LiveView: 0.19

Allow admin-instance resource config

See #33

Problems:

  1. right now default config must be defined at the app level, which makes it awkward to use a different set of default configs for different admin UIs (if more than 1 is desired for whatever reason).

  2. since overrides are likely to be desired for most resources, the router config is large/noisy (see this thread)

Proposed solution: deprecate app level default config, and add support for passing a module, which specifies all configuration, including resources:

defmodule MyApp.AdminA do
  use LiveAdmin, 
    ecto_repo: MyApp.RepoA, 
    create_with: {MyApp.AdminContext, :default_create, []},
    update_with: :schema_function_name,
    resources: :get_resources

    def get_resources, do: [MyApp.SchemaA]
end

defmodule MyApp.AdminB do
  use LiveAdmin, 
    ecto_repo: MyApp.RepoB,
    resources: [MyApp.SchemaB]
end

Alternatives:

  • extract resource level config into separate macro that would be called as part of a block, similar to how Phoenix routes work:
live_admin "/admin_url", title: "MyAdmin" do
  admin_resource "/a", MyApp.SchemaA, create_with: :create
  admin_resource "/b", MyApp.SchemaB, create_with: :something
end

It might be that these are two separate problems with config that need to be addressed separately.

Some README suggestions

I came to this project because the docs / setup for Kaffy were lacking and setup was taking too long.

So I have some suggestions to make suggestions for this lib better imho.

Suggestion One

Put

Finally, tell LiveAdmin what Ecto repo to use to run queries in your runtime.ex: config :live_admin, ecto_repo: MyApp.Repo

In a block like

Finally, tell LiveAdmin what Ecto repo to use to run queries in your runtime.ex:

config :live_admin, ecto_repo: MyApp.Repo

Suggestion two

You have this line of code in the README

    admin_resource "/my_schemas", MyApp.MyResource

It would really help to generate the first parameter dynamically, and for the second parameter specify that it is a schema, maybe have a comment above or any sort of thing to make it clearer.

# The second is the module for the Schemas, for instance the Ecto Schema

Suggestion three

putting the following in runtime.ex

config :live_admin, ecto_repo: MyApp.Repo

Will result in an error in development.

So like that should be fixed.


This is a great initiative and I want to see it succeed. I would love to submit PRs for the following items if you let me know how the README is generated. I don't want to spend time understanding how that happens right now.

Component Override Functions Arity/Signature Issue

Hi, thanks for building the library! We're trying to use the component override functionality and have passed in render methods similar to your example dev app, but for some reason render_edit/2 is being called instead of the expected render_edit/1.

When inspecting the arguments passed to the function, the first argument is always an empty map, while the second argument is the normal assigns map that's expected. Do you have any idea what could be causing that?

We're using the same pattern as:

 components: [
          new: {LiveAdmin.Components.Container, :render_new, []},
          edit: {LiveAdmin.Components.Container, :render_edit, []}
        ],

from dev.exs.

Add Bandit Support

There may be an issue with live_admin when switching to bandit. I get this when loading any live_admin page:

[error] GenServer #PID<0.1178.0> terminating
** (FunctionClauseError) no function clause matching in Bandit.HTTP1.Handler.handle_info/2
    (bandit 1.0.0) /myapp/deps/thousand_island/lib/thousand_island/handler.ex:5: Bandit.HTTP1.Handler.handle_info(:clear_flash, {%ThousandIsland.Socket{socket: #Port<0.34>, transport_module: ThousandIsland.Transports.TCP, read_timeout: 60000, silent_terminate_on_error: false, span: %ThousandIsland.Telemetry{span_name: :connection, telemetry_span_context: #Reference<0.227480821.3738697732.157881>, start_time: -576460716151695028, start_metadata: %{remote_port: 40614, remote_address: {127, 0, 0, 1}, telemetry_span_context: #Reference<0.227480821.3738697732.157881>, parent_telemetry_span_context: #Reference<0.227480821.3738697733.138759>}}}, %{opts: %{websocket: [], http_1: [], http_2: []}, plug: {Phoenix.Endpoint.SyncCodeReloadPlug, {PetalProWeb.Endpoint, []}}, handler_module: Bandit.HTTP1.Handler, http_1_enabled: true, http_2_enabled: true, websocket_enabled: true, requests_processed: 2}})
    (stdlib 5.1.1) gen_server.erl:1077: :gen_server.try_handle_info/3
    (stdlib 5.1.1) gen_server.erl:1165: :gen_server.handle_msg/6
    (stdlib 5.1.1) proc_lib.erl:241: :proc_lib.init_p_do_apply/3
Last message: :clear_flash
State: {%ThousandIsland.Socket{socket: #Port<0.34>, transport_module: ThousandIsland.Transports.TCP, read_timeout: 60000, silent_terminate_on_error: false, span: %ThousandIsland.Telemetry{span_name: :connection, telemetry_span_context: #Reference<0.227480821.3738697732.157881>, start_time: -576460716151695028, start_metadata: %{remote_port: 40614, remote_address: {127, 0, 0, 1}, telemetry_span_context: #Reference<0.227480821.3738697732.157881>, parent_telemetry_span_context: #Reference<0.227480821.3738697733.138759>}}}, %{opts: %{websocket: [], http_1: [], http_2: []}, plug: {Phoenix.Endpoint.SyncCodeReloadPlug, {PetalProWeb.Endpoint, []}}, handler_module: Bandit.HTTP1.Handler, http_1_enabled: true, http_2_enabled: true, websocket_enabled: true, requests_processed: 2}}

I'm using live_admin 10.2 modified to support live_view 0.20.x.

Here is the description of the issue: mtrudel/bandit#141

And proposed solution: mtrudel/bandit#141 (comment)

Hope that is helpful.

Can't assign belongs to relationship value via new or edit forms

Describe the bug
Thanks for the package. I have two schemas, user and conversation, where conversation belongs to user

defmodule Grayrock.Users.User do
  use Ecto.Schema
  use LiveAdmin.Resource
  import Ecto.Changeset
  @primary_key {:id, :binary_id, autogenerate: true}
  @foreign_key_type :binary_id
  @derive {
    Flop.Schema,
    default_limit: 20,
    filterable: [:search_phrase, :email, :confirmed_at, :inserted_at],
    sortable: [:email, :confirmed_at, :inserted_at],
    compound_fields: [search_phrase: [:email]]
  }
  schema "users" do
    field :email, :string
    ...
  end

(boilerplate from https://livesaaskit.com)

defmodule MyApp.Chatbot.Conversation do
  use Ecto.Schema
  use LiveAdmin.Resource
  import Ecto.Changeset

  @primary_key {:id, :binary_id, autogenerate: true}
  @foreign_key_type :binary_id
  schema "chatbot_conversations" do
    ...
    belongs_to :user, Grayrock.Users.User

I am unable to assign a value to the user field via the conversation form (see video)

Screen.Recording.2023-12-02.at.9.47.09.PM.mov

To Reproduce
Steps to reproduce the behavior:

  1. Setup app / database with above schemas and panel following readme instructions
  2. Visit conversation form
  3. Go create or edit a conversation
  4. Attempt to set the user field
  5. See video

Expected behavior
Something would show in these places:

Screenshot 2023-12-02 at 9 55 58 PM Screenshot 2023-12-02 at 9 56 06 PM

Environment:

  • Elixir 1.15.7
  • {:live_admin, "~> 0.11.2"},
  • Phoenix 1.7.9
  • macOS 13.4.1
  • safari 16.5.2

Additional Context
No errors shown in phoenix server logs or browser js console, everything else seems to work as expected

"New" button is still rendered with create_with: nil or create_with: false

Describe the bug
Using v0.11.0, disabling certain verbs no longer works. For instance, use LiveAdmin.Resource, create_with: false or use LiveAdmin.Resource, create_with: nil does not remove the "New" button from the container component.

After some snooping, I wonder if it's because this line changed recently (appearing to have fallbacks that are overriding a falsey value, which is the method that the container relies upon for conditional rendering).

The example app shows create_with: nil being used though, and container.ex compares explicitly to false here, so that might have changed recently too.

To Reproduce
Simply insert use LiveAdmin.Resource, create_with: false or use LiveAdmin.Resource, create_with: nil, and "New" will still be clickable.

In iex, this is what's returned for either value above:

iex(1)> MyResource.__live_admin_config__(:create_with)
nil

Since container.ex is checking for false, that will always be true.

Expected behavior
I expect "New" to be hidden when create_with: false or create_with: nil is used.

Environment:

  • OS: Mac OS Ventura 13.2.1
  • Browser: Arc (Chromium)
  • Version: 1.11.0

Batch operations

See #30 (comment)

  • list rows are selectable
  • actions can be applied to all selected rows
  • select all checkbox selects all currently visible rows
  • Progress with toast (stretch: incremental)

function xxx.__live_admin_config__/1 is undefined or private

I'm trying to use the most basic configurations of the new config dynamic, but I can't seem to understand what I'm missing.

[error] #PID<0.4633.0> running Phoenix.Endpoint.SyncCodeReloadPlug (connection #PID<0.4185.0>, stream id 10) terminated
Server: localhost:4000 (http)
Request: GET /admin/min
** (exit) an exception was raised:
    ** (UndefinedFunctionError) function MyApp.Accounts.User.__live_admin_config__/1 is undefined or private
        (petal_pro 1.5.2) MyApp.Accounts.User.__live_admin_config__(:schema)
        (live_admin 0.9.0) lib/live_admin/components/nav.ex:33: anonymous fn/2 in LiveAdmin.Components.Nav.render_dropdowns/1
        (elixir 1.14.4) lib/enum.ex:2468: Enum."-reduce/3-lists^foldl/2-0-"/3
        (live_admin 0.9.0) lib/live_admin/components/nav.ex:31: LiveAdmin.Components.Nav.render_dropdowns/1
        (live_admin 0.9.0) lib/live_admin/components/nav.ex:18: anonymous fn/2 in LiveAdmin.Components.Nav.render/1
        (phoenix_live_view 0.19.3) lib/phoenix_live_view/diff.ex:386: Phoenix.LiveView.Diff.traverse/7
        (phoenix_live_view 0.19.3) lib/phoenix_live_view/diff.ex:711: Phoenix.LiveView.Diff.render_component/9
        (phoenix_live_view 0.19.3) lib/phoenix_live_view/diff.ex:657: anonymous fn/5 in Phoenix.LiveView.Diff.render_pending_components/6
        (elixir 1.14.4) lib/enum.ex:2468: Enum."-reduce/3-lists^foldl/2-0-"/3
        (stdlib 4.3) maps.erl:411: :maps.fold_1/3
        (phoenix_live_view 0.19.3) lib/phoenix_live_view/diff.ex:629: Phoenix.LiveView.Diff.render_pending_components/6
        (phoenix_live_view 0.19.3) lib/phoenix_live_view/diff.ex:143: Phoenix.LiveView.Diff.render/3
        (phoenix_live_view 0.19.3) lib/phoenix_live_view/static.ex:252: Phoenix.LiveView.Static.to_rendered_content_tag/4
        (phoenix_live_view 0.19.3) lib/phoenix_live_view/static.ex:135: Phoenix.LiveView.Static.render/3
        (phoenix_live_view 0.19.3) lib/phoenix_live_view/controller.ex:39: Phoenix.LiveView.Controller.live_render/3
        (phoenix 1.7.6) lib/phoenix/router.ex:430: Phoenix.Router.__call__/5
        (petal_pro 1.5.2) lib/petal_pro_web/endpoint.ex:1: MyAppWeb.Endpoint.plug_builder_call/2
        (petal_pro 1.5.2) lib/petal_pro_web/endpoint.ex:1: MyAppWeb.Endpoint."call (overridable 3)"/2
        (petal_pro 1.5.2) deps/plug/lib/plug/debugger.ex:136: MyAppWeb.Endpoint."call (overridable 4)"/2
        (petal_pro 1.5.2) lib/petal_pro_web/endpoint.ex:1: MyAppWeb.Endpoint.call/2

Here is my config code. I don't think I'm passing in options as intended, but I couldn't tell how to do it from the documentation.

live_admin "/min" do
  admin_resource "/users", MyApp.Accounts.User

  admin_resource(
    "/profiles",
    {MyApp.Profiles.Profile,
      hidden_fields: [:history, :iterations, :versions, :uuid],
      immutable_fields: [:updated_at, :inserted_at, :display_ref, :id],
      label_with: :display_ref}
  )
end

Depedency Versions:

Elixir 1.14.4
  "live_admin": {:hex, :live_admin, "0.9.0", "aa9e34f014948de22e289c5c1cb735bd0be11843eca03b4b8af8517c1a3fee84", [:mix], [{:ecto, "~> 3.8", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.8", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:phoenix_ecto, "~> 4.4", [hex: :phoenix_ecto, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}], "hexpm", "5cf6ff9f3fec57fe91cb091b72eaebb114676f50abb18cfa980fc225f485cf35"},

  "phoenix": {:hex, :phoenix, "1.7.6", "61f0625af7c1d1923d582470446de29b008c0e07ae33d7a3859ede247ddaf59a", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "f6b4be7780402bb060cbc6e83f1b6d3f5673b674ba73cc4a7dd47db0322dfb88"},
  "phoenix_ecto": {:hex, :phoenix_ecto, "4.4.2", "b21bd01fdeffcfe2fab49e4942aa938b6d3e89e93a480d4aee58085560a0bc0d", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "70242edd4601d50b69273b057ecf7b684644c19ee750989fd555625ae4ce8f5d"},
  "phoenix_html": {:hex, :phoenix_html, "3.3.1", "4788757e804a30baac6b3fc9695bf5562465dd3f1da8eb8460ad5b404d9a2178", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "bed1906edd4906a15fd7b412b85b05e521e1f67c9a85418c55999277e553d0d3"},
  "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.0", "0b3158b5b198aa444473c91d23d79f52fb077e807ffad80dacf88ce078fa8df2", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:ecto_sqlite3_extras, "~> 1.1.7", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "87785a54474fed91a67a1227a741097eb1a42c2e49d3c0d098b588af65cd410d"},
  "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.4.1", "2aff698f5e47369decde4357ba91fc9c37c6487a512b41732818f2204a8ef1d3", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "9bffb834e7ddf08467fe54ae58b5785507aaba6255568ae22b4d46e2bb3615ab"},
  "phoenix_live_view": {:hex, :phoenix_live_view, "0.19.3", "3918c1b34df8ac71a9a636806ba5b7f053349a0392b312e16f35b0bf4d070aab", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "545626887948495fd8ea23d83b75bd7aaf9dc4221563e158d2c4b52ea1dd7e00"},
  "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"},
  "phoenix_swoosh": {:hex, :phoenix_swoosh, "1.2.0", "a544d83fde4a767efb78f45404a74c9e37b2a9c5ea3339692e65a6966731f935", [:mix], [{:finch, "~> 0.8", [hex: :finch, repo: "hexpm", optional: true]}, {:hackney, "~> 1.10", [hex: :hackney, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.5", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "e88d117251e89a16b92222415a6d87b99a96747ddf674fc5c7631de734811dba"},
  "phoenix_template": {:hex, :phoenix_template, "1.0.1", "85f79e3ad1b0180abb43f9725973e3b8c2c3354a87245f91431eec60553ed3ef", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "157dc078f6226334c91cb32c1865bf3911686f8bcd6bcff86736f6253e6993ee"},
  "phoenix_view": {:hex, :phoenix_view, "2.0.2", "6bd4d2fd595ef80d33b439ede6a19326b78f0f1d8d62b9a318e3d9c1af351098", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "a929e7230ea5c7ee0e149ffcf44ce7cf7f4b6d2bfe1752dd7c084cdff152d36f"},
  "plug": {:hex, :plug, "1.14.2", "cff7d4ec45b4ae176a227acd94a7ab536d9b37b942c8e8fa6dfc0fff98ff4d80", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "842fc50187e13cf4ac3b253d47d9474ed6c296a8732752835ce4a86acdf68d13"},
  "plug_cowboy": {:hex, :plug_cowboy, "2.6.1", "9a3bbfceeb65eff5f39dab529e5cd79137ac36e913c02067dba3963a26efe9b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "de36e1a21f451a18b790f37765db198075c25875c64834bcc82d90b309eb6613"},
  "plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"},

Phoenix 1.7 - function nil.home_path/2 is undefined

Note, this only affects the branch in #12 as Phoenix 1.7 requires LV 0.18 but I'm putting it here in case that gets merged.

Phoenix 1.7 ships with router helpers disabled by default.

The default layout uses a router helper here:

<%= live_redirect("Home", to: @socket.router.__helpers__().home_path(@socket, :home)) %>

Therefore, with default 1.7 config we get an UndefinedFunctionError.

As a temporary workaround, update myapp_web.ex and change use Phoenix.Router, helpers: false to use Phoenix.Router, helpers: true.

Other than that it seems that everything works well with 1.7!

Add classes based on name of field/embed for CSS targeting

Is your feature request related to a problem? Please describe.

I'd like to be able to target certain fields with custom CSS

Describe the solution you'd like

Append class names to the div.field__group and to the wrapper div to support better CSS targeting. Class names below are just for illustration purposes.

<div class="field__group field_<FIELD_NAME>">
  <label class="field__label" for="params_<FIELD_NAME>">Field Name</label>
  <textarea class="field__text" id="params_<FIELD_NAME>" name="params[<FIELD_NAME>]" rows="1">ut!</textarea>
</div>

And to embeddable types, add <embeddable_type> and <button__wrapper> and <embed__group__wrapper>:

<div class="embed__wrapper .embed_<embeddable_type>">
  <h2 class="embed__title">Field Name</h2>
  <div class="embed__group">
      <div class="embed__item">
        <div class="button__wrapper"></div>
        <div class="embed__group__wrapper"></div>
      </div>
  </div>
</div>

This would then allow css like:

.embed__wrapper .embed_<embeddable_type> .embed__item .embed_group_wrapper {
  grid-template-columns: repeat(3, minmax(0, 1fr));
  display: grid;
}

Describe alternatives you've considered
Using CSS like the following would be super brittle

.div:nth-child(n) .embed_group .embed_item div:last-child {
  grid-template-columns: repeat(3, minmax(0, 1fr));
  display: grid;
}

function nil.all/2 is undefined

I set up an empty Phoenix project to make sure I was getting the configuration right, but ran into this error. Perhaps I made an error.

[error] GenServer #PID<0.2196.0> terminating
** (UndefinedFunctionError) function nil.all/2 is undefined
    nil.all(#Ecto.Query<from p0 in VanillaPhoenix.Posts.Post, order_by: [asc: p0.id], limit: 10, offset: ^0>, [prefix: nil])
    (live_admin 0.9.1) lib/live_admin/resource.ex:189: LiveAdmin.Resource.build_list/3
    (live_admin 0.9.1) lib/live_admin/components/resource/index.ex:22: LiveAdmin.Components.Container.Index.update/2
    (phoenix_live_view 0.19.3) lib/phoenix_live_view/utils.ex:487: Phoenix.LiveView.Utils.maybe_call_update!/3
    (phoenix_live_view 0.19.3) lib/phoenix_live_view/diff.ex:653: anonymous fn/5 in Phoenix.LiveView.Diff.render_pending_components/6
    (elixir 1.14.4) lib/enum.ex:2468: Enum."-reduce/3-lists^foldl/2-0-"/3
    (stdlib 4.3) maps.erl:411: :maps.fold_1/3
    (phoenix_live_view 0.19.3) lib/phoenix_live_view/diff.ex:629: Phoenix.LiveView.Diff.render_pending_components/6
    (phoenix_live_view 0.19.3) lib/phoenix_live_view/diff.ex:143: Phoenix.LiveView.Diff.render/3
    (phoenix_live_view 0.19.3) lib/phoenix_live_view/channel.ex:833: Phoenix.LiveView.Channel.render_diff/3
    (phoenix_live_view 0.19.3) lib/phoenix_live_view/channel.ex:469: Phoenix.LiveView.Channel.mount_handle_params_result/3
    (phoenix_live_view 0.19.3) lib/phoenix_live_view/channel.ex:1043: Phoenix.LiveView.Channel.verified_mount/8
    (phoenix_live_view 0.19.3) lib/phoenix_live_view/channel.ex:59: Phoenix.LiveView.Channel.handle_info/2
    (stdlib 4.3) gen_server.erl:1123: :gen_server.try_dispatch/4
    (stdlib 4.3) gen_server.erl:1200: :gen_server.handle_msg/6
    (stdlib 4.3) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
Last message: {Phoenix.Channel, %{"params" => %{"_csrf_token" => "Aj8TNEZ0OHs9Hg1DFnU7DUI-PAMMCiQhoRqe66h4Zn9vXDjKszouEYJx", "_mounts" => 0}, "session" => "SFMyNTY.g2gDaAJhBXQAAAAIZAACaWRtAAAAFHBoeC1GMjd3U2NvSG1aZzlaQXVIZAAMbGl2ZV9zZXNzaW9uaAJkABFsaXZlX2FkbWluXy9hZG1pbm4IAERq4WsO8G4XZAAKcGFyZW50X3BpZGQAA25pbGQACHJvb3RfcGlkZAADbmlsZAAJcm9vdF92aWV3ZAAlRWxpeGlyLkxpdmVBZG1pbi5Db21wb25lbnRzLkNvbnRhaW5lcmQABnJvdXRlcmQAH0VsaXhpci5WYW5pbGxhUGhvZW5peFdlYi5Sb3V0ZXJkAAdzZXNzaW9udAAAAAVtAAAACWJhc2VfcGF0aG0AAAAGL2FkbWlubQAAAApjb21wb25lbnRzdAAAAAZkAARlZGl0ZAAqRWxpeGlyLkxpdmVBZG1pbi5Db21wb25lbnRzLkNvbnRhaW5lci5Gb3JtZAAEaG9tZWQAKEVsaXhpci5MaXZlQWRtaW4uQ29tcG9uZW50cy5Ib21lLkNvbnRlbnRkAARsaXN0ZAArRWxpeGlyLkxpdmVBZG1pbi5Db21wb25lbnRzLkNvbnRhaW5lci5JbmRleGQAA25hdmQAH0VsaXhpci5MaXZlQWRtaW4uQ29tcG9uZW50cy5OYXZkAANuZXdkACpFbGl4aXIuTGl2ZUFkbWluLkNvbXBvbmVudHMuQ29udGFpbmVyLkZvcm1kAAdzZXNzaW9uZAArRWxpeGlyLkxpdmVBZG1pbi5Db21wb25lbnRzLlNlc3Npb24uQ29udGVudG0AAAAEcmVwb2QAA25pbG0AAAAKc2Vzc2lvbl9pZG0AAAAkY2EwZWQ3OWUtZjA3MS00ZjlkLWIyMDgtMWY0OGY1M2UzMGU1bQAAAAV0aXRsZW0AAAAJTGl2ZUFkbWluZAAEdmlld2QAJUVsaXhpci5MaXZlQWRtaW4uQ29tcG9uZW50cy5Db250YWluZXJuBgCHNX0liQFiAAFRgA.h1upa5iXBYTd_fT0mzBchLvns-ZNy-2hOCYjiQACQJw", "static" => "SFMyNTY.g2gDaAJhBXQAAAADZAAKYXNzaWduX25ld2pkAAVmbGFzaHQAAAAAZAACaWRtAAAAFHBoeC1GMjd3U2NvSG1aZzlaQXVIbgYAhzV9JYkBYgABUYA.oGPtGQuvF4zhl-_zw0GQZK1jkNDyu2T3OyPnnYhNASI", "url" => "http://local.wunderwork.io:4000/admin/posts"}, {#PID<0.2184.0>, #Reference<0.2342050874.256114691.122563>}, %Phoenix.Socket{assigns: %{}, channel: Phoenix.LiveView.Channel, channel_pid: nil, endpoint: VanillaPhoenixWeb.Endpoint, handler: Phoenix.LiveView.Socket, id: nil, joined: false, join_ref: "4", private: %{connect_info: %{session: %{"_csrf_token" => "mmbQpBPOgp45N1QF1DSvISnY"}}}, pubsub_server: VanillaPhoenix.PubSub, ref: nil, serializer: Phoenix.Socket.V2.JSONSerializer, topic: "lv:phx-F27wScoHmZg9ZAuH", transport: :websocket, transport_pid: #PID<0.2184.0>}}
State: #Reference<0.2342050874.256114691.122565>
[error] an exception was raised:
    ** (UndefinedFunctionError) function nil.all/2 is undefined
        nil.all(#Ecto.Query<from p0 in VanillaPhoenix.Posts.Post, order_by: [asc: p0.id], limit: 10, offset: ^0>, [prefix: nil])
        (live_admin 0.9.1) lib/live_admin/resource.ex:189: LiveAdmin.Resource.build_list/3
        (live_admin 0.9.1) lib/live_admin/components/resource/index.ex:22: LiveAdmin.Components.Container.Index.update/2
        (phoenix_live_view 0.19.3) lib/phoenix_live_view/utils.ex:487: Phoenix.LiveView.Utils.maybe_call_update!/3
        (phoenix_live_view 0.19.3) lib/phoenix_live_view/diff.ex:653: anonymous fn/5 in Phoenix.LiveView.Diff.render_pending_components/6
        (elixir 1.14.4) lib/enum.ex:2468: Enum."-reduce/3-lists^foldl/2-0-"/3
        (stdlib 4.3) maps.erl:411: :maps.fold_1/3
        (phoenix_live_view 0.19.3) lib/phoenix_live_view/diff.ex:629: Phoenix.LiveView.Diff.render_pending_components/6
        (phoenix_live_view 0.19.3) lib/phoenix_live_view/diff.ex:143: Phoenix.LiveView.Diff.render/3
        (phoenix_live_view 0.19.3) lib/phoenix_live_view/channel.ex:833: Phoenix.LiveView.Channel.render_diff/3
        (phoenix_live_view 0.19.3) lib/phoenix_live_view/channel.ex:469: Phoenix.LiveView.Channel.mount_handle_params_result/3
        (phoenix_live_view 0.19.3) lib/phoenix_live_view/channel.ex:1043: Phoenix.LiveView.Channel.verified_mount/8
        (phoenix_live_view 0.19.3) lib/phoenix_live_view/channel.ex:59: Phoenix.LiveView.Channel.handle_info/2
        (stdlib 4.3) gen_server.erl:1123: :gen_server.try_dispatch/4
        (stdlib 4.3) gen_server.erl:1200: :gen_server.handle_msg/6
        (stdlib 4.3) proc_lib.erl:240: :proc_lib.init_p_do_apply/3

Steps to reproduce:

mix phx.new vanilla_phoenix
cd vanilla_phoenix
mix deps.get
mix phx.gen.live Posts Post posts  title:string hidden:boolean view:integer
[copy over routes]
mix ecto.setup

Setup live_admin

{:live_admin, "0.9.1"}

Router

  import LiveAdmin.Router

  scope "/", VanillaPhoenixWeb do
    pipe_through :browser
    
     # bla bla

    live_admin "/admin" do
      admin_resource("/posts", VanillaPhoenix.Posts.Post)
    end
  end

Resource Config in post.ex

defmodule VanillaPhoenix.Posts.Post do
  use Ecto.Schema
  use LiveAdmin.Resource
.
.
.
end

Start App

iex -S mix phx.server

visit /admin and click to posts.

This is using latest phoenix/live_view

  "live_admin": {:hex, :live_admin, "0.9.1", "570dbfe0173185f47807a182628c1d020334b183724915b3725b9dc5b4188246", [:mix], [{:ecto, "~> 3.8", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.8", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:phoenix_ecto, "~> 4.4", [hex: :phoenix_ecto, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}], "hexpm", "248a93b9fac43b6f404bd52a92ae17bd2c3d2153d5f539c72f7b1bd31e966165"},


"phoenix": {:hex, :phoenix, "1.7.6", "61f0625af7c1d1923d582470446de29b008c0e07ae33d7a3859ede247ddaf59a", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "f6b4be7780402bb060cbc6e83f1b6d3f5673b674ba73cc4a7dd47db0322dfb88"},
  "phoenix_ecto": {:hex, :phoenix_ecto, "4.4.2", "b21bd01fdeffcfe2fab49e4942aa938b6d3e89e93a480d4aee58085560a0bc0d", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "70242edd4601d50b69273b057ecf7b684644c19ee750989fd555625ae4ce8f5d"},
  "phoenix_html": {:hex, :phoenix_html, "3.3.1", "4788757e804a30baac6b3fc9695bf5562465dd3f1da8eb8460ad5b404d9a2178", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "bed1906edd4906a15fd7b412b85b05e521e1f67c9a85418c55999277e553d0d3"},
  "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.0", "0b3158b5b198aa444473c91d23d79f52fb077e807ffad80dacf88ce078fa8df2", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:ecto_sqlite3_extras, "~> 1.1.7", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "87785a54474fed91a67a1227a741097eb1a42c2e49d3c0d098b588af65cd410d"},
  "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.4.1", "2aff698f5e47369decde4357ba91fc9c37c6487a512b41732818f2204a8ef1d3", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "9bffb834e7ddf08467fe54ae58b5785507aaba6255568ae22b4d46e2bb3615ab"},
  "phoenix_live_view": {:hex, :phoenix_live_view, "0.19.3", "3918c1b34df8ac71a9a636806ba5b7f053349a0392b312e16f35b0bf4d070aab", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "545626887948495fd8ea23d83b75bd7aaf9dc4221563e158d2c4b52ea1dd7e00"},
  "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"},
  "phoenix_template": {:hex, :phoenix_template, "1.0.1", "85f79e3ad1b0180abb43f9725973e3b8c2c3354a87245f91431eec60553ed3ef", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "157dc078f6226334c91cb32c1865bf3911686f8bcd6bcff86736f6253e6993ee"},
  "phoenix_view": {:hex, :phoenix_view, "2.0.2", "6bd4d2fd595ef80d33b439ede6a19326b78f0f1d8d62b9a318e3d9c1af351098", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "a929e7230ea5c7ee0e149ffcf44ce7cf7f4b6d2bfe1752dd7c084cdff152d36f"},
  "plug": {:hex, :plug, "1.14.2", "cff7d4ec45b4ae176a227acd94a7ab536d9b37b942c8e8fa6dfc0fff98ff4d80", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "842fc50187e13cf4ac3b253d47d9474ed6c296a8732752835ce4a86acdf68d13"},
  "plug_cowboy": {:hex, :plug_cowboy, "2.6.1", "9a3bbfceeb65eff5f39dab529e5cd79137ac36e913c02067dba3963a26efe9b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "de36e1a21f451a18b790f37765db198075c25875c64834bcc82d90b309eb6613"},

Array values getting dropped

When validating or updating a record, the checked values for array fields get dropped.

For example, option one is checked, after validating, option one would be unchecked.

<div class="checkbox__group">
  <input id="params_additional_skills" name="params[additional_skills][]" type="hidden">
  
    <input checked="" id="params_additional_skillsone" name="params[additional_skills][]" type="checkbox" value="one">
    <label for="params_additional_skillsone">
      one
    </label>
  
    <input id="params_additional_skillstwo" name="params[additional_skills][]" type="checkbox" value="two">
    <label for="params_additional_skillstwo">
      two
    </label>
  
    <input id="params_additional_skillsthree" name="params[additional_skills][]" type="checkbox" value="three">
    <label for="params_additional_skillsthree">
      three
    </label>
  
    <input id="params_additional_skillsfour" name="params[additional_skills][]" type="checkbox" value="four">
    <label for="params_additional_skillsfour">
      four
    </label>
</div>

This is without using validate_with or save_with options on version 10.1

Feature requests

Thanks so much for creating this awesome library. I just noticed how to use render_with. Super helpful!

I have two requests:

  • Allow links/redirects in record actions menu, with a way to pass the current record - In my case, I'd like to link to a preview page and a page with a specialized form that is outside of the scope of an edit page.
  • Configure index table fields and order - I'd love to be able to order and hide some.

If I have overlooked existing capabilities, please let me know!

Thanks!

-Mario

Issue with search_select

I'm running into an issue with search_select:

** (KeyError) key :repo not found in: %{__changed__: %{flash: true, options: true}, flash: %{}, myself: %Phoenix.LiveComponent.CID{cid: 4}, options: []}
    (live_admin 0.9.1) lib/live_admin/components/resource/form/search_select.ex:23: LiveAdmin.Components.Container.Form.SearchSelect.update/2
    (phoenix_live_view 0.19.3) lib/phoenix_live_view/utils.ex:487: Phoenix.LiveView.Utils.maybe_call_update!/3
    (phoenix_live_view 0.19.3) lib/phoenix_live_view/diff.ex:653: anonymous fn/5 in Phoenix.LiveView.Diff.render_pending_components/6
    (elixir 1.14.4) lib/enum.ex:2468: Enum."-reduce/3-lists^foldl/2-0-"/3
    (stdlib 4.3) maps.erl:411: :maps.fold_1/3
    (phoenix_live_view 0.19.3) lib/phoenix_live_view/diff.ex:629: Phoenix.LiveView.Diff.render_pending_components/6
    (phoenix_live_view 0.19.3) lib/phoenix_live_view/diff.ex:143: Phoenix.LiveView.Diff.render/3
    (phoenix_live_view 0.19.3) lib/phoenix_live_view/channel.ex:833: Phoenix.LiveView.Channel.render_diff/3
    (phoenix_live_view 0.19.3) lib/phoenix_live_view/channel.ex:469: Phoenix.LiveView.Channel.mount_handle_params_result/3
    (phoenix_live_view 0.19.3) lib/phoenix_live_view/channel.ex:1043: Phoenix.LiveView.Channel.verified_mount/8
    (phoenix_live_view 0.19.3) lib/phoenix_live_view/channel.ex:59: Phoenix.LiveView.Channel.handle_info/2
    (stdlib 4.3) gen_server.erl:1123: :gen_server.try_dispatch/4
    (stdlib 4.3) gen_server.erl:1200: :gen_server.handle_msg/6
    (stdlib 4.3) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
Last message: {Phoenix.Channel, %{"params" => %{"_csrf_token" => "WjIXLQEQDiQZKlMCB3IACTUsTCoSL2UjcxS_tiTmLRdQhEUlfs-YwG4t", "_mounts" => 0}, "session" => "SFMyNTY.g2gDaAJhBXQAAAAIZAACaWRtAAAAFHBoeC1GMjc1NENONzlJVUNDUUJIZAAMbGl2ZV9zZXNzaW9uaAJkABVsaXZlX2FkbWluXy9hZG1pbi9taW5uCABnvjtYtPluF2QACnBhcmVudF9waWRkAANuaWxkAAhyb290X3BpZGQAA25pbGQACXJvb3Rfdmlld2QAJUVsaXhpci5MaXZlQWRtaW4uQ29tcG9uZW50cy5Db250YWluZXJkAAZyb3V0ZXJkABlFbGl4aXIuUGV0YWxQcm9XZWIuUm91dGVyZAAHc2Vzc2lvbnQAAAAFbQAAAAliYXNlX3BhdGhtAAAACi9hZG1pbi9taW5tAAAACmNvbXBvbmVudHN0AAAABmQABGVkaXRkACpFbGl4aXIuTGl2ZUFkbWluLkNvbXBvbmVudHMuQ29udGFpbmVyLkZvcm1kAARob21lZAAoRWxpeGlyLkxpdmVBZG1pbi5Db21wb25lbnRzLkhvbWUuQ29udGVudGQABGxpc3RkACtFbGl4aXIuTGl2ZUFkbWluLkNvbXBvbmVudHMuQ29udGFpbmVyLkluZGV4ZAADbmF2ZAAfRWxpeGlyLkxpdmVBZG1pbi5Db21wb25lbnRzLk5hdmQAA25ld2QAKkVsaXhpci5MaXZlQWRtaW4uQ29tcG9uZW50cy5Db250YWluZXIuRm9ybWQAB3Nlc3Npb25kACtFbGl4aXIuTGl2ZUFkbWluLkNvbXBvbmVudHMuU2Vzc2lvbi5Db250ZW50bQAAAARyZXBvZAAURWxpeGlyLlBldGFsUHJvLlJlcG9tAAAACnNlc3Npb25faWRtAAAAJGY2NmU0ZDU2LTI0Y2YtNDg3Ni04NjQwLTc5ZjkxYTY2Nzk1Y20AAAAFdGl0bGVtAAAACUxpdmVBZG1pbmQABHZpZXdkACVFbGl4aXIuTGl2ZUFkbWluLkNvbXBvbmVudHMuQ29udGFpbmVybgYArQ4eJokBYgABUYA.qQNzOjF7UIa_aS8miAesVtfCv9MaDn-PaUOTwd-JVJs", "static" => "SFMyNTY.g2gDaAJhBXQAAAADZAAKYXNzaWduX25ld2pkAAVmbGFzaHQAAAAAZAACaWRtAAAAFHBoeC1GMjc1NENONzlJVUNDUUJIbgYArQ4eJokBYgABUYA.I6Y0m10SBSuBBTYUVET-Upd22gP-Yf37HsXH5rZbeog", "url" => "http://local.wunderwork.io:4000/admin/min/jobs/edit/1"}, {#PID<0.1117.0>, #Reference<0.1630307316.1626603528.84781>}, %Phoenix.Socket{assigns: %{}, channel: Phoenix.LiveView.Channel, channel_pid: nil, endpoint: MyAppWeb.Endpoint, handler: Phoenix.LiveView.Socket, id: "users_sessions:LDi9TaYhpRXnHJcWZlwusi6DkVpc-_wKR7TAeCjsAwg=", joined: false, join_ref: "4", private: %{connect_info: %{session: %{"_csrf_token" => "9JDruyZIUx7So7UeS_asehQW", "live_socket_id" => "users_sessions:LDi9TaYhpRXnHJcWZlwusi6DkVpc-_wKR7TAeCjsAwg=", "locale" => "en", "user_token" => <<44, 56, 189, 77, 166, 33, 165, 21, 231, 28, 151, 22, 102, 92, 46, 178, 46, 131, 145, 90, 92, 251, 252, 10, 71, 180, 192, 120, 40, 236, 3, ...>>}, user_agent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"}}, pubsub_server: MyApp.PubSub, ref: nil, serializer: Phoenix.Socket.V2.JSONSerializer, topic: "lv:phx-F2754CN79IUCCQBH", transport: :websocket, transport_pid: #PID<0.1117.0>}}
State: #Reference<0.1630307316.1626603522.102768>
[error] an exception was raised:
    ** (KeyError) key :repo not found in: %{__changed__: %{flash: true, options: true}, flash: %{}, myself: %Phoenix.LiveComponent.CID{cid: 4}, options: []}
        (live_admin 0.9.1) lib/live_admin/components/resource/form/search_select.ex:23: LiveAdmin.Components.Container.Form.SearchSelect.update/2
        (phoenix_live_view 0.19.3) lib/phoenix_live_view/utils.ex:487: Phoenix.LiveView.Utils.maybe_call_update!/3
        (phoenix_live_view 0.19.3) lib/phoenix_live_view/diff.ex:653: anonymous fn/5 in Phoenix.LiveView.Diff.render_pending_components/6
        (elixir 1.14.4) lib/enum.ex:2468: Enum."-reduce/3-lists^foldl/2-0-"/3
        (stdlib 4.3) maps.erl:411: :maps.fold_1/3
        (phoenix_live_view 0.19.3) lib/phoenix_live_view/diff.ex:629: Phoenix.LiveView.Diff.render_pending_components/6
        (phoenix_live_view 0.19.3) lib/phoenix_live_view/diff.ex:143: Phoenix.LiveView.Diff.render/3
        (phoenix_live_view 0.19.3) lib/phoenix_live_view/channel.ex:833: Phoenix.LiveView.Channel.render_diff/3
        (phoenix_live_view 0.19.3) lib/phoenix_live_view/channel.ex:469: Phoenix.LiveView.Channel.mount_handle_params_result/3
        (phoenix_live_view 0.19.3) lib/phoenix_live_view/channel.ex:1043: Phoenix.LiveView.Channel.verified_mount/8
        (phoenix_live_view 0.19.3) lib/phoenix_live_view/channel.ex:59: Phoenix.LiveView.Channel.handle_info/2
        (stdlib 4.3) gen_server.erl:1123: :gen_server.try_dispatch/4
        (stdlib 4.3) gen_server.erl:1200: :gen_server.handle_msg/6
        (stdlib 4.3) proc_lib.erl:240: :proc_lib.init_p_do_apply/3

It seems that the issue is here: https://github.com/tfwright/live_admin/blob/main/lib/live_admin/components/resource/form/search_select.ex#L21

It seems that the repo key is present in assigns.repo, not socket.assigns.repo. If I inspect the socket.assigns I see that the repo key is missing.

%{
  __changed__: %{flash: true, options: true},
  flash: %{},
  myself: %Phoenix.LiveComponent.CID{cid: 4},
  options: []
}

If I switch to assigns.repo on line 21, all works smoothly.

ID collisions for embedded schemas

Describe the bug
It seems that embedded schemas that call LiveAdmin.Components.Container.Form.ArrayInput create ID collisions. I tried 0.8.0 and current main.

Relevant Resource schema

schema "profiles" do
    embeds_many(:work_experiences, WorkExperience, on_replace: :delete)
end

Embedded schema:

  embedded_schema do
    field :skills, {:array, :string}, default: []
  end

Error message:

** (RuntimeError) found duplicate ID :skills for component LiveAdmin.Components.Container.Form.ArrayInput when rendering template
    (phoenix_live_view 0.19.2) lib/phoenix_live_view/diff.ex:635: anonymous fn/5 in Phoenix.LiveView.Diff.render_pending_components/6
    (elixir 1.14.4) lib/enum.ex:2468: Enum."-reduce/3-lists^foldl/2-0-"/3
    (stdlib 4.3) maps.erl:411: :maps.fold_1/3
    (phoenix_live_view 0.19.2) lib/phoenix_live_view/diff.ex:628: Phoenix.LiveView.Diff.render_pending_components/6
    (phoenix_live_view 0.19.2) lib/phoenix_live_view/diff.ex:143: Phoenix.LiveView.Diff.render/3
    (phoenix_live_view 0.19.2) lib/phoenix_live_view/channel.ex:833: Phoenix.LiveView.Channel.render_diff/3
    (phoenix_live_view 0.19.2) lib/phoenix_live_view/channel.ex:469: Phoenix.LiveView.Channel.mount_handle_params_result/3
    (phoenix_live_view 0.19.2) lib/phoenix_live_view/channel.ex:1041: Phoenix.LiveView.Channel.verified_mount/8
    (phoenix_live_view 0.19.2) lib/phoenix_live_view/channel.ex:59: Phoenix.LiveView.Channel.handle_info/2
    (stdlib 4.3) gen_server.erl:1123: :gen_server.try_dispatch/4
    (stdlib 4.3) gen_server.erl:1200: :gen_server.handle_msg/6
    (stdlib 4.3) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
Last message: {Phoenix.Channel, %{"params" => %{"_csrf_token" => "GAsPMxpgZxoTPQoafxBnFGMBAhczVBE9YZZVkMWuFK977iJn0tAzKyxo", "_mounts" => 0}, "session" => "SFMyNTY.g2gDaAJhBXQAAAAIZAACaWRtAAAAFHBoeC1GMnFtUHVQMXlNWV9DZ1FHZAAMbGl2ZV9zZXNzaW9uaAJkAApsaXZlX2FkbWlubggAeca6BkCdahdkAApwYXJlbnRfcGlkZAADbmlsZAAIcm9vdF9waWRkAANuaWxkAAlyb290X3ZpZXdkACVFbGl4aXIuTGl2ZUFkbWluLkNvbXBvbmVudHMuQ29udGFpbmVyZAAGcm91dGVyZAAZRWxpeGlyLlBldGFsUHJvV2ViLlJvdXRlcmQAB3Nlc3Npb250AAAAA20AAAAJcmVzb3VyY2VzbAAAAARkABpFbGl4aXIuUGV0YWxQcm8uVXNlcnMuVXNlcmQAGEVsaXhpci5QZXRhbFByby5Kb2JzLkpvYmgCZAAgRWxpeGlyLlBldGFsUHJvLlByb2ZpbGVzLlByb2ZpbGVsAAAAA2gCZAANaGlkZGVuX2ZpZWxkc2wAAAADZAAHaGlzdG9yeWQACml0ZXJhdGlvbnNkAAh2ZXJzaW9uc2poAmQAEGltbXV0YWJsZV9maWVsZHNsAAAABGQACnVwZGF0ZWRfYXRkAAtpbnNlcnRlZF9hdGQAC2Rpc3BsYXlfcmVmZAAEdXVpZGpoAmQACmxhYmVsX3dpdGhkAAtkaXNwbGF5X3JlZmpoAmQAIkVsaXhpci5QZXRhbFByby5Qcm9zcGVjdHMuUHJvc3BlY3RsAAAAA2gCZAANaGlkZGVuX2ZpZWxkc2wAAAABZAAHaGlzdG9yeWpoAmQAEGltbXV0YWJsZV9maWVsZHNsAAAABGQACnVwZGF0ZWRfYXRkAAtpbnNlcnRlZF9hdGQAC2Rpc3BsYXlfcmVmZAAEdXVpZGpoAmQACmxhYmVsX3dpdGhkAAtkaXNwbGF5X3JlZmpqbQAAAApzZXNzaW9uX2lkbQAAACRlMjZjYzU1OS1jYTYyLTQwMWQtYTQ2Yi0xNDYxNmY3NDg2NjRtAAAABXRpdGxlbQAAAAlMaXZlQWRtaW5kAAR2aWV3ZAAlRWxpeGlyLkxpdmVBZG1pbi5Db21wb25lbnRzLkNvbnRhaW5lcm4GAJYch92IAWIAAVGA.cqEG4kB-N3WCDaHpPDrWMRE2W4P-M2KuYXpVqJDozvc", "static" => "SFMyNTY.g2gDaAJhBXQAAAADZAAKYXNzaWduX25ld2pkAAVmbGFzaHQAAAAAZAACaWRtAAAAFHBoeC1GMnFtUHVQMXlNWV9DZ1FHbgYAlhyH3YgBYgABUYA.y1Mwg0fi1uBvB3mOANqEN7Ia3RtyKEo7H5OebWAe8X8", "url" => "http://local.wunderwork.io:4000/min-admin/petal_pro_prospects_prospect/edit/1"}, {#PID<0.1873.0>, #Reference<0.3783962687.1405878278.173945>}, %Phoenix.Socket{assigns: %{}, channel: Phoenix.LiveView.Channel, channel_pid: nil, endpoint: PetalProWeb.Endpoint, handler: Phoenix.LiveView.Socket, id: "users_sessions:etD_rWtW5t2B0rJOxzPEjxFPkDCFQl9CNomicZwPu2U=", joined: false, join_ref: "4", private: %{connect_info: %{session: %{"_csrf_token" => "AQUeq-0oUv3-Hy-zSuCmx-iR", "live_socket_id" => "users_sessions:etD_rWtW5t2B0rJOxzPEjxFPkDCFQl9CNomicZwPu2U=", "locale" => "en", "user_token" => <<122, 208, 255, 173, 107, 86, 230, 221, 129, 210, 178, 78, 199, 51, 196, 143, 17, 79, 144, 48, 133, 66, 95, 66, 54, 137, 162, 113, 156, 15, 187, ...>>}, user_agent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"}}, pubsub_server: PetalPro.PubSub, ref: nil, serializer: Phoenix.Socket.V2.JSONSerializer, topic: "lv:phx-F2qmPuP1yMY_CgQG", transport: :websocket, transport_pid: #PID<0.1873.0>}}
State: #Reference<0.3783962687.1405878278.173948>
[error] an exception was raised:
    ** (RuntimeError) found duplicate ID :skills for component LiveAdmin.Components.Container.Form.ArrayInput when rendering template
        (phoenix_live_view 0.19.2) lib/phoenix_live_view/diff.ex:635: anonymous fn/5 in Phoenix.LiveView.Diff.render_pending_components/6
        (elixir 1.14.4) lib/enum.ex:2468: Enum."-reduce/3-lists^foldl/2-0-"/3
        (stdlib 4.3) maps.erl:411: :maps.fold_1/3
        (phoenix_live_view 0.19.2) lib/phoenix_live_view/diff.ex:628: Phoenix.LiveView.Diff.render_pending_components/6
        (phoenix_live_view 0.19.2) lib/phoenix_live_view/diff.ex:143: Phoenix.LiveView.Diff.render/3
        (phoenix_live_view 0.19.2) lib/phoenix_live_view/channel.ex:833: Phoenix.LiveView.Channel.render_diff/3
        (phoenix_live_view 0.19.2) lib/phoenix_live_view/channel.ex:469: Phoenix.LiveView.Channel.mount_handle_params_result/3
        (phoenix_live_view 0.19.2) lib/phoenix_live_view/channel.ex:1041: Phoenix.LiveView.Channel.verified_mount/8
        (phoenix_live_view 0.19.2) lib/phoenix_live_view/channel.ex:59: Phoenix.LiveView.Channel.handle_info/2
        (stdlib 4.3) gen_server.erl:1123: :gen_server.try_dispatch/4
        (stdlib 4.3) gen_server.erl:1200: :gen_server.handle_msg/6
        (stdlib 4.3) proc_lib.erl:240: :proc_lib.init_p_do_apply/3

Custom components for various 'types' or specified fields.

I have some dates and also image urls i would like to render more precisely with live_admin in the table anyway.

A super handy and powerful feature would being able to supply custom MFAs that could be used to either format a string, or more powerfully be a heex function component with a defined API.

This would be an opt in feature - and not require any breaking changes, I do not thing.

This may need to be a compile time app config to ensure it is available at compile time.

I am thinking something like this, thoughts?

config :live_admin,
   # create a keyword list or something to 'register' custom components
   custom_components: [{:custom_utc_datetime, MFA}],
   # defines a global override for all  `utc_datetime` type
   content_overrides: %{utc_datetime:  :custom_utc_datetime} 
   live_admin("/",
    resources: [
        {
          LiveAdminTest.User,
          # override rendering of specified fields with the registered components
          field_overrides: %{inserted_at: :custom_utc_datetime} 
        },
        LiveAdminTest.Post
      ]
    )

I might create a PR with a proof of concept - but would like to bounce around some ideas first!
Maybe you have already thought about this?

Infinity Reload because of session

Hey folks,

I use the newest Phoenix Framework Version and I installed live_admin like in the readme. When I access the home page, the site keeps reloading.

In the console I get this message:

[debug] Processing with LiveAdmin.Components.Home.home_/my_admin/2
  Parameters: %{}
  Pipelines: []
[info] Sent 200 in 2ms
[info] CONNECTED TO Phoenix.LiveView.Socket in 39ยตs
  Transport: :websocket
  Serializer: Phoenix.Socket.V2.JSONSerializer
  Parameters: %{"_csrf_token" => "KycIGgICLDMPEwkuLRM9azArOSRQOSMCFD1VconPlvZEjgL8aFhCfIUZ", "_live_referer" => "undefined", "_mounts" => "0", "vsn" => "2.0.0"}
[debug] LiveView session was misconfigured or the user token is outdated.

1) Ensure your session configuration in your endpoint is in a module attribute:

    @session_options [
      ...
    ]

2) Change the `plug Plug.Session` to use said attribute:

    plug Plug.Session, @session_options

3) Also pass the `@session_options` to your LiveView socket:

    socket "/live", Phoenix.LiveView.Socket,
      websocket: [connect_info: [session: @session_options]]

4) Ensure the `protect_from_forgery` plug is in your router pipeline:

    plug :protect_from_forgery

5) Define the CSRF meta tag inside the `<head>` tag in your layout:

    <meta name="csrf-token" content={Plug.CSRFProtection.get_csrf_token()} />

6) Pass it forward in your app.js:

    let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content");
    let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}});

I did all steps but it doesnยดt helped.

Environment:

  • OS: Browser / Mac
  • Browser all
  • Elixir: 1.14 Phoenix: 1.7.3 Live View: 0.19

Thanks for your help :)

fails to compile

Describe the bug

== Compilation error in file lib/live_admin/view.ex ==
** (CompileError) lib/live_admin/view.ex:4: module Phoenix.View is not loaded and could not be found
(elixir 1.14.4) expanding macro: Kernel.use/2
lib/live_admin/view.ex:4: LiveAdmin.View (module)

Brand new app generated by mix phx.new and gen.auth
phoenix 1.7.5, liveview 0.19.2

Add detail view page

Love this library and where it's headed. ๐Ÿ™ Thanks!

Is your feature request related to a problem? Please describe.
Often a table can't display clearly all information about an entity, Would prefer not having to horizontally scroll.

Describe the solution you'd like

  1. I would like to be able to view an entity with perhaps many fields as a vertical scroll rather than in table form (horizontal) on a separate page from "List". Call it "View"
  2. I would suggest losing the hamburger menu and replacing it with a "View" button
  3. And from this "View" page give the option to "Edit"
  4. And move the delete option to the "Edit" page or have "Delete" next to "Edit" on the "View" page, either way

Describe alternatives you've considered
Using the edit page as the detail page. Not ideal.

Would you accept a PR for this?

Complitation warnings

Is your feature request related to a problem? Please describe.
When compiling on phoenix_live_view 0.18.18 and latest phoenix, I get the following warnings:

warning: attribute "as" in component Phoenix.Component.form/1 must be an :atom, got: "session"
  lib/live_admin/components/session/content.ex:17: (file)

warning: undefined attribute "phx_submit" for component Phoenix.Component.form/1
  lib/live_admin/components/session/content.ex:17: (file)

warning: attribute "as" in component Phoenix.Component.form/1 must be an :atom, got: "params"
  lib/live_admin/components/resource/form.ex:59: (file)

warning: undefined attribute "phx_change" for component Phoenix.Component.form/1
  lib/live_admin/components/resource/form.ex:60: (file)

warning: undefined attribute "phx_submit" for component Phoenix.Component.form/1
  lib/live_admin/components/resource/form.ex:61: (file)

warning: undefined attribute "phx_target" for component Phoenix.Component.form/1
  lib/live_admin/components/resource/form.ex:62: (file)

Deprecation Warning

Just in case you hadn't seen it, I noticed a deprecation warning in my newly generated app, which uses a newer version of phoenix_html.

warning: inputs_for/3 without an anonymous function is deprecated. If you are using Phoenix.LiveView, use the new Phoenix.Component.inputs_for/1 component
  (phoenix_html 3.3.2) lib/phoenix_html/form.ex:606: Phoenix.HTML.Form.inputs_for/3
  (live_admin 0.10.1) lib/live_admin/components/resource/form/embed.ex:15: LiveAdmin.Components.Container.Form.Embed.update/2
  (phoenix_live_view 0.19.5) lib/phoenix_live_view/utils.ex:487: Phoenix.LiveView.Utils.maybe_call_update!/3
  (phoenix_live_view 0.19.5) lib/phoenix_live_view/diff.ex:653: anonymous fn/5 in Phoenix.LiveView.Diff.render_pending_components/6
  (elixir 1.14.4) lib/enum.ex:2468: Enum."-reduce/3-lists^foldl/2-0-"/3
  (stdlib 4.3) maps.erl:411: :maps.fold_1/3
  (phoenix_live_view 0.19.5) lib/phoenix_live_view/diff.ex:629: Phoenix.LiveView.Diff.render_pending_components/6
  (phoenix_live_view 0.19.5) lib/phoenix_live_view/diff.ex:212: Phoenix.LiveView.Diff.write_component/4
  (phoenix_live_view 0.19.5) lib/phoenix_live_view/channel.ex:553: Phoenix.LiveView.Channel.component_handle_event/6
  (stdlib 4.3) gen_server.erl:1123: :gen_server.try_dispatch/4
  (stdlib 4.3) gen_server.erl:1200: :gen_server.handle_msg/6
  (stdlib 4.3) proc_lib.erl:240: :proc_lib.init_p_do_apply/3

I thought I would post it for you in case you hadn't seen it.

LiveAdmin crashes when rendering a field that is list of binaries

Describe the bug
LiveAdmin crashes when rendering a field that is a list of binaries

To Reproduce

  1. Define a field on an Ecto resource that is a list of strings
field :home_notifications, {:array, :string}
  1. Populate the filed on the record with a simple list
["background_check"]
  1. Try to list the record in LiveAdmin
  2. See error

Expected behavior
Show the list of binaries

Screenshots
Example console error

[error] GenServer #PID<0.4154.0> terminating
** (Protocol.UndefinedError) protocol String.Chars not implemented for {:safe, [[[60, "p", [], 62, ["background_check"], 60, 47, "p", 62], 10]]} of type Tuple. This protocol is implemented for the following type(s): Atom, BitString, Date, DateTime, Decimal, Explorer.Duration, Float, Floki.Selector, Floki.Selector.AttributeSelector, Floki.Selector.Combinator, Floki.Selector.Functional, Floki.Selector.PseudoClass, Integer, List, NaiveDateTime, Phoenix.LiveComponent.CID, Postgrex.Copy, Postgrex.Query, RemoteIp.Block, Time, URI, Version, Version.Requirement
    (elixir 1.15.7) lib/string/chars.ex:3: String.Chars.impl_for!/1
    (elixir 1.15.7) lib/string/chars.ex:22: String.Chars.to_string/1
    (elixir 1.15.7) lib/enum.ex:4369: Enum.map_intersperse_list/3
    (elixir 1.15.7) lib/enum.ex:1794: Enum.map_join/3
    (live_admin 0.11.4) lib/live_admin/components/resource/index.ex:124: anonymous fn/4 in LiveAdmin.Components.Container.Index.render/1
    (elixir 1.15.7) lib/enum.ex:2510: Enum."-reduce/3-lists^foldl/2-0-"/3
    (live_admin 0.11.4) lib/live_admin/components/resource/index.ex:115: anonymous fn/2 in LiveAdmin.Components.Container.Index.render/1
    (elixir 1.15.7) lib/enum.ex:1693: Enum."-map/2-lists^map/1-1-"/2
    (live_admin 0.11.4) lib/live_admin/components/resource/index.ex:103: anonymous fn/2 in LiveAdmin.Components.Container.Index.render/1
    (phoenix_live_view 0.20.5) lib/phoenix_live_view/diff.ex:391: Phoenix.LiveView.Diff.traverse/7
    (phoenix_live_view 0.20.5) lib/phoenix_live_view/diff.ex:766: Phoenix.LiveView.Diff.render_component/8
    (phoenix_live_view 0.20.5) lib/phoenix_live_view/diff.ex:705: Phoenix.LiveView.Diff.zip_components/5
    (phoenix_live_view 0.20.5) lib/phoenix_live_view/diff.ex:690: anonymous fn/4 in Phoenix.LiveView.Diff.render_pending_components/6
    (stdlib 5.1.1) maps.erl:416: :maps.fold_1/4
    (phoenix_live_view 0.20.5) lib/phoenix_live_view/diff.ex:635: Phoenix.LiveView.Diff.render_pending_components/6
    (phoenix_live_view 0.20.5) lib/phoenix_live_view/diff.ex:143: Phoenix.LiveView.Diff.render/3
    (phoenix_live_view 0.20.5) lib/phoenix_live_view/channel.ex:953: anonymous fn/4 in Phoenix.LiveView.Channel.render_diff/3
    (telemetry 1.2.1) /Users/tmaszk/code/colife/deps/telemetry/src/telemetry.erl:321: :telemetry.span/3
    (phoenix_live_view 0.20.5) lib/phoenix_live_view/channel.ex:948: Phoenix.LiveView.Channel.render_diff/3
    (phoenix_live_view 0.20.5) lib/phoenix_live_view/channel.ex:575: Phoenix.LiveView.Channel.mount_handle_params_result/3

Environment:

  • OS: macOS
  • Browser: safari
  • Version: master or 0.11.4

Additional context
I think the problem is this line

defp render_field(val) when is_list(val), do: Enum.map_join(val, ", ", &render_field/1)

I think it is rendering a field within a field, which is causing the crash. I tried this locally and it worked as I would have expected

defp render_field(val) when is_list(val), do: Enum.map_join(val, ", ", &inspect/1)

:locale not found in: nil

I updated to 0.10.0 and ran into this error. I don't need a locale for my liveadmin.

[error] GenServer #PID<0.929.0> terminating
** (KeyError) key :locale not found in: nil. If you are using the dot syntax, such as map.field, make sure the left-hand side of the dot is a map
    (live_admin 0.10.0) lib/live_admin/router.ex:117: LiveAdmin.Router.on_mount/4
    (phoenix_live_view 0.19.5) lib/phoenix_live_view/lifecycle.ex:149: anonymous fn/4 in Phoenix.LiveView.Lifecycle.mount/3
    (phoenix_live_view 0.19.5) lib/phoenix_live_view/lifecycle.ex:212: Phoenix.LiveView.Lifecycle.reduce_socket/3
    (phoenix_live_view 0.19.5) lib/phoenix_live_view/utils.ex:392: anonymous fn/6 in Phoenix.LiveView.Utils.maybe_call_live_view_mount!/5
    (telemetry 1.2.1) /home/mario/wunderwork_petal/deps/telemetry/src/telemetry.erl:321: :telemetry.span/3
    (phoenix_live_view 0.19.5) lib/phoenix_live_view/channel.ex:1041: Phoenix.LiveView.Channel.verified_mount/8
    (phoenix_live_view 0.19.5) lib/phoenix_live_view/channel.ex:59: Phoenix.LiveView.Channel.handle_info/2
    (stdlib 4.3) gen_server.erl:1123: :gen_server.try_dispatch/4
    (stdlib 4.3) gen_server.erl:1200: :gen_server.handle_msg/6
    (stdlib 4.3) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
Last message: {Phoenix.Channel, %{"flash" => nil, "params" => %{"_csrf_token" => "QGMMPB8SO0sBEDZoWj06AmN8SR0KNy8QuUgvfYU8NeRQlnJ2T43QIggQ", "_mounts" => 1}, "session" => "SFMyNTY.g2gDaAJhBXQAAAAIZAACaWRtAAAAFHBoeC1GM1dtUWEzZEs3clNETmhrZAAMbGl2ZV9zZXNzaW9uaAJkABVsaXZlX2FkbWluXy9hZG1pbi9taW5uCAACMLA7QaZ1F2QACnBhcmVudF9waWRkAANuaWxkAAhyb290X3BpZGQAA25pbGQACXJvb3Rfdmlld2QAJUVsaXhpci5MaXZlQWRtaW4uQ29tcG9uZW50cy5Db250YWluZXJkAAZyb3V0ZXJkABlFbGl4aXIuUGV0YWxQcm9XZWIuUm91dGVyZAAHc2Vzc2lvbnQAAAAGbQAAAAliYXNlX3BhdGhtAAAACi9hZG1pbi9taW5tAAAACmNvbXBvbmVudHN0AAAAB2QABGVkaXRkACpFbGl4aXIuTGl2ZUFkbWluLkNvbXBvbmVudHMuQ29udGFpbmVyLkZvcm1kAARob21lZAAoRWxpeGlyLkxpdmVBZG1pbi5Db21wb25lbnRzLkhvbWUuQ29udGVudGQABGxpc3RkACtFbGl4aXIuTGl2ZUFkbWluLkNvbXBvbmVudHMuQ29udGFpbmVyLkluZGV4ZAADbmF2ZAAfRWxpeGlyLkxpdmVBZG1pbi5Db21wb25lbnRzLk5hdmQAA25ld2QAKkVsaXhpci5MaXZlQWRtaW4uQ29tcG9uZW50cy5Db250YWluZXIuRm9ybWQAB3Nlc3Npb25kACtFbGl4aXIuTGl2ZUFkbWluLkNvbXBvbmVudHMuU2Vzc2lvbi5Db250ZW50ZAAEdmlld2QAKkVsaXhpci5MaXZlQWRtaW4uQ29tcG9uZW50cy5Db250YWluZXIuVmlld20AAAAIb25fbW91bnRkAANuaWxtAAAABHJlcG9kABRFbGl4aXIuUGV0YWxQcm8uUmVwb20AAAAKc2Vzc2lvbl9pZG0AAAAkMDk0OGE0OTAtMzYwNS00NGIxLThiNWMtMWNlNzUwM2FhZDZlbQAAAAV0aXRsZW0AAAAJTGl2ZUFkbWluZAAEdmlld2QAJUVsaXhpci5MaXZlQWRtaW4uQ29tcG9uZW50cy5Db250YWluZXJuBgBH7xOWiQFiAAFRgA.tSZHvA6kcLlHZ4givnmKya8N-Y4kISpYOgNc4tX1DTE", "static" => "SFMyNTY.g2gDaAJhBXQAAAADZAAKYXNzaWduX25ld2pkAAVmbGFzaHQAAAAAZAACaWRtAAAAFHBoeC1GM1dtUWEzZEs3clNETmhrbgYAR-8TlokBYgABUYA.KZG1iBvp5NKPqzCFVqAgpbn9loMJH2AeoxJbP-_hVdw", "url" => "http://local.wunderwork.io:4000/admin/min/org-memberships"}, {#PID<0.923.0>, #Reference<0.673198271.1378091009.215671>}, %Phoenix.Socket{assigns: %{}, channel: Phoenix.LiveView.Channel, channel_pid: nil, endpoint: PetalProWeb.Endpoint, handler: Phoenix.LiveView.Socket, id: "users_sessions:Fj4fDG5YYxwH1nYkQiinl7XMVuR6erxmMlo6nPGeXcU=", joined: false, join_ref: "11", private: %{connect_info: %{session: %{"_csrf_token" => "56kJyKnsOud96Sp07HzLCPHA", "live_socket_id" => "users_sessions:Fj4fDG5YYxwH1nYkQiinl7XMVuR6erxmMlo6nPGeXcU=", "locale" => "en", "user_token" => <<22, 62, 31, 12, 110, 88, 99, 28, 7, 214, 118, 36, 66, 40, 167, 151, 181, 204, 86, 228, 122, 122, 188, 102, 50, 90, 58, 156, 241, 158, 93, ...>>}, user_agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36"}}, pubsub_server: PetalPro.PubSub, ref: nil, serializer: Phoenix.Socket.V2.JSONSerializer, topic: "lv:phx-F3WmQa3dK7rSDNhk", transport: :websocket, transport_pid: #PID<0.923.0>}}
State: #Reference<0.673198271.1378091009.215673>
[error] an exception was raised:
    ** (KeyError) key :locale not found in: nil. If you are using the dot syntax, such as map.field, make sure the left-hand side of the dot is a map
        (live_admin 0.10.0) lib/live_admin/router.ex:117: LiveAdmin.Router.on_mount/4
        (phoenix_live_view 0.19.5) lib/phoenix_live_view/lifecycle.ex:149: anonymous fn/4 in Phoenix.LiveView.Lifecycle.mount/3
        (phoenix_live_view 0.19.5) lib/phoenix_live_view/lifecycle.ex:212: Phoenix.LiveView.Lifecycle.reduce_socket/3
        (phoenix_live_view 0.19.5) lib/phoenix_live_view/utils.ex:392: anonymous fn/6 in Phoenix.LiveView.Utils.maybe_call_live_view_mount!/5
        (telemetry 1.2.1) /home/mario/wunderwork_petal/deps/telemetry/src/telemetry.erl:321: :telemetry.span/3
        (phoenix_live_view 0.19.5) lib/phoenix_live_view/channel.ex:1041: Phoenix.LiveView.Channel.verified_mount/8
        (phoenix_live_view 0.19.5) lib/phoenix_live_view/channel.ex:59: Phoenix.LiveView.Channel.handle_info/2
        (stdlib 4.3) gen_server.erl:1123: :gen_server.try_dispatch/4
        (stdlib 4.3) gen_server.erl:1200: :gen_server.handle_msg/6
        (stdlib 4.3) proc_lib.erl:240: :proc_lib.init_p_do_apply/3

App versions:

"phoenix": "1.7.7"
"phoenix_ecto": "4.4.2"
"phoenix_html": "3.3.1"
"phoenix_live_dashboard": "0.8.0"
"phoenix_live_reload": "1.4.1"
"phoenix_live_view": "0.19.5"
"phoenix_pubsub": "2.1.3"
"phoenix_swoosh": "1.2.0"
"phoenix_template": "1.0.3"
"phoenix_view": "2.0.2"
"plug": "1.14.2"
"plug_cowboy": "2.6.1"

Use of reserved `:socket` assign results in 500 on LiveView 0.18.17.

Describe the bug
Updating to LiveView 0.18.17 results in ** (ArgumentError) :socket is a reserved assign by LiveView and it cannot be set directly when visiting the LiveAdmin index page.

To Reproduce
Upgrade to LiveView 0.18.17, and visit the LiveAdmin index page.

[info] Sent 500 in 162ms
[error] #PID<0.1201.0> running BeaconWeb.Endpoint (connection #PID<0.1083.0>, stream id 22) terminated
Server: localhost:4000 (http)
Request: GET /admin
** (exit) an exception was raised:
    ** (ArgumentError) :socket is a reserved assign by LiveView and it cannot be set directly
        (phoenix_live_view 0.18.17) lib/phoenix_component.ex:1258: Phoenix.Component.validate_assign_key!/1
        (phoenix_live_view 0.18.17) lib/phoenix_component.ex:1209: Phoenix.Component.assign/3
        (elixir 1.14.1) lib/enum.ex:2468: Enum."-reduce/3-lists^foldl/2-0-"/3
        (live_admin 0.7.2) lib/live_admin/router.ex:81: LiveAdmin.Router.on_mount/4
        (phoenix_live_view 0.18.17) lib/phoenix_live_view/lifecycle.ex:149: anonymous fn/4 in Phoenix.LiveView.Lifecycle.mount/3
        (phoenix_live_view 0.18.17) lib/phoenix_live_view/lifecycle.ex:215: Phoenix.LiveView.Lifecycle.reduce_socket/3
        (phoenix_live_view 0.18.17) lib/phoenix_live_view/utils.ex:389: anonymous fn/6 in Phoenix.LiveView.Utils.maybe_call_live_view_mount!/5
        (telemetry 1.2.1) /Users/jmill/code/viabeacon/beacon/deps/telemetry/src/telemetry.erl:321: :telemetry.span/3
        (phoenix_live_view 0.18.17) lib/phoenix_live_view/static.ex:278: Phoenix.LiveView.Static.call_mount_and_handle_params!/5
        (phoenix_live_view 0.18.17) lib/phoenix_live_view/static.ex:119: Phoenix.LiveView.Static.render/3
        (phoenix_live_view 0.18.17) lib/phoenix_live_view/controller.ex:39: Phoenix.LiveView.Controller.live_render/3
        (phoenix 1.6.16) lib/phoenix/router.ex:354: Phoenix.Router.__call__/2
        ...(application code omitted)

Environment:

  • LiveAdmin 0.7.2
  • Phoenix 1.6.16
  • LiveView 0.18.17

Managing Associations within Parent Forms

Is it possible to edit has_many associations in the parent schema?

I have already come across this issue on GitHub: #15, but it only explains how to select the parent ID.

For instance:

It would be beneficial if I could create or edit ProductVariants for a Product directly within the product form.

Product Schema:

  use Ecto.Schema
  use LiveAdmin.Resource
  import Ecto.Changeset

  schema "products" do
    field :description, :string
    field :pseudo_price, :decimal
    field :title, :string
    field :gender, Ecto.Enum, values: [:male, :female, :unisex]

    has_many :product_variants, ClothoBackend.Products.ProductVariant

    timestamps()
  end

  @doc false
  def changeset(product, attrs) do
    product
    |> cast(attrs, [
      :title,
      :description,
      :pseudo_price,
      :gender
    ])
    |> cast_assoc(:product_variants)
    |> validate_required([
      :title,
      :description,
      :pseudo_price,
      :gender
    ])
  end
end

Product Variants Schema:

  use Ecto.Schema
  use LiveAdmin.Resource
  import Ecto.Changeset

  schema "product_variants" do
    field(:pseudo_price, :decimal)
    field(:title, :string)


    belongs_to(:product, ClothoBackend.Products.Product)


    field :temp_id, :string, virtual: true
    field :delete, :boolean, virtual: true
    field :edit, :boolean, virtual: true

    timestamps()
  end

  @doc false
  def changeset(product_variant, attrs) do
    product_variant
    # So its persisted
    |> Map.put(:temp_id, product_variant.temp_id || attrs["temp_id"])
    |> cast(attrs, [:title, :pseudo_price, :delete, :edit])
    |> validate_required([:pseudo_price])
    |> maybe_mark_for_deletion()
  end

  defp maybe_mark_for_deletion(%{data: %{id: nil}} = changeset), do: changeset

  defp maybe_mark_for_deletion(changeset) do
    if get_change(changeset, :delete) do
      %{changeset | action: :delete}
    else
      changeset
    end
  end
end

BUG: Ecto.Enum no longer selects?

This might be the intended behavior or a regression, so I'm reluctant to make this a bug ticket. I noticed in 0.8.0 that Ecto.Enum fields would be a select input with the options nicely set-up. In main, at least as of 31f76dc, the fields now render as textareas. Example of currently rendered HTML:

<div class="field__group field__enum">
  <label class="field__label" for="params_status">Status</label>
      <textarea disabled="" id="params_status" name="params[status]" rows="1">:talent_interest_pending</textarea>  
</div>

Edit and view Associations

Hello,

First of all, you did a great job with embedded schemas: it works out of the box and saves perfectly, even for custom types! Impressive, other admin solutions failed to do so!

Unfortunately, all other schema-to-schema or table-to-table associations (1 to many, many to many) can only display a disabled id field.
Is there a way to render such associations similar to embedded ones? Could you please let me know where help is needed and what is already in progress?

Best,
Alexander

Cannot compile on new phoenix 1.7.0

== Compilation error in file lib/live_admin/view.ex ==
** (CompileError) lib/live_admin/view.ex:4: module Phoenix.View is not loaded and could not be found
(elixir 1.14.3) expanding macro: Kernel.use/2
lib/live_admin/view.ex:4: LiveAdmin.View (module)

Support alternate primary keys / remove assumption of `:id` key on struct

Describe the bug
If a schema does not have an :id key, it cannot be rendered with LiveAdmin due to the assumption on line 109 at

data-record-id={record.id}

To Reproduce
Steps to reproduce the behavior:
0. Create a schema without an :id key

  1. Go to LiveAdmin / try to list out the resource
  2. See error

Expected behavior
Resource should show correctly

Screenshots
If applicable, add screenshots to help explain your problem.

Environment:

  • OS: [e.g. iOS]
  • Browser [e.g. chrome, safari]
  • Version [e.g. 22]

Additional context
Conversation starts here https://elixirforum.com/t/liveadmin-phoenix-admin-ui-built-on-liveview/46421/33?u=johnnycurran

Fix belongs_to n+1

Currently belongs_to assocs are preloaded at render in the list table, potentially generating a ton of extra queries. It would be better to preload everything with the initial query.

Can't compile with :phoenix_html version 4.0

Thanks for creating and sharing live_admin. It is very useful!

When upgrading the dependencies of my project, specifically :phoenix_html to 4.0, the live_admin package cannot compile.

To Reproduce

  1. include packages :live_admin and :phoenix_html, "~> 4.0" in a project
  2. run mix compile
  3. See error included below

Expected behavior
The project to compile and run

Screenshots
Here's the compiler error message:

==> live_admin
Compiling 23 files (.ex)

== Compilation error in file lib/live_admin/components/resource/form/map_input.ex ==
** (RuntimeError) use Phoenix.HTML is no longer supported in v4.0.

To keep compatibility with previous versions, add {:phoenix_html_helpers, "~> 1.0"} to your mix.exs deps
and then, instead of "use Phoenix.HTML", you might:

    import Phoenix.HTML
    import Phoenix.HTML.Form
    use PhoenixHTMLHelpers


    (phoenix_html 4.0.0) expanding macro: Phoenix.HTML.__using__/1
    lib/live_admin/components/resource/form/map_input.ex:3: LiveAdmin.Components.Container.Form.MapInput (module)
    (elixir 1.15.7) expanding macro: Kernel.use/1
    lib/live_admin/components/resource/form/map_input.ex:3: LiveAdmin.Components.Container.Form.MapInput (module)

Environment:

  • OS: macOS (Sonoma, but I don't think that matters)
  • live _admin Version: 0.11.4

Additional context
phoenix_html changelog

Fix field_class form Ecto.UUID

I saw you ran into a similar issue with 31f76dc

The same issue occurs if a field is Ecto.UUID.

** (FunctionClauseError) no function clause matching in LiveAdmin.Components.Container.Form.field_class/1
    (live_admin 0.8.0) lib/live_admin/components/resource/form.ex:471: LiveAdmin.Components.Container.Form.field_class(Ecto.UUID)
    (live_admin 0.8.0) lib/live_admin/components/resource/form.ex:238: anonymous fn/2 in LiveAdmin.Components.Container.Form.field/1
    (live_admin 0.8.0) /home/mario/code/wunderwork/petal_ww/deps/live_admin/lib/live_admin/components/resource/form.ex:66: LiveAdmin.Components.Container.Form.render/1
    (elixir 1.14.4) lib/enum.ex:1780: Enum."-map_reduce/3-lists^mapfoldl/2-0-"/3
    (elixir 1.14.4) lib/enum.ex:1780: Enum."-map_reduce/3-lists^mapfoldl/2-0-"/3
    (phoenix_live_view 0.19.3) lib/phoenix_live_view/diff.ex:486: Phoenix.LiveView.Diff.traverse/7
    (phoenix_live_view 0.19.3) lib/phoenix_live_view/diff.ex:538: anonymous fn/4 in Phoenix.LiveView.Diff.traverse_dynamic/7
    (elixir 1.14.4) lib/enum.ex:2468: Enum."-reduce/3-lists^foldl/2-0-"/3
    (phoenix_live_view 0.19.3) lib/phoenix_live_view/diff.ex:384: Phoenix.LiveView.Diff.traverse/7
    (phoenix_live_view 0.19.3) lib/phoenix_live_view/diff.ex:538: anonymous fn/4 in Phoenix.LiveView.Diff.traverse_dynamic/7
    (elixir 1.14.4) lib/enum.ex:2468: Enum."-reduce/3-lists^foldl/2-0-"/3
    (phoenix_live_view 0.19.3) lib/phoenix_live_view/diff.ex:384: Phoenix.LiveView.Diff.traverse/7
    (phoenix_live_view 0.19.3) lib/phoenix_live_view/diff.ex:538: anonymous fn/4 in Phoenix.LiveView.Diff.traverse_dynamic/7
    (elixir 1.14.4) lib/enum.ex:2468: Enum."-reduce/3-lists^foldl/2-0-"/3
    (phoenix_live_view 0.19.3) lib/phoenix_live_view/diff.ex:384: Phoenix.LiveView.Diff.traverse/7
    (phoenix_live_view 0.19.3) lib/phoenix_live_view/diff.ex:711: Phoenix.LiveView.Diff.render_component/9
    (phoenix_live_view 0.19.3) lib/phoenix_live_view/diff.ex:657: anonymous fn/5 in Phoenix.LiveView.Diff.render_pending_components/6
    (elixir 1.14.4) lib/enum.ex:2468: Enum."-reduce/3-lists^foldl/2-0-"/3

foreign key fields skip `render_field`

It seems to be impossible to customize the rendering of foreign key fields (org_id)? These fields don't seem to go through the render_field function. I was trying to specify how to handle those in :render_with .

Originally posted by @Rio517 in #52 (comment)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.