towerops/lib/towerops_web/live/onboarding_live.html.heex

309 lines
14 KiB
Text

<Layouts.authenticated
flash={@flash}
current_scope={@current_scope}
>
<div class="max-w-2xl mx-auto py-8">
<%!-- Step indicator --%>
<nav class="mb-8">
<ol class="flex items-center gap-2">
<%= for step <- @steps do %>
<li class="flex items-center gap-2">
<button
phx-click="go_to_step"
phx-value-step={step}
class={[
"flex items-center gap-1.5 text-sm font-medium px-2 py-1 rounded transition",
if(step == @step,
do: "text-indigo-600 dark:text-indigo-400 bg-indigo-50 dark:bg-indigo-900/30",
else: if(step_index(step) < step_index(@step),
do: "text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white",
else: "text-gray-400 dark:text-gray-500"
)
)
]}
>
<span class={[
"flex items-center justify-center w-5 h-5 rounded-full text-xs font-bold",
if(step == @step,
do: "bg-indigo-600 text-white dark:bg-indigo-500",
else: if(step_index(step) < step_index(@step),
do: "bg-gray-400 dark:bg-gray-500 text-white",
else: "bg-gray-200 dark:bg-gray-700 text-gray-500 dark:text-gray-400"
)
)
]}>
<%= if step_index(step) < step_index(@step) do %>
<.icon name="hero-check-mini" class="h-3 w-3" />
<% else %>
{step_index(step) + 1}
<% end %>
</span>
{step_label(step)}
</button>
<%= if step != List.last(@steps) do %>
<.icon name="hero-chevron-right-mini" class="h-4 w-4 text-gray-300 dark:text-gray-600" />
<% end %>
</li>
<% end %>
</ol>
</nav>
<%!-- Step content --%>
<div class="bg-white dark:bg-white/5 rounded-lg border border-gray-200 dark:border-white/10 p-6">
<%= case @step do %>
<% :snmp -> %>
<h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-1">
{t("SNMP Configuration")}
</h2>
<p class="text-sm text-gray-500 dark:text-gray-400 mb-6">
{t("Set organization-wide SNMP defaults. Can be overridden per-site or per-device.")}
</p>
<.form for={@form} phx-change="validate_snmp" phx-submit="save_snmp">
<div class="space-y-4">
<.input
field={@form[:snmp_version]}
type="select"
label={t("SNMP Version")}
options={[{"v1", "1"}, {"v2c", "2c"}, {"v3", "3"}]}
/>
<%= if @form[:snmp_version].value in ["1", "2c"] do %>
<.input
field={@form[:snmp_community]}
type="text"
label={t("Community String")}
placeholder="public"
/>
<% end %>
<%= if @form[:snmp_version].value == "3" do %>
<.input
field={@form[:snmpv3_username]}
type="text"
label={t("Username")}
/>
<.input
field={@form[:snmpv3_auth_protocol]}
type="select"
label={t("Auth Protocol")}
prompt="None"
options={["MD5", "SHA", "SHA-224", "SHA-256", "SHA-384", "SHA-512"]}
/>
<.input
field={@form[:snmpv3_auth_password]}
type="password"
label={t("Auth Password")}
/>
<.input
field={@form[:snmpv3_priv_protocol]}
type="select"
label={t("Privacy Protocol")}
prompt="None"
options={["DES", "AES", "AES-192", "AES-256", "AES-256-C"]}
/>
<.input
field={@form[:snmpv3_priv_password]}
type="password"
label={t("Privacy Password")}
/>
<% end %>
<div class="flex items-center justify-between pt-4 border-t border-gray-200 dark:border-white/10">
<button
type="button"
phx-click="skip"
class="text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200"
>
{t("Skip")}
</button>
<button
type="submit"
class="rounded-md bg-indigo-600 px-4 py-2 text-sm font-semibold text-white hover:bg-indigo-500 dark:bg-indigo-500 dark:hover:bg-indigo-400"
>
{t("Save & Continue")}
</button>
</div>
</div>
</.form>
<% :site -> %>
<h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-1">
{t("Add Your First Site")}
</h2>
<p class="text-sm text-gray-500 dark:text-gray-400 mb-6">
{t("Sites represent physical locations — tower sites, POPs, data centers, etc.")}
</p>
<.form for={@site_form} phx-change="validate_site" phx-submit="save_site">
<div class="space-y-4">
<.input field={@site_form[:name]} type="text" label={t("Name")} placeholder="e.g. Main Tower" required />
<.input field={@site_form[:address]} type="text" label={t("Address")} placeholder="123 Tower Rd" />
<div class="grid grid-cols-2 gap-4">
<.input field={@site_form[:latitude]} type="number" label={t("Latitude")} step="any" placeholder="38.8977" />
<.input field={@site_form[:longitude]} type="number" label={t("Longitude")} step="any" placeholder="-77.0365" />
</div>
<div class="flex items-center justify-between pt-4 border-t border-gray-200 dark:border-white/10">
<button
type="button"
phx-click="skip"
class="text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200"
>
{t("Skip")}
</button>
<button
type="submit"
class="rounded-md bg-indigo-600 px-4 py-2 text-sm font-semibold text-white hover:bg-indigo-500 dark:bg-indigo-500 dark:hover:bg-indigo-400"
>
{t("Create Site & Continue")}
</button>
</div>
</div>
</.form>
<% :billing -> %>
<h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-1">
{t("Connect a Billing Platform")}
</h2>
<p class="text-sm text-gray-500 dark:text-gray-400 mb-6">
{t("Optional. Sync subscriber data for outage impact analysis and inventory reconciliation.")}
</p>
<%= if @selected_provider == nil do %>
<div class="grid grid-cols-2 gap-3 mb-6">
<%= for provider <- @billing_providers do %>
<button
type="button"
phx-click="select_provider"
phx-value-provider={provider.id}
class="flex items-center gap-3 p-4 rounded-lg border border-gray-200 dark:border-white/10 hover:border-indigo-400 dark:hover:border-indigo-500 hover:bg-gray-50 dark:hover:bg-white/5 transition text-left"
>
<.icon name={provider.icon} class="h-6 w-6 text-gray-400 dark:text-gray-500" />
<span class="text-sm font-medium text-gray-900 dark:text-white">{provider.name}</span>
</button>
<% end %>
</div>
<% else %>
<div class="mb-4">
<button
type="button"
phx-click="select_provider"
phx-value-provider=""
class="text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200"
>
← {t("Back to providers")}
</button>
</div>
<.form for={@integration_form} phx-change="validate_integration" phx-submit="save_integration">
<div class="space-y-4">
<%= case @selected_provider do %>
<% "sonar" -> %>
<.input name="integration[instance_url]" type="text" label={t("Instance URL")} placeholder="https://myisp.sonar.software" value="" />
<.input name="integration[api_token]" type="password" label={t("API Token")} value="" />
<% "splynx" -> %>
<.input name="integration[instance_url]" type="text" label={t("Instance URL")} placeholder="https://myisp.splynx.app" value="" />
<.input name="integration[api_key]" type="text" label={t("API Key")} value="" />
<.input name="integration[api_secret]" type="password" label={t("API Secret")} value="" />
<% _ -> %>
<.input name="integration[api_key]" type="password" label={t("API Key")} value="" />
<% end %>
<div class="flex items-center justify-between pt-4 border-t border-gray-200 dark:border-white/10">
<button
type="button"
phx-click="skip"
class="text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200"
>
{t("Skip")}
</button>
<button
type="submit"
class="rounded-md bg-indigo-600 px-4 py-2 text-sm font-semibold text-white hover:bg-indigo-500 dark:bg-indigo-500 dark:hover:bg-indigo-400"
>
{t("Save & Continue")}
</button>
</div>
</div>
</.form>
<% end %>
<%= if @selected_provider == nil do %>
<div class="flex items-center justify-between pt-4 border-t border-gray-200 dark:border-white/10">
<button
type="button"
phx-click="skip"
class="text-sm font-medium text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200"
>
{t("Skip — I'll set this up later")}
</button>
</div>
<% end %>
<% :agent -> %>
<h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-1">
{t("Deploy Agent")}
</h2>
<p class="text-sm text-gray-500 dark:text-gray-400 mb-6">
{t("The TowerOps agent polls your network devices via SNMP. Deploy it on a host with network access to your infrastructure.")}
</p>
<%= if @agent_token do %>
<div class="space-y-4">
<%= if @agent_token_value do %>
<div class="rounded-md bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 p-3">
<p class="text-sm text-amber-800 dark:text-amber-200">
<.icon name="hero-exclamation-triangle" class="h-4 w-4 inline -mt-0.5" />
{t("Save this token — it won't be shown again.")}
</p>
<code class="mt-2 block text-xs font-mono bg-amber-100 dark:bg-amber-900/40 rounded p-2 break-all select-all text-amber-900 dark:text-amber-100">
{@agent_token_value}
</code>
</div>
<% else %>
<div class="rounded-md bg-gray-50 dark:bg-white/5 border border-gray-200 dark:border-white/10 p-3">
<p class="text-xs text-gray-500 dark:text-gray-400">
{t("Agent:")} <span class="font-medium text-gray-900 dark:text-white">{@agent_token.name}</span>
</p>
</div>
<% end %>
<div>
<h3 class="text-sm font-medium text-gray-900 dark:text-white mb-2">{t("Docker")}</h3>
<pre class="text-xs font-mono bg-gray-900 text-green-400 rounded-lg p-4 overflow-x-auto select-all"><code>docker run -d \
--name towerops-agent \
--restart unless-stopped \
-e TOWEROPS_TOKEN=<%= if @agent_token_value, do: @agent_token_value, else: "<your-token>" %> \
-e TOWEROPS_SERVER=<%= ToweropsWeb.Endpoint.url() %> \
--network host \
ghcr.io/towerops/agent:latest</code></pre>
</div>
<div>
<h3 class="text-sm font-medium text-gray-900 dark:text-white mb-2">{t("Systemd")}</h3>
<pre class="text-xs font-mono bg-gray-900 text-green-400 rounded-lg p-4 overflow-x-auto select-all"><code>curl -sSL https://install.towerops.net | \
TOWEROPS_TOKEN=<%= if @agent_token_value, do: @agent_token_value, else: "<your-token>" %> \
TOWEROPS_SERVER=<%= ToweropsWeb.Endpoint.url() %> \
bash</code></pre>
</div>
</div>
<% else %>
<p class="text-sm text-gray-500 dark:text-gray-400">
{t("Failed to create agent token. You can create one later in Settings → Agents.")}
</p>
<% end %>
<div class="flex items-center justify-end pt-6 border-t border-gray-200 dark:border-white/10 mt-6">
<button
type="button"
phx-click="finish"
class="rounded-md bg-indigo-600 px-4 py-2 text-sm font-semibold text-white hover:bg-indigo-500 dark:bg-indigo-500 dark:hover:bg-indigo-400"
>
{t("Done — Go to Dashboard")}
</button>
</div>
<% end %>
</div>
</div>
</Layouts.authenticated>