<.link navigate={~p"/dashboard"} 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

Organization Settings

Manage organization defaults, integrations, and configuration

<%= if @active_tab not in ["integrations", "members"] do %> <.form for={@form} id="organization-form" phx-change="validate" phx-submit="save" data-1p-ignore data-form-type="other" >
<%= if @active_tab == "general" do %>

Organization Name

Update the name of your organization.

<.input field={@form[:name]} type="text" label="Organization Name" required />
<%= if @user_orgs_count > 1 do %>

Default Organization

Set this organization as your default. When you log in, you'll be directed to your default organization.

<%= if @membership.is_default do %>
<.icon name="hero-check-circle" class="h-5 w-5 text-green-600 dark:text-green-400" />

This is your default organization

<% else %>

Click to make this your default organization

<% end %>
<% end %>

Site Organization

Enable site organization to group devices into physical locations (offices, datacenters, etc.).

<.input field={@form[:use_sites]} type="checkbox" label="Use sites to organize devices" />

When enabled, you can organize devices into sites. When disabled, all devices belong directly to the organization.

<%= if @organization.use_sites && @form[:use_sites].value == false do %>
<.icon name="hero-exclamation-triangle" class="h-5 w-5 text-amber-400" />

Warning: Disabling Sites

All devices will be removed from their current sites and assigned directly to the organization. Site assignments will be lost, but devices will remain in the organization.

<% end %>
<% end %> <%= if @active_tab == "snmp" do %>

SNMP Configuration

Set default SNMP settings for all devices in this organization. These can be overridden at the site or device level.

<.icon name="hero-information-circle" class="h-4 w-4 inline" /> Hierarchy: Device > Site > Organization

<.input field={@form[:snmp_version]} type="select" label="SNMP Version" prompt="Select 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="SNMP Community String" placeholder="e.g., public" /> <% end %> <%= if @form[:snmp_version].value == "3" do %> <.input field={@form[:snmpv3_security_level]} type="select" label="Security Level" prompt="Select security level" options={[ {"No Auth, No Priv", "noAuthNoPriv"}, {"Auth, No Priv", "authNoPriv"}, {"Auth, Priv", "authPriv"} ]} /> <.input field={@form[:snmpv3_username]} type="text" label="Username" placeholder="e.g., snmpuser" /> <%= if @form[:snmpv3_security_level].value in ["authNoPriv", "authPriv"] do %> <.input field={@form[:snmpv3_auth_protocol]} type="select" label="Auth Protocol" prompt="Select protocol" options={[ {"SHA-256 (recommended)", "SHA-256"}, {"SHA-512", "SHA-512"}, {"SHA-384", "SHA-384"}, {"SHA-224", "SHA-224"}, {"SHA (SHA-1)", "SHA"}, {"MD5", "MD5"} ]} /> <.input field={@form[:snmpv3_auth_password]} type="password" label="Auth Password" placeholder="Min 8 characters" autocomplete="off" /> <% end %> <%= if @form[:snmpv3_security_level].value == "authPriv" do %> <.input field={@form[:snmpv3_priv_protocol]} type="select" label="Privacy Protocol" prompt="Select protocol" options={[ {"AES-128 (recommended)", "AES"}, {"AES-256", "AES-256"}, {"AES-192", "AES-192"}, {"AES-256-C", "AES-256-C"}, {"DES (legacy)", "DES"} ]} /> <.input field={@form[:snmpv3_priv_password]} type="password" label="Privacy Password" placeholder="Min 8 characters" autocomplete="off" /> <% end %> <% end %> <.input field={@form[:snmp_port]} type="number" label="SNMP Port" placeholder="161" /> <%= if @organization.snmp_community do %>

Force Apply to All Devices

This will override SNMP settings for ALL devices across all sites in this organization.

<% end %>
<% end %> <%= if @active_tab == "mikrotik" && @current_scope.user.is_superuser do %>

MikroTik API Configuration

Set default MikroTik RouterOS API credentials for all devices in this organization. Only applies to devices detected as MikroTik.

<.icon name="hero-beaker" class="h-4 w-4 inline" /> Experimental Feature: MikroTik API integration is under active development.

<.icon name="hero-information-circle" class="h-4 w-4 inline" /> Hierarchy: Device > Site > Organization

