defmodule ToweropsWeb.AgentLive.Show do @moduledoc false use ToweropsWeb, :live_view import ToweropsWeb.AgentLive.Helpers alias Towerops.Agents alias Towerops.Agents.ReleaseChecker @impl true def mount(%{"id" => id}, _session, socket) do organization = socket.assigns.current_scope.organization agent_token = Agents.get_agent_token!(id) current_user = socket.assigns.current_scope.user # Verify access: agent must belong to this org, or superadmin can view any agent has_access = agent_token.organization_id == organization.id || current_user.is_superuser if !has_access do raise Ecto.NoResultsError, queryable: Towerops.Agents.AgentToken end # device this agent is responsible for) polling_targets = Agents.list_agent_polling_targets(agent_token.id) # Get direct assignments direct_assignments = Agents.count_assigned_devices(agent_token.id) {:ok, socket |> assign(:page_title, agent_token.name) |> assign(:timezone, socket.assigns.current_scope.timezone) |> assign(:agent_token, agent_token) |> assign(:polling_targets, polling_targets) |> assign(:direct_assignments, direct_assignments) |> assign(:active_tab, "overview")} end @impl true def handle_params(_params, _url, socket) do {:noreply, socket} end @impl true def handle_event("switch_tab", %{"tab" => tab}, socket) do {:noreply, assign(socket, :active_tab, tab)} end def handle_event("restart_agent", _params, socket) do if socket.assigns.current_scope.user.is_superuser do Agents.restart_agent(socket.assigns.agent_token.id) {:noreply, put_flash(socket, :info, t("Restart command sent to agent"))} else {:noreply, socket} end end def handle_event("update_agent", _params, socket) do if socket.assigns.current_scope.user.is_superuser do agent_token = socket.assigns.agent_token arch = get_in(agent_token.metadata, ["arch"]) || "x86_64" {:noreply, send_update_command(socket, agent_token, arch)} else {:noreply, socket} end end defp send_update_command(socket, agent_token, arch) do with {:ok, release} <- ReleaseChecker.get_latest_release(), {:ok, asset} <- ReleaseChecker.asset_for_arch(release.assets, arch) do Agents.update_agent(agent_token.id, asset.url, asset.checksum) put_flash(socket, :info, t("Update command sent to agent (v%{version})", version: release.version)) else {:error, :no_matching_asset} -> put_flash(socket, :error, t("No binary available for architecture: %{arch}", arch: arch)) {:error, _reason} -> put_flash(socket, :error, t("Failed to fetch latest release")) end end defp assignment_source(device, agent_token_id) do cond do directly_assigned?(device, agent_token_id) -> {:direct, "Directly assigned"} site_assigned?(device, agent_token_id) -> {:site, "From site: #{device.site.name}"} organization_assigned?(device, agent_token_id) -> {:organization, "From organization"} true -> {:unknown, "Unknown"} end end defp directly_assigned?(device, agent_token_id) do Enum.any?(device.agent_assignments || [], fn a -> a.agent_token_id == agent_token_id and a.enabled end) end defp site_assigned?(device, agent_token_id) do device.site && device.site.agent_token_id == agent_token_id end defp organization_assigned?(device, agent_token_id) do device.site && device.site.organization && device.site.organization.default_agent_token_id == agent_token_id end defp source_badge_class(source) do case source do :direct -> "bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300" :site -> "bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-300" :organization -> "bg-amber-100 text-amber-800 dark:bg-amber-900 dark:text-amber-300" :unknown -> "bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-300" end end end