defmodule ToweropsWeb.NetworkMapLive do @moduledoc false use ToweropsWeb, :live_view alias Towerops.Snmp @impl true def mount(_params, _session, socket) do organization = socket.assigns.current_organization # Subscribe to topology changes if connected _ = if connected?(socket) do Phoenix.PubSub.subscribe(Towerops.PubSub, "topology:#{organization.id}") end # Default empty topology default_topology = %{ nodes: [], edges: [], subnets: [], stats: %{ total_devices: 0, added_devices: 0, discovered_devices: 0, total_links: 0, total_subnets: 0 }, last_updated: DateTime.utc_now() } {:ok, socket |> assign(:page_title, "Network Map") |> assign(:loading, true) |> assign(:topology, default_topology) |> assign(:active_tab, "added")} end @impl true def handle_params(params, _url, socket) do tab = Map.get(params, "tab", "added") organization = socket.assigns.current_organization {:noreply, socket |> assign(:active_tab, tab) |> load_topology_data(organization.id, tab)} end @impl true def handle_event("refresh_topology", _params, socket) do organization = socket.assigns.current_organization {:noreply, socket |> assign(:loading, true) |> load_topology_data(organization.id, socket.assigns.active_tab) |> put_flash(:info, "Network map refreshed")} end @impl true def handle_event("node_clicked", %{"node_id" => node_id}, socket) do # Handle node click - could navigate to device detail page {:noreply, assign(socket, :selected_node, node_id)} end @impl true def handle_info({:topology_updated, _organization_id}, socket) do # Real-time topology update {:noreply, load_topology_data(socket, socket.assigns.current_organization.id, socket.assigns.active_tab)} end defp load_topology_data(socket, organization_id, tab) do topology = Snmp.get_network_topology(organization_id) # Filter topology based on active tab filtered_topology = case tab do "all" -> topology _ -> # "added" tab - show only added devices %{ topology | nodes: Enum.filter(topology.nodes, &(!&1.discovered)), edges: filter_edges_for_nodes(topology.edges, topology.nodes, &(!&1.discovered)) } end socket |> assign(:topology, filtered_topology) |> assign(:loading, false) |> push_event("update_topology", %{topology: filtered_topology}) end # Filter edges to only include those where both source and target are in the filtered nodes defp filter_edges_for_nodes(edges, all_nodes, filter_fn) do filtered_node_ids = all_nodes |> Enum.filter(filter_fn) |> MapSet.new(& &1.id) Enum.filter(edges, fn edge -> MapSet.member?(filtered_node_ids, edge.source) and MapSet.member?(filtered_node_ids, edge.target) end) end end