New "Sync from phpIPAM" action on IP Addresses, alongside the existing
"Sync from Tailscale" / "Sync from Proxmox" ones and built the same way:
a new integration type (config in-app, URL + API app ID + app token,
credentials encrypted at rest) that this page pulls from on demand.
Deliberately import-only, not a full integration -- no dedicated page,
dashboard widget, or nav entry, since that's all this was asked for.
Auth is phpIPAM's static "App token" method: create an API app under
Administration -> API with its security set to "SSL with App token", and
its one-time code goes straight in as the `token` header (also sent as
`phpipam-token`, in case a given version expects that name instead) --
no login call, no token to renew. The user/password "User token" method
isn't implemented.
Addresses are read the standard way: GET /subnets/, then GET
/subnets/{id}/addresses/ for each, rather than assuming a single
"all addresses" endpoint exists on every version. phpIPAM wraps every
response as {code, success, data} -- including an empty result: a subnet
with nothing in it answers success:false, message:"No addresses found"
rather than success:true, data:[]. That's read as "nothing here", not a
failure; anything else with success:false throws with phpIPAM's own
message. One subnet failing outright (e.g. the app lacks permission on
it) is skipped with a note rather than aborting the whole sync. Every
address field is read defensively -- optional, independently
type-checked -- so a field phpIPAM renames or drops in some version
leaves that value blank instead of breaking the import.
Imported entries: label from hostname or description, "phpIPAM" as
vendor, the subnet's own description (or its CIDR, if it has none) as
location, and description/note/MAC folded into notes. Existing sync
plumbing (upsertSyncedEntry) gained a location parameter so this and any
future sync can set it; the two existing syncs pass null, unchanged.
Endpoints, the token header, and the address/subnet field names are
cross-checked against phpIPAM's own published API documentation. Not
verified against a live instance -- there wasn't one available while
building this, so if a real sync comes back empty or with the wrong
fields, that's the next thing to check.
Verified with 23 backend checks against a fake phpIPAM server matching
that documented shape (the empty-subnet quirk, a subnet that fails
outright, malformed/missing fields, a non-JSON response) and the real
route (added/updated/skipped counts, a manually-entered IP never
overwritten, roles, no-enabled-integration, upstream failure surfaced
per-integration rather than as a 500, audit entries) plus a browser check
of the real IP Addresses page against the real routers: the sync button,
its result message, re-syncing (updates rather than duplicates), the
manual entry staying untouched, and the viewer view.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>