<.link navigate={~p"/orgs/#{@organization.slug}/settings"} class="inline-flex items-center gap-1 text-sm text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white" > <.icon name="hero-arrow-left" class="h-4 w-4" /> Back to Settings

{t("Integrations")}

{t("Connect third-party services to enhance your monitoring capabilities.")}

{category.name}

{category.description}

<%= if category.exclusive do %> <%!-- Exclusive category (billing): show as picker + single config panel --%> <% active_billing = Enum.find(category.providers, fn p -> @integrations[p.id] && @integrations[p.id].enabled end) %>
<.icon name="hero-arrow-path" class="h-3.5 w-3.5" /> Choose one — only one billing platform can be active at a time
<%= if active_billing do %> <%!-- Active billing provider --%> <% integration = @integrations[active_billing.id] %>
<.icon name={active_billing.icon} class="h-6 w-6 text-indigo-600 dark:text-indigo-400" />

{active_billing.name}

Connected

{active_billing.description}

<%= if integration.last_synced_at do %> {t("Last synced:")} <.timestamp datetime={integration.last_synced_at} timezone={@timezone} format="absolute" /> <% end %> <%= if integration.last_sync_status && integration.last_sync_status != "never" do %> "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400" "partial" -> "bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400" "failed" -> "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400" _ -> "bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400" end ]}> {String.capitalize(integration.last_sync_status)} <% end %> · Syncs every {humanize_interval(integration.sync_interval_minutes)} <%= if integration.last_synced_at do %> · Next in ~{next_sync_minutes( integration.last_synced_at, integration.sync_interval_minutes )}m <% end %> <%= if active_billing.id == "gaiia" do %> <.link navigate={ ~p"/orgs/#{@organization.slug}/settings/integrations/gaiia/mapping" } class="text-sm text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300" > Device & Site Linking → <.link navigate={ ~p"/orgs/#{@organization.slug}/settings/integrations/gaiia/reconciliation" } class="text-sm text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300" > Comparison Report → <% end %>
<%!-- Switch provider link --%>

Using {active_billing.name}. To switch providers, disable this integration first.

<% else %> <%!-- No active billing — show provider picker --%>

{t("Which billing platform does your organization use?")}

<% end %> <%!-- Config form for whichever billing provider is being configured --%> <%= if @configuring && find_category_id(@configuring) == "billing" do %> <% provider = Enum.find(category.providers, &(&1.id == @configuring)) %>

Configure {provider.name}

<.form for={@form} id={"#{provider.id}-form"} phx-change="validate" phx-submit="save" >
<%= if provider.id in ["splynx", "sonar"] do %> <.input field={@form[:instance_url]} type="text" label={t("Instance URL")} placeholder={"https://your-instance.#{provider.id}.com"} autocomplete="off" value={get_credential(@integrations[provider.id], "instance_url")} /> <% end %> <%= if provider.id == "sonar" do %> <.input field={@form[:api_token]} type="password" label={t("API Token")} placeholder="Enter your Sonar API token" autocomplete="off" value={get_credential(@integrations[provider.id], "api_token")} /> <% else %> <.input field={@form[:api_key]} type="password" label={t("API Key")} placeholder={"Enter your #{provider.name} API key"} autocomplete="off" value={get_credential(@integrations[provider.id], "api_key")} /> <% end %> <%= if provider.id == "splynx" do %> <.input field={@form[:api_secret]} type="password" label={t("API Secret")} placeholder="Enter your Splynx API secret" autocomplete="off" value={get_credential(@integrations[provider.id], "api_secret")} /> <% end %> <.input field={@form[:sync_interval_minutes]} type="number" label={t("Sync interval (minutes)")} min="5" value={ if(@integrations[provider.id], do: @integrations[provider.id].sync_interval_minutes, else: if(provider.id == "gaiia", do: 15, else: 10) ) } /> <%= if @test_result do %>
"bg-green-50 dark:bg-green-900/20" {:error, _} -> "bg-red-50 dark:bg-red-900/20" end ]}>

"text-green-800 dark:text-green-200" {:error, _} -> "text-red-800 dark:text-red-200" end ]}> {elem(@test_result, 1)}

<% end %>
<%!-- Gaiia webhook config --%> <%= if @configuring == "gaiia" && @integrations["gaiia"] do %>

{t("Webhook Configuration")}

{t( "Webhooks keep your data in sync in real-time. Without webhooks, data is only updated during scheduled syncs (every %{interval} minutes). With webhooks enabled, changes in Gaiia are reflected here within seconds.", interval: if(@integrations["gaiia"], do: @integrations["gaiia"].sync_interval_minutes, else: 15 ) )}

