From c6caa81850670693db59185be25951bf4930e9a1 Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Tue, 10 Feb 2026 11:07:41 -0600 Subject: [PATCH] Group cloud pollers and org agents separately on admin agents page Cloud pollers render in their own table at the top, org agents below with the organization name shown under each agent name. --- .../live/admin/agent_live/index.ex | 16 +- .../live/admin/agent_live/index.html.heex | 206 ++++++++++++------ 2 files changed, 152 insertions(+), 70 deletions(-) diff --git a/lib/towerops_web/live/admin/agent_live/index.ex b/lib/towerops_web/live/admin/agent_live/index.ex index 43a6dc67..eb7c8ff0 100644 --- a/lib/towerops_web/live/admin/agent_live/index.ex +++ b/lib/towerops_web/live/admin/agent_live/index.ex @@ -14,13 +14,17 @@ defmodule ToweropsWeb.Admin.AgentLive.Index do end agent_tokens = Agents.list_all_agent_tokens() + {cloud_pollers, org_agents} = Enum.split_with(agent_tokens, & &1.is_cloud_poller) device_counts = calculate_device_counts(agent_tokens) {:ok, socket |> assign(:page_title, "All Agents") |> assign(:timezone, socket.assigns.current_scope.timezone) - |> stream(:agent_tokens, agent_tokens) + |> stream(:cloud_pollers, cloud_pollers) + |> stream(:org_agents, org_agents) + |> assign(:has_cloud_pollers, cloud_pollers != []) + |> assign(:has_org_agents, org_agents != []) |> assign(:device_counts, device_counts) |> assign(:now, DateTime.utc_now())} end @@ -56,8 +60,10 @@ defmodule ToweropsWeb.Admin.AgentLive.Index do direct = Agents.count_assigned_devices(agent_token_id) total = length(Agents.list_agent_polling_targets(agent_token_id)) + stream_name = if agent_token.is_cloud_poller, do: :cloud_pollers, else: :org_agents + socket - |> stream_insert(:agent_tokens, agent_token) + |> stream_insert(stream_name, agent_token) |> assign(:device_counts, Map.put(socket.assigns.device_counts, agent_token_id, %{direct: direct, total: total})) rescue Ecto.NoResultsError -> socket @@ -65,10 +71,14 @@ defmodule ToweropsWeb.Admin.AgentLive.Index do defp reload_all(socket) do agent_tokens = Agents.list_all_agent_tokens() + {cloud_pollers, org_agents} = Enum.split_with(agent_tokens, & &1.is_cloud_poller) device_counts = calculate_device_counts(agent_tokens) socket - |> stream(:agent_tokens, agent_tokens, reset: true) + |> stream(:cloud_pollers, cloud_pollers, reset: true) + |> stream(:org_agents, org_agents, reset: true) + |> assign(:has_cloud_pollers, cloud_pollers != []) + |> assign(:has_org_agents, org_agents != []) |> assign(:device_counts, device_counts) end diff --git a/lib/towerops_web/live/admin/agent_live/index.html.heex b/lib/towerops_web/live/admin/agent_live/index.html.heex index 87fd34f1..1b6120dc 100644 --- a/lib/towerops_web/live/admin/agent_live/index.html.heex +++ b/lib/towerops_web/live/admin/agent_live/index.html.heex @@ -7,77 +7,149 @@

- <.table id="admin-agents-table" rows={@streams.agent_tokens}> - <:col :let={{_id, agent}} label="Name"> -
- <.link - navigate={~p"/agents/#{agent.id}"} - class="font-medium text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300" - > - {agent.name} - - <%= if agent.is_cloud_poller do %> - - <.icon name="hero-cloud" class="h-3 w-3" /> Cloud + <%!-- Cloud Pollers --%> + <%= if @has_cloud_pollers do %> +
+

+ <.icon name="hero-cloud" class="w-5 h-5 inline mr-1" /> Cloud Pollers +

