Releases: dsmorgan/patchbay
Release list
0.17.1
Fixed
- A switch-to-switch cable that both LibreNMS (LLDP) and the UniFi
controller report drew twice when the controller stored the operator's
port label where SNMP reported the ifName. The normalizer now also
matches unifi links to lldp links by device pair, keeping genuine
parallel cables (#56, Sam). The pair pass runs after the weaker-source
passes, so a retired unifi row still claims its port against MAC-table
inference and ghost switches.
0.17.0
Added
- Topology: undo and group moves (#55, from Sam / @slmingol). Ctrl+Z
or Cmd+Z steps back up to ten node moves. Shift+drag on the background
draws a lasso; dragging any selected node moves the group together,
spacing kept, and one undo restores the whole group. Escape or a
shift-click on the background clears the selection. Landed with two
fixes: undo redraws the node even after the simulation has cooled, and
it restores the node's saved state instead of turning a settled node's
hold into a pin.
0.16.0
Added
-
Load view: UniFi switch ports carry throughput (#53, from Sam /
@slmingol). The controller reports a rolling byte rate per switch port,
the same figure its own UI shows, and the collector now stores it as the
port's in/out rate and as arate_historysample. Switch-to-switch and
switch-to-AP legs on UniFi-managed switches color in the load view and
get a 24-hour peak, where before only SNMP-polled ports did. An AP's
uplink carries the same figures and is stored too. A down device keeps
its last good reading, as it does for port status and speed. -
Load view: the busier end of a cable wins. Where both ends of a
link report a rate, the edge used to show whichever end sorted first by
name. The two ends read the same wire through different windows, a
UniFi rolling rate against a five-minute SNMP average, so the edge now
shows the higher reading, for the current figure and the 24-hour peak
separately. A burst one poller caught no longer hides behind the
other's average.
Changed
- Deployment guide: verify snapshot delivery before you need it (#25).
A new step walks through mounting the off-host share into both patchbay
services, triggering one snapshot, checking the share from another
machine for the renamed copy and no leftover.partfile, waiting for
the sync, and opening the off-host copy with the stack down.
Fixed
-
Rate samples age out whatever the source. The seven-day
rate_historyprune lived in the LibreNMS collector, so a site whose
rates came from another source would have kept every sample forever.
The normalizer's housekeeping pass prunes now, every cycle, beside the
raw-payload expiry. -
The test suite passes under bare
pytest(#54, from Sam / @slmingol).
Two test modules import helpers from a sibling through thetests
package, which resolves only with the repository root on the path.
python -m pytestputs the working directory there and plainpytest
does not, so the same tree passed on one machine and failed eight tests
on another. The pytest config adds the root now.
0.15.0
Changed
- Agent instructions live in
AGENTS.md, the convention shared across
AI coding tools, instead ofCLAUDE.md.CLAUDE.mdand
CLAUDE.local.mdare gitignored for personal notes (Sam's suggestion
in #51). Claude Code 2.1.277 or later readsAGENTS.mdnatively.
Fixed
-
Topology: a filter toggle no longer re-flows the map (#52, from
Sam / @slmingol). Every toggle restarts the simulation, and on a map
without saved positions that moved every node the operator hadn't
dragged — by a hundred pixels or more forcore only. Now the first
settle is the arrangement: the nodes that took part in it are held where
they landed, and a node a toggle reveals later flows into the gaps
between them. Drag still pins and saves, shift-click frees, reload
re-settles. A map whose nodes all carry saved positions is unchanged. -
pfSense: a 404 note says which kind of absence it is (#51, from
Sam / @slmingol). The OpenVPN and IPsec status endpoints also answer 404
when that feature is not configured on the firewall, pfrest installed
and current, and the old note sent operators after the package. Those
two calls are optional now and their note names the feature; a core
endpoint's 404 still means pfrest is missing or too old, and says so.
0.14.0
Added
-
Routed view: every router draws (#50, ADR-0002 Decision 4). The
routing tier holds them all: routers whose lanes don't overlap share a
column, overlapping ones — an HA pair, a core router behind the edge
firewall — take successive columns toward the internet, and whoever
holds a default route stands last beside its cloud (a multi-egress site
gets a cloud per default route). Router boxes are open frames now, so a
lane bound for a further router visibly passes the nearer one; a lane
lists every router that claims it (all gateways in the tooltip); a
tunnel leaves the router that terminates it. A plain router wears the
router glyph and color, a firewall the firewall's, as on the Overview.
Scenario tests cover the HA pair, the inner router, and two egress
routers — no live site yet exercises them, so they are the contract. -
Routed view: Load mode (#50, ADR-0002 Decision 3). The router's
per-network legs are the only edges on this view with counters — its
VLAN interfaces, when the firewall is a polled device — so Load
heat-tints a segment where each lane meets the router, the number
beside it, and the default route's drop from the WAN interface, on the
topology's palette with the samenow/24h peakselect
(load=peak). Busier direction over capacity; a declared service
capacity beats the port speed. Lanes and attachments go quiet; grey
means no measurement. The demo seeds firewall counters so the public
snapshot shows it. -
Routed view: Protocol mode (#50, ADR-0002 Decision 3). The third
radio paints address families: IPv4 lanes teal, IPv6 lanes amber, a
dual-stack lane teal with an amber dash riding it, and every attachment
dot — a host's leg, the router's gateway — colored by the families it
actually holds there. The select narrows to one family and dims
whatever lacks it (proto=4/proto=6; the default is both). A leg
now lists every address a device holds on a network, and a rail knows
its IPv4 and IPv6 gateways separately (both in the tooltip). -
Routed view in the snapshot (#50). The break-glass file now carries
the L3 picture under the topology map, from the same builder and
template as /routed — never a second implementation. Its state lives in
memory there (the URL belongs to the topology map on the same page),
thevertical railschip redraws in place instead of reloading, and
double-click jumps to the device's section or the VLAN's row. -
Topology: derived zones (#47). A translucent hull now sits behind
each hypervisor and the guests that are nodes on the map (a virtualized
firewall, a router VM), and a fainter one around the whole cluster when
the hypervisors share a vSphere — named after the vSphere server, like
the routed view's box. Zones are computed from the parent relationship
patchbay already holds, never drawn by hand; a grouping force pulls
members together, the VM-on-host edge hides inside a drawn zone, and
zones draw under everything and take no clicks. In the tiers layout a
zone keeps only the members in its hypervisor's band, so a hull never
wraps the fabric between an Edge-band guest and its host. Thezones
chip (zones=0) turns them off. The demo's hypervisors gained specs
so the public snapshot shows the #44 line. -
Topology: node detail panel (#45). Clicking a node no longer
leaves the map: a panel floats over the map's right edge with the
node's role, status and how long ago it was seen, address, hardware,
OS, specs, host, VLANs, and every link with the port at each end, the
speed, utilization, and who reported it. A peer's name in the list
selects that node; Escape or × closes; the selection rides the URL as
sel=. The device page is the secondary action — double-click, or
⌘/Ctrl-click for a new tab — the routed view's contract. The panel
reads the graph JSON, so the snapshot has it too (its link jumps to
the device's section). Read-only by design: edits belong on /ops. -
Topology: node card refresh (#44). Every card node now shares one
anatomy: a rounded-square icon chip anchors the left edge, the status
LED pins the top-right corner, and the name and subtitle read from a
fixed inset — so a mixed row of switches, hypervisors, APs, and hosts
scans as one kind of thing. Hypervisors gain a third line of hardware
mini-specs (24c · 192 GB), recorded by the vSphere collector from
vCenter's hardware summary into two newdevicescolumns (cpus,
mem_bytes, migrated on start) and printed on the device page and
Overview card too; the line only appears when the data does. Role color
stays the stroke and the glyph, status stays the LED — no glow channel.
Fixed
-
The routed view remembers your view options. A bare
/routed— the
rail's link — restores the last-used mode, family and load choice,
visibility toggles, and axis from the browser's storage by rewriting the
URL on arrival, the way/topologyhas since #16. The two maps now
follow one rule: an explicit URL always wins outright,focusis never
remembered, and the snapshot is exempt. -
Device merge dropped the new specs columns. A hypervisor that
LibreNMS also polls folds its vSphere row into the SNMP-owned primary,
and the merge's identity field list must name every column or the
specs vanish on every poll (the wayip6once did). Named, with a
regression test. -
Topology: speed and VLAN chips on edges (#43). The bare mid-edge
speed text is now a pill on the edge, and under it a second pill names
the link's VLANs when there are three or fewer — an access link says
VLAN 20, a trunk carrying twelve stays quiet (the tooltip lists
them). Chips carry facts, not provenance: card fill and a line border,
so edge color stays the reporter's alone; the speed text still warns
amber at ≤100M and red at ≤10M, and the load view appends the
utilization. Theedge chipschip (chips=0) hides them. -
Topology: port names on hover (#48). Edges no longer print their
interface names permanently — on a dense map neighboring labels
overlapped into noise. Hovering an edge (its hit area is now a wide
invisible twin of the line, which also carries the tooltip) reveals the
names at both ends; hovering a device reveals the names on every one of
its links — the "what is plugged into this switch" question. Revealed
names paint above every node. Theport nameschip (ports=1) shows
them all, for the old always-on picture. -
Device totals in the nav rail (#46). Every page's rail carries how
many devices patchbay knows and how they split: up, down, and stale — a
device no source has reported for two hours counts as stale whatever
its last status said, the same window that ages out inferred links.
Inferred unmanaged switches are a guess, not a device, and don't count.
Collapsed, the numbers stack under their dots; the block links to the
Overview. The snapshot header carries the same totals, frozen at
generation beside the data ages.
0.13.0
Added
- Routed view: networks-as-lanes layout (default; the
vertical rails
chip turns the same drawing on its side). VLANs are horizontal lanes
reading edge → internet, left to right: lane labels with subnets in a
left gutter, loose single-homed chips at the edge, one logical
wireless container holding every AP's clients (per-AP attribution in
tooltips), hypervisor slabs spanning their lanes with tenants inside,
the router spanning everything it routes, and the internet cloud plus
tunnels at the far right. Height is fixed by network count, width grows
with devices — landscape-native, made to read at a distance. - Routed view: one virtualization box. All hypervisors fold into a
single box — the wireless container's sibling — named after the vSphere
server when the guest-aware collector owns a hypervisor row. The routed
view is logical, so which physical host a VM runs on becomes tooltip
detail, and a router that runs as a guest draws inside the box. - Routed view: one renderer, two axes. The vertical-rails view is the
lanes drawing turned upright — same containers, boxes, chips, and
rules, edge at the bottom and the internet at the top — instead of the
earlier tiered layout. Lanes sit tighter, multi-homed
hosts share a column when their spans don't overlap, and VPN tunnels
sit beside the internet cloud so the transport leaves the router
straight from its edge. The viewBox hugs the drawing, so fit means
the whole map. - Routed view: click focuses, a page is the secondary action. A plain
click on a network, host, box, or router now focuses it in place —
highlight what's attached, dim the rest,focus=in the URL — and a
second click or a click on the background clears it. Double-click opens
the thing's page; ⌘/Ctrl-click or middle-click opens it in a new tab.
Navigating away on a plain click felt like falling through the map. - Routed view: the shell's last-polled indicator and auto-refresh, which
every other page already had. - Routed view: Evidence mode, and with it the segmented view control
from ADR-0002 Decision 3. Each lane takes the color of its strongest
reporter — a firewall interface (it routes it), a switch carrying the
VLAN, the controller, a hypervisor port group, IPAM alone (dashed:
documented, nothing carries it), a route learned through a tunnel —
the tag carries a badge per reporter, and everything attached goes
quiet.view=evidencein the URL. Load, Protocol, multi-router sites,
and snapshot embedding are tracked in #50; ADR-0002 carries an
amendment recording the lanes-era decisions. The "still settling"
banner is gone.
Fixed
- Powered-off VMs and down devices no longer count on the routed view.
A VM that is off still has legs on paper (its port group VLAN, a
documented address, a cached guest IP) and was counted in the VM chips;
now only active devices count, unknown status still counts, and the
virtualization box lists the sleeping guests in its tooltip. The
builder gained scenario tests for typical sites — ARP-only flat
networks, IPv6-only sightings, ARP plus learned-VLAN fusion without
IPAM, mixed-case MACs, addresses outside every network, wireless
clients across APs, an empty database, and a router with no addresses. - Merging a re-duplicated device no longer drops its addresses. A
device LibreNMS re-creates under its FQDN every poll merged into the
fresher row, and colliding port rows on the older duplicate were
discarded wholesale — so the firewall's interface addresses (which only
the firewall collector writes) vanished whenever that collector was
skipped or failed, and with them the routed view's gateway exclusion,
which let dnsmasq's "gateway" ARP rows draw as a phantom host spanning
every network. Identity facts (ip, ip6, mac, description, ifindex) now
fill the primary's gaps whatever their age; liveness (status, speed,
rates) still follows the fresher row. Separately, an ARP or IPAM row
carrying a MAC some device's interface owns is that device, never a
host, whatever name the address wears.
Changed
- IPAM is identity, never liveness. phpIPAM no longer writes endpoint
rows (it re-stampedlast_seenevery poll, so documented-but-gone
hosts never aged out and ghost hosts haunted the routed view); it now
only lends hostnames to endpoints real observers saw, and legacy
doc-rows are retired on the next poll. On the routed view, IPAM
addresses matching an observed host add "ipam" legs — including
networks nothing can observe, like an isolated storage VLAN — but IPAM
alone never draws a host. UniFi clears stale AP attribution for
clients gone from the controller's station list, so ex-wireless hosts
stop counting as clients. A tunnel route whose destination contains
local networks (WireGuard allowed-ips for the home supernet) is the
tunnel's source side: named on hover, never drawn as a network. - Switch MAC tables discover hosts, but a bare MAC is not a host. A
MAC learned on a pure access port counts as a sighting on that port's
VLAN (trunks and mirror destinations excluded), named by its hostname
or, failing that, its address from any endpoint or IPAM row — a
WAN-side neighbor with an address outside every documented subnet
shows as that address. A MAC with neither is usually a bond member or
kernel port of a host already drawn, so it is counted in the lane's
tooltip and never listed as a host. - The switch MAC table keeps the VLAN a MAC was learned in. LibreNMS
reports it per entry (platforms that don't leave it 0), andfdbrows
are now keyed by it, so a trunked host — a storage box with a VLAN
interface per network on one 10G port — is placed on every network it
talks in, from real switch evidence, no alias needed. Legs name the
address that belongs on that network, and a second address on the
same network (bond plus trunk sub-interface on mgmt) rides the leg in
the tooltip. Databases from before the change migrate in place. - Guests are placed by what ARP saw their NICs do. A VM's collector
reports NIC MACs but no guest addresses, and an untagged port group
never reaches the VLAN tags, so a two-NIC guest drew as single-homed.
A device's NIC seen by ARP (or documented in IPAM) with an address is
now a leg on that address's network;mgmt_ipis the last resort.
Routers still claim networks from their own interface config only. - phpIPAM lends names by exact address first, MAC second. One NIC can
carry several documented addresses; matching by MAC alone handed the
wrong row's name to whichever came first. Names an earlier MAC-only
lend got wrong are corrected on the next poll.
0.12.0
Added
-
Firewall VPN tunnels are first-class, type-labeled objects
(#42). WireGuard,
OpenVPN, and IPsec tunnels from OPNsense (peer/session/SA status
endpoints) and pfSense (OpenVPN/IPsec status; the WireGuard VPN gateway,
previously dropped, becomes tunnel health) land in a newtunnels
table — prune-per-type with empty-response guards, and no key material
stored, not even public keys. On the topology they draw as dashed
purple egress nodes hung off their firewall, labeled with type and
peer; on the routed view they sit beside the internet cloud, and a
subnet reachable through a tunnel rails off the tunnel node — drawn
even when nothing local claims it, because reachability through the
tunnel is its participation. WireGuard liveness derives from handshake
age (up / idle / down); tunnel interfaces stay out of the port model,
exactly as before. The demo network gains a site-b WireGuard peer so
both maps show the feature out of the box. The OPNsense API user needs
the VPN page privileges — optional, everything else degrades cleanly
without them. The terminating firewall's device page lists its tunnels
in their own section, separate from the port table. -
Routed view redesign: rails spread to a computed gap (wider for
more networks, capped for few); hypervisors and APs draw as spanning
boxes in tiers of their own, with guest VMs grouped inside their
hypervisor and wireless clients inside their AP — every host counts in
exactly one place. Dual-homed hosts known only from ARP now fuse by
canonical hostname into real host boxes (rail gateway addresses are
excluded, so dnsmasq's per-VLAN "gateway" rows can't invent a phantom
host). Boxes carry the topology's role icons and colors, and hovering
a network now dims isolated (gray) rails too.
Fixed
- OPNsense 403 poll notes now name the exact privilege to grant (and note
that a Status sub-privilege covers the VPN reads) instead of "the
matching page privilege". - Brocade/Ruckus FastIron ports no longer show the long-form port name
("GigabitEthernet1/1/10") as their description — that is the vendor
echoing the ifName when no comment is set, not documentation. - Snapshot polish: per-source data ages are humanized ("16 h", not
"967m"), VM cards drop the "· ?" when no hardware string exists, and
VPN tunnels now appear in the snapshot both on the map and as a table
under the terminating firewall. - Hypervisor device pages split vmk* kernel interfaces into their own
section — they carry the management addresses but no cable ends on
one, so they no longer pad the physical port list. - The configs page shows the firewall's management IP on its API-sourced
row, the patch-panel section header is just the panel's declared name,
and the topology toolbar controls share one height.
0.11.2
A data-quality release: the liveness and pruning gaps from the post-0.10.0 audit (#41), and the mid-poll duplicate-cable flicker (#38).
Fixed
- The transient duplicate AP↔switch cable right after a poll starts is gone (#38). Normalize now runs inside the same transaction as each collector, so the web UI never reads fresh-but-unnormalized state. A normalize failure rolls back to a savepoint and the source's data still lands; per-source atomicity is unchanged.
- The audit's liveness and pruning gaps (#41) — the family where nothing lies, things just never leave:
- vSphere no longer writes a VM's cached
powerState(with a fresh timestamp) while the owning host is not responding — a virtualized firewall's own status report wins again. Placement still lands. - UniFi no longer writes port oper/admin/speed from a down or heartbeat-missed device's cached
port_table; the last good values stand, the same gate temperature already had. vnic_vlansis refreshed by replace: a port group moved to untagged, or a deleted VM's MAC, no longer re-emits phantom 802.1Q membership forever.- Devices removed from LibreNMS or the UniFi controller are retired instead of haunting every page with frozen status (each collector's own rows only, guarded on a non-empty listing).
- Endpoint observations (ARP, leases, controller clients, the IPAM address book) age out after a week unrefreshed, so /drift stops treating months-old rows as live sightings.
- VLANs that vanish from every switch config and SNMP table get the same claim-aware prune phpIPAM's rows got in 0.11.1.
- The oxidized per-device
port_vlansrewrite is scoped to its own rows, like theport_rolesdelete beside it always was.
- vSphere no longer writes a VM's cached
Ten new regression tests cover the round (suite at 230).
Upgrade: docker compose pull patchbay patchbay-poller && docker compose up -d patchbay patchbay-poller. No schema or configuration changes.
0.11.1
Quick fix round on top of 0.11.0:
- phpIPAM prunes VLANs deleted from IPAM — the collector's own rows only, guarded on a real listing, and never a VLAN a device still claims (that one just loses its IPAM documentation and reads "no subnet documented in IPAM"). Previously a deleted VLAN sat on /vlans forever.
- The routed view carries a visible still-settling note linking to the issue tracker.
Full details in the changelog.
0.11.0
The routed view finishes its real-site shakedown, and a security audit hardens every store.
Highlights
- The routed view discovers the internet uplink — the default route's exit interface resolves to its VLAN's rail: drawn green, named on the drop line ("via VLAN 299"), hover-linked to the cloud. Appliance-style WAN ports keep the plain cloud-to-router drawing; no configuration either way.
- Rails must participate — IPAM-only supernets, aggregates, and unclaimed VLANs stay on /vlans; the map shows the networks that exist. ADR-0002 is accepted and #17 closed.
- UniFi device temperature (#40, thanks @slmingol) — the controller's reading lands as a device fact on the detail page, gated on a real sensor and an up device.
- Security hardening — raw payloads strip credential fields (LibreNMS device rows carry SNMPv3 secrets); firewall-config redaction matches secret tags as substrings and catches OTP seeds; the routed graph JSON is script-escaped; pfSense/phpIPAM keep last good data through failed or empty polls.
- Targeted deletion — snapshots and stored firewall config revisions can be deleted from the UI.
- The break-glass snapshot embeds the latest firewall config revision (double-scrubbed),
PATCHBAY_SNAPSHOT_ATis editable on /ops, and /configs shows canonical device names.
Upgrading note: stored firewall revisions captured before 0.11.0 predate the wider redaction — use "delete all stored revisions" on the device's config page and let the next poll re-capture.
Full details in the changelog.