Skip to content

feat: live NetBox IP conflict check on reservation add form #64

@marcinpsk

Description

@marcinpsk

Summary

When creating a new reservation via the UI, check whether the IP address the user is typing already exists in NetBox IPAM. Surface the result as an inline advisory on the form — no blocking, just visibility.

Motivation

Currently the form gives no feedback about the NetBox state of an IP until after the reservation is saved and optionally synced. If the IP already exists in NetBox and was not created by Kea sync (e.g. it is a manually maintained entry assigned to a device interface), the user has no way to know this until sync silently overwrites it.

Proposed behaviour

On blur of the IP address field (DHCPv4: ip_address, DHCPv6: ip_addresses), fire a lightweight AJAX call to a new server-scoped endpoint. The endpoint queries NetBox and returns a small HTML fragment injected below the field:

NetBox state Fragment
IP not in NetBox (empty — no output)
IP exists, description starts with "Synced from Kea DHCP" or is blank Blue info: "Already in NetBox IPAM (status: reserved). Saving will update it."
IP exists, foreign description or assigned to an interface Yellow warning: "This IP exists in NetBox and was not created by Kea sync (status: active, description: 'Router loopback', assigned to: Router-1/eth0). Syncing will overwrite this entry."

The check is advisory only — the reservation can still be saved regardless. It only runs in Add mode; in Edit mode the IP field is already disabled and cannot change.

Implementation plan

1. New view — ReservationCheckNetboxIPView (sync_views.py)

  • GET /plugins/netbox-kea/servers/<pk>/reservation/check-ip/?ip=<addr>
  • Server-scoped via pk so existing Server permission checking applies.
  • Validates ip param with netaddr.IPAddress; returns HttpResponse("") on invalid/missing.
  • Calls sync.get_netbox_ip(ip_str) to look up the NetBox entry.
  • Determines ownership: description.startswith("Synced from Kea DHCP") or blank description → Kea-managed; anything else → foreign.
  • Returns rendered netbox_kea/inc/reservation_ip_check.html fragment.

2. New fragment template — netbox_kea/inc/reservation_ip_check.html

Three states described in the table above. Shows IP address as a link, status badge, description, and assigned object when present.

3. URL registration — urls.py

path(
    "servers/<int:pk>/reservation/check-ip/",
    views.ReservationCheckNetboxIPView.as_view(),
    name="reservation_check_ip",
),

4. Template change — server_reservation_form.html

Add a target div after {% render_form form %}:

<div id="netbox-ip-check"></div>

Add an inline <script> block (plain fetch, no HTMX dependency) that:

  • Skips attachment when action != "Add".
  • Selects #id_ip_address (v4) or #id_ip_addresses (v6) based on dhcp_version.
  • On blur, takes the first comma-separated IP (v6 may have multiple), fetches the endpoint, swaps the fragment into #netbox-ip-check.
  • Clears the div when the field is empty.

What does NOT change

  • sync.py — no changes to sync functions or ownership logic.
  • Form fields — no new form fields, no widget attribute changes.
  • POST behaviour — saving the reservation and syncing to NetBox work exactly as today; this is purely a pre-submission hint.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions