Commit 9fffe077320bb60d4b24ca717f4838755222dea1

Thomas de Grivel 2022-01-27T19:06:35

[admin] sort user index by columns

diff --git a/lib/kmxgit/user_manager.ex b/lib/kmxgit/user_manager.ex
index 1787026..33fb39e 100644
--- a/lib/kmxgit/user_manager.ex
+++ b/lib/kmxgit/user_manager.ex
@@ -9,10 +9,123 @@ defmodule Kmxgit.UserManager do
   alias Kmxgit.SlugManager.Slug
   alias Kmxgit.UserManager.{Avatar, User, UserToken, UserNotifier}
 
+  @list_preload [:owned_repositories,
+                 :slug]
+
   def list_users do
+    update_disk_usage()
+    Repo.all from user in User,
+      preload: ^@list_preload
+  end
+  def list_users(%{column: "id", reverse: true}) do
+    update_disk_usage()
+    Repo.all from user in User,
+      preload: ^@list_preload,
+      order_by: [desc: :id]
+  end
+  def list_users(%{column: "id"}) do
+    update_disk_usage()
+    Repo.all from user in User,
+      preload: ^@list_preload,
+      order_by: :id
+  end
+  def list_users(%{column: "name", reverse: true}) do
+    update_disk_usage()
+    Repo.all from user in User,
+      preload: ^@list_preload,
+      order_by: [desc: :name]
+  end
+  def list_users(%{column: "name"}) do
+    update_disk_usage()
+    Repo.all from user in User,
+      preload: ^@list_preload,
+      order_by: :name
+  end
+  def list_users(%{column: "email", reverse: true}) do
+    update_disk_usage()
+    Repo.all from user in User,
+      preload: ^@list_preload,
+      order_by: [desc: :email]
+  end
+  def list_users(%{column: "email"}) do
+    update_disk_usage()
+    Repo.all from user in User,
+      preload: ^@list_preload,
+      order_by: :email
+  end
+  def list_users(%{column: "login", reverse: true}) do
+    update_disk_usage()
+    Repo.all from user in User,
+      join: s in Slug,
+      on: s.user_id == user.id,
+      preload: ^@list_preload,
+      order_by: [desc: s.slug]
+  end
+  def list_users(%{column: "login"}) do
+    update_disk_usage()
+    Repo.all from user in User,
+      join: s in Slug,
+      on: s.user_id == user.id,
+      preload: ^@list_preload,
+      order_by: s.slug
+  end
+  def list_users(%{column: "du", reverse: true}) do
+    update_disk_usage()
+    Repo.all from user in User,
+      preload: ^@list_preload,
+      order_by: [desc: :disk_usage]
+  end
+  def list_users(%{column: "du"}) do
+    update_disk_usage()
+    Repo.all from user in User,
+      preload: ^@list_preload,
+      order_by: :disk_usage
+  end
+  def list_users(%{column: "mfa", reverse: true}) do
+    update_disk_usage()
+    Repo.all from user in User,
+      preload: ^@list_preload,
+      order_by: [desc: user.totp_last == 0]
+  end
+  def list_users(%{column: "mfa"}) do
+    update_disk_usage()
+    Repo.all from user in User,
+      preload: ^@list_preload,
+      order_by: user.totp_last == 0
+  end
+  def list_users(%{column: "admin", reverse: true}) do
+    update_disk_usage()
     Repo.all from user in User,
-      preload: [:owned_repositories,
-                :slug]
+      preload: ^@list_preload,
+      order_by: [desc: :is_admin]
+  end
+  def list_users(%{column: "admin"}) do
+    update_disk_usage()
+    Repo.all from user in User,
+      preload: ^@list_preload,
+      order_by: :is_admin
+  end
+  def list_users(%{column: "deploy", reverse: true}) do
+    update_disk_usage()
+    Repo.all from user in User,
+      preload: ^@list_preload,
+      order_by: [desc: :deploy_only]
+  end
+  def list_users(%{column: "deploy"}) do
+    update_disk_usage()
+    Repo.all from user in User,
+      preload: ^@list_preload,
+      order_by: :deploy_only
+  end
+
+  def update_disk_usage() do
+    users = Repo.all(from user in User, preload: :slug)
+    |> Enum.map(fn user ->
+      user
+      |> Ecto.Changeset.cast(%{}, [])
+      |> Ecto.Changeset.put_change(:disk_usage, User.disk_usage(user))
+      |> Repo.update!()
+    end)
   end
 
   def put_disk_usage(user = %User{}) do
diff --git a/lib/kmxgit/user_manager/user.ex b/lib/kmxgit/user_manager/user.ex
index ed53e1d..d327d34 100644
--- a/lib/kmxgit/user_manager/user.ex
+++ b/lib/kmxgit/user_manager/user.ex
@@ -12,7 +12,7 @@ defmodule Kmxgit.UserManager.User do
     field :confirmed_at, :utc_datetime
     field :deploy_only, :boolean, null: false, default: false
     field :description, :string, null: true
-    field :disk_usage, :integer, virtual: true, default: 0
+    field :disk_usage, :integer, default: 0
     field :email, :string
     field :hashed_password, :string, redact: true
     field :is_admin, :boolean, null: false, default: false
diff --git a/lib/kmxgit_web/controllers/admin/user_controller.ex b/lib/kmxgit_web/controllers/admin/user_controller.ex
index 2f6b4af..b527253 100644
--- a/lib/kmxgit_web/controllers/admin/user_controller.ex
+++ b/lib/kmxgit_web/controllers/admin/user_controller.ex
@@ -7,11 +7,12 @@ defmodule KmxgitWeb.Admin.UserController do
   alias Kmxgit.UserManager.User
   alias KmxgitWeb.ErrorView
 
-  def index(conn, _params) do
-    users = UserManager.list_users
-    |> UserManager.put_disk_usage()
+  def index(conn, params) do
+    sort = KmxgitWeb.Admin.sort_param(params["sort"])
+    users = UserManager.list_users(sort)
     conn
     |> assign(:page_title, gettext("Users"))
+    |> assign(:sort, sort)
     |> assign(:users, users)
     |> render("index.html")
   end
diff --git a/lib/kmxgit_web/templates/admin/repository/index.html.heex b/lib/kmxgit_web/templates/admin/repository/index.html.heex
index 6492d81..37d7480 100644
--- a/lib/kmxgit_web/templates/admin/repository/index.html.heex
+++ b/lib/kmxgit_web/templates/admin/repository/index.html.heex
@@ -2,30 +2,34 @@
   <h1>Repositories</h1>
 
   <table class="table admin-index">
-    <tr>
-      <th><%= link gettext("Id"), to: Routes.admin_repository_path(@conn, :index, sort: "id#{if @sort.column == "id" && !@sort.reverse, do: "-"}") %><%= if @sort.column == "id" do %><%= if @sort.reverse do %> <i class="fa fa-angle-down"></i><% else %> <i class="fa fa-angle-up"></i><% end %><% end %></th>
-      <th><%= link gettext("Owner"), to: Routes.admin_repository_path(@conn, :index, sort: "owner#{if @sort.column == "owner" && !@sort.reverse, do: "-"}") %><%= if @sort.column == "owner" do %><%= if @sort.reverse do %> <i class="fa fa-angle-down"></i><% else %> <i class="fa fa-angle-up"></i><% end %><% end %></th>
-      <th><%= link gettext("Slug"), to: Routes.admin_repository_path(@conn, :index, sort: "slug#{if @sort.column == "slug" && !@sort.reverse, do: "-"}") %><%= if @sort.column == "slug" do %><%= if @sort.reverse do %> <i class="fa fa-angle-down"></i><% else %> <i class="fa fa-angle-up"></i><% end %><% end %></th>
-      <th><%= link gettext("Disk usage"), to: Routes.admin_repository_path(@conn, :index, sort: "du#{if @sort.column == "du" && !@sort.reverse, do: "-"}") %><%= if @sort.column == "du" do %><%= if @sort.reverse do %> <i class="fa fa-angle-down"></i><% else %> <i class="fa fa-angle-up"></i><% end %><% end %></th>
-      <th><%= gettext("Actions") %></th>
-    </tr>
-    <%= Enum.map @repos, fn(repo) -> %>
+    <thead>
       <tr>
