towerops/lib/towerops_web/live/org/integrations_live.ex
Graham McIntire 21bbcccc30
fix preseem api base url and remove base_url override
the default base url was pointing at the docs site
(apidocs.preseem.com) instead of the actual api endpoint
(api.preseem.com), causing all api calls to fail with
unauthorized. removed the unnecessary base_url user override
since the api endpoint is fixed.
2026-02-13 10:12:43 -06:00

202 lines
5.9 KiB
Elixir

defmodule ToweropsWeb.Org.IntegrationsLive do
@moduledoc false
use ToweropsWeb, :live_view
alias Towerops.Integrations
alias Towerops.Integrations.Integration
alias Towerops.Preseem.Client, as: PreseemClient
@providers [
%{
id: "preseem",
name: "Preseem",
description: "QoE monitoring and subscriber experience analytics for wireless ISPs.",
icon: "hero-signal"
}
]
@impl true
def mount(_params, _session, socket) do
organization = socket.assigns.current_scope.organization
integrations = load_integrations(organization.id)
{:ok,
socket
|> assign(:organization, organization)
|> assign(:providers, @providers)
|> assign(:integrations, integrations)
|> assign(:configuring, nil)
|> assign(:form, nil)
|> assign(:test_result, nil)}
end
@impl true
def handle_event("configure", %{"provider" => provider}, socket) do
integration = Map.get(socket.assigns.integrations, provider)
form =
case integration do
nil ->
%Integration{}
|> Integrations.change_integration(%{provider: provider})
|> to_form()
existing ->
existing
|> Integrations.change_integration(%{})
|> to_form()
end
{:noreply,
socket
|> assign(:configuring, provider)
|> assign(:form, form)
|> assign(:test_result, nil)}
end
@impl true
def handle_event("close_config", _params, socket) do
{:noreply,
socket
|> assign(:configuring, nil)
|> assign(:form, nil)
|> assign(:test_result, nil)}
end
@impl true
def handle_event("validate", %{"integration" => params}, socket) do
integration = get_current_integration(socket)
changeset =
integration
|> Integrations.change_integration(normalize_params(params))
|> Map.put(:action, :validate)
{:noreply, assign(socket, :form, to_form(changeset))}
end
@impl true
def handle_event("save", %{"integration" => params}, socket) do
organization = socket.assigns.organization
provider = socket.assigns.configuring
existing = Map.get(socket.assigns.integrations, provider)
attrs = normalize_params(params)
result =
case existing do
nil ->
Integrations.create_integration(organization.id, Map.put(attrs, :provider, provider))
integration ->
Integrations.update_integration(integration, attrs)
end
case result do
{:ok, _integration} ->
integrations = load_integrations(organization.id)
{:noreply,
socket
|> assign(:integrations, integrations)
|> assign(:configuring, nil)
|> assign(:form, nil)
|> assign(:test_result, nil)
|> put_flash(:info, "Integration saved successfully")}
{:error, changeset} ->
{:noreply, assign(socket, :form, to_form(changeset))}
end
end
@impl true
def handle_event("test_connection", _params, socket) do
api_key = extract_api_key(socket)
if api_key == "" or is_nil(api_key) do
{:noreply, assign(socket, :test_result, {:error, "Please enter an API key first"})}
else
result = PreseemClient.test_connection(api_key)
{:noreply, assign(socket, :test_result, format_connection_result(result))}
end
end
@impl true
def handle_event("toggle_enabled", %{"provider" => provider}, socket) do
case Map.get(socket.assigns.integrations, provider) do
nil ->
{:noreply, socket}
integration ->
case Integrations.update_integration(integration, %{enabled: !integration.enabled}) do
{:ok, _updated} ->
integrations = load_integrations(socket.assigns.organization.id)
{:noreply,
socket
|> assign(:integrations, integrations)
|> put_flash(:info, "Integration updated")}
{:error, _changeset} ->
{:noreply, put_flash(socket, :error, "Failed to update integration")}
end
end
end
defp load_integrations(organization_id) do
organization_id
|> Integrations.list_integrations()
|> Map.new(fn integration -> {integration.provider, integration} end)
end
defp get_current_integration(socket) do
provider = socket.assigns.configuring
case Map.get(socket.assigns.integrations, provider) do
nil -> %Integration{}
integration -> integration
end
end
defp normalize_params(params) do
api_key = Map.get(params, "api_key", "")
%{credentials: %{"api_key" => api_key}}
end
defp get_credential(nil, _key), do: ""
defp get_credential(%Integration{credentials: credentials}, key) when is_map(credentials) do
Map.get(credentials, key, "")
end
defp get_credential(_integration, _key), do: ""
defp extract_api_key(socket) do
changeset = socket.assigns.form.source
data = Ecto.Changeset.apply_changes(changeset)
case data.credentials do
%{"api_key" => key} -> key
_ -> nil
end
end
defp format_connection_result({:ok, _body}), do: {:ok, "Connection successful"}
defp format_connection_result({:error, :unauthorized}), do: {:error, "Invalid API key"}
defp format_connection_result({:error, :forbidden}), do: {:error, "Access forbidden"}
defp format_connection_result({:error, {:unexpected_status, status}}),
do: {:error, "API returned unexpected status: #{status}"}
defp format_connection_result({:error, %{reason: :timeout}}),
do: {:error, "Connection timeout - unable to reach Preseem API"}
defp format_connection_result({:error, %{reason: :econnrefused}}),
do: {:error, "Connection refused - check API endpoint"}
defp format_connection_result({:error, %{reason: :nxdomain}}), do: {:error, "DNS lookup failed - check API endpoint"}
# Never expose internal error details to users
defp format_connection_result({:error, _reason}),
do: {:error, "Connection failed - please check your API credentials and try again"}
end