add device count to superadmin

This commit is contained in:
Graham McIntire 2026-02-06 09:32:05 -06:00
parent 8044a6d140
commit 20952a781f
No known key found for this signature in database
4 changed files with 114 additions and 1 deletions

View file

@ -33,6 +33,7 @@ defmodule Towerops.Accounts.User do
field :privacy_policy_consent, :boolean, virtual: true
field :terms_of_service_consent, :boolean, virtual: true
field :password_breach_count, :integer, virtual: true
field :device_count, :integer, virtual: true
field :last_sudo_at, :utc_datetime
has_many :memberships, Membership

View file

@ -14,7 +14,7 @@ defmodule Towerops.Admin do
## User Management
@doc """
Lists all users in the system with their organizations.
Lists all users in the system with their organizations and device counts.
## Options
@ -34,7 +34,17 @@ defmodule Towerops.Admin do
limit = Keyword.get(opts, :limit, 100)
offset = Keyword.get(opts, :offset, 0)
# Subquery to count devices across all user's organizations
device_count_query =
from d in "devices",
join: m in "organization_memberships",
on: d.organization_id == m.organization_id,
where: m.user_id == parent_as(:user).id,
select: count(d.id)
User
|> from(as: :user)
|> select_merge([u], %{device_count: subquery(device_count_query)})
|> order_by([u], desc: u.inserted_at)
|> limit(^limit)
|> offset(^offset)

View file

@ -10,6 +10,7 @@
<:col :let={user} label="Email">{user.email}</:col>
<:col :let={user} label="Superuser">{if user.is_superuser, do: "Yes", else: "No"}</:col>
<:col :let={user} label="Organizations">{length(user.organizations)}</:col>
<:col :let={user} label="Devices">{user.device_count || 0}</:col>
<:col :let={user} label="Joined">
{ToweropsWeb.TimeHelpers.format_date(user.inserted_at, @timezone)}
</:col>

View file

@ -0,0 +1,101 @@
defmodule ToweropsWeb.Admin.UserLiveTest do
use ToweropsWeb.ConnCase
import Phoenix.LiveViewTest
import Towerops.AccountsFixtures
import Towerops.DevicesFixtures
import Towerops.OrganizationsFixtures
describe "Index" do
setup do
superuser =
user_fixture()
|> Ecto.Changeset.change(%{is_superuser: true})
|> Towerops.Repo.update!()
%{superuser: superuser}
end
test "displays device count for users", %{conn: conn, superuser: superuser} do
# Create a regular user
user = user_fixture()
# Create organizations with user as owner
org1 = organization_fixture(user.id)
org2 = organization_fixture(user.id)
# Create devices in different organizations (device_fixture auto-creates sites)
device_fixture(%{organization_id: org1.id})
device_fixture(%{organization_id: org1.id})
device_fixture(%{organization_id: org2.id})
# Log in as superuser
conn = log_in_user(conn, superuser)
# Visit the admin users page
{:ok, view, html} = live(conn, ~p"/admin/users")
# Check that the device count column header is displayed
assert html =~ "Devices"
# Verify device count is in the assigns
users = :sys.get_state(view.pid).socket.assigns.users
test_user = Enum.find(users, &(&1.id == user.id))
assert test_user.device_count == 3
end
test "displays 0 devices for users with no organizations", %{conn: conn, superuser: superuser} do
# Create a user with no organizations
user = user_fixture()
# Log in as superuser
conn = log_in_user(conn, superuser)
# Visit the admin users page
{:ok, view, html} = live(conn, ~p"/admin/users")
# Check that the device count column header is displayed
assert html =~ "Devices"
# Verify device count is 0 in the assigns
users = :sys.get_state(view.pid).socket.assigns.users
test_user = Enum.find(users, &(&1.id == user.id))
assert test_user.device_count == 0
end
test "device count only includes devices from user's organizations", %{
conn: conn,
superuser: superuser
} do
# Create two users
user1 = user_fixture()
user2 = user_fixture()
# Create organizations (user1 belongs to org1, user2 to org2)
org1 = organization_fixture(user1.id)
org2 = organization_fixture(user2.id)
# Create devices (user1 should only see devices from org1)
device_fixture(%{organization_id: org1.id})
device_fixture(%{organization_id: org1.id})
device_fixture(%{organization_id: org2.id})
# Log in as superuser
conn = log_in_user(conn, superuser)
# Visit the admin users page
{:ok, view, _html} = live(conn, ~p"/admin/users")
# Get all users from the assigns
users = :sys.get_state(view.pid).socket.assigns.users
# Find user1 and verify device count
user1_with_count = Enum.find(users, &(&1.id == user1.id))
assert user1_with_count.device_count == 2
# Find user2 and verify device count
user2_with_count = Enum.find(users, &(&1.id == user2.id))
assert user2_with_count.device_count == 1
end
end
end