Metadata-Version: 2.4
Name: netbox-plugin-autocabling
Version: 0.1.0
Summary: NetBox plugin for automatic fiber cabling over structured cabling (end-to-end free-fiber 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/blob/master/docs/index.md
Project-URL: Changelog, https://github.com/bpapaylle/netbox-plugin-autocabling/blob/master/CHANGELOG.md
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.13
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 plugin for **automatic fiber cabling over structured cabling**. Give it two
endpoints — two racks or two device ports — and it finds the free fiber channel end to end
across the existing backbone (patch panels, trunks, modules) and **creates the patch
cables** that connect them. Deterministic, idempotent, and module-agnostic.

It also audits the backbone: per-panel capacities, saturation, and a data-quality
dashboard that surfaces the inconsistencies the routing engine would otherwise hit.

## Compatibility

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

See [COMPATIBILITY.md](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
```

## How it works

The plugin builds a **channel graph** of a site from the structured cabling: rear ports,
their modules (media: SMF/MMF), the rear↔rear **trunks** between racks, and the
front-port mappings used to patch through. Only panels carrying the **backbone tag**
(`trunk_tag`, default `autocable-backbone`) are routable, so you decide which cabling the
engine is allowed to use.

Routing happens at the **rack + media** level (module-agnostic): the engine selects a free
fiber end to end, then emits the concrete patch cables (front-port to front-port at each
hop, equipment port at the ends). The graph is cached per site and invalidated
automatically on any cable, termination or backbone-tag change.

## Features

### Port-to-port auto-cabling

Cable two **device ports** (Interface / FrontPort / RearPort) over the backbone.

- **New cabling** (sidebar *Auto-cabling → Cabling → New cabling*): pick source &
  destination device/port + media → **preview** (annotated path diagram, the exact cables
  to create, human-readable status, bridge/overflow alerts) → **execute**.
- **⚡ Auto-cable from this port** button on any uncabled Interface / FrontPort page.
- Every execution records a **cabling job** (history) with the created cable ids.

### Rack-to-rack auto-cabling

Cable two racks end to end from the CLI (read-only by default, `--create` to write,
`--dry-run` to show the plan without writing):

```bash
python manage.py autocable <rackA> <rackB> SMF --site Site-1 --create
```

### Routing rules

- **Same rack** → direct intra-rack cable.
- **Satellite rack** (no panel of its own) → enters the backbone via a panel in the
  **same room** (intra-room adjacency; toggleable).
- **Multi-hop reroute** → a saturated direct trunk is automatically routed around over
  free trunks on a longer path; the result is `exhausted` only when no free path exists.
- **Saturation overflow** → when a rack's own trunks are saturated, routing can fall back
  — as a *last resort* — to a **co-located rack** via a same-room cable to pull
  (confirmation required; toggleable via `saturation_overflow`).
- **Same-room bridge** → if two disjoint backbone segments meet in a room, a **direct
  panel-to-panel cable** can join them *in the middle* of the path. A bridge is a physical
  cable to pull, so it is only preferred over an all-trunk path when it avoids a detour of
  at least `bridge_detour_threshold` hops per bridge (it is still used unconditionally when
  it is the *only* way). Which racks may be bridged can be restricted (see **Allowed
  bridges**).
- **Same room, no structured path** → a **direct device-to-device cable** is proposed
  (confirmation; toggleable via `allow_direct`).
- Otherwise `no_path` is **explained**: rack off backbone, two unconnected islands, or a
  path available only in the other media.

### CSV batch

Cable many pairs at once (*Auto-cabling → Cabling → CSV batch*) with **collision-free
allocation** (each row reserves its fibers so later rows don't reuse them):

```csv
src_device,dest_device,src_port,dest_port,media
```

Analyze → per-row path, duplicate / bridge / overflow flags → execute.

### Allowed bridges (per-location whitelist)

By default every pair of co-located racks may be bridged. A location with **≥ 1** allowed
bridge becomes *restricted*: only its listed rack pairs may be bridged — useful to forbid
pulling a fiber between the two most distant racks of a large room. A location with **none**
stays open (e.g. floor rooms).

Managed under *Auto-cabling → Configuration → Allowed bridges* (guided
site → room → racks form), with a native **CSV/JSON/YAML import**:

```csv
site,location,rack_a,rack_b
```

`site`/`location` are optional but recommended — they scope the rack lookup so same-named
racks in different rooms are disambiguated.

### Capacities audit

*Auto-cabling → Cabling → Capacities*: per-panel **free / total** fibers with a fill bar,
filterable by location (whole subtree) / rack / media, plus **CSV export**. Patch-panel and
rack pages also show **backbone cards** — next-hop trunks with a traffic-light saturation
bar and linked neighbor racks.

### Health / data-quality dashboard

*Auto-cabling → Cabling → Health*: per-site **structural overview** (racks on backbone,
trunks, bridges, media split, disconnected **islands**, off-backbone racks) plus the data
**anomalies** the routing engine silently discards, each row linked to its NetBox object:

- **mixed-media trunks** — a trunk with SMF on one end and MMF on the other;
- **self-referencing trunks** — a panel cabled to itself;
- **dangling trunks** — a rear↔rear cable with a single visible endpoint;
- **unmapped module rear ports** — a rear port with no front mapping (unusable fiber).

It also lists, as **informational** (not anomalies), legitimate **intra-rack
cross-connects** (rear↔rear cables between two panels of the same rack) — valid cabling
that simply doesn't participate in inter-rack routing.

The same scan is available headless:

```bash
python manage.py cabling_anomalies --site Site-1
```

### Work order & rollback

From a job (or a batch / a selection): export an **Excel work order** for the field
technician, and **roll back** a job (deletes exactly the cables it created).

### Search

Cabling jobs are indexed in NetBox **global search**, and a background **anomaly-scan job**
can be scheduled.

## Settings

Static defaults in the configuration file:

```python
PLUGINS_CONFIG = {
    "netbox_autocabling": {
        "trunk_tag": "autocable-backbone",                    # routable backbone panels
        "cable_types": {"SMF": "smf-os2", "MMF": "mmf-om4"},  # cable type created per media
    }
}
```

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

| Setting | Effect |
|---|---|
| `intra_room_adjacency` | satellite racks enter the backbone via a same-room panel |
| `saturation_overflow` | allow borrowing a co-located rack when own trunks are full |
| `allow_direct` | propose a direct cable when there is no structured path in the room |
| `bridge_detour_threshold` | hops/bridge a bridge must save to win over an all-trunk path (default 3; 1 = bridge wins as soon as it is shorter) |
| `trunk_tag` | slug of the backbone tag |
| `cable_types` | cable type created per media, e.g. `{"SMF": "smf-os2", "MMF": "mmf-om5"}` |
| `default_media` | media preselected in the cabling form |

## REST API

```bash
# plan (read-only)
curl -X POST .../api/plugins/autocabling/cabling-jobs/plan/ \
  -d '{"port_a": 1234, "port_b": 5678, "media": "SMF"}'
# create (requires dcim.add_cable)
curl -X POST .../api/plugins/autocabling/cabling-jobs/cable/ \
  -d '{"port_a": 1234, "port_b": 5678, "media": "SMF"}'
```

Endpoints are also exposed for `cabling-jobs`, `allowed-bridges` and `settings`.

## Python API

```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, type-agnostic
```

## Seeding a test site

All idempotent and `--dry-run` compatible — instantiate switches on a site that has none,
then tag the backbone:

```bash
python manage.py autocable_seed_device_types            # generic Cisco device types
python manage.py autocable_seed_switches --site site-1  # one switch per rack
python manage.py autocable_tag_backbone  --site site-1  # tag the backbone panels
```

## Development

Tooling is standardized on **uv**:

```bash
uvx ruff check .
uv build
python manage.py test netbox_autocabling
```

CI (`.github/workflows/ci.yml`) runs the suite against NetBox 4.5 and 4.6 plus a
`netbox-plugin-audit` pass.

## License

Apache-2.0 — see [LICENSE](LICENSE).
