Quick start ย ยทย Features ย ยทย Screenshots ย ยทย Architecture ย ยทย REST API ย ยทย Roadmap ย ยทย Changelog
|
Manuale tecnico โ Italiano 48 pagine illustrate ยท interfaccia, onboarding e manuale completi in italiano, con selettore IT/EN nell'app. |
Technical manual โ English 48 illustrated pages ยท fully bilingual UI, onboarding and manual, with an in-app IT/EN switcher. |
InfraNet Pro is free and open source. If it helps your work, a coffee funds the next feature. โ

A quick tour โ auto-discovered topology, live 19โณ racks, one-click VLAN isolation, SNMP discovery and the grounded AI assistant. More screenshots โ
InfraNet Pro is a self-hosted web application that lets network engineers draw rack layouts and floor-plan diagrams, then bring them to life by polling live data from real devices via SNMP. Interfaces, VLANs, LAG groups and neighbour topology are discovered automatically โ no external database, no cloud dependency, minimal tooling (a lightweight esbuild bundle for the frontend; npm start builds it).
Current product direction: InfraNet Pro keeps discovery and classification inside the app. External discovery and monitoring engines are not part of the active roadmap; the internal SNMP/sysObjectID/LLDP/CDP/FDB engine is the source of truth and can be refined with local plugins over time.
|
๐บ๏ธ The drawing checks itself You draw the racks and the floor plan. One button polls the real devices and reports what moved: port state, an IP change on the same MAC, a swapped serial, a device that vanished, a cable that never existed. |
โ Manual-first, always What you declare is law. Discovery proposes and never overwrites: edited fields carry a padlock, and a measurement that disagrees is raised as a warning instead of quietly winning the argument. |
๐ซ It never bluffs Every figure declares where it came from โ declared, measured (with its age), or derived. A number nobody measured shows as a dash, not a zero, and each lens names what it is not looking at. |
|
๐ฆ Zero infrastructure One Node process and one main JSON file per project, with optional history and snapshot sidecars kept outside that project file. No database, no cloud, no agent on your devices, no telemetry. Binds to 127.0.0.1 by default, and runs in Docker with one command.
|
๐ Standard MIBs, any vendor IF-MIB, Q-BRIDGE, LLDP/CDP, ENTITY-MIB, IEEE 802.3ad, UPS-MIB, Printer-MIB, HOST-RESOURCES. Vendor intelligence lives in hot-reloadable local plugins โ never hardcoded into the scan path. |
๐ค Built to hand over A vector PDF dossier with an audit-ready asset register, editable draw.io racks (one layer per VLAN), printable cable labels, a read-only REST API and a ready-made Ansible inventory. |
git clone https://github.com/muttley1973/infranetpro.git
cd infranetpro
npm install
npm startOpen http://localhost:8421. An admin account is created on first start and you are prompted to change its password.
๐ณ Prefer a container?docker compose up -d --buildHost networking by default, so discovery is complete. See Docker. |
๐ฅ๏ธ On Windows? Double-click avvia.bat.Full detail in Installation and Configuration. |
Your first five minutes: New project โ Add device โ give it an IP โ Properties โ Integration โ community โ Poll. Then run Discover subnet on your LAN, and press Verify to see your document compared against the live network, row by row.
๐ฐ What's new (v2.10.0) โ the document learns where things are, and what hosts what. A NetBox location becomes a room on the floor plan, with its racks and devices inside it; what has no location stays out of the rooms rather than tucked into one nobody declared. Hosting virtual machines is a capability, not a type โ storage boxes, NAS and servers carry the same VM section a hypervisor does, and the import stops rewriting the type to make room for them. A device can declare where it is in its life cycle, so a planned device that stays quiet is expected and a decommissioned one that answers is flagged. Imported ports, patch-panel slots and racks keep their DCIM identity, so renaming a rack upstream is a rename and not a delete.
Under that, the neighbour protocols got honest: an LLDP identifier is read from the subtype it declares, not from the length of its value, and the port a neighbour names is resolved instead of assumed. A neighbour that produces no cable now says why, and two devices that announce each other are drawn as adjacent even when the port is unknown โ with
?where the port would be, rather than a port picked at random.๐ Earlier releases โ networks as first-class objects with IPv6 parity (2.9.0), the NetBox/DCIM import (2.8.0), per-interface addresses (2.8.2) โ are in the CHANGELOG.
๐ Security-audited & hardened. The codebase has undergone an application-security audit (no critical issues) and the follow-up fixes are covered by automated security regression tests: the data surfaces (AI context, REST DTOs, exports) are allowlist-only so secrets never leave the machine, OS commands run via
execFilewith no shell, project IDs are path-traversal-safe, and secrets use a CSPRNG. See Authentication & Roles โ Security hardening & audit.
|
๐ Get it running |
๐งญ What it does |
๐ฌ Under the hood |
Roadmap ยท Feedback & requests ยท Contributing ยท Support ยท License ยท ARCHITECTURE.md ยท CHANGELOG.md ยท LICENSING.md
Full feature manual (PDF) โ
Italiano ยท
English
Dark cover, white printable interior, 19 illustrated chapters, 48 pages per language.

Topology โ auto-discovered L1/L2 neighbours (LLDP / CDP / FDB) drawn over the floor plan

