<.header> {@page_title} <:subtitle>Manage remote agents for local SNMP polling <:actions> <.button phx-click={JS.show(to: "#new-agent-form")} variant="primary"> <.icon name="hero-plus" class="h-5 w-5" /> New Agent <%= if @agent_tokens == [] do %>
<.icon name="hero-server" class="mx-auto h-12 w-12 text-gray-400 dark:text-gray-500" />

No agents

Get started by creating your first remote agent.

<.button phx-click={JS.show(to: "#new-agent-form")} variant="primary"> <.icon name="hero-plus" class="h-5 w-5" /> New Agent
<% else %>
<.table id="agents-table" rows={@agent_tokens}> <:col :let={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 <% end %> <%= if agent.allow_remote_debug do %> <.icon name="hero-bug-ant" class="h-3 w-3" /> Debug <% end %>
<:col :let={agent} label="Status"> <% {status, label} = agent_status(agent) %> {label} <:col :let={agent} label="Device"> <% 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={agent} label="Last Seen">
<.timestamp datetime={agent.last_seen_at} timezone={@timezone} />
<%= if agent.last_seen_at do %>
<.timestamp datetime={agent.last_seen_at} timezone={@timezone} />
<% end %> <%= if agent.last_ip do %>
{agent.last_ip}
<% end %> <:col :let={agent} label="Metadata"> <%= if agent.metadata["hostname"] do %>
<.icon name="hero-computer-desktop" class="h-4 w-4 inline" /> {agent.metadata["hostname"]}
<% end %> <:col :let={agent} label="Created">
{ToweropsWeb.TimeHelpers.format_date(agent.inserted_at, @timezone)}
<:action :let={agent}> <%= if agent.enabled do %>
<% else %> Disabled <% end %>
<% end %> <%!-- Cloud Pollers Section (Superadmin Only) --%> <%= if @is_superuser && @cloud_pollers != [] do %>

Cloud Pollers

Application-wide agents that can poll devices for any organization. Only visible and manageable by superadmins.

<.table id="cloud-pollers-table" rows={@cloud_pollers}> <:col :let={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.id == @global_default_cloud_poller_id do %> <.icon name="hero-check-circle" class="h-3 w-3" /> Default <% end %> <%= if agent.allow_remote_debug do %> <.icon name="hero-bug-ant" class="h-3 w-3" /> Debug <% end %>
<:col :let={agent} label="Status"> <% {status, label} = agent_status(agent) %> {label} <:col :let={agent} label="Last Seen">
<.timestamp datetime={agent.last_seen_at} timezone={@timezone} />
<%= if agent.last_seen_at do %>
<.timestamp datetime={agent.last_seen_at} timezone={@timezone} />
<% end %> <%= if agent.last_ip do %>
{agent.last_ip}
<% end %> <:col :let={agent} label="Metadata"> <%= if agent.metadata["hostname"] do %>
<.icon name="hero-computer-desktop" class="h-4 w-4 inline" /> {agent.metadata["hostname"]}
<% end %> <:col :let={agent} label="Created">
{ToweropsWeb.TimeHelpers.format_date(agent.inserted_at, @timezone)}
<:action :let={agent}> <%= if agent.enabled do %>
<% else %> Disabled <% end %>
<% end %> <%!-- Global Default Cloud Poller (Superadmin Only) --%> <%= if @is_superuser do %>

Global Default Cloud Poller

Fallback agent for organizations without a default agent configured. Devices with no assignment at any level will use this agent.

<.form for={%{}} phx-change="update_selected_global_default" class="max-w-md">
<%= if @global_default_cloud_poller_id do %> <% selected_poller = Enum.find(@cloud_pollers, &(&1.id == @global_default_cloud_poller_id)) %> <%= if selected_poller do %>

<.icon name="hero-check-circle" class="h-4 w-4 inline" /> Currently using: {selected_poller.name}

<% else %>

<.icon name="hero-exclamation-triangle" class="h-4 w-4 inline" /> Warning: Selected agent no longer exists. Please choose a new agent.

<% end %> <% else %>

<.icon name="hero-information-circle" class="h-4 w-4 inline" /> No global default configured. Devices without assignments will use direct Phoenix cluster polling.

<% end %>
<.button type="button" phx-click="save_global_default" variant="primary" disabled={@selected_global_default == (@global_default_cloud_poller_id || "")} > <.icon name="hero-check" class="h-4 w-4" /> Save Changes <%= if @selected_global_default != (@global_default_cloud_poller_id || "") do %> <% end %>
<% end %> <%= if @show_token_modal && @new_token do %>

Agent Setup Instructions

Use the token and Docker Compose configuration below to deploy this agent.

{@new_token.agent_token.name}

Click the token to select all, or use the Copy button

Docker Compose Setup with Auto-Updates

This configuration includes Watchtower for automatic agent updates. The agent will automatically update to the latest version every 15 minutes during development.

services:
  towerops-agent:
    image: {@agent_image}
    container_name: towerops-agent
    restart: unless-stopped
    environment:
      - TOWEROPS_API_URL={url(@socket, ~p"/")}
      - TOWEROPS_AGENT_TOKEN={@new_token.token}
    volumes:
      - ./data:/data
    labels:
      - "com.centurylinklabs.watchtower.enable=true"
      - "com.centurylinklabs.watchtower.scope=towerops"

  watchtower:
    image: containrrr/watchtower:latest
    container_name: towerops-watchtower
    restart: unless-stopped
    environment:
      # Check for updates every 15 minutes (900 seconds)
      - WATCHTOWER_POLL_INTERVAL=900
      - WATCHTOWER_LABEL_ENABLE=true
      - WATCHTOWER_CLEANUP=true
      - WATCHTOWER_LOG_LEVEL=info
      # Use latest Docker API version
      - DOCKER_API_VERSION=1.44
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
<.button phx-click="close_token_modal" variant="primary"> Close
<% end %>