towerops/lib/towerops_web/live/help_live/index.ex
Graham McInitre 9d4a5f6d81 refactor: comprehensive simplification and functional programming improvements
- help_live/index.ex: 2,642→173 lines, 15 section modules + sidebar extracted
  MikroTik now real section, dead if false removed
- agent_channel.ex: 2,506→1,751 lines, 3 helper modules extracted
  (heartbeat/subscriptions/job_builder), decode_and_process/4 eliminates
  8 repeated handle_in patterns, guard-based size checks
- antenna_catalog.ex: 1,174→47 lines, 107 specs → priv/antennas/catalog.json
- topology.ex: unbounded query → batched loading (100/batch),
  all guard errors fixed, zero if/case/cond conditionals
- proto: decoder_macros.ex + field_specs.ex infrastructure for
  macro-generated protobuf decoders
2026-07-21 17:03:47 -05:00

173 lines
5.9 KiB
Elixir

defmodule ToweropsWeb.HelpLive.Index do
@moduledoc false
use ToweropsWeb, :live_view
alias Towerops.Accounts
alias Towerops.Accounts.Scope
alias Towerops.HTTP
alias Towerops.Organizations
alias ToweropsWeb.HelpLive.Sections.About
alias ToweropsWeb.HelpLive.Sections.Agents
alias ToweropsWeb.HelpLive.Sections.ApiTokens
alias ToweropsWeb.HelpLive.Sections.CloudPollers
alias ToweropsWeb.HelpLive.Sections.GettingStarted
alias ToweropsWeb.HelpLive.Sections.GraphqlApi
alias ToweropsWeb.HelpLive.Sections.Graphs
alias ToweropsWeb.HelpLive.Sections.Insights
alias ToweropsWeb.HelpLive.Sections.Integrations
alias ToweropsWeb.HelpLive.Sections.Mikrotik
alias ToweropsWeb.HelpLive.Sections.NetworkMap
alias ToweropsWeb.HelpLive.Sections.NotFound
alias ToweropsWeb.HelpLive.Sections.RestApi
alias ToweropsWeb.HelpLive.Sections.Settings
alias ToweropsWeb.HelpLive.Sections.Sites
@impl true
def mount(_params, session, socket) do
# Check if user is logged in via session
current_user = get_current_user(socket, session)
is_authenticated = !is_nil(current_user)
# Load user's default organization if authenticated
current_organization =
if current_user do
case Organizations.list_user_organizations(current_user.id) do
[first_org | _] -> first_org
[] -> nil
end
end
# Build current_scope if authenticated
current_scope =
if current_user do
current_user
|> Scope.for_user()
|> Scope.put_organization(current_organization)
end
socket =
socket
|> assign(:page_title, t("Help"))
|> assign(:current_user, current_user)
|> assign(:is_authenticated, is_authenticated)
|> assign(:current_scope, current_scope)
|> assign(:generated_password, nil)
|> assign(:password_generating, false)
# Generate initial password
if connected?(socket) do
send(self(), :fetch_random_password)
end
{:ok, socket}
end
@impl true
def handle_params(params, _url, socket) do
section = Map.get(params, "section", "about")
socket =
socket
|> assign(:active_section, section)
|> assign(:generated_password, nil)
|> assign(:password_generating, false)
{:noreply, socket}
end
@impl true
def handle_event("generate_password", _params, socket) do
socket = assign(socket, :password_generating, true)
send(self(), :fetch_random_password)
{:noreply, socket}
end
@impl true
def handle_info(:fetch_random_password, socket) do
case generate_random_password() do
{:ok, password} ->
{:noreply,
socket
|> assign(:generated_password, password)
|> assign(:password_generating, false)}
{:error, reason} ->
{:noreply,
socket
|> assign(:password_generating, false)
|> put_flash(:error, t("Failed to generate password: %{reason}", reason: reason))}
end
end
defp generate_random_password do
# Generate a 24-character truly random password from random.org
# Format: uppercase, lowercase, and digits
url =
"https://www.random.org/strings/?num=1&len=24&digits=on&upperalpha=on&loweralpha=on&unique=on&format=plain&rnd=new"
case HTTP.get(__MODULE__, url) do
{:ok, %{status: 200, body: body}} ->
password = String.trim(body)
{:ok, password}
{:ok, %{status: status}} ->
{:error, "random.org returned status #{status}"}
{:error, error} ->
{:error, format_request_error(error)}
end
end
defp format_request_error(%_{} = error), do: Exception.message(error)
defp format_request_error(error), do: inspect(error)
defp get_current_user(socket, session) do
# Try to get current_user from socket assigns (if already set by on_mount)
with nil <- Map.get(socket.assigns, :current_user),
token when not is_nil(token) <- session["user_token"],
{user, _authenticated_at} <- Accounts.get_user_by_session_token(token) do
user
else
%Towerops.Accounts.User{} = user -> user
_ -> nil
end
end
defp help_content(assigns) do
~H"""
<div class="w-full">
<div class="mb-6">
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">Help & Documentation</h1>
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
Learn how to use Towerops to monitor your network infrastructure
</p>
</div>
<div class="grid grid-cols-1 lg:grid-cols-4 gap-6">
<ToweropsWeb.HelpLive.Sidebar.sidebar active_section={@active_section} />
<div class="lg:col-span-3">
<div class="bg-white dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-white/10">
{render_section(@active_section, assigns)}
</div>
</div>
</div>
</div>
"""
end
defp render_section("about", assigns), do: About.render(assigns)
defp render_section("getting-started", assigns), do: GettingStarted.render(assigns)
defp render_section("settings", assigns), do: Settings.render(assigns)
defp render_section("sites", assigns), do: Sites.render(assigns)
defp render_section("cloud-pollers", assigns), do: CloudPollers.render(assigns)
defp render_section("agents", assigns), do: Agents.render(assigns)
defp render_section("integrations", assigns), do: Integrations.render(assigns)
defp render_section("graphs", assigns), do: Graphs.render(assigns)
defp render_section("insights", assigns), do: Insights.render(assigns)
defp render_section("network-map", assigns), do: NetworkMap.render(assigns)
defp render_section("api-tokens", assigns), do: ApiTokens.render(assigns)
defp render_section("rest-api", assigns), do: RestApi.render(assigns)
defp render_section("graphql-api", assigns), do: GraphqlApi.render(assigns)
defp render_section("mikrotik", assigns), do: Mikrotik.render(assigns)
defp render_section(_section, assigns), do: NotFound.render(assigns)
end