{t("What webhooks enable")}
  • <.icon name="hero-user-group" class="mt-0.5 h-3.5 w-3.5 shrink-0 text-gray-400" /> Account changes — new subscribers, status changes, and address updates are reflected instantly for accurate outage impact analysis.
  • <.icon name="hero-credit-card" class="mt-0.5 h-3.5 w-3.5 shrink-0 text-gray-400" /> Billing subscription changes — plan upgrades, cancellations, and MRR changes stay current so revenue impact estimates are always accurate.
  • <.icon name="hero-wrench-screwdriver" class="mt-0.5 h-3.5 w-3.5 shrink-0 text-gray-400" /> Inventory item changes — equipment assignments, IP changes, and new installs are matched to your monitored devices automatically.

{t( "Used to verify that incoming webhooks are genuinely from Gaiia. Keep this secret." )}

{t("Setup Instructions")}
  1. In your Gaiia admin panel, navigate to Settings → Webhooks
  2. Click Add Webhook
  3. Paste the Webhook URL shown above into the endpoint field
  4. Copy the Webhook Secret above and paste it into Gaiia's "Secret" field
  5. Enable the following event types: Account, Billing Subscription, Inventory Item
  6. Save the webhook configuration in Gaiia

{t( "Once configured, Gaiia will send updates to Towerops automatically. You can verify it's working by making a change in Gaiia and checking that it appears here within a few seconds." )}

<% end %> <% end %>
<% else %> <%!-- Non-exclusive categories: one card per provider --%>
<.icon name={provider.icon} class="h-6 w-6 text-indigo-600 dark:text-indigo-400" />

{provider.name}

{provider.description}

<%= if integration = @integrations[provider.id] do %>
{if integration.enabled, do: "Enabled", else: "Disabled"} <%= if integration.last_synced_at do %> {t("Last synced:")} <.timestamp datetime={integration.last_synced_at} timezone={@timezone} format="absolute" /> <% end %> <%= if integration.last_sync_status && integration.last_sync_status != "never" do %> "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400" "partial" -> "bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400" "failed" -> "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400" _ -> "bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400" end ]}> {String.capitalize(integration.last_sync_status)} <% end %> · Syncs every {humanize_interval(integration.sync_interval_minutes)} <%= if integration.last_synced_at do %> · Next in ~{next_sync_minutes( integration.last_synced_at, integration.sync_interval_minutes )}m <% end %> <%= if provider.id == "preseem" do %> <.link navigate={ ~p"/orgs/#{@organization.slug}/settings/integrations/preseem/devices" } class="text-sm text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300" > Manage Devices → <.link navigate={~p"/insights?source=preseem"} class="text-sm text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300" > Network Insights → <% end %>
<% end %>
<%= if integration = @integrations[provider.id] do %> <% end %>
<%= if @configuring == provider.id do %>
<.form for={@form} id={"#{provider.id}-form"} phx-change="validate" phx-submit="save" >
<%= if provider.id == "cn_maestro" do %> <.input field={@form[:url]} type="text" label={t("cnMaestro URL")} placeholder="https://cloud.cambiumnetworks.com" autocomplete="off" value={get_credential(@integrations[provider.id], "url")} /> <.input field={@form[:client_id]} type="text" label={t("Client ID")} placeholder="Your OAuth Client ID" autocomplete="off" value={get_credential(@integrations[provider.id], "client_id")} /> <.input field={@form[:client_secret]} type="password" label={t("Client Secret")} placeholder="Your OAuth Client Secret" autocomplete="off" value={get_credential(@integrations[provider.id], "client_secret")} /> <% end %> <%= if provider.id == "uisp" do %> <.input field={@form[:url]} type="text" label={t("UISP URL")} placeholder="https://uisp.example.com/nms/api/v2.1" autocomplete="off" value={get_credential(@integrations[provider.id], "url")} /> <% end %> <.input field={@form[:api_key]} type="password" label={t("API Key")} placeholder={"Enter your #{provider.name} API key"} autocomplete="off" value={get_credential(@integrations[provider.id], "api_key")} /> <.input field={@form[:sync_interval_minutes]} type="number" label={t("Sync interval (minutes)")} min="5" value={ if(@integrations[provider.id], do: @integrations[provider.id].sync_interval_minutes, else: 10 ) } /> <%= if @test_result do %>
"bg-green-50 dark:bg-green-900/20" {:error, _} -> "bg-red-50 dark:bg-red-900/20" end ]}>

"text-green-800 dark:text-green-200" {:error, _} -> "text-red-800 dark:text-red-200" end ]}> {elem(@test_result, 1)}

<% end %>
<% end %>
<% end %>