From 8e739283ad36886a52abb70bf15e099fb386ccb3 Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Thu, 29 Jan 2026 10:03:43 -0600 Subject: [PATCH] refactoring --- lib/mix/tasks/geoip.import.ex | 91 +++++++++++-------- lib/towerops/accounts/scope.ex | 28 +++++- lib/towerops/application.ex | 4 +- lib/towerops/snmp/agent_discovery.ex | 47 ++++++---- lib/towerops/snmp/client.ex | 57 ++++++------ lib/towerops/snmp/mib_translator.ex | 4 +- lib/towerops_web/components/layouts.ex | 19 ++-- .../controllers/page_html/privacy.html.heex | 1 - .../controllers/page_html/terms.html.heex | 1 - .../user_settings_html/edit.html.heex | 2 +- lib/towerops_web/live/account_live/my_data.ex | 9 +- lib/towerops_web/live/agent_live/edit.ex | 2 +- lib/towerops_web/live/agent_live/index.ex | 10 +- .../live/agent_live/index.html.heex | 2 - lib/towerops_web/live/agent_live/show.ex | 2 +- .../live/agent_live/show.html.heex | 12 +-- lib/towerops_web/live/alert_live/index.ex | 34 ++++--- .../live/alert_live/index.html.heex | 2 - lib/towerops_web/live/dashboard_live.ex | 6 +- .../live/dashboard_live.html.heex | 5 +- lib/towerops_web/live/device_live/form.ex | 4 +- .../live/device_live/form.html.heex | 2 - lib/towerops_web/live/device_live/index.ex | 90 +++++++++--------- .../live/device_live/index.html.heex | 3 - lib/towerops_web/live/device_live/show.ex | 17 ++-- .../live/device_live/show.html.heex | 2 - lib/towerops_web/live/graph_live/show.ex | 15 ++- .../live/graph_live/show.html.heex | 2 - lib/towerops_web/live/help_live/index.ex | 9 +- .../live/help_live/index.html.heex | 2 - lib/towerops_web/live/network_map_live.ex | 8 +- .../live/network_map_live.html.heex | 2 - lib/towerops_web/live/org/settings_live.ex | 2 +- .../live/org/settings_live.html.heex | 1 - .../live/org_live/index.html.heex | 2 - lib/towerops_web/live/org_live/new.html.heex | 2 - lib/towerops_web/live/site_live/form.ex | 2 +- .../live/site_live/form.html.heex | 2 - lib/towerops_web/live/site_live/index.ex | 2 +- .../live/site_live/index.html.heex | 2 - lib/towerops_web/live/site_live/show.ex | 2 +- .../live/site_live/show.html.heex | 2 - lib/towerops_web/live/user_settings_live.ex | 2 - lib/towerops_web/user_auth.ex | 36 ++++++-- test/snmpkit/snmp_mgr/mib_extraction_test.exs | 4 +- test/snmpkit/snmp_mgr/mib_test.exs | 4 +- test/towerops_web/user_auth_test.exs | 8 +- 47 files changed, 311 insertions(+), 256 deletions(-) diff --git a/lib/mix/tasks/geoip.import.ex b/lib/mix/tasks/geoip.import.ex index bd62f755..d6aa1b84 100644 --- a/lib/mix/tasks/geoip.import.ex +++ b/lib/mix/tasks/geoip.import.ex @@ -56,18 +56,7 @@ defmodule Mix.Tasks.Geoip.Import do case remaining_args do [directory] -> if opts[:production] do - # Production: Send batches to API - api_key = System.get_env("TOWEROPS_KEY") - - if !api_key do - Mix.shell().error("Error: TOWEROPS_KEY environment variable must be set for production import") - exit({:shutdown, 1}) - end - - Mix.Task.run("app.config") - {:ok, _} = Application.ensure_all_started(:req) - - import_via_api(directory, api_key) + run_production_import(directory) else # Development: Direct database import Mix.Task.run("app.start") @@ -75,19 +64,38 @@ defmodule Mix.Tasks.Geoip.Import do end _ -> - Mix.shell().error(""" - Usage: mix geoip.import [--production] - - Examples: - # Import to local database (development) - mix geoip.import ~/Downloads/GeoLite2-City-CSV_20260127/ - - # Import to production via API (requires TOWEROPS_KEY env var) - mix geoip.import ~/Downloads/GeoLite2-City-CSV_20260127/ --production - """) + show_usage_help() end end + defp run_production_import(directory) do + # Production: Send batches to API + api_key = System.get_env("TOWEROPS_KEY") + + if !api_key do + Mix.shell().error("Error: TOWEROPS_KEY environment variable must be set for production import") + exit({:shutdown, 1}) + end + + Mix.Task.run("app.config") + {:ok, _} = Application.ensure_all_started(:req) + + import_via_api(directory, api_key) + end + + defp show_usage_help do + Mix.shell().error(""" + Usage: mix geoip.import [--production] + + Examples: + # Import to local database (development) + mix geoip.import ~/Downloads/GeoLite2-City-CSV_20260127/ + + # Import to production via API (requires TOWEROPS_KEY env var) + mix geoip.import ~/Downloads/GeoLite2-City-CSV_20260127/ --production + """) + end + defp parse_args(args) do {opts, remaining, _invalid} = OptionParser.parse(args, @@ -263,7 +271,9 @@ defmodule Mix.Tasks.Geoip.Import do end defp parse_location_line(line) do - # CSV format: geoname_id,locale_code,continent_code,continent_name,country_iso_code,country_name,subdivision_1_iso_code,subdivision_1_name,subdivision_2_iso_code,subdivision_2_name,city_name,metro_code,time_zone,is_in_european_union + # CSV format: geoname_id,locale_code,continent_code,continent_name,country_iso_code,country_name, + # subdivision_1_iso_code,subdivision_1_name,subdivision_2_iso_code,subdivision_2_name, + # city_name,metro_code,time_zone,is_in_european_union parts = String.split(line, ",") case parts do @@ -332,30 +342,33 @@ defmodule Mix.Tasks.Geoip.Import do # Try geoname_id first, fall back to registered_country_id geoname_id_int = parse_geoname_id(geoname_id) registered_id_int = parse_geoname_id(registered_country_id) - final_geoname_id = geoname_id_int || registered_id_int - if final_geoname_id do - case parse_cidr(network) do - {:ok, start_int, end_int} -> - %{ - network: network, - start_ip_int: start_int, - end_ip_int: end_int, - geoname_id: final_geoname_id, - registered_country_geoname_id: registered_id_int - } - - :error -> - nil - end - end + build_block_entry(network, final_geoname_id, registered_id_int) _ -> nil end end + defp build_block_entry(_network, nil, _registered_id_int), do: nil + + defp build_block_entry(network, final_geoname_id, registered_id_int) do + case parse_cidr(network) do + {:ok, start_int, end_int} -> + %{ + network: network, + start_ip_int: start_int, + end_ip_int: end_int, + geoname_id: final_geoname_id, + registered_country_geoname_id: registered_id_int + } + + :error -> + nil + end + end + defp parse_geoname_id(""), do: nil defp parse_geoname_id(geoname_id_str) do diff --git a/lib/towerops/accounts/scope.ex b/lib/towerops/accounts/scope.ex index 998530d1..63203e31 100644 --- a/lib/towerops/accounts/scope.ex +++ b/lib/towerops/accounts/scope.ex @@ -17,8 +17,9 @@ defmodule Towerops.Accounts.Scope do """ alias Towerops.Accounts.User + alias Towerops.Organizations.Organization - defstruct user: nil, superuser: nil, impersonating?: false + defstruct user: nil, superuser: nil, impersonating?: false, organization: nil, timezone: "UTC" @doc """ Creates a scope for the given user. @@ -26,7 +27,12 @@ defmodule Towerops.Accounts.Scope do Returns nil if no user is given. """ def for_user(%User{} = user) do - %__MODULE__{user: user, superuser: nil, impersonating?: false} + %__MODULE__{ + user: user, + superuser: nil, + impersonating?: false, + timezone: user.timezone || "UTC" + } end def for_user(nil), do: nil @@ -41,10 +47,26 @@ defmodule Towerops.Accounts.Scope do %__MODULE__{ user: target_user, superuser: superuser, - impersonating?: true + impersonating?: true, + timezone: target_user.timezone || "UTC" } end + @doc """ + Adds organization context to an existing scope. + + Returns updated scope with organization field set. + """ + def put_organization(%__MODULE__{} = scope, %Organization{} = organization) do + %{scope | organization: organization} + end + + def put_organization(%__MODULE__{} = scope, nil) do + %{scope | organization: nil} + end + + def put_organization(nil, _organization), do: nil + @doc """ Returns true if the scope has superuser privileges. diff --git a/lib/towerops/application.ex b/lib/towerops/application.ex index d80a6486..7ead7f76 100644 --- a/lib/towerops/application.ex +++ b/lib/towerops/application.ex @@ -7,6 +7,8 @@ defmodule Towerops.Application do use Application + alias Towerops.Snmp.MibLoader + # Capture the build timestamp at compile time (fallback for development) @build_timestamp DateTime.utc_now() @@ -46,7 +48,7 @@ defmodule Towerops.Application do SnmpKit.SnmpMgr.Config, SnmpKit.SnmpMgr.MIB, # Load vendor MIBs after SnmpKit.SnmpMgr.MIB starts - {Task, fn -> Towerops.Snmp.MibLoader.load_all_mibs() end} + {Task, fn -> MibLoader.load_all_mibs() end} ] ++ dev_only_workers() ++ background_workers() ++ diff --git a/lib/towerops/snmp/agent_discovery.ex b/lib/towerops/snmp/agent_discovery.ex index 6bf88fa9..4ea6a77e 100644 --- a/lib/towerops/snmp/agent_discovery.ex +++ b/lib/towerops/snmp/agent_discovery.ex @@ -85,30 +85,37 @@ defmodule Towerops.Snmp.AgentDiscovery do # Run discovery using standard pipeline # This will call all the same functions as direct discovery, # but read from the OID map instead of making SNMP queries - case Discovery.discover_device_with_opts(device, client_opts) do - {:ok, discovered_device} -> - interfaces_count = - case discovered_device.interfaces do - %NotLoaded{} -> 0 - list when is_list(list) -> length(list) - _ -> 0 - end + perform_agent_discovery(device, client_opts) + end + end - Logger.debug("Agent discovery succeeded for #{device.name}", - device_id: device.id, - interfaces: interfaces_count, - sensors: count_sensors(discovered_device) - ) + defp perform_agent_discovery(device, client_opts) do + case Discovery.discover_device_with_opts(device, client_opts) do + {:ok, discovered_device} -> + interfaces_count = count_interfaces(discovered_device) - {:ok, discovered_device} + Logger.debug("Agent discovery succeeded for #{device.name}", + device_id: device.id, + interfaces: interfaces_count, + sensors: count_sensors(discovered_device) + ) - {:error, reason} = error -> - Logger.error("Agent discovery failed for #{device.name}: #{inspect(reason)}", - device_id: device.id - ) + {:ok, discovered_device} - error - end + {:error, reason} = error -> + Logger.error("Agent discovery failed for #{device.name}: #{inspect(reason)}", + device_id: device.id + ) + + error + end + end + + defp count_interfaces(discovered_device) do + case discovered_device.interfaces do + %NotLoaded{} -> 0 + list when is_list(list) -> length(list) + _ -> 0 end end diff --git a/lib/towerops/snmp/client.ex b/lib/towerops/snmp/client.ex index 5c58bac9..9c598409 100644 --- a/lib/towerops/snmp/client.ex +++ b/lib/towerops/snmp/client.ex @@ -343,38 +343,41 @@ defmodule Towerops.Snmp.Client do if String.starts_with?(oid, ".") or String.match?(oid, ~r/^\d+(\.\d+)*$/) do oid else - # Handle MODULE-NAME::objectName format (strip module prefix) - object_name = - if String.contains?(oid, "::") do - oid |> String.split("::") |> List.last() - else - oid - end - - # Try to resolve MIB name to numeric OID - case SnmpKit.resolve(object_name) do - {:ok, numeric_oid} -> - # Convert OID list to dotted string if needed - resolved_oid = - if is_list(numeric_oid) do - Enum.join(numeric_oid, ".") - else - numeric_oid - end - - Logger.debug("Resolved MIB name #{oid} to #{resolved_oid}") - resolved_oid - - {:error, reason} -> - # If resolution fails, log and use as-is - Logger.debug("Could not resolve MIB name #{oid}: #{inspect(reason)}, using as-is") - oid - end + resolve_symbolic_oid(oid) end end def resolve_mib_name(oid), do: oid + defp resolve_symbolic_oid(oid) do + # Handle MODULE-NAME::objectName format (strip module prefix) + object_name = + if String.contains?(oid, "::") do + oid |> String.split("::") |> List.last() + else + oid + end + + # Try to resolve MIB name to numeric OID + case SnmpKit.resolve(object_name) do + {:ok, numeric_oid} -> + resolved_oid = format_resolved_oid(numeric_oid) + Logger.debug("Resolved MIB name #{oid} to #{resolved_oid}") + resolved_oid + + {:error, reason} -> + # If resolution fails, log and use as-is + Logger.debug("Could not resolve MIB name #{oid}: #{inspect(reason)}, using as-is") + oid + end + end + + defp format_resolved_oid(numeric_oid) when is_list(numeric_oid) do + Enum.join(numeric_oid, ".") + end + + defp format_resolved_oid(numeric_oid), do: numeric_oid + # Extract value from snmpkit's typed responses defp extract_snmp_value({:octet_string, value}), do: value defp extract_snmp_value({:integer, value}), do: value diff --git a/lib/towerops/snmp/mib_translator.ex b/lib/towerops/snmp/mib_translator.ex index bc48dbad..a09af0bc 100644 --- a/lib/towerops/snmp/mib_translator.ex +++ b/lib/towerops/snmp/mib_translator.ex @@ -11,6 +11,8 @@ defmodule Towerops.Snmp.MibTranslator do Requires net-snmp package to be installed for best results, but works without it. """ + alias SnmpKit.SnmpMgr.MIB + require Logger # Fallback OID registry for when MIB translation fails @@ -438,7 +440,7 @@ defmodule Towerops.Snmp.MibTranslator do |> String.split(".") |> List.first() - case SnmpKit.SnmpMgr.MIB.resolve(symbol_name) do + case MIB.resolve(symbol_name) do {:ok, oid_list} -> # Extract instance suffix (e.g., ".0" from "afLTUFirmwareVersion.0") suffix = diff --git a/lib/towerops_web/components/layouts.ex b/lib/towerops_web/components/layouts.ex index 476f03c9..8e3209b9 100644 --- a/lib/towerops_web/components/layouts.ex +++ b/lib/towerops_web/components/layouts.ex @@ -97,7 +97,7 @@ defmodule ToweropsWeb.Layouts do ## Examples - +

