<.link navigate={ if @live_action == :edit, do: ~p"/devices/#{@device.id}", else: ~p"/devices" } 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" /> {if @live_action == :edit, do: "Back to Device", else: "Back to Device List"}

{@page_title}

{if @live_action == :new, do: "Add new device to monitor", else: "Update device details"}

<%= if @live_action == :new do %> <% percent = if @device_quota.limit != :unlimited and @device_quota.limit > 0 do (@device_quota.current / @device_quota.limit * 100) |> trunc() else 0 end %> <% badge_class = cond do @current_scope.user.is_superuser -> "bg-blue-50 text-blue-800 dark:bg-blue-900/20 dark:text-blue-400" @device_quota.at_limit -> "bg-red-50 text-red-800 border-red-200 dark:bg-red-900/20 dark:text-red-400 dark:border-red-800" percent >= 90 -> "bg-orange-50 text-orange-800 border-orange-200 dark:bg-orange-900/20 dark:text-orange-400 dark:border-orange-800" percent >= 75 -> "bg-yellow-50 text-yellow-800 border-yellow-200 dark:bg-yellow-900/20 dark:text-yellow-400 dark:border-yellow-800" @device_quota.limit == :unlimited -> "bg-blue-50 text-blue-800 dark:bg-blue-900/20 dark:text-blue-400" true -> "bg-green-50 text-green-800 border-green-200 dark:bg-green-900/20 dark:text-green-400 dark:border-green-800" end %>
<%= cond do %> <% @current_scope.user.is_superuser -> %> {@device_quota.current} devices <% @device_quota.limit == :unlimited -> %> {@device_quota.current} devices <% true -> %> {@device_quota.current}/{@device_quota.limit} devices <% end %>
<% end %>
<%= if @live_action == :new and not @device_quota.at_limit and @device_quota.limit != :unlimited and not @current_scope.user.is_superuser do %> <% remaining = @device_quota.limit - @device_quota.current %> <%= if remaining <= 3 do %>
<.icon name="hero-exclamation-triangle" class="h-5 w-5 text-orange-400" />

Approaching device limit

You have {remaining} {if remaining == 1, do: "slot", else: "slots"} remaining.

<% end %> <% end %> <.form for={@form} id="device-form" phx-change="validate" phx-submit="save">

Basic Information

Core device identification and location details.

<.input field={@form[:name]} type="text" label="Device Name" placeholder={ if @monitoring_mode == "snmp_and_icmp", do: "Leave blank to automatically populate from SNMP device name (sysName)", else: nil } required={@monitoring_mode == "icmp_only"} />
<.input field={@form[:ip_address]} type="text" label="IP Address" required />
<.icon name="hero-exclamation-triangle" class="h-5 w-5 flex-shrink-0" />

A device with this IP address already exists: <.link navigate={~p"/devices/#{@duplicate_device.id}"} class="font-medium underline hover:text-amber-900 dark:hover:text-amber-100" > {@duplicate_device.name || @duplicate_device.ip_address}

<.icon name="hero-exclamation-circle" class="h-5 w-5 flex-shrink-0 mt-0.5" />

Non-routable IP address detected

This IP address (private network or CGNAT range) cannot be reached from the cloud. To monitor this device, assign it to a <.link navigate={~p"/agents"} class="font-medium underline"> remote agent deployed on the same network.

<.input field={@form[:site_id]} type="select" label="Site (Optional)" prompt="No site (ungrouped)" options={Enum.map(@available_sites, &{&1.name, &1.id})} />

Optionally group this device into a site (office, datacenter, etc.)

<.input field={@form[:description]} type="textarea" label="Description" />

Monitoring Configuration

Configure how and how often this device should be monitored.

<.input field={@form[:check_interval_seconds]} type="number" label="Check Interval (seconds)" min="30" max="3600" />
<.input field={@form[:monitoring_enabled]} type="checkbox" label="Enable Monitoring" />
<%= if @available_agents != [] do %>
<.input field={@form[:agent_token_id]} type="select" label="Remote Agent" prompt="Inherit from site/organization" options={ Enum.map(@available_agents, fn agent -> label = if agent.is_cloud_poller, do: "#{agent.name} (Cloud Poller)", else: agent.name {label, agent.id} end) } /> <%= if @live_action == :edit and Map.has_key?(assigns, :agent_source) do %> <%= case @agent_source do %> <% :device -> %>

<.icon name="hero-exclamation-triangle" class="h-4 w-4" /> Overriding site/organization defaults - this device is explicitly assigned

<% :site -> %>

<.icon name="hero-building-office" class="h-4 w-4" /> Inherited from site: {@effective_agent_name}

<% :organization -> %>

<.icon name="hero-building-office-2" class="h-4 w-4" /> Inherited from organization: {@effective_agent_name}

<% :global -> %>

<.icon name="hero-globe-alt" class="h-4 w-4" /> Using global default cloud poller: {@effective_agent_name}

<% :none -> %>

<.icon name="hero-cloud" class="h-4 w-4" /> No agent assigned - cloud polling

<% end %> <% else %>

Assign this device to a remote agent for local SNMP polling. Leave empty to inherit from site or organization defaults.

<% end %>
<% end %>

SNMP Configuration

Choose monitoring mode and configure SNMP settings for detailed device information.

<%= if @monitoring_mode == "snmp_and_icmp" do %>

Monitor device availability with ICMP pings and collect detailed information via SNMP (interfaces, sensors, system info).

<% else %>

Monitor device availability with ICMP pings only (no SNMP discovery).

