diff --git a/elixir/apps/domain/lib/domain/actors.ex b/elixir/apps/domain/lib/domain/actors.ex index 37d28e45d..4e2f2e37f 100644 --- a/elixir/apps/domain/lib/domain/actors.ex +++ b/elixir/apps/domain/lib/domain/actors.ex @@ -129,12 +129,18 @@ defmodule Domain.Actors do end end - def fetch_actor_by_id(id, %Auth.Subject{} = subject) do + def fetch_actor_by_id(id, %Auth.Subject{} = subject, opts \\ []) do + {preload, _opts} = Keyword.pop(opts, :preload, []) + with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_actors_permission()), true <- Validator.valid_uuid?(id) do Actor.Query.by_id(id) |> Authorizer.for_subject(subject) |> Repo.fetch() + |> case do + {:ok, actor} -> {:ok, Repo.preload(actor, preload)} + {:error, reason} -> {:error, reason} + end else false -> {:error, :not_found} other -> other @@ -156,14 +162,17 @@ defmodule Domain.Actors do end def list_actors(%Auth.Subject{} = subject, opts \\ []) do - with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_actors_permission()) do - {hydrate, _opts} = Keyword.pop(opts, :hydrate, []) + {preload, _opts} = Keyword.pop(opts, :preload, []) + {hydrate, _opts} = Keyword.pop(opts, :hydrate, []) - Actor.Query.all() - |> Authorizer.for_subject(subject) - # TODO: add filters - |> hydrate_fields(hydrate) - |> Repo.list() + with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_actors_permission()) do + {:ok, actors} = + Actor.Query.all() + |> Authorizer.for_subject(subject) + |> hydrate_fields(hydrate) + |> Repo.list() + + {:ok, Repo.preload(actors, preload)} end end diff --git a/elixir/apps/domain/lib/domain/devices.ex b/elixir/apps/domain/lib/domain/devices.ex index 2dd121ee0..1cba291a1 100644 --- a/elixir/apps/domain/lib/domain/devices.ex +++ b/elixir/apps/domain/lib/domain/devices.ex @@ -26,7 +26,9 @@ defmodule Domain.Devices do |> Repo.aggregate(:count) end - def fetch_device_by_id(id, %Auth.Subject{} = subject) do + def fetch_device_by_id(id, %Auth.Subject{} = subject, opts \\ []) do + {preload, _opts} = Keyword.pop(opts, :preload, []) + required_permissions = {:one_of, [ @@ -39,6 +41,10 @@ defmodule Domain.Devices do Device.Query.by_id(id) |> Authorizer.for_subject(subject) |> Repo.fetch() + |> case do + {:ok, device} -> {:ok, Repo.preload(device, preload)} + {:error, reason} -> {:error, reason} + end else false -> {:error, :not_found} other -> other @@ -53,7 +59,9 @@ defmodule Domain.Devices do |> Repo.preload(preload) end - def list_devices(%Auth.Subject{} = subject) do + def list_devices(%Auth.Subject{} = subject, opts \\ []) do + {preload, _opts} = Keyword.pop(opts, :preload, []) + required_permissions = {:one_of, [ @@ -62,9 +70,12 @@ defmodule Domain.Devices do ]} with :ok <- Auth.ensure_has_permissions(subject, required_permissions) do - Device.Query.all() - |> Authorizer.for_subject(subject) - |> Repo.list() + {:ok, devices} = + Device.Query.all() + |> Authorizer.for_subject(subject) + |> Repo.list() + + {:ok, Repo.preload(devices, preload)} end end diff --git a/elixir/apps/domain/priv/repo/seeds.exs b/elixir/apps/domain/priv/repo/seeds.exs index 8f3e2422c..076240589 100644 --- a/elixir/apps/domain/priv/repo/seeds.exs +++ b/elixir/apps/domain/priv/repo/seeds.exs @@ -146,6 +146,31 @@ end IO.puts("") +user_iphone = + Domain.Devices.upsert_device( + %{ + name: "FZ User iPhone", + external_id: Ecto.UUID.generate(), + public_key: :crypto.strong_rand_bytes(32) |> Base.encode64(), + last_seen_user_agent: "iOS/12.7 (iPhone) connlib/0.7.412" + }, + unprivileged_subject + ) + +admin_iphone = + Domain.Devices.upsert_device( + %{ + name: "FZ Admin iPhone", + external_id: Ecto.UUID.generate(), + public_key: :crypto.strong_rand_bytes(32) |> Base.encode64(), + last_seen_user_agent: "iOS/12.7 (iPhone) connlib/0.7.412" + }, + admin_subject + ) + +IO.puts("Devices created") +IO.puts("") + relay_group = account |> Relays.Group.Changeset.create_changeset( diff --git a/elixir/apps/domain/test/domain/actors_test.exs b/elixir/apps/domain/test/domain/actors_test.exs index 086c26cb2..79c667667 100644 --- a/elixir/apps/domain/test/domain/actors_test.exs +++ b/elixir/apps/domain/test/domain/actors_test.exs @@ -624,6 +624,17 @@ defmodule Domain.ActorsTest do {:unauthorized, [missing_permissions: [Actors.Authorizer.manage_actors_permission()]]}} end + + test "associations are preloaded when opts given" do + account = AccountsFixtures.create_account() + actor = ActorsFixtures.create_actor(type: :account_admin_user, account: account) + identity = AuthFixtures.create_identity(account: account, actor: actor) + subject = AuthFixtures.create_subject(identity) + + {:ok, actor} = fetch_actor_by_id(actor.id, subject, preload: :identities) + + assert Ecto.assoc_loaded?(actor.identities) == true + end end describe "fetch_actor_by_id/1" do @@ -704,6 +715,22 @@ defmodule Domain.ActorsTest do {:unauthorized, [missing_permissions: [Actors.Authorizer.manage_actors_permission()]]}} end + + test "associations are preloaded when opts given" do + account = AccountsFixtures.create_account() + + actor1 = ActorsFixtures.create_actor(type: :account_admin_user, account: account) + identity1 = AuthFixtures.create_identity(account: account, actor: actor1) + subject = AuthFixtures.create_subject(identity1) + + actor2 = ActorsFixtures.create_actor(type: :account_user, account: account) + AuthFixtures.create_identity(account: account, actor: actor2) + + {:ok, actors} = list_actors(subject, preload: :identities) + assert length(actors) == 2 + + assert Enum.all?(actors, fn a -> Ecto.assoc_loaded?(a.identities) end) == true + end end describe "create_actor/4" do diff --git a/elixir/apps/web/lib/web/components/layouts/app.html.heex b/elixir/apps/web/lib/web/components/layouts/app.html.heex index 1207b83bc..f42e4c899 100644 --- a/elixir/apps/web/lib/web/components/layouts/app.html.heex +++ b/elixir/apps/web/lib/web/components/layouts/app.html.heex @@ -101,13 +101,15 @@ Dashboard - <.sidebar_item_group id="organization"> - <:name>Organization - - <:item navigate={~p"/#{@account}/actors"}>Users - <:item navigate={~p"/#{@account}/groups"}>Groups - <:item navigate={~p"/#{@account}/devices"}>Devices - + <.sidebar_item navigate={~p"/#{@account}/actors"} icon="hero-user-circle-solid"> + Actors + + <.sidebar_item navigate={~p"/#{@account}/groups"} icon="hero-user-group-solid"> + Groups + + <.sidebar_item navigate={~p"/#{@account}/devices"} icon="hero-device-phone-mobile-solid"> + Devices + <.sidebar_item navigate={~p"/#{@account}/gateways"} icon="hero-arrow-left-on-rectangle-solid"> Gateways @@ -121,7 +123,7 @@ Policies - <.sidebar_item_group id="settings"> + <.sidebar_item_group id="settings" icon="hero-cog-solid"> <:name>Settings <:item navigate={~p"/#{@account}/settings/account"}>Account diff --git a/elixir/apps/web/lib/web/components/navigation_components.ex b/elixir/apps/web/lib/web/components/navigation_components.ex index bd3de26ea..094bfb1ae 100644 --- a/elixir/apps/web/lib/web/components/navigation_components.ex +++ b/elixir/apps/web/lib/web/components/navigation_components.ex @@ -55,7 +55,7 @@ defmodule Web.NavigationComponents do end attr :id, :string, required: true, doc: "ID of the nav group container" - # attr :icon, :string, required: true + attr :icon, :string, required: true # attr :navigate, :string, required: true slot :name, required: true @@ -77,7 +77,7 @@ defmodule Web.NavigationComponents do aria-controls={"dropdown-#{@id}"} data-collapse-toggle={"dropdown-#{@id}"} > - <.icon name="hero-user-group-solid" class={~w[ + <.icon name={@icon} class={~w[ w-6 h-6 text-gray-500 transition duration-75 group-hover:text-gray-900 diff --git a/elixir/apps/web/lib/web/components/table_components.ex b/elixir/apps/web/lib/web/components/table_components.ex index d88f5dcf6..1ccfd7048 100644 --- a/elixir/apps/web/lib/web/components/table_components.ex +++ b/elixir/apps/web/lib/web/components/table_components.ex @@ -223,9 +223,14 @@ defmodule Web.TableComponents do """ + attr :class, :string, default: nil + attr :rest, :global + + slot :inner_block + def vertical_table(assigns) do ~H""" -
| + | <%= render_slot(@label) %> | -+ | <%= render_slot(@value) %> |
|---|
| - - | -- - | -- - | -- Actions - | -
|---|---|---|---|
| - <.link - navigate={~p"/#{@account}/devices/DF43E951-7DFB-4921-8F7F-BF0F8D31FA89"} - class="font-medium text-blue-600 dark:text-blue-500 hover:underline" - > - v1.01 Linux - - | -- <.link - navigate={~p"/#{@account}/actors/DF43E951-7DFB-4921-8F7F-BF0F8D31FA89"} - class="font-medium text-blue-600 dark:text-blue-500 hover:underline" - > - John Doe - - | - -- - Online - - | -
-
-
-
-
|
-
| - <.link - navigate={~p"/#{@account}/devices/DF43E951-7DFB-4921-8F7F-BF0F8D31FA89"} - class="font-medium text-blue-600 dark:text-blue-500 hover:underline" - > - v1.01 iOS - - | -- <.link - navigate={~p"/#{@account}/actors/DF43E951-7DFB-4921-8F7F-BF0F8D31FA89"} - class="font-medium text-blue-600 dark:text-blue-500 hover:underline" - > - Steve Johnson - - | - -- - Last seen 2 hours ago - - | -
-
-
-
-
|
-
| - <.link - navigate={~p"/#{@account}/devices/DF43E951-7DFB-4921-8F7F-BF0F8D31FA89"} - class="font-medium text-blue-600 dark:text-blue-500 hover:underline" - > - v1.01 macOS - - | -- <.link - navigate={~p"/#{@account}/actors/DF43E951-7DFB-4921-8F7F-BF0F8D31FA89"} - class="font-medium text-blue-600 dark:text-blue-500 hover:underline" - > - Steinberg, Gabriel - - | - -- - Archived 6 months ago - - | -
-
-
-
-
|
-
| + <.vertical_table_row> + <:label>Identifier + <:value><%= @device.id %> + + <.vertical_table_row> + <:label>Owner + <:value> + <.link + navigate={~p"/#{@account}/actors/#{@device.actor.id}"} + class="font-medium text-blue-600 dark:text-blue-500 hover:underline" > - Identifier - | -- BF0F8D31FA89 - | -
|---|---|
| - User - | -- <.link - navigate={~p"/#{@account}/actors/55DDA8CB-69A7-48FC-9048-639021C205A2"} - class="text-blue-600 hover:underline" - > - Andrew Dryga - - | -
| - First seen - | -- 3 days ago in Bangalore, India - | -
| - Last seen - | -- 1 hour ago in San Francisco, CA - | -
| - Remote IPv4 - | -
- 69.100.123.11
- |
-
| - Remote IPv6 - | -
- 2001:0db8:85a3:0000:0000:8a2e:0370:7334
- |
-
| - Transfer - | -- 4.43 GB up, 1.23 GB down - | -
| - Client version - | -- v1.01 for macOS/arm64 - | -
| - OS version - | -- macOS 13.4.1 - | -
| - Machine type - | -- Macbook Pro - | -
<%= @device.ipv4 %>
+
+ <.vertical_table_row>
+ <:label>Remote IPv6
+ <:value><%= @device.ipv6 %>
+
+ <.vertical_table_row>
+ <:label>Transfer
+ <:value>TODO
+
+ <.vertical_table_row>
+ <:label>Client Version
+ <:value>TODO
+
+ <.vertical_table_row>
+ <:label>OS Version
+ <:value>TODO
+
+ <.vertical_table_row>
+ <:label>Machine Type
+ <:value>TODO
+
+
Bou Kheir, Jamil
-
-
-
- | - - | -
-
- Identifiers
-
- <.icon name="hero-chevron-up-down-solid" class="w-4 h-4 ml-1" />
-
-
- |
- - - | -
-
- Last active
-
- <.icon name="hero-chevron-up-down-solid" class="w-4 h-4 ml-1" />
-
-
- |
- - Actions - | -
|---|---|---|---|---|
| - <.link - navigate={~p"/#{@account}/actors/DF43E951-7DFB-4921-8F7F-BF0F8D31FA89"} - class="font-medium text-blue-600 dark:text-blue-500 hover:underline" - > - Bou Kheir, Jamil - - | -- email:jamil@firezone.dev,okta:jamil@firezone.dev - | - -Admin, Engineering, 3 more... | -Today at 2:30 pm | -
-
-
-
-
-
- Delete
-
-
- |
-
| - <.link - navigate={~p"/#{@account}/actors/DF43E951-7DFB-4921-8F7F-BF0F8D31FA89"} - class="font-medium text-blue-600 dark:text-blue-500 hover:underline" - > - Dryga, Andrew - - | -- email:a@firezone.dev,okta:andrew.dryga@firezone.dev - | - -Admin, DevOps, Engineering, 3 more... | -Just now | -
-
-
-
-
-
- Delete
-
-
- |
-
| - <.link - navigate={~p"/#{@account}/actors/DF43E951-7DFB-4921-8F7F-BF0F8D31FA89"} - class="font-medium text-blue-600 dark:text-blue-500 hover:underline" - > - Steinberg, Gabriel - - | -- email:a@firezone.dev,okta:andrew.dryga@firezone.dev - | - -DevOps, IT, Security | -A month ago | -
-
-
-
-
-
- Delete
-
-
- |
-
Bou Kheir, Jamil
-
- <:actions>
- <.edit_button navigate={~p"/#{@account}/actors/DF43E951-7DFB-4921-8F7F-BF0F8D31FA89/edit"}>
- Edit user
-
-
-
-
- | - First name - | -- Steve - | -
|---|---|
| - Last name - | -- Johnson - | -
| - Source - | -- Manually created by - Jamil Bou Kheir - on May 3rd, 2023. - | -
| - Email - | -- steve@tesla.com - Verified - | -
| - Role - | -- Admin - | -
| - Groups - | -- <.link - navigate={~p"/#{@account}/groups/55DDA8CB-69A7-48FC-9048-639021C205A2"} - class="text-blue-600 hover:underline" - > - Engineering - - | -
| - Last active - | -- 1 hour ago - | -