Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ Create a screen record:

```elixir
alias AshUI.DSL.Builder
alias AshUI.Domain
alias AshUI.Data, as: Domain
alias AshUI.Resources.Screen

{:ok, _screen} =
Expand Down
2 changes: 1 addition & 1 deletion examples/basic_dashboard/lib/basic_dashboard.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ defmodule BasicDashboard do
"""

alias AshUI.DSL.Builder
alias AshUI.Domain
alias AshUI.Data, as: Domain
alias AshUI.Resources.Binding
alias AshUI.Resources.Element
alias AshUI.Resources.Screen
Expand Down
4 changes: 2 additions & 2 deletions guides/user/UG-0001-getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ For a simple screen, the lowest-friction path is to create a `Screen` record wit

```elixir
alias AshUI.DSL.Builder
alias AshUI.Domain
alias AshUI.Data, as: Domain
alias AshUI.Resources.Screen

dashboard_dsl =
Expand Down Expand Up @@ -163,7 +163,7 @@ alias AshUI.Rendering.LiveUIAdapter
Bindings are separate records. They connect a UI target such as `"value"` or `"submit"` to a source map that identifies a resource field or action.

```elixir
alias AshUI.Domain
alias AshUI.Data, as: Domain
alias AshUI.Resources.Binding
alias AshUI.Resources.Element

Expand Down
2 changes: 1 addition & 1 deletion guides/user/UG-0002-resources.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ Important fields:
Create a screen:

```elixir
alias AshUI.Domain
alias AshUI.Data, as: Domain
alias AshUI.Resources.Screen

{:ok, screen} =
Expand Down
6 changes: 3 additions & 3 deletions guides/user/UG-0003-data-binding.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ Use `:value` when a field should be read into UI state and potentially written b