<% end %>
<.input field={@form[:snmp_version]} type="select" label="SNMP Version" prompt="Inherit from site/organization" options={[{"v1", "1"}, {"v2c", "2c"}, {"v3", "3"}]} />
<.input field={@form[:snmp_community]} type="text" label="Community String" placeholder="Leave blank to inherit from site/organization" autocomplete="off" /> <%= if @live_action == :edit and Map.has_key?(assigns, :snmp_config_source) do %> <%= case @snmp_config_source do %> <% :device -> %>

<.icon name="hero-exclamation-triangle" class="h-4 w-4" /> Overriding site/organization SNMP defaults

<% :site -> %>

<.icon name="hero-building-office" class="h-4 w-4" /> Inherited from site (v{@effective_snmp_version}, community: {String.slice( @effective_snmp_community, 0..1 )}{"*" |> String.duplicate( max(0, String.length(@effective_snmp_community) - 2) )})

<% :organization -> %>

<.icon name="hero-building-office-2" class="h-4 w-4" /> Inherited from organization (v{@effective_snmp_version}, community: {String.slice( @effective_snmp_community, 0..1 )}{"*" |> String.duplicate( max(0, String.length(@effective_snmp_community) - 2) )})

<% :default -> %>

<.icon name="hero-information-circle" class="h-4 w-4" /> Using default (v2c, no community set - configure at organization or site level)

<% end %> <% end %>
<%= if @form[:snmp_version].value == "3" do %>
<.input field={@form[:snmpv3_username]} type="text" label="Username" placeholder="Leave blank to inherit from site/organization" />
<% # Determine current security level from device data current_auth = @form.data.snmpv3_auth_protocol current_priv = @form.data.snmpv3_priv_protocol current_security_level = cond do current_auth in [nil, ""] -> "noAuthNoPriv" current_priv not in [nil, ""] -> "authPriv" true -> "authNoPriv" end # Get selected security level (from params if user is changing, otherwise current) selected_security_level = if Map.has_key?(@form.params, "snmpv3_security_level"), do: @form.params["snmpv3_security_level"], else: current_security_level %>
<.input field={@form[:snmpv3_security_level]} type="select" label="Auth Level" value={current_security_level} options={[ {"No Authentication, No Privacy", "noAuthNoPriv"}, {"Authentication, No Privacy", "authNoPriv"}, {"Authentication + Privacy", "authPriv"} ]} />
<%= if selected_security_level in ["authNoPriv", "authPriv"] do %>
<.input field={@form[:snmpv3_auth_protocol]} type="select" label="Auth Protocol" prompt="Select authentication algorithm" 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 selected_security_level == "authPriv" do %>
<.input field={@form[:snmpv3_priv_protocol]} type="select" label="Privacy Protocol" prompt="Select encryption algorithm" 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" min="1" max="65535" value={@form[:snmp_port].value || 161} />
<.button type="button" phx-click="test_snmp" phx-disable-with="Testing..."> Test SNMP Connection <%= if @live_action == :edit do %> <.button type="button" phx-click="trigger_discovery" phx-disable-with="Discovering..." > <.icon name="hero-magnifying-glass" class="h-4 w-4" /> Rediscover Device <% end %>
<%= if @snmp_test_result do %> <%= cond do %> <% Map.get(@snmp_test_result, :testing) -> %>
<.icon name="hero-arrow-path" class="h-5 w-5 text-blue-400 animate-spin" />

Testing SNMP credentials...

<% Map.get(@snmp_test_result, :success) -> %>
<.icon name="hero-check-circle" class="h-5 w-5 text-green-400" />

Connection successful!

{Map.get(@snmp_test_result, :message)}

<% true -> %>
<.icon name="hero-x-circle" class="h-5 w-5 text-red-400" />

Connection failed

{Map.get(@snmp_test_result, :message)}

<% end %> <% end %>
<%= if @live_action == :edit and @is_mikrotik_device and @current_scope.user.is_superuser do %>

MikroTik API Configuration

Configure MikroTik RouterOS API access. Works alongside SNMP for enhanced device management.

<%= if Map.has_key?(assigns, :mikrotik_config_source) and @mikrotik_config_source != :device do %>

Inheriting credentials from {@mikrotik_config_source}. Username: {@effective_mikrotik_username}

<% end %>
<.input field={@form[:mikrotik_enabled]} type="checkbox" label="Enable MikroTik API" />
<%= if @form[:mikrotik_enabled].value do %>
<.input field={@form[:mikrotik_username]} label="Username" placeholder="Leave blank to inherit from site/organization" />
<.input field={@form[:mikrotik_password]} type="password" label="Password" placeholder="Leave blank to inherit from site/organization" />
<.input field={@form[:mikrotik_port]} type="number" label="API Port" placeholder="8729 (SSL) or 8728 (plain)" />
<.input field={@form[:mikrotik_use_ssl]} type="checkbox" label="Use SSL (API-SSL)" />
<%= if @form[:mikrotik_use_ssl].value == false do %>

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

<% else %>

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

<% end %> <% end %>
<% end %>
<.button navigate={~p"/devices"}>Cancel <.button phx-disable-with="Saving..." variant="primary" disabled={@duplicate_device != nil or @non_routable_ip_error} > Save Device
<%= if @live_action == :edit do %>

Danger Zone

Permanently delete this device and all its data.

Once you delete this device, there is no going back. All monitoring history and alerts will be permanently deleted.

<.button phx-click="delete" data-confirm="Are you sure you want to delete this device? All monitoring history and alerts will be permanently deleted." variant="danger" > <.icon name="hero-trash" class="h-4 w-4" /> Delete Device
<% end %>