towerops/lib/towerops_web/live/org/settings_live.html.heex

445 lines
21 KiB
Text

<Layouts.authenticated
flash={@flash}
current_scope={@current_scope}
>
<div class="border-b border-gray-200 pb-5 dark:border-white/5">
<div class="mb-4">
<.link
navigate={~p"/orgs/#{@organization.slug}"}
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 Dashboard
</.link>
</div>
<h1 class="text-3xl font-semibold tracking-tight text-gray-900 dark:text-white">
Organization Settings
</h1>
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
Manage organization defaults for SNMP, agents, and MikroTik API configuration
</p>
</div>
<.form for={@form} id="organization-form" phx-change="validate" phx-submit="save">
<div class="divide-y divide-gray-200 dark:divide-white/10">
<!-- Organization Name Section -->
<div class="grid max-w-7xl grid-cols-1 gap-x-8 gap-y-10 px-4 py-16 sm:px-6 md:grid-cols-3 lg:px-8">
<div>
<h2 class="text-base/7 font-semibold text-gray-900 dark:text-white">
Organization Name
</h2>
<p class="mt-1 text-sm/6 text-gray-500 dark:text-gray-400">
Update the name of your organization.
</p>
</div>
<div class="md:col-span-2">
<div class="grid grid-cols-1 gap-x-6 gap-y-8 sm:max-w-xl sm:grid-cols-6">
<div class="col-span-full">
<label
for="organization_name"
class="block text-sm/6 font-medium text-gray-900 dark:text-white"
>
Organization Name
</label>
<div class="mt-2">
<input
type="text"
name={@form[:name].name}
id="organization_name"
value={@form[:name].value}
required
class="block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6 dark:bg-white/5 dark:text-white dark:outline-white/10 dark:placeholder:text-gray-500 dark:focus:outline-indigo-500"
/>
</div>
</div>
</div>
</div>
</div>
<!-- SNMP Configuration Section -->
<div class="grid max-w-7xl grid-cols-1 gap-x-8 gap-y-10 px-4 py-16 sm:px-6 md:grid-cols-3 lg:px-8">
<div>
<h2 class="text-base/7 font-semibold text-gray-900 dark:text-white">
SNMP Configuration
</h2>
<p class="mt-1 text-sm/6 text-gray-500 dark:text-gray-400">
Set default SNMP settings for all devices in this organization. These can be overridden at the site or device level.
</p>
<p class="mt-3 text-xs text-gray-500 dark:text-gray-400 italic">
<.icon name="hero-information-circle" class="h-4 w-4 inline" />
Hierarchy: Device > Site > Organization
</p>
</div>
<div class="md:col-span-2">
<div class="grid grid-cols-1 gap-x-6 gap-y-8 sm:max-w-xl sm:grid-cols-6">
<div class="col-span-full">
<label
for="snmp_version"
class="block text-sm/6 font-medium text-gray-900 dark:text-white"
>
SNMP Version
</label>
<div class="mt-2 grid grid-cols-1">
<select
name={@form[:snmp_version].name}
id="snmp_version"
class="col-start-1 row-start-1 w-full appearance-none rounded-md bg-white py-1.5 pr-8 pl-3 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6 dark:bg-white/5 dark:text-white dark:outline-white/10 dark:*:bg-gray-800 dark:focus:outline-indigo-500"
>
<option value="">Select SNMP version</option>
<option value="1" selected={@form[:snmp_version].value == "1"}>SNMP v1</option>
<option value="2c" selected={@form[:snmp_version].value == "2c"}>
SNMP v2c
</option>
<option value="3" selected={@form[:snmp_version].value == "3"}>SNMP v3</option>
</select>
<svg
viewBox="0 0 16 16"
fill="currentColor"
data-slot="icon"
aria-hidden="true"
class="pointer-events-none col-start-1 row-start-1 mr-2 size-5 self-center justify-self-end text-gray-400 sm:size-4"
>
<path
d="M4.22 6.22a.75.75 0 0 1 1.06 0L8 8.94l2.72-2.72a.75.75 0 1 1 1.06 1.06l-3.25 3.25a.75.75 0 0 1-1.06 0L4.22 7.28a.75.75 0 0 1 0-1.06Z"
clip-rule="evenodd"
fill-rule="evenodd"
/>
</svg>
</div>
</div>
<div class="col-span-full">
<label
for="snmp_community"
class="block text-sm/6 font-medium text-gray-900 dark:text-white"
>
SNMP Community String
</label>
<div class="mt-2">
<input
type="text"
name={@form[:snmp_community].name}
id="snmp_community"
value={@form[:snmp_community].value}
placeholder="e.g., public"
class="block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6 dark:bg-white/5 dark:text-white dark:outline-white/10 dark:placeholder:text-gray-500 dark:focus:outline-indigo-500"
/>
</div>
</div>
</div>
<%= if @organization.snmp_community do %>
<div class="mt-6 rounded-md bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 p-4">
<h3 class="text-sm font-medium text-amber-900 dark:text-amber-200 mb-2">
Force Apply to All Devices
</h3>
<p class="text-sm text-amber-700 dark:text-amber-300 mb-3">
This will override SNMP settings for ALL devices across all sites in this organization.
</p>
<button
type="button"
phx-click="apply_snmp_to_all"
data-confirm="This will replace SNMP settings for ALL devices in this organization. Are you sure?"
class="rounded-md bg-red-600 px-3 py-2 text-sm font-semibold text-white shadow-xs hover:bg-red-500 dark:bg-red-500 dark:shadow-none dark:hover:bg-red-400"
>
<.icon name="hero-arrow-path" class="h-4 w-4 inline" />
Apply SNMP Config to All Devices
</button>
</div>
<% end %>
</div>
</div>
<%= if @current_scope.user.is_superuser do %>
<!-- MikroTik API Configuration Section -->
<div class="grid max-w-7xl grid-cols-1 gap-x-8 gap-y-10 px-4 py-16 sm:px-6 md:grid-cols-3 lg:px-8">
<div>
<h2 class="text-base/7 font-semibold text-gray-900 dark:text-white">
MikroTik API Configuration
</h2>
<p class="mt-1 text-sm/6 text-gray-500 dark:text-gray-400">
Set default MikroTik RouterOS API credentials for all devices in this organization. Only applies to devices detected as MikroTik.
</p>
<p class="mt-3 text-xs text-gray-500 dark:text-gray-400 italic">
<.icon name="hero-information-circle" class="h-4 w-4 inline" />
Hierarchy: Device > Site > Organization
</p>
</div>
<div class="md:col-span-2">
<div class="grid grid-cols-1 gap-x-6 gap-y-8 sm:max-w-xl sm:grid-cols-6">
<div class="col-span-full">
<div class="flex items-center gap-x-3">
<input
type="checkbox"
name={@form[:mikrotik_enabled].name}
id="mikrotik_enabled"
checked={@form[:mikrotik_enabled].value}
class="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600 dark:border-white/10 dark:bg-white/5 dark:focus:ring-indigo-500"
/>
<label
for="mikrotik_enabled"
class="text-sm/6 font-medium text-gray-900 dark:text-white"
>
Enable MikroTik API
</label>
</div>
</div>
<div class={"col-span-full #{unless @form[:mikrotik_enabled].value, do: "hidden"}"}>
<label
for="mikrotik_username"
class="block text-sm/6 font-medium text-gray-900 dark:text-white"
>
Username
</label>
<div class="mt-2">
<input
type="text"
name={@form[:mikrotik_username].name}
id="mikrotik_username"
value={@form[:mikrotik_username].value}
placeholder="e.g., admin"
class="block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6 dark:bg-white/5 dark:text-white dark:outline-white/10 dark:placeholder:text-gray-500 dark:focus:outline-indigo-500"
/>
</div>
</div>
<div class={"col-span-full #{unless @form[:mikrotik_enabled].value, do: "hidden"}"}>
<label
for="mikrotik_password"
class="block text-sm/6 font-medium text-gray-900 dark:text-white"
>
Password
</label>
<div class="mt-2">
<input
type="password"
name={@form[:mikrotik_password].name}
id="mikrotik_password"
value={@form[:mikrotik_password].value}
placeholder="RouterOS API password"
class="block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6 dark:bg-white/5 dark:text-white dark:outline-white/10 dark:placeholder:text-gray-500 dark:focus:outline-indigo-500"
/>
</div>
</div>
<div class={"col-span-full #{unless @form[:mikrotik_enabled].value, do: "hidden"}"}>
<label
for="mikrotik_port"
class="block text-sm/6 font-medium text-gray-900 dark:text-white"
>
API Port
</label>
<div class="mt-2">
<input
type="number"
name={@form[:mikrotik_port].name}
id="mikrotik_port"
value={@form[:mikrotik_port].value}
placeholder="8729"
class="block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6 dark:bg-white/5 dark:text-white dark:outline-white/10 dark:placeholder:text-gray-500 dark:focus:outline-indigo-500"
/>
</div>
</div>
<div class={"col-span-full #{unless @form[:mikrotik_enabled].value, do: "hidden"}"}>
<div class="flex items-center gap-x-3">
<input
type="checkbox"
name={@form[:mikrotik_use_ssl].name}
id="mikrotik_use_ssl"
checked={@form[:mikrotik_use_ssl].value}
class="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600 dark:border-white/10 dark:bg-white/5 dark:focus:ring-indigo-500"
/>
<label
for="mikrotik_use_ssl"
class="text-sm/6 font-medium text-gray-900 dark:text-white"
>
Use SSL (API-SSL)
</label>
</div>
</div>
<div class={"col-span-full #{unless @form[:mikrotik_enabled].value, do: "hidden"} #{if @form[:mikrotik_use_ssl].value != false, do: "hidden"}"}>
<div class="rounded-md bg-red-50 dark:bg-red-900/20 p-4">
<p class="text-sm text-red-700 dark:text-red-300">
<strong>Critical Security Warning:</strong>
Plain API (port 8728) sends credentials unencrypted over the network. This setting is
<strong>blocked for devices using cloud pollers</strong>
and should only be used with local agents on trusted networks.
</p>
</div>
</div>
<div class={"col-span-full #{unless @form[:mikrotik_enabled].value, do: "hidden"} #{if @form[:mikrotik_use_ssl].value == false, do: "hidden"}"}>
<div class="rounded-md bg-yellow-50 dark:bg-yellow-900/20 p-4">
<p class="text-sm text-yellow-700 dark:text-yellow-300">
<strong>Security Warning:</strong>
Plain API (port 8728) sends credentials unencrypted. Use SSL (port 8729) whenever possible.
</p>
</div>
</div>
</div>
</div>
</div>
<% end %>
<!-- Default Agent Section -->
<%= if @available_agents != [] do %>
<div class="grid max-w-7xl grid-cols-1 gap-x-8 gap-y-10 px-4 py-16 sm:px-6 md:grid-cols-3 lg:px-8">
<div>
<h2 class="text-base/7 font-semibold text-gray-900 dark:text-white">
Default Agent
</h2>
<p class="mt-1 text-sm/6 text-gray-500 dark:text-gray-400">
Select a default agent for SNMP polling. This will be used for all devices in this organization unless overridden at the site or device level.
</p>
<p class="mt-3 text-xs text-gray-500 dark:text-gray-400 italic">
<.icon name="hero-information-circle" class="h-4 w-4 inline" />
Hierarchy: Device > Site > Organization
</p>
</div>
<div class="md:col-span-2">
<div class="grid grid-cols-1 gap-x-6 gap-y-8 sm:max-w-xl sm:grid-cols-6">
<div class="col-span-full">
<label
for="default_agent_token_id"
class="block text-sm/6 font-medium text-gray-900 dark:text-white"
>
Default Remote Agent
</label>
<div class="mt-2 grid grid-cols-1">
<select
name={@form[:default_agent_token_id].name}
id="default_agent_token_id"
class="col-start-1 row-start-1 w-full appearance-none rounded-md bg-white py-1.5 pr-8 pl-3 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6 dark:bg-white/5 dark:text-white dark:outline-white/10 dark:*:bg-gray-800 dark:focus:outline-indigo-500"
>
<option value="">No default agent - cloud polling</option>
<%= for agent <- @available_agents do %>
<option
value={agent.id}
selected={@form[:default_agent_token_id].value == agent.id}
>
{agent.name}
</option>
<% end %>
</select>
<svg
viewBox="0 0 16 16"
fill="currentColor"
data-slot="icon"
aria-hidden="true"
class="pointer-events-none col-start-1 row-start-1 mr-2 size-5 self-center justify-self-end text-gray-400 sm:size-4"
>
<path
d="M4.22 6.22a.75.75 0 0 1 1.06 0L8 8.94l2.72-2.72a.75.75 0 1 1 1.06 1.06l-3.25 3.25a.75.75 0 0 1-1.06 0L4.22 7.28a.75.75 0 0 1 0-1.06Z"
clip-rule="evenodd"
fill-rule="evenodd"
/>
</svg>
</div>
</div>
<div class="col-span-full">
<div class="rounded-md bg-gray-50 dark:bg-gray-800/50 p-4">
<p class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Current Device Assignment:
</p>
<div class="space-y-1 text-sm text-gray-600 dark:text-gray-400">
<div class="flex items-center gap-2">
<.icon name="hero-server" class="h-4 w-4" />
<span>
<strong>{@assignment_breakdown.direct}</strong> device-level override
</span>
</div>
<div class="flex items-center gap-2">
<.icon name="hero-building-office" class="h-4 w-4" />
<span>
<strong>{@assignment_breakdown.site}</strong> inherited from site
</span>
</div>
<div class="flex items-center gap-2">
<.icon name="hero-building-office-2" class="h-4 w-4" />
<span>
<strong>{@assignment_breakdown.organization}</strong>
inheriting organization default
</span>
</div>
<div class="flex items-center gap-2">
<.icon name="hero-cloud" class="h-4 w-4" />
<span>
<strong>{@assignment_breakdown.cloud}</strong> cloud polling (no agent)
</span>
</div>
</div>
</div>
</div>
</div>
<%= if @organization.default_agent_token_id do %>
<div class="mt-6 rounded-md bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 p-4">
<h3 class="text-sm font-medium text-amber-900 dark:text-amber-200 mb-2">
Force Apply to All Devices
</h3>
<p class="text-sm text-amber-700 dark:text-amber-300 mb-3">
This will assign the default agent to ALL devices across all sites in this organization.
</p>
<button
type="button"
phx-click="apply_agent_to_all"
data-confirm="This will replace agent assignments for ALL devices in this organization. Are you sure?"
class="rounded-md bg-red-600 px-3 py-2 text-sm font-semibold text-white shadow-xs hover:bg-red-500 dark:bg-red-500 dark:shadow-none dark:hover:bg-red-400"
>
<.icon name="hero-arrow-path" class="h-4 w-4 inline" />
Apply Default Agent to All Devices
</button>
</div>
<% end %>
</div>
</div>
<% else %>
<div class="grid max-w-7xl grid-cols-1 gap-x-8 gap-y-10 px-4 py-16 sm:px-6 md:grid-cols-3 lg:px-8">
<div>
<h2 class="text-base/7 font-semibold text-gray-900 dark:text-white">
Default Agent
</h2>
<p class="mt-1 text-sm/6 text-gray-500 dark:text-gray-400">
No agents configured yet.
<.link
navigate={~p"/agents"}
class="text-indigo-600 hover:text-indigo-700 dark:text-indigo-400 dark:hover:text-indigo-300"
>
Create an agent
</.link>
to enable remote polling.
</p>
</div>
<div class="md:col-span-2">
<!-- Empty space for layout consistency -->
</div>
</div>
<% end %>
</div>
<!-- Save Button at Bottom -->
<div class="flex items-center justify-end gap-x-6 border-t border-gray-200 px-4 py-4 sm:px-6 lg:px-8 dark:border-white/10">
<.link
navigate={~p"/orgs/#{@organization.slug}"}
class="text-sm font-semibold text-gray-900 dark:text-white"
>
Cancel
</.link>
<button
type="submit"
phx-disable-with="Saving..."
class="rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-xs hover:bg-indigo-500 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 dark:bg-indigo-500 dark:shadow-none dark:hover:bg-indigo-400 dark:focus-visible:outline-indigo-500"
>
Save Settings
</button>
</div>
</.form>
</Layouts.authenticated>