```elixir
{:ok, _binding} =
AshUI.Domain.create(AshUI.Resources.Binding,
AshUI.Data.create(AshUI.Resources.Binding,
attrs: %{
screen_id: screen.id,
element_id: name_input.id,
Expand Down Expand Up @@ -90,7 +90,7 @@ Use `:list` when the element expects a collection.

```elixir
{:ok, _binding} =
AshUI.Domain.create(AshUI.Resources.Binding,
AshUI.Data.create(AshUI.Resources.Binding,
attrs: %{
screen_id: screen.id,
element_id: audit_list.id,
Expand All @@ -109,7 +109,7 @@ Use `:action` when the UI should trigger an Ash-side operation.

```elixir
{:ok, _binding} =
AshUI.Domain.create(AshUI.Resources.Binding,
AshUI.Data.create(AshUI.Resources.Binding,
attrs: %{
screen_id: screen.id,
element_id: save_button.id,
Expand Down
2 changes: 1 addition & 1 deletion guides/user/UG-0005-migration-v0-to-v1.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ If you previously modeled screens as custom resources in your app, migrate the u

```elixir
alias AshUI.DSL.Builder
alias AshUI.Domain
alias AshUI.Data, as: Domain
alias AshUI.Resources.Screen

{:ok, _screen} =
Expand Down
30 changes: 3 additions & 27 deletions lib/ash_ui/authorization/binding_policy.ex
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ defmodule AshUI.Authorization.BindingPolicy do
@doc """
Check if binding source resource is accessible.
"""
def source_accessible?(user, binding) do
def source_accessible?(_user, binding) do
source = normalize_source(binding)

cond do
Expand All @@ -92,38 +92,24 @@ defmodule AshUI.Authorization.BindingPolicy do
# Check resource-level access
not Policies.can_read_source(binding) -> false

# Check field-level access
not field_accessible?(user, binding) -> false

# Default allow
true -> true
end
end

# Private functions

defp can_access_binding?(user, binding) do
# In production, would check parent screen access
Policies.user_active(user)
end

defp screen_owned?(user, binding) do
# In production, would check parent screen ownership
true
end

defp has_data_access?(binding, user) do
defp has_data_access?(binding, _user) do
source = normalize_source(binding)

cond do
map_size(source) == 0 -> true
not Policies.can_read_source(binding) -> false
not Policies.can_access_field(binding.source, Map.get(source, "field")) -> false
true -> true
end
end

defp has_write_access?(binding, user) do
defp has_write_access?(binding, _user) do
source = normalize_source(binding)

cond do
Expand All @@ -133,16 +119,6 @@ defmodule AshUI.Authorization.BindingPolicy do
end
end

defp field_accessible?(user, binding) do
source = normalize_source(binding)
field = Map.get(source, "field")

case field do
nil -> true
_ -> Policies.can_access_field(binding, field)
end
end

defp normalize_source(binding) do
case Map.get(binding, :source) do
source when is_map(source) -> source
Expand Down
26 changes: 0 additions & 26 deletions lib/ash_ui/authorization/element_policy.ex
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,6 @@ defmodule AshUI.Authorization.ElementPolicy do
# Check element visibility conditions
not meets_visibility_condition?(element, user) -> false

# Check parent screen access
not screen_accessible?(user, element) -> false

# Default visible
true -> true
end
Expand All @@ -60,36 +57,13 @@ defmodule AshUI.Authorization.ElementPolicy do
# Check if element is explicitly read-only
Map.get(element, :read_only, false) -> false

# Must own parent screen
not screen_owned?(user, element) -> false

# Default editable
true -> true
end
end

# Private functions

defp screen_accessible?(user, element) do
# In production, would check if user can access parent screen
true
end

defp screen_owned?(user, element) do
# In production, would check if user owns parent screen
true
end

defp element_visible?(element) do
# Check if element has visibility condition
case Map.get(element, :visible_when) do
nil -> true
condition when is_function(condition, 0) -> condition.()
condition when is_boolean(condition) -> condition
_ -> true
end
end

defp meets_visibility_condition?(element, user) do
case Map.get(element, :visible_when) do
nil -> true
Expand Down
4 changes: 2 additions & 2 deletions lib/ash_ui/authorization/error.ex
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ defmodule AshUI.AuthorizationError do
@doc """
Returns the human-readable message for the authorization error.
"""
def message(%__MODULE__{reason: reason} = error) do
def message(%__MODULE__{} = error) do
format_message(error)
end

Expand Down Expand Up @@ -116,7 +116,7 @@ defmodule AshUI.AuthorizationError do
AuthorizationError.format_message(error)
"""
@spec format_message(t()) :: String.t()
def format_message(%__MODULE__{reason: :unauthenticated} = error) do
def format_message(%__MODULE__{reason: :unauthenticated}) do
"You must be logged in to access this resource"
end

Expand Down
5 changes: 3 additions & 2 deletions lib/ash_ui/authorization/policies.ex
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ defmodule AshUI.Authorization.Policies do
Application.get_env(:ash_ui, :env, :dev)
end

defp can_access_resource?(nil, _action), do: false
defp can_access_resource?(_resource, _action), do: true
defp can_access_resource?(nil, _action), do: true
defp can_access_resource?(resource, _action) when is_binary(resource), do: true
defp can_access_resource?(_resource, _action), do: false
end
2 changes: 1 addition & 1 deletion lib/ash_ui/authorization/policy_dsl.ex
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ defmodule AshUI.Authorization.PolicyDSL do
end
end

def visible_if(user, condition) when is_boolean(condition) do
def visible_if(_user, condition) when is_boolean(condition) do
condition
end

Expand Down
9 changes: 5 additions & 4 deletions lib/ash_ui/authorization/runtime.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ defmodule AshUI.Authorization.Runtime do

alias AshUI.Authorization.Policies
alias AshUI.Authorization.ScreenPolicy
alias AshUI.Authorization.ElementPolicy
alias AshUI.Authorization.BindingPolicy
alias AshUI.Telemetry

Expand Down Expand Up @@ -142,6 +141,7 @@ defmodule AshUI.Authorization.Runtime do

with :ok <- emit_auth_telemetry(:read_attempt, context),
:ok <- check_user_present(user),
:ok <- check_user_active(user),
:ok <- check_data_source_accessible(user, binding),
:ok <- check_policy(user, binding, :read) do
emit_auth_telemetry(:read_success, context)
Expand Down Expand Up @@ -175,6 +175,7 @@ defmodule AshUI.Authorization.Runtime do

with :ok <- emit_auth_telemetry(:write_attempt, context),
:ok <- check_user_present(user),
:ok <- check_user_active(user),
:ok <- check_data_source_writable(user, binding),
:ok <- check_policy(user, binding, :update) do
emit_auth_telemetry(:write_success, context)
Expand Down Expand Up @@ -290,7 +291,7 @@ defmodule AshUI.Authorization.Runtime do
Runtime.invalidate_resource_cache(screen)
"""
@spec invalidate_resource_cache(term()) :: :ok
def invalidate_resource_cache(resource) do
def invalidate_resource_cache(_resource) do
# In production, would selectively invalidate by resource
:ets.delete_all_objects(:ash_ui_auth_cache)
:ok
Expand Down Expand Up @@ -390,7 +391,7 @@ defmodule AshUI.Authorization.Runtime do
end
end

defp check_action_allowed(user, action, params) do
defp check_action_allowed(user, _action, _params) do
# Check if user role allows this action
if Policies.user_role(user, :admin) do
:ok
Expand Down Expand Up @@ -426,7 +427,7 @@ defmodule AshUI.Authorization.Runtime do
end
end

defp check_policy(user, resource, action) do
defp check_policy(_user, _resource, _action) do
# In production, would use Ash.Policy.Authorizer
:ok
end
Expand Down
4 changes: 2 additions & 2 deletions lib/ash_ui/binding/actions.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ defmodule AshUI.Binding.Actions do
end
end

defp resolve_source(source, context) do
defp resolve_source(source, _context) do
# Parse source path like "MyApp.Accounts.User.name"
# and resolve to actual value from context
parts = String.split(source, ".")
_parts = String.split(source, ".")
# TODO: Implement actual source resolution
{:ok, "Resolved Value"}
end
Expand Down
36 changes: 18 additions & 18 deletions lib/ash_ui/compiler/extensions.ex
Original file line number Diff line number Diff line change
Expand Up @@ -303,13 +303,13 @@ defmodule AshUI.Compiler.Extensions do

defp store_widget(type, definition) do
ensure_tables()
:ets.insert(:ash_ui_widgets, {type, definition})
:ets.insert(:ash_ui_widgets, {type, Map.put_new(definition, :type, type)})
:ok
end

defp store_layout(type, definition) do
ensure_tables()
:ets.insert(:ash_ui_layouts, {type, definition})
:ets.insert(:ash_ui_layouts, {type, Map.put_new(definition, :type, type)})
:ok
end

Expand Down Expand Up @@ -341,7 +341,7 @@ defmodule AshUI.Compiler.Extensions do
@spec available_widget_types() :: [String.t()]
def available_widget_types do
built_in = ["text", "button", "input", "checkbox", "select", "image", "spacer"]
custom = Enum.map(registered_widgets(), fn x -> x.type end)
custom = Enum.map(registered_widgets(), &Map.get(&1, :type))

built_in ++ custom
end
Expand All @@ -356,7 +356,7 @@ defmodule AshUI.Compiler.Extensions do
@spec available_layout_types() :: [String.t()]
def available_layout_types do
built_in = ["row", "column", "grid", "stack", "fragment", "container"]
custom = Enum.map(registered_layouts(), fn x -> x.type end)
custom = Enum.map(registered_layouts(), &Map.get(&1, :type))

built_in ++ custom
end
Expand Down Expand Up @@ -389,8 +389,8 @@ defmodule AshUI.Compiler.Extensions do
end
end

defp validate_widget_props_spec(definition, errors) do
props = definition.props || []
defp validate_widget_props_spec(errors, definition) do
props = Map.get(definition, :props, [])

Enum.reduce(props, errors, fn prop_spec, acc ->
case validate_prop_spec(prop_spec) do
Expand All @@ -411,16 +411,16 @@ defmodule AshUI.Compiler.Extensions do
end
end

defp validate_widget_module(definition, errors) do
if Code.ensure_loaded?(definition.module) do
defp validate_widget_module(errors, definition) do
if is_atom(Map.get(definition, :module)) do
errors
else
["Module #{inspect(definition.module)} not loaded" | errors]
["Widget must declare a module" | errors]
end
end

defp validate_widget_compile(definition, errors) do
if is_function(definition.compile) do
defp validate_widget_compile(errors, definition) do
if is_function(Map.get(definition, :compile)) do
errors
else
["Widget must have a compile function" | errors]
Expand Down Expand Up @@ -448,8 +448,8 @@ defmodule AshUI.Compiler.Extensions do
end
end

defp validate_layout_props_spec(definition, errors) do
props = definition.props || []
defp validate_layout_props_spec(errors, definition) do
props = Map.get(definition, :props, [])

Enum.reduce(props, errors, fn prop_spec, acc ->
case validate_prop_spec(prop_spec) do
Expand All @@ -459,16 +459,16 @@ defmodule AshUI.Compiler.Extensions do
end)
end

defp validate_layout_module(definition, errors) do
if Code.ensure_loaded?(definition.module) do
defp validate_layout_module(errors, definition) do
if is_atom(Map.get(definition, :module)) do
errors
else
["Module #{inspect(definition.module)} not loaded" | errors]
["Layout must declare a module" | errors]
end
end

defp validate_layout_compile(definition, errors) do
if is_function(definition.compile) do
defp validate_layout_compile(errors, definition) do
if is_function(Map.get(definition, :compile)) do
errors
else
["Layout must have a compile function" | errors]
Expand Down
Loading
Loading