The user had manually added debug logging in commit 66fd6fa.
Now that the cloud poller checkbox issue is fixed, removing the debug output.
278 lines
9.6 KiB
Elixir
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
|