Metadata-Version: 2.4
Name: netbox-plugin-autocabling
Version: 0.3.0
Summary: NetBox plugin for automatic cabling over structured cabling — fiber and copper (end-to-end free-channel selection).
Author-email: papaylle <bpapaylle@users.noreply.github.com>
License-Expression: Apache-2.0
Project-URL: Homepage, https://github.com/bpapaylle/netbox-plugin-autocabling
Project-URL: Source, https://github.com/bpapaylle/netbox-plugin-autocabling
Project-URL: Issues, https://github.com/bpapaylle/netbox-plugin-autocabling/issues
Project-URL: Documentation, https://github.com/bpapaylle/netbox-plugin-autocabling#readme
Project-URL: Changelog, https://github.com/bpapaylle/netbox-plugin-autocabling/releases
Keywords: netbox,netbox-plugin,cabling,fiber,patching
Classifier: Development Status :: 4 - Beta
Classifier: Framework :: Django
Classifier: Intended Audience :: System Administrators
Classifier: Topic :: System :: Networking
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.14
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: openpyxl>=3.1
Provides-Extra: dev
Requires-Dist: ruff; extra == "dev"
Requires-Dist: build; extra == "dev"
Requires-Dist: pytest; extra == "dev"
Dynamic: license-file

# netbox-plugin-autocabling

