From 2875866c07ec5ff07246ef0a99008e536bcc8f01 Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Wed, 11 Feb 2026 14:03:04 -0600 Subject: [PATCH] fix agent-polled devices stuck in unknown status MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit store_monitoring_check saved ping results from agents but never updated device status. DeviceMonitorWorker skips agent-assigned devices, so nothing ever moved them from :unknown to :up/:down. added update_device_status_from_check to derive status from check result, update the device, create alerts on transitions, and broadcast via PubSub — matching DeviceMonitorWorker behavior. --- CHANGELOG.txt | 11 ++++ lib/towerops_web/channels/agent_channel.ex | 70 ++++++++++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index cdaa3286..4c94d41b 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,6 +1,17 @@ CHANGELOG - towerops-web ======================== +2026-02-11 - fix: update device status from agent monitoring checks + - File: lib/towerops_web/channels/agent_channel.ex (store_monitoring_check/2) + - Bug: Devices polled by remote agents stayed in "unknown" state because + store_monitoring_check saved the ping result but never called + Devices.update_device_status/2. The DeviceMonitorWorker (Phoenix-side) + skips agent-assigned devices entirely, so nothing ever updated status. + - Fix: Added update_device_status_from_check/2 and handle_agent_status_change/3 + to derive :up/:down from check status, update device, create alerts on + transitions, and broadcast via PubSub — matching DeviceMonitorWorker behavior + - Also added alias Towerops.Alerts to the channel module + 2026-02-11 - fix: update last_snmp_poll_at for agent-polled devices - File: lib/towerops_web/channels/agent_channel.ex (process_polling_result/3) - Bug: Devices polled by remote agents showed "Last Polled: Never" because diff --git a/lib/towerops_web/channels/agent_channel.ex b/lib/towerops_web/channels/agent_channel.ex index 7ac694f7..116504f5 100644 --- a/lib/towerops_web/channels/agent_channel.ex +++ b/lib/towerops_web/channels/agent_channel.ex @@ -30,6 +30,7 @@ defmodule ToweropsWeb.AgentChannel do alias Towerops.Agent.SnmpQuery alias Towerops.Agent.Validator alias Towerops.Agents + alias Towerops.Alerts alias Towerops.Devices alias Towerops.Devices.BackupRequests alias Towerops.Devices.MikrotikBackups @@ -1163,6 +1164,7 @@ defmodule ToweropsWeb.AgentChannel do case Monitoring.create_check(attrs) do {:ok, _check} -> + update_device_status_from_check(device, status) :ok {:error, changeset} -> @@ -1184,6 +1186,74 @@ defmodule ToweropsWeb.AgentChannel do end end + defp update_device_status_from_check(device, check_status) do + new_status = if check_status in [:success, :up, :ok], do: :up, else: :down + old_status = device.status + + Devices.update_device_status(device, new_status) + + if old_status != new_status do + handle_agent_status_change(device, old_status, new_status) + end + + Phoenix.PubSub.broadcast( + Towerops.PubSub, + "device:#{device.id}", + {:device_status_changed, device.id, new_status, nil} + ) + end + + defp handle_agent_status_change(device, _old_status, :down) do + if !Alerts.has_active_alert?(device.id, :device_down) do + now = DateTime.truncate(DateTime.utc_now(), :second) + + message = + if device.snmp_enabled, + do: "Device is not responding to SNMP", + else: "Device is not responding to ping" + + Alerts.create_alert(%{ + device_id: device.id, + alert_type: :device_down, + triggered_at: now, + message: message + }) + + Phoenix.PubSub.broadcast( + Towerops.PubSub, + "alerts:new", + {:new_alert, device.id, :device_down} + ) + end + end + + defp handle_agent_status_change(device, _old_status, :up) do + now = DateTime.truncate(DateTime.utc_now(), :second) + + message = + if device.snmp_enabled, + do: "Device is now responding to SNMP", + else: "Device is now responding to ping" + + Alerts.create_alert(%{ + device_id: device.id, + alert_type: :device_up, + triggered_at: now, + message: message + }) + + case Alerts.get_active_alert(device.id, :device_down) do + nil -> :ok + alert -> Alerts.resolve_alert(alert) + end + + Phoenix.PubSub.broadcast( + Towerops.PubSub, + "alerts:resolved", + {:alert_resolved, device.id, :device_down} + ) + end + defp fetch_device(device_id) do case Devices.get_device_with_details(device_id) do nil -> {:error, :device_not_found}