<.input field={@form[:mikrotik_enabled]} type="checkbox" label="Enable MikroTik API" />
<.input field={@form[:mikrotik_username]} type="text" label="Username" placeholder="e.g., admin" autocomplete="off" />
<.input field={@form[:mikrotik_password]} type="password" label="Password" placeholder="RouterOS API password" autocomplete="off" />
<.input field={@form[:mikrotik_port]} type="number" label="API Port" placeholder="8729" autocomplete="off" />
<.input field={@form[:mikrotik_use_ssl]} type="checkbox" label="Use SSL (API-SSL)" />

Critical Security Warning: Plain API (port 8728) sends credentials unencrypted over the network. This setting is blocked for devices using cloud pollers and should only be used with local agents on trusted networks.

Security Warning: Plain API (port 8728) sends credentials unencrypted. Use SSL (port 8729) whenever possible.

<% end %> <%= if @active_tab == "agents" do %> <%= if @available_agents != [] do %>

Default Agent

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.

<.icon name="hero-information-circle" class="h-4 w-4 inline" /> Hierarchy: Device > Site > Organization

<.input field={@form[:default_agent_token_id]} type="select" label="Default Remote Agent" prompt="No default agent - cloud polling" options={Enum.map(@available_agents, &{&1.name, &1.id})} />

Current Device Assignment:

<.icon name="hero-server" class="h-4 w-4" /> {@assignment_breakdown.direct} device-level override
<.icon name="hero-building-office" class="h-4 w-4" /> {@assignment_breakdown.site} inherited from site
<.icon name="hero-building-office-2" class="h-4 w-4" /> {@assignment_breakdown.organization} inheriting organization default
<.icon name="hero-cloud" class="h-4 w-4" /> {@assignment_breakdown.cloud} cloud polling (no agent)
<%= if @organization.default_agent_token_id do %>

Force Apply to All Devices

This will assign the default agent to ALL devices across all sites in this organization.

<% end %>
<% else %>

Default Agent

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 to enable remote polling.

<% end %> <% end %>
<.link navigate={~p"/dashboard"} class="text-sm font-semibold text-gray-900 dark:text-white" > Cancel
<% end %> <%= if @active_tab == "members" do %>
<%= if @membership.role in [:owner, :admin] do %>

Invite a Team Member

Send an invitation email to add someone to this organization.

<.form for={@invite_form} id="invite-form" phx-submit="send_invitation">
<.input field={@invite_form[:email]} type="email" label="Email address" placeholder="colleague@example.com" required />
<.input field={@invite_form[:role]} type="select" label="Role" options={[{"Admin", "admin"}, {"Member", "member"}, {"Viewer", "viewer"}]} />
<% end %> <%= if @pending_invitations != [] do %>

Pending Invitations

Invitations that haven't been accepted yet.

<%= if @membership.role in [:owner, :admin] do %> <% end %> <%= if @membership.role in [:owner, :admin] do %> <% end %>
Email Role Sent Expires Actions
{invitation.email} {invitation.role} <.timestamp datetime={invitation.inserted_at} timezone={@timezone} format="absolute" /> <.timestamp datetime={invitation.expires_at} timezone={@timezone} format="absolute" />
<% end %>

Members

People who have access to this organization.

<%= if @membership.role in [:owner, :admin] do %> <% end %> <%= if @membership.role in [:owner, :admin] do %> <% end %>
User Role Joined Actions
{member.user.email}
"bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-400" :admin -> "bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400" :member -> "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400" :viewer -> "bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-400" end ]}> {member.role} <.timestamp datetime={member.inserted_at} timezone={@timezone} format="absolute" /> <%= if member.role != :owner do %>
<% end %>
<% end %> <%= if @active_tab == "integrations" do %>
<.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 %> 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 %> <%= if integration.last_sync_message && integration.last_sync_message != "" do %> {integration.last_sync_message} <% end %> <%= if integration.last_synced_at do %> · Next sync 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 %> <%= if provider.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" > Entity Mapping → <.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" > Reconciliation → <% end %> <%!-- Sync schedule info --%> <%= if integration.enabled do %>

<.icon name="hero-clock" class="h-3.5 w-3.5 inline" /> Sync Schedule

<%= if provider.id == "preseem" do %> Syncs every {integration.sync_interval_minutes} minutes. Baselines and fleet profiles computed nightly. <% end %> <%= if provider.id == "gaiia" do %> Syncs every {integration.sync_interval_minutes} minutes. Reconciliation runs nightly. Also receives real-time webhook updates. <% end %> <%= if provider.id == "pagerduty" do %> Event-driven — no polling needed. TowerOps sends events to PagerDuty in real time:

  • Device down → PagerDuty incident triggered (critical)
  • Alert acknowledged → PagerDuty incident acknowledged
  • Device recovers → PagerDuty incident auto-resolved