[![NetBox](https://img.shields.io/badge/NetBox-4.5%20–%204.6-blue)](https://netbox.dev/)
[![Python](https://img.shields.io/badge/python-≥%203.12-blue)](https://www.python.org/)
[![License](https://img.shields.io/badge/license-Apache--2.0-green)](https://github.com/bpapaylle/netbox-plugin-autocabling/blob/master/LICENSE)
[![PyPI](https://img.shields.io/pypi/v/netbox-plugin-autocabling)](https://pypi.org/project/netbox-plugin-autocabling/)

### Leverage your NetBox implementation to do complex cabling.

## The problem

NetBox already knows your panels, your modules, your media and which ports are taken. It
just never shows them together. So linking two ports in two distant racks means walking it
by hand: one tab per panel, then one cable form per cord, each asking for both terminations
by name. Three cables, three round trips through the tabs. None of it is guesswork, all of
it is precision work. And a panel that turns out full at the far end sends you back to the
start.

Here, you pick two ports and the plugin does the work.

## What it does

It reads that same data as a graph: a free end-to-end channel of the right media
(single-mode, multi-mode or copper) across your structured cabling, and every cable to pull
to make it real.

![Candidate routes and the resulting cabling plan](https://raw.githubusercontent.com/bpapaylle/netbox-plugin-autocabling/master/docs/img/plan-routes.png)

## Contents

[Install](#installation) · [60-second demo](#get-started-in-60-seconds) ·
[Concepts](#concepts-in-a-minute) · [Features](#features) · [Settings](#settings) ·
[API & CLI](#api--cli)

## Compatibility

| NetBox    | Python | netbox-plugin-autocabling |
|-----------|--------|---------------------------|
| 4.5 – 4.6 | ≥ 3.12 | 0.3.x                     |

See [COMPATIBILITY.md](https://github.com/bpapaylle/netbox-plugin-autocabling/blob/master/COMPATIBILITY.md).

## Installation

```bash
pip install netbox-plugin-autocabling
```

```python
# configuration.py. Note the underscore: the pip package is netbox-plugin-autocabling,
# but the importable plugin (used here) is netbox_autocabling
PLUGINS = ["netbox_autocabling"]
```

```bash
python manage.py migrate netbox_autocabling
```

## Get started in 60 seconds

No documented backbone to try it on yet? Build a synthetic one:

```bash
python manage.py autocable_seed_demo
```

This creates a self-contained **Demo** site: a datacenter, two intermediate rooms and two
floors, wired with single-mode, multi-mode and copper. Then open
*Auto-cabling → Cabling → New cabling* and cable `ACC-SW-1` to `CORE-SW-1`.

The demo assumes a few settings (route choice, per-hop picker, intra-room adjacency, media
enforcement, free bridges). The seed does **not** touch your settings: it prints the values it
assumes, and `--apply-settings` writes them — they apply install-wide, not just to the demo.

To rebuild the demo, `--wipe` deletes the site first. It is destructive (every cable, device,
rack and location of that site, then the site itself), it only accepts a site this seeder
created, and it asks you to type the slug — add `--yes` in a script.

On your own site the only prerequisite is to tag the patch panels the engine may traverse
with the backbone tag (`autocable-backbone`).

## Concepts in a minute

| Term | What it means |
|---|---|
| **Trunk** | A rear↔rear cable between two panels: the structural link you rarely touch. |
| **Patch** | A front↔front cord (and switch→panel), the thing the plugin creates for you. |
| **Module** | A block of same-type ports fitted into a panel: 12 LC strands, 24 RJ45 positions. Its module type is what tells the plugin the media of every port in the block. |
| **Panel** | Usually terminates **one** destination; its modules are the media that link carries. A panel terminating two is traversed by cross-connecting two of its own front ports. |
| **Backbone tag** | Marks the panels the engine is allowed to route over. |
| **Bridge** | A cord between two racks that doesn't exist yet. The plugin can plan it, if whitelisted. |
| **Satellite rack** | A rack whose switch has no local panel; it reaches the backbone next door. |
| **Leaf** | A rackless wall outlet hanging off a panel's rear. |

A channel is one media from end to end: an SMF port only ever routes over SMF trunks, and
there is no media conversion mid-path.

**Modelling panels with modules is optional.** Routing works on plain panels too, taking the
media from the trunk's cable type. What you lose is per-port media: a panel with no module
cannot be checked end to end, so a **mixed-media trunk goes undetected** and the Health page
has nothing to report. Model the modules if you want that check.

---

# Features

## Cabling two ports

*Auto-cabling → Cabling → New cabling*. Pick a source and a destination device, then a free
port on each, and the media. The plugin builds a graph of the site's backbone, finds a free
single-media channel end to end, and shows the plan: the path, the exact cables to create,
and a readable reason when there is none.

An endpoint doesn't have to be an interface: the picker lists a device's free **interfaces,
front ports and rear ports** together. A **backbone panel** is the exception. It is the transit
the engine routes over, not somewhere a link starts, so its own ports are never offered.

Execute it and the cables are created as **planned** cables, coloured by media. There is also
a **⚡ Auto-cable from this port** button on any uncabled interface or front port.

## Candidate routes, and you pick

When several physically distinct paths exist, the plugin lists them instead of silently
choosing: hop count, how many bridges each one needs, and how many free strands are left on
it. Route #1 is the recommended one; pick another and the plan below updates to match.

In the screenshot above, route #1 goes straight through Room B in 2 hops, while route #4
crosses the Room-A bridge, a cord that does not exist yet and would have to be pulled.

*Enable with the `route_choice` setting.*

## Port picker per hop

At the bottom of the plan card, each hop can be pinned to an exact port instead of letting
the engine take the first free one. Useful when a particular strand is reserved, damaged, or
has to match a labelling scheme. Works for a fiber strand and a copper position alike.

*Enable with the `hop_picker` setting.*

## Capacities

![Free and used strands per trunk](https://raw.githubusercontent.com/bpapaylle/netbox-plugin-autocabling/master/docs/img/capacities.png)

Every panel-to-panel trunk with its media, free/total strands and fill gauge, filtered by
site, location, rack or media, and exportable as CSV. The **Leaves** section lists the outlets
each panel serves. A strand counts as used when it carries a cable **or** is marked connected
in NetBox.

## Wall outlets, and devices that aren't in a rack

![A copper panel and the wall outlets it serves](https://raw.githubusercontent.com/bpapaylle/netbox-plugin-autocabling/master/docs/img/panel-fl1rj.png)

A **leaf** is a rackless faceplate hanging off a panel's rear: the outlets of an office or an
open space. They are surfaced on the panel and rack pages with their free/total counts.

A device that isn't in a rack has no obvious entry into the backbone, so the form lets you
declare the outlet it is plugged into: pick the leaf device, then its free port, and the
pathfinder starts from there.

## Satellite racks

![An access rack with no fiber panel of its own](https://raw.githubusercontent.com/bpapaylle/netbox-plugin-autocabling/master/docs/img/rack-fl1acc.png)

Some access racks have a fiber panel of their own. Others hold nothing but a switch and a
copper patch, and their fiber lives in a neighbouring rack of the same room, where an operator
patches across the aisle without thinking about it. The plugin does the same: the switch
enters the backbone through that neighbouring panel, so it is cabled like any other, and no
imaginary cable appears in the plan.

Above, `FL1-ACC` holds `ACC-SW-1` and its RJ panel. The floor's fiber panels are in `FL1-FIB`,
and that is where routes from this switch begin.

*Governed by the `intra_room_adjacency` setting; turn it off and such a switch becomes
unroutable. In a room restricted by [allowed bridges](#allowed-bridges), the pair must also
be listed: the entry patch is a direct cord like any other.*


## Health

![Backbone anomalies](https://raw.githubusercontent.com/bpapaylle/netbox-plugin-autocabling/master/docs/img/health.png)

The routing engine silently ignores backbone data it cannot use. This page shows what it
ignored, so a mis-patch doesn't turn into a mystery:

| Anomaly | What it means |
|---|---|
| **Mixed-media trunk** | The two ends resolve to different media (SMF one side, MMF the other), physically impossible, so excluded from routing. |
| **Self-referencing trunk** | Both rear ports sit on the same device: a panel cabled to itself. |
| **Dangling trunk** | A rear↔rear cable with a single end visible in the site: a phantom or incomplete inter-site trunk. |
| **Unmapped module rear** | A rear port with no mapped front port, so the strand cannot be patched. |

Two more are listed for information rather than as faults: **intra-rack cross-connects**
(legitimate, but invisible to inter-rack routing) and **patched, leading nowhere**, a cord
whose chain reaches no equipment on either side: either a leftover to reclaim, or capacity
patched ahead of the equipment.

`manage.py cabling_anomalies [--site]` prints the same report on the CLI.

## Allowed bridges

![The bridge matrix](https://raw.githubusercontent.com/bpapaylle/netbox-plugin-autocabling/master/docs/img/bridge-matrix.png)

Two backbone segments can meet in the same room without being trunked to each other; joining
them means pulling a new cord between two racks, a **bridge**. By default every pair of
co-located racks may be joined. Adding at least one allowed bridge to a location makes that
location *restricted*: only the listed pairs may be joined. The matrix edits them per site,
and CSV import is one way to load them.

The rule covers every direct cord the plugin proposes in a room, not just bridges: a
[satellite rack](#satellite-racks) reaching a neighbour's panel, a saturated rack borrowing a
neighbour's capacity, and the last-resort direct cable between two devices when no structured
path exists. All of them need their pair listed once the room is restricted. So in a data
centre of eight racks you can forbid a cord running the length of the room, and it stays
forbidden on every path the plugin can take. A cable between two devices in the same rack is
never concerned: it joins no pair of racks.

What a bridge is worth is yours to set: see [`bridge_detour_threshold`](#settings).

## CSV batch

*Auto-cabling → Cabling → CSV batch* plans many links in one pass, with **collision-free
allocation**: each row reserves its strands so a later row cannot reuse them. A
[bridge](#allowed-bridges) is not a strand but a cord to pull, so several rows may cross the same
one — pulling a cord does not stop you pulling another. It stays bounded by what a cord costs:
each one lands on a free front port at either end, so a pair is bridged as many times as its two
panels have free fronts and then reports *exhausted* like any other saturated path.

```csv
src_device,dest_device,src_port,dest_port,media
ACC-SW-1,CORE-SW-1,eth0,eth0,SMF
ACC-SW-2,CORE-SW-2,eth0,eth0,SMF
```

Analyse first: you get a per-row path with duplicate, bridge and saturation flags. Then
execute.

## Work orders & rollback

![An executed job](https://raw.githubusercontent.com/bpapaylle/netbox-plugin-autocabling/master/docs/img/job-workorder.png)

Every execution is recorded as a **cabling job**: the cables it created, the path, the status.
Export it as a work order for whoever pulls the cables, and roll it back in one click to
delete those cables and free their strands. **History** keeps every run.

## Cabling applications

![A cabling application](https://raw.githubusercontent.com/bpapaylle/netbox-plugin-autocabling/master/docs/img/cabling-app.png)

Cables created outside the form (by a Custom Script, a recipe, the REST API) can be grouped
into a **cabling application**: one object holding the created cable ids, with the same
one-click rollback. Useful when a script cables a whole server build and you want a single
undo.

## When no route is found

The plan says which case it is, rather than just failing:

| Status | Meaning |
|---|---|
| `no_path` | The two racks aren't connected over that media: one is off the backbone, or they sit on two disjoint islands. |
| `exhausted` | A path exists but every strand on it is taken. Capacities shows where. |
| `media_mismatch` | The two ports aren't the same media class (a copper port on a fiber job). |
| `port_occupied` | One of the two endpoint ports already carries a cable. |
| `no_rack` | An endpoint isn't in a rack and has no outlet to enter through. |

Two causes worth checking first: the panels aren't **tagged**, or a trunk's two ends resolve
to **different media** and it was therefore excluded. Health lists those.

---

## Settings

A **Settings page** (*Auto-cabling → Configuration → Settings*) overrides these at runtime,
without a restart. Resolution order: Settings page → `PLUGINS_CONFIG` → built-in default.

| Setting | Default | What it does |
|---|---|---|
| `trunk_tag` | `autocable-backbone` | Which panels the engine may traverse |
| `enforce_port_media` | `true` | Reject a copper port on a fiber job, and vice versa |
| `intra_room_adjacency` | `true` | A switch whose rack has no panel enters via a panel in the same room |
| `saturation_overflow` | `true` | When a rack's own trunks are full, allow a last-resort same-room cable |
| `allow_direct` | `true` | Propose a direct device-to-device cable when no structured path exists |
| `bridge_detour_threshold` | `0` | What a bridge is worth, in hops. `0` = pull a cord whenever it strictly shortens the route; raise it to reserve bridges for big detours |
| `route_choice` | `false` | Offer candidate routes instead of a single one |
| `route_candidates` | `5` | How many routes to list |
| `route_max_extra_hops` | `2` | Only list routes at most this much longer than the shortest |
| `hop_picker` | `false` | Let the operator pin the port used on each hop |
| `cable_types` | `smf-os2` / `mmf-om4` / `cat6a` | Cable type created per media |
| `cable_colors` | yellow / orange / blue | Colour given to created cables, one picker per media |
| `default_media` | *(none)* | Media preselected in the form |

```python
PLUGINS_CONFIG = {
    "netbox_autocabling": {
        "trunk_tag": "autocable-backbone",
        "cable_types": {"SMF": "smf-os2", "MMF": "mmf-om5", "COPPER": "cat6a"},
    }
}
```

## API & CLI

```python
from netbox_autocabling.services import plan_port_to_port, create_port_cabling
from netbox_autocabling.graph_cache import get_graph

plan = plan_port_to_port(if_a, if_b, "SMF", graph=get_graph(if_a.device.site))
if plan.ok:
    create_port_cabling(plan)      # idempotent
```

| Surface | Entry point |
|---|---|
| REST · plan | `POST cabling-jobs/plan/` `{port_a, port_b, media}` → status, cables, per-hop options (no writes) |
| REST · cable | `POST cabling-jobs/cable/` (+ optional overrides) → creates the cabling, records a job |
| REST · CRUD | `cabling-jobs` · `allowed-bridges` · `settings` · `cabling-applications` |
| CLI · port | `autocable_port <if_a> <if_b> <media> [--create] [--record]`. The New-cabling flow |
| CLI · rack | `autocable <rackA> <rackB> <media> [--create] [--dry-run] [--record]`. Reserves a strand between two racks without naming the equipment ports |
| CLI · audit | `cabling_anomalies [--site]`. The Health report on the CLI |
| CLI · demo | `autocable_seed_demo [--apply-settings] [--wipe [--yes]] [--ports N]`. Builds the synthetic Demo site. `--wipe` is destructive and only accepts a site the seeder created; `--ports` (default 12) sizes the panels for a lighter demo |

Two conventions, so read the right one before running anything. `autocable` and
`autocable_port` cable nothing unless `--create` is passed, and `cabling_anomalies` only ever
reads. The scaffolding commands are the other way round: `autocable_seed_device_types`,
`autocable_seed_switches` and `autocable_tag_backbone` **write by default** and are held back
by `--dry-run`, while `autocable_seed_demo` builds its site outright and `--wipe` deletes one.
Creating cables requires the `dcim.add_cable` permission.

## Project

[Concepts](https://github.com/bpapaylle/netbox-plugin-autocabling/blob/master/docs/concepts.md) · [Reference](https://github.com/bpapaylle/netbox-plugin-autocabling/blob/master/docs/reference.md) ·
[Changelog](https://github.com/bpapaylle/netbox-plugin-autocabling/blob/master/CHANGELOG.md) · [Roadmap](https://github.com/bpapaylle/netbox-plugin-autocabling/blob/master/ROADMAP.md) · [Contributing](https://github.com/bpapaylle/netbox-plugin-autocabling/blob/master/CONTRIBUTING.md) ·
[Compatibility](https://github.com/bpapaylle/netbox-plugin-autocabling/blob/master/COMPATIBILITY.md)

## License

Apache-2.0, see [LICENSE](https://github.com/bpapaylle/netbox-plugin-autocabling/blob/master/LICENSE).
