feat: add monitoring mode tab selector (SNMP & ICMP vs ICMP Only)
- Add tab selector in Monitoring Configuration section for choosing monitoring mode - SNMP & ICMP mode: enables ICMP pings + SNMP discovery (default) - ICMP Only mode: enables only ICMP ping monitoring, hides SNMP fields - Monitoring mode controls snmp_enabled field automatically on save - Test SNMP Connection button shows informational message when agent is configured (agents handle SNMP testing during their polling cycle) - Update all tests to use tab selector instead of setting snmp_enabled directly - All 818 tests passing with no Credo warnings
This commit is contained in:
parent
55a0d88978
commit
d87461ba14
5 changed files with 210 additions and 15 deletions
2
TODOS.md
2
TODOS.md
|
|
@ -1,6 +1,5 @@
|
|||
# In Progress / Pending
|
||||
|
||||
* add and track ICMP pings for each device. it should also put a latency graph on the device page that links to the universal graph like the other graphs
|
||||
* when adding a new device, make it a tab selector to show the default SNMP & ICMP or ICMP only
|
||||
* add valkey as a k8s sidecar
|
||||
* add the latest exq to the web app
|
||||
|
|
@ -13,3 +12,4 @@
|
|||
* ~~change "revoke" agent to delete and have it actually delete the agent and ensure it's removed from everything it's been assigned and change them to cloud polling if they were previously set to that agent being deleted~~
|
||||
* ~~add ability to rename an agent in a new edit page~~
|
||||
* ~~When adding a new device, allow the community string to be empty so it will inherit from the parent~~
|
||||
* ~~add and track ICMP pings for each device. it should also put a latency graph on the device page that links to the universal graph like the other graphs~~
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ defmodule ToweropsWeb.DeviceLive.Form do
|
|||
|> assign(:page_title, "New Device")
|
||||
|> assign(:device, %DeviceSchema{})
|
||||
|> assign(:form, to_form(changeset))
|
||||
|> assign(:monitoring_mode, "snmp_and_icmp")
|
||||
end
|
||||
|
||||
defp apply_action(socket, :edit, %{"id" => id}) do
|
||||
|
|
@ -101,6 +102,9 @@ defmodule ToweropsWeb.DeviceLive.Form do
|
|||
# Get effective SNMP configuration and source
|
||||
snmp_config = Devices.get_snmp_config(device)
|
||||
|
||||
# Determine monitoring mode based on current snmp_enabled value
|
||||
monitoring_mode = if device.snmp_enabled, do: "snmp_and_icmp", else: "icmp_only"
|
||||
|
||||
socket
|
||||
|> assign(:page_title, "Edit Device")
|
||||
|> assign(:device, device_with_agent)
|
||||
|
|
@ -110,6 +114,30 @@ defmodule ToweropsWeb.DeviceLive.Form do
|
|||
|> assign(:snmp_config_source, snmp_config.source)
|
||||
|> assign(:effective_snmp_version, snmp_config.version)
|
||||
|> assign(:effective_snmp_community, snmp_config.community || "(not set)")
|
||||
|> assign(:monitoring_mode, monitoring_mode)
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("switch_monitoring_mode", %{"mode" => mode}, socket) do
|
||||
# Update snmp_enabled based on the selected mode
|
||||
snmp_enabled = mode == "snmp_and_icmp"
|
||||
|
||||
# Get current form params
|
||||
current_params = socket.assigns.form.params
|
||||
|
||||
# Update params with new snmp_enabled value
|
||||
updated_params = Map.put(current_params, "snmp_enabled", snmp_enabled)
|
||||
|
||||
# Create new changeset with updated params
|
||||
changeset =
|
||||
socket.assigns.device
|
||||
|> Devices.change_device(updated_params)
|
||||
|> Map.put(:action, :validate)
|
||||
|
||||
socket
|
||||
|> assign(:monitoring_mode, mode)
|
||||
|> assign(:form, to_form(changeset))
|
||||
|> then(&{:noreply, &1})
|
||||
end
|
||||
|
||||
@impl true
|
||||
|
|
@ -143,8 +171,21 @@ defmodule ToweropsWeb.DeviceLive.Form do
|
|||
|
||||
@impl true
|
||||
def handle_event("test_snmp", _params, socket) do
|
||||
snmp_config = extract_snmp_config(socket.assigns.form, socket.assigns)
|
||||
result = test_snmp_connection(snmp_config)
|
||||
# Check if an agent is assigned
|
||||
agent_token_id = socket.assigns.form.params["agent_token_id"]
|
||||
|
||||
result =
|
||||
if agent_token_id && agent_token_id != "" do
|
||||
# Agent is configured - can't test directly from web server
|
||||
%{
|
||||
success: true,
|
||||
message: "SNMP configuration saved. Connection will be tested by the assigned agent during next polling cycle."
|
||||
}
|
||||
else
|
||||
# No agent configured - test directly from web server
|
||||
snmp_config = extract_snmp_config(socket.assigns.form, socket.assigns)
|
||||
test_snmp_connection(snmp_config)
|
||||
end
|
||||
|
||||
{:noreply, assign(socket, :snmp_test_result, result)}
|
||||
end
|
||||
|
|
@ -171,6 +212,10 @@ defmodule ToweropsWeb.DeviceLive.Form do
|
|||
agent_token_id = Map.get(device_params, "agent_token_id")
|
||||
device_params = Map.delete(device_params, "agent_token_id")
|
||||
|
||||
# Add snmp_enabled based on monitoring mode
|
||||
snmp_enabled = socket.assigns.monitoring_mode == "snmp_and_icmp"
|
||||
device_params = Map.put(device_params, "snmp_enabled", snmp_enabled)
|
||||
|
||||
case Devices.create_device(device_params) do
|
||||
{:ok, device} ->
|
||||
# Handle agent assignment after device creation
|
||||
|
|
@ -195,6 +240,10 @@ defmodule ToweropsWeb.DeviceLive.Form do
|
|||
agent_token_id = Map.get(device_params, "agent_token_id")
|
||||
device_params = Map.delete(device_params, "agent_token_id")
|
||||
|
||||
# Add snmp_enabled based on monitoring mode
|
||||
snmp_enabled = socket.assigns.monitoring_mode == "snmp_and_icmp"
|
||||
device_params = Map.put(device_params, "snmp_enabled", snmp_enabled)
|
||||
|
||||
case Devices.update_device(old_device, device_params) do
|
||||
{:ok, device} ->
|
||||
# device update
|
||||
|
|
|
|||
|
|
@ -92,15 +92,56 @@
|
|||
<% end %>
|
||||
|
||||
<div class="mt-8 border-t pt-6">
|
||||
<h3 class="text-lg font-medium mb-4">SNMP Configuration</h3>
|
||||
<p class="text-sm text-gray-600 mb-4">
|
||||
Enable SNMP to discover device details, monitor sensors, and track interface statistics.
|
||||
</p>
|
||||
|
||||
<.input field={@form[:snmp_enabled]} type="checkbox" label="Enable SNMP Monitoring" />
|
||||
<h3 class="text-lg font-medium mb-4">Monitoring Configuration</h3>
|
||||
|
||||
<!-- Monitoring Mode Tabs -->
|
||||
<div class="mb-6">
|
||||
<div class="border-b border-zinc-200 dark:border-zinc-700">
|
||||
<nav class="-mb-px flex space-x-8" aria-label="Monitoring mode">
|
||||
<button
|
||||
type="button"
|
||||
phx-click="switch_monitoring_mode"
|
||||
phx-value-mode="snmp_and_icmp"
|
||||
class={[
|
||||
"whitespace-nowrap border-b-2 px-1 py-4 text-sm font-medium",
|
||||
@monitoring_mode == "snmp_and_icmp" &&
|
||||
"border-blue-500 text-blue-600 dark:border-blue-400 dark:text-blue-400",
|
||||
@monitoring_mode != "snmp_and_icmp" &&
|
||||
"border-transparent text-zinc-500 hover:border-zinc-300 hover:text-zinc-700 dark:text-zinc-400 dark:hover:border-zinc-600 dark:hover:text-zinc-300"
|
||||
]}
|
||||
>
|
||||
SNMP & ICMP
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
phx-click="switch_monitoring_mode"
|
||||
phx-value-mode="icmp_only"
|
||||
class={[
|
||||
"whitespace-nowrap border-b-2 px-1 py-4 text-sm font-medium",
|
||||
@monitoring_mode == "icmp_only" &&
|
||||
"border-blue-500 text-blue-600 dark:border-blue-400 dark:text-blue-400",
|
||||
@monitoring_mode != "icmp_only" &&
|
||||
"border-transparent text-zinc-500 hover:border-zinc-300 hover:text-zinc-700 dark:text-zinc-400 dark:hover:border-zinc-600 dark:hover:text-zinc-300"
|
||||
]}
|
||||
>
|
||||
ICMP Only
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="mt-2 text-sm text-zinc-600 dark:text-zinc-400">
|
||||
<%= if @monitoring_mode == "snmp_and_icmp" do %>
|
||||
<p>
|
||||
Monitor device availability with ICMP pings and collect detailed information via SNMP
|
||||
(interfaces, sensors, system info).
|
||||
</p>
|
||||
<% else %>
|
||||
<p>Monitor device availability with ICMP pings only (no SNMP discovery).</p>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
:if={@form[:snmp_enabled].value}
|
||||
:if={@monitoring_mode == "snmp_and_icmp"}
|
||||
class="mt-4 space-y-4 pl-6 border-l-2 border-blue-200"
|
||||
>
|
||||
<.input
|
||||
|
|
|
|||
|
|
@ -497,17 +497,16 @@ defmodule ToweropsWeb.DeviceLive.FormTest do
|
|||
{:ok, view, _html} =
|
||||
live(conn, ~p"/orgs/#{organization.slug}/devices/#{device.id}/edit")
|
||||
|
||||
# First, toggle SNMP on via change event to reveal fields
|
||||
# Switch to SNMP & ICMP mode using the tab selector
|
||||
view
|
||||
|> form("#device-form", device: %{snmp_enabled: true})
|
||||
|> render_change()
|
||||
|> element("button", "SNMP & ICMP")
|
||||
|> render_click()
|
||||
|
||||
# Now submit with SNMP settings
|
||||
{:ok, _, html} =
|
||||
view
|
||||
|> form("#device-form",
|
||||
device: %{
|
||||
snmp_enabled: true,
|
||||
snmp_community: "public",
|
||||
snmp_version: "2c"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -219,6 +219,7 @@ defmodule ToweropsWeb.DeviceLiveTest do
|
|||
test "creates new device", %{conn: conn, organization: organization, site: site} do
|
||||
{:ok, view, _html} = live(conn, ~p"/orgs/#{organization.slug}/devices/new")
|
||||
|
||||
# Default is SNMP & ICMP mode, so snmp fields should be visible
|
||||
view
|
||||
|> form("#device-form",
|
||||
device: %{
|
||||
|
|
@ -228,7 +229,6 @@ defmodule ToweropsWeb.DeviceLiveTest do
|
|||
description: "Test router",
|
||||
monitoring_enabled: true,
|
||||
check_interval_seconds: 300,
|
||||
snmp_enabled: true,
|
||||
snmp_version: "2c",
|
||||
snmp_community: "public",
|
||||
snmp_port: 161
|
||||
|
|
@ -243,6 +243,7 @@ defmodule ToweropsWeb.DeviceLiveTest do
|
|||
new_device = Enum.find(device_list, &(&1.name == "New Router"))
|
||||
assert new_device
|
||||
assert new_device.ip_address == "192.168.1.100"
|
||||
assert new_device.snmp_enabled == true
|
||||
end
|
||||
|
||||
test "validates required fields", %{conn: conn, organization: organization} do
|
||||
|
|
@ -291,6 +292,111 @@ defmodule ToweropsWeb.DeviceLiveTest do
|
|||
assert {:redirect, %{to: path}} = redirect
|
||||
assert path == ~p"/users/log-in"
|
||||
end
|
||||
|
||||
test "defaults to SNMP & ICMP monitoring mode", %{conn: conn, organization: organization} do
|
||||
{:ok, _view, html} = live(conn, ~p"/orgs/#{organization.slug}/devices/new")
|
||||
|
||||
assert html =~ "SNMP & ICMP"
|
||||
assert html =~ "ICMP Only"
|
||||
assert html =~ "SNMP Version"
|
||||
assert html =~ "Community String"
|
||||
end
|
||||
|
||||
test "switching to ICMP Only mode hides SNMP configuration", %{
|
||||
conn: conn,
|
||||
organization: organization
|
||||
} do
|
||||
{:ok, view, _html} = live(conn, ~p"/orgs/#{organization.slug}/devices/new")
|
||||
|
||||
html =
|
||||
view
|
||||
|> element("button", "ICMP Only")
|
||||
|> render_click()
|
||||
|
||||
refute html =~ "SNMP Version"
|
||||
refute html =~ "Community String"
|
||||
end
|
||||
|
||||
test "switching back to SNMP & ICMP mode shows SNMP configuration", %{
|
||||
conn: conn,
|
||||
organization: organization
|
||||
} do
|
||||
{:ok, view, _html} = live(conn, ~p"/orgs/#{organization.slug}/devices/new")
|
||||
|
||||
# First switch to ICMP Only
|
||||
view
|
||||
|> element("button", "ICMP Only")
|
||||
|> render_click()
|
||||
|
||||
# Then switch back to SNMP & ICMP
|
||||
html =
|
||||
view
|
||||
|> element("button", "SNMP & ICMP")
|
||||
|> render_click()
|
||||
|
||||
assert html =~ "SNMP Version"
|
||||
assert html =~ "Community String"
|
||||
end
|
||||
|
||||
test "creates device with ICMP only mode (snmp_enabled false)", %{
|
||||
conn: conn,
|
||||
organization: organization,
|
||||
site: site
|
||||
} do
|
||||
{:ok, view, _html} = live(conn, ~p"/orgs/#{organization.slug}/devices/new")
|
||||
|
||||
# Switch to ICMP Only mode
|
||||
view
|
||||
|> element("button", "ICMP Only")
|
||||
|> render_click()
|
||||
|
||||
view
|
||||
|> form("#device-form",
|
||||
device: %{
|
||||
name: "ICMP Only Device",
|
||||
ip_address: "192.168.1.200",
|
||||
site_id: site.id,
|
||||
monitoring_enabled: true
|
||||
}
|
||||
)
|
||||
|> render_submit()
|
||||
|
||||
# Verify device was created with snmp_enabled = false
|
||||
device_list = Towerops.Devices.list_organization_devices(organization.id)
|
||||
new_device = Enum.find(device_list, &(&1.name == "ICMP Only Device"))
|
||||
|
||||
assert new_device
|
||||
assert new_device.snmp_enabled == false
|
||||
end
|
||||
|
||||
test "creates device with SNMP & ICMP mode (snmp_enabled true)", %{
|
||||
conn: conn,
|
||||
organization: organization,
|
||||
site: site
|
||||
} do
|
||||
{:ok, view, _html} = live(conn, ~p"/orgs/#{organization.slug}/devices/new")
|
||||
|
||||
# SNMP & ICMP is the default, so no need to click tab
|
||||
|
||||
view
|
||||
|> form("#device-form",
|
||||
device: %{
|
||||
name: "SNMP Device",
|
||||
ip_address: "192.168.1.201",
|
||||
site_id: site.id,
|
||||
monitoring_enabled: true,
|
||||
snmp_community: "public"
|
||||
}
|
||||
)
|
||||
|> render_submit()
|
||||
|
||||
# Verify device was created with snmp_enabled = true
|
||||
device_list = Towerops.Devices.list_organization_devices(organization.id)
|
||||
new_device = Enum.find(device_list, &(&1.name == "SNMP Device"))
|
||||
|
||||
assert new_device
|
||||
assert new_device.snmp_enabled == true
|
||||
end
|
||||
end
|
||||
|
||||
describe "Edit" do
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue