Promoted pure presentation and utility helpers from `defp` to `def @doc false` across ~20 LiveViews, Oban workers, and sync modules so they're reachable from unit tests. Refactored several `cond` blocks into idiomatic function heads with guards. Added ~250 new test cases in new files under test/towerops and test/towerops_web, including DB-backed tests for CnMaestro.Sync and AlertNotificationWorker, and removed dead LiveView tab components and CapacityLive (no callers anywhere in lib/test). Configured mix.exs test_coverage.ignore_modules to exclude vendored third-party code (SnmpKit, protobuf-generated Towerops.Agent.*, Absinthe GraphQL types, Phoenix HTML modules, Inspect protocol impls) from coverage calculations — these are not our project code. Coverage: 66.93% → 70.09%. Full suite: 10,127 tests, 0 failures.
259 lines
7.1 KiB
Elixir
259 lines
7.1 KiB
Elixir
defmodule ToweropsWeb.OnboardingLive do
|
|
@moduledoc false
|
|
use ToweropsWeb, :live_view
|
|
|
|
alias Towerops.Agents
|
|
alias Towerops.Integrations
|
|
alias Towerops.Integrations.Integration
|
|
alias Towerops.Organizations
|
|
alias Towerops.Sites
|
|
alias Towerops.Sites.Site
|
|
|
|
@steps [:snmp, :site, :billing, :agent]
|
|
|
|
@billing_providers [
|
|
%{id: "gaiia", name: "Gaiia", icon: "hero-user-group"},
|
|
%{id: "sonar", name: "Sonar", icon: "hero-currency-dollar"},
|
|
%{id: "splynx", name: "Splynx", icon: "hero-banknotes"},
|
|
%{id: "visp", name: "VISP", icon: "hero-cloud"}
|
|
]
|
|
|
|
@impl true
|
|
def mount(_params, _session, socket) do
|
|
organization = socket.assigns.current_scope.organization
|
|
changeset = Organizations.change_organization(organization)
|
|
|
|
site_changeset = Sites.change_site(%Site{}, %{})
|
|
|
|
{:ok,
|
|
socket
|
|
|> assign(:page_title, t("Setup"))
|
|
|> assign(:organization, organization)
|
|
|> assign(:step, :snmp)
|
|
|> assign(:steps, @steps)
|
|
|> assign(:form, to_form(changeset))
|
|
|> assign(:site_form, to_form(site_changeset, as: "site"))
|
|
|> assign(:billing_providers, @billing_providers)
|
|
|> assign(:selected_provider, nil)
|
|
|> assign(:integration_form, nil)
|
|
|> assign(:agent_token, nil)
|
|
|> assign(:agent_token_value, nil)}
|
|
end
|
|
|
|
@impl true
|
|
def handle_params(_params, _url, socket) do
|
|
{:noreply, socket}
|
|
end
|
|
|
|
# SNMP step events
|
|
|
|
@impl true
|
|
def handle_event("validate_snmp", %{"organization" => params}, socket) do
|
|
changeset =
|
|
socket.assigns.organization
|
|
|> Organizations.change_organization(params)
|
|
|> Map.put(:action, :validate)
|
|
|
|
{:noreply, assign(socket, :form, to_form(changeset))}
|
|
end
|
|
|
|
@impl true
|
|
def handle_event("save_snmp", %{"organization" => params}, socket) do
|
|
case Organizations.update_organization(socket.assigns.organization, params) do
|
|
{:ok, organization} ->
|
|
{:noreply,
|
|
socket
|
|
|> assign(:organization, organization)
|
|
|> put_flash(:info, t("SNMP configuration saved"))
|
|
|> assign(:step, :site)}
|
|
|
|
{:error, changeset} ->
|
|
{:noreply, assign(socket, :form, to_form(changeset))}
|
|
end
|
|
end
|
|
|
|
# Site step events
|
|
|
|
@impl true
|
|
def handle_event("validate_site", %{"site" => params}, socket) do
|
|
changeset =
|
|
%Site{}
|
|
|> Sites.change_site(params)
|
|
|> Map.put(:action, :validate)
|
|
|
|
{:noreply, assign(socket, :site_form, to_form(changeset, as: "site"))}
|
|
end
|
|
|
|
@impl true
|
|
def handle_event("save_site", %{"site" => params}, socket) do
|
|
org = socket.assigns.organization
|
|
attrs = Map.put(params, "organization_id", org.id)
|
|
|
|
case Sites.create_site(attrs) do
|
|
{:ok, _site} ->
|
|
# Enable sites on the org if not already
|
|
if !org.use_sites do
|
|
Organizations.update_organization(org, %{use_sites: true})
|
|
end
|
|
|
|
{:noreply,
|
|
socket
|
|
|> put_flash(:info, t("Site created"))
|
|
|> assign(:step, :billing)}
|
|
|
|
{:error, changeset} ->
|
|
{:noreply, assign(socket, :site_form, to_form(changeset, as: "site"))}
|
|
end
|
|
end
|
|
|
|
# Billing step events
|
|
|
|
@impl true
|
|
def handle_event("select_provider", %{"provider" => provider}, socket) do
|
|
form =
|
|
%Integration{}
|
|
|> Integrations.change_integration(%{provider: provider})
|
|
|> to_form()
|
|
|
|
{:noreply,
|
|
socket
|
|
|> assign(:selected_provider, provider)
|
|
|> assign(:integration_form, form)}
|
|
end
|
|
|
|
@impl true
|
|
def handle_event("validate_integration", %{"integration" => params}, socket) do
|
|
changeset =
|
|
%Integration{}
|
|
|> Integrations.change_integration(normalize_integration_params(params, socket.assigns.selected_provider))
|
|
|> Map.put(:action, :validate)
|
|
|
|
{:noreply, assign(socket, :integration_form, to_form(changeset))}
|
|
end
|
|
|
|
@impl true
|
|
def handle_event("save_integration", %{"integration" => params}, socket) do
|
|
org = socket.assigns.organization
|
|
provider = socket.assigns.selected_provider
|
|
attrs = params |> normalize_integration_params(provider) |> Map.put(:provider, provider) |> Map.put(:enabled, true)
|
|
|
|
case Integrations.create_integration(org.id, attrs) do
|
|
{:ok, _integration} ->
|
|
{:noreply,
|
|
socket
|
|
|> put_flash(:info, t("Integration saved"))
|
|
|> assign(:step, :agent)
|
|
|> load_agent_token()}
|
|
|
|
{:error, changeset} ->
|
|
{:noreply, assign(socket, :integration_form, to_form(changeset))}
|
|
end
|
|
end
|
|
|
|
# Navigation events
|
|
|
|
@impl true
|
|
def handle_event("skip", _params, socket) do
|
|
next = next_step(socket.assigns.step)
|
|
|
|
socket =
|
|
if next == :done do
|
|
complete_onboarding(socket)
|
|
else
|
|
socket = assign(socket, :step, next)
|
|
if next == :agent, do: load_agent_token(socket), else: socket
|
|
end
|
|
|
|
{:noreply, socket}
|
|
end
|
|
|
|
@impl true
|
|
def handle_event("finish", _params, socket) do
|
|
{:noreply, complete_onboarding(socket)}
|
|
end
|
|
|
|
@impl true
|
|
def handle_event("go_to_step", %{"step" => step}, socket) do
|
|
step = String.to_existing_atom(step)
|
|
|
|
socket =
|
|
if step == :agent do
|
|
socket |> assign(:step, step) |> load_agent_token()
|
|
else
|
|
assign(socket, :step, step)
|
|
end
|
|
|
|
{:noreply, socket}
|
|
end
|
|
|
|
# Private helpers
|
|
|
|
@doc false
|
|
def next_step(:snmp), do: :site
|
|
def next_step(:site), do: :billing
|
|
def next_step(:billing), do: :agent
|
|
def next_step(:agent), do: :done
|
|
|
|
@doc false
|
|
def step_index(:snmp), do: 0
|
|
def step_index(:site), do: 1
|
|
def step_index(:billing), do: 2
|
|
def step_index(:agent), do: 3
|
|
|
|
defp step_label(:snmp), do: t("SNMP")
|
|
defp step_label(:site), do: t("Site")
|
|
defp step_label(:billing), do: t("Billing")
|
|
defp step_label(:agent), do: t("Agent")
|
|
|
|
defp complete_onboarding(socket) do
|
|
org = socket.assigns.organization
|
|
Organizations.update_organization(org, %{onboarding_completed: true})
|
|
|
|
socket
|
|
|> put_flash(:info, t("Setup complete"))
|
|
|> push_navigate(to: ~p"/dashboard")
|
|
end
|
|
|
|
defp load_agent_token(socket) do
|
|
org = socket.assigns.organization
|
|
tokens = Agents.list_organization_agent_tokens(org.id)
|
|
|
|
case tokens do
|
|
[token | _] ->
|
|
assign(socket, agent_token: token, agent_token_value: nil)
|
|
|
|
[] ->
|
|
case Agents.create_agent_token(org.id, "Default Agent") do
|
|
{:ok, token, token_value} ->
|
|
assign(socket, agent_token: token, agent_token_value: token_value)
|
|
|
|
{:error, _} ->
|
|
assign(socket, agent_token: nil, agent_token_value: nil)
|
|
end
|
|
end
|
|
end
|
|
|
|
@doc false
|
|
def normalize_integration_params(params, "sonar") do
|
|
%{
|
|
credentials: %{
|
|
"instance_url" => String.trim(Map.get(params, "instance_url", "")),
|
|
"api_token" => Map.get(params, "api_token", "")
|
|
}
|
|
}
|
|
end
|
|
|
|
def normalize_integration_params(params, "splynx") do
|
|
%{
|
|
credentials: %{
|
|
"instance_url" => String.trim(Map.get(params, "instance_url", "")),
|
|
"api_key" => Map.get(params, "api_key", ""),
|
|
"api_secret" => Map.get(params, "api_secret", "")
|
|
}
|
|
}
|
|
end
|
|
|
|
def normalize_integration_params(params, _provider) do
|
|
%{credentials: %{"api_key" => Map.get(params, "api_key", "")}}
|
|
end
|
|
end
|