-        <td><%= link repo.id, to: Routes.admin_repository_path(@conn, :show, repo) %></td>
-        <td>
-          <%= case owner = Repository.owner(repo) do %>
-            <% %Organisation{} -> %>
-              <%= link owner.name || owner.slug.slug, to: Routes.admin_organisation_path(@conn, :show, owner) %>
-            <% %User{} -> %>
-              <%= link owner.slug.slug, to: Routes.admin_user_path(@conn, :show, owner) %>
-          <% end %>
-        </td>
-        <td><%= link Repository.full_slug(repo), to: Routes.admin_repository_path(@conn, :show, repo) %></td>
-        <td><%= disk_usage(repo.disk_usage) %></td>
-        <td>
-          <%= link gettext("Show"), to: Routes.repository_path(@conn, :show, Repository.owner_slug(repo), Repository.splat(repo)), class: "btn btn-sm btn-primary" %>
-        </td>
+        <th><%= link gettext("Id"), to: Routes.admin_repository_path(@conn, :index, sort: "id#{if @sort.column == "id" && !@sort.reverse, do: "-"}") %><%= if @sort.column == "id" do %><%= if @sort.reverse do %> <i class="fa fa-angle-down"></i><% else %> <i class="fa fa-angle-up"></i><% end %><% end %></th>
+        <th><%= link gettext("Owner"), to: Routes.admin_repository_path(@conn, :index, sort: "owner#{if @sort.column == "owner" && !@sort.reverse, do: "-"}") %><%= if @sort.column == "owner" do %><%= if @sort.reverse do %> <i class="fa fa-angle-down"></i><% else %> <i class="fa fa-angle-up"></i><% end %><% end %></th>
+        <th><%= link gettext("Slug"), to: Routes.admin_repository_path(@conn, :index, sort: "slug#{if @sort.column == "slug" && !@sort.reverse, do: "-"}") %><%= if @sort.column == "slug" do %><%= if @sort.reverse do %> <i class="fa fa-angle-down"></i><% else %> <i class="fa fa-angle-up"></i><% end %><% end %></th>
+        <th><%= link gettext("Disk usage"), to: Routes.admin_repository_path(@conn, :index, sort: "du#{if @sort.column == "du" && !@sort.reverse, do: "-"}") %><%= if @sort.column == "du" do %><%= if @sort.reverse do %> <i class="fa fa-angle-down"></i><% else %> <i class="fa fa-angle-up"></i><% end %><% end %></th>
+        <th><%= gettext("Actions") %></th>
       </tr>
-    <% end %>
+    </thead>
+    <tbody>
+      <%= Enum.map @repos, fn(repo) -> %>
+        <tr>
+          <td><%= link repo.id, to: Routes.admin_repository_path(@conn, :show, repo) %></td>
+          <td>
+            <%= case owner = Repository.owner(repo) do %>
+              <% %Organisation{} -> %>
+                <%= link owner.name || owner.slug.slug, to: Routes.admin_organisation_path(@conn, :show, owner) %>
+              <% %User{} -> %>
+                <%= link owner.slug.slug, to: Routes.admin_user_path(@conn, :show, owner) %>
+            <% end %>
+          </td>
+          <td><%= link Repository.full_slug(repo), to: Routes.admin_repository_path(@conn, :show, repo) %></td>
+          <td><%= disk_usage(repo.disk_usage) %></td>
+          <td>
+            <%= link gettext("Show"), to: Routes.repository_path(@conn, :show, Repository.owner_slug(repo), Repository.splat(repo)), class: "btn btn-sm btn-primary" %>
+          </td>
+        </tr>
+      <% end %>
+    </tbody>
   </table>
 </div>
diff --git a/lib/kmxgit_web/templates/admin/user/index.html.heex b/lib/kmxgit_web/templates/admin/user/index.html.heex
index 5f9089b..9e88f77 100644
--- a/lib/kmxgit_web/templates/admin/user/index.html.heex
+++ b/lib/kmxgit_web/templates/admin/user/index.html.heex
@@ -9,30 +9,33 @@
   </div>
 
   <table class="table admin-index">
-    <tr>
-      <th><%= gettext "Id" %></th>
-      <th><%= gettext "Name" %></th>
-      <th><%= gettext "Email" %></th>
-      <th><%= gettext "Login" %></th>
-      <th><%= gettext "Disk usage" %></th>
-      <th><%= gettext "2FA" %></th>
-      <th><%= gettext "Admin" %></th>
-      <th><%= gettext "Deploy" %></th>
-      <th><%= gettext "Actions" %></th>
-    </tr>
-    <%= Enum.map @users, fn(user) -> %>
+    <thead>
       <tr>