Content

@@ -106,18 +106,12 @@ defmodule ToweropsWeb.Layouts do attr :current_scope, :map, default: nil, - doc: "the current scope (contains user and impersonation state)" - - attr :current_organization, :any, - default: nil, - doc: "the current organization (can be nil for pages without org context)" + doc: "the current scope (contains user, organization, timezone, and impersonation state)" attr :active_page, :string, default: nil, doc: "the currently active page (dashboard, sites, device, or alerts)" - attr :timezone, :string, default: "UTC", doc: "the user's timezone" - slot :inner_block, required: true def authenticated(assigns) do @@ -125,6 +119,15 @@ defmodule ToweropsWeb.Layouts do requires_cookie_consent = Process.get(:requires_cookie_consent, false) assigns = Map.put(assigns, :requires_cookie_consent, requires_cookie_consent) + # Extract organization and timezone from scope for template convenience + current_organization = assigns[:current_scope] && assigns.current_scope.organization + timezone = (assigns[:current_scope] && assigns.current_scope.timezone) || "UTC" + + assigns = + assigns + |> Map.put(:current_organization, current_organization) + |> Map.put(:timezone, timezone) + ~H"""
diff --git a/lib/towerops_web/controllers/page_html/privacy.html.heex b/lib/towerops_web/controllers/page_html/privacy.html.heex index e26c14ce..30ddeb4b 100644 --- a/lib/towerops_web/controllers/page_html/privacy.html.heex +++ b/lib/towerops_web/controllers/page_html/privacy.html.heex @@ -119,7 +119,6 @@ {content.()} diff --git a/lib/towerops_web/controllers/page_html/terms.html.heex b/lib/towerops_web/controllers/page_html/terms.html.heex index 90ae39d6..16bf7427 100644 --- a/lib/towerops_web/controllers/page_html/terms.html.heex +++ b/lib/towerops_web/controllers/page_html/terms.html.heex @@ -396,7 +396,6 @@ {content.()} diff --git a/lib/towerops_web/controllers/user_settings_html/edit.html.heex b/lib/towerops_web/controllers/user_settings_html/edit.html.heex index a0541326..3f052486 100644 --- a/lib/towerops_web/controllers/user_settings_html/edit.html.heex +++ b/lib/towerops_web/controllers/user_settings_html/edit.html.heex @@ -1,4 +1,4 @@ - +
<.header> Account Settings diff --git a/lib/towerops_web/live/account_live/my_data.ex b/lib/towerops_web/live/account_live/my_data.ex index d6ca6cde..3b5a5d7e 100644 --- a/lib/towerops_web/live/account_live/my_data.ex +++ b/lib/towerops_web/live/account_live/my_data.ex @@ -5,6 +5,7 @@ defmodule ToweropsWeb.AccountLive.MyData do use ToweropsWeb, :live_view alias Towerops.Accounts + alias Towerops.Accounts.Scope alias Towerops.Admin alias Towerops.Alerts alias Towerops.Devices @@ -29,12 +30,15 @@ defmodule ToweropsWeb.AccountLive.MyData do audit_logs: get_user_audit_logs(user) } + # Update scope with organization + scope = Scope.put_organization(socket.assigns.current_scope, default_organization) + {:ok, socket + |> assign(:current_scope, scope) |> assign(:user_data, user_data) |> assign(:page_title, "My Data") - |> assign(:is_owner, is_owner) - |> assign(:current_organization, default_organization)} + |> assign(:is_owner, is_owner)} end @impl true @@ -43,7 +47,6 @@ defmodule ToweropsWeb.AccountLive.MyData do
diff --git a/lib/towerops_web/live/agent_live/edit.ex b/lib/towerops_web/live/agent_live/edit.ex index 8cf65e66..045c42d6 100644 --- a/lib/towerops_web/live/agent_live/edit.ex +++ b/lib/towerops_web/live/agent_live/edit.ex @@ -7,7 +7,7 @@ defmodule ToweropsWeb.AgentLive.Edit do @impl true def mount(%{"id" => id}, _session, socket) do - organization = socket.assigns.current_organization + organization = socket.assigns.current_scope.organization # Get agent token and verify it belongs to this organization agent_token = Towerops.Repo.get_by!(AgentToken, id: id, organization_id: organization.id) diff --git a/lib/towerops_web/live/agent_live/index.ex b/lib/towerops_web/live/agent_live/index.ex index aa07b71c..dd971e74 100644 --- a/lib/towerops_web/live/agent_live/index.ex +++ b/lib/towerops_web/live/agent_live/index.ex @@ -11,7 +11,7 @@ defmodule ToweropsWeb.AgentLive.Index do @impl true def mount(_params, _session, socket) do - organization = socket.assigns.current_organization + organization = socket.assigns.current_scope.organization current_scope = socket.assigns.current_scope is_superuser = Scope.superuser?(current_scope) agent_tokens = Agents.list_organization_agent_tokens(organization.id) @@ -68,7 +68,7 @@ defmodule ToweropsWeb.AgentLive.Index do {name, is_cloud_poller} = parse_agent_params(params) with :ok <- validate_cloud_poller_permission(socket.assigns.current_scope, is_cloud_poller), - {:ok, agent_token, token} <- create_agent(socket.assigns.current_organization, name, is_cloud_poller) do + {:ok, agent_token, token} <- create_agent(socket.assigns.current_scope.organization, name, is_cloud_poller) do handle_agent_creation_success(socket, agent_token, token, is_cloud_poller) else {:error, :unauthorized} -> @@ -103,7 +103,7 @@ defmodule ToweropsWeb.AgentLive.Index do @impl true def handle_event("regenerate_token", %{"id" => id}, socket) do agent_token = Agents.get_agent_token!(id) - organization = socket.assigns.current_organization + organization = socket.assigns.current_scope.organization # Revoke the old token and create a new one case Agents.revoke_agent_token(id) do @@ -132,7 +132,7 @@ defmodule ToweropsWeb.AgentLive.Index do def handle_event("delete_agent", %{"id" => id}, socket) do case Agents.delete_agent_token(id) do {:ok, _} -> - organization = socket.assigns.current_organization + organization = socket.assigns.current_scope.organization # If the deleted agent was the global default, clear it if Scope.superuser?(socket.assigns.current_scope) && @@ -251,7 +251,7 @@ defmodule ToweropsWeb.AgentLive.Index do end defp handle_agent_creation_success(socket, agent_token, token, is_cloud_poller) do - organization = socket.assigns.current_organization + organization = socket.assigns.current_scope.organization agent_tokens = Agents.list_organization_agent_tokens(organization.id) cloud_pollers = load_cloud_pollers_if_superuser(socket.assigns.current_scope) diff --git a/lib/towerops_web/live/agent_live/index.html.heex b/lib/towerops_web/live/agent_live/index.html.heex index 9bcb4675..a4e0fa06 100644 --- a/lib/towerops_web/live/agent_live/index.html.heex +++ b/lib/towerops_web/live/agent_live/index.html.heex @@ -1,9 +1,7 @@ <.header> {@page_title} diff --git a/lib/towerops_web/live/agent_live/show.ex b/lib/towerops_web/live/agent_live/show.ex index e08e6a6e..f7a6d4dc 100644 --- a/lib/towerops_web/live/agent_live/show.ex +++ b/lib/towerops_web/live/agent_live/show.ex @@ -8,7 +8,7 @@ defmodule ToweropsWeb.AgentLive.Show do @impl true def mount(%{"id" => id}, _session, socket) do - organization = socket.assigns.current_organization + organization = socket.assigns.current_scope.organization agent_token = Agents.get_agent_token!(id) current_user = socket.assigns.current_scope.user diff --git a/lib/towerops_web/live/agent_live/show.html.heex b/lib/towerops_web/live/agent_live/show.html.heex index 5e3ace31..762f7350 100644 --- a/lib/towerops_web/live/agent_live/show.html.heex +++ b/lib/towerops_web/live/agent_live/show.html.heex @@ -1,9 +1,7 @@ <.header> {@page_title} @@ -169,17 +167,11 @@

