diff --git a/lib/towerops/agents.ex b/lib/towerops/agents.ex index 2ec88b02..0d6bd9d5 100644 --- a/lib/towerops/agents.ex +++ b/lib/towerops/agents.ex @@ -102,6 +102,8 @@ defmodule Towerops.Agents do {1, nil} """ + @spec update_agent_token_heartbeat(Ecto.UUID.t(), String.t() | nil, map() | nil) :: + {non_neg_integer(), nil | [term()]} def update_agent_token_heartbeat(agent_token_id, ip_address, metadata \\ nil) do now = DateTime.truncate(DateTime.utc_now(), :second) @@ -133,6 +135,7 @@ defmodule Towerops.Agents do {:error, :invalid_token} """ + @spec verify_agent_token(String.t()) :: {:ok, AgentToken.t()} | {:error, :invalid_token} def verify_agent_token(token) do with {:ok, token_string} <- AgentToken.verify_token(token), %AgentToken{} = agent_token <- @@ -238,6 +241,7 @@ defmodule Towerops.Agents do [%Equipment{snmp_enabled: true, site: %Site{}, snmp_device: %Device{sensors: [...], interfaces: [...]}}] """ + @spec list_agent_polling_targets(Ecto.UUID.t()) :: [Equipment.t()] def list_agent_polling_targets(agent_token_id) do # Use SQL to filter equipment by effective agent assignment # This avoids loading all equipment into memory and filtering in application code diff --git a/lib/towerops_web/channels/agent_channel.ex b/lib/towerops_web/channels/agent_channel.ex index 55b6231c..4436aab9 100644 --- a/lib/towerops_web/channels/agent_channel.ex +++ b/lib/towerops_web/channels/agent_channel.ex @@ -33,6 +33,8 @@ defmodule ToweropsWeb.AgentChannel do require Logger @impl true + @spec join(String.t(), map(), Phoenix.Socket.t()) :: + {:ok, Phoenix.Socket.t()} | {:error, map()} def join("agent:" <> _agent_id, %{"token" => token}, socket) do # Verify agent token from join payload case Agents.verify_agent_token(token) do @@ -60,6 +62,7 @@ defmodule ToweropsWeb.AgentChannel do end @impl true + @spec handle_info(:send_jobs, Phoenix.Socket.t()) :: {:noreply, Phoenix.Socket.t()} def handle_info(:send_jobs, socket) do jobs = build_jobs_for_agent(socket.assigns.agent_token_id) job_list = %AgentJobList{jobs: jobs} @@ -70,6 +73,7 @@ defmodule ToweropsWeb.AgentChannel do end @impl true + @spec handle_in(String.t(), map(), Phoenix.Socket.t()) :: {:noreply, Phoenix.Socket.t()} def handle_in("result", %{"binary" => binary_b64}, socket) do binary = Base.decode64!(binary_b64) result = SnmpResult.decode(binary) @@ -77,7 +81,6 @@ defmodule ToweropsWeb.AgentChannel do {:noreply, socket} end - @impl true def handle_in("heartbeat", %{"binary" => binary_b64}, socket) do binary = Base.decode64!(binary_b64) heartbeat = AgentHeartbeat.decode(binary) @@ -108,6 +111,7 @@ defmodule ToweropsWeb.AgentChannel do # Private helpers + @spec build_jobs_for_agent(Ecto.UUID.t()) :: [AgentJob.t()] defp build_jobs_for_agent(agent_token_id) do agent_token_id |> Agents.list_agent_polling_targets() diff --git a/lib/towerops_web/channels/agent_socket.ex b/lib/towerops_web/channels/agent_socket.ex index 7185f48e..98d63829 100644 --- a/lib/towerops_web/channels/agent_socket.ex +++ b/lib/towerops_web/channels/agent_socket.ex @@ -13,12 +13,14 @@ defmodule ToweropsWeb.AgentSocket do channel "agent:*", ToweropsWeb.AgentChannel @impl true + @spec connect(map(), Phoenix.Socket.t(), map()) :: {:ok, Phoenix.Socket.t()} def connect(_params, socket, _connect_info) do # Allow all connections - authentication happens at channel join {:ok, socket} end @impl true + @spec id(Phoenix.Socket.t()) :: String.t() | nil def id(socket) do # Return nil before authentication (channel join will set agent_token_id) case Map.get(socket.assigns, :agent_token_id) do