+ <.table id="cloud-pollers-table" rows={@streams.cloud_pollers}> + <:col :let={{_id, agent}} label="Name"> +
+ <.link + navigate={~p"/agents/#{agent.id}"} + class="font-medium text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300" + > + {agent.name} + + <%= if agent.allow_remote_debug do %> + + <.icon name="hero-bug-ant" class="h-3 w-3" /> Debug + + <% end %> +
+ + + <:col :let={{_id, agent}} label="Status"> + <% {status, label} = agent_status(agent) %> + + + {label} - <% end %> - <%= if agent.allow_remote_debug do %> - - <.icon name="hero-bug-ant" class="h-3 w-3" /> Debug + + + <:col :let={{_id, agent}} label="Devices"> + <% counts = Map.get(@device_counts, agent.id, %{direct: 0, total: 0}) %> +
+ {counts.total} total +
+
+ {counts.direct} direct + <%= if counts.total > counts.direct do %> + · {counts.total - counts.direct} inherited + <% end %> +
+ + + <:col :let={{_id, agent}} label="Last Seen"> +
+ <.timestamp datetime={agent.last_seen_at} timezone={@timezone} now={@now} /> +
+ <%= if agent.last_seen_at do %> +
+ <.timestamp + datetime={agent.last_seen_at} + timezone={@timezone} + format="absolute" + /> +
+ <% end %> + + + <:col :let={{_id, agent}} label="Version"> + <%= if agent.metadata["version"] do %> +
+ {format_agent_version(agent.metadata["version"])} +
+ <% end %> + + +
+ <% end %> + + <%!-- Organization Agents --%> + <%= if @has_org_agents do %> +
+

+ <.icon name="hero-server" class="w-5 h-5 inline mr-1" /> Organization Agents +

+ <.table id="org-agents-table" rows={@streams.org_agents}> + <:col :let={{_id, agent}} label="Name"> +
+
+ <.link + navigate={~p"/agents/#{agent.id}"} + class="font-medium text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300" + > + {agent.name} + + <%= if agent.allow_remote_debug do %> + + <.icon name="hero-bug-ant" class="h-3 w-3" /> Debug + + <% end %> +
+
+ {agent.organization && agent.organization.name} +
+
+ + + <:col :let={{_id, agent}} label="Status"> + <% {status, label} = agent_status(agent) %> + + + {label} - <% end %> -
- + - <:col :let={{_id, agent}} label="Organization"> - <%= if agent.is_cloud_poller do %> - Cloud Poller - <% else %> - - {agent.organization && agent.organization.name} - - <% end %> - + <:col :let={{_id, agent}} label="Devices"> + <% counts = Map.get(@device_counts, agent.id, %{direct: 0, total: 0}) %> +
+ {counts.total} total +
+
+ {counts.direct} direct + <%= if counts.total > counts.direct do %> + · {counts.total - counts.direct} inherited + <% end %> +
+ - <:col :let={{_id, agent}} label="Status"> - <% {status, label} = agent_status(agent) %> - - - {label} - - + <:col :let={{_id, agent}} label="Last Seen"> +
+ <.timestamp datetime={agent.last_seen_at} timezone={@timezone} now={@now} /> +
+ <%= if agent.last_seen_at do %> +
+ <.timestamp + datetime={agent.last_seen_at} + timezone={@timezone} + format="absolute" + /> +
+ <% end %> + - <:col :let={{_id, agent}} label="Devices"> - <% counts = Map.get(@device_counts, agent.id, %{direct: 0, total: 0}) %> -
- {counts.total} total -
-
- {counts.direct} direct - <%= if counts.total > counts.direct do %> - · {counts.total - counts.direct} inherited - <% end %> -
- - - <:col :let={{_id, agent}} label="Last Seen"> -
- <.timestamp datetime={agent.last_seen_at} timezone={@timezone} now={@now} /> -
- <%= if agent.last_seen_at do %> -
- <.timestamp datetime={agent.last_seen_at} timezone={@timezone} format="absolute" /> -
- <% end %> - - - <:col :let={{_id, agent}} label="Version"> - <%= if agent.metadata["version"] do %> -
- {format_agent_version(agent.metadata["version"])} -
- <% end %> - - + <:col :let={{_id, agent}} label="Version"> + <%= if agent.metadata["version"] do %> +
+ {format_agent_version(agent.metadata["version"])} +
+ <% end %> + + +
+ <% end %>