2.6 KiB · text History b868907
defmodule GitGudWeb.AuditLive.Admin do
  @moduledoc """
  `/admin/audit[/:category[/*refine]]` — everything the audit log holds
  (`GitGud.Audit`): every org's and repository's entries, and the
  instance's own — federation blocks, instance settings, registration
  invites, global runners, pruning.

  Instance admins only (`live_session :admin`), in sudo mode, checked
  again on every `handle_params`. The one view that shows each entry's
  client address and user agent. Filters by who, by organization, and
  to what belongs to no org or repository at all.
  """
  use GitGudWeb, :live_view

  alias GitGud.Audit.Catalog
  alias GitGudWeb.AuditLive.Page

  @impl true
  def mount(_params, _session, socket) do
    {:ok,
     socket
     |> assign(:page_title, "Audit log")
     |> Page.setup(Catalog.categories(), refines: [:actor, :org, :scope], show_request: true)}
  end

  @impl true
  def handle_params(params, uri, socket) do
    # `:require_admin` ran at mount; a live patch doesn't run it again.
    if admin_now?(socket),
      do: Page.params(socket, params, uri, ~p"/admin/audit", :instance),
      else: {:noreply, socket |> put_flash(:error, "Admins only.") |> redirect(to: ~p"/")}
  end

  @impl true
  def handle_event("filter", %{"filter" => params}, socket), do: Page.filter(socket, params)
  def handle_event(_event, _params, socket), do: {:noreply, socket}

  @impl true
  def handle_info({:audit, id}, socket), do: Page.live(socket, id, admin_now?(socket))

  # Read again rather than trusting the mount's copy of the account: a
  # page can outlive its viewer's admin role.
  defp admin_now?(socket) do
    case GitGud.Repo.get(GitGud.Accounts.User, socket.assigns.current_scope.user.id) do
      %{is_admin: true} -> true
      _ -> false
    end
  end

  @impl true
  def render(assigns) do
    ~H"""
    <Layouts.app flash={@flash} current_scope={@current_scope}>
      <div class="space-y-6">
        <header class="space-y-1">
          <h1 class="text-xl font-semibold">Audit log</h1>
          <p class="text-sm opacity-70">
            Everything recorded on this instance, newest first. Each entry's
            details include the address and browser it came from — shown here only.
          </p>
        </header>
        <Page.body
          base={@base}
          filters={@filters}
          filter_form={@filter_form}
          categories={@categories}
          refines={@refines}
          show_request={@show_request}
          entries={@streams.entries}
          older={@older}
          newer={@newer}
        />
      </div>
    </Layouts.app>
    """
  end
end