feat: add API key setup help to integration configuration panels

Add setup_help info boxes to each integration provider showing where
to create API keys, step-by-step instructions, required permissions,
and example URL patterns.
This commit is contained in:
Graham McIntire 2026-03-11 09:46:22 -05:00
parent 5e5da89f3a
commit 198f2c3cbc
No known key found for this signature in database
2 changed files with 176 additions and 14 deletions

View file

@ -27,27 +27,67 @@ defmodule ToweropsWeb.Org.SettingsLive do
name: "Gaiia",
description:
"Billing and subscriber management. Enables outage impact analysis, inventory reconciliation, and subscriber-aware monitoring.",
icon: "hero-user-group"
icon: "hero-user-group",
setup_help: %{
url_pattern: "https://app.gaiia.com/{your-org}/admin/apiKeys",
steps: [
"Log into Gaiia and go to **Admin** → **API Keys**",
"Click **Create API Key**",
"Give it a descriptive name (e.g. \"TowerOps\")",
"Copy the generated API key and paste it below"
],
permissions:
"Read-only access. The key only needs permission to read accounts, subscriptions, and inventory data."
}
},
%{
id: "sonar",
name: "Sonar",
description:
"Billing and subscriber management. Sync accounts, network sites, and inventory from your Sonar instance.",
icon: "hero-currency-dollar"
icon: "hero-currency-dollar",
setup_help: %{
url_pattern: "https://{your-instance}.sonar.software/admin/users/{your-user-id}",
steps: [
"Log into Sonar and go to **Settings** → **Security** → **API Tokens** (or your user profile → API Tokens)",
"Click **Create Token**",
"Name it (e.g. \"TowerOps\") and copy the generated token"
],
permissions:
"Requires a **read-only** personal access token. The token needs access to: Accounts, Network Sites, Inventory Items, and Address data."
}
},
%{
id: "splynx",
name: "Splynx",
description:
"ISP billing and management platform. Sync customers, internet services, and network routers from Splynx.",
icon: "hero-banknotes"
icon: "hero-banknotes",
setup_help: %{
url_pattern: "https://{your-instance}.splynx.app/admin/administration/api-keys",
steps: [
"Log into Splynx and go to **Administration** → **API Keys**",
"Click **Add** to create a new API key",
"Copy both the **API Key** and **API Secret**"
],
permissions:
"Requires read access to **Customers**, **Internet Services**, and **Routers**. No write permissions needed."
}
},
%{
id: "visp",
name: "VISP",
description: "Cloud-based ISP management. Sync subscribers, sites, equipment, and MRR data from VISP.",
icon: "hero-cloud"
icon: "hero-cloud",
setup_help: %{
url_pattern: "https://app.visp.net/settings/api",
steps: [
"Log into VISP and go to **Settings** → **API**",
"Click **Generate New Key**",
"Name it (e.g. \"TowerOps\") and copy the generated API key"
],
permissions: "Requires read-only API access to **Subscribers**, **Sites**, **Equipment**, and **Billing** data."
}
},
# Quality of Experience
%{
@ -55,7 +95,16 @@ defmodule ToweropsWeb.Org.SettingsLive do
name: "Preseem",
description: "QoE monitoring and subscriber experience analytics for wireless ISPs.",
icon: "hero-signal",
category: "Quality of Experience"
category: "Quality of Experience",
setup_help: %{
url_pattern: "https://app.preseem.com/settings/api",
steps: [
"Log into Preseem and go to **Settings** → **API**",
"Click **Generate API Key**",
"Copy the generated key and paste it below"
],
permissions: "Read-only access. The key needs permission to read subscriber QoE metrics and access point data."
}
},
# Incident Management
%{
@ -73,7 +122,18 @@ defmodule ToweropsWeb.Org.SettingsLive do
description:
"Infrastructure source of truth. Sync devices, sites, IP addresses, and interfaces between TowerOps and your NetBox instance.",
icon: "hero-server-stack",
category: "Infrastructure & IPAM"
category: "Infrastructure & IPAM",
setup_help: %{
url_pattern: "https://{your-netbox-instance}/user/api-tokens/",
steps: [
"Log into NetBox and go to your **profile** (top right) → **API Tokens**",
"Click **Add a token**",
"Optionally set an expiry date and description (e.g. \"TowerOps\")",
"Copy the generated token"
],
permissions:
"Requires **read** permission at minimum. If you want to push data to NetBox (bidirectional sync), the token also needs **write** permission."
}
}
]
@ -686,6 +746,10 @@ defmodule ToweropsWeb.Org.SettingsLive do
defp get_credential(_integration, _key), do: ""
defp format_help_step(text) do
String.replace(text, ~r/\*\*(.+?)\*\*/, "<strong>\\1</strong>")
end
defp current_integration(socket) do
provider = socket.assigns.configuring
Map.get(socket.assigns.integrations, provider)

View file

