- 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
173 lines
5.9 KiB
Elixir
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
|