Last Seen

- <.timestamp - datetime={@agent_token.last_seen_at} - timezone={@timezone} - /> + <.timestamp datetime={@agent_token.last_seen_at} />

<%= if @agent_token.last_seen_at do %>

- <.timestamp - datetime={@agent_token.last_seen_at} - timezone={@timezone} - /> + <.timestamp datetime={@agent_token.last_seen_at} />

<% end %> <%= if @agent_token.last_ip do %> diff --git a/lib/towerops_web/live/alert_live/index.ex b/lib/towerops_web/live/alert_live/index.ex index b233ce80..6f08dde5 100644 --- a/lib/towerops_web/live/alert_live/index.ex +++ b/lib/towerops_web/live/alert_live/index.ex @@ -7,7 +7,7 @@ defmodule ToweropsWeb.AlertLive.Index do @impl true def mount(_params, _session, socket) do - organization = socket.assigns.current_organization + organization = socket.assigns.current_scope.organization # Subscribe to alert events _ = @@ -30,12 +30,12 @@ defmodule ToweropsWeb.AlertLive.Index do {:noreply, socket |> assign(:filter, filter) - |> load_alerts(socket.assigns.current_organization.id)} + |> load_alerts(socket.assigns.current_scope.organization.id)} end @impl true def handle_event("acknowledge", %{"id" => id}, socket) do - organization = socket.assigns.current_organization + organization = socket.assigns.current_scope.organization user_id = socket.assigns.current_scope.user.id case Alerts.get_alert(id) do @@ -47,30 +47,34 @@ defmodule ToweropsWeb.AlertLive.Index do alert = Repo.preload(alert, device: [site: :organization]) if alert.device.site.organization_id == organization.id do - case Alerts.acknowledge_alert(alert, user_id) do - {:ok, _alert} -> - {:noreply, - socket - |> put_flash(:info, "Alert acknowledged") - |> load_alerts(organization.id)} - - {:error, _changeset} -> - {:noreply, put_flash(socket, :error, "Unable to acknowledge alert")} - end + perform_acknowledge_alert(socket, alert, user_id, organization.id) else {:noreply, put_flash(socket, :error, "You don't have access to this alert")} end end end + defp perform_acknowledge_alert(socket, alert, user_id, organization_id) do + case Alerts.acknowledge_alert(alert, user_id) do + {:ok, _alert} -> + {:noreply, + socket + |> put_flash(:info, "Alert acknowledged") + |> load_alerts(organization_id)} + + {:error, _changeset} -> + {:noreply, put_flash(socket, :error, "Unable to acknowledge alert")} + end + end + @impl true def handle_info({:new_alert, _device_id, _alert_type}, socket) do - {:noreply, load_alerts(socket, socket.assigns.current_organization.id)} + {:noreply, load_alerts(socket, socket.assigns.current_scope.organization.id)} end @impl true def handle_info({:alert_resolved, _device_id, _alert_type}, socket) do - {:noreply, load_alerts(socket, socket.assigns.current_organization.id)} + {:noreply, load_alerts(socket, socket.assigns.current_scope.organization.id)} end defp load_alerts(socket, organization_id) do diff --git a/lib/towerops_web/live/alert_live/index.html.heex b/lib/towerops_web/live/alert_live/index.html.heex index 76a27f5e..dfd7cd7d 100644 --- a/lib/towerops_web/live/alert_live/index.html.heex +++ b/lib/towerops_web/live/alert_live/index.html.heex @@ -1,9 +1,7 @@ <.header> Alerts diff --git a/lib/towerops_web/live/dashboard_live.ex b/lib/towerops_web/live/dashboard_live.ex index 0d67eda9..c9351f71 100644 --- a/lib/towerops_web/live/dashboard_live.ex +++ b/lib/towerops_web/live/dashboard_live.ex @@ -8,7 +8,7 @@ defmodule ToweropsWeb.DashboardLive do @impl true def mount(_params, _session, socket) do - organization = socket.assigns.current_organization + organization = socket.assigns.current_scope.organization # Subscribe to real-time alert updates _ = @@ -25,12 +25,12 @@ defmodule ToweropsWeb.DashboardLive do @impl true def handle_info({:new_alert, _device_id, _alert_type}, socket) do - {:noreply, load_dashboard_data(socket, socket.assigns.current_organization.id)} + {:noreply, load_dashboard_data(socket, socket.assigns.current_scope.organization.id)} end @impl true def handle_info({:alert_resolved, _device_id, _alert_type}, socket) do - {:noreply, load_dashboard_data(socket, socket.assigns.current_organization.id)} + {:noreply, load_dashboard_data(socket, socket.assigns.current_scope.organization.id)} end defp load_dashboard_data(socket, organization_id) do diff --git a/lib/towerops_web/live/dashboard_live.html.heex b/lib/towerops_web/live/dashboard_live.html.heex index 3b487678..c021d608 100644 --- a/lib/towerops_web/live/dashboard_live.html.heex +++ b/lib/towerops_web/live/dashboard_live.html.heex @@ -1,13 +1,11 @@ <.header> Dashboard - <:subtitle>Welcome to {@current_organization.name} + <:subtitle>Welcome to {@current_scope.organization.name} <%= if @sites_count == 0 do %> @@ -165,7 +163,6 @@
<.timestamp datetime={alert.triggered_at} - timezone={@timezone} class="text-xs text-gray-500 dark:text-gray-400 whitespace-nowrap" />
diff --git a/lib/towerops_web/live/device_live/form.ex b/lib/towerops_web/live/device_live/form.ex index d1d40e31..fe77a2c2 100644 --- a/lib/towerops_web/live/device_live/form.ex +++ b/lib/towerops_web/live/device_live/form.ex @@ -12,7 +12,7 @@ defmodule ToweropsWeb.DeviceLive.Form do @impl true def mount(params, _session, socket) do - organization = socket.assigns.current_organization + organization = socket.assigns.current_scope.organization sites = Sites.list_organization_sites(organization.id) agents = Agents.list_organization_agent_tokens(organization.id) @@ -101,7 +101,7 @@ defmodule ToweropsWeb.DeviceLive.Form do end defp apply_action(socket, :edit, %{"id" => id}) do - organization = socket.assigns.current_organization + organization = socket.assigns.current_scope.organization case Devices.get_device(id) do nil -> diff --git a/lib/towerops_web/live/device_live/form.html.heex b/lib/towerops_web/live/device_live/form.html.heex index fa31a946..0fb2508b 100644 --- a/lib/towerops_web/live/device_live/form.html.heex +++ b/lib/towerops_web/live/device_live/form.html.heex @@ -1,8 +1,6 @@
diff --git a/lib/towerops_web/live/device_live/index.ex b/lib/towerops_web/live/device_live/index.ex index d0a7bf58..5dce64c1 100644 --- a/lib/towerops_web/live/device_live/index.ex +++ b/lib/towerops_web/live/device_live/index.ex @@ -11,7 +11,7 @@ defmodule ToweropsWeb.DeviceLive.Index do @impl true def mount(_params, _session, socket) do - organization = socket.assigns.current_organization + organization = socket.assigns.current_scope.organization device = Devices.list_organization_devices(organization.id) sites = Sites.list_organization_sites(organization.id) grouped_devices = group_devices_by_site(device) @@ -36,7 +36,7 @@ defmodule ToweropsWeb.DeviceLive.Index do end defp apply_action(socket, :index, _params, tab) do - organization = socket.assigns.current_organization + organization = socket.assigns.current_scope.organization case tab do "discovered" -> @@ -73,7 +73,7 @@ defmodule ToweropsWeb.DeviceLive.Index do @impl true def handle_event("reorder_site", %{"site_id" => site_id, "new_position" => new_position}, socket) do - organization = socket.assigns.current_organization + organization = socket.assigns.current_scope.organization # Verify site belongs to current organization case Sites.get_site(site_id) do @@ -82,24 +82,7 @@ defmodule ToweropsWeb.DeviceLive.Index do site -> if site.organization_id == organization.id do - # Handle both integer and string input - position = if is_integer(new_position), do: new_position, else: String.to_integer(new_position) - - case Sites.reorder_site(site_id, position) do - {:ok, _site} -> - # Reload devices with updated order - devices = Devices.list_organization_devices(organization.id) - grouped_devices = group_devices_by_site(devices) - - {:noreply, - socket - |> assign(:device, devices) - |> assign(:grouped_devices, grouped_devices) - |> put_flash(:info, "Site order updated")} - - {:error, _changeset} -> - {:noreply, put_flash(socket, :error, "Failed to reorder site")} - end + perform_site_reorder(socket, site_id, new_position, organization.id) else {:noreply, put_flash(socket, :error, "You don't have access to this site")} end @@ -108,7 +91,7 @@ defmodule ToweropsWeb.DeviceLive.Index do @impl true def handle_event("reorder_device", %{"device_id" => device_id, "new_position" => new_position}, socket) do - organization = socket.assigns.current_organization + organization = socket.assigns.current_scope.organization # Verify device belongs to current organization case Devices.get_device(device_id) do @@ -120,24 +103,7 @@ defmodule ToweropsWeb.DeviceLive.Index do device = Repo.preload(device, site: :organization) if device.site.organization_id == organization.id do - # Handle both integer and string input - position = if is_integer(new_position), do: new_position, else: String.to_integer(new_position) - - case Devices.reorder_device(device_id, position) do - {:ok, _device} -> - # Reload devices with updated order - devices = Devices.list_organization_devices(organization.id) - grouped_devices = group_devices_by_site(devices) - - {:noreply, - socket - |> assign(:device, devices) - |> assign(:grouped_devices, grouped_devices) - |> put_flash(:info, "Device order updated")} - - {:error, _changeset} -> - {:noreply, put_flash(socket, :error, "Failed to reorder device")} - end + perform_device_reorder(socket, device_id, new_position, organization.id) else {:noreply, put_flash(socket, :error, "You don't have access to this device")} end @@ -151,7 +117,7 @@ defmodule ToweropsWeb.DeviceLive.Index do @impl true def handle_event("reset_order", _params, socket) do - organization_id = socket.assigns.current_organization.id + organization_id = socket.assigns.current_scope.organization.id Sites.reset_site_order(organization_id) Devices.reset_organization_device_order(organization_id) @@ -180,6 +146,48 @@ defmodule ToweropsWeb.DeviceLive.Index do {:noreply, push_navigate(socket, to: ~p"/devices/new?#{prefill_params}")} end + defp perform_site_reorder(socket, site_id, new_position, organization_id) do + # Handle both integer and string input + position = if is_integer(new_position), do: new_position, else: String.to_integer(new_position) + + case Sites.reorder_site(site_id, position) do + {:ok, _site} -> + # Reload devices with updated order + devices = Devices.list_organization_devices(organization_id) + grouped_devices = group_devices_by_site(devices) + + {:noreply, + socket + |> assign(:device, devices) + |> assign(:grouped_devices, grouped_devices) + |> put_flash(:info, "Site order updated")} + + {:error, _changeset} -> + {:noreply, put_flash(socket, :error, "Failed to reorder site")} + end + end + + defp perform_device_reorder(socket, device_id, new_position, organization_id) do + # Handle both integer and string input + position = if is_integer(new_position), do: new_position, else: String.to_integer(new_position) + + case Devices.reorder_device(device_id, position) do + {:ok, _device} -> + # Reload devices with updated order + devices = Devices.list_organization_devices(organization_id) + grouped_devices = group_devices_by_site(devices) + + {:noreply, + socket + |> assign(:device, devices) + |> assign(:grouped_devices, grouped_devices) + |> put_flash(:info, "Device order updated")} + + {:error, _changeset} -> + {:noreply, put_flash(socket, :error, "Failed to reorder device")} + end + end + defp enqueue_discovery(device_id) do if Application.get_env(:towerops, :env) == :test do _ = Task.start(fn -> Snmp.discover_device(Devices.get_device!(device_id)) end) diff --git a/lib/towerops_web/live/device_live/index.html.heex b/lib/towerops_web/live/device_live/index.html.heex index 4da77c3c..5efdd65b 100644 --- a/lib/towerops_web/live/device_live/index.html.heex +++ b/lib/towerops_web/live/device_live/index.html.heex @@ -1,9 +1,7 @@
@@ -460,7 +458,6 @@ <:col :let={discovered} label="Last Seen"> <.timestamp datetime={discovered.last_seen} - timezone={@timezone} class="text-sm text-gray-600 dark:text-gray-400" /> diff --git a/lib/towerops_web/live/device_live/show.ex b/lib/towerops_web/live/device_live/show.ex index 43b99930..6ad31381 100644 --- a/lib/towerops_web/live/device_live/show.ex +++ b/lib/towerops_web/live/device_live/show.ex @@ -41,7 +41,7 @@ defmodule ToweropsWeb.DeviceLive.Show do @impl true def handle_params(%{"id" => id} = params, _, socket) do - organization = socket.assigns.current_organization + organization = socket.assigns.current_scope.organization # Check if device exists and verify organization access case Devices.get_device(id) do @@ -56,11 +56,7 @@ defmodule ToweropsWeb.DeviceLive.Show do device = Repo.preload(device, site: :organization) if device.site.organization_id == organization.id do - _ = - if connected?(socket) do - _ = Phoenix.PubSub.subscribe(Towerops.PubSub, "device:#{id}") - Process.send_after(self(), :refresh_data, 10_000) - end + maybe_subscribe_and_schedule_refresh(socket, id) tab = Map.get(params, "tab", "overview") @@ -161,6 +157,15 @@ defmodule ToweropsWeb.DeviceLive.Show do # Private functions + defp maybe_subscribe_and_schedule_refresh(socket, device_id) do + if connected?(socket) do + Phoenix.PubSub.subscribe(Towerops.PubSub, "device:#{device_id}") + Process.send_after(self(), :refresh_data, 10_000) + end + + :ok + end + defp load_equipment_data(socket, device_id) do device = Devices.get_device!(device_id) device_with_associations = Repo.preload(device, site: [organization: :default_agent_token]) diff --git a/lib/towerops_web/live/device_live/show.html.heex b/lib/towerops_web/live/device_live/show.html.heex index 88e187eb..7640504f 100644 --- a/lib/towerops_web/live/device_live/show.html.heex +++ b/lib/towerops_web/live/device_live/show.html.heex @@ -1,9 +1,7 @@
diff --git a/lib/towerops_web/live/graph_live/show.ex b/lib/towerops_web/live/graph_live/show.ex index d8906b33..71fa3960 100644 --- a/lib/towerops_web/live/graph_live/show.ex +++ b/lib/towerops_web/live/graph_live/show.ex @@ -14,7 +14,7 @@ defmodule ToweropsWeb.GraphLive.Show do @impl true def handle_params(%{"id" => device_id, "sensor_type" => sensor_type} = params, _, socket) do - organization = socket.assigns.current_organization + organization = socket.assigns.current_scope.organization # Verify device exists and user has access case Devices.get_device(device_id) do @@ -29,10 +29,7 @@ defmodule ToweropsWeb.GraphLive.Show do device = Repo.preload(device, site: :organization) if device.site.organization_id == organization.id do - _ = - if connected?(socket) do - Phoenix.PubSub.subscribe(Towerops.PubSub, "device:#{device_id}") - end + maybe_subscribe_to_device(socket, device_id) range = Map.get(params, "range", "24h") interface_id = Map.get(params, "interface_id") @@ -59,6 +56,14 @@ defmodule ToweropsWeb.GraphLive.Show do end end + defp maybe_subscribe_to_device(socket, device_id) do + if connected?(socket) do + Phoenix.PubSub.subscribe(Towerops.PubSub, "device:#{device_id}") + end + + :ok + end + @impl true def handle_event("change_range", %{"range" => range}, socket) do params = build_graph_params(socket.assigns, range) diff --git a/lib/towerops_web/live/graph_live/show.html.heex b/lib/towerops_web/live/graph_live/show.html.heex index 65b9245f..d6fd1ccd 100644 --- a/lib/towerops_web/live/graph_live/show.html.heex +++ b/lib/towerops_web/live/graph_live/show.html.heex @@ -1,9 +1,7 @@
diff --git a/lib/towerops_web/live/help_live/index.ex b/lib/towerops_web/live/help_live/index.ex index f9041fde..9b78eef8 100644 --- a/lib/towerops_web/live/help_live/index.ex +++ b/lib/towerops_web/live/help_live/index.ex @@ -3,6 +3,7 @@ defmodule ToweropsWeb.HelpLive.Index do use ToweropsWeb, :live_view alias Towerops.Accounts + alias Towerops.Accounts.Scope alias Towerops.Organizations @impl true @@ -23,10 +24,9 @@ defmodule ToweropsWeb.HelpLive.Index do # Build current_scope if authenticated current_scope = if current_user do - %{ - user: current_user, - impersonating?: false - } + current_user + |> Scope.for_user() + |> Scope.put_organization(current_organization) end socket = @@ -35,7 +35,6 @@ defmodule ToweropsWeb.HelpLive.Index do |> assign(:current_user, current_user) |> assign(:is_authenticated, is_authenticated) |> assign(:current_scope, current_scope) - |> assign(:current_organization, current_organization) |> assign(:timezone, if(current_user, do: current_user.timezone, else: "UTC")) {:ok, socket} diff --git a/lib/towerops_web/live/help_live/index.html.heex b/lib/towerops_web/live/help_live/index.html.heex index b0c3c6f3..54a12edc 100644 --- a/lib/towerops_web/live/help_live/index.html.heex +++ b/lib/towerops_web/live/help_live/index.html.heex @@ -2,9 +2,7 @@ <.help_content active_section={@active_section} /> diff --git a/lib/towerops_web/live/network_map_live.ex b/lib/towerops_web/live/network_map_live.ex index 55fdf927..d6b1b4a3 100644 --- a/lib/towerops_web/live/network_map_live.ex +++ b/lib/towerops_web/live/network_map_live.ex @@ -6,7 +6,7 @@ defmodule ToweropsWeb.NetworkMapLive do @impl true def mount(_params, _session, socket) do - organization = socket.assigns.current_organization + organization = socket.assigns.current_scope.organization # Subscribe to topology changes if connected _ = @@ -40,7 +40,7 @@ defmodule ToweropsWeb.NetworkMapLive do @impl true def handle_params(params, _url, socket) do tab = Map.get(params, "tab", "added") - organization = socket.assigns.current_organization + organization = socket.assigns.current_scope.organization {:noreply, socket @@ -50,7 +50,7 @@ defmodule ToweropsWeb.NetworkMapLive do @impl true def handle_event("refresh_topology", _params, socket) do - organization = socket.assigns.current_organization + organization = socket.assigns.current_scope.organization {:noreply, socket @@ -68,7 +68,7 @@ defmodule ToweropsWeb.NetworkMapLive do @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)} + {:noreply, load_topology_data(socket, socket.assigns.current_scope.organization.id, socket.assigns.active_tab)} end defp load_topology_data(socket, organization_id, tab) do diff --git a/lib/towerops_web/live/network_map_live.html.heex b/lib/towerops_web/live/network_map_live.html.heex index 5d99b299..2ad5a6af 100644 --- a/lib/towerops_web/live/network_map_live.html.heex +++ b/lib/towerops_web/live/network_map_live.html.heex @@ -1,9 +1,7 @@ <.header> diff --git a/lib/towerops_web/live/org/settings_live.ex b/lib/towerops_web/live/org/settings_live.ex index 77822760..9fd18dd7 100644 --- a/lib/towerops_web/live/org/settings_live.ex +++ b/lib/towerops_web/live/org/settings_live.ex @@ -7,7 +7,7 @@ defmodule ToweropsWeb.Org.SettingsLive do @impl true def mount(_params, _session, socket) do - organization = socket.assigns.current_organization + organization = socket.assigns.current_scope.organization available_agents = Agents.list_organization_agent_tokens(organization.id) # Get assignment breakdown to show impact diff --git a/lib/towerops_web/live/org/settings_live.html.heex b/lib/towerops_web/live/org/settings_live.html.heex index 7d7f4b6f..85dc8b33 100644 --- a/lib/towerops_web/live/org/settings_live.html.heex +++ b/lib/towerops_web/live/org/settings_live.html.heex @@ -1,7 +1,6 @@
<.link diff --git a/lib/towerops_web/live/org_live/index.html.heex b/lib/towerops_web/live/org_live/index.html.heex index 6a17fb86..e4060364 100644 --- a/lib/towerops_web/live/org_live/index.html.heex +++ b/lib/towerops_web/live/org_live/index.html.heex @@ -1,8 +1,6 @@ <.header> {@page_title} diff --git a/lib/towerops_web/live/org_live/new.html.heex b/lib/towerops_web/live/org_live/new.html.heex index 538d8b23..3cce6b53 100644 --- a/lib/towerops_web/live/org_live/new.html.heex +++ b/lib/towerops_web/live/org_live/new.html.heex @@ -1,8 +1,6 @@ <.header> {@page_title} diff --git a/lib/towerops_web/live/site_live/form.ex b/lib/towerops_web/live/site_live/form.ex index 0f941551..33a7c88e 100644 --- a/lib/towerops_web/live/site_live/form.ex +++ b/lib/towerops_web/live/site_live/form.ex @@ -8,7 +8,7 @@ defmodule ToweropsWeb.SiteLive.Form do @impl true def mount(_params, _session, socket) do - organization = socket.assigns.current_organization + organization = socket.assigns.current_scope.organization {:ok, socket diff --git a/lib/towerops_web/live/site_live/form.html.heex b/lib/towerops_web/live/site_live/form.html.heex index 9aa4724d..9ba40dd7 100644 --- a/lib/towerops_web/live/site_live/form.html.heex +++ b/lib/towerops_web/live/site_live/form.html.heex @@ -1,8 +1,6 @@
<.link diff --git a/lib/towerops_web/live/site_live/index.ex b/lib/towerops_web/live/site_live/index.ex index 0ad6ac8c..6168ccf8 100644 --- a/lib/towerops_web/live/site_live/index.ex +++ b/lib/towerops_web/live/site_live/index.ex @@ -6,7 +6,7 @@ defmodule ToweropsWeb.SiteLive.Index do @impl true def mount(_params, _session, socket) do - organization = socket.assigns.current_organization + organization = socket.assigns.current_scope.organization sites = Sites.list_organization_sites(organization.id) site_tree = Sites.build_site_tree(organization.id) diff --git a/lib/towerops_web/live/site_live/index.html.heex b/lib/towerops_web/live/site_live/index.html.heex index d8995418..a2acba5c 100644 --- a/lib/towerops_web/live/site_live/index.html.heex +++ b/lib/towerops_web/live/site_live/index.html.heex @@ -1,9 +1,7 @@ <.header> {@page_title} diff --git a/lib/towerops_web/live/site_live/show.ex b/lib/towerops_web/live/site_live/show.ex index 081c6cd2..2ec77938 100644 --- a/lib/towerops_web/live/site_live/show.ex +++ b/lib/towerops_web/live/site_live/show.ex @@ -15,7 +15,7 @@ defmodule ToweropsWeb.SiteLive.Show do @impl true def handle_params(%{"id" => id}, _, socket) do - organization = socket.assigns.current_organization + organization = socket.assigns.current_scope.organization site = Sites.get_organization_site!(organization.id, id) device = Devices.list_site_devices(site.id) latency_chart_data = load_site_latency_chart_data(device) diff --git a/lib/towerops_web/live/site_live/show.html.heex b/lib/towerops_web/live/site_live/show.html.heex index 7f667615..d4552198 100644 --- a/lib/towerops_web/live/site_live/show.html.heex +++ b/lib/towerops_web/live/site_live/show.html.heex @@ -1,9 +1,7 @@ <.header> {@page_title} diff --git a/lib/towerops_web/live/user_settings_live.ex b/lib/towerops_web/live/user_settings_live.ex index d756a823..64de6b5e 100644 --- a/lib/towerops_web/live/user_settings_live.ex +++ b/lib/towerops_web/live/user_settings_live.ex @@ -259,8 +259,6 @@ defmodule ToweropsWeb.UserSettingsLive do

diff --git a/lib/towerops_web/user_auth.ex b/lib/towerops_web/user_auth.ex index d1c8fa83..9cd6a4a8 100644 --- a/lib/towerops_web/user_auth.ex +++ b/lib/towerops_web/user_auth.ex @@ -354,15 +354,15 @@ defmodule ToweropsWeb.UserAuth do """ def load_current_organization(conn, _opts) do org_slug = conn.path_params["org_slug"] - user = conn.assigns.current_scope && conn.assigns.current_scope.user + scope = conn.assigns.current_scope - if org_slug && user do + if org_slug && scope && scope.user do organization = Towerops.Organizations.get_organization_by_slug!(org_slug) - membership = Towerops.Organizations.get_membership(organization.id, user.id) + membership = Towerops.Organizations.get_membership(organization.id, scope.user.id) if membership do conn - |> assign(:current_organization, organization) + |> assign(:current_scope, Scope.put_organization(scope, organization)) |> assign(:current_membership, membership) else conn @@ -392,6 +392,7 @@ defmodule ToweropsWeb.UserAuth do * `:redirect_if_user_is_authenticated` - Redirects authenticated users * `:require_authenticated_user` - Requires user authentication * `:load_current_organization` - Loads organization from URL slug + * `:require_organization_owner` - Requires user to be an owner of the current organization """ def on_mount(:redirect_if_user_is_authenticated, _params, session, socket) do @@ -421,16 +422,16 @@ defmodule ToweropsWeb.UserAuth do end def on_mount(:load_current_organization, %{"org_slug" => org_slug}, _session, socket) do - user = socket.assigns.current_scope && socket.assigns.current_scope.user + scope = socket.assigns.current_scope - if org_slug && user do + if org_slug && scope && scope.user do organization = Towerops.Organizations.get_organization_by_slug!(org_slug) - membership = Towerops.Organizations.get_membership(organization.id, user.id) + membership = Towerops.Organizations.get_membership(organization.id, scope.user.id) if membership do {:cont, socket - |> Phoenix.Component.assign(:current_organization, organization) + |> Phoenix.Component.assign(:current_scope, Scope.put_organization(scope, organization)) |> Phoenix.Component.assign(:current_membership, membership)} else socket = @@ -518,6 +519,21 @@ defmodule ToweropsWeb.UserAuth do end end + def on_mount(:require_organization_owner, _params, _session, socket) do + membership = Map.get(socket.assigns, :current_membership) + + if membership && membership.role == :owner do + {:cont, socket} + else + socket = + socket + |> LiveView.put_flash(:error, "You must be an organization owner to access this page.") + |> LiveView.redirect(to: ~p"/orgs") + + {:halt, socket} + end + end + def on_mount(:load_cookie_consent, _params, session, socket) do requires_consent = Map.get(session, "requires_cookie_consent", false) @@ -570,9 +586,11 @@ defmodule ToweropsWeb.UserAuth do halt_with_error(socket, "You don't have access to any organizations.") mem -> + scope = socket.assigns.current_scope + {:cont, socket - |> Phoenix.Component.assign(:current_organization, organization) + |> Phoenix.Component.assign(:current_scope, Scope.put_organization(scope, organization)) |> Phoenix.Component.assign(:current_membership, mem)} end end diff --git a/test/snmpkit/snmp_mgr/mib_extraction_test.exs b/test/snmpkit/snmp_mgr/mib_extraction_test.exs index c35e1d3f..7fe5d468 100644 --- a/test/snmpkit/snmp_mgr/mib_extraction_test.exs +++ b/test/snmpkit/snmp_mgr/mib_extraction_test.exs @@ -1,11 +1,13 @@ defmodule SnmpKit.SnmpMgr.MIB.ExtractionTest do use ExUnit.Case, async: true + alias SnmpKit.SnmpLib.MIB.Parser + # Test the actual extract_mib_mappings private function behavior test "extract_mib_mappings resolves UBNT-MIB OIDs" do mib_path = Path.join(:code.priv_dir(:towerops), "mibs/ubnt/UBNT-MIB") {:ok, mib_content} = File.read(mib_path) - {:ok, parsed} = SnmpKit.SnmpLib.MIB.Parser.parse(mib_content) + {:ok, parsed} = Parser.parse(mib_content) # Simulate state with standard MIBs mock_state = %{ diff --git a/test/snmpkit/snmp_mgr/mib_test.exs b/test/snmpkit/snmp_mgr/mib_test.exs index 06ef3704..c850ff24 100644 --- a/test/snmpkit/snmp_mgr/mib_test.exs +++ b/test/snmpkit/snmp_mgr/mib_test.exs @@ -1,10 +1,12 @@ defmodule SnmpKit.SnmpMgr.MIBTest do use ExUnit.Case, async: false + alias SnmpKit.SnmpLib.MIB.Parser + # Test helper to parse a MIB and extract name->OID map defp parse_and_extract(mib_path) do {:ok, content} = File.read(mib_path) - {:ok, parsed} = SnmpKit.SnmpLib.MIB.Parser.parse(content) + {:ok, parsed} = Parser.parse(content) definitions = Map.get(parsed, :definitions, []) # Extract OID definitions (simulating build_oid_definition_map) diff --git a/test/towerops_web/user_auth_test.exs b/test/towerops_web/user_auth_test.exs index 19a80013..d93aaff8 100644 --- a/test/towerops_web/user_auth_test.exs +++ b/test/towerops_web/user_auth_test.exs @@ -367,7 +367,7 @@ defmodule ToweropsWeb.UserAuthTest do |> UserAuth.load_current_organization([]) refute conn.halted - assert conn.assigns.current_organization.id == organization.id + assert conn.assigns.current_scope.organization.id == organization.id assert conn.assigns.current_membership end @@ -699,7 +699,7 @@ defmodule ToweropsWeb.UserAuthTest do socket ) - assert socket.assigns.current_organization.id == organization.id + assert socket.assigns.current_scope.organization.id == organization.id assert socket.assigns.current_membership.role == :owner end @@ -908,7 +908,7 @@ defmodule ToweropsWeb.UserAuthTest do socket ) - assert result_socket.assigns.current_organization.id == organization.id + assert result_socket.assigns.current_scope.organization.id == organization.id assert result_socket.assigns.current_membership end @@ -928,7 +928,7 @@ defmodule ToweropsWeb.UserAuthTest do socket ) - assert result_socket.assigns.current_organization.id == organization.id + assert result_socket.assigns.current_scope.organization.id == organization.id assert result_socket.assigns.current_membership end