-        <td><%= link user.id, to: Routes.admin_user_path(@conn, :show, user) %></td>
-        <td><%= link user.name, to: Routes.admin_user_path(@conn, :show, user) %></td>
-        <td><%= link user.email, to: "mailto:#{user.email}" %></td>
-        <td><%= link User.login(user), to: Routes.admin_user_path(@conn, :show, user) %></td>
-        <td><%= disk_usage(user.disk_usage) %></td>
-        <td><%= if user.totp_last != 0, do: "TOTP", else: "none" %></td>
-        <td><%= user.is_admin %></td>
-        <td><%= user.deploy_only %></td>
-        <td><%= link gettext("Show"), to: Routes.slug_path(@conn, :show, User.login(user) || ""), class: "btn btn-sm btn-primary" %></td>
+        <th><%= link gettext("Id"), to: Routes.admin_user_path(@conn, :index, sort: "id#{if @sort.column == "id" && !@sort.reverse, do: "-"}") %><%= if @sort.column == "id" do %><%= if @sort.reverse do %> <i class="fa fa-angle-down"></i><% else %> <i class="fa fa-angle-up"></i><% end %><% end %></th>
+        <th><%= link gettext("Name"), to: Routes.admin_user_path(@conn, :index, sort: "name#{if @sort.column == "name" && !@sort.reverse, do: "-"}") %><%= if @sort.column == "name" do %><%= if @sort.reverse do %> <i class="fa fa-angle-down"></i><% else %> <i class="fa fa-angle-up"></i><% end %><% end %></th>
+        <th><%= link gettext("Email"), to: Routes.admin_user_path(@conn, :index, sort: "email#{if @sort.column == "email" && !@sort.reverse, do: "-"}") %><%= if @sort.column == "email" do %><%= if @sort.reverse do %> <i class="fa fa-angle-down"></i><% else %> <i class="fa fa-angle-up"></i><% end %><% end %></th>
+        <th><%= link gettext("Login"), to: Routes.admin_user_path(@conn, :index, sort: "login#{if @sort.column == "login" && !@sort.reverse, do: "-"}") %><%= if @sort.column == "login" do %><%= if @sort.reverse do %> <i class="fa fa-angle-down"></i><% else %> <i class="fa fa-angle-up"></i><% end %><% end %></th>
+        <th><%= link gettext("Disk usage"), to: Routes.admin_user_path(@conn, :index, sort: "du#{if @sort.column == "du" && !@sort.reverse, do: "-"}") %><%= if @sort.column == "du" do %><%= if @sort.reverse do %> <i class="fa fa-angle-down"></i><% else %> <i class="fa fa-angle-up"></i><% end %><% end %></th>
+        <th><%= link gettext("2FA"), to: Routes.admin_user_path(@conn, :index, sort: "mfa#{if @sort.column == "mfa" && !@sort.reverse, do: "-"}") %><%= if @sort.column == "mfa" do %><%= if @sort.reverse do %> <i class="fa fa-angle-down"></i><% else %> <i class="fa fa-angle-up"></i><% end %><% end %></th>
+        <th><%= link gettext("Admin"), to: Routes.admin_user_path(@conn, :index, sort: "admin#{if @sort.column == "admin" && !@sort.reverse, do: "-"}") %><%= if @sort.column == "admin" do %><%= if @sort.reverse do %> <i class="fa fa-angle-down"></i><% else %> <i class="fa fa-angle-up"></i><% end %><% end %></th>
+        <th><%= link gettext("Deploy"), to: Routes.admin_user_path(@conn, :index, sort: "deploy#{if @sort.column == "deploy" && !@sort.reverse, do: "-"}") %><%= if @sort.column == "deploy" do %><%= if @sort.reverse do %> <i class="fa fa-angle-down"></i><% else %> <i class="fa fa-angle-up"></i><% end %><% end %></th>
+        <th><%= gettext "Actions" %></th>
       </tr>
-    <% end %>
+    </thead>
+    <tbody>
+      <%= Enum.map @users, fn(user) -> %>
+        <tr>
+          <td><%= link user.id, to: Routes.admin_user_path(@conn, :show, user) %></td>
+          <td><%= link user.name, to: Routes.admin_user_path(@conn, :show, user) %></td>
+          <td><%= link user.email, to: "mailto:#{user.email}" %></td>
+          <td><%= link User.login(user), to: Routes.admin_user_path(@conn, :show, user) %></td>
+          <td><%= disk_usage(user.disk_usage) %></td>
+          <td><%= if user.totp_last != 0, do: "TOTP", else: "none" %></td>
+          <td><%= user.is_admin %></td>
+          <td><%= user.deploy_only %></td>
+          <td><%= link gettext("Show"), to: Routes.slug_path(@conn, :show, User.login(user) || ""), class: "btn btn-sm btn-primary" %></td>
+        </tr>
+      <% end %>
+    </tbody>
   </table>
-
 </div>
diff --git a/priv/repo/migrations/20220127154904_add_disk_usage_to_users.exs b/priv/repo/migrations/20220127154904_add_disk_usage_to_users.exs
new file mode 100644
index 0000000..f2d7d76
--- /dev/null
+++ b/priv/repo/migrations/20220127154904_add_disk_usage_to_users.exs
@@ -0,0 +1,9 @@
+defmodule Kmxgit.Repo.Migrations.AddDiskUsageToUsers do
+  use Ecto.Migration
+
+  def change do
+    alter table(:users) do
+      add :disk_usage, :integer, default: 0
+    end
+  end
+end