Dashboard โ three standing questions in three columns, every number carrying its provenance: declared, from a scan, derived, or not declared as a dash rather than a zero. Rows open in place. (Shown in Italian โ the whole UI ships in both languages.)
| Rack view | Rack detail |
|---|---|
![]() |
![]() |
| 19โณ rack with live, colour-coded square port LEDs and an SNMP status stripe | Front-panel detail: port numbering, MGMT / SFP blocks; device data lives in the Properties panel |
| VLAN filter | Physical cable path |
|---|---|
![]() |
![]() |
| Isolate a VLAN across the whole map in one click | Double-click a cable to trace switch โ patch panel โ wall socket โ endpoint |
| Discovery (SNMP) | Login |
|---|---|
![]() |
![]() |
| Scan a subnet with SNMP v1 / v2c / v3 and import reachable devices | Session-based auth, IT / EN switcher, bound to 127.0.0.1 by default |
| Area | At a glance |
|---|---|
| ๐บ๏ธ Diagramming | 19โณ racks with live port LEDs, floor plans, ~4,100 device models across 52 vendors, MGMT & SFP blocks, hypervisors and VMs, the Dashboard, exports to PDF ยท SVG ยท draw.io |
| ๐ก Live SNMP | v1 / v2c / v3 discovery, interfaces, VLANs, LAG, LLDP/CDP neighbours, ENTITY-MIB inventory, wireless associations, DHCP lease import, the Verify / Drift report |
| ๐ DCIM / IPAM sync | Import an existing NetBox into a new project over its REST API โ sites, racks (front/rear split), floor-placed, devices, interfaces, VLANs/prefixes and patch-panel cabling; free import, paid write-back |
| ๐ LAG detection | A four-level cascade โ ifStackTable ยท IEEE 802.3ad ยท LACP actor state ยท LLDP-inferred โ plus LACP mode coherence across both ends |
| ๐ท๏ธ VLAN | Access and trunk detection, Q-BRIDGE bitmaps with a VTP fallback, auto-derived trunks, per-VLAN IPAM occupancy, one-click isolation across the whole map |
| ๐ถ Wireless | Up to 8 radios per device with their own SSID, band, channel, security and VLAN; over-the-air association discovery from the bridge FDB and the L3 neighbour table |
| ๐งต Cabling | Segment editor on the TIA-568 hierarchy, copper and fibre reach validation, end-to-end physical path trace, printable label sheets and CSV |
| ๐ History & automation | One Automatic monitoring scheduler (Light / Full), opt-in autosave, a verification timeline and restorable full-state snapshots โ kept outside the project file, behind a database-ready interface |
| ๐ค AI assistant | Bring-your-own-key, OpenAI-compatible, local by default; allowlist context, grounded answers with clickable citations, Ansible drafts โ advisory, never auto-applied |
| ๐ Security | Session auth with admin/viewer roles, rate-limited login, loopback bind, secrets structurally excluded from every data surface |
| ๐ Bilingual | Complete Italian and English interface, onboarding and ~49-page manual, guarded by an it โ en key-parity test |
Every heading below opens. Deeper detail lives in ARCHITECTURE.md, the technical manuals and the commit history.
๐บ๏ธ Diagramming โ racks, floor plans, labels, hypervisors, the Dashboard and every export
- Rack view โ drag-and-drop 19โณ rack units (1Uโ8U) with colour-coded port LEDs.
- Apply model โ search a real switch or router model and apply it in one click: port count and front panel are set natively and drawn by the built-in renderer. The catalogue ships ~4,100 models across 52 vendors, generated from public-domain device data (
tools/import-device-types.js). - Front-panel controls โ per-device port count and layout (Auto / Linear / Sequential / Cisco-alternating), with an optional separate SFP block and a dedicated MGMT block.
- Dedicated MGMT ports โ up to 4 cyan cells outside the regular
1..Nnumbering, with an editable label (MGMT, iLO, iDRAC, fxp0โฆ). Excluded from VLAN/LAG/FDB data-plane logic. - SFP block โ a separate cell group with an anodised border, left or right of the main port grid, up to 48 per block; high-density combinations compact the gaps and cells automatically so copper, SFP and MGMT ports remain visible.
- Floor map โ place devices on an SVG floor plan; cables drawn as bezier curves.
- Labels say what a thing is โ the name on top, the address underneath. When Discover finds no hostname it stores the IP as the name, so the readable line is derived for display from the classified type and vendor (
IoT-AzureWave,NAS-LaCie);node.nameis never rewritten and a declared name always wins (lib/node-label.js). - Multi-port floor devices โ PCs, access points and custom endpoints can declare several ports, each independently cablable. Orthogonal to the pass-through model of wall sockets and VoIP phones.
- VMs under whatever hosts them โ hypervisor, home lab, storage array, desktop NAS or server: hosting VMs is something a device does, not what it is, and a Synology or QNAP running them from a package is still a storage box. Model VMs under a host (
node.vms[]): a compact list, and a dedicated VM card with identity, network & access, allocated resources and handover data. A VM can declare several vNICs (a virtual firewall has WAN + LAN + DMZ), each feeding the derived trunk, the documented devices of the Check and the duplicate audit. A vNIC has no cable of its own: it rides the host uplink, and with uplinks in teaming which one carries it is not knowable โ so it is not declared. - VMs over SNMP โ a VM exposing its own agent is polled like any host (
vm.integrationmirrors the device shape field for field). What comes back is a measured block stamped with the read time, kept apart from what you declared. An answer proves the VM is running; silence never marks it stopped. - Absorb a discovered tile into its host โ drag a loose tile onto the host's Virtual machines section and it becomes a VM, inheriting name/IP/MAC, so it stops being flagged undocumented. A MAC or an IP is enough. Undoable.
- Uniform floor/rack interaction โ single click selects, double click opens Properties, on the floor as in the rack.
- Operating-system logos โ in the device and VM panel headers and the VM list, from public-domain and permissively-licensed sets. A specific logo only from an authoritative source (SNMP
sysDescr, a manual field, a guest OS), a grey family glyph for a mere TTL hint, and nothing when the OS is unknown (lib/os-icon.js). - Sub-header bar โ breadcrumb, the next recommended step with a one-click button, and project stats on the right (documentation completeness, device count, SNMP health dot). Computed, never estimated (
lib/subbar-stats.js). - Dashboard view โ a read-only view switch that answers three standing questions in three columns: LAN (is the document complete?), Conformance (does it still match reality?), Expansion (how much can I grow?). Every cell carries a number, a plain-word verdict and the provenance of the figure โ declared, measured with a date, derived, or not declared as a dashed cell rather than a zero.
- Dashboard verdicts โ each column opens with a health dot and a sober phrase; red is reserved for flying blind, so a synced-but-imperfect project reads amber. A since-last-read delta shows problems closed or opened versus the previous read, and after seven days without contact a green verdict in the two measurement-based columns degrades and says how long ago it was read.
- Dashboard drill-downs โ every row opens in place. Free addresses count capacity on the declared subnet prefix (a /16 is ~65,000 addresses, not 254), networks you use but never declared surface as undeclared, free ports split into in-rack / outside-rack / by-speed, and IP and MAC share one row as the project's ARP pairing.
- Dashboard lenses โ three opt-in full-width lenses beyond the summary: Recoverability (DR) (backup freshness, hardware identity, location, presence), Security & Services (encrypted versus cleartext SNMP, default communities counted without the value ever leaving the engine, management-VLAN segmentation), and Health โ the only one that speaks about the present, composing telemetry already returned through documented thresholds.
- "What I'm not looking at" โ a footnote under every lens naming the dimensions the summary does not judge (WAN, L3 routing, spanning tree, firewall/ACL, AAA, restore proof, temperature, trunk symmetry), so the absence of an alarm is never read as all clear. It is data in the engine, retired one line at a time.
- Wireless links โ mark a connection wireless and it draws as a sine wave, skips cable validation and is auto-suggested when one end is an access point. Wi-Fi-capable devices expose a radio port that hosts many clients without consuming physical ports; any other device can opt into AP mode to broadcast an SSID without changing its type.
- Wireless properties โ SSID, band, channel (grouped by sub-band, DFS-marked), security and 802.11 standard, validated with educational warnings; the association inherits them read-only and carries its own RSSI and distance (
lib/wifi-spec.js). - UPS / ATS live SNMP โ a read-only live block from UPS-MIB (RFC 1628) and the APC PowerNet profile: mains or battery, charge, runtime remaining, load, input/output voltage. A transfer switch whose profile doesn't answer says so rather than showing a card of dashes (
lib/power-mib.js). - L3-lite gateway โ who routes each network, one row per declared prefix, both address families: an L3 badge, a read-only SVI panel section, and a Report โ L3 map with orphan, out-of-subnet, wrong-family and reserved-address warnings plus CSV. Binding a VLAN to its routing device stays on the VLAN, auto-suggested with the manual override winning โ the SVI is one interface even when it carries an IPv4 and an IPv6 gateway.
- IPAM hygiene โ the L3 map flags the same address documented on two devices (IPv4 and IPv6, compared canonically, so two spellings of one IPv6 are one address) and any two declared prefixes that overlap (
lib/ipam-audit.js, document against document, never invented). - LAG member consistency โ warns when a group's members have different speeds or access VLANs; they would not bundle on real hardware (
lib/lag-audit.js). - LACP mode & cross-end coherence โ each group has a mode (active / passive / static), auto-derived over SNMP and manual-first. InfraNet resolves the peer LAG from the cabling and warns on the two classic failures: both ends passive, and LACP against static.
- Cable path insight โ cable properties reconstruct the linear path across wall ports, patch panels and media converters.
- Multiple projects โ create, rename, copy and delete independent maps.
- Vector PDF / SVG export โ full rack export including MGMT and SFP side blocks, with a port-assignment table.
- draw.io rack export โ a native, editable
.drawiodiagram, one page per rack, with real port cells inside draw.io's own numbered rack container. Cables export as one native edge each on one layer per VLAN, coloured and routed so they never overlap, each layer carrying a clickable cable table. Pages auto-fit A4 or A3. - Audit-ready asset register (PDF) โ an optional per-device inventory page built from the same secret-free allowlist DTO as the REST API, plus a last revised timestamp on the cover: the documentation evidence NIS2 and ISO 27001 A.5.9 ask for. The whole report is bilingual and follows the UI language.
- Dashboard page in the dossier โ one page after the floor plan carrying the three questions, their verdicts, the provenance dots and the "what I'm not looking at" line. The executive summary the dossier was missing.
- Recoverability (DR) section in the PDF โ one row per managed device: where the backup lives (the pointer, never the config or credentials), when it was taken, the serial and firmware to procure, the lifecycle dates and the rack it returns to. Kept off-site, it survives the LAN it rebuilds.
- Progressive patch-panel numbering โ several panels serving one run can continue each other's numbering via an explicit chain, with a cycle guard. Display-only: port IDs stay stable.
- Cable-label export โ pick the fields you need with a live preview; export as CSV for mail-merge or as ready-to-print PDF label sheets (Avery A4 grids, Dymo rolls, configurable generic). Includes a wrap/flag mode that repeats the ID so it reads from both sides. The room is derived geometrically from the floor position.
- Dark UI โ a focused dark theme driven by semantic CSS tokens, so a light theme would only add a second value set.
๐ก Live Device Integration (SNMP) โ discovery, polling, neighbours, honest presence, the drift report and DHCP leases
- SNMP v1 / v2c / v3 (authPriv, authNoPriv, noAuthNoPriv) out of the box.
- Auto-discovery โ scan a subnet (CIDR or range) and auto-place reachable devices.
- Interface discovery โ every physical interface with speed, duplex, admin and operational state.
- Hardware inventory โ
brand/model/serialNumber/firmwareVerfrom ENTITY-MIB (RFC 6933). Manually edited values are never overwritten. - LLDP / CDP neighbour polling โ resolves connected neighbours and auto-draws cables.
- Wireless association discovery โ the Sync draws over-the-air associations from the bridge FDB (a client MAC on a radio interface) and the L3 neighbour table, the latter universally implemented so it covers all-in-one boxes and software hotspots. The SSID is chosen by VLAN match; ambiguity is left for you (
lib/wifi-assoc.js). - Auto-link creation โ duplicate links between the same pair become a LAG automatically; virtual MACs (Docker, VMware, Hyper-V, Xen, KVM) are filtered out via the OUI engine.
- Topology walk โ one-click recursive discovery across a seed device's LLDP neighbours.
- Off-segment discovery via SNMP ARP โ the walk also reads each reachable device's ARP table and proposes hosts that answer neither ping nor SNMP nor LLDP/CDP. Bounded to the scanned subnet, deduped, presented as observed and not pre-selected.
- Manual-first โ user-edited
hostname,ipandintegration.hostare protected by*Manualflags and never overwritten by SNMP or discovery. - Port mapping by ifName โ SNMP interfaces are matched to ports by name, not by position, so a hand-cabled port is never silently reassigned. A genuine access-versus-trunk mismatch is surfaced as a warning, not hidden. Validated on a multivendor lab: Cisco vIOS, MikroTik, VyOS, net-snmp, two LACP bundles, four VLANs.
- Reality Check / Drift Report โ one button runs the SNMP sync plus a multi-signal presence sweep (ping / ARP / TCP on top of SNMP and FDB), then compares the live network against the documentation in 6 categories: consistent ports, state drift, IP change on the same MAC, documented-but-absent, undocumented devices, and ghost cables.
- Honest presence โ red only from a signal a live host cannot suppress (a local ARP miss on the server's own segment, or a switch access port down for N consecutive syncs). A merely silent device, or one on a subnet the sweep never reached, is reported not verified โ never wrongly absent. A device proven alive by a router's ARP table stays green across subnets.
- One click per row โ update doc, ignore (persisted until the condition changes), investigate. The diff is a pure tested function (
lib/drift-report.js), and the result lands in the Dashboard's Conformance column as saved state rather than a transient overlay. - DHCP lease import โ paste or load a lease table (ISC dhcpd, dnsmasq, Kea, generic CSV; pfSense, OPNsense, MikroTik, Synology, Windows exports) for authoritative MAC โ IP across all VLANs โ what local ARP cannot see behind an L3 firewall. Multiple servers accumulate as persisted sources. A lease table is an identity map, not a liveness probe: a documented device missing from it is unverifiable, never absent (
lib/dhcp-lease.js). Live vendor pull is a separately-distributed driver pack. - Endpoint/BYOD transparency โ undocumented entries that look like user devices (guest VLAN, crowded uplink port, randomised MAC) collapse into a group so the actionable infrastructure stays clean. Each hidden row says why in plain language, and a toggle reveals them.
- "Management VLAN" role โ the opposite of a guest VLAN: an undocumented device seen there is forced to infrastructure, never collapsed as BYOD, and flagged with a red security badge.
๐ DCIM / IPAM sync (NetBox) โ import an existing NetBox into a new project; write-back is a paid module
- Live connection over the REST API โ set a base URL and an API token, then Test connection probes
/api/status/(chip turns green on success, red on failure). If an API endpoint is pasted by mistake, InfraNet reduces it to the instance base automatically. The token is stored server-side at0o600, is never returned to the browser, and never enters git โ the same secret handling as the SNMP and AI keys. - Three-step import wizard โ new project โ Scope (pick a site, with counts), Entities (devices+ports+cables / IPAM / racks toggles), Preview (live counts, per-row deselect, honest warnings) โ Create project. A staged progress screen, a result with counts and Open project, and a retry on error. Import is read-only (GET); it never writes to NetBox.
- NetBox authentication โ use the v2 token format (
nbt_<key>.<secret>), sent asAuthorization: Bearer; legacy tokens continue temporarily asAuthorization: Tokenand are identified by the connection test with a migration warning. Raw tokens and completeToken .../Bearer ...header values are accepted. REST paths remain unversioned (/api/dcim/...,/api/ipam/...,/api/status/). - One site = one project โ the site names the project, racks keep their NetBox names, and a device's Location becomes a note (InfraNet has no multi-floor model); import one site at a time so rack names stay unambiguous.
- Racks placed on the floor plan โ each imported rack is auto-positioned on a non-overlapping grid and appears as a clickable floor icon; the first rack opens in the Rack view, populated.
- Front/rear cabinet split โ a NetBox rack with devices on both faces becomes two InfraNet racks (
โฆ ยท retro), each device on its own side, with cross-face cables drawn as cross-rack links. - Patch-panel cabling โ front/rear-port terminations are reconstructed as a native pass-through chain (switch โ panel-A โ panel-B โ server) sharing the pass-through pid โ no synthetic segments. Type-aware termination resolution avoids id collisions across NetBox's separate id spaces, and the NetBox 4.6
rear_ports[]array schema is handled alongside the legacy singular field. Power/PDU and WAN-circuit cables are out of scope and skipped quietly. - Catalogue reconciliation โ a NetBox
device_type.slugis matched against the built-in device-type catalogue (both seeded from the same public NetBox library), applying the native port count and front panel; otherwise the imported interface count is used. - PDU power connections โ up to 48 outlets are rendered inside a frame that adapts to the device height (
1U,2Uand above). The PDU and single-outlet Properties โ Alimentazione accordions expose the powered device through a dropdown of devices placed in the project racks and its power port from NetBox; the aggregated outlet list uses the same dark controls, typography and focus treatment as the single-outlet panel. Manual edits are stored as protected overrides, with a one-click reset to the imported value. Outlet state follows NetBox'sEnabled/Disabled/Faultymodel as active / inactive / fault, defaults to inactive when undocumented, and becomes active when a connection is documented unless an explicit manual or imported state says otherwise. A manual state always wins over the imported NetBox value, including the raw imported status. - Manual-first, non-destructive โ import creates a new project and never clobbers an existing one. Write-back to NetBox is a paid module (
modules/dcim-export/): dry-run diff first, create-or-PATCH by natural key, never delete; the free build feature-detects it and hides the Export tab.
๐ LAG / EtherChannel (multi-level detection) โ the four-level cascade, and what Cisco does differently
- L0 โ
ifStackTablehigher/lower layer analysis. - L1 โ
dot3adAggMemberPorts(IEEE 802.3ad MIB). - L2 โ
lagAttached+ actor operational state bitmask. - LLDP-inferred โ two or more parallel LLDP links between the same device pair.
- Cisco IOS
Port-channel(ifType 53 / propVirtual) fully supported. - Groups auto-named from the aggregator interface (
Port-channel1,bond0). - Selecting a LAG member port highlights all its siblings.
๐ท๏ธ VLAN Management โ access and trunk, the VTP fallback, derived trunks and IPAM occupancy
- Per-port VLAN assignment (access mode) and trunk detection with native VLAN and allowed list.
- VLAN list shown in both the cable popup and the port popup, in compact range notation (
1,10,100-120,200). - Fallback VLAN discovery via Cisco VTP MIB โ no per-VLAN community required.
- Optional VLAN legend on the floor plan; in Topology view it stays on as the clickable VLAN filter.
- VLAN details grouped by device โ the members modal collapses access ports into per-device accordions.
- Auto-derived trunks โ a link's trunk membership is derived from the VLANs its endpoints carry (VoIP voice VLAN, per-SSID Wi-Fi VLANs) plus the polled trunk. Manual-first: a hand-set trunk wins (
lib/vlan-trunk.js). - Unified VLAN distribution, cable โ wireless โ the same propagation seeds physical ports and radios, so a wireless client inherits its SSID's VLAN exactly like a wired access port.
- Site default native VLAN โ change the untagged default site-wide; per-port and per-trunk overrides win.
- IPAM occupancy from DHCP leases โ real address usage per VLAN: capacity, a usage bar, and a documented / DHCP-only / free breakdown, with an "N undocumented โ Adopt" shortcut that carries MAC, IP and hostname (
lib/ipam.js, read-only).
๐ถ Wireless โ radios, SSIDs, bands and channels โ and why a radio only talks to a radio
- Up to 8 radio interfaces per device, each with its own SSID, band, channel, security and VLAN (
lib/radio.js). - Wireless is its own connection type โ a radio port only connects to another radio port; radio to network-port is rejected.
- Per-device radio layout โ floor tiles show radios on 8 perimeter anchors, rack devices line them up on the left edge.
- A device's SSID VLANs are carried tagged on its wired uplink, which automatically becomes a trunk.
๐งต Cabling Metadata โ segments, the TIA-568 hierarchy, reach validation and the physical path trace
- Cable-level metadata on
state.links[]: type, length, colour, install date and installer, permanence, notes โ with backward-compatible normalisation of legacy fields. - Segment editor โ highlight mode lights every free pass-through port; click one to split a cable into two real segments (
PC โ patch panel โ switch). Remove hop merges them back (lib/cabling.js). - TIA-568 hierarchy rule โ a hop can only be inserted if it sits between the endpoints in the structured-cabling hierarchy, so a completed run cannot be extended with out-of-place hops. VoIP phones are pass-through at level 0.5.
- End-to-end chain validation โ a badge flags structurally anomalous paths (active device mid-span, non-monotone order, too many hops). Informational, non-blocking.
- Cable validation โ copper reach per category (Cat8 is 30 m, and 10G over Cat6 within 55 m is compliant, so it does not warn) and fibre reach by optical class and speed โ OM3 carries 300 m at 10G but 100 m at 40G. Without a class, a speed or a length, nothing is asserted.
- Chain-aware topology state โ a routed inferred cable stays inferred on every hop until the whole chain is confirmed, so there are no mixed animated and solid segments.
- Map view bar โ the Topology toggle and the VLAN filter legend sit together at the top-right of the floor plan.
- Topology legend toggles โ
TRUNKhighlights trunk links and reveals them inside the rack window;ENDPOINThides the last hop to leaf devices to declutter the backbone. - Physical-path trace โ double-click a cable in Topology to light up the whole run (switch โ patch โ wall socket โ endpoint) across racks and floor.
๐ค AI Assistant (advisory) โ bring-your-own-key, allowlist context, grounded answers and Ansible drafts
- In-app assistant, bring-your-own-key โ a third Assistant tab that answers questions about your documented network in plain language: who is on a VLAN, what is on a port, which IPs are free, why a device is absent, SNMP health, topology, SSIDs and hardware capabilities. Provider-agnostic through a single OpenAI-compatible endpoint, local by default so data never leaves the machine.
- Data security by construction โ the API key lives only on the server and never returns to the browser. The context is built from the same allowlist as the REST API, so the SNMP community and credentials are not in the list and physically cannot leave; a secret-name denylist guards the health passthrough. A "Show what leaves" button previews the exact sanitised JSON, and a build-failing test asserts no secret can reach the context.
- No hallucination โ "InfraNet computes, the AI narrates": drift, free IPs and gaps are pre-computed and passed as facts, and the model is told to answer "not in the documentation" when it doesn't know.
- Scope & capability toggles โ pick what leaves the machine (inventory, ports, health, topology, drift) and what the assistant may do (Q&A, diagnostics, gap-finding, suggestions, Ansible draft). Zero-dependency server client; no model bundled.
- Chat controls โ the robot button in the toolbar opens the scope and capability settings at any time, a red trash button clears the conversation (session-only, never persisted), and saving the config refreshes the panel instantly.
- Clickable citations & anti-invention check โ answers surface the devices and VLANs they used as chips that jump to the node on the map, and a downstream check flags any IP or MAC the model names that isn't in your data (
lib/ai-grounding.js; SNMP OIDs are recognised as such, never mistaken for IPs). - Find gaps, draft Ansible, explain Drift โ the context carries pre-computed gaps and the next free IP per VLAN. Ask for automation and you get a playbook rendered as a draft card, never executed, and every actionable Verify row has an Explain button that seeds a grounded question about that exact case.
- Hardware capabilities & per-model advice โ each device's documented capabilities are pre-computed (
lib/hw-capabilities.js): PoE budget and headroom, UPS runtime, CPU/RAM/storage, NAS capacity, firewall throughput, controller AP capacity, port capacity and aggregate uplink bandwidth. An undocumented field is omitted, never invented. Model-specific suggestions are labelled "typical, verify on the datasheet" and kept separate from InfraNet's authoritative data. - Onboarding copilot โ a next-step chip from deterministic rules over your project state, working even before a model is configured (
lib/onboarding.js). "Show me" lights the real toolbar button with a coach-mark that stays until you click it; "Ask" seeds a grounded how-to. Help is anchored to the real command surface derived from the UI itself, so it cites labels that exist. - Onboarding guide โ the assistant orients you across the full workflow (build โ document โ verify โ analyse โ hand off โ automate) by the features' real labels, and proactively surfaces under-used ones relevant to your project's actual state.
- Health monitoring & proactive alerts โ the assistant sees each device's real SNMP health (printer supplies, host CPU/RAM/disks, UPS). A pure engine derives deterministic alerts against thresholds (
lib/health-alerts.js) and the prompt tells the assistant to report problems first, using only pre-computed values. HOST-RESOURCES is polled for network gear too, so Linux-based devices give CPU, RAM and disk for free. Reachability stays with Verify; temperature and traffic are not collected, so they are never fabricated.
๐ Security โ authentication, roles, what it binds to and what it never sends
- Session-based authentication (express-session + bcryptjs) with a rate-limited login endpoint.
- Two roles: admin (full control) and viewer (read-only), with SNMP secrets redacted for viewers.
- Auto-generated session secret persisted to
.session-secret, owner-only and written atomically. - Binds to
127.0.0.1only โ not exposed to the network by default. - Baseline security headers on every response, path-traversal-safe project IDs, and CSPRNG-generated secrets. See Security hardening & audit.
๐ Internationalization (i18n) โ the bilingual interface, and the test that keeps it honest
- Bilingual UI (Italian / English) with a switcher in the user menu and on the login page; the choice is persisted and carried into the app.
- Pure, zero-dependency
lib/i18n.js:t(key, vars)with anit โ enfallback, so an untranslated key never breaks the UI. - Two wiring mechanisms:
data-i18nattributes for static HTML,t('key')inline for JS-generated panels. - The technical glossary (VLAN, SNMP, LLDP/CDP, SFPโฆ) and vendor names are intentionally left untranslated.
- An
it โ enkey-parity test guards against missing translations โ including the pure validators, whose warnings used to be hardcoded Italian.
A single Node/Express backend serves a static frontend and persists each project as a JSON file โ no database, no cloud. Top-level layout:
infranetpro/
โโโ server.js ยท auth.js ยท utils.js # Express bootstrap, session auth (bcrypt), shared helpers
โโโ server/ # Backend modules: projects store, netscan, classify, PDF/label render, AI (context/prompt/provider), routes
โโโ drivers/snmp.js # SNMP v1/v2c/v3 driver (poll / probe / neighbours)
โโโ engine/ # Plugin engines: sysobject-engine.js, oui-engine.js, fusion-scorer.js (+ index.js)
โโโ plugins/ ยท plugins/oui/ # Seed vendor catalogs (sysObjectID + OUI/MAC), zero-database
โโโ data/ # oui-db.json (IEEE snapshot) ยท ai-config.json (BYO key, git-ignored)
โโโ lib/ # Pure shared logic (browser + tests): i18n, cidr, correlate, and the app-*.js glue modules
โโโ src/ # Frontend ESM bundled by esbuild โ dist/app.bundle.js (app.js nucleus + glue)
โโโ styles/ # Modular CSS (partials + design tokens)
โโโ netmapper.html ยท login.html ยท export.js
โโโ test/ ยท tests/ ยท tools/ # Regression suites + syntax check
โโโ projects/ ยท users.json ยท .session-secret # Runtime data (git-ignored)
Design principles:
- Minimal-tooling frontend โ the only build step is a lightweight esbuild bundle of the
src/ESM modules; the purelib/*.jsandexport.jsstay classic static assets by design. The strangler migration to ESM is complete; retiring the transitionalwindowbridge (win.*reads โimport, inline handlers โ event delegation) is being finished one panel at a time โ Axis A (win.*โimport) is at its floor, and Axis B (inline handlers โ delegation) is driven down behind a monotonic ratchet that only shrinks. See ARCHITECTURE.md ยง10. - File-based storage โ each project is a plain JSON file (easy to back up / version-control); the floor-plan image is kept out of the JSON as a sidecar asset and re-attached as a data-URL on load, so saves stay fast even with large maps.
- Internal plugin model โ discovery intelligence is extended with local SNMP/sysObjectID/OUI plugins and self-contained drivers, never external discovery platforms.
- Tested core โ bug-prone parsing/normalization logic is covered by a dependency-free regression suite (
npm test); CI also runs a syntax check, an ESLint gate, atscJSDoc type check and a real-browser e2e suite.
The full module-by-module layout is documented in ARCHITECTURE.md.
| Dependency | Version |
|---|---|
| Node.js | โฅ 16.0.0 |
| npm | โฅ 8 (bundled with Node 16) |
| Network access | UDP 161 to managed devices |
No external database. A one-command Docker setup is provided, but it's optional โ bare-metal Node works exactly the same.
# 1. Clone the repository
git clone https://github.com/muttley1973/infranetpro.git
cd infranetpro
# 2. Install dependencies
npm install
# 3. Start the server
node server.js
# or
npm startOpen your browser at http://localhost:8421
On first start, a default admin account is created automatically. You will be prompted to change the password on first login.
Windows users: double-click
avvia.batto start the server in a console window.
Prefer
git cloneover "Download ZIP". The frontend bundle (dist/app.bundle.js) is a build artifact and is git-ignored โ it is never in the repo.npm installrebuilds it automatically (via thepostinstallhook), so a clone +npm installalways runs the current code. A stale ZIP, by contrast, has no.git(you can't tell which version it is) and won't update โ the classic "I'm on an old version" trap. If you must use a ZIP, download it from themainbranch and re-npm install. To confirm you're current:src/app.jsexists and there is no rootapp.js.
Run InfraNet Pro in a container โ no Node install required. The image builds the frontend bundle internally and keeps all data (projects, skins, user accounts) in a named volume, so it survives container re-creation and upgrades.
# 1. Set a fixed session secret (otherwise logins reset on every re-create)
cp .env.example .env # then edit .env โ SESSION_SECRET=<random>
# openssl rand -base64 48
# 2. Build and start
docker compose up -d --buildOpen http://<host-ip>:8421 (the IP of the machine running Docker). On first start the
generated admin password is printed to the container log โ read it with
docker compose logs infranetpro, then change it on first login.
The default docker-compose.yml uses network_mode: host, so the container behaves
like a native install: it sees your real network. Discovery is complete โ ARP gives
device MACs โ vendor names (OUI), alongside SNMP and LLDP/CDP โ and the UI is reachable
at http://<host-ip>:8421.
โ ๏ธ Security: host mode publishes the (login-protected) UI on the host's interfaces. Keep it on a trusted network; for outside access use a VPN or a reverse proxy with TLS โ never expose it directly to the internet. To bind the server to one address setHOST=<host-ip>(orHOST=127.0.0.1for host-loopback only).Host networking needs a Linux host. On Docker Desktop (macOS/Windows) host mode is limited โ use the isolated variant below.
Isolated (bridge) variant โ for a sandboxed container behind a reverse proxy/VPN, or on Docker Desktop:
docker compose -f docker-compose.bridge.yml up -d --buildHere the container's network is isolated from the host: SNMP discovery still works (it's L3),
but ARP-based MAC/vendor detection does not โ devices without SNMP appear with no
MAC/vendor. It binds host-loopback only by default; set BIND_ADDR=0.0.0.0 to reach it from
the LAN.
docker build -t infranetpro .
docker run -d --name infranetpro \
--network host \
-e SESSION_SECRET="$(openssl rand -base64 48)" \
-v infranet_data:/data \
--cap-add NET_RAW \
infranetpro| Volume path | Holds |
|---|---|
/data/projects |
saved projects + image assets |
/data/skins |
uploaded panel skins |
/data/users.json |
user accounts (bcrypt hashes) |
All configuration is done via environment variables โ no config file needed.
| Variable | Default | Description |
|---|---|---|
PORT |
8421 |
TCP port the server listens on |
HOST |
127.0.0.1 |
Interface to bind. Keep loopback unless behind a proxy/VPN; set 0.0.0.0 in a container |
SESSION_SECRET |
(auto-generated) | Override the session signing secret |
INFRANET_PROJECTS_DIR |
./projects |
Where project JSON + image assets are stored |
INFRANET_SKINS_DIR |
./skins |
Where uploaded panel skins are stored |
INFRANET_USERS_FILE |
./users.json |
Path to the user-accounts file |
INFRANET_TRUST_PROXY |
(off) | Set 1 when behind a TLS reverse proxy: flags the session cookie secure (HTTPS-only) and trusts X-Forwarded-*. Leave unset for plain HTTP / localhost |
Example:
PORT=80 node server.jsTo expose the server on all interfaces (e.g. inside a trusted LAN):
HOST=0.0.0.0 node server.js
โ ๏ธ InfraNet Pro is designed for internal/trusted networks. Do not expose it directly to the internet without a reverse proxy and TLS. When you put it behind a TLS reverse proxy, also setINFRANET_TRUST_PROXY=1so the session cookie is flaggedsecure(sent over HTTPS only).
Click New Project, give it a name (e.g. Core Network). Each project stores its own devices, cables, VLANs and layout independently.
Use Add Device to place a switch, router, server or generic device on the rack or floor map. Set the hostname, IP, icon and number of ports.
Select a device โ Poll tab โ choose driver (snmp-v2c, snmp-v3, โฆ) โ enter community / credentials โ Poll. The app fills in:
- Hostname (sysName)
- All physical interfaces with speed, admin/oper state, duplex
- LAG aggregators and their member ports
- VLAN information
- LLDP/CDP neighbours
Use Discover Subnet to scan a CIDR block (e.g. 192.168.1.0/24) and auto-place all reachable SNMP devices. Then Walk Topology on a seed device to recursively pull LLDP neighbours and auto-draw cables between them.
Click any cable to inspect it (mode: trunk/access, native VLAN, allowed VLANs). Click any port LED to see the interface detail and edit its VLAN assignment. LAG member ports glow yellow when selected.
| Driver ID | Protocol | Notes |
|---|---|---|
snmp-v1 |
SNMPv1 | Legacy; community string |
snmp-v2c |
SNMPv2c | Recommended for most switches |
snmp-v3 |
SNMPv3 | authPriv / authNoPriv / noAuthNoPriv |
auto |
v1 + v2c + v3 | Discovery only: probes all versions in parallel, keeps whichever answers, and reports every version a host responds to (snmpVersions). |
Universal discovery. The Scopri dialog scans with auto (only Community +
Timeout to set). A host that speaks v2c is imported with data; a v3-only host
is detected without credentials via the SNMPv3 engineID/USM handshake and flagged
"v3 da configurare" (a ๐ pill on the device + a counter next to Sync jump you
to each one). You then fill the v3 credentials in Properties โ Integration and
Sync. Devices that answer both show SNMPv2c ยท +v3 (v2c is exposed โ consider
disabling it). The Integration panel is available for any device with an IP (rack
and floor: printers, APs, cameras, NASโฆ), not just rack devices.
| Field | Description |
|---|---|
| Username | Security name (USM) |
| Auth protocol | MD5 or SHA |
| Auth passphrase | โฅ 8 characters |
| Privacy protocol | DES or AES |
| Privacy passphrase | โฅ 8 characters |
| Security level | noAuthNoPriv / authNoPriv / authPriv |
| Context name | SNMPv3 context โ required by some agents (e.g. HP JetDirect โ jetdirect); leave empty for the default context |
Standard, vendor-neutral MIBs (v2c and v3 expose the same OIDs):
- IF-MIB (
ifTable/ifXTable,ifStackTable) โ interfaces (name, type, speed, status) + LAG stacking - IEEE 802.3ad (
dot3adAgg*) โ LACP aggregator / member ports - BRIDGE-MIB / Q-BRIDGE-MIB โ bridge port mapping, VLAN egress/untagged bitmaps
- LLDP-MIB + CISCO-CDP-MIB โ neighbour discovery
- Cisco VTP MIB โ VLAN names without a per-VLAN community
- SNMPv2-MIB (
sysName/sysDescr/sysObjectID/sysServices,sysLocation/sysContact/sysUpTime) โ identity, vendor/model intelligence, live system card - ENTITY-MIB (
entPhysical*) โ hardware inventory (vendor, model, serial, firmware) - Printer-MIB (RFC 3805) +
hrPrinterStatusโ toner/ink %, page count, status (printers) - HOST-RESOURCES-MIB (RFC 2790) โ CPU / RAM / disk (server/pc/nas/homelab, and network gear)
Live read-only cards. System / Printer / Host-resources data is shown as read-only "live" cards in the Integration panel and never overwrites manual fields (manual-first), appearing only when the device exposes them. Printer-MIB is read in an isolated concurrency-1 pass (weak agent stacks like HP JetDirect truncate the supplies columns under a concurrent walk); HOST-RESOURCES is fetched only for compute devices.
A dependency-free sysObjectID engine (engine/sysobject-engine.js, public via const { SysObjectEngine } = require('./engine')) enriches SNMP discovery results without a database. It resolves an OID against local plugins under plugins/ using longest-prefix-wins, runs one isolated instance per webapp, hot-reloads plugin files at runtime, and isolates failures (a plugin throwing in enrich() returns null, never crashes the engine). It can also return OS/agent fingerprints โ context-only matches use vendorPrefix: '0' via engine.fingerprint(ctx). server/classify.js resolves row.objectId through it before the legacy PEN/regex fallback. A storage constructor seam is reserved for a future SQLite catalog; today it stays zero-database. This is the preferred extension path for vendor intelligence โ refine local plugins, don't add external discovery dependencies. See ARCHITECTURE.md.
The bundled seed catalog covers common home-lab / SMB vendors โ network (Cisco, HPE/Aruba, MikroTik, Ubiquiti, Zyxel, Netgear, TP-Link, D-Link), security (Fortinet, Palo Alto), storage/server (Synology, QNAP, VMware), power/video (APC, Eaton, Axis, Hikvision) and OS/agent fingerprints (Windows, Net-SNMP/Linux, Proxmox, TrueNAS, Apple macOS/iOS, Android, Chromecast). It's intentionally practical, not globally complete: sysObjectID has no universal official model database โ the stable part is the IANA PEN/vendor prefix, while model-level mappings are vendor/community-specific.
Add one file under plugins/ exporting exactly vendorPrefix, match(oid, ctx) and enrich(oid, ctx) โ where enrich returns vendor / deviceType / family / confidence plus optional os and infranet hints. Use the vendor PEN prefix (1.3.6.1.4.1.<PEN>), keep deviceType aligned with InfraNet types (switch, router, firewall, server, nas, ap, printer, webcam, nvr, ups, pdu, iot, pc), prefer generic family logic over per-lab hacks, and never query SQLite/HTTP/external files from a plugin. For OS-only/context fingerprints use vendorPrefix = '0'. Run npm test after changes. The full plugin contract and a worked example live in ARCHITECTURE.md and the seed files under plugins/.
A second plugin-based engine (engine/oui-engine.js, public via const { OuiEngine } = require('./engine')) resolves MAC OUI โ vendor + device intelligence, mirroring the sysObjectID engine: plugin-based, hot-reload, zero-database. Lookup uses a compact prefix trie (longest-prefix-wins with priority tie-break) over 24/28/36-bit IEEE assignments plus special non-IEEE blocks (e.g. Docker 0242). A catch-all plugin (plugins/oui/_ieee-database.js) loads data/oui-db.json โ the official IEEE snapshot (~57k entries: MA-L + MA-M + MA-S + IAB) regenerated by npm run update-oui and committed so the engine works right after git clone. 32 vendor-specific seed plugins under plugins/oui/ (virtual NICs, network, endpoint, IoT/CCTV, NAS, printer, security) win over the IEEE fallback (priority 0). server/classify.js._resolveOui() enriches every discovery row with vendor (and often deviceType) from MAC, feeds the scoring engine, and filters virtual NICs (isVirtual()) out of auto-link/topology. Helpers: lookup / isVirtual / isLocallyAdministered / isMulticast / getVendor / format. See ARCHITECTURE.md.
The Fusion Scoring Engine (engine/fusion-scorer.js, pure and tested) is the central decision layer: it fuses every discovery signal โ the sysObjectID engine, OS fingerprint, OUI engine, sysServices OSI bits, TCP ports and hostname/vendor/banner regexes โ into a single classified device with a numeric confidence (10โ99), ranked alternatives, the full scores map and an evidences/reasons trail.
Design invariant โ vendor identity โ device type. Exactly as nmap / Fingerbank / netdisco do, the vendor (from a MAC OUI or a
sysObjectIDPEN) is identity only and is never keywordโmatched for the type nounsgateway|switch|router|firewall(so a "Gateway Inc." PC or an org literally named "SWITCH" isn't mistyped). Type comes from behaviour/structure, and signals are tiered so a measured signal (SNMP, banner/model text, a probed service port, NetBIOS/SMB, Google Cast, the optโin mDNS/SSDP listen for closedโport devices) always outranks a vendorโidentity inference; a device known only by inference has its confidence capped (manualโfirst).
It is the single authoritative classifier โ the Discover UI defers to it (the thin client _guessType only fills gaps), the inโline legacy twin was removed, and behaviour is frozen by the 55โdevice tests/classify-golden.test.js plus a representative freeze in tests/fusion-scorer.test.js. server/classify.js._scoreDiscoveredDevice(row) is the production entry point; the discovery payload exposes a classification object (deviceType / confidence / alternatives / scores / reasons) alongside the legacy deviceClass/confidence. See ARCHITECTURE.md.
InfraNet Pro uses a four-level cascade to detect link aggregation groups:
Level 0 โ ifStackTable
Higher/lower layer walk. If ifA is stacked above ifB,
ifA is the aggregator and ifB is a member.
Level 1 โ dot3adAggMemberPorts
IEEE 802.3ad MIB. Direct map of aggPortAttachedAggID.
Level 2 โ dot3adAggPortActorOperState
LACP bitmask โ distinguishes active/collecting/distributing ports.
LLDP-inferred
If two or more LLDP links exist between the same device pair,
they are automatically grouped into a logical LAG,
even without SNMP LAG MIB support on the device.
Cisco IOS specifics:
Port-channelinterfaces have ifType 53 (propVirtual), not 161 (ieee8023adLag)- The regex
/^(port-?channel|bond\d*|ae\d|po\d+$|lag\d)/icatches all common naming conventions - LAG groups are auto-named from the first aggregator interface with a name (e.g.
Port-channel1)
VLAN data is collected from three sources, in priority order:
- Q-BRIDGE-MIB egress/untagged bitmaps (
dot1qVlanCurrentEgressPorts) โ most accurate, but requires per-VLAN SNMP community context (public@100) on Cisco IOS - Bridge port โ VLAN membership from
state.portsโ used when explicit VLAN bitmaps are available - Cisco VTP MIB (
vtpVlanName, OID1.3.6.1.4.1.9.9.46.1.3.1.1.2) โ fallback that works without any special community, returns all VLANs defined in the VTP domain
Trunk vs access detection is derived from the egress / untagged bitmaps: a port is a trunk if it carries any VLAN not in its untagged set.
| Role | Capabilities |
|---|---|
| admin | Full access: create/edit/delete projects, poll devices, manage users |
| viewer | Read-only: browse diagrams, inspect ports and cables |
Users are stored in users.json with bcrypt-hashed passwords (cost factor 12).
The login endpoint is rate-limited to 10 attempts per 15 minutes per IP.
InfraNet Pro is designed for a trusted LAN, behind login, bound to 127.0.0.1 by default. The codebase has undergone an application-security audit (no critical findings) and the follow-up hardening is enforced by tests.
The 14 hardening measures, and the test that guards each one
- Secrets never leave the machine on the data surfaces โ the AI context, the REST API v1 DTOs and the exports are built from an explicit allowlist (
lib/api-shape.js,server/ai/context.js): SNMP communities, Wi-Fi passphrases/PSK, API keys and tokens are structurally excluded. A build-failing guard test (test/ai-context.test.js) fails the build if a secret-looking field ever reaches the AI context. - The bring-your-own AI key is stored owner-only โ
data/ai-config.jsonis written0o600(and re-tightened at startup) so a co-tenant on the host can't read the key; supply it viaINFRANET_AI_KEYto keep it off disk entirely (server/ai-config.js, guarded bytest/ai-config.test.js). - Uploaded skin SVGs are sanitized before render โ a shared library skin is stripped of
<script>/ event handlers (on*, in every quoting form) / external references, both by a server-side regex pass and by a real DOM parse on the client for the preview and the rack, so a poisoned skin-pack can't run script in another user's Properties panel (lib/panel-skin.js,src/app-panel-skin.js, guarded bytest/panel-skin.test.js). - Login is constant-work (no user enumeration) โ a dummy bcrypt compare runs when the username is unknown so response timing doesn't reveal which usernames exist; login / RBAC / session-invalidation-on-role-change / last-admin / rate-limiter are covered by regression tests (
test/auth-api.test.js,test/auth-store.test.js). - Errors return JSON, never a stack trace โ a global Express error handler maps malformed/oversized bodies and thrown errors to a clean JSON error instead of an HTML page leaking server paths (
server.js). - Durable, owner-only secret files โ the session secret is
0o600(with a startup retrofit);api-tokens.json,ai-config.jsonand skin SVGs are written atomically (temp + fsync + rename +.bak) so a crash mid-write can't truncate them or silently invalidate every API token. - No command injection โ every OS call (
ping,arp, โฆ) usesexecFilewith an argument array (no shell); scan inputs are regex-validated and capped. - Path-traversal-safe project IDs โ every
projectIdis coerced to a positive integer before touching the filesystem (guarded bytest/ai-route-security.test.js). - CSPRNG secrets โ the session secret and the first-run admin password are generated with
crypto.randomBytes/crypto.randomInt, neverMath.random. - Cookies โ session cookies are
httpOnly+sameSite=strict; setINFRANET_TRUST_PROXY=1behind a TLS reverse proxy to also flag themsecure(HTTPS-only). - SNMP secrets never reach a read-only viewer โ
GET /api/projects/:idstrips the community and v3 auth/priv passphrases from the project for any non-admin (viewers can't save, so the redaction is loss-free), so a read-only account can't lift the credentials to the backbone (server/routes/projects.js, guarded bytest/security-hardening.test.js). - The dev auth-bypass is fail-closed โ
INFRANET_DEV_NO_AUTH=1(a preview convenience) is honoured only when the server is bound to loopback andNODE_ENVis notproduction; on a network-reachable bind it is ignored with a loud warning, so it can never silently disable auth in production (auth.js, guarded bytest/security-hardening.test.js). - Baseline HTTP security headers on every response โ
Content-Security-Policy(self-hosted assets โdefault-src 'self'withobject-src 'none',base-uri 'self',frame-ancestors 'none'; inline kept because the UI needs it),X-Content-Type-Options: nosniff,X-Frame-Options: DENY,Referrer-Policy: no-referrer(server.js). - Skin CSS sanitized too โ beyond
<script>/ event handlers / external refs,<style>andstyle=""are stripped of external /data:/javascript:url()(localurl(#id)kept),expression()and@import, andvbscript:is neutralised likejavascript:(lib/panel-skin.js).
๐ Found a vulnerability? Please report it privately to the maintainer instead of opening a public issue.
Log in as admin โ Settings โ Users to:
- Add new users
- Change passwords
- Promote / demote roles
- Delete users
Each project is a plain JSON file in projects/<id>.json: top-level format, schemaVersion, id / name / created_at / updated_at plus a state object holding the network. The main collections are nodes (devices), links (cables โ with cabling metadata, LAG grouping, auto-link confidence and pass-through segments), ports (keyed by portId), racks, lagGroups, vlans and VLAN/IPAM state; the floor-plan image is kept out of the JSON as a sidecar asset. The state is migrated idempotently and carries its schema version so older projects remain readable. Verification history and restorable snapshots live in projects/history/<id>/ with retention and are removed together with the project.
The Export โ JSON action creates a portable infranet-project-export envelope containing the schema version and state, while removing SNMP credentials and credentials embedded in backup pointers. Import accepts this envelope as well as legacy bare state JSON and server project envelopes.
The secret-free device projection reused by the REST API and the exports is defined in lib/api-shape.js (nodeToDevice). Field-by-field detail of each object (node / link / port / rack) lives in ARCHITECTURE.md.
All endpoints require an authenticated session. Write endpoints require the admin role.
| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/api/projects |
any | List all projects (metadata only) |
POST |
/api/projects |
admin | Create a new project |
GET |
/api/projects/:id |
any | Get full project (including state) |
PUT |
/api/projects/:id |
admin | Update project name or state |
DELETE |
/api/projects/:id |
admin | Delete a project |
POST |
/api/projects/:id/copy |
admin | Duplicate a project |
POST |
/api/poll |
admin | Poll a single device via SNMP |
POST |
/api/discover |
admin | Scan a subnet/range and return enriched discovery results |
POST |
/api/topology |
admin | Pull LLDP/CDP neighbours from a single device |
POST |
/api/discover/topology |
admin | Start a topology crawl via SSE from one or more seed IPs |
POST |
/api/auth/login |
โ | Log in (rate-limited) |
POST |
/api/auth/logout |
any | Log out |
GET |
/api/auth/me |
any | Current session user info |
GET |
/api/auth/users |
admin | List all users |
POST |
/api/auth/users |
admin | Create a user |
PUT |
/api/auth/users/:id |
admin | Update user (password / role) |
DELETE |
/api/auth/users/:id |
admin | Delete a user |
GET |
/api/auth/tokens |
admin | List API tokens (prefixes only) |
POST |
/api/auth/tokens |
admin | Mint an API token (shown once) |
DELETE |
/api/auth/tokens/:id |
admin | Revoke an API token |
Full request/response schemas are in the machine-readable OpenAPI 3.0 spec at GET /api/v1/openapi.json. In short: /api/poll takes { driver, host, community, port, timeout }; /api/discover takes a subnet (CIDR or a.b.c.1-254 range) plus driver / community / concurrency / timeout and scan flags (safeMode, deepScan); /api/discover/topology takes one or more seed(s) with maxDepth / maxDevices and streams text/event-stream progress (start, probing, found, queued, dup, skip, warn, done).
A versioned, read-only API for external consumers (Ansible, dashboards, wikis, automation) to read the documented network as a source of truth. Unlike the session-gated endpoints above, /api/v1/* authenticates with a bearer token (no browser session needed) and returns sanitized data only โ never SNMP communities or other secrets.
Mint a token as an admin in Users and access โ API tokens (shown once), then pass it as a bearer header:
| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/api/v1/openapi.json |
โ | OpenAPI 3.0 description (public) |
GET |
/api/v1/projects |
token | List projects |
GET |
/api/v1/projects/:id |
token | Full inventory: VLANs, racks, devices |
GET |
/api/v1/projects/:id/devices |
token | Device list only |
GET |
/api/v1/projects/:id/ansible-inventory |
token | Ansible dynamic inventory (--list format) |
curl -H "Authorization: Bearer inp_โฆ" http://<host>:8421/api/v1/projects/1Each device exposes id, name, type, brand, model, ip, mac, vlan (derived from IP โ subnet), rack, snmp (a boolean โ the community is never exposed) and wireless.
integrations/ansible/ ships a ready-made dynamic inventory (infranet_inventory.py, Python standard library only): every device with an IP becomes a host (ansible_host = its IP), grouped automatically by type_*, vlan_*, rack_*, brand_* and backup_missing. Each host also carries ansible_network_os (derived from the documented vendor + sysDescr โ Cisco IOS/NX-OS/ASA, Arista, Juniper, VyOS, MikroTik, Fortinet; omitted, never guessed, for unknown vendors) and a config-backup pointer (config_backup_ref/_method/_at) โ so a generated backup playbook targets the right module and destination with no hand-editing. InfraNet documents where the backup lives, never the config itself, and never a credential. InfraNet stays the source of truth; Ansible executes. Set INFRANET_URL, INFRANET_TOKEN and INFRANET_PROJECT, then:
ansible-inventory -i infranet_inventory.py --graphSee integrations/ansible/README.md for the full walkthrough.
| Area | Limitation | Workaround |
|---|---|---|
| Cisco IOS Q-BRIDGE | dot1qVlanStaticName and egress bitmaps return empty without per-VLAN community context (public@100) |
VTP MIB fallback is used automatically |
| VLAN bitmap size | Q-BRIDGE bitmaps cover VLANs 1โ4094; extended range VLANs (4095+) not supported | โ |
| SNMPv3 EngineID | Must be auto-discovered; manual EngineID entry not yet supported | Use v2c if v3 discovery fails |
| CDP | Read-only; Cisco proprietary CDP is polled but not written | Use LLDP where possible |
| Concurrent users | No WebSocket push; each browser polls independently | Refresh manually after another admin makes changes |
| Storage | File-based JSON; not suitable for >1000 projects or multi-server deployments | Migrate to a database backend for large scale |
| Physical Path | Segment editing (P1.5) supports linear chains through port-type pass-throughs (wallport, patchpanel, voip); device-type media converters are not yet offered as routing hops |
Media-converter routing + automatic voice-VLAN tagging are archived for a later step |
Full release notes live in CHANGELOG.md. Highlights of what has shipped:
โ Shipped โ 29 milestones
- DCIM / IPAM sync (NetBox), read side โ sites, racks, devices, interfaces, patch-panel front/rear ports, PDU outlets, virtual machines on their host, IPAM prefixes/addresses/reservations and VLAN roles. Every import is a list of decisions, one row per choice, with what is not coming across declared rather than dropped; re-import compares without writing (
lib/dcim-diff.js) and one site is one project. Imported objects keep their DCIM id in a field of their own, so a rename never looks like a delete plus an add - Declared life-cycle status โ planned / staged / in stock / in service / failed / decommissioning / out of service, filled by the import or by hand. It changes how silence is read (a planned device that stays quiet is expected, not a fault) and flags the opposite too: declared out of service but answering. The measure underneath is never touched
- Outlet groups on UPSs and power strips โ two declared axes (switchable / always-on, battery-backed / filtered-only) because RFC 1628 has no groups and every vendor keeps them in a private MIB; outlets arrive from the catalogue and the group is read from the maker's own naming where present. The rack shows them as colour bands; the handover dossier prints a per-PDU recovery sheet
- A port shut on purpose vs one merely idle โ
ifAdminStatusenters as a measure, the two off-states share a monochrome scale in rack, PDF and draw.io, and a cable drawn over a shut port gets a "Port shut" badge. A status you declared keeps your colour, and the reading expires when the switch stops confirming it - Prefix-first IPAM โ the prefix is a first-class object (
ipam.prefixes[], v4 and v6, with or without a VLAN), an editable Networks field per VLAN, and overlap/duplicate auditing across all declared prefixes rather than one per VLAN - Dashboard (summary view) โ a read-only view (like Topology; it never touches the document) in three columns โ Document / Conformance / Expansion โ each cell a number and a plain-word verdict declaring the provenance of every figure (declared / from scan / derived / not declared, a missing datum shown dashed, never a zero); an at-a-glance health dot + verdict per column, a severity-coloured entry-point accent on the most urgent tile, and a since-last-read delta (โN / +N, baseline in
localStorage, never in the document). Every row drills down in place. Composes existing engines only โ no new measurement (lib/overview.js, pure + tests) - AI assistant โ advisory, bring-your-own-key, OpenAI-compatible (local Ollama by default); server-side key, allowlist context + a build-failing anti-leak guard test; scope/capability toggles; never auto-applies
- REST API v1 + Ansible dynamic inventory โ read-only, bearer-token, sanitized
/api/v1/*; token UI; stdlib-onlyinfranet_inventory.pywith rich host-vars (VLAN/subnet/gateway, serial/firmware, rack, mgmt,ansible_network_os, config-backup pointer) and abackup_missinggroup - DHCP lease import โ cross-VLAN authoritative MAC โ IP for the documentation check; multi-server persisted sources; treated as an identity map, never a false absent
- IPAM occupancy ยท management-VLAN role ยท VM import โ real per-VLAN address usage (documented / DHCP-only / free); anti-guest management VLAN; absorb a discovered floor tile as a host VM
- Reality Check / Drift Report + Adopt โ doc-vs-network diff in 7 categories (state, IP change, hardware identity โ a swapped serial/model vs ENTITY-MIB, absent, undocumented, ghost cable, unverifiable) with per-row update/ignore/investigate and a multi-signal ping/ARP/TCP presence sweep; one-click Adopt of undocumented devices
- Handoff Dossier + Audit Trail โ one-click handover PDF; append-only project changelog with CSV export
- Visible locks for documented values โ one-click freeze on IP / hostname / port-VLAN (surfaces the existing manual-first pins)
- Wireless โ Packet-Tracer sine-wave links, up to 8 radios/device (SSID/band/channel/security/VLAN), SSID-VLAN trunk derivation
- L3-lite gateway โ network โ routing-device resolution (one row per prefix, v4 and v6), VLAN โ SVI binding, L3 badge, Report โ L3 map with CSV
- VLAN โ IPAM (subnet/gateway/DNS), floor legend/filter, per-device VLAN accordions, auto-derived trunks
- Cabling โ segment editor (TIA-568 hierarchy), physical-path trace, progressive patch-panel numbering, cable metadata, cable-label PDF/CSV export
- Free ports report โ "where do I plug in?" rack highlight + CSV / PDF page
- draw.io (diagrams.net) rack export (
lib/drawio-export.js): native, editable mxGraph rack, one page per rack, devices/ports as cells in draw.io's numbered rack container (snap-to-U), names outside the rack; the live SNMP status stripe is not exported. Cables = one native edge each, one draw.io layer per VLAN (coloured by VLAN, per-cable anti-overlap routing) with a click-to-highlight cable table per layer; A4 portrait auto-switching to A3 when content doesn't fit - Vector PDF / SVG export + audit-ready asset register โ full rack SVG (MGMT/SFP side blocks); bilingual (it/en) report; secret-free per-device inventory page with a "last revised" timestamp
- Classification engines โ sysObjectID + OUI (IEEE ~57k) + Fusion Scorer (vendor identity โ device type), plugin-based, hot-reload, zero-database; behaviour frozen by the 55-device golden
- SNMP parameter import โ live read-only system / Printer-MIB / HOST-RESOURCES cards; manual-first; validated on real hardware
- Discovery โ deep scan (TCP/NetBIOS/SMB) + confidence scoring, reachability states, off-segment SNMP-ARP (
arpnip), switch-port mapping (FDBmacsuck), DHCP-as-source, mDNS/SSDP/ONVIF listen - Device catalog โ NVR, SD-WAN edge, VPN concentrator, door controller, panelboard; dedicated MGMT + SFP (ร2) blocks; stacking (StackWise/VSF/Virtual Chassis/IRF); HA pair/cluster modeling; management-protocol launcher
- Multi-vendor LAG detection โ four-level cascade (ifStack / 802.3ad / ActorOperState / LLDP-inferred), logical id, LACP mode coherence
- Topology "to confirm" states โ deduced infra/uplink cables (guessed remote port, materialised gateway, FDB uplink-resolution of a documented device) are born Inferred ยท to verify (amber Confirm/Delete, dashed on the map), never mislabelled
LLDPโ norLAGwhen the uplink lands on a local LAG member port toward a blind switch whose port we can't know; a hidden multi-port intermediary behind a 2โ4-MAC access port is surfaced as a shared L2 segment with a role suggested from the endpoints (other subnet โ gateway ยท virtual OUI โ hypervisor ยท randomised MAC โ AP ยท else switch) and materialised from the Shared L2 panel - Engineering โ zero-dep regression suite + CI, server modularization, frontend ESM/esbuild migration, correlation primitives (
lib/correlate.js), ENTITY-MIB inventory,node.specrefactor - IPv6 (Scope A), treated like IPv4: address field in device Properties with the same padlock (
ip6Manual); the SNMP poll reads the device's own address (ipAddressTable) so the Sync auto-populates it and Verify flags a locked divergence. Plus Neighbour Discovery (ipNetToPhysicalTable, routable global/ULA only) โ which now also feeds cross-subnet presence: a device in a router's ND cache is green even if IPv6-only or ARP-aged (twin of the router-ARP path) โ EUI-64 โ vendor hint, privacy-IID โ BYOD. IPv6 is also declarable and audited like IPv4: a VLAN carries both prefixes, each with its own gateway, and the L3 map checks the v6 gateway, its containment, and which device answers it โ addresses compare by identity, not by text, so2001:DB8:0:20:0:0:0:1and2001:db8:0:20::1are one address. There is no capacity bar for a /64 (2^64 is not a percentage): occupancy counts the addresses actually seen. Active IPv6 sweep (ping ff02::1) stays parked. - OS-family hint from ping TTL (nmap-style, zero-cost, low-weight, embedded-appliance-suppressed; internal โ not shown in the scan table)
Planned:
- Per-field provenance in the schema โ every field carrying an explicit origin (observed / declared / derived), so the document can say where each value came from instead of the app inferring it per screen
- DCIM write-back โ the half that writes, gated: maker-checker, dry-run, re-read after the write, and a closed list of writable fields. A deduced cable is never promoted to the DCIM unless its proof state allows it
-
ENTITY-SENSOR-MIB(temperatures/fans/PSU) + real PoE wattage per switch - Explicit topology states in the UI (
exact / probable / ambiguous / shared-segment / uplink-to-unknown) - SQLite-backed storage for discovery/IP history, FDB cache and audit log
- Internal discovery/classification hardening (richer local plugins, more real-device tests)
- Topology multi-source fusion (LLDP + FDB agreement boost; stricter unmanaged-switch detection)
- Discovered-device de-duplication, shadow/rogue-device signal
- Keep discovery propose-and-reconcile, never overwrite (the "discovered โ intent" model)
Out of scope (parked): WebSocket multi-user live push, SNMP trap receiver, temporal confidence on links, per-VLAN community auto-config wizard, BGP4 / POWER-ETHERNET / Print MIBs, conduit/cable-tray modeling, fiber loss-budget math, HA Tappe B+C.
The project ships with a zero-dependency regression suite built on Node's
built-in test runner (node --test). No framework, no node_modules for tests.
npm test # run the regression suite (node --test)
npm run check # syntax-check all project JS sources
npm run typecheck # JSDoc + checkJs type-check of the pure libs (tsc --noEmit)
npm run build # bundle the migrated ESM frontend modules (esbuild โ dist/)
RUN_E2E=1 npm run e2e # headless end-to-end in a real Chrome (login bypass) โ off by defaultA real headless E2E (test/e2e/, Playwright on the system Chrome via
INFRANET_DEV_NO_AUTH) drives the critical flows in a real browser (cable
routing, VLAN propagation, wireless, rack drag/pan); it spawns an isolated
server on a temp store and is skipped unless RUN_E2E=1.
Coverage focuses on the pure, bug-prone logic that has historically broken: SNMP parsing & extraction (test/snmp.test.js, test/extractData.test.js), discovery & classification (test/discovery.test.js, 14 real-device cases), correlation primitives (test/correlate.test.js), the sysObjectID / OUI / Fusion engines (tests/*.test.js), front-panel state, cable validation (incl. Cat8 30 m reach), IPAM & LAG audits, and an app-wide smoke E2E (test/smoke-app.test.js) that loads every netmapper.html script plus the esbuild bundle into a vm + DOM stub and asserts renderAll/renderProps never throw on any device type.
Current local quality baseline:
npm run checkvalidates all project JS sources (839 files)npm testruns the full regression suite (currently 2,615 tests, 0 failing) plus a realโbrowser E2E suite (RUN_E2E=1, 109 flows)- final visual verification is still important for rack/front-panel refinements
Pure functions are exposed for tests via an additive
_internalsexport ondrivers/snmp.jsandserver.jsโ runtime behaviour is unaffected.server.jsstarts its listener only underrequire.main === module, so it can be imported by tests without binding a port.
CI (GitHub Actions) runs npm run check and
npm test on every push and pull request to main, across Node 18 and 20.
Found a bug, or want to request a change? Here's where it goes:
- ๐ Bugs โ open an issue using the Bug report template (steps, version, OS, logs).
- ๐ก Feature requests / changes โ open an issue using the Feature request template.
- ๐ฌ Questions & ideas โ start a Discussion โ best for open-ended ideas before they become a concrete issue.
- ๐ผ Commercial license / private enquiries โ see LICENSING.md.
Contributions are welcome! Please follow these guidelines:
- Fork the repository and create a feature branch (
git checkout -b feature/my-feature) - Respect the minimal-build philosophy โ the only frontend build step is the lightweight esbuild bundle of the
src/ESM glue (npm run buildโdist/app.bundle.js, run automatically bynpm install/npm start). The purelib/*.js,app.js,export.jsand the modularstyles/CSS stay plain static assets loaded directly; no transpile step - New device drivers go in
drivers/<protocol>.jsand must exportpoll(cfg),probe(cfg)and optionallypollNeighbors(cfg) - Test against real hardware or a GNS3/EVE-NG lab before submitting
- Open a Pull Request with a clear description of what changed and why
// drivers/myprotocol.js
'use strict';
/**
* @param {object} cfg { host, port, timeout, ...protocolOptions }
* @returns {Promise<{ hostname, interfaces, lags, vlans }>}
*/
async function poll(cfg) {
// ... your implementation
return { hostname, interfaces, lags, vlans };
}
/**
* Quick reachability probe โ used by /api/discover
* @returns {Promise<{ reachable: boolean, hostname?: string, descr?: string }>}
*/
async function probe(cfg) {
// ...
return { reachable: true, hostname: 'myswitch' };
}
module.exports = { poll, probe };Register it in server.js:
const DRIVERS = {
'snmp-v2c': loadDriver('snmp'),
'myprotocol': loadDriver('myprotocol'), // โ add here
};InfraNet Pro is free and open source (AGPLv3). If it saves you time, you can support its development with a coffee on Ko-fi โ it funds the work that keeps new features coming. โ
Need it adapted to your company's specific devices/APIs, or embedded in a closed-source product? Custom integration and commercial licensing are available โ see LICENSING.md.
GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later) โ Copyright ยฉ 2026 muttley1973. Full text in LICENSE.
InfraNet Pro is free software: you can use, study, share and modify it under the terms of the AGPLv3. In short โ if you run a modified version to provide a service over a network, you must make your modified source available to its users.
A commercial license is available for organizations that prefer not to be bound by the AGPL copyleft obligations (e.g. embedding InfraNet Pro in a closed-source product). Custom integration to specific device APIs is also offered. See LICENSING.md for details and contact.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.

InfraNet Pro
Built with โค๏ธ for network engineers who prefer developing with a coding agent.
If it earns a place in your workflow, a โญ helps other engineers find it.
Quick start ยท Changelog ยท Architecture ยท Report a bug ยท Discussions ยท Commercial licence







