towerops/lib/towerops_web/live/agent_live/index.ex
Graham McIntire 6516f509e9
cleanup: remove debug logging from agent creation (again)
The user had manually added debug logging in commit 66fd6fa.
Now that the cloud poller checkbox issue is fixed, removing the debug output.
2026-01-25 09:10:47 -06:00

278 lines
9.6 KiB
Elixir

defmodule ToweropsWeb.AgentLive.Index do
@moduledoc false
use ToweropsWeb, :live_view
import ToweropsWeb.AgentLive.Helpers
alias Towerops.Agents
alias Towerops.Agents.Stats
alias Towerops.Settings
@impl true
def mount(_params, _session, socket) do
organization = socket.assigns.current_organization
current_user = socket.assigns.current_scope.user
agent_tokens = Agents.list_organization_agent_tokens(organization.id)
# If superadmin, also load cloud pollers and global default
{cloud_pollers, global_default_cloud_poller_id} =
if current_user.is_superuser do
{Agents.list_cloud_pollers(), Settings.get_global_default_cloud_poller()}
else
{[], nil}
end
# device counts for each agent (both direct and total with inheritance)
equipment_counts =
Map.new(agent_tokens, fn token ->
direct = Agents.count_assigned_devices(token.id)
total = length(Agents.list_agent_polling_targets(token.id))
{token.id, %{direct: direct, total: total}}
end)
# Get organization-wide agent health statistics
agent_health_stats = Stats.get_organization_agent_health(organization.id)
assignment_breakdown = Stats.get_device_assignment_breakdown(organization.id)
offline_agents = Stats.get_offline_agents(organization.id)
# Get agent image URL from config or use default
agent_image = Application.get_env(:towerops, :agent_docker_image, "towerops/agent:latest")
{:ok,
socket
|> assign(:page_title, "Remote Agents")
|> assign(:agent_tokens, agent_tokens)
|> assign(:cloud_pollers, cloud_pollers)
|> assign(:global_default_cloud_poller_id, global_default_cloud_poller_id)
|> assign(:device_counts, equipment_counts)
|> assign(:agent_health_stats, agent_health_stats)
|> assign(:assignment_breakdown, assignment_breakdown)
|> assign(:offline_agents, offline_agents)
|> assign(:agent_image, agent_image)
|> assign(:new_token, nil)
|> assign(:show_token_modal, false)
|> assign(:agent_form, to_form(%{"name" => "", "is_cloud_poller" => false}))}
end
@impl true
def handle_params(params, _url, socket) do
{:noreply, apply_action(socket, socket.assigns.live_action, params)}
end
@impl true
def handle_event("create_agent", params, socket) do
{name, is_cloud_poller} = parse_agent_params(params)
with :ok <- validate_cloud_poller_permission(socket.assigns.current_scope.user, is_cloud_poller),
{:ok, agent_token, token} <- create_agent(socket.assigns.current_organization, name, is_cloud_poller) do
handle_agent_creation_success(socket, agent_token, token, is_cloud_poller)
else
{:error, :unauthorized} ->
{:noreply, put_flash(socket, :error, "Only superadmins can create cloud pollers")}
{:error, _changeset} ->
error_message =
if is_cloud_poller, do: "Failed to create cloud poller", else: "Failed to create agent"
{:noreply, put_flash(socket, :error, error_message)}
end
end
@impl true
def handle_event("close_token_modal", _params, socket) do
{:noreply,
socket
|> assign(:show_token_modal, false)
|> assign(:new_token, nil)}
end
@impl true
def handle_event("show_setup", %{"id" => id}, socket) do
agent_token = Agents.get_agent_token!(id)
{:noreply,
socket
|> assign(:new_token, %{agent_token: agent_token, token: agent_token.token})
|> assign(:show_token_modal, true)}
end
@impl true
def handle_event("regenerate_token", %{"id" => id}, socket) do
agent_token = Agents.get_agent_token!(id)
organization = socket.assigns.current_organization
# Revoke the old token and create a new one
case Agents.revoke_agent_token(id) do
{:ok, _} ->
case Agents.create_agent_token(organization.id, agent_token.name) do
{:ok, new_agent_token, token} ->
agent_tokens = Agents.list_organization_agent_tokens(organization.id)
{:noreply,
socket
|> assign(:agent_tokens, agent_tokens)
|> assign(:new_token, %{agent_token: new_agent_token, token: token})
|> assign(:show_token_modal, true)
|> put_flash(:info, "New token generated successfully")}
{:error, _changeset} ->
{:noreply, put_flash(socket, :error, "Failed to generate new token")}
end
{:error, _} ->
{:noreply, put_flash(socket, :error, "Failed to revoke old token")}
end
end
@impl true
def handle_event("delete_agent", %{"id" => id}, socket) do
case Agents.delete_agent_token(id) do
{:ok, _} ->
organization = socket.assigns.current_organization
agent_tokens = Agents.list_organization_agent_tokens(organization.id)
# Refresh cloud pollers list if superadmin (in case a cloud poller was deleted)
cloud_pollers = load_cloud_pollers_if_superuser(socket.assigns.current_scope.user)
# Recalculate device counts after deletion
equipment_counts =
Map.new(agent_tokens, fn token ->
direct = Agents.count_assigned_devices(token.id)
total = length(Agents.list_agent_polling_targets(token.id))
{token.id, %{direct: direct, total: total}}
end)
# Refresh health statistics
agent_health_stats = Stats.get_organization_agent_health(organization.id)
assignment_breakdown = Stats.get_device_assignment_breakdown(organization.id)
offline_agents = Stats.get_offline_agents(organization.id)
{:noreply,
socket
|> assign(:agent_tokens, agent_tokens)
|> assign(:cloud_pollers, cloud_pollers)
|> assign(:device_counts, equipment_counts)
|> assign(:agent_health_stats, agent_health_stats)
|> assign(:assignment_breakdown, assignment_breakdown)
|> assign(:offline_agents, offline_agents)
|> put_flash(:info, "Agent deleted successfully. Devices now fall back to site/org defaults or cloud polling.")}
{:error, _} ->
{:noreply, put_flash(socket, :error, "Failed to delete agent")}
end
end
@impl true
def handle_event("set_global_default", %{"agent_token_id" => agent_token_id}, socket) do
current_user = socket.assigns.current_scope.user
if current_user.is_superuser do
# Handle empty string as nil
agent_token_id = if agent_token_id == "", do: nil, else: agent_token_id
case Settings.set_global_default_cloud_poller(agent_token_id) do
{:ok, _} ->
handle_global_default_success(socket, agent_token_id)
{:error, _} ->
{:noreply, put_flash(socket, :error, "Failed to update global default cloud poller")}
end
else
{:noreply, put_flash(socket, :error, "Only superadmins can set the global default cloud poller")}
end
end
defp apply_action(socket, :index, _params) do
socket
end
defp parse_agent_params(params) do
case params do
%{"agent_form" => %{"name" => n, "is_cloud_poller" => cp}} ->
{n, cp in ["true", "on", true]}
%{"agent_form" => %{"name" => n}} ->
{n, false}
%{"name" => n, "is_cloud_poller" => cp} ->
{n, cp in ["true", "on", true]}
%{"name" => n} ->
{n, false}
end
end
defp validate_cloud_poller_permission(user, is_cloud_poller) do
if is_cloud_poller && !user.is_superuser do
{:error, :unauthorized}
else
:ok
end
end
defp create_agent(organization, name, is_cloud_poller) do
if is_cloud_poller do
Agents.create_cloud_poller(name)
else
Agents.create_agent_token(organization.id, name)
end
end
defp handle_agent_creation_success(socket, agent_token, token, is_cloud_poller) do
organization = socket.assigns.current_organization
agent_tokens = Agents.list_organization_agent_tokens(organization.id)
cloud_pollers = load_cloud_pollers_if_superuser(socket.assigns.current_scope.user)
equipment_counts = calculate_device_counts(agent_tokens)
agent_health_stats = Stats.get_organization_agent_health(organization.id)
assignment_breakdown = Stats.get_device_assignment_breakdown(organization.id)
offline_agents = Stats.get_offline_agents(organization.id)
success_message = get_success_message(is_cloud_poller)
{:noreply,
socket
|> assign(:agent_tokens, agent_tokens)
|> assign(:cloud_pollers, cloud_pollers)
|> assign(:device_counts, equipment_counts)
|> assign(:agent_health_stats, agent_health_stats)
|> assign(:assignment_breakdown, assignment_breakdown)
|> assign(:offline_agents, offline_agents)
|> assign(:new_token, %{agent_token: agent_token, token: token})
|> assign(:show_token_modal, true)
|> assign(:agent_form, to_form(%{"name" => "", "is_cloud_poller" => false}))
|> put_flash(:info, success_message)}
end
defp load_cloud_pollers_if_superuser(user) do
if user.is_superuser, do: Agents.list_cloud_pollers(), else: []
end
defp calculate_device_counts(agent_tokens) do
Map.new(agent_tokens, fn t ->
direct = Agents.count_assigned_devices(t.id)
total = length(Agents.list_agent_polling_targets(t.id))
{t.id, %{direct: direct, total: total}}
end)
end
defp get_success_message(is_cloud_poller) do
if is_cloud_poller,
do: "Cloud poller created successfully",
else: "Agent created successfully"
end
defp handle_global_default_success(socket, agent_token_id) do
message =
if agent_token_id,
do: "Global default cloud poller set successfully",
else: "Global default cloud poller cleared"
{:noreply,
socket
|> assign(:global_default_cloud_poller_id, agent_token_id)
|> put_flash(:info, message)}
end
end