refactoring
This commit is contained in:
parent
857c8d2196
commit
8e739283ad
47 changed files with 311 additions and 256 deletions
|
|
@ -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 <directory> [--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 <directory> [--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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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() ++
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 =
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ defmodule ToweropsWeb.Layouts do
|
|||
|
||||
## Examples
|
||||
|
||||
<Layouts.authenticated flash={@flash} current_organization={@current_organization} active_page="dashboard">
|
||||
<Layouts.authenticated flash={@flash} current_scope={@current_scope} active_page="dashboard">
|
||||
<h1>Content</h1>
|
||||
</Layouts.authenticated>
|
||||
|
||||
|
|
@ -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"""
|
||||
<div class="flex min-h-screen flex-col bg-gray-50 dark:bg-gray-950">
|
||||
<!-- Impersonation Banner -->
|
||||
|
|
|
|||
|
|
@ -119,7 +119,6 @@
|
|||
<Layouts.authenticated
|
||||
flash={@flash}
|
||||
current_scope={@current_scope}
|
||||
current_organization={@current_organization}
|
||||
>
|
||||
{content.()}
|
||||
</Layouts.authenticated>
|
||||
|
|
|
|||
|
|
@ -396,7 +396,6 @@
|
|||
<Layouts.authenticated
|
||||
flash={@flash}
|
||||
current_scope={@current_scope}
|
||||
current_organization={@current_organization}
|
||||
>
|
||||
{content.()}
|
||||
</Layouts.authenticated>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<Layouts.authenticated flash={@flash} current_scope={@current_scope} current_organization={nil}>
|
||||
<Layouts.authenticated flash={@flash} current_scope={@current_scope}>
|
||||
<div class="text-center">
|
||||
<.header>
|
||||
Account Settings
|
||||
|
|
|
|||
|
|
@ -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
|
|||
<Layouts.authenticated
|
||||
flash={@flash}
|
||||
current_scope={@current_scope}
|
||||
current_organization={@current_organization}
|
||||
active_page="settings"
|
||||
>
|
||||
<div class="space-y-8">
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
<Layouts.authenticated
|
||||
flash={@flash}
|
||||
current_scope={@current_scope}
|
||||
current_organization={@current_organization}
|
||||
active_page="agents"
|
||||
timezone={@timezone}
|
||||
>
|
||||
<.header>
|
||||
{@page_title}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
<Layouts.authenticated
|
||||
flash={@flash}
|
||||
current_scope={@current_scope}
|
||||
current_organization={@current_organization}
|
||||
active_page="agents"
|
||||
timezone={@timezone}
|
||||
>
|
||||
<.header>
|
||||
{@page_title}
|
||||
|
|
@ -169,17 +167,11 @@
|
|||
<div class="min-w-0 flex-1">
|
||||
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Last Seen</p>
|
||||
<p class="mt-2 text-lg font-semibold text-gray-900 dark:text-white">
|
||||
<.timestamp
|
||||
datetime={@agent_token.last_seen_at}
|
||||
timezone={@timezone}
|
||||
/>
|
||||
<.timestamp datetime={@agent_token.last_seen_at} />
|
||||
</p>
|
||||
<%= if @agent_token.last_seen_at do %>
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
<.timestamp
|
||||
datetime={@agent_token.last_seen_at}
|
||||
timezone={@timezone}
|
||||
/>
|
||||
<.timestamp datetime={@agent_token.last_seen_at} />
|
||||
</p>
|
||||
<% end %>
|
||||
<%= if @agent_token.last_ip do %>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
<Layouts.authenticated
|
||||
flash={@flash}
|
||||
current_scope={@current_scope}
|
||||
current_organization={@current_organization}
|
||||
active_page="alerts"
|
||||
timezone={@timezone}
|
||||
>
|
||||
<.header>
|
||||
Alerts
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
<Layouts.authenticated
|
||||
flash={@flash}
|
||||
current_scope={@current_scope}
|
||||
current_organization={@current_organization}
|
||||
active_page="dashboard"
|
||||
timezone={@timezone}
|
||||
>
|
||||
<.header>
|
||||
Dashboard
|
||||
<:subtitle>Welcome to {@current_organization.name}</:subtitle>
|
||||
<:subtitle>Welcome to {@current_scope.organization.name}</:subtitle>
|
||||
</.header>
|
||||
|
||||
<%= if @sites_count == 0 do %>
|
||||
|
|
@ -165,7 +163,6 @@
|
|||
</div>
|
||||
<.timestamp
|
||||
datetime={alert.triggered_at}
|
||||
timezone={@timezone}
|
||||
class="text-xs text-gray-500 dark:text-gray-400 whitespace-nowrap"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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 ->
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
<Layouts.authenticated
|
||||
flash={@flash}
|
||||
current_scope={@current_scope}
|
||||
current_organization={@organization}
|
||||
timezone={@timezone}
|
||||
>
|
||||
<div phx-hook="ScrollToTop" id="scroll-container">
|
||||
<div class="mb-4">
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
<Layouts.authenticated
|
||||
flash={@flash}
|
||||
current_scope={@current_scope}
|
||||
current_organization={@current_organization}
|
||||
active_page="devices"
|
||||
timezone={@timezone}
|
||||
>
|
||||
<div class="flex items-start justify-between mb-8">
|
||||
<div>
|
||||
|
|
@ -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"
|
||||
/>
|
||||
</:col>
|
||||
|
|
|
|||
|
|
@ -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])
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
<Layouts.authenticated
|
||||
flash={@flash}
|
||||
current_scope={@current_scope}
|
||||
current_organization={@current_organization}
|
||||
active_page="devices"
|
||||
timezone={@timezone}
|
||||
>
|
||||
<div class="mb-6 -mt-2">
|
||||
<!-- Breadcrumbs -->
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
<Layouts.authenticated
|
||||
flash={@flash}
|
||||
current_scope={@current_scope}
|
||||
current_organization={@current_organization}
|
||||
active_page="devices"
|
||||
timezone={@timezone}
|
||||
>
|
||||
<div class="mb-6">
|
||||
<!-- Header with back button -->
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,7 @@
|
|||
<Layouts.authenticated
|
||||
flash={@flash}
|
||||
current_scope={@current_scope}
|
||||
current_organization={@current_organization}
|
||||
active_page="help"
|
||||
timezone={@timezone}
|
||||
>
|
||||
<.help_content active_section={@active_section} />
|
||||
</Layouts.authenticated>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
<Layouts.authenticated
|
||||
flash={@flash}
|
||||
current_scope={@current_scope}
|
||||
current_organization={@current_organization}
|
||||
active_page="network-map"
|
||||
timezone={@timezone}
|
||||
>
|
||||
<.header>
|
||||
<span class="flex items-center gap-2">
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
<Layouts.authenticated
|
||||
flash={@flash}
|
||||
current_scope={@current_scope}
|
||||
current_organization={@organization}
|
||||
>
|
||||
<div class="mb-4">
|
||||
<.link
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
<Layouts.authenticated
|
||||
flash={@flash}
|
||||
current_scope={@current_scope}
|
||||
current_organization={nil}
|
||||
timezone={@timezone}
|
||||
>
|
||||
<.header>
|
||||
{@page_title}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
<Layouts.authenticated
|
||||
flash={@flash}
|
||||
current_scope={@current_scope}
|
||||
current_organization={nil}
|
||||
timezone={@timezone}
|
||||
>
|
||||
<.header>
|
||||
{@page_title}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
<Layouts.authenticated
|
||||
flash={@flash}
|
||||
current_scope={@current_scope}
|
||||
current_organization={@organization}
|
||||
timezone={@timezone}
|
||||
>
|
||||
<div class="mb-4">
|
||||
<.link
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
<Layouts.authenticated
|
||||
flash={@flash}
|
||||
current_scope={@current_scope}
|
||||
current_organization={@current_organization}
|
||||
active_page="sites"
|
||||
timezone={@timezone}
|
||||
>
|
||||
<.header>
|
||||
{@page_title}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
<Layouts.authenticated
|
||||
flash={@flash}
|
||||
current_scope={@current_scope}
|
||||
current_organization={@current_organization}
|
||||
active_page="sites"
|
||||
timezone={@timezone}
|
||||
>
|
||||
<.header>
|
||||
{@page_title}
|
||||
|
|
|
|||
|
|
@ -259,8 +259,6 @@ defmodule ToweropsWeb.UserSettingsLive do
|
|||
<Layouts.authenticated
|
||||
flash={@flash}
|
||||
current_scope={@current_scope}
|
||||
current_organization={@default_organization}
|
||||
timezone={@timezone}
|
||||
>
|
||||
<div class="border-b border-gray-200 pb-5 dark:border-white/5">
|
||||
<h1 class="text-3xl font-semibold tracking-tight text-gray-900 dark:text-white">
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 = %{
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue