From e0592dbf3ded3419908d9b6d108d7f7074f6cc03 Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Thu, 5 Feb 2026 09:21:43 -0600 Subject: [PATCH] fix: use agent-collected data for live polling when agent assigned Modified GraphLive live polling to check for agent assignments and use agent-collected sensor data from the database instead of doing direct SNMP polling. This respects the agent assignment and prevents Phoenix from bypassing the agent for live graph updates. Also refactored handle_params to reduce nesting depth. --- lib/towerops_web/live/graph_live/show.ex | 119 ++++++++++++++++------- 1 file changed, 84 insertions(+), 35 deletions(-) diff --git a/lib/towerops_web/live/graph_live/show.ex b/lib/towerops_web/live/graph_live/show.ex index 99c79291..8fe8d705 100644 --- a/lib/towerops_web/live/graph_live/show.ex +++ b/lib/towerops_web/live/graph_live/show.ex @@ -2,6 +2,7 @@ defmodule ToweropsWeb.GraphLive.Show do @moduledoc false use ToweropsWeb, :live_view + alias Towerops.Agents alias Towerops.Devices alias Towerops.Monitoring alias Towerops.Snmp @@ -18,46 +19,64 @@ defmodule ToweropsWeb.GraphLive.Show do def handle_params(%{"id" => device_id, "sensor_type" => sensor_type} = params, _, socket) do organization = socket.assigns.current_scope.organization - # Verify device exists and user has access - case Devices.get_device(device_id) do - nil -> + with {:ok, device} <- find_device(device_id), + :ok <- verify_device_access(device, organization) do + socket = initialize_graph_view(socket, device_id, sensor_type, params) + {:noreply, socket} + else + {:error, :not_found} -> {:noreply, socket |> put_flash(:error, "Device not found") |> push_navigate(to: ~p"/devices")} - device -> - # Check if device belongs to current organization - if device.organization_id == organization.id do - maybe_subscribe_to_device(socket, device_id) - - range = Map.get(params, "range", "24h") - interface_id = Map.get(params, "interface_id") - sensor_id = Map.get(params, "sensor_id") - storage_id = Map.get(params, "storage_id") - - socket = - socket - |> assign(:device_id, device_id) - |> assign(:sensor_type, sensor_type) - |> assign(:range, range) - |> assign(:interface_id, interface_id) - |> assign(:sensor_id, sensor_id) - |> assign(:storage_id, storage_id) - |> assign(:is_live_mode, range == "live") - |> maybe_start_live_polling() - |> load_graph_data() - - {:noreply, socket} - else - {:noreply, - socket - |> put_flash(:error, "You don't have access to this device") - |> push_navigate(to: ~p"/devices")} - end + {:error, :access_denied} -> + {:noreply, + socket + |> put_flash(:error, "You don't have access to this device") + |> push_navigate(to: ~p"/devices")} end end + defp find_device(device_id) do + case Devices.get_device(device_id) do + nil -> {:error, :not_found} + device -> {:ok, device} + end + end + + defp verify_device_access(device, organization) do + if device.organization_id == organization.id do + :ok + else + {:error, :access_denied} + end + end + + defp initialize_graph_view(socket, device_id, sensor_type, params) do + maybe_subscribe_to_device(socket, device_id) + + range = Map.get(params, "range", "24h") + interface_id = Map.get(params, "interface_id") + sensor_id = Map.get(params, "sensor_id") + storage_id = Map.get(params, "storage_id") + + # Check if device has agent assignment + has_agent = Agents.get_device_assignment(device_id) != nil + + socket + |> assign(:device_id, device_id) + |> assign(:sensor_type, sensor_type) + |> assign(:range, range) + |> assign(:interface_id, interface_id) + |> assign(:sensor_id, sensor_id) + |> assign(:storage_id, storage_id) + |> assign(:is_live_mode, range == "live") + |> assign(:has_agent_assignment, has_agent) + |> maybe_start_live_polling() + |> load_graph_data() + end + defp maybe_subscribe_to_device(socket, device_id) do _ = if connected?(socket) do @@ -644,12 +663,19 @@ defmodule ToweropsWeb.GraphLive.Show do # Poll sensors and update buffer defp poll_and_update_buffer(socket, device) do - client_opts = build_snmp_client_opts(device) now = DateTime.utc_now() timestamp_ms = DateTime.to_unix(now, :millisecond) - # Poll sensors based on graph type - new_points = poll_sensors_for_live_mode(socket.assigns, client_opts, timestamp_ms) + # Check if device has agent assignment + new_points = + if socket.assigns.has_agent_assignment do + # Use agent-collected data from database + fetch_latest_agent_data(socket.assigns, timestamp_ms) + else + # Do direct SNMP polling + client_opts = build_snmp_client_opts(device) + poll_sensors_for_live_mode(socket.assigns, client_opts, timestamp_ms) + end # Update buffer with new points (rolling window) {updated_buffer, point_count} = @@ -669,6 +695,29 @@ defmodule ToweropsWeb.GraphLive.Show do }) end + # Fetch latest sensor data collected by agent + defp fetch_latest_agent_data(assigns, timestamp_ms) do + sensors = get_sensors_for_live_mode(assigns) + + sensors + |> Enum.map(fn sensor -> + # Get the most recent sensor reading from the database + case Snmp.get_latest_sensor_reading(sensor.id) do + nil -> + nil + + reading -> + %{ + sensor_id: sensor.id, + label: sensor.sensor_descr, + value: Float.round(reading.value || 0.0, 1), + timestamp: timestamp_ms + } + end + end) + |> Enum.reject(&is_nil/1) + end + # Build SNMP client options defp build_snmp_client_opts(device) do snmp_config = Devices.get_snmp_config(device)