@ -1190,10 +1190,7 @@
</h4>
</div>
<p class="text-xs text-gray-500 dark:text-gray-400 mb-4 ml-6">
{t(
"Your NetBox instance URL and API token. The token needs read permission at minimum."
)}
{t("Write permission is required if you want to push data to NetBox.")}
{t("Your NetBox instance URL and API token.")}
</p>
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
<div>
@ -1223,6 +1220,31 @@
/>
</div>
</div>
<div class="mt-4 rounded-lg bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800/50 p-4">
<h4 class="flex items-center gap-1.5 text-xs font-semibold text-blue-900 dark:text-blue-300 mb-2">
<.icon name="hero-information-circle" class="h-4 w-4" />
{t("How to create your API token")}
</h4>
<ol class="list-decimal list-inside space-y-1 text-xs text-blue-800 dark:text-blue-300 ml-5">
<%= for step <- provider.setup_help.steps do %>
<li>{raw(format_help_step(step))}</li>
<% end %>
</ol>
<p class="mt-2.5 text-xs text-blue-700 dark:text-blue-400 ml-5">
<.icon
name="hero-shield-check"
class="h-3.5 w-3.5 inline -mt-0.5"
/>
{raw(format_help_step(provider.setup_help.permissions))}
</p>
<p class="mt-2 text-xs text-blue-600/80 dark:text-blue-400/70 ml-5">
<.icon
name="hero-arrow-top-right-on-square"
class="h-3 w-3 inline -mt-0.5"
/> Example URL:
<code class="font-mono">{provider.setup_help.url_pattern}</code>
</p>
</div>
</div>
<%!-- Sync Direction Section --%>
@ -1577,7 +1599,7 @@
</h4>
</div>
<p class="text-xs text-gray-500 dark:text-gray-400 mb-4 ml-6">
{t("Your Sonar instance URL and API token from the Sonar admin panel.")}
{t("Your Sonar instance URL and API token.")}
</p>
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
<div>
@ -1607,6 +1629,31 @@
/>
</div>
</div>
<div class="mt-4 rounded-lg bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800/50 p-4">
<h4 class="flex items-center gap-1.5 text-xs font-semibold text-blue-900 dark:text-blue-300 mb-2">
<.icon name="hero-information-circle" class="h-4 w-4" />
{t("How to create your API token")}
</h4>
<ol class="list-decimal list-inside space-y-1 text-xs text-blue-800 dark:text-blue-300 ml-5">
<%= for step <- provider.setup_help.steps do %>
<li>{raw(format_help_step(step))}</li>
<% end %>
</ol>
<p class="mt-2.5 text-xs text-blue-700 dark:text-blue-400 ml-5">
<.icon
name="hero-shield-check"
class="h-3.5 w-3.5 inline -mt-0.5"
/>
{raw(format_help_step(provider.setup_help.permissions))}
</p>
<p class="mt-2 text-xs text-blue-600/80 dark:text-blue-400/70 ml-5">
<.icon
name="hero-arrow-top-right-on-square"
class="h-3 w-3 inline -mt-0.5"
/> Example URL:
<code class="font-mono">{provider.setup_help.url_pattern}</code>
</p>
</div>
</div>
<div class="border-t border-gray-200 pt-5 dark:border-white/10">
@ -1682,9 +1729,7 @@
</h4>
</div>
<p class="text-xs text-gray-500 dark:text-gray-400 mb-4 ml-6">
{t(
"Your Splynx instance URL, API key, and API secret. Find these in Splynx under Administration → API Keys."
)}
{t("Your Splynx instance URL, API key, and API secret.")}
</p>
<div class="grid grid-cols-1 gap-4">
<div>
@ -1729,6 +1774,31 @@
</div>
</div>
</div>
<div class="mt-4 rounded-lg bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800/50 p-4">
<h4 class="flex items-center gap-1.5 text-xs font-semibold text-blue-900 dark:text-blue-300 mb-2">
<.icon name="hero-information-circle" class="h-4 w-4" />
{t("How to create your API key")}
</h4>
<ol class="list-decimal list-inside space-y-1 text-xs text-blue-800 dark:text-blue-300 ml-5">
<%= for step <- provider.setup_help.steps do %>
<li>{raw(format_help_step(step))}</li>
<% end %>
</ol>
<p class="mt-2.5 text-xs text-blue-700 dark:text-blue-400 ml-5">
<.icon
name="hero-shield-check"
class="h-3.5 w-3.5 inline -mt-0.5"
/>
{raw(format_help_step(provider.setup_help.permissions))}
</p>
<p class="mt-2 text-xs text-blue-600/80 dark:text-blue-400/70 ml-5">
<.icon
name="hero-arrow-top-right-on-square"
class="h-3 w-3 inline -mt-0.5"
/> Example URL:
<code class="font-mono">{provider.setup_help.url_pattern}</code>
</p>
</div>
</div>
<div class="border-t border-gray-200 pt-5 dark:border-white/10">
@ -1826,6 +1896,34 @@
</div>
</div>
<%= if Map.has_key?(provider, :setup_help) && provider.id != "pagerduty" do %>
<div class="rounded-lg bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800/50 p-4">
<h4 class="flex items-center gap-1.5 text-xs font-semibold text-blue-900 dark:text-blue-300 mb-2">
<.icon name="hero-information-circle" class="h-4 w-4" />
{t("How to create your API key")}
</h4>
<ol class="list-decimal list-inside space-y-1 text-xs text-blue-800 dark:text-blue-300 ml-5">
<%= for step <- provider.setup_help.steps do %>
<li>{raw(format_help_step(step))}</li>
<% end %>
</ol>
<p class="mt-2.5 text-xs text-blue-700 dark:text-blue-400 ml-5">
<.icon
name="hero-shield-check"
class="h-3.5 w-3.5 inline -mt-0.5"
/>
{raw(format_help_step(provider.setup_help.permissions))}
</p>
<p class="mt-2 text-xs text-blue-600/80 dark:text-blue-400/70 ml-5">
<.icon
name="hero-arrow-top-right-on-square"
class="h-3 w-3 inline -mt-0.5"
/> Example URL:
<code class="font-mono">{provider.setup_help.url_pattern}</code>
</p>
</div>
<% end %>
<%= if provider.id == "pagerduty" do %>
<div class="rounded-lg bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800/50 p-4">
<h4 class="flex items-center gap-1.5 text-xs font-semibold text-blue-900 dark:text-blue-300 mb-2">