<% end %> <%= if provider.id == "netbox" do %> <% direction = get_credential(integration, "sync_direction") %> Syncs every {integration.sync_interval_minutes} minutes. <%= case direction do %> <% "push" -> %> TowerOps → NetBox (TowerOps is source of truth) <% "both" -> %> Bidirectional sync (merge from both systems) <% _ -> %> NetBox → TowerOps (NetBox is source of truth) <% end %> <% end %>

<% end %>
<% end %>
<%= if integration = @integrations[provider.id] do %> <% end %>
<%= if @configuring == provider.id do %>
<%= if provider.id == "netbox" do %> <.form for={@integration_form} id="netbox-form" phx-change="validate_integration" phx-submit="save_integration" >
<%!-- Connection Section --%>

Connection

Your NetBox instance URL and API token. The token needs read permission at minimum. Write permission is required if you want to push data to NetBox.

<.input field={@integration_form[:url]} type="url" label="NetBox URL" placeholder="https://netbox.example.com" autocomplete="off" value={get_credential(@integrations["netbox"], "url")} /> <.input field={@integration_form[:api_token]} type="password" label="API Token" placeholder="Enter your NetBox API token" autocomplete="off" value={get_credential(@integrations["netbox"], "api_token")} />
<%!-- Sync Direction Section --%>

Sync Direction

Choose how data flows between TowerOps and NetBox. You can change this later.

<%!-- What to Sync Section --%>

What to Sync

Choose which NetBox objects participate in sync. Devices and sites are recommended at minimum.

<%!-- Filtering Section --%>

Filters (optional)

Narrow the sync scope. Leave blank to sync everything. Comma-separated for multiple values.

<%!-- Sync Interval --%>
<.input field={@integration_form[:sync_interval_minutes]} type="number" label="Sync interval (minutes)" min="5" value={ if(@integrations["netbox"], do: @integrations["netbox"].sync_interval_minutes, else: 30 ) } />

How often TowerOps checks NetBox for changes. 30 minutes is recommended for most setups.

<%!-- Test & Save --%> <%= if @test_result do %>
"bg-green-50 dark:bg-green-900/20" {:error, _} -> "bg-red-50 dark:bg-red-900/20" end ]}>

"text-green-700 dark:text-green-400" {:error, _} -> "text-red-700 dark:text-red-400" end ]}> <%= case @test_result do %> <% {:ok, msg} -> %> <.icon name="hero-check-circle" class="h-4 w-4 inline" /> {msg} <% {:error, msg} -> %> <.icon name="hero-x-circle" class="h-4 w-4 inline" /> {msg} <% end %>

<% end %>
<% else %> <.form for={@integration_form} id={"#{provider.id}-form"} phx-change="validate_integration" phx-submit="save_integration" >
<.input field={@integration_form[:api_key]} type="password" label={ if(provider.id == "pagerduty", do: "Integration Key (Routing Key)", else: "API Key" ) } placeholder={ if(provider.id == "pagerduty", do: "Enter your PagerDuty integration key", else: "Enter your #{provider.name} API key" ) } autocomplete="off" value={get_credential(@integrations[provider.id], "api_key")} /> <%= if provider.id == "pagerduty" do %>

Where to find your Integration Key

  1. In PagerDuty, go to Services → select your service (or create one)
  2. Click the Integrations tab
  3. Click Add Integration → select Events API v2
  4. Copy the Integration Key and paste it above

When connected, TowerOps will automatically trigger, acknowledge, and resolve PagerDuty incidents in sync with your alerts.

<% end %> <%= if provider.id != "pagerduty" do %> <.input field={@integration_form[:sync_interval_minutes]} type="number" label="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) ) } /> <% end %> <%= 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 %> <%= if provider.id == "gaiia" && @integrations["gaiia"] && @configuring == "gaiia" do %>

Webhook Configuration

Receive real-time updates from Gaiia when accounts, subscriptions, or inventory items change.

Paste the secret key generated by Gaiia when you create the webhook.

Setup Instructions
  1. In your Gaiia admin panel, go to Settings → Webhooks
  2. Click "Add Webhook"
  3. Paste the Webhook URL above
  4. Under Events, select "All Events"
  5. Gaiia will generate a secret key — copy it
  6. Paste the secret key into the Webhook Secret field above
  7. Save the webhook

Once configured, Towerops will receive real-time updates when accounts, subscriptions, or inventory items change in Gaiia.

<% end %>
<% end %>