fix agent-polled devices stuck in unknown status

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.
This commit is contained in:
Graham McIntire 2026-02-11 14:03:04 -06:00
parent cb3f2edf50
commit 2875866c07
No known key found for this signature in database
2 changed files with 81 additions and 0 deletions

View file

@ -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

View file

@ -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}