mirror of
https://github.com/ash-project/ash_admin.git
synced 2024-09-19 12:53:28 +12:00
improvement: remove compile-time router, use params instead
improvement!: support latest surface/phoenix
This commit is contained in:
parent
23de463c92
commit
b214535f0c
28 changed files with 918 additions and 1207 deletions
|
@ -11,6 +11,7 @@ locals_without_parens = [
|
|||
polymorphic_tables: 1,
|
||||
read_actions: 1,
|
||||
relationship_display_fields: 1,
|
||||
show?: 1,
|
||||
show_action: 1,
|
||||
table_columns: 1,
|
||||
type: 1,
|
||||
|
|
2
dev.exs
2
dev.exs
|
@ -38,7 +38,7 @@ defmodule DemoWeb.Router do
|
|||
pipe_through :browser
|
||||
import AshAdmin.Router
|
||||
|
||||
ash_admin("/", apis: [Demo.Accounts.Api, Demo.Tickets.Api])
|
||||
ash_admin("/")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
defmodule Demo.Accounts.Api do
|
||||
@moduledoc false
|
||||
use Ash.Api
|
||||
use Ash.Api,
|
||||
extensions: [AshAdmin.Api]
|
||||
|
||||
admin do
|
||||
show? true
|
||||
end
|
||||
|
||||
resources do
|
||||
resource Demo.Accounts.User
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
defmodule Demo.Tickets.Api do
|
||||
@moduledoc false
|
||||
use Ash.Api
|
||||
use Ash.Api,
|
||||
extensions: [AshAdmin.Api]
|
||||
|
||||
alias Demo.Tickets.{Comment, Customer, Representative, Ticket, TicketLink, Organization}
|
||||
|
||||
admin do
|
||||
show? true
|
||||
end
|
||||
|
||||
resources do
|
||||
resource(Customer)
|
||||
resource(Representative)
|
||||
|
|
|
@ -36,7 +36,7 @@ defmodule AshAdmin.ActorPlug do
|
|||
session["actor_paused"]
|
||||
end
|
||||
|
||||
actor = actor_from_session(session)
|
||||
actor = actor_from_session(conn.private.phoenix_endpoint, session)
|
||||
|
||||
authorizing = session_bool(authorizing)
|
||||
actor_paused = session_bool(actor_paused)
|
||||
|
@ -56,37 +56,60 @@ defmodule AshAdmin.ActorPlug do
|
|||
|
||||
def actor_session(conn, _), do: conn
|
||||
|
||||
def actor_api_from_session(%{"actor_api" => api}) do
|
||||
Module.concat([api])
|
||||
def actor_api_from_session(endpoint, %{"actor_api" => api}) do
|
||||
otp_app = endpoint.config(:otp_app)
|
||||
apis = Application.get_env(otp_app, :ash_apis)
|
||||
|
||||
Enum.find(apis, fn allowed_api ->
|
||||
AshAdmin.Api.show?(allowed_api) && AshAdmin.Api.name(allowed_api) == api
|
||||
end)
|
||||
end
|
||||
|
||||
def actor_from_session(%{
|
||||
def actor_api_from_session(_, _), do: nil
|
||||
|
||||
def actor_from_session(endpoint, %{
|
||||
"actor_resource" => resource,
|
||||
"actor_api" => api,
|
||||
"actor_primary_key" => primary_key,
|
||||
"actor_action" => action
|
||||
})
|
||||
when not is_nil(resource) and not is_nil(api) do
|
||||
resource = Module.concat([resource])
|
||||
api = Module.concat([api])
|
||||
otp_app = endpoint.config(:otp_app)
|
||||
apis = Application.get_env(otp_app, :ash_apis)
|
||||
|
||||
action =
|
||||
if action do
|
||||
Ash.Resource.Info.action(resource, String.to_existing_atom(action), :read)
|
||||
api =
|
||||
Enum.find(apis, fn allowed_api ->
|
||||
AshAdmin.Api.show?(allowed_api) && AshAdmin.Api.name(allowed_api) == api
|
||||
end)
|
||||
|
||||
resource =
|
||||
if api do
|
||||
api
|
||||
|> Ash.Api.resources()
|
||||
|> Enum.find(fn api_resource ->
|
||||
AshAdmin.Resource.name(api_resource) == resource
|
||||
end)
|
||||
end
|
||||
|
||||
case decode_primary_key(resource, primary_key) do
|
||||
:error ->
|
||||
nil
|
||||
if api && resource do
|
||||
action =
|
||||
if action do
|
||||
Ash.Resource.Info.action(resource, String.to_existing_atom(action), :read)
|
||||
end
|
||||
|
||||
{:ok, filter} ->
|
||||
resource
|
||||
|> Ash.Query.filter(^filter)
|
||||
|> api.read_one!(action: action)
|
||||
case decode_primary_key(resource, primary_key) do
|
||||
:error ->
|
||||
nil
|
||||
|
||||
{:ok, filter} ->
|
||||
resource
|
||||
|> Ash.Query.filter(^filter)
|
||||
|> api.read_one!(action: action)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def actor_from_session(_), do: nil
|
||||
def actor_from_session(_, _), do: nil
|
||||
|
||||
def session_bool(value) do
|
||||
case value do
|
||||
|
|
|
@ -4,7 +4,13 @@ defmodule AshAdmin.Api do
|
|||
name: :admin,
|
||||
schema: [
|
||||
name: [
|
||||
type: :string
|
||||
type: :string,
|
||||
doc: "The name of the api in the dashboard. Will be derived if not set."
|
||||
],
|
||||
show?: [
|
||||
type: :boolean,
|
||||
default: false,
|
||||
doc: "Wether or not this api and its resources should be included in the admin dashboard"
|
||||
]
|
||||
]
|
||||
}
|
||||
|
@ -26,6 +32,10 @@ defmodule AshAdmin.Api do
|
|||
Ash.Dsl.Extension.get_opt(api, [:admin], :name, nil, true) || default_name(api)
|
||||
end
|
||||
|
||||
def show?(api) do
|
||||
Ash.Dsl.Extension.get_opt(api, [:admin], :show?, false, true)
|
||||
end
|
||||
|
||||
defp default_name(api) do
|
||||
split = api |> Module.split()
|
||||
|
||||
|
|
|
@ -7,30 +7,30 @@ defmodule AshAdmin.Components.HeroIcon do
|
|||
prop(class, :css_class)
|
||||
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
{{render_heroicon(@name, @type, assigns)}}
|
||||
~F"""
|
||||
{render_heroicon(@name, @type, assigns)}
|
||||
"""
|
||||
end
|
||||
|
||||
defp render_heroicon("minus", "solid", assigns) do
|
||||
~H"""
|
||||
<svg class={{@class}} xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
~F"""
|
||||
<svg class={@class} xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 12H4" />
|
||||
</svg>
|
||||
"""
|
||||
end
|
||||
|
||||
defp render_heroicon("plus", "solid", assigns) do
|
||||
~H"""
|
||||
<svg class={{@class}} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
~F"""
|
||||
<svg class={@class} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M10 5a1 1 0 011 1v3h3a1 1 0 110 2h-3v3a1 1 0 11-2 0v-3H6a1 1 0 110-2h3V6a1 1 0 011-1z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
"""
|
||||
end
|
||||
|
||||
defp render_heroicon("search-circle", "solid", assigns) do
|
||||
~H"""
|
||||
<svg class={{@class}} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
~F"""
|
||||
<svg class={@class} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path d="M9 9a2 2 0 114 0 2 2 0 01-4 0z" />
|
||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-13a4 4 0 00-3.446 6.032l-2.261 2.26a1 1 0 101.414 1.415l2.261-2.261A4 4 0 1011 5z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
|
@ -38,48 +38,48 @@ defmodule AshAdmin.Components.HeroIcon do
|
|||
end
|
||||
|
||||
defp render_heroicon("key", "solid", assigns) do
|
||||
~H"""
|
||||
<svg class={{@class}} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
~F"""
|
||||
<svg class={@class} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M18 8a6 6 0 01-7.743 5.743L10 14l-1 1-1 1H6v2H2v-4l4.257-4.257A6 6 0 1118 8zm-6-4a1 1 0 100 2 2 2 0 012 2 1 1 0 102 0 4 4 0 00-4-4z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
"""
|
||||
end
|
||||
|
||||
defp render_heroicon("check", "solid", assigns) do
|
||||
~H"""
|
||||
<svg class={{@class}} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
~F"""
|
||||
<svg class={@class} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
"""
|
||||
end
|
||||
|
||||
defp render_heroicon("x", "solid", assigns) do
|
||||
~H"""
|
||||
<svg class={{@class}} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
~F"""
|
||||
<svg class={@class} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
"""
|
||||
end
|
||||
|
||||
defp render_heroicon("information-circle", "solid", assigns) do
|
||||
~H"""
|
||||
<svg class={{@class}} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
~F"""
|
||||
<svg class={@class} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
"""
|
||||
end
|
||||
|
||||
defp render_heroicon("pencil", "solid", assigns) do
|
||||
~H"""
|
||||
<svg class={{@class}} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
~F"""
|
||||
<svg class={@class} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z" />
|
||||
</svg>
|
||||
"""
|
||||
end
|
||||
|
||||
defp render_heroicon("x-circle", "solid", assigns) do
|
||||
~H"""
|
||||
<svg class={{@class}} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
~F"""
|
||||
<svg class={@class} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
"""
|
||||
|
|
|
@ -5,8 +5,8 @@ defmodule AshAdmin.Components.Resource.AttributeTable do
|
|||
prop(resource, :any, required: true)
|
||||
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<div :if={{ Enum.any?(attributes(@resource)) }}>
|
||||
~F"""
|
||||
<div :if={Enum.any?(attributes(@resource))}>
|
||||
<h1 class="text-center text-3xl rounded-t py-8">
|
||||
Attributes
|
||||
</h1>
|
||||
|
@ -24,20 +24,20 @@ defmodule AshAdmin.Components.Resource.AttributeTable do
|
|||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
class={{ "h-10", "bg-gray-200": rem(index, 2) == 0 }}
|
||||
:for.with_index={{ {attribute, index} <- attributes(@resource) }}
|
||||
class={"h-10", "bg-gray-200": rem(index, 2) == 0}
|
||||
:for.with_index={{attribute, index} <- attributes(@resource)}
|
||||
>
|
||||
<th scope="row" class="text-center px-3">
|
||||
{{ attribute.name }}
|
||||
{attribute.name}
|
||||
</th>
|
||||
<td class="text-center px-3">
|
||||
{{ attribute_type(attribute) }}
|
||||
{attribute_type(attribute)}
|
||||
</td>
|
||||
<td class="text-center max-w-sm min-w-sm">{{ attribute.description }}</td>
|
||||
<td class="text-center">{{ to_string(attribute.primary_key?) }}</td>
|
||||
<td class="text-center">{{ to_string(attribute.private?) }}</td>
|
||||
<td class="text-center">{{ to_string(attribute.allow_nil?) }}</td>
|
||||
<td class="text-center">{{ to_string(attribute.writable?) }}</td>
|
||||
<td class="text-center max-w-sm min-w-sm">{attribute.description}</td>
|
||||
<td class="text-center">{to_string(attribute.primary_key?)}</td>
|
||||
<td class="text-center">{to_string(attribute.private?)}</td>
|
||||
<td class="text-center">{to_string(attribute.allow_nil?)}</td>
|
||||
<td class="text-center">{to_string(attribute.writable?)}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
@ -144,25 +144,25 @@ defmodule AshAdmin.Components.Resource.DataTable do
|
|||
end
|
||||
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
~F"""
|
||||
<div>
|
||||
<div class="sm:mt-0 bg-gray-300 min-h-screen">
|
||||
<div
|
||||
:if={{ @action.arguments != [] }}
|
||||
:if={@action.arguments != []}
|
||||
class="md:grid md:grid-cols-3 md:gap-6 md:mx-16 md:pt-10 mb-10"
|
||||
>
|
||||
<div class="md:mt-0 md:col-span-2">
|
||||
<div class="shadow-lg overflow-hidden pt-2 sm:rounded-md bg-white">
|
||||
<div class="px-4 sm:p-6">
|
||||
<Form
|
||||
:if={{ @query }}
|
||||
as={{ :query }}
|
||||
for={{ @query }}
|
||||
:if={@query}
|
||||
as={:query}
|
||||
for={@query}
|
||||
change="validate"
|
||||
submit="save"
|
||||
:let={{ form: form }}
|
||||
:let={form: form}
|
||||
>
|
||||
{{ AshAdmin.Components.Resource.Form.render_attributes(assigns, @resource, @action, form) }}
|
||||
{AshAdmin.Components.Resource.Form.render_attributes(assigns, @resource, @action, form)}
|
||||
<div class="px-4 py-3 text-right sm:px-6">
|
||||
<button
|
||||
type="submit"
|
||||
|
@ -178,46 +178,46 @@ defmodule AshAdmin.Components.Resource.DataTable do
|
|||
</div>
|
||||
|
||||
<div
|
||||
:if={{ AshAdmin.Resource.polymorphic?(@resource) }}
|
||||
:if={AshAdmin.Resource.polymorphic?(@resource)}
|
||||
class="md:grid md:grid-cols-3 md:gap-6 md:mx-16 md:pt-10 mb-10"
|
||||
>
|
||||
<div class="md:mt-0 md:col-span-2">
|
||||
<div class="px-4 sm:p-6">
|
||||
<AshAdmin.Components.Resource.SelectTable
|
||||
resource={{ @resource }}
|
||||
resource={@resource}
|
||||
on_change="change_table"
|
||||
table={{ @table }}
|
||||
tables={{ @tables }}
|
||||
table={@table}
|
||||
tables={@tables}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div :if={{ @action.arguments == [] || @params["args"] }} class="h-full overflow-scroll md:mx-4">
|
||||
<div :if={@action.arguments == [] || @params["args"]} class="h-full overflow-scroll md:mx-4">
|
||||
<div class="shadow-lg overflow-scroll sm:rounded-md bg-white">
|
||||
<div :if={{ match?({:error, _}, @data) }}>
|
||||
{{ {:error, %{query: query}} = @data
|
||||
nil }}
|
||||
<div :if={match?({:error, _}, @data)}>
|
||||
{{:error, %{query: query}} = @data
|
||||
nil}
|
||||
<ul>
|
||||
<li :for={{ error <- query.errors }}>
|
||||
{{ message(error) }}
|
||||
<li :for={error <- query.errors}>
|
||||
{message(error)}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="px-2">
|
||||
{{ render_pagination_links(assigns, :top) }}
|
||||
{render_pagination_links(assigns, :top)}
|
||||
<Table
|
||||
:if={{ match?({:ok, _data}, @data) }}
|
||||
table={{ @table }}
|
||||
data={{ data(@data) }}
|
||||
resource={{ @resource }}
|
||||
api={{ @api }}
|
||||
set_actor={{ @set_actor }}
|
||||
attributes={{ AshAdmin.Resource.table_columns(@resource) }}
|
||||
format_fields={{ AshAdmin.Resource.format_fields(@resource) }}
|
||||
prefix={{ @prefix }}
|
||||
:if={match?({:ok, _data}, @data)}
|
||||
table={@table}
|
||||
data={data(@data)}
|
||||
resource={@resource}
|
||||
api={@api}
|
||||
set_actor={@set_actor}
|
||||
attributes={AshAdmin.Resource.table_columns(@resource)}
|
||||
format_fields={AshAdmin.Resource.format_fields(@resource)}
|
||||
prefix={@prefix}
|
||||
/>
|
||||
{{ render_pagination_links(assigns, :bottom) }}
|
||||
{render_pagination_links(assigns, :bottom)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -264,36 +264,28 @@ defmodule AshAdmin.Components.Resource.DataTable do
|
|||
def handle_event("change_table", %{"table" => %{"table" => table}}, socket) do
|
||||
{:noreply,
|
||||
push_redirect(socket,
|
||||
to:
|
||||
ash_action_path(
|
||||
socket.assigns.prefix,
|
||||
socket.assigns.api,
|
||||
socket.assigns.resource,
|
||||
socket.assigns.action.type,
|
||||
socket.assigns.action.name,
|
||||
table
|
||||
)
|
||||
to: self_path(socket.assigns.url_path, socket.assigns.params, %{"table" => table})
|
||||
)}
|
||||
end
|
||||
|
||||
defp render_pagination_links(assigns, placement) do
|
||||
~H"""
|
||||
~F"""
|
||||
<div
|
||||
:if={{ (offset?(@data) || keyset?(@data)) && show_pagination_links?(@data, placement) }}
|
||||
:if={(offset?(@data) || keyset?(@data)) && show_pagination_links?(@data, placement)}
|
||||
class="w-5/6 mx-auto"
|
||||
>
|
||||
<div class="bg-white px-4 py-3 flex items-center justify-between border-t border-gray-200 sm:px-6">
|
||||
<div class="flex-1 flex justify-between sm:hidden">
|
||||
<button
|
||||
:if={{ !(keyset?(@data) && is_nil(@params["page"])) && prev_page?(@data) }}
|
||||
:if={!(keyset?(@data) && is_nil(@params["page"])) && prev_page?(@data)}
|
||||
:on-click="prev_page"
|
||||
class="relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:text-gray-500"
|
||||
>
|
||||
Previous
|
||||
</button>
|
||||
{{ render_pagination_information(assigns, true) }}
|
||||
{render_pagination_information(assigns, true)}
|
||||
<button
|
||||
:if={{ next_page?(@data) }}
|
||||
:if={next_page?(@data)}
|
||||
:on-click="next_page"
|
||||
class="ml-3 relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:text-gray-500"
|
||||
>
|
||||
|
@ -302,12 +294,12 @@ defmodule AshAdmin.Components.Resource.DataTable do
|
|||
</div>
|
||||
<div class="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
|
||||
<div>
|
||||
{{ render_pagination_information(assigns) }}
|
||||
{render_pagination_information(assigns)}
|
||||
</div>
|
||||
<div>
|
||||
<nav class="relative z-0 inline-flex rounded-md shadow-sm -space-x-px" aria-label="Pagination">
|
||||
<button
|
||||
:if={{ !(keyset?(@data) && is_nil(@params["page"])) && prev_page?(@data) }}
|
||||
:if={!(keyset?(@data) && is_nil(@params["page"])) && prev_page?(@data)}
|
||||
:on-click="prev_page"
|
||||
class="relative inline-flex items-center px-2 py-2 rounded-l-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50"
|
||||
>
|
||||
|
@ -327,13 +319,13 @@ defmodule AshAdmin.Components.Resource.DataTable do
|
|||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<span :if={{ offset?(@data) }}>
|
||||
{{ render_page_links(assigns, leading_page_nums(@data)) }}
|
||||
{{ render_middle_page_num(assigns, @page_num, trailing_page_nums(@data)) }}
|
||||
{{ render_page_links(assigns, trailing_page_nums(@data)) }}
|
||||
<span :if={offset?(@data)}>
|
||||
{render_page_links(assigns, leading_page_nums(@data))}
|
||||
{render_middle_page_num(assigns, @page_num, trailing_page_nums(@data))}
|
||||
{render_page_links(assigns, trailing_page_nums(@data))}
|
||||
</span>
|
||||
<button
|
||||
:if={{ next_page?(@data) }}
|
||||
:if={next_page?(@data)}
|
||||
:on-click="next_page"
|
||||
class="relative inline-flex items-center px-2 py-2 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50"
|
||||
>
|
||||
|
@ -362,33 +354,33 @@ defmodule AshAdmin.Components.Resource.DataTable do
|
|||
end
|
||||
|
||||
defp render_page_links(assigns, page_nums) do
|
||||
~H"""
|
||||
~F"""
|
||||
<button
|
||||
:on-click="specific_page"
|
||||
phx-value-page={{ i }}
|
||||
:for={{ i <- page_nums }}
|
||||
class={{
|
||||
phx-value-page={i}
|
||||
:for={i <- page_nums}
|
||||
class={
|
||||
"relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50",
|
||||
"bg-gray-300": @page_num == i
|
||||
}}
|
||||
}
|
||||
>
|
||||
{{ i }}
|
||||
{i}
|
||||
</button>
|
||||
"""
|
||||
end
|
||||
|
||||
defp render_pagination_information(assigns, small? \\ false) do
|
||||
~H"""
|
||||
<p class={{ "text-sm text-gray-700", "sm:hidden": small? }}>
|
||||
<span :if={{ offset?(@data) }}>
|
||||
~F"""
|
||||
<p class={"text-sm text-gray-700", "sm:hidden": small?}>
|
||||
<span :if={offset?(@data)}>
|
||||
Showing
|
||||
<span class="font-medium">{{ first(@data) }}</span>
|
||||
<span class="font-medium">{first(@data)}</span>
|
||||
to
|
||||
<span class="font-medium">{{ last(@data) }}</span>
|
||||
<span class="font-medium">{last(@data)}</span>
|
||||
of
|
||||
</span>
|
||||
<span :if={{ count(@data) }}>
|
||||
<span class="font-medium">{{ count(@data) }}</span>
|
||||
<span :if={count(@data)}>
|
||||
<span class="font-medium">{count(@data)}</span>
|
||||
results
|
||||
</span>
|
||||
</p>
|
||||
|
@ -435,19 +427,19 @@ defmodule AshAdmin.Components.Resource.DataTable do
|
|||
defp render_middle_page_num(assigns, num, trailing_page_nums) do
|
||||
ellipsis? = num in trailing_page_nums || num <= 3
|
||||
|
||||
~H"""
|
||||
~F"""
|
||||
<span
|
||||
:if={{ show_ellipses?(@data) }}
|
||||
class={{
|
||||
:if={show_ellipses?(@data)}
|
||||
class={
|
||||
"relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-700",
|
||||
"bg-gray-300": !ellipsis?
|
||||
}}
|
||||
}
|
||||
>
|
||||
<span :if={{ ellipsis? }}>
|
||||
<span :if={ellipsis?}>
|
||||
...
|
||||
</span>
|
||||
<span :if={{ !ellipsis? }}>
|
||||
{{ num }}
|
||||
<span :if={!ellipsis?}>
|
||||
{num}
|
||||
</span>
|
||||
</span>
|
||||
"""
|
||||
|
|
|
@ -90,23 +90,23 @@ defmodule AshAdmin.Components.Resource.Form do
|
|||
end
|
||||
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
~F"""
|
||||
<div class="md:pt-10 sm:mt-0 bg-gray-300 min-h-screen">
|
||||
<div class="md:grid md:grid-cols-3 md:gap-6 md:mx-16 md:mt-10">
|
||||
<div class="mt-5 md:mt-0 md:col-span-2">
|
||||
{{ render_form(assigns) }}
|
||||
{render_form(assigns)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div :if={{ @type != :create }} class="md:grid md:grid-cols-3 md:gap-6 md:mx-16 md:mt-10">
|
||||
<div :if={@type != :create} class="md:grid md:grid-cols-3 md:gap-6 md:mx-16 md:mt-10">
|
||||
<div class="mt-5 md:mt-0 md:col-span-2">
|
||||
{{ AshAdmin.Components.Resource.Show.render_show(
|
||||
{AshAdmin.Components.Resource.Show.render_show(
|
||||
assigns,
|
||||
@record,
|
||||
@resource,
|
||||
"Original Record",
|
||||
false
|
||||
) }}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -114,61 +114,61 @@ defmodule AshAdmin.Components.Resource.Form do
|
|||
end
|
||||
|
||||
defp render_form(assigns) do
|
||||
~H"""
|
||||
~F"""
|
||||
<div class="shadow-lg overflow-hidden sm:rounded-md bg-white">
|
||||
<div :if={{ @form.submitted_once? }} class="ml-4 mt-4 text-red-500">
|
||||
<div :if={@form.submitted_once?} class="ml-4 mt-4 text-red-500">
|
||||
<ul>
|
||||
<li :for={{ {field, message} <- AshPhoenix.Form.errors(@form) }}>
|
||||
<span :if={{field}}>
|
||||
{{ to_name(field) }}:
|
||||
<li :for={{field, message} <- AshPhoenix.Form.errors(@form)}>
|
||||
<span :if={field}>
|
||||
{to_name(field)}:
|
||||
</span>
|
||||
<span>
|
||||
{{message}}
|
||||
{message}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<h1 class="text-lg mt-2 ml-4">
|
||||
{{ String.capitalize(to_string(@action.type)) }} {{AshAdmin.Resource.name(@resource)}}
|
||||
{String.capitalize(to_string(@action.type))} {AshAdmin.Resource.name(@resource)}
|
||||
</h1>
|
||||
<div class="flex justify-between col-span-6 mr-4 mt-2 overflow-auto px-4">
|
||||
<AshAdmin.Components.Resource.SelectTable
|
||||
resource={{ @resource }}
|
||||
resource={@resource}
|
||||
on_change="change_table"
|
||||
table={{ @table }}
|
||||
tables={{ @tables }}
|
||||
table={@table}
|
||||
tables={@tables}
|
||||
/>
|
||||
<Form
|
||||
as={{ :action }}
|
||||
for={{ :action }}
|
||||
as={:action}
|
||||
for={:action}
|
||||
change="change_action"
|
||||
opts={{id: @id <> "_action_form"}}
|
||||
opts={id: @id <> "_action_form"}
|
||||
>
|
||||
<FieldContext name="action">
|
||||
<Label>Action</Label>
|
||||
<Select
|
||||
opts={{disabled: Enum.count(actions(@resource, @type)) <= 1 }}
|
||||
selected={{ to_string(@action.name) }} options={{ actions(@resource, @type) }} />
|
||||
opts={disabled: Enum.count(actions(@resource, @type)) <= 1}
|
||||
selected={to_string(@action.name)} options={actions(@resource, @type)} />
|
||||
</FieldContext>
|
||||
</Form>
|
||||
</div>
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<Form
|
||||
for={{ @form }}
|
||||
for={@form}
|
||||
change="validate"
|
||||
submit="save"
|
||||
opts={{ autocomplete: false, id: @id <> "_form" }}
|
||||
:let={{ form: form }}
|
||||
opts={autocomplete: false, id: @id <> "_form"}
|
||||
:let={form: form}
|
||||
>
|
||||
<input hidden phx-hook="FormChange" id="resource_form">
|
||||
<input :for={{kv <- form.hidden}} name={{form.name <> "[#{elem(kv, 0)}]"}} value={{elem(kv, 1)}} hidden>
|
||||
{{ render_attributes(assigns, @resource, @action, form) }}
|
||||
<input :for={kv <- form.hidden} name={form.name <> "[#{elem(kv, 0)}]"} value={elem(kv, 1)} hidden>
|
||||
{render_attributes(assigns, @resource, @action, form)}
|
||||
<div class="px-4 py-3 text-right sm:px-6">
|
||||
<button
|
||||
type="submit"
|
||||
class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
>
|
||||
{{ save_button_text(@type) }}
|
||||
{save_button_text(@type)}
|
||||
</button>
|
||||
</div>
|
||||
</Form>
|
||||
|
@ -188,72 +188,72 @@ defmodule AshAdmin.Components.Resource.Form do
|
|||
exactly \\ nil,
|
||||
skip \\ []
|
||||
) do
|
||||
~H"""
|
||||
{{ {attributes, flags, bottom_attributes, relationship_args} = attributes(resource, action, exactly)
|
||||
nil }}
|
||||
<Context put={{ Form, form: form }}>
|
||||
~F"""
|
||||
{{attributes, flags, bottom_attributes, relationship_args} = attributes(resource, action, exactly)
|
||||
nil}
|
||||
<Context put={Form, form: form}>
|
||||
<div class="grid grid-cols-6 gap-6">
|
||||
<div
|
||||
:for={{ attribute <- Enum.reject(attributes, &(&1.name in skip)) }}
|
||||
class={{
|
||||
:for={attribute <- Enum.reject(attributes, &(&1.name in skip))}
|
||||
class={
|
||||
"col-span-6",
|
||||
"sm:col-span-2": short_text?(resource, attribute),
|
||||
"sm:col-span-3": !long_text?(resource, attribute)
|
||||
}}
|
||||
}
|
||||
>
|
||||
<FieldContext name={{ attribute.name }}>
|
||||
<Label class="block text-sm font-medium text-gray-700">{{ to_name(attribute.name) }}</Label>
|
||||
{{ render_attribute_input(assigns, attribute, form) }}
|
||||
<ErrorTag :if={{!Ash.Type.embedded_type?(attribute.type)}} field={{ attribute.name }} />
|
||||
<FieldContext name={attribute.name}>
|
||||
<Label class="block text-sm font-medium text-gray-700">{to_name(attribute.name)}</Label>
|
||||
{render_attribute_input(assigns, attribute, form)}
|
||||
<ErrorTag :if={!Ash.Type.embedded_type?(attribute.type)} field={attribute.name} />
|
||||
</FieldContext>
|
||||
</div>
|
||||
</div>
|
||||
<div :if={{ !Enum.empty?(flags) }} class="hidden sm:block" aria-hidden="true">
|
||||
<div :if={!Enum.empty?(flags)} class="hidden sm:block" aria-hidden="true">
|
||||
<div class="py-5">
|
||||
<div class="border-t border-gray-200" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-6 gap-6" :if={{ !Enum.empty?(flags) }}>
|
||||
<div class="grid grid-cols-6 gap-6" :if={!Enum.empty?(flags)}>
|
||||
<div
|
||||
:for={{ attribute <- flags }}
|
||||
class={{
|
||||
:for={attribute <- flags}
|
||||
class={
|
||||
"col-span-6",
|
||||
"sm:col-span-2": short_text?(resource, attribute),
|
||||
"sm:col-span-3": !long_text?(resource, attribute)
|
||||
}}
|
||||
}
|
||||
>
|
||||
<FieldContext name={{ attribute.name }}>
|
||||
<Label class="block text-sm font-medium text-gray-700">{{ to_name(attribute.name) }}</Label>
|
||||
{{ render_attribute_input(assigns, attribute, form) }}
|
||||
<ErrorTag :if={{!Ash.Type.embedded_type?(attribute.type)}} field={{ attribute.name }} />
|
||||
<FieldContext name={attribute.name}>
|
||||
<Label class="block text-sm font-medium text-gray-700">{to_name(attribute.name)}</Label>
|
||||
{render_attribute_input(assigns, attribute, form)}
|
||||
<ErrorTag :if={!Ash.Type.embedded_type?(attribute.type)} field={attribute.name} />
|
||||
</FieldContext>
|
||||
</div>
|
||||
</div>
|
||||
<div :if={{ !Enum.empty?(bottom_attributes) }} class="hidden sm:block" aria-hidden="true">
|
||||
<div :if={!Enum.empty?(bottom_attributes)} class="hidden sm:block" aria-hidden="true">
|
||||
<div class="py-5">
|
||||
<div class="border-t border-gray-200" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-6 gap-6" :if={{ !Enum.empty?(bottom_attributes) }}>
|
||||
<div class="grid grid-cols-6 gap-6" :if={!Enum.empty?(bottom_attributes)}>
|
||||
<div
|
||||
:for={{ attribute <- bottom_attributes }}
|
||||
class={{
|
||||
:for={attribute <- bottom_attributes}
|
||||
class={
|
||||
"col-span-6",
|
||||
"sm:col-span-2": short_text?(resource, attribute),
|
||||
"sm:col-span-3": !(long_text?(resource, attribute) || Ash.Type.embedded_type?(attribute.type))
|
||||
}}
|
||||
}
|
||||
>
|
||||
<FieldContext name={{ attribute.name }}>
|
||||
<Label class="block text-sm font-medium text-gray-700">{{ to_name(attribute.name) }}</Label>
|
||||
{{ render_attribute_input(assigns, attribute, form) }}
|
||||
<ErrorTag :if={{!Ash.Type.embedded_type?(attribute.type)}} field={{ attribute.name }} />
|
||||
<FieldContext name={attribute.name}>
|
||||
<Label class="block text-sm font-medium text-gray-700">{to_name(attribute.name)}</Label>
|
||||
{render_attribute_input(assigns, attribute, form)}
|
||||
<ErrorTag :if={!Ash.Type.embedded_type?(attribute.type)} field={attribute.name} />
|
||||
</FieldContext>
|
||||
</div>
|
||||
</div>
|
||||
<div :for={{{relationship, argument, opts} <- relationship_args}}>
|
||||
<FieldContext name={{argument.name}} :if={{relationship not in skip and argument.name not in skip}}>
|
||||
<Label class="block text-sm font-medium text-gray-700">{{ to_name(argument.name)}}</Label>
|
||||
{{ render_relationship_input(assigns, Ash.Resource.Info.relationship(form.source.resource, relationship), form, argument, opts) }}
|
||||
<div :for={{relationship, argument, opts} <- relationship_args}>
|
||||
<FieldContext name={argument.name} :if={relationship not in skip and argument.name not in skip}>
|
||||
<Label class="block text-sm font-medium text-gray-700">{to_name(argument.name)}</Label>
|
||||
{render_relationship_input(assigns, Ash.Resource.Info.relationship(form.source.resource, relationship), form, argument, opts)}
|
||||
</FieldContext>
|
||||
</div>
|
||||
</Context>
|
||||
|
@ -267,39 +267,39 @@ defmodule AshAdmin.Components.Resource.Form do
|
|||
argument,
|
||||
opts
|
||||
) do
|
||||
~H"""
|
||||
<div :if={{ !needs_to_load?(opts) || loaded?(form.source.source, relationship.name) }}>
|
||||
~F"""
|
||||
<div :if={!needs_to_load?(opts) || loaded?(form.source.source, relationship.name)}>
|
||||
<Inputs
|
||||
form={{ form }}
|
||||
for={{ argument.name }}
|
||||
:let={{ form: inner_form }}
|
||||
form={form}
|
||||
for={argument.name}
|
||||
:let={form: inner_form}
|
||||
>
|
||||
<div :if={{ @form.submitted_once? }} class="ml-4 mt-4 text-red-500">
|
||||
<div :if={@form.submitted_once?} class="ml-4 mt-4 text-red-500">
|
||||
<ul>
|
||||
<li :for={{ {field, message} <- AshPhoenix.Form.errors(@form, inner_form.name) }}>
|
||||
<span :if={{field}}>
|
||||
{{ to_name(field) }}:
|
||||
<li :for={{field, message} <- AshPhoenix.Form.errors(@form, inner_form.name)}>
|
||||
<span :if={field}>
|
||||
{to_name(field)}:
|
||||
</span>
|
||||
<span>
|
||||
{{message}}
|
||||
{message}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<input :for={{kv <- inner_form.hidden}} name={{inner_form.name <> "[#{elem(kv, 0)}]"}} value={{elem(kv, 1)}} hidden>
|
||||
{{ render_attributes(
|
||||
<input :for={kv <- inner_form.hidden} name={inner_form.name <> "[#{elem(kv, 0)}]"} value={elem(kv, 1)} hidden>
|
||||
{render_attributes(
|
||||
assigns,
|
||||
inner_form.source.resource,
|
||||
inner_form.source.source.action,
|
||||
inner_form,
|
||||
relationship_fields(inner_form),
|
||||
skip_related(relationship)
|
||||
) }}
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
:on-click="remove_form"
|
||||
:if={{can_remove_related?(inner_form, opts)}}
|
||||
phx-value-path={{ inner_form.name }}
|
||||
:if={can_remove_related?(inner_form, opts)}
|
||||
phx-value-path={inner_form.name}
|
||||
class="flex h-6 w-6 mt-2 border-gray-600 hover:bg-gray-400 rounded-md justify-center items-center"
|
||||
>
|
||||
<HeroIcon name="minus" class="h-4 w-4 text-gray-500" />
|
||||
|
@ -309,9 +309,9 @@ defmodule AshAdmin.Components.Resource.Form do
|
|||
<button
|
||||
type="button"
|
||||
:on-click="add_form"
|
||||
:if={{ can_add_related?(form, :read_action, argument)}}
|
||||
phx-value-path={{ form.name <> "[#{argument.name}]" }}
|
||||
phx-value-type={{ "lookup" }}
|
||||
:if={can_add_related?(form, :read_action, argument)}
|
||||
phx-value-path={form.name <> "[#{argument.name}]"}
|
||||
phx-value-type={"lookup"}
|
||||
class="flex h-6 w-6 m-2 border-gray-600 hover:bg-gray-400 rounded-md justify-center items-center"
|
||||
>
|
||||
<HeroIcon name="search-circle" class="h-4 w-4 text-gray-500" />
|
||||
|
@ -320,9 +320,9 @@ defmodule AshAdmin.Components.Resource.Form do
|
|||
<button
|
||||
type="button"
|
||||
:on-click="add_form"
|
||||
:if={{ can_add_related?(form, :create_action, argument) }}
|
||||
phx-value-path={{ form.name <> "[#{argument.name}]" }}
|
||||
phx-value-type={{"create"}}
|
||||
:if={can_add_related?(form, :create_action, argument)}
|
||||
phx-value-path={form.name <> "[#{argument.name}]"}
|
||||
phx-value-type={"create"}
|
||||
class="flex h-6 w-6 m-2 border-gray-600 hover:bg-gray-400 rounded-md justify-center items-center"
|
||||
>
|
||||
<HeroIcon name="plus" class="h-4 w-4 text-gray-500" />
|
||||
|
@ -330,29 +330,29 @@ defmodule AshAdmin.Components.Resource.Form do
|
|||
<button
|
||||
type="button"
|
||||
:on-click="add_form"
|
||||
:if={{ form.source.form_keys[argument.name][:read_form] && !relationship_set?(form.source.source, relationship.name, argument.name) }}
|
||||
phx-value-path={{ form.name <> "[#{argument.name}]" }}
|
||||
phx-value-type={{ "lookup" }}
|
||||
:if={form.source.form_keys[argument.name][:read_form] && !relationship_set?(form.source.source, relationship.name, argument.name)}
|
||||
phx-value-path={form.name <> "[#{argument.name}]"}
|
||||
phx-value-type={"lookup"}
|
||||
class="flex h-6 w-6 m-2 border-gray-600 hover:bg-gray-400 rounded-md justify-center items-center"
|
||||
>
|
||||
<HeroIcon name="plus" class="h-4 w-4 text-gray-500" />
|
||||
</button>
|
||||
</div>
|
||||
<div :if={{ needs_to_load?(opts) && !loaded?(form.source.source, relationship.name) }}>
|
||||
<div :if={needs_to_load?(opts) && !loaded?(form.source.source, relationship.name)}>
|
||||
<button
|
||||
:on-click="load"
|
||||
phx-value-path={{form.name}}
|
||||
phx-value-relationship={{relationship.name}}
|
||||
phx-value-path={form.name}
|
||||
phx-value-relationship={relationship.name}
|
||||
type="button"
|
||||
class="flex py-2 ml-4 px-4 mt-2 bg-indigo-600 text-white border-gray-600 hover:bg-gray-400 rounded-md justify-center items-center"
|
||||
>
|
||||
Load
|
||||
</button>
|
||||
<div :if={{ is_exception(@load_errors[relationship.name]) }}>
|
||||
{{ Exception.message(@load_errors[relationship.name]) }}
|
||||
<div :if={is_exception(@load_errors[relationship.name])}>
|
||||
{Exception.message(@load_errors[relationship.name])}
|
||||
</div>
|
||||
<div :if={{ @load_errors[relationship.name] && !is_exception(@load_errors[relationship.name]) }}>
|
||||
{{ inspect(@load_errors[relationship.name]) }}
|
||||
<div :if={@load_errors[relationship.name] && !is_exception(@load_errors[relationship.name])}>
|
||||
{inspect(@load_errors[relationship.name])}
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
|
@ -478,13 +478,13 @@ defmodule AshAdmin.Components.Resource.Form do
|
|||
value,
|
||||
name
|
||||
) do
|
||||
~H"""
|
||||
~F"""
|
||||
<Checkbox
|
||||
form={{ form }}
|
||||
value={{value(value, form, attribute)}}
|
||||
name={{name || form.name <> "[#{attribute.name}]"}}
|
||||
form={form}
|
||||
value={value(value, form, attribute)}
|
||||
name={name || form.name <> "[#{attribute.name}]"}
|
||||
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded"
|
||||
:props={{props(value, attribute)}}
|
||||
:props={props(value, attribute)}
|
||||
/>
|
||||
"""
|
||||
end
|
||||
|
@ -498,13 +498,13 @@ defmodule AshAdmin.Components.Resource.Form do
|
|||
value,
|
||||
name
|
||||
) do
|
||||
~H"""
|
||||
~F"""
|
||||
<Select
|
||||
form={{ form }}
|
||||
options={{ Nil: nil, True: "true", False: "false" }}
|
||||
selected={{value(value, form, attribute)}}
|
||||
name={{name || form.name <> "[#{attribute.name}]"}}
|
||||
:props={{props(value, attribute)}} />
|
||||
form={form}
|
||||
options={Nil: nil, True: "true", False: "false"}
|
||||
selected={value(value, form, attribute)}
|
||||
name={name || form.name <> "[#{attribute.name}]"}
|
||||
:props={props(value, attribute)} />
|
||||
"""
|
||||
end
|
||||
|
||||
|
@ -521,54 +521,54 @@ defmodule AshAdmin.Components.Resource.Form do
|
|||
when type in [Ash.Type.CiString, Ash.Type.String, Ash.Type.UUID, Ash.Type.Atom] do
|
||||
cond do
|
||||
type == Ash.Type.Atom && attribute.constraints[:one_of] ->
|
||||
~H"""
|
||||
~F"""
|
||||
<Select
|
||||
form={{ form }}
|
||||
:props={{props(value, attribute)}}
|
||||
options={{ Enum.map(attribute.constraints[:one_of], &{to_name(&1), &1}) ++ allow_nil_option(attribute) }}
|
||||
selected={{value(value, form, attribute)}}
|
||||
name={{name || form.name <> "[#{attribute.name}]"}}
|
||||
form={form}
|
||||
:props={props(value, attribute)}
|
||||
options={Enum.map(attribute.constraints[:one_of], &{to_name(&1), &1}) ++ allow_nil_option(attribute)}
|
||||
selected={value(value, form, attribute)}
|
||||
name={name || form.name <> "[#{attribute.name}]"}
|
||||
/>
|
||||
"""
|
||||
|
||||
long_text?(form.source.resource, attribute) ->
|
||||
~H"""
|
||||
~F"""
|
||||
<TextArea
|
||||
form={{ form }}
|
||||
:props={{props(value, attribute)}}
|
||||
name={{name || form.name <> "[#{attribute.name}]"}}
|
||||
opts={{
|
||||
form={form}
|
||||
:props={props(value, attribute)}
|
||||
name={name || form.name <> "[#{attribute.name}]"}
|
||||
opts={
|
||||
type: text_input_type(attribute),
|
||||
placeholder: placeholder(default),
|
||||
phx_hook: "MaintainAttrs",
|
||||
data_attrs: "style"
|
||||
}}
|
||||
value={{value(value, form, attribute)}}
|
||||
}
|
||||
value={value(value, form, attribute)}
|
||||
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md resize-y"
|
||||
/>
|
||||
"""
|
||||
|
||||
short_text?(form.source.resource, attribute) ->
|
||||
~H"""
|
||||
~F"""
|
||||
<TextInput
|
||||
form={{ form }}
|
||||
:props={{props(value, attribute)}}
|
||||
opts={{ type: text_input_type(attribute), placeholder: placeholder(default) }}
|
||||
value={{value(value, form, attribute)}}
|
||||
form={form}
|
||||
:props={props(value, attribute)}
|
||||
opts={type: text_input_type(attribute), placeholder: placeholder(default)}
|
||||
value={value(value, form, attribute)}
|
||||
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md"
|
||||
name={{name || form.name <> "[#{attribute.name}]"}}
|
||||
name={name || form.name <> "[#{attribute.name}]"}
|
||||
/>
|
||||
"""
|
||||
|
||||
true ->
|
||||
~H"""
|
||||
~F"""
|
||||
<TextInput
|
||||
form={{ form }}
|
||||
:props={{props(value, attribute)}}
|
||||
opts={{ type: text_input_type(attribute), placeholder: placeholder(default) }}
|
||||
value={{value(value, form, attribute)}}
|
||||
form={form}
|
||||
:props={props(value, attribute)}
|
||||
opts={type: text_input_type(attribute), placeholder: placeholder(default)}
|
||||
value={value(value, form, attribute)}
|
||||
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md"
|
||||
name={{name || form.name <> "[#{attribute.name}]"}}
|
||||
name={name || form.name <> "[#{attribute.name}]"}
|
||||
/>
|
||||
"""
|
||||
end
|
||||
|
@ -587,33 +587,33 @@ defmodule AshAdmin.Components.Resource.Form do
|
|||
def render_attribute_input(assigns, %{type: Ash.Type.Map} = attribute, form, value, name) do
|
||||
encoded = Jason.encode!(value(value, form, attribute))
|
||||
|
||||
~H"""
|
||||
~F"""
|
||||
<div>
|
||||
<div
|
||||
phx-hook="JsonEditor"
|
||||
phx-update="ignore"
|
||||
data-input-id={{form.id <> "_#{attribute.name}"}}
|
||||
id={{form.id <> "_#{attribute.name}_json"}}
|
||||
data-input-id={form.id <> "_#{attribute.name}"}
|
||||
id={form.id <> "_#{attribute.name}_json"}
|
||||
/>
|
||||
|
||||
<HiddenInput
|
||||
opts={{phx_hook: "JsonEditorSource", data_editor_id: form.id <> "_#{attribute.name}_json"}}
|
||||
form={{ form }}
|
||||
value={{encoded}}
|
||||
name={{name || form.name <> "[#{attribute.name}]"}}
|
||||
id={{form.id <> "_#{attribute.name}"}}
|
||||
opts={phx_hook: "JsonEditorSource", data_editor_id: form.id <> "_#{attribute.name}_json"}
|
||||
form={form}
|
||||
value={encoded}
|
||||
name={name || form.name <> "[#{attribute.name}]"}
|
||||
id={form.id <> "_#{attribute.name}"}
|
||||
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md"
|
||||
/>
|
||||
</div>
|
||||
"""
|
||||
rescue
|
||||
_ ->
|
||||
~H"""
|
||||
~F"""
|
||||
<TextInput
|
||||
form={{ form }}
|
||||
opts={{ disabled: true }}
|
||||
value={{"..."}}
|
||||
name={{name || form.name <> "[#{attribute.name}]"}}
|
||||
form={form}
|
||||
opts={disabled: true}
|
||||
value={"..."}
|
||||
name={name || form.name <> "[#{attribute.name}]"}
|
||||
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md"
|
||||
/>
|
||||
"""
|
||||
|
@ -622,26 +622,26 @@ defmodule AshAdmin.Components.Resource.Form do
|
|||
def render_attribute_input(assigns, attribute, form, value, name) do
|
||||
cond do
|
||||
Ash.Type.embedded_type?(attribute.type) ->
|
||||
~H"""
|
||||
<Inputs form={{ form }} for={{ attribute.name }} :let={{ form: inner_form }}>
|
||||
<input :for={{kv <- inner_form.hidden}} name={{inner_form.name <> "[#{elem(kv, 0)}]"}} value={{elem(kv, 1)}} hidden>
|
||||
~F"""
|
||||
<Inputs form={form} for={attribute.name} :let={form: inner_form}>
|
||||
<input :for={kv <- inner_form.hidden} name={inner_form.name <> "[#{elem(kv, 0)}]"} value={elem(kv, 1)} hidden>
|
||||
<button
|
||||
type="button"
|
||||
:on-click="remove_form"
|
||||
phx-value-path={{ inner_form.name }}
|
||||
phx-value-path={inner_form.name}
|
||||
class="flex h-6 w-6 mt-2 border-gray-600 hover:bg-gray-400 rounded-md justify-center items-center"
|
||||
>
|
||||
<HeroIcon name="minus" class="h-4 w-4 text-gray-500" />
|
||||
</button>
|
||||
|
||||
{{ render_attributes(assigns, inner_form.source.resource, inner_form.source.source.action, inner_form) }}
|
||||
{render_attributes(assigns, inner_form.source.resource, inner_form.source.source.action, inner_form)}
|
||||
</Inputs>
|
||||
<button
|
||||
type="button"
|
||||
:on-click="add_form"
|
||||
:if={{can_append_embed?(form.source.source, attribute.name)}}
|
||||
phx-value-pkey={{embedded_type_pkey(attribute.type)}}
|
||||
phx-value-path={{ name || form.name <> "[#{attribute.name}]" }}
|
||||
:if={can_append_embed?(form.source.source, attribute.name)}
|
||||
phx-value-pkey={embedded_type_pkey(attribute.type)}
|
||||
phx-value-path={name || form.name <> "[#{attribute.name}]"}
|
||||
class="flex h-6 w-6 mt-2 border-gray-600 hover:bg-gray-400 rounded-md justify-center items-center"
|
||||
>
|
||||
<HeroIcon name="plus" class="h-4 w-4 text-gray-500" />
|
||||
|
@ -649,13 +649,13 @@ defmodule AshAdmin.Components.Resource.Form do
|
|||
"""
|
||||
|
||||
is_atom(attribute.type) && :erlang.function_exported(attribute.type, :values, 0) ->
|
||||
~H"""
|
||||
~F"""
|
||||
<Select
|
||||
form={{ form }}
|
||||
:props={{props(value, attribute)}}
|
||||
options={{ Enum.map(attribute.type.values(), &{to_name(&1), &1}) ++ allow_nil_option(attribute) }}
|
||||
selected={{value(value, form, attribute)}}
|
||||
name={{name || form.name <> "[#{attribute.name}]"}}
|
||||
form={form}
|
||||
:props={props(value, attribute)}
|
||||
options={Enum.map(attribute.type.values(), &{to_name(&1), &1}) ++ allow_nil_option(attribute)}
|
||||
selected={value(value, form, attribute)}
|
||||
name={name || form.name <> "[#{attribute.name}]"}
|
||||
/>
|
||||
"""
|
||||
|
||||
|
@ -667,16 +667,16 @@ defmodule AshAdmin.Components.Resource.Form do
|
|||
defp render_fallback_attribute(assigns, form, %{type: {:array, type}} = attribute, value, name) do
|
||||
name = name || form.name <> "[#{attribute.name}]"
|
||||
|
||||
~H"""
|
||||
~F"""
|
||||
<div>
|
||||
<div :for.with_index={{{value, index} <- list_value(value || Phoenix.HTML.FormData.input_value(form.source, form, attribute.name))}}>
|
||||
{{render_attribute_input(assigns, %{attribute | type: type, constraints: attribute.constraints[:items] || []}, %{form | params: %{"#{attribute.name}" => form.params["#{attribute.name}"]["#{index}"]}}, {:value, value}, name <> "[#{index}]")}}
|
||||
<div :for.with_index={{value, index} <- list_value(value || Phoenix.HTML.FormData.input_value(form.source, form, attribute.name))}>
|
||||
{render_attribute_input(assigns, %{attribute | type: type, constraints: attribute.constraints[:items] || []}, %{form | params: %{"#{attribute.name}" => form.params["#{attribute.name}"]["#{index}"]}}, {:value, value}, name <> "[#{index}]")}
|
||||
<button
|
||||
type="button"
|
||||
:on-click="remove_value"
|
||||
phx-value-path={{ form.name }}
|
||||
phx-value-field={{ attribute.name}}
|
||||
phx-value-index={{ index}}
|
||||
phx-value-path={form.name}
|
||||
phx-value-field={attribute.name}
|
||||
phx-value-index={index}
|
||||
class="flex h-6 w-6 mt-2 border-gray-600 hover:bg-gray-400 rounded-md justify-center items-center"
|
||||
>
|
||||
<HeroIcon name="minus" class="h-4 w-4 text-gray-500" />
|
||||
|
@ -685,8 +685,8 @@ defmodule AshAdmin.Components.Resource.Form do
|
|||
<button
|
||||
type="button"
|
||||
:on-click="append_value"
|
||||
phx-value-path={{ form.name }}
|
||||
phx-value-field={{ attribute.name }}
|
||||
phx-value-path={form.name}
|
||||
phx-value-field={attribute.name}
|
||||
class="flex h-6 w-6 mt-2 border-gray-600 hover:bg-gray-400 rounded-md justify-center items-center"
|
||||
>
|
||||
<HeroIcon name="plus" class="h-4 w-4 text-gray-500" />
|
||||
|
@ -698,12 +698,12 @@ defmodule AshAdmin.Components.Resource.Form do
|
|||
defp render_fallback_attribute(assigns, form, attribute, value, name) do
|
||||
casted_value = Phoenix.HTML.Safe.to_iodata(value(value, form, attribute))
|
||||
|
||||
~H"""
|
||||
~F"""
|
||||
<TextInput
|
||||
form={{ form }}
|
||||
opts={{ type: text_input_type(attribute), placeholder: placeholder(attribute.default) }}
|
||||
value={{casted_value}}
|
||||
name={{name || form.name <> "[#{attribute.name}]"}}
|
||||
form={form}
|
||||
opts={type: text_input_type(attribute), placeholder: placeholder(attribute.default)}
|
||||
value={casted_value}
|
||||
name={name || form.name <> "[#{attribute.name}]"}
|
||||
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md"
|
||||
/>
|
||||
"""
|
||||
|
@ -711,23 +711,23 @@ defmodule AshAdmin.Components.Resource.Form do
|
|||
_ ->
|
||||
case Map.fetch(form.params, to_string(attribute.name)) do
|
||||
{:ok, value} ->
|
||||
~H"""
|
||||
~F"""
|
||||
<TextInput
|
||||
form={{ form }}
|
||||
opts={{ type: text_input_type(attribute), placeholder: placeholder(attribute.default) }}
|
||||
value={{value}}
|
||||
name={{name || form.name <> "[#{attribute.name}]"}}
|
||||
form={form}
|
||||
opts={type: text_input_type(attribute), placeholder: placeholder(attribute.default)}
|
||||
value={value}
|
||||
name={name || form.name <> "[#{attribute.name}]"}
|
||||
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md"
|
||||
/>
|
||||
"""
|
||||
|
||||
:error ->
|
||||
~H"""
|
||||
~F"""
|
||||
<TextInput
|
||||
form={{ form }}
|
||||
opts={{ disabled: true }}
|
||||
value={{"..."}}
|
||||
name={{name || form.name <> "[#{attribute.name}]"}}
|
||||
form={form}
|
||||
opts={disabled: true}
|
||||
value={"..."}
|
||||
name={name || form.name <> "[#{attribute.name}]"}
|
||||
class="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md"
|
||||
/>
|
||||
"""
|
||||
|
@ -827,13 +827,7 @@ defmodule AshAdmin.Components.Resource.Form do
|
|||
socket
|
||||
|> redirect(
|
||||
to:
|
||||
ash_show_path(
|
||||
socket.assigns.prefix,
|
||||
socket.assigns.api,
|
||||
socket.assigns.resource,
|
||||
record,
|
||||
socket.assigns.table
|
||||
)
|
||||
"#{socket.assigns.prefix}?api=#{AshAdmin.Api.name(socket.assigns.api)}&resource=#{AshAdmin.Resource.name(socket.assigns.resource)}&tab=show&table=#{socket.assigns.table}&primary_key=#{encode_primary_key(record)}"
|
||||
)}
|
||||
else
|
||||
case Ash.Resource.Info.primary_action(socket.assigns.resource, :update) do
|
||||
|
@ -841,22 +835,15 @@ defmodule AshAdmin.Components.Resource.Form do
|
|||
{:noreply,
|
||||
redirect(socket,
|
||||
to:
|
||||
ash_admin_path(socket.assigns.prefix, socket.assigns.api, socket.assigns.resource)
|
||||
"#{socket.assigns.prefix}?api=#{AshAdmin.Api.name(socket.assigns.api)}&resource=#{AshAdmin.Resource.name(socket.assigns.resource)}"
|
||||
)}
|
||||
|
||||
update ->
|
||||
_update ->
|
||||
{:noreply,
|
||||
socket
|
||||
|> redirect(
|
||||
to:
|
||||
ash_update_path(
|
||||
socket.assigns.prefix,
|
||||
socket.assigns.api,
|
||||
socket.assigns.resource,
|
||||
record,
|
||||
update,
|
||||
socket.assigns.table
|
||||
)
|
||||
"#{socket.assigns.prefix}?api=#{AshAdmin.Api.name(socket.assigns.api)}&resource=#{AshAdmin.Resource.name(socket.assigns.resource)}&action_type=update&tab=update&table=#{socket.assigns.table}&primary_key=#{encode_primary_key(record)}"
|
||||
)}
|
||||
end
|
||||
end
|
||||
|
@ -867,42 +854,19 @@ defmodule AshAdmin.Components.Resource.Form do
|
|||
:create ->
|
||||
{:noreply,
|
||||
push_redirect(socket,
|
||||
to:
|
||||
ash_create_path(
|
||||
socket.assigns.prefix,
|
||||
socket.assigns.api,
|
||||
socket.assigns.resource,
|
||||
socket.assigns.action.name,
|
||||
table
|
||||
)
|
||||
to: self_path(socket.assigns.url_path, socket.assigns.params, %{"table" => table})
|
||||
)}
|
||||
|
||||
:update ->
|
||||
{:noreply,
|
||||
push_redirect(socket,
|
||||
to:
|
||||
ash_update_path(
|
||||
socket.assigns.prefix,
|
||||
socket.assigns.api,
|
||||
socket.assigns.resource,
|
||||
socket.assigns.record,
|
||||
socket.assigns.action.name,
|
||||
table
|
||||
)
|
||||
to: self_path(socket.assigns.url_path, socket.assigns.params, %{"table" => table})
|
||||
)}
|
||||
|
||||
:destroy ->
|
||||
{:noreply,
|
||||
push_redirect(socket,
|
||||
to:
|
||||
ash_destroy_path(
|
||||
socket.assigns.prefix,
|
||||
socket.assigns.api,
|
||||
socket.assigns.resource,
|
||||
socket.assigns.record,
|
||||
socket.assigns.action.name,
|
||||
table
|
||||
)
|
||||
to: self_path(socket.assigns.url_path, socket.assigns.params, %{"table" => table})
|
||||
)}
|
||||
end
|
||||
end
|
||||
|
@ -926,42 +890,19 @@ defmodule AshAdmin.Components.Resource.Form do
|
|||
:create ->
|
||||
{:noreply,
|
||||
push_redirect(socket,
|
||||
to:
|
||||
ash_create_path(
|
||||
socket.assigns.prefix,
|
||||
socket.assigns.api,
|
||||
socket.assigns.resource,
|
||||
action.name,
|
||||
socket.assigns.table
|
||||
)
|
||||
to: self_path(socket.assigns.url_path, socket.assigns.params, %{"action" => action})
|
||||
)}
|
||||
|
||||
:update ->
|
||||
{:noreply,
|
||||
push_redirect(socket,
|
||||
to:
|
||||
ash_update_path(
|
||||
socket.assigns.prefix,
|
||||
socket.assigns.api,
|
||||
socket.assigns.resource,
|
||||
socket.assigns.record,
|
||||
action.name,
|
||||
socket.assigns.table
|
||||
)
|
||||
to: self_path(socket.assigns.url_path, socket.assigns.params, %{"action" => action})
|
||||
)}
|
||||
|
||||
:destroy ->
|
||||
{:noreply,
|
||||
push_redirect(socket,
|
||||
to:
|
||||
ash_destroy_path(
|
||||
socket.assigns.prefix,
|
||||
socket.assigns.api,
|
||||
socket.assigns.resource,
|
||||
socket.assigns.record,
|
||||
action.name,
|
||||
socket.assigns.table
|
||||
)
|
||||
to: self_path(socket.assigns.url_path, socket.assigns.params, %{"action" => action})
|
||||
)}
|
||||
end
|
||||
end
|
||||
|
@ -1123,11 +1064,7 @@ defmodule AshAdmin.Components.Resource.Form do
|
|||
socket
|
||||
|> redirect(
|
||||
to:
|
||||
ash_admin_path(
|
||||
socket.assigns.prefix,
|
||||
socket.assigns.api,
|
||||
socket.assigns.resource
|
||||
)
|
||||
"#{socket.assigns.prefix}?api=#{AshAdmin.Api.name(socket.assigns.api)}&resource=#{AshAdmin.Resource.name(socket.assigns.resource)}"
|
||||
)}
|
||||
|
||||
{:error, form} ->
|
||||
|
|
|
@ -3,7 +3,6 @@ defmodule AshAdmin.Components.Resource.Nav do
|
|||
use Surface.Component
|
||||
alias Surface.Components.LiveRedirect
|
||||
alias AshAdmin.Components.TopNav.Dropdown
|
||||
import AshAdmin.Helpers
|
||||
|
||||
prop(resource, :any, required: true)
|
||||
prop(api, :any, required: true)
|
||||
|
@ -13,29 +12,23 @@ defmodule AshAdmin.Components.Resource.Nav do
|
|||
prop(prefix, :any, default: nil)
|
||||
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
~F"""
|
||||
<nav class="bg-gray-800 w-full">
|
||||
<div class="px-4 sm:px-6 lg:px-8 w-full">
|
||||
<div class="flex items-center justify-between h-16 w-full">
|
||||
<div class="flex items-center w-full">
|
||||
<div class="flex-shrink-0">
|
||||
<h3 class="text-white text-lg">
|
||||
<LiveRedirect to={{ ash_admin_path(@prefix, @api, @resource) }}>
|
||||
{{ AshAdmin.Resource.name(@resource) }}
|
||||
<LiveRedirect to={"#{@prefix}?api=#{AshAdmin.Api.name(@api)}&resource=#{AshAdmin.Resource.name(@resource)}"}>
|
||||
{AshAdmin.Resource.name(@resource)}
|
||||
</LiveRedirect>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<div class="ml-12 flex items-center space-x-1">
|
||||
<div :if={{ has_create_action?(@resource) }} class="relative">
|
||||
<div :if={has_create_action?(@resource)} class="relative">
|
||||
<LiveRedirect
|
||||
to={{ash_create_path(
|
||||
@prefix,
|
||||
@api,
|
||||
@resource,
|
||||
Ash.Resource.Info.primary_action(@resource, :create).name,
|
||||
@table
|
||||
)}}
|
||||
to={"#{@prefix}?api=#{AshAdmin.Api.name(@api)}&resource=#{AshAdmin.Resource.name(@resource)}&action_type=create&action=#{Ash.Resource.Info.primary_action(@resource, :create).name}&tab=create&table=#{@table}"}
|
||||
class="inline-flex justify-center w-full rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-100 focus:ring-indigo-500"
|
||||
>
|
||||
Create
|
||||
|
@ -44,9 +37,9 @@ defmodule AshAdmin.Components.Resource.Nav do
|
|||
|
||||
<Dropdown
|
||||
name="Read"
|
||||
id={{ "#{@resource}_data_dropdown" }}
|
||||
active={{ @tab == "data" }}
|
||||
groups={{ data_groups(@prefix, @api, @resource, @action, @table) }}
|
||||
id={"#{@resource}_data_dropdown"}
|
||||
active={@tab == "data"}
|
||||
groups={data_groups(@prefix, @api, @resource, @action, @table)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -68,7 +61,8 @@ defmodule AshAdmin.Components.Resource.Nav do
|
|||
|> Enum.map(fn action ->
|
||||
%{
|
||||
text: action_name(action),
|
||||
to: ash_action_path(prefix, api, resource, :read, action.name, table),
|
||||
to:
|
||||
"#{prefix}?api=#{AshAdmin.Api.name(api)}&resource=#{AshAdmin.Resource.name(resource)}&table=#{table}&action_type=read&action=#{action.name}",
|
||||
active: current_action == action
|
||||
}
|
||||
end)
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
defmodule AshAdmin.Components.Resource.RelationshipTable do
|
||||
@moduledoc false
|
||||
use Surface.Component
|
||||
import AshAdmin.Helpers
|
||||
alias Surface.Components.LiveRedirect
|
||||
|
||||
prop(resource, :any, required: true)
|
||||
|
@ -9,8 +8,8 @@ defmodule AshAdmin.Components.Resource.RelationshipTable do
|
|||
prop(prefix, :any, required: true)
|
||||
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<div class="w-full" :if={{ Enum.any?(relationships(@resource)) }}>
|
||||
~F"""
|
||||
<div class="w-full" :if={Enum.any?(relationships(@resource))}>
|
||||
<h1 class="text-center text-3xl rounded-t py-8">
|
||||
Relationships
|
||||
</h1>
|
||||
|
@ -25,20 +24,20 @@ defmodule AshAdmin.Components.Resource.RelationshipTable do
|
|||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
class={{ "h-10", "bg-gray-200": rem(index, 2) == 0 }}
|
||||
:for.with_index={{ {relationship, index} <- relationships(@resource) }}
|
||||
class={"h-10", "bg-gray-200": rem(index, 2) == 0}
|
||||
:for.with_index={{relationship, index} <- relationships(@resource)}
|
||||
>
|
||||
<th scope="row">
|
||||
{{ relationship.name }}
|
||||
{relationship.name}
|
||||
</th>
|
||||
<td class="text-center">
|
||||
{{ relationship.type }}</td>
|
||||
{relationship.type}</td>
|
||||
<td class="text-center">
|
||||
<LiveRedirect to={{ ash_admin_path(@prefix, @api, relationship.destination) }}>
|
||||
{{ AshAdmin.Resource.name(relationship.destination) }}
|
||||
<LiveRedirect to={"#{@prefix}?api=#{AshAdmin.Api.name(@api)}&resource=#{AshAdmin.Resource.name(relationship.destination)}"}>
|
||||
{AshAdmin.Resource.name(relationship.destination)}
|
||||
</LiveRedirect>
|
||||
</td>
|
||||
<td class="text-center max-w-sm min-w-sm">{{ relationship.description }}</td>
|
||||
<td class="text-center max-w-sm min-w-sm">{relationship.description}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
@ -22,106 +22,107 @@ defmodule AshAdmin.Components.Resource do
|
|||
prop(table, :any, default: nil)
|
||||
prop(tables, :any, default: nil)
|
||||
prop(prefix, :any, default: nil)
|
||||
prop(action_type, :atom)
|
||||
|
||||
data(filter_open, :boolean, default: false)
|
||||
slot(default)
|
||||
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
~F"""
|
||||
<div class="content-center h-screen">
|
||||
<Nav
|
||||
resource={{ @resource }}
|
||||
api={{ @api }}
|
||||
tab={{ @tab }}
|
||||
action={{ @action }}
|
||||
table={{ @table }}
|
||||
prefix={{ @prefix }}
|
||||
resource={@resource}
|
||||
api={@api}
|
||||
tab={@tab}
|
||||
action={@action}
|
||||
table={@table}
|
||||
prefix={@prefix}
|
||||
/>
|
||||
<div class="mx-24 relative grid grid-cols-1 justify-items-center">
|
||||
</div>
|
||||
<slot />
|
||||
<div :if={{ @record && match?({:ok, record} when not is_nil(record), @record) && @tab == "update" }}>
|
||||
{{ {:ok, record} = @record
|
||||
nil }}
|
||||
<#slot />
|
||||
<div :if={@record && match?({:ok, record} when not is_nil(record), @record) && @tab == "update"}>
|
||||
{{:ok, record} = @record
|
||||
nil}
|
||||
<Form
|
||||
type={{ :update }}
|
||||
record={{ record }}
|
||||
resource={{ @resource }}
|
||||
action={{ @action }}
|
||||
api={{ @api }}
|
||||
id={{ update_id(@resource) }}
|
||||
actor={{ @actor }}
|
||||
set_actor={{ @set_actor }}
|
||||
authorizing={{ @authorizing }}
|
||||
tenant={{ @tenant }}
|
||||
table={{ @table }}
|
||||
tables={{ @tables }}
|
||||
prefix={{ @prefix }}
|
||||
type={:update}
|
||||
record={record}
|
||||
resource={@resource}
|
||||
action={@action}
|
||||
api={@api}
|
||||
id={update_id(@resource)}
|
||||
actor={@actor}
|
||||
set_actor={@set_actor}
|
||||
authorizing={@authorizing}
|
||||
tenant={@tenant}
|
||||
table={@table}
|
||||
tables={@tables}
|
||||
prefix={@prefix}
|
||||
/>
|
||||
</div>
|
||||
<div :if={{ @record && match?({:ok, record} when not is_nil(record), @record) && @tab == "destroy" }}>
|
||||
{{ {:ok, record} = @record
|
||||
nil }}
|
||||
<div :if={@record && match?({:ok, record} when not is_nil(record), @record) && @tab == "destroy"}>
|
||||
{{:ok, record} = @record
|
||||
nil}
|
||||
<Form
|
||||
type={{ :destroy }}
|
||||
record={{ record }}
|
||||
resource={{ @resource }}
|
||||
action={{ @action }}
|
||||
set_actor={{ @set_actor }}
|
||||
api={{ @api }}
|
||||
id={{ destroy_id(@resource) }}
|
||||
actor={{ @actor }}
|
||||
authorizing={{ @authorizing }}
|
||||
tenant={{ @tenant }}
|
||||
table={{ @table }}
|
||||
tables={{ @tables }}
|
||||
prefix={{ @prefix }}
|
||||
type={:destroy}
|
||||
record={record}
|
||||
resource={@resource}
|
||||
action={@action}
|
||||
set_actor={@set_actor}
|
||||
api={@api}
|
||||
id={destroy_id(@resource)}
|
||||
actor={@actor}
|
||||
authorizing={@authorizing}
|
||||
tenant={@tenant}
|
||||
table={@table}
|
||||
tables={@tables}
|
||||
prefix={@prefix}
|
||||
/>
|
||||
</div>
|
||||
<Show
|
||||
:if={{ @tab == "read" && match?({:ok, %_{}}, @record) }}
|
||||
resource={{ @resource }}
|
||||
api={{ @api }}
|
||||
id={{ show_id(@resource) }}
|
||||
record={{ unwrap(@record) }}
|
||||
actor={{ @actor }}
|
||||
authorizing={{ @authorizing }}
|
||||
tenant={{ @tenant }}
|
||||
set_actor={{ @set_actor }}
|
||||
table={{ @table }}
|
||||
prefix={{ @prefix }}
|
||||
:if={@tab == "show" && match?({:ok, %_{}}, @record)}
|
||||
resource={@resource}
|
||||
api={@api}
|
||||
id={show_id(@resource)}
|
||||
record={unwrap(@record)}
|
||||
actor={@actor}
|
||||
authorizing={@authorizing}
|
||||
tenant={@tenant}
|
||||
set_actor={@set_actor}
|
||||
table={@table}
|
||||
prefix={@prefix}
|
||||
/>
|
||||
<Info :if={{ @tab == "info" }} resource={{ @resource }} api={{ @api }} prefix={{ @prefix }} />
|
||||
<Info :if={@tab == "info" || (is_nil(@tab) && is_nil(@action_type))} resource={@resource} api={@api} prefix={@prefix} />
|
||||
<Form
|
||||
:if={{ @tab == "create" }}
|
||||
type={{ :create }}
|
||||
resource={{ @resource }}
|
||||
api={{ @api }}
|
||||
set_actor={{ @set_actor }}
|
||||
action={{ @action }}
|
||||
id={{ create_id(@resource) }}
|
||||
actor={{ @actor }}
|
||||
authorizing={{ @authorizing }}
|
||||
tenant={{ @tenant }}
|
||||
table={{ @table }}
|
||||
tables={{ @tables }}
|
||||
prefix={{ @prefix }}
|
||||
:if={@tab == "create"}
|
||||
type={:create}
|
||||
resource={@resource}
|
||||
api={@api}
|
||||
set_actor={@set_actor}
|
||||
action={@action}
|
||||
id={create_id(@resource)}
|
||||
actor={@actor}
|
||||
authorizing={@authorizing}
|
||||
tenant={@tenant}
|
||||
table={@table}
|
||||
tables={@tables}
|
||||
prefix={@prefix}
|
||||
/>
|
||||
<DataTable
|
||||
:if={{ @tab == "data" }}
|
||||
resource={{ @resource }}
|
||||
action={{ @action }}
|
||||
actor={{ @actor }}
|
||||
api={{ @api }}
|
||||
url_path={{ @url_path }}
|
||||
params={{ @params }}
|
||||
set_actor={{ @set_actor }}
|
||||
id={{ data_table_id(@resource) }}
|
||||
authorizing={{ @authorizing }}
|
||||
table={{ @table }}
|
||||
tables={{ @tables }}
|
||||
prefix={{ @prefix }}
|
||||
tenant={{ @tenant }}
|
||||
:if={@action_type == :read && @tab != "show"}
|
||||
resource={@resource}
|
||||
action={@action}
|
||||
actor={@actor}
|
||||
api={@api}
|
||||
url_path={@url_path}
|
||||
params={@params}
|
||||
set_actor={@set_actor}
|
||||
id={data_table_id(@resource)}
|
||||
authorizing={@authorizing}
|
||||
table={@table}
|
||||
tables={@tables}
|
||||
prefix={@prefix}
|
||||
tenant={@tenant}
|
||||
/>
|
||||
</div>
|
||||
"""
|
||||
|
|
|
@ -9,10 +9,10 @@ defmodule AshAdmin.Components.Resource.Info do
|
|||
prop(prefix, :any, required: true)
|
||||
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
~F"""
|
||||
<div class="relative mx-12">
|
||||
<AttributeTable resource={{ @resource }} />
|
||||
<RelationshipTable api={{ @api }} resource={{ @resource }} prefix={{ @prefix }} />
|
||||
<AttributeTable resource={@resource} />
|
||||
<RelationshipTable api={@api} resource={@resource} prefix={@prefix} />
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
|
|
@ -11,13 +11,13 @@ defmodule AshAdmin.Components.Resource.SelectTable do
|
|||
prop(tables, :any, required: true)
|
||||
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
~F"""
|
||||
<div>
|
||||
<div :if={{ @resource && AshAdmin.Resource.polymorphic?(@resource) }}>
|
||||
<Form as={{ :table }} for={{ :table }} change={{ @on_change }}>
|
||||
<div :if={@resource && AshAdmin.Resource.polymorphic?(@resource)}>
|
||||
<Form as={:table} for={:table} change={@on_change}>
|
||||
<FieldContext name="table">
|
||||
<Label>Table</Label>
|
||||
<Select selected={{ @table }} options={{ @tables || [] }} />
|
||||
<Select selected={@table} options={@tables || []} />
|
||||
</FieldContext>
|
||||
</Form>
|
||||
</div>
|
||||
|
|
|
@ -21,16 +21,16 @@ defmodule AshAdmin.Components.Resource.Show do
|
|||
data(load_errors, :map, default: %{})
|
||||
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
~F"""
|
||||
<div class="md:pt-10 sm:mt-0 bg-gray-300 min-h-screen pb-20">
|
||||
<div class="md:grid md:grid-cols-3 md:gap-6 md:mx-16 md:mt-10">
|
||||
<div class="mt-5 md:mt-0 md:col-span-2">
|
||||
{{ render_show(assigns, @record, @resource) }}
|
||||
{render_show(assigns, @record, @resource)}
|
||||
</div>
|
||||
</div>
|
||||
<div class="md:grid md:grid-cols-3 md:gap-6 md:mx-16 md:mt-10">
|
||||
<div class="mt-5 md:mt-0 md:col-span-2">
|
||||
{{ render_relationships(assigns, @record, @resource) }}
|
||||
{render_relationships(assigns, @record, @resource)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -38,48 +38,34 @@ defmodule AshAdmin.Components.Resource.Show do
|
|||
end
|
||||
|
||||
def render_show(assigns, record, resource, title \\ nil, buttons? \\ true) do
|
||||
~H"""
|
||||
~F"""
|
||||
<div class="shadow-lg overflow-hidden sm:rounded-md bg-white">
|
||||
<h1 :if={{ title }} class="pt-2 pl-4 text-lg">{{ title }}</h1>
|
||||
<h1 :if={title} class="pt-2 pl-4 text-lg">{title}</h1>
|
||||
<button
|
||||
:if={{ AshAdmin.Resource.actor?(@resource) }}
|
||||
:if={AshAdmin.Resource.actor?(@resource)}
|
||||
class="float-right pt-4 pr-4"
|
||||
:on-click={{ @set_actor }}
|
||||
phx-value-resource={{ @resource }}
|
||||
phx-value-api={{ @api }}
|
||||
phx-value-pkey={{ encode_primary_key(@record) }}
|
||||
:on-click={@set_actor}
|
||||
phx-value-resource={@resource}
|
||||
phx-value-api={@api}
|
||||
phx-value-pkey={encode_primary_key(@record)}
|
||||
>
|
||||
<HeroIcon name="key" class="h-5 w-5 text-gray-500" />
|
||||
</button>
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<div>
|
||||
{{ render_attributes(assigns, record, resource) }}
|
||||
<div :if={{ buttons? }} class="px-4 py-3 text-right sm:px-6">
|
||||
{render_attributes(assigns, record, resource)}
|
||||
<div :if={buttons?} class="px-4 py-3 text-right sm:px-6">
|
||||
<LiveRedirect
|
||||
to={{ash_destroy_path(
|
||||
@prefix,
|
||||
@api,
|
||||
@resource,
|
||||
@record,
|
||||
Ash.Resource.Info.primary_action(@resource, :destroy).name,
|
||||
@table
|
||||
)}}
|
||||
:if={{ destroy?(@resource) }}
|
||||
to={"#{@prefix}?api=#{AshAdmin.Api.name(@api)}&resource=#{AshAdmin.Resource.name(@resource)}&action_type=destroy&action=#{Ash.Resource.Info.primary_action(@resource, :destroy).name}&tab=destroy&table=#{@table}&primary_key=#{encode_primary_key(@record)}"}
|
||||
:if={destroy?(@resource)}
|
||||
class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
>
|
||||
Destroy
|
||||
</LiveRedirect>
|
||||
|
||||
<LiveRedirect
|
||||
to={{ash_update_path(
|
||||
@prefix,
|
||||
@api,
|
||||
@resource,
|
||||
@record,
|
||||
Ash.Resource.Info.primary_action(@resource, :update).name,
|
||||
@table
|
||||
)}}
|
||||
:if={{ update?(@resource) }}
|
||||
to={"#{@prefix}?api=#{AshAdmin.Api.name(@api)}&resource=#{AshAdmin.Resource.name(@resource)}&action_type=update&action=#{Ash.Resource.Info.primary_action(@resource, :update).name}&tab=update&table=#{@table}&primary_key=#{encode_primary_key(@record)}"}
|
||||
:if={update?(@resource)}
|
||||
class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
>
|
||||
Update
|
||||
|
@ -92,35 +78,35 @@ defmodule AshAdmin.Components.Resource.Show do
|
|||
end
|
||||
|
||||
defp render_relationships(assigns, _record, resource) do
|
||||
~H"""
|
||||
~F"""
|
||||
<div
|
||||
:for={{ relationship <- AshAdmin.Components.Resource.Form.relationships(resource, :show) }}
|
||||
:for={relationship <- AshAdmin.Components.Resource.Form.relationships(resource, :show)}
|
||||
class="shadow-lg overflow-hidden sm:rounded-md mb-2 bg-white"
|
||||
>
|
||||
<div class="px-4 py-5 mt-2">
|
||||
<div>
|
||||
{{ to_name(relationship.name) }}
|
||||
{to_name(relationship.name)}
|
||||
<button
|
||||
:if={{ !loaded?(@record, relationship.name) }}
|
||||
:if={!loaded?(@record, relationship.name)}
|
||||
:on-click="load"
|
||||
phx-value-relationship={{ relationship.name }}
|
||||
phx-value-relationship={relationship.name}
|
||||
type="button"
|
||||
class="flex py-2 ml-4 px-4 mt-2 bg-indigo-600 text-white border-gray-600 hover:bg-gray-400 rounded-md justify-center items-center"
|
||||
>
|
||||
Load
|
||||
</button>
|
||||
<button
|
||||
:if={{ loaded?(@record, relationship.name) && relationship.cardinality == :many }}
|
||||
:if={loaded?(@record, relationship.name) && relationship.cardinality == :many}
|
||||
:on-click="unload"
|
||||
phx-value-relationship={{ relationship.name }}
|
||||
phx-value-relationship={relationship.name}
|
||||
type="button"
|
||||
class="flex py-2 ml-4 px-4 mt-2 bg-indigo-600 text-white border-gray-600 hover:bg-gray-400 rounded-md justify-center items-center"
|
||||
>
|
||||
Unload
|
||||
</button>
|
||||
|
||||
<div :if={{ loaded?(@record, relationship.name) }}>
|
||||
{{ render_relationship_data(assigns, @record, relationship) }}
|
||||
<div :if={loaded?(@record, relationship.name)}>
|
||||
{render_relationship_data(assigns, @record, relationship)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -136,22 +122,16 @@ defmodule AshAdmin.Components.Resource.Show do
|
|||
}) do
|
||||
case Map.get(record, name) do
|
||||
nil ->
|
||||
~H"None"
|
||||
~F"None"
|
||||
|
||||
record ->
|
||||
~H"""
|
||||
~F"""
|
||||
<div class="mb-10">
|
||||
{{ render_attributes(assigns, record, destination) }}
|
||||
{render_attributes(assigns, record, destination)}
|
||||
<div class="px-4 py-3 text-right sm:px-6">
|
||||
<LiveRedirect
|
||||
:if={{ AshAdmin.Resource.show_action(destination) }}
|
||||
to={{ash_show_path(
|
||||
@prefix,
|
||||
@api,
|
||||
destination,
|
||||
record,
|
||||
context[:data_layer][:table]
|
||||
)}}
|
||||
:if={AshAdmin.Resource.show_action(destination)}
|
||||
to={"#{@prefix}?api=#{AshAdmin.Api.name(@api)}&resource=#{AshAdmin.Resource.name(@resource)}&tab=show&table=#{context[:data_layer][:table]}&primary_key=#{encode_primary_key(@record)}"}
|
||||
class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
>
|
||||
Show
|
||||
|
@ -171,74 +151,74 @@ defmodule AshAdmin.Components.Resource.Show do
|
|||
}) do
|
||||
data = Map.get(record, name)
|
||||
|
||||
~H"""
|
||||
~F"""
|
||||
<div class="mb-10 overflow-scroll">
|
||||
<Table
|
||||
data={{ data }}
|
||||
resource={{ destination }}
|
||||
api={{ @api }}
|
||||
set_actor={{ @set_actor }}
|
||||
table={{ context[:data_layer][:table] }}
|
||||
prefix={{ @prefix }}
|
||||
skip={{ [destination_field] }}
|
||||
data={data}
|
||||
resource={destination}
|
||||
api={@api}
|
||||
set_actor={@set_actor}
|
||||
table={context[:data_layer][:table]}
|
||||
prefix={@prefix}
|
||||
skip={[destination_field]}
|
||||
/>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
defp render_attributes(assigns, record, resource) do
|
||||
~H"""
|
||||
{{ {attributes, flags, bottom_attributes, _} =
|
||||
~F"""
|
||||
{{attributes, flags, bottom_attributes, _} =
|
||||
AshAdmin.Components.Resource.Form.attributes(resource, :show)
|
||||
|
||||
nil }}
|
||||
nil}
|
||||
<div class="grid grid-cols-6 gap-6">
|
||||
<div
|
||||
:for={{ attribute <- attributes }}
|
||||
class={{
|
||||
:for={attribute <- attributes}
|
||||
class={
|
||||
"col-span-6",
|
||||
"sm:col-span-2": short_text?(resource, attribute),
|
||||
"sm:col-span-3": !long_text?(resource, attribute)
|
||||
}}
|
||||
}
|
||||
>
|
||||
<div class="block text-sm font-medium text-gray-700">{{ to_name(attribute.name) }}</div>
|
||||
<div>{{ render_attribute(assigns, resource, record, attribute) }}</div>
|
||||
<div class="block text-sm font-medium text-gray-700">{to_name(attribute.name)}</div>
|
||||
<div>{render_attribute(assigns, resource, record, attribute)}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div :if={{ !Enum.empty?(flags) }} class="hidden sm:block" aria-hidden="true">
|
||||
<div :if={!Enum.empty?(flags)} class="hidden sm:block" aria-hidden="true">
|
||||
<div class="py-5">
|
||||
<div class="border-t border-gray-200" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-6 gap-6" :if={{ !Enum.empty?(flags) }}>
|
||||
<div class="grid grid-cols-6 gap-6" :if={!Enum.empty?(flags)}>
|
||||
<div
|
||||
:for={{ attribute <- flags }}
|
||||
class={{
|
||||
:for={attribute <- flags}
|
||||
class={
|
||||
"col-span-6",
|
||||
"sm:col-span-2": short_text?(resource, attribute),
|
||||
"sm:col-span-3": !long_text?(resource, attribute)
|
||||
}}
|
||||
}
|
||||
>
|
||||
<div class="block text-sm font-medium text-gray-700">{{ to_name(attribute.name) }}</div>
|
||||
<div>{{ render_attribute(assigns, resource, record, attribute) }}</div>
|
||||
<div class="block text-sm font-medium text-gray-700">{to_name(attribute.name)}</div>
|
||||
<div>{render_attribute(assigns, resource, record, attribute)}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div :if={{ !Enum.empty?(bottom_attributes) }} class="hidden sm:block" aria-hidden="true">
|
||||
<div :if={!Enum.empty?(bottom_attributes)} class="hidden sm:block" aria-hidden="true">
|
||||
<div class="py-5">
|
||||
<div class="border-t border-gray-200" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-6 gap-6" :if={{ !Enum.empty?(bottom_attributes) }}>
|
||||
<div class="grid grid-cols-6 gap-6" :if={!Enum.empty?(bottom_attributes)}>
|
||||
<div
|
||||
:for={{ attribute <- bottom_attributes }}
|
||||
class={{
|
||||
:for={attribute <- bottom_attributes}
|
||||
class={
|
||||
"col-span-6",
|
||||
"sm:col-span-2": short_text?(resource, attribute),
|
||||
"sm:col-span-3": !(long_text?(resource, attribute) || Ash.Type.embedded_type?(attribute.type))
|
||||
}}
|
||||
}
|
||||
>
|
||||
<div class="block text-sm font-medium text-gray-700">{{ to_name(attribute.name) }}</div>
|
||||
<div>{{ render_attribute(assigns, resource, record, attribute) }}</div>
|
||||
<div class="block text-sm font-medium text-gray-700">{to_name(attribute.name)}</div>
|
||||
<div>{render_attribute(assigns, resource, record, attribute)}</div>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
|
@ -256,24 +236,24 @@ defmodule AshAdmin.Components.Resource.Show do
|
|||
all_classes = "mb-4 pb-4 shadow-md"
|
||||
|
||||
if Map.get(record, name) in [[], nil] do
|
||||
~H"""
|
||||
~F"""
|
||||
None
|
||||
"""
|
||||
else
|
||||
if nested? do
|
||||
~H"""
|
||||
~F"""
|
||||
<ul>
|
||||
<li :for={{ value <- List.wrap(Map.get(record, name)) }} class={{ all_classes }}>
|
||||
{{ render_attribute(assigns, resource, Map.put(record, name, value), %{attribute | type: type}, true) }}
|
||||
<li :for={value <- List.wrap(Map.get(record, name))} class={all_classes}>
|
||||
{render_attribute(assigns, resource, Map.put(record, name, value), %{attribute | type: type}, true)}
|
||||
</li>
|
||||
</ul>
|
||||
"""
|
||||
else
|
||||
~H"""
|
||||
~F"""
|
||||
<div class="shadow-md border mt-4 mb-4 ml-4">
|
||||
<ul>
|
||||
<li :for={{ value <- List.wrap(Map.get(record, name)) }} class={{ "my-4", all_classes }}>
|
||||
{{ render_attribute(assigns, resource, Map.put(record, name, value), %{attribute | type: type}, true) }}
|
||||
<li :for={value <- List.wrap(Map.get(record, name))} class={"my-4", all_classes}>
|
||||
{render_attribute(assigns, resource, Map.put(record, name, value), %{attribute | type: type}, true)}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -295,16 +275,16 @@ defmodule AshAdmin.Components.Resource.Show do
|
|||
defp render_attribute(assigns, _resource, record, %{type: Ash.Type.Map} = attribute, _nested?) do
|
||||
encoded = Jason.encode!(Map.get(record, attribute.name))
|
||||
|
||||
~H"""
|
||||
~F"""
|
||||
<div
|
||||
phx-hook="JsonView"
|
||||
data-json={{encoded}}
|
||||
id={{"_#{AshAdmin.Helpers.encode_primary_key(record)}_#{attribute.name}_json"}}
|
||||
data-json={encoded}
|
||||
id={"_#{AshAdmin.Helpers.encode_primary_key(record)}_#{attribute.name}_json"}
|
||||
/>
|
||||
"""
|
||||
rescue
|
||||
_ ->
|
||||
~H"""
|
||||
~F"""
|
||||
...
|
||||
"""
|
||||
end
|
||||
|
@ -312,17 +292,17 @@ defmodule AshAdmin.Components.Resource.Show do
|
|||
defp render_attribute(assigns, _resource, record, %{name: name, type: Ash.Type.Boolean}, _) do
|
||||
case Map.get(record, name) do
|
||||
true ->
|
||||
~H"""
|
||||
~F"""
|
||||
<HeroIcon name="check" class="h-4 w-4 text-gray-600" />
|
||||
"""
|
||||
|
||||
false ->
|
||||
~H"""
|
||||
~F"""
|
||||
<HeroIcon name="x" class="h-4 w-4 text-gray-600" />
|
||||
"""
|
||||
|
||||
nil ->
|
||||
~H"""
|
||||
~F"""
|
||||
<HeroIcon name="minus" class="h-4 w-4 text-gray-600" />
|
||||
"""
|
||||
end
|
||||
|
@ -333,20 +313,20 @@ defmodule AshAdmin.Components.Resource.Show do
|
|||
both_classes = "ml-1 pl-2 pr-2"
|
||||
|
||||
if Map.get(record, attribute.name) in [nil, []] do
|
||||
~H"""
|
||||
~F"""
|
||||
None
|
||||
"""
|
||||
else
|
||||
if nested? do
|
||||
~H"""
|
||||
<div class={{ both_classes }}>
|
||||
{{ render_attributes(assigns, Map.get(record, attribute.name), attribute.type) }}
|
||||
~F"""
|
||||
<div class={both_classes}>
|
||||
{render_attributes(assigns, Map.get(record, attribute.name), attribute.type)}
|
||||
</div>
|
||||
"""
|
||||
else
|
||||
~H"""
|
||||
<div class={{ "shadow-md border mt-4 mb-4 ml-2 rounded py-2 px-2", both_classes }}>
|
||||
{{ render_attributes(assigns, Map.get(record, attribute.name), attribute.type) }}
|
||||
~F"""
|
||||
<div class={"shadow-md border mt-4 mb-4 ml-2 rounded py-2 px-2", both_classes}>
|
||||
{render_attributes(assigns, Map.get(record, attribute.name), attribute.type)}
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
@ -355,32 +335,32 @@ defmodule AshAdmin.Components.Resource.Show do
|
|||
if attribute.type == Ash.Type.String do
|
||||
cond do
|
||||
short_text?(resource, attribute) ->
|
||||
~H"""
|
||||
{{ value!(Map.get(record, attribute.name)) }}
|
||||
~F"""
|
||||
{value!(Map.get(record, attribute.name))}
|
||||
"""
|
||||
|
||||
long_text?(resource, attribute) ->
|
||||
~H"""
|
||||
~F"""
|
||||
<textarea
|
||||
rows="3"
|
||||
cols="40"
|
||||
disabled
|
||||
class="resize-y mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md"
|
||||
>{{ value!(Map.get(record, attribute.name)) }}</textarea>
|
||||
>{value!(Map.get(record, attribute.name))}</textarea>
|
||||
"""
|
||||
|
||||
true ->
|
||||
~H"""
|
||||
~F"""
|
||||
<textarea
|
||||
rows="1"
|
||||
cols="20"
|
||||
disabled
|
||||
class="resize-y mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md"
|
||||
>{{ value!(Map.get(record, attribute.name)) }}</textarea>
|
||||
>{value!(Map.get(record, attribute.name))}</textarea>
|
||||
"""
|
||||
end
|
||||
else
|
||||
~H"{{ value!(Map.get(record, attribute.name)) }}"
|
||||
~F"{value!(Map.get(record, attribute.name))}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -19,57 +19,44 @@ defmodule AshAdmin.Components.Resource.Table do
|
|||
prop(format_fields, :any, default: [])
|
||||
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
~F"""
|
||||
<div>
|
||||
<table class="rounded-t-lg m-5 w-5/6 mx-auto text-left">
|
||||
<thead class="text-left border-b-2">
|
||||
<th :for={{ attribute <- attributes(@resource, @attributes, @skip) }}>
|
||||
{{ to_name(attribute.name) }}
|
||||
<th :for={attribute <- attributes(@resource, @attributes, @skip)}>
|
||||
{to_name(attribute.name)}
|
||||
</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr :for={{ record <- @data }} class="border-b-2">
|
||||
<td :for={{ attribute <- attributes(@resource, @attributes, @skip) }} class="py-3">{{ render_attribute(@api, record, attribute, @format_fields) }}</td>
|
||||
<td :if={{ @actions && actions?(@resource) }}>
|
||||
<tr :for={record <- @data} class="border-b-2">
|
||||
<td :for={attribute <- attributes(@resource, @attributes, @skip)} class="py-3">{render_attribute(@api, record, attribute, @format_fields)}</td>
|
||||
<td :if={@actions && actions?(@resource)}>
|
||||
<div class="flex h-max justify-items-center">
|
||||
<div :if={{ AshAdmin.Resource.show_action(@resource) }}>
|
||||
<LiveRedirect to={{ ash_show_path(@prefix, @api, @resource, record, @table) }}>
|
||||
<div :if={AshAdmin.Resource.show_action(@resource)}>
|
||||
<LiveRedirect to={"#{@prefix}?api=#{AshAdmin.Api.name(@api)}&resource=#{AshAdmin.Resource.name(@resource)}&tab=show&table=#{@table}&primary_key=#{encode_primary_key(record)}"}>
|
||||
<HeroIcon name="information-circle" class="h-5 w-5 text-gray-500" />
|
||||
</LiveRedirect>
|
||||
</div>
|
||||
|
||||
<div :if={{ Ash.Resource.Info.primary_action(@resource, :update) }}>
|
||||
<LiveRedirect to={{ash_update_path(
|
||||
@prefix,
|
||||
@api,
|
||||
@resource,
|
||||
record,
|
||||
Ash.Resource.Info.primary_action(@resource, :update).name,
|
||||
@table
|
||||
)}}>
|
||||
<div :if={Ash.Resource.Info.primary_action(@resource, :update)}>
|
||||
<LiveRedirect
|
||||
to={"#{@prefix}?api=#{AshAdmin.Api.name(@api)}&resource=#{AshAdmin.Resource.name(@resource)}&action_type=update&action=#{Ash.Resource.Info.primary_action(@resource, :update).name}&tab=update&table=#{@table}&primary_key=#{encode_primary_key(record)}"}>
|
||||
<HeroIcon name="pencil" class="h-5 w-5 text-gray-500" />
|
||||
</LiveRedirect>
|
||||
</div>
|
||||
|
||||
<div :if={{ Ash.Resource.Info.primary_action(@resource, :destroy) }}>
|
||||
<LiveRedirect to={{ash_destroy_path(
|
||||
@prefix,
|
||||
@api,
|
||||
@resource,
|
||||
record,
|
||||
Ash.Resource.Info.primary_action(@resource, :destroy).name,
|
||||
@table
|
||||
)}}>
|
||||
<div :if={Ash.Resource.Info.primary_action(@resource, :destroy)}>
|
||||
<LiveRedirect to={"#{@prefix}?api=#{AshAdmin.Api.name(@api)}&resource=#{AshAdmin.Resource.name(@resource)}&action_type=destroy&action=#{Ash.Resource.Info.primary_action(@resource, :destroy).name}&tab=destroy&table=#{@table}&primary_key=#{encode_primary_key(record)}"}>
|
||||
<HeroIcon name="x-circle" class="h-5 w-5 text-gray-500" />
|
||||
</LiveRedirect>
|
||||
</div>
|
||||
|
||||
<button
|
||||
:if={{ AshAdmin.Resource.actor?(@resource) }}
|
||||
:on-click={{ @set_actor }}
|
||||
phx-value-resource={{ @resource }}
|
||||
phx-value-api={{ @api }}
|
||||
phx-value-pkey={{ encode_primary_key(record) }}
|
||||
:if={AshAdmin.Resource.actor?(@resource)}
|
||||
:on-click={@set_actor}
|
||||
phx-value-resource={@resource}
|
||||
phx-value-api={@api}
|
||||
phx-value-pkey={encode_primary_key(record)}
|
||||
>
|
||||
<HeroIcon name="key" class="h-5 w-5 text-gray-500" />
|
||||
</button>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
defmodule AshAdmin.Components.TopNav do
|
||||
@moduledoc false
|
||||
use Surface.LiveComponent
|
||||
import AshAdmin.Helpers
|
||||
alias Surface.Components.LiveRedirect
|
||||
alias AshAdmin.Components.TopNav.{ActorSelect, DrawerDropdown, TenantForm, Dropdown}
|
||||
|
||||
|
@ -24,14 +23,14 @@ defmodule AshAdmin.Components.TopNav do
|
|||
prop(prefix, :any, required: true)
|
||||
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
~F"""
|
||||
<nav x-data="{ navOpen: false }" @keydown.window.escape="navOpen = false" class="bg-gray-800">
|
||||
<div class="px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex items-center justify-between h-16">
|
||||
<div class="flex items-center w-full">
|
||||
<div class="flex-shrink-0">
|
||||
<h3 class="text-white text-lg">
|
||||
<LiveRedirect to={{ ash_admin_path(@prefix) }}>
|
||||
<LiveRedirect to={@prefix}>
|
||||
Admin
|
||||
</LiveRedirect>
|
||||
</h3>
|
||||
|
@ -40,34 +39,34 @@ defmodule AshAdmin.Components.TopNav do
|
|||
<div class="flex justify-between">
|
||||
<div class="ml-10 flex items-center">
|
||||
<Dropdown
|
||||
:for={{ api <- @apis }}
|
||||
active={{ api == @api }}
|
||||
:for={api <- @apis}
|
||||
active={api == @api}
|
||||
class="mr-1"
|
||||
id={{ AshAdmin.Api.name(api) <> "_api_nav" }}
|
||||
name={{ AshAdmin.Api.name(api) }}
|
||||
groups={{ dropdown_groups(@prefix, @resource, api) }}
|
||||
id={AshAdmin.Api.name(api) <> "_api_nav"}
|
||||
name={AshAdmin.Api.name(api)}
|
||||
groups={dropdown_groups(@prefix, @resource, api)}
|
||||
/>
|
||||
</div>
|
||||
<div class="ml-10 flex items-center">
|
||||
<ActorSelect
|
||||
:if={{ @actor_resources != []}}
|
||||
actor_resources={{ @actor_resources }}
|
||||
authorizing={{ @authorizing }}
|
||||
actor_paused={{ @actor_paused }}
|
||||
actor={{ @actor }}
|
||||
toggle_authorizing={{ @toggle_authorizing }}
|
||||
toggle_actor_paused={{ @toggle_actor_paused }}
|
||||
clear_actor={{ @clear_actor }}
|
||||
actor_api={{ @actor_api }}
|
||||
api={{ @api }}
|
||||
prefix={{ @prefix }}
|
||||
:if={@actor_resources != []}
|
||||
actor_resources={@actor_resources}
|
||||
authorizing={@authorizing}
|
||||
actor_paused={@actor_paused}
|
||||
actor={@actor}
|
||||
toggle_authorizing={@toggle_authorizing}
|
||||
toggle_actor_paused={@toggle_actor_paused}
|
||||
clear_actor={@clear_actor}
|
||||
actor_api={@actor_api}
|
||||
api={@api}
|
||||
prefix={@prefix}
|
||||
/>
|
||||
<TenantForm
|
||||
:if={{ show_tenant_form?(@apis) }}
|
||||
tenant={{ @tenant }}
|
||||
:if={show_tenant_form?(@apis)}
|
||||
tenant={@tenant}
|
||||
id="tenant_editor"
|
||||
set_tenant={{ @set_tenant }}
|
||||
clear_tenant={{ @clear_tenant }}
|
||||
set_tenant={@set_tenant}
|
||||
clear_tenant={@clear_tenant}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -104,33 +103,33 @@ defmodule AshAdmin.Components.TopNav do
|
|||
<div class="relative px-2 pt-2 pb-3 sm:px-3">
|
||||
<div class="block px-4 py-2 text-sm">
|
||||
<ActorSelect
|
||||
:if={{ @actor_resources != [] }}
|
||||
actor_resources={{ @actor_resources }}
|
||||
authorizing={{ @authorizing }}
|
||||
actor_paused={{ @actor_paused }}
|
||||
actor={{ @actor }}
|
||||
toggle_authorizing={{ @toggle_authorizing }}
|
||||
toggle_actor_paused={{ @toggle_actor_paused }}
|
||||
clear_actor={{ @clear_actor }}
|
||||
actor_api={{ @actor_api }}
|
||||
api={{ @api }}
|
||||
prefix={{ @prefix }}
|
||||
:if={@actor_resources != []}
|
||||
actor_resources={@actor_resources}
|
||||
authorizing={@authorizing}
|
||||
actor_paused={@actor_paused}
|
||||
actor={@actor}
|
||||
toggle_authorizing={@toggle_authorizing}
|
||||
toggle_actor_paused={@toggle_actor_paused}
|
||||
clear_actor={@clear_actor}
|
||||
actor_api={@actor_api}
|
||||
api={@api}
|
||||
prefix={@prefix}
|
||||
/>
|
||||
</div>
|
||||
<div class="block px-4 py-2 text-sm">
|
||||
<TenantForm
|
||||
:if={{ show_tenant_form?(@apis) }}
|
||||
tenant={{ @tenant }}
|
||||
:if={show_tenant_form?(@apis)}
|
||||
tenant={@tenant}
|
||||
id="tenant_editor_drawer"
|
||||
set_tenant={{ @set_tenant }}
|
||||
clear_tenant={{ @clear_tenant }}
|
||||
set_tenant={@set_tenant}
|
||||
clear_tenant={@clear_tenant}
|
||||
/>
|
||||
</div>
|
||||
<DrawerDropdown
|
||||
:for={{ api <- @apis }}
|
||||
id={{ AshAdmin.Api.name(api) <> "_api_nav_drawer" }}
|
||||
name={{ AshAdmin.Api.name(api) }}
|
||||
groups={{ dropdown_groups(@prefix, @resource, api) }}
|
||||
:for={api <- @apis}
|
||||
id={AshAdmin.Api.name(api) <> "_api_nav_drawer"}
|
||||
name={AshAdmin.Api.name(api)}
|
||||
groups={dropdown_groups(@prefix, @resource, api)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -143,7 +142,8 @@ defmodule AshAdmin.Components.TopNav do
|
|||
for resource <- Ash.Api.resources(api) do
|
||||
%{
|
||||
text: AshAdmin.Resource.name(resource),
|
||||
to: ash_admin_path(prefix, api, resource),
|
||||
to:
|
||||
"#{prefix}?api=#{AshAdmin.Api.name(api)}&resource=#{AshAdmin.Resource.name(resource)}",
|
||||
active: resource == current_resource
|
||||
}
|
||||
end
|
||||
|
|
|
@ -18,13 +18,13 @@ defmodule AshAdmin.Components.TopNav.ActorSelect do
|
|||
prop(prefix, :any, required: true)
|
||||
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
~F"""
|
||||
<div id="actor-hook" class="flex items-center mr-5 text-white" phx-hook="Actor">
|
||||
<div>
|
||||
<span>
|
||||
<button :on-click={{ @toggle_authorizing }} type="button">
|
||||
<button :on-click={@toggle_authorizing} type="button">
|
||||
<svg
|
||||
:if={{ @authorizing }}
|
||||
:if={@authorizing}
|
||||
width="1em"
|
||||
height="1em"
|
||||
viewBox="0 0 16 16"
|
||||
|
@ -37,7 +37,7 @@ defmodule AshAdmin.Components.TopNav.ActorSelect do
|
|||
/>
|
||||
</svg>
|
||||
<svg
|
||||
:if={{ !@authorizing }}
|
||||
:if={!@authorizing}
|
||||
width="1em"
|
||||
height="1em"
|
||||
viewBox="0 0 16 16"
|
||||
|
@ -50,9 +50,9 @@ defmodule AshAdmin.Components.TopNav.ActorSelect do
|
|||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<button :if={{@actor}} :on-click={{ @toggle_actor_paused }} type="button">
|
||||
<button :if={@actor} :on-click={@toggle_actor_paused} type="button">
|
||||
<svg
|
||||
:if={{ @actor_paused }}
|
||||
:if={@actor_paused}
|
||||
width="1em"
|
||||
height="1em"
|
||||
viewBox="0 0 16 16"
|
||||
|
@ -62,7 +62,7 @@ defmodule AshAdmin.Components.TopNav.ActorSelect do
|
|||
<path d="M11.596 8.697l-6.363 3.692c-.54.313-1.233-.066-1.233-.697V4.308c0-.63.692-1.01 1.233-.696l6.363 3.692a.802.802 0 0 1 0 1.393z" />
|
||||
</svg>
|
||||
<svg
|
||||
:if={{ !@actor_paused }}
|
||||
:if={!@actor_paused}
|
||||
width="1em"
|
||||
height="1em"
|
||||
viewBox="0 0 16 16"
|
||||
|
@ -73,19 +73,12 @@ defmodule AshAdmin.Components.TopNav.ActorSelect do
|
|||
</svg>
|
||||
</button>
|
||||
<LiveRedirect
|
||||
:if={{@actor}}
|
||||
:if={@actor}
|
||||
class="hover:text-blue-400 hover:underline"
|
||||
to={{ash_show_path(
|
||||
@prefix,
|
||||
@actor_api,
|
||||
@actor.__struct__,
|
||||
@actor,
|
||||
nil
|
||||
)}}
|
||||
>
|
||||
{{ user_display(@actor) }}
|
||||
to={"#{@prefix}?api=#{AshAdmin.Api.name(@actor_api)}&resource=#{AshAdmin.Resource.name(@actor.__struct__)}&tab=show&primary_key=#{encode_primary_key(@actor)}"}>
|
||||
{user_display(@actor)}
|
||||
</LiveRedirect>
|
||||
<button :if={{@actor}} :on-click={{ @clear_actor }} type="button">
|
||||
<button :if={@actor} :on-click={@clear_actor} type="button">
|
||||
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="white" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
|
@ -99,44 +92,30 @@ defmodule AshAdmin.Components.TopNav.ActorSelect do
|
|||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<div :if={{ !@actor }}>
|
||||
{{ render_actor_link(assigns, @actor_resources) }}
|
||||
<div :if={!@actor}>
|
||||
{render_actor_link(assigns, @actor_resources)}
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
defp render_actor_link(assigns, [{api, resource}]) do
|
||||
~H"""
|
||||
<LiveRedirect to={{ash_action_path(
|
||||
@prefix,
|
||||
api,
|
||||
resource,
|
||||
:read,
|
||||
Ash.Resource.Info.primary_action(resource, :read).name,
|
||||
nil
|
||||
)}}>
|
||||
Set {{ AshAdmin.Resource.name(resource) }}
|
||||
~F"""
|
||||
<LiveRedirect to={"#{@prefix}?api=#{AshAdmin.Api.name(api)}&resource=#{AshAdmin.Resource.name(resource)}&action_type=read"}>
|
||||
Set {AshAdmin.Resource.name(resource)}
|
||||
</LiveRedirect>
|
||||
"""
|
||||
end
|
||||
|
||||
defp render_actor_link(assigns, apis_and_resources) do
|
||||
~H"""
|
||||
~F"""
|
||||
<div aria-labelledby="actor-banner">
|
||||
<LiveRedirect
|
||||
to={{ash_action_path(
|
||||
@prefix,
|
||||
api,
|
||||
resource,
|
||||
:read,
|
||||
Ash.Resource.Info.primary_action(resource, :read).name,
|
||||
nil
|
||||
)}}
|
||||
:for.with_index={{ {{api, resource}, i} <- apis_and_resources }}
|
||||
to={"#{@prefix}?api=#{AshAdmin.Api.name(api)}&resource=#{AshAdmin.Resource.name(resource)}&action_type=read"}
|
||||
:for.with_index={{{api, resource}, i} <- apis_and_resources}
|
||||
>
|
||||
Set {{ AshAdmin.Resource.name(resource) }}
|
||||
<span :if={{ i != Enum.count(apis_and_resources) - 1 }}>
|
||||
Set {AshAdmin.Resource.name(resource)}
|
||||
<span :if={i != Enum.count(apis_and_resources) - 1}>
|
||||
|
|
||||
</span>
|
||||
</LiveRedirect>
|
||||
|
|
|
@ -9,22 +9,22 @@ defmodule AshAdmin.Components.TopNav.DrawerDropdown do
|
|||
prop(id, :string, required: true)
|
||||
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
~F"""
|
||||
<div class="relative">
|
||||
<div x-data="{isOpen: false}">
|
||||
<a
|
||||
@click="isOpen = !isOpen"
|
||||
id={{ "#{@id}_dropdown_drawer" }}
|
||||
id={"#{@id}_dropdown_drawer"}
|
||||
class="mt-1 block px-3 py-2 rounded-t text-base font-medium text-gray-300 hover:text-white hover:bg-gray-700 focus:outline-none focus:text-white focus:bg-gray-700"
|
||||
href="#"
|
||||
x-bind:class="{'text-white bg-gray-700': isOpen}"
|
||||
>
|
||||
{{ @name }}
|
||||
{@name}
|
||||
</a>
|
||||
|
||||
<div
|
||||
:for={{ group <- @groups }}
|
||||
aria-labelledby={{ "#{@id}_dropown_drawer" }}
|
||||
:for={group <- @groups}
|
||||
aria-labelledby={"#{@id}_dropown_drawer"}
|
||||
class="bg-gray-700 text-white"
|
||||
x-show="isOpen"
|
||||
role="menu"
|
||||
|
@ -36,12 +36,12 @@ defmodule AshAdmin.Components.TopNav.DrawerDropdown do
|
|||
x-transition:leave-end="opacity-0 transform -translate-y-3"
|
||||
>
|
||||
<LiveRedirect
|
||||
:for={{ link <- group }}
|
||||
to={{ link.to }}
|
||||
:for={link <- group}
|
||||
to={link.to}
|
||||
class="block px-4 py-2 text-sm hover:bg-gray-200 hover:text-gray-900"
|
||||
opts={{ role: "menuitem" }}
|
||||
opts={role: "menuitem"}
|
||||
>
|
||||
{{ link.text }}
|
||||
{link.text}
|
||||
</LiveRedirect>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -11,22 +11,22 @@ defmodule AshAdmin.Components.TopNav.Dropdown do
|
|||
prop(class, :css_class)
|
||||
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<div class={{ "relative", @class }}>
|
||||
~F"""
|
||||
<div class={"relative", @class}>
|
||||
<div x-data="{isOpen: false}">
|
||||
<button
|
||||
type="button"
|
||||
class={{
|
||||
class={
|
||||
"inline-flex justify-center w-full rounded-md border border-gray-300 shadow-sm px-4 py-2 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-100 focus:ring-indigo-500",
|
||||
"bg-gray-800 hover:bg-gray-900 text-white": @active,
|
||||
"bg-white text-gray-700 hover:bg-gray-300": !@active
|
||||
}}
|
||||
}
|
||||
@click="isOpen = !isOpen"
|
||||
id={{ "#{@id}_dropown" }}
|
||||
id={"#{@id}_dropown"}
|
||||
aria-haspopup="true"
|
||||
aria-expanded="true"
|
||||
>
|
||||
{{ @name }}
|
||||
{@name}
|
||||
|
||||
<svg
|
||||
class="-mr-1 ml-2 h-5 w-5"
|
||||
|
@ -45,10 +45,10 @@ defmodule AshAdmin.Components.TopNav.Dropdown do
|
|||
|
||||
<div
|
||||
x-show="isOpen"
|
||||
class={{
|
||||
class={
|
||||
"origin-top-right absolute left-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 divide-y divide-gray-100 z-10",
|
||||
"bg-gray-600 hover:bg-gray-700": single_active_group?(@groups)
|
||||
}}
|
||||
}
|
||||
x-transition:enter="transition ease-out duration-100"
|
||||
x-transition:enter-start="transform opacity-0 scale-95"
|
||||
x-transition:enter-end="transform opacity-0 scale-95"
|
||||
|
@ -58,26 +58,26 @@ defmodule AshAdmin.Components.TopNav.Dropdown do
|
|||
role="menu"
|
||||
aria-orientation="vertical"
|
||||
@click.away="isOpen=false"
|
||||
id={{ "#{@id}_dropown" }}
|
||||
id={"#{@id}_dropown"}
|
||||
>
|
||||
<div
|
||||
:for={{ group <- @groups }}
|
||||
:for={group <- @groups}
|
||||
class="py-1"
|
||||
role="menu"
|
||||
aria-orientation="vertical"
|
||||
aria-labelledby={{ "#{@id}_dropown" }}
|
||||
aria-labelledby={"#{@id}_dropown"}
|
||||
>
|
||||
<LiveRedirect
|
||||
:for={{ link <- group }}
|
||||
to={{ link.to }}
|
||||
class={{
|
||||
:for={link <- group}
|
||||
to={link.to}
|
||||
class={
|
||||
"block px-4 py-2 text-sm ",
|
||||
"bg-gray-600 text-white hover:bg-gray-700": Map.get(link, :active),
|
||||
"text-gray-700 hover:bg-gray-100 hover:text-gray-900": !Map.get(link, :active)
|
||||
}}
|
||||
opts={{ role: "menuitem" }}
|
||||
}
|
||||
opts={role: "menuitem"}
|
||||
>
|
||||
{{ link.text }}
|
||||
{link.text}
|
||||
</LiveRedirect>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -9,10 +9,10 @@ defmodule AshAdmin.Components.TopNav.TenantForm do
|
|||
prop(set_tenant, :event, required: true)
|
||||
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
~F"""
|
||||
<div id="tenant-hook" class="relative text-white" phx-hook="Tenant">
|
||||
<form :if={{ @editing_tenant }} :on-submit={{ @set_tenant }}>
|
||||
<input type="text" name="tenant" value={{ @tenant }} class={{ "text-black": @editing_tenant }}>
|
||||
<form :if={@editing_tenant} :on-submit={@set_tenant}>
|
||||
<input type="text" name="tenant" value={@tenant} class={"text-black": @editing_tenant}>
|
||||
<button :on-click="stop_editing_tenant">
|
||||
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="white" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
|
@ -26,7 +26,7 @@ defmodule AshAdmin.Components.TopNav.TenantForm do
|
|||
</svg>
|
||||
</button>
|
||||
</form>
|
||||
<button :if={{ @tenant }} :on-click={{ @clear_tenant }}>
|
||||
<button :if={@tenant} :on-click={@clear_tenant}>
|
||||
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="white" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
|
@ -34,8 +34,8 @@ defmodule AshAdmin.Components.TopNav.TenantForm do
|
|||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<a :if={{ !@editing_tenant }} href="#" :on-click="start_editing_tenant">
|
||||
{{ @tenant || "No tenant" }}
|
||||
<a :if={!@editing_tenant} href="#" :on-click="start_editing_tenant">
|
||||
{@tenant || "No tenant"}
|
||||
</a>
|
||||
</div>
|
||||
"""
|
||||
|
|
|
@ -48,138 +48,6 @@ defmodule AshAdmin.Helpers do
|
|||
end
|
||||
end
|
||||
|
||||
defp prefix(nil, path), do: path
|
||||
defp prefix(prefix, path), do: prefix <> path
|
||||
|
||||
def ash_admin_path(prefix) do
|
||||
prefix(prefix, "/")
|
||||
end
|
||||
|
||||
def ash_admin_path(prefix, api) do
|
||||
prefix(prefix, "/#{AshAdmin.Api.name(api)}")
|
||||
end
|
||||
|
||||
def ash_admin_path(prefix, api, resource) do
|
||||
prefix(
|
||||
prefix,
|
||||
"/#{AshAdmin.Api.name(api)}/#{AshAdmin.Resource.name(resource)}"
|
||||
)
|
||||
end
|
||||
|
||||
def ash_create_path(prefix, api, resource) do
|
||||
prefix(
|
||||
prefix,
|
||||
"/#{AshAdmin.Api.name(api)}/#{AshAdmin.Resource.name(resource)}/create"
|
||||
)
|
||||
end
|
||||
|
||||
def ash_create_path(prefix, api, resource, action_name, nil) do
|
||||
prefix(
|
||||
prefix,
|
||||
"/#{AshAdmin.Api.name(api)}/#{AshAdmin.Resource.name(resource)}/create/#{action_name}"
|
||||
)
|
||||
end
|
||||
|
||||
def ash_create_path(prefix, api, resource, action_name, table) do
|
||||
prefix(
|
||||
prefix,
|
||||
"/#{AshAdmin.Api.name(api)}/#{AshAdmin.Resource.name(resource)}/#{table}/create/#{
|
||||
action_name
|
||||
}"
|
||||
)
|
||||
end
|
||||
|
||||
def ash_update_path(prefix, api, resource, record) do
|
||||
prefix(
|
||||
prefix,
|
||||
"/#{AshAdmin.Api.name(api)}/#{AshAdmin.Resource.name(resource)}/update/#{
|
||||
encode_primary_key(record)
|
||||
}"
|
||||
)
|
||||
end
|
||||
|
||||
def ash_update_path(prefix, api, resource, record, action_name, nil) do
|
||||
prefix(
|
||||
prefix,
|
||||
"/#{AshAdmin.Api.name(api)}/#{AshAdmin.Resource.name(resource)}/update/#{action_name}/#{
|
||||
encode_primary_key(record)
|
||||
}"
|
||||
)
|
||||
end
|
||||
|
||||
def ash_update_path(prefix, api, resource, record, action_name, table) do
|
||||
prefix(
|
||||
prefix,
|
||||
"/#{AshAdmin.Api.name(api)}/#{AshAdmin.Resource.name(resource)}/#{table}/update/#{
|
||||
action_name
|
||||
}/#{encode_primary_key(record)}"
|
||||
)
|
||||
end
|
||||
|
||||
def ash_destroy_path(prefix, api, resource, record) do
|
||||
prefix(
|
||||
prefix,
|
||||
"/#{AshAdmin.Api.name(api)}/#{AshAdmin.Resource.name(resource)}/destroy/#{
|
||||
encode_primary_key(record)
|
||||
}"
|
||||
)
|
||||
end
|
||||
|
||||
def ash_destroy_path(prefix, api, resource, record, action_name, nil) do
|
||||
prefix(
|
||||
prefix,
|
||||
"/#{AshAdmin.Api.name(api)}/#{AshAdmin.Resource.name(resource)}/destroy/#{action_name}/#{
|
||||
encode_primary_key(record)
|
||||
}"
|
||||
)
|
||||
end
|
||||
|
||||
def ash_destroy_path(prefix, api, resource, record, action_name, table) do
|
||||
prefix(
|
||||
prefix,
|
||||
"/#{AshAdmin.Api.name(api)}/#{AshAdmin.Resource.name(resource)}/#{table}/destroy/#{
|
||||
action_name
|
||||
}#{encode_primary_key(record)}"
|
||||
)
|
||||
end
|
||||
|
||||
def ash_action_path(prefix, api, resource, action_type, action_name, nil) do
|
||||
prefix(
|
||||
prefix,
|
||||
"/#{AshAdmin.Api.name(api)}/#{AshAdmin.Resource.name(resource)}/#{action_type}/#{
|
||||
action_name
|
||||
}"
|
||||
)
|
||||
end
|
||||
|
||||
def ash_action_path(prefix, api, resource, action_type, action_name, table) do
|
||||
prefix(
|
||||
prefix,
|
||||
"/#{AshAdmin.Api.name(api)}/#{AshAdmin.Resource.name(resource)}/#{table}/#{action_type}/#{
|
||||
action_name
|
||||
}"
|
||||
)
|
||||
end
|
||||
|
||||
# sobelow_skip ["DOS.StringToAtom"]
|
||||
def ash_show_path(prefix, api, resource, record, nil) do
|
||||
prefix(
|
||||
prefix,
|
||||
"/#{AshAdmin.Api.name(api)}/#{AshAdmin.Resource.name(resource)}/show/#{
|
||||
encode_primary_key(record)
|
||||
}"
|
||||
)
|
||||
end
|
||||
|
||||
def ash_show_path(prefix, api, resource, record, table) do
|
||||
prefix(
|
||||
prefix,
|
||||
"/#{AshAdmin.Api.name(api)}/#{AshAdmin.Resource.name(resource)}/#{table}/show/#{
|
||||
encode_primary_key(record)
|
||||
}"
|
||||
)
|
||||
end
|
||||
|
||||
def encode_primary_key(record) do
|
||||
pkey = Ash.Resource.Info.primary_key(record.__struct__)
|
||||
|
||||
|
|
|
@ -21,16 +21,11 @@ defmodule AshAdmin.PageLive do
|
|||
def mount(
|
||||
_params,
|
||||
%{
|
||||
"prefix" => prefix,
|
||||
"api" => api,
|
||||
"apis" => apis,
|
||||
"tab" => tab,
|
||||
"action_type" => action_type,
|
||||
"action_name" => action_name,
|
||||
"resource" => resource
|
||||
"prefix" => prefix
|
||||
} = session,
|
||||
socket
|
||||
) do
|
||||
otp_app = socket.endpoint.config(:otp_app)
|
||||
socket = assign(socket, :prefix, prefix)
|
||||
|
||||
actor_paused =
|
||||
|
@ -40,79 +35,66 @@ defmodule AshAdmin.PageLive do
|
|||
AshAdmin.ActorPlug.session_bool(session["actor_paused"])
|
||||
end
|
||||
|
||||
action =
|
||||
if action_type && action_name && resource do
|
||||
Ash.Resource.Info.action(resource, action_name, action_type)
|
||||
end
|
||||
|
||||
tables =
|
||||
if resource do
|
||||
AshAdmin.Resource.polymorphic_tables(resource, apis)
|
||||
end
|
||||
apis = apis(otp_app)
|
||||
|
||||
{:ok,
|
||||
socket
|
||||
|> Surface.init()
|
||||
|> assign(:prefix, prefix)
|
||||
|> assign(:api, api)
|
||||
|> assign(:apis, apis)
|
||||
|> assign(:resource, resource)
|
||||
|> assign(:action, action)
|
||||
|> assign(:primary_key, nil)
|
||||
|> assign(:record, nil)
|
||||
|> assign(:tab, tab)
|
||||
|> assign(:actor_resources, actor_resources(apis))
|
||||
|> assign(:apis, apis)
|
||||
|> assign(:tenant, session["tenant"])
|
||||
|> assign(:actor, AshAdmin.ActorPlug.actor_from_session(session))
|
||||
|> assign(:actor_api, AshAdmin.ActorPlug.actor_api_from_session(session))
|
||||
|> assign(:actor, AshAdmin.ActorPlug.actor_from_session(socket.endpoint, session))
|
||||
|> assign(:actor_api, AshAdmin.ActorPlug.actor_api_from_session(socket.endpoint, session))
|
||||
|> assign(:actor_resources, actor_resources(apis))
|
||||
|> assign(
|
||||
:authorizing,
|
||||
AshAdmin.ActorPlug.session_bool(session["actor_authorizing"]) || false
|
||||
)
|
||||
|> assign(:actor_paused, actor_paused)
|
||||
|> assign(:tables, tables)
|
||||
|> assign(:table, session["table"] || Enum.at(tables || [], 0))}
|
||||
|> assign(:actor_paused, actor_paused)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
~F"""
|
||||
<TopNav
|
||||
id="top_nav"
|
||||
apis={{ @apis }}
|
||||
api={{ @api }}
|
||||
actor_api={{ @actor_api }}
|
||||
resource={{ @resource }}
|
||||
tenant={{ @tenant }}
|
||||
actor_resources={{ @actor_resources }}
|
||||
authorizing={{ @authorizing }}
|
||||
actor_paused={{ @actor_paused }}
|
||||
actor={{ @actor }}
|
||||
apis={@apis}
|
||||
api={@api}
|
||||
actor_api={@actor_api}
|
||||
resource={@resource}
|
||||
tenant={@tenant}
|
||||
actor_resources={@actor_resources}
|
||||
authorizing={@authorizing}
|
||||
actor_paused={@actor_paused}
|
||||
actor={@actor}
|
||||
set_tenant="set_tenant"
|
||||
clear_tenant="clear_tenant"
|
||||
toggle_authorizing="toggle_authorizing"
|
||||
toggle_actor_paused="toggle_actor_paused"
|
||||
clear_actor="clear_actor"
|
||||
prefix={{ @prefix }}
|
||||
prefix={@prefix}
|
||||
/>
|
||||
<Resource
|
||||
:if={{ @resource }}
|
||||
id={{ @resource }}
|
||||
resource={{ @resource }}
|
||||
:if={@resource}
|
||||
id={@resource}
|
||||
resource={@resource}
|
||||
set_actor="set_actor"
|
||||
primary_key={{ @primary_key }}
|
||||
record={{ @record }}
|
||||
api={{ @api }}
|
||||
tab={{ @tab }}
|
||||
url_path={{ @url_path }}
|
||||
params={{ @params }}
|
||||
action={{ @action }}
|
||||
tenant={{ @tenant }}
|
||||
actor={{ unless @actor_paused, do: @actor }}
|
||||
authorizing={{ @authorizing }}
|
||||
table={{ @table }}
|
||||
tables={{ @tables }}
|
||||
prefix={{ @prefix }}
|
||||
primary_key={@primary_key}
|
||||
record={@record}
|
||||
api={@api}
|
||||
tab={@tab}
|
||||
action_type={@action_type}
|
||||
url_path={@url_path}
|
||||
params={@params}
|
||||
action={@action}
|
||||
tenant={@tenant}
|
||||
actor={unless @actor_paused, do: @actor}
|
||||
authorizing={@authorizing}
|
||||
table={@table}
|
||||
tables={@tables}
|
||||
prefix={@prefix}
|
||||
/>
|
||||
"""
|
||||
end
|
||||
|
@ -128,13 +110,206 @@ defmodule AshAdmin.PageLive do
|
|||
end)
|
||||
end
|
||||
|
||||
defp apis(otp_app) do
|
||||
otp_app
|
||||
|> Application.get_env(:ash_apis)
|
||||
|> Enum.filter(&AshAdmin.Api.show?/1)
|
||||
end
|
||||
|
||||
# defp find_api(api, otp_app) do
|
||||
# otp_app
|
||||
# |> apis()
|
||||
# |> Enum.find(fn api ->
|
||||
# AshAdmin.Api.name(api) == api
|
||||
# end)
|
||||
# end
|
||||
|
||||
# defp find_resource(api, resource, otp_app) do
|
||||
# case find_api(api, otp_app) do
|
||||
# nil ->
|
||||
# nil
|
||||
|
||||
# api ->
|
||||
# api
|
||||
# |> Ash.Api.resources()
|
||||
# |> Enum.find(fn resource ->
|
||||
# AshAdmin.Resource.name(resource) == resource
|
||||
# end)
|
||||
# end
|
||||
# end
|
||||
|
||||
# defp match_result(%{path: path}, otp_app) when path == "/" || path == "" do
|
||||
# {:ok, [api: Enum.at(apis(otp_app), 0)]}
|
||||
# end
|
||||
|
||||
# defp match_result(url, otp_app) do
|
||||
# url.path
|
||||
# |> Enum.split("/")
|
||||
# |> case do
|
||||
# [api] ->
|
||||
# case find_api(api, otp_app) do
|
||||
# nil ->
|
||||
# :error
|
||||
|
||||
# api ->
|
||||
# {:ok, api: api, tab: "info"}
|
||||
# end
|
||||
|
||||
# [api, resource] ->
|
||||
# case find_resource(api, resource, otp_app) do
|
||||
# {api, resource} ->
|
||||
# {:ok, api: api, resource: resource, tab: "info"}
|
||||
|
||||
# nil ->
|
||||
# :error
|
||||
# end
|
||||
|
||||
# [api, resource, table_or_action_type] ->
|
||||
# case find_table_or_action_type(api, resource, table_or_action_type, otp_app) do
|
||||
# {:table, api, resource} ->
|
||||
# {:ok, api: api, resource: resource, table: table_or_action_type}
|
||||
|
||||
# {:action, api, resource, action_type} ->
|
||||
# action = Ash.Resource.Info.primary_action!(resource, action_type)
|
||||
# {:ok, api: api, resource: resource, action_type: action_type, action: action.name}
|
||||
# end
|
||||
|
||||
# [api, resource, table_or_action_type, action_type_or_action_name, otp_app] ->
|
||||
# case find_action_type_or_action_name(
|
||||
# api,
|
||||
# resource,
|
||||
# table_or_action_type,
|
||||
# action_type_or_action_name
|
||||
# ) do
|
||||
# {:action_name, api, resource, action_type, action_name} ->
|
||||
# {:ok, api: api, resource: resource, action_type: action_type, action: action_name}
|
||||
|
||||
# {:action_type, api, resource, action_type} ->
|
||||
# action = Ash.Resource.Info.primary_action!(resource, action_type)
|
||||
|
||||
# {:ok,
|
||||
# api: api,
|
||||
# resource: resource,
|
||||
# action_type: action_type,
|
||||
# action: action.name,
|
||||
# table: table_or_action_type}
|
||||
|
||||
# {:action_type_with_pkey, api, resource, action_type, primary_key} ->
|
||||
# action = Ash.Resource.Info.primary_action!(resource, action_type)
|
||||
# {:ok, api: api, resource: resource, action_type: action_type, action: action.name}
|
||||
# end
|
||||
|
||||
# [api, resource, table_or_action_type, action_type_or_action_name, primary_key] ->
|
||||
# case find_action_type_or_action_name_with_primary_key(
|
||||
# api,
|
||||
# resource,
|
||||
# table_or_action_type,
|
||||
# action_type_or_action_name,
|
||||
# otp_app
|
||||
# ) do
|
||||
# {:table, api, resource, action_type, primary_key} ->
|
||||
# action = Ash.Resource.Info.primary_action!(resource, action_type)
|
||||
|
||||
# {:ok,
|
||||
# api: api,
|
||||
# resource: resource,
|
||||
# action_type: action_type,
|
||||
# primary_key: primary_key,
|
||||
# table: table_or_action_type,
|
||||
# action: action.name}
|
||||
|
||||
# {:action_name, api, resource, action_type, action_name, primary_key} ->
|
||||
# {:ok,
|
||||
# api: api,
|
||||
# resource: resource,
|
||||
# action_type: action_type,
|
||||
# action: action_name,
|
||||
# primary_key: primary_key}
|
||||
# end
|
||||
# [api, resource, table, action_type, action_name, primary_key] ->
|
||||
# action_type = case action_type do
|
||||
# "update" -> :update
|
||||
# "destroy" -> :destroy
|
||||
# end
|
||||
# case find_resource(api, resource, otp_app) do
|
||||
|
||||
# end
|
||||
|
||||
# end
|
||||
# end
|
||||
|
||||
defp assign_api(socket, api) do
|
||||
api =
|
||||
Enum.find(socket.assigns.apis, fn shown_api ->
|
||||
AshAdmin.Api.name(shown_api) == api
|
||||
end) || Enum.at(socket.assigns.apis, 0)
|
||||
|
||||
assign(socket, :api, api)
|
||||
end
|
||||
|
||||
defp assign_resource(socket, resource) do
|
||||
resources = Ash.Api.resources(socket.assigns.api)
|
||||
|
||||
resource =
|
||||
Enum.find(resources, fn api_resource ->
|
||||
AshAdmin.Resource.name(api_resource) == resource
|
||||
end) || Enum.at(resources, 0)
|
||||
|
||||
assign(socket, :resource, resource)
|
||||
end
|
||||
|
||||
defp assign_action(socket, action, action_type) do
|
||||
action_type =
|
||||
case action_type do
|
||||
"read" -> :read
|
||||
"update" -> :update
|
||||
"create" -> :create
|
||||
"destroy" -> :destroy
|
||||
nil -> nil
|
||||
end
|
||||
|
||||
if action_type do
|
||||
action =
|
||||
Enum.find(Ash.Resource.Info.actions(socket.assigns.resource), fn resource_action ->
|
||||
to_string(resource_action.name) == action && resource_action.type == action_type
|
||||
end) || Ash.Resource.Info.primary_action!(socket.assigns.resource, action_type)
|
||||
|
||||
assign(socket, action_type: action_type, action: action)
|
||||
else
|
||||
assign(socket, action: nil, action_type: nil)
|
||||
end
|
||||
end
|
||||
|
||||
defp assign_tables(socket, table) do
|
||||
tables =
|
||||
if socket.assigns.resource do
|
||||
AshAdmin.Resource.polymorphic_tables(socket.assigns.resource, socket.assigns.apis)
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
if table && table != "" do
|
||||
assign(socket, table: table, tables: tables)
|
||||
else
|
||||
assign(socket, table: Enum.at(tables, 0), tables: tables)
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_params(params, url, socket) do
|
||||
url = URI.parse(url)
|
||||
|
||||
socket =
|
||||
if params["primary_key"] do
|
||||
case decode_primary_key(socket.assigns.resource, params["primary_key"]) do
|
||||
socket
|
||||
|> assign_api(params["api"])
|
||||
|> assign_resource(params["resource"])
|
||||
|> assign_action(params["action"], params["action_type"])
|
||||
|> assign_tables(params["table"])
|
||||
|> assign(primary_key: params["primary_key"], tab: params["tab"])
|
||||
|
||||
socket =
|
||||
if socket.assigns[:primary_key] do
|
||||
case decode_primary_key(socket.assigns.resource, socket.assigns[:primary_key]) do
|
||||
{:ok, primary_key} ->
|
||||
actor =
|
||||
if socket.assigns.actor_paused do
|
||||
|
@ -157,9 +332,7 @@ defmodule AshAdmin.PageLive do
|
|||
case record do
|
||||
{:error, error} ->
|
||||
Logger.warn(
|
||||
"Error while loading record on admin dashboard\n: #{
|
||||
Exception.format(:error, error)
|
||||
}"
|
||||
"Error while loading record on admin dashboard\n: #{Exception.format(:error, error)}"
|
||||
)
|
||||
|
||||
{:ok, _} ->
|
||||
|
@ -185,6 +358,10 @@ defmodule AshAdmin.PageLive do
|
|||
socket
|
||||
|> assign(:url_path, url.path)
|
||||
|> assign(:params, params)}
|
||||
|
||||
# :error ->
|
||||
# {:error, "Not Found"}
|
||||
# end
|
||||
end
|
||||
|
||||
defp to_one_relationships(resource) do
|
||||
|
@ -238,15 +415,18 @@ defmodule AshAdmin.PageLive do
|
|||
|> Ash.Query.filter(^pkey_filter)
|
||||
|> api.read_one!(action: action)
|
||||
|
||||
api_name = AshAdmin.Api.name(api)
|
||||
resource_name = AshAdmin.Resource.name(resource)
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> push_event(
|
||||
"set_actor",
|
||||
%{
|
||||
resource: to_string(resource),
|
||||
resource: to_string(resource_name),
|
||||
primary_key: encode_primary_key(actor),
|
||||
action: to_string(action.name),
|
||||
api: to_string(api)
|
||||
api: to_string(api_name)
|
||||
}
|
||||
)
|
||||
|> assign(actor: actor, actor_api: api)}
|
||||
|
|
|
@ -44,8 +44,6 @@ defmodule AshAdmin.Router do
|
|||
Defines an AshAdmin route.
|
||||
It expects the `path` the admin dashboard will be mounted at
|
||||
and a set of options.
|
||||
## Options
|
||||
* `:apis` - The list of Apis to include in the admin dashboard
|
||||
## Examples
|
||||
defmodule MyAppWeb.Router do
|
||||
use Phoenix.Router
|
||||
|
@ -63,260 +61,23 @@ defmodule AshAdmin.Router do
|
|||
end
|
||||
"""
|
||||
defmacro ash_admin(path, opts \\ []) do
|
||||
quote bind_quoted: binding() do
|
||||
scope path, alias: false, as: false do
|
||||
import Phoenix.LiveView.Router, only: [live: 4]
|
||||
|
||||
prefix =
|
||||
if opts[:prefix] do
|
||||
opts[:prefix] <> path
|
||||
else
|
||||
path
|
||||
end
|
||||
|
||||
prefix = String.trim_trailing(prefix, "/")
|
||||
|
||||
apis = opts[:apis]
|
||||
Enum.each(apis, &Code.ensure_compiled/1)
|
||||
api = List.first(opts[:apis])
|
||||
|
||||
resource =
|
||||
api
|
||||
|> Ash.Api.resources()
|
||||
|> List.first()
|
||||
quote bind_quoted: [path: path, opts: opts] do
|
||||
import Phoenix.LiveView.Router
|
||||
live_socket_path = Keyword.get(opts, :live_socket_path, "/live")
|
||||
|
||||
live_session :default,
|
||||
session: {AshAdmin.Router, :__session__, %{"prefix" => path}},
|
||||
root_layout: {AshAdmin.LayoutView, :admin} do
|
||||
live(
|
||||
"/",
|
||||
"#{path}/*route",
|
||||
AshAdmin.PageLive,
|
||||
:page,
|
||||
AshAdmin.Router.__options__(opts, %{
|
||||
"prefix" => prefix,
|
||||
"apis" => apis,
|
||||
"api" => api,
|
||||
"tab" => nil,
|
||||
"resource" => nil,
|
||||
"action_type" => nil,
|
||||
"action_name" => nil
|
||||
})
|
||||
private: %{live_socket_path: live_socket_path}
|
||||
)
|
||||
|
||||
for api <- apis do
|
||||
live(
|
||||
"/#{AshAdmin.Api.name(api)}",
|
||||
AshAdmin.PageLive,
|
||||
:api_page,
|
||||
AshAdmin.Router.__options__(opts, %{
|
||||
"prefix" => prefix,
|
||||
"apis" => apis,
|
||||
"api" => api,
|
||||
"tab" => "info",
|
||||
"resource" => nil,
|
||||
"action_type" => nil,
|
||||
"action_name" => nil
|
||||
})
|
||||
)
|
||||
|
||||
for resource <- Ash.Api.resources(api) do
|
||||
for {table, alias_part, polymorphic_part} <-
|
||||
AshAdmin.Router.polymorphic_parts(resource, apis) do
|
||||
live(
|
||||
"/#{AshAdmin.Api.name(api)}/#{AshAdmin.Resource.name(resource)}#{polymorphic_part}",
|
||||
AshAdmin.PageLive,
|
||||
:resource_page,
|
||||
AshAdmin.Router.__options__(opts, %{
|
||||
"prefix" => prefix,
|
||||
"apis" => apis,
|
||||
"api" => api,
|
||||
"tab" => "info",
|
||||
"resource" => resource,
|
||||
"action_type" => nil,
|
||||
"action_name" => nil,
|
||||
"table" => table
|
||||
})
|
||||
)
|
||||
|
||||
if Enum.any?(Ash.Resource.Info.actions(resource), &(&1.type == :create)) do
|
||||
live(
|
||||
"/#{AshAdmin.Api.name(api)}/#{AshAdmin.Resource.name(resource)}#{
|
||||
polymorphic_part
|
||||
}/create",
|
||||
AshAdmin.PageLive,
|
||||
:resource_page,
|
||||
AshAdmin.Router.__options__(opts, %{
|
||||
"prefix" => prefix,
|
||||
"apis" => apis,
|
||||
"api" => api,
|
||||
"resource" => resource,
|
||||
"tab" => "create",
|
||||
"action_type" => :create,
|
||||
"action_name" => Ash.Resource.Info.primary_action!(resource, :create).name,
|
||||
"table" => table
|
||||
})
|
||||
)
|
||||
end
|
||||
|
||||
for %{type: :create} = action <- Ash.Resource.Info.actions(resource) do
|
||||
live(
|
||||
"/#{AshAdmin.Api.name(api)}/#{AshAdmin.Resource.name(resource)}#{
|
||||
polymorphic_part
|
||||
}/create/#{action.name}",
|
||||
AshAdmin.PageLive,
|
||||
:resource_page,
|
||||
AshAdmin.Router.__options__(opts, %{
|
||||
"prefix" => prefix,
|
||||
"apis" => apis,
|
||||
"api" => api,
|
||||
"resource" => resource,
|
||||
"tab" => "create",
|
||||
"action_type" => :create,
|
||||
"action_name" => action.name,
|
||||
"table" => table
|
||||
})
|
||||
)
|
||||
end
|
||||
|
||||
if Enum.any?(Ash.Resource.Info.actions(resource), &(&1.type == :update)) do
|
||||
live(
|
||||
"/#{AshAdmin.Api.name(api)}/#{AshAdmin.Resource.name(resource)}#{
|
||||
polymorphic_part
|
||||
}/update/:primary_key",
|
||||
AshAdmin.PageLive,
|
||||
:resource_page,
|
||||
AshAdmin.Router.__options__(opts, %{
|
||||
"prefix" => prefix,
|
||||
"apis" => apis,
|
||||
"api" => api,
|
||||
"resource" => resource,
|
||||
"tab" => "update",
|
||||
"action_type" => :update,
|
||||
"action_name" => Ash.Resource.Info.primary_action!(resource, :update).name,
|
||||
"table" => table
|
||||
})
|
||||
)
|
||||
|
||||
for %{type: :update} = action <- Ash.Resource.Info.actions(resource) do
|
||||
live(
|
||||
"/#{AshAdmin.Api.name(api)}/#{AshAdmin.Resource.name(resource)}#{
|
||||
polymorphic_part
|
||||
}/update/#{action.name}/:primary_key",
|
||||
AshAdmin.PageLive,
|
||||
:resource_page,
|
||||
AshAdmin.Router.__options__(opts, %{
|
||||
"prefix" => prefix,
|
||||
"apis" => apis,
|
||||
"api" => api,
|
||||
"resource" => resource,
|
||||
"tab" => "update",
|
||||
"action_type" => :update,
|
||||
"action_name" => action.name,
|
||||
"table" => table
|
||||
})
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
if Enum.any?(Ash.Resource.Info.actions(resource), &(&1.type == :destroy)) do
|
||||
live(
|
||||
"/#{AshAdmin.Api.name(api)}/#{AshAdmin.Resource.name(resource)}#{
|
||||
polymorphic_part
|
||||
}/destroy/:primary_key",
|
||||
AshAdmin.PageLive,
|
||||
:resource_page,
|
||||
AshAdmin.Router.__options__(opts, %{
|
||||
"prefix" => prefix,
|
||||
"apis" => apis,
|
||||
"api" => api,
|
||||
"resource" => resource,
|
||||
"tab" => "destroy",
|
||||
"action_type" => :destroy,
|
||||
"action_name" => Ash.Resource.Info.primary_action!(resource, :destroy).name,
|
||||
"table" => table
|
||||
})
|
||||
)
|
||||
|
||||
for %{type: :destroy} = action <- Ash.Resource.Info.actions(resource) do
|
||||
live(
|
||||
"/#{AshAdmin.Api.name(api)}/#{AshAdmin.Resource.name(resource)}#{
|
||||
polymorphic_part
|
||||
}/destroy/#{action.name}/:primary_key",
|
||||
AshAdmin.PageLive,
|
||||
:resource_page,
|
||||
AshAdmin.Router.__options__(opts, %{
|
||||
"prefix" => prefix,
|
||||
"apis" => apis,
|
||||
"api" => api,
|
||||
"resource" => resource,
|
||||
"tab" => "destroy",
|
||||
"action_type" => :destroy,
|
||||
"action_name" => action.name,
|
||||
"table" => table
|
||||
})
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
show_action = AshAdmin.Resource.show_action(resource)
|
||||
|
||||
if show_action do
|
||||
action =
|
||||
Ash.Resource.Info.action(resource, AshAdmin.Resource.show_action(resource))
|
||||
|
||||
live(
|
||||
"/#{AshAdmin.Api.name(api)}/#{AshAdmin.Resource.name(resource)}#{
|
||||
polymorphic_part
|
||||
}/show/:primary_key",
|
||||
AshAdmin.PageLive,
|
||||
:show_page,
|
||||
AshAdmin.Router.__options__(opts, %{
|
||||
"prefix" => prefix,
|
||||
"apis" => apis,
|
||||
"api" => api,
|
||||
"resource" => resource,
|
||||
"tab" => "read",
|
||||
"action_type" => :read,
|
||||
"action_name" => action.name,
|
||||
"table" => table
|
||||
})
|
||||
)
|
||||
end
|
||||
|
||||
for %{type: :read} = action <- Ash.Resource.Info.actions(resource) do
|
||||
live(
|
||||
"/#{AshAdmin.Api.name(api)}/#{AshAdmin.Resource.name(resource)}#{
|
||||
polymorphic_part
|
||||
}/#{action.type}/#{action.name}",
|
||||
AshAdmin.PageLive,
|
||||
:resource_page,
|
||||
AshAdmin.Router.__options__(opts, %{
|
||||
"prefix" => prefix,
|
||||
"apis" => apis,
|
||||
"api" => api,
|
||||
"resource" => resource,
|
||||
"tab" => "data",
|
||||
"action_type" => :read,
|
||||
"action_name" => action.name,
|
||||
"table" => table
|
||||
})
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def __options__(options, session) do
|
||||
live_socket_path = Keyword.get(options, :live_socket_path, "/live")
|
||||
|
||||
[
|
||||
session: {__MODULE__, :__session__, [session]},
|
||||
private: %{live_socket_path: live_socket_path},
|
||||
layout: {AshAdmin.LayoutView, :admin}
|
||||
]
|
||||
end
|
||||
|
||||
@cookies_to_replicate [
|
||||
"tenant",
|
||||
"actor_resource",
|
||||
|
@ -339,15 +100,4 @@ defmodule AshAdmin.Router do
|
|||
end
|
||||
end)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def polymorphic_parts(resource, apis) do
|
||||
case AshAdmin.Resource.polymorphic_tables(resource, apis) do
|
||||
[] ->
|
||||
[{nil, "", ""}]
|
||||
|
||||
tables ->
|
||||
[{nil, "", ""} | Enum.map(tables, &{&1, &1, "/#{&1}"})]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
10
mix.exs
10
mix.exs
|
@ -88,14 +88,14 @@ defmodule AshAdmin.MixProject do
|
|||
[
|
||||
{:ash, "~> 1.47 and >= 1.47.3"},
|
||||
# {:ash, path: "../ash", override: true},
|
||||
{:ash_phoenix, "~> 0.5 and >= 0.5.7"},
|
||||
{:ash_phoenix, "~> 0.5 and >= 0.5.11"},
|
||||
# {:ash_phoenix, path: "../ash_phoenix"},
|
||||
{:surface, "~> 0.4.1"},
|
||||
{:phoenix_live_view, "~> 0.15.4"},
|
||||
{:phoenix_html, "~> 2.14.1 or ~> 2.15"},
|
||||
{:surface, "~> 0.5.1"},
|
||||
{:phoenix_live_view, "~> 0.16"},
|
||||
{:phoenix_html, "~> 3.0"},
|
||||
{:jason, "~> 1.0"},
|
||||
# Dev dependencies
|
||||
{:surface_formatter, "~> 0.4.1", only: [:dev, :test]},
|
||||
{:surface_formatter, "~> 0.5.0", only: [:dev, :test]},
|
||||
{:plug_cowboy, "~> 2.0", only: [:dev, :test]},
|
||||
{:phoenix_live_reload, "~> 1.2", only: [:dev, :test]},
|
||||
{:ash_postgres, "~> 0.40.9", only: [:dev, :test]},
|
||||
|
|
22
mix.lock
22
mix.lock
|
@ -1,6 +1,6 @@
|
|||
%{
|
||||
"ash": {:hex, :ash, "1.47.3", "6c00875d4c95e859f67073d032069b7ec3019e507d57d8d1ea8c8ecf79d1cec9", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8.0", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.3.5", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.1.5", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:timex, ">= 3.0.0", [hex: :timex, repo: "hexpm", optional: false]}], "hexpm", "0aa1b7ccf699624179a36d0603376bb4782ff032a7fc4a6b6671341990142e07"},
|
||||
"ash_phoenix": {:hex, :ash_phoenix, "0.5.7", "0b11395376b135c9daa30b12b826428ca585003ad54255e05a57c2bb19c0f839", [:mix], [{:ash, "~> 1.46 and >= 1.46.8", [hex: :ash, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.5.6", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.15", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}], "hexpm", "e10e43fba8d467c9f13db7b5541ef0ac935111a688d628dc264655c3419edd5a"},
|
||||
"ash": {:hex, :ash, "1.47.11", "1ce1f77cb0f687a01880b9573102f516be80c90880dcbe4cb53746e5ed765df9", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8.0", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.3.5", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:timex, ">= 3.0.0", [hex: :timex, repo: "hexpm", optional: false]}], "hexpm", "02b73a3fb80fd90c54301c69c5713e4422a538da0b2f76a1d0ab3cdadc6d94a2"},
|
||||
"ash_phoenix": {:hex, :ash_phoenix, "0.5.11", "8c3ce8a5d79803b6e87bf2663e58d10d779fc27b90c00d71c3e8e713dedc3b3b", [:mix], [{:ash, "~> 1.47 and >= 1.47.8", [hex: :ash, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.5.6", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.15", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}], "hexpm", "e7b6a2eac05105e56a34617a54eae7cc40cdfe7c12bb6166296b473be6389236"},
|
||||
"ash_policy_authorizer": {:hex, :ash_policy_authorizer, "0.16.2", "9d8446bd7d79ac2c77b459ca0e79cd4a3873eed57d6853aebf7bf01002d32693", [:mix], [{:ash, "~> 1.46", [hex: :ash, repo: "hexpm", optional: false]}], "hexpm", "508a0c17f906d5f21ef461026957258a3ba90d1ebd2e9ceb480e88f84e613e08"},
|
||||
"ash_postgres": {:hex, :ash_postgres, "0.40.9", "b184e6ddc200271157ff84bfadd5e2a212f363cf0cdcff15fe7d9b8d10f012ff", [:mix], [{:ash, "~> 1.46 and >= 1.46.11", [hex: :ash, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.5", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: false]}], "hexpm", "6b2e62b1e47f0898c5f576ed68c3fe92feb59d69d4dfefcf51e3581afb72fd18"},
|
||||
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"},
|
||||
|
@ -37,28 +37,28 @@
|
|||
"makeup_elixir": {:hex, :makeup_elixir, "0.15.1", "b5888c880d17d1cc3e598f05cdb5b5a91b7b17ac4eaf5f297cb697663a1094dd", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "db68c173234b07ab2a07f645a5acdc117b9f99d69ebf521821d89690ae6c6ec8"},
|
||||
"makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"},
|
||||
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
|
||||
"mime": {:hex, :mime, "1.6.0", "dabde576a497cef4bbdd60aceee8160e02a6c89250d6c0b29e56c0dfb00db3d2", [:mix], [], "hexpm", "31a1a8613f8321143dde1dafc36006a17d28d02bdfecb9e95a880fa7aabd19a7"},
|
||||
"mime": {:hex, :mime, "2.0.1", "0de4c81303fe07806ebc2494d5321ce8fb4df106e34dd5f9d787b637ebadc256", [:mix], [], "hexpm", "7a86b920d2aedce5fb6280ac8261ac1a739ae6c1a1ad38f5eadf910063008942"},
|
||||
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
|
||||
"nimble_options": {:hex, :nimble_options, "0.3.6", "365d03c05d43483d3eacf820671dafce5b49d692667b3bb8cae28447fd2414ef", [:mix], [], "hexpm", "1c1d3536c4aee1be2c8f3c691bf27c62dbd88d9bb3a0b1a011913453932e8c15"},
|
||||
"nimble_parsec": {:hex, :nimble_parsec, "1.1.0", "3a6fca1550363552e54c216debb6a9e95bd8d32348938e13de5eda962c0d7f89", [:mix], [], "hexpm", "08eb32d66b706e913ff748f11694b17981c0b04a33ef470e33e11b3d3ac8f54b"},
|
||||
"parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"},
|
||||
"phoenix": {:hex, :phoenix, "1.5.9", "a6368d36cfd59d917b37c44386e01315bc89f7609a10a45a22f47c007edf2597", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7e4bce20a67c012f1fbb0af90e5da49fa7bf0d34e3a067795703b74aef75427d"},
|
||||
"phoenix_html": {:hex, :phoenix_html, "2.14.3", "51f720d0d543e4e157ff06b65de38e13303d5778a7919bcc696599e5934271b8", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "efd697a7fff35a13eeeb6b43db884705cba353a1a41d127d118fda5f90c8e80f"},
|
||||
"phoenix": {:hex, :phoenix, "1.5.12", "75fddb14c720388eea93d33886166a690416a7ff8633fbd93f364355b6fe1166", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8f0ae6734fcc18bbaa646c161e2febc46fb899eae43f82679b92530983324113"},
|
||||
"phoenix_html": {:hex, :phoenix_html, "3.0.2", "0d71bd7dfa5fad2103142206e25e16accd64f41bcbd0002af3f0da17e530968d", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "d6c6e85d9bef8d52a5a66fcccd15529651f379eaccbf10500343a17f6f814f82"},
|
||||
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.3.0", "f35f61c3f959c9a01b36defaa1f0624edd55b87e236b606664a556d6f72fd2e7", [: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", "02c1007ae393f2b76ec61c1a869b1e617179877984678babde131d716f95b582"},
|
||||
"phoenix_live_view": {:hex, :phoenix_live_view, "0.15.7", "09720b8e5151b3ca8ef739cd7626d4feb987c69ba0b509c9bbdb861d5a365881", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.5.7", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 0.5", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3a756cf662420272d0f1b3b908cce5222163b5a95aa9bab404f9d29aff53276e"},
|
||||
"phoenix_live_view": {:hex, :phoenix_live_view, "0.16.1", "a17652e936718b6b6b52ef64d4b9860bc30c41b9a491e25f2b49a70604efa436", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.5.9 or ~> 1.6.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "94bbc572471ad151b756b38dd10acbf91e0bcc132ad8b78240baa0dcf77cea74"},
|
||||
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"},
|
||||
"picosat_elixir": {:hex, :picosat_elixir, "0.1.5", "23673bd3080a4489401e25b4896aff1f1138d47b2f650eab724aad1506188ebb", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "b30b3c3abd1f4281902d3b5bc9b67e716509092d6243b010c29d8be4a526e8c8"},
|
||||
"plug": {:hex, :plug, "1.12.0", "39dc7f1ef8c46bb1bf6dd8f6a49f526c45b4b92ce553687fd885b559a46d0230", [: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 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5282c76e89efdf43f2e04bd268ca99d738039f9518137f02ff468cee3ba78096"},
|
||||
"picosat_elixir": {:hex, :picosat_elixir, "0.2.1", "407dcb90755167fd9e3311b60565ff32ed0d234010363406c07cdb4175b95bc5", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "68f4bdb2ac3b594209e54625d3d58c9e2e98b90f2ec8e03235f66e88c9eda5fe"},
|
||||
"plug": {:hex, :plug, "1.12.1", "645678c800601d8d9f27ad1aebba1fdb9ce5b2623ddb961a074da0b96c35187d", [: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", "d57e799a777bc20494b784966dc5fbda91eb4a09f571f76545b72a634ce0d30b"},
|
||||
"plug_cowboy": {:hex, :plug_cowboy, "2.5.1", "7cc96ff645158a94cf3ec9744464414f02287f832d6847079adfe0b58761cbd0", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "107d0a5865fa92bcb48e631cc0729ae9ccfa0a9f9a1bd8f01acb513abf1c2d64"},
|
||||
"plug_crypto": {:hex, :plug_crypto, "1.2.2", "05654514ac717ff3a1843204b424477d9e60c143406aa94daf2274fdd280794d", [:mix], [], "hexpm", "87631c7ad914a5a445f0a3809f99b079113ae4ed4b867348dd9eec288cecb6db"},
|
||||
"postgrex": {:hex, :postgrex, "0.15.9", "46f8fe6f25711aeb861c4d0ae09780facfdf3adbd2fb5594ead61504dd489bda", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "610719103e4cb2223d4ab78f9f0f3e720320eeca6011415ab4137ddef730adee"},
|
||||
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
|
||||
"sobelow": {:hex, :sobelow, "0.11.1", "23438964486f8112b41e743bbfd402da3e5b296fdc9eacab29914b79c48916dd", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "9897363a7eff96f4809304a90aad819e2ad5e5d24db547af502885146746a53c"},
|
||||
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
|
||||
"surface": {:hex, :surface, "0.4.1", "baecbf1f0e008ad19ef5a971e9db4ca1d69e8d72bb6cdae7a09f2bdc3bb4975f", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.15", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}], "hexpm", "aa60074ccc3ad05db2b866275da159bf13e3c7f7810ebc0f8a098b44e10e58ad"},
|
||||
"surface_formatter": {:hex, :surface_formatter, "0.4.1", "1f98b0751e010e94fca722fe6a8fecd03315e9e59d4f7ec3e5609e05d2051e9f", [:mix], [{:surface, "~> 0.4.0", [hex: :surface, repo: "hexpm", optional: false]}], "hexpm", "3dc3c4cabb06315a8f502ee4f240a33a2770830efef1f4b082d29e1e2a307e20"},
|
||||
"surface": {:hex, :surface, "0.5.1", "2aa593d8ba5dde584e288f697ec8318352f2ff2037ad6d195788c702f104bff2", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.15", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}], "hexpm", "a853bfe3479e1f711c84d69d988f85612bd2847dc18ada80be951af40dbe136c"},
|
||||
"surface_formatter": {:hex, :surface_formatter, "0.5.4", "ce3332e2516615795d10bcf8fb10c765128ff9ccb0fa0e21aa4f384a58498d52", [:mix], [{:surface, "~> 0.5.0", [hex: :surface, repo: "hexpm", optional: false]}], "hexpm", "ea1a5666e4abf1a6c61048fb9a64040ce59865cf33002ca08d2011b6d700feb4"},
|
||||
"telemetry": {:hex, :telemetry, "0.4.3", "a06428a514bdbc63293cd9a6263aad00ddeb66f608163bdec7c8995784080818", [:rebar3], [], "hexpm", "eb72b8365ffda5bed68a620d1da88525e326cb82a75ee61354fc24b844768041"},
|
||||
"timex": {:hex, :timex, "3.7.5", "3eca56e23bfa4e0848f0b0a29a92fa20af251a975116c6d504966e8a90516dfd", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "a15608dca680f2ef663d71c95842c67f0af08a0f3b1d00e17bbd22872e2874e4"},
|
||||
"timex": {:hex, :timex, "3.7.6", "502d2347ec550e77fdf419bc12d15bdccd31266bb7d925b30bf478268098282f", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "a296327f79cb1ec795b896698c56e662ed7210cc9eb31f0ab365eb3a62e2c589"},
|
||||
"typable": {:hex, :typable, "0.3.0", "0431e121d124cd26f312123e313d2689b9a5322b15add65d424c07779eaa3ca1", [:mix], [], "hexpm", "880a0797752da1a4c508ac48f94711e04c86156f498065a83d160eef945858f8"},
|
||||
"tzdata": {:hex, :tzdata, "1.1.0", "72f5babaa9390d0f131465c8702fa76da0919e37ba32baa90d93c583301a8359", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "18f453739b48d3dc5bcf0e8906d2dc112bb40baafe2c707596d89f3c8dd14034"},
|
||||
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
|
||||
|
|
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue