{"id":49989025,"url":"https://github.com/iamjohnnymac/netbox-osp","last_synced_at":"2026-05-19T02:01:18.724Z","repository":{"id":357502213,"uuid":"1237238405","full_name":"iamjohnnymac/netbox-osp","owner":"iamjohnnymac","description":"Outside-plant fibre management for NetBox — cables, splice closures, fibre links with loss budgets, and an offline-capable Leaflet plant map.","archived":false,"fork":false,"pushed_at":"2026-05-13T03:53:43.000Z","size":239,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-13T04:33:36.510Z","etag":null,"topics":["fibre","gis","leaflet","netbox","netbox-plugin","outside-plant"],"latest_commit_sha":null,"homepage":"https://pypi.org/project/netbox-osp/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/iamjohnnymac.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-13T02:14:52.000Z","updated_at":"2026-05-13T03:53:46.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/iamjohnnymac/netbox-osp","commit_stats":null,"previous_names":["iamjohnnymac/netbox-osp"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/iamjohnnymac/netbox-osp","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iamjohnnymac%2Fnetbox-osp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iamjohnnymac%2Fnetbox-osp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iamjohnnymac%2Fnetbox-osp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iamjohnnymac%2Fnetbox-osp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/iamjohnnymac","download_url":"https://codeload.github.com/iamjohnnymac/netbox-osp/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iamjohnnymac%2Fnetbox-osp/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33198811,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-18T09:27:30.708Z","status":"online","status_checked_at":"2026-05-19T02:00:06.763Z","response_time":58,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["fibre","gis","leaflet","netbox","netbox-plugin","outside-plant"],"created_at":"2026-05-19T02:01:07.625Z","updated_at":"2026-05-19T02:01:18.703Z","avatar_url":"https://github.com/iamjohnnymac.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# netbox-osp\n\n\u003cdiv align=\"center\"\u003e\n\n\u003cimg src=\"https://raw.githubusercontent.com/iamjohnnymac/netbox-osp/main/icon.svg\" alt=\"netbox-osp\" width=\"96\" height=\"96\" /\u003e\n\n**Outside-plant fibre management for NetBox — cables, splice closures, loss budgets,\nand an offline-capable plant map.**\n\n[![PyPI version](https://img.shields.io/pypi/v/netbox-osp.svg)](https://pypi.org/project/netbox-osp/)\n[![Python versions](https://img.shields.io/pypi/pyversions/netbox-osp.svg)](https://pypi.org/project/netbox-osp/)\n[![NetBox 4.6](https://img.shields.io/badge/NetBox-4.6-orange)](https://github.com/netbox-community/netbox)\n[![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)\n[![Tests](https://github.com/iamjohnnymac/netbox-osp/actions/workflows/test.yml/badge.svg)](https://github.com/iamjohnnymac/netbox-osp/actions/workflows/test.yml)\n[![Docs](https://img.shields.io/badge/docs-mkdocs--material-blue)](https://iamjohnnymac.github.io/netbox-osp/)\n[![Demo walkthrough](https://img.shields.io/badge/demo-walkthrough-purple)](https://iamjohnnymac.github.io/netbox-osp/demo/)\n\n\u003c/div\u003e\n\nOutside-plant (OSP) fibre management for NetBox. `netbox-osp` extends NetBox's `dcim` models\nwith a full OSP data model — cables, buffer tubes, strands, splice closures, trays, and end-to-end\nfibre links — and pairs it with a full-screen interactive plant map. The plugin is offline-first\n(no PostGIS, no upstream tile servers required), applies TIA-598-C colour ordering automatically,\nand computes a per-link loss budget so operators can see at a glance whether a span meets its\nattenuation target.\n\n## Screenshots\n\n\u003cdiv align=\"center\"\u003e\n\n\u003cimg src=\"https://raw.githubusercontent.com/iamjohnnymac/netbox-osp/main/docs/screenshots/network_map.png\" alt=\"Full-screen network map with cables, splice closures, and Location GPS markers\" width=\"800\" /\u003e\n\n\u003csub\u003e\u003cem\u003eFull-screen Leaflet plant map at \u003ccode\u003e/plugins/osp/map/\u003c/code\u003e — OSP cables, splice closures, Sites, and per-Location markers all filterable.\u003c/em\u003e\u003c/sub\u003e\n\n\u003cimg src=\"https://raw.githubusercontent.com/iamjohnnymac/netbox-osp/main/docs/screenshots/mtp_harness_deploy.png\" alt=\"One-click MTP harness deploy form\" width=\"800\" /\u003e\n\n\u003csub\u003e\u003cem\u003eOne form submit atomically deploys a parent FibreTrunk + N cassette devices + N cables + N TrunkBreakout rows.\u003c/em\u003e\u003c/sub\u003e\n\n\u003cimg src=\"https://raw.githubusercontent.com/iamjohnnymac/netbox-osp/main/docs/screenshots/core_tracer.png\" alt=\"Visual core tracer end-to-end fibre path with per-hop loss\" width=\"800\" /\u003e\n\n\u003csub\u003e\u003cem\u003eClick \"Trace this core\" on any Strand / FrontPort / Interface. Each hop is clickable; loss budget banded ok/warn/fail above the graph.\u003c/em\u003e\u003c/sub\u003e\n\n\u003c/div\u003e\n\n## Features\n\n- **OSP cable / tube / strand data model** — multi-tube armoured cables with TIA-598-C\n  colour-coded tubes and strands. The `fibre_count == tube_count * fibres_per_tube`\n  invariant is enforced in `clean()`.\n- **Splice closures, trays, and splices** — model dome / inline / pedestal / handhole /\n  wall-mount / aerial closures, with measured per-splice loss and OTDR trace links.\n- **Fibre-link loss budget** — chain strands through splices into an end-to-end logical\n  link. The plugin computes strand attenuation × length + per-splice + per-connector loss,\n  renders an SVG gauge, and bands the result as **ok / warn / fail** against a configurable\n  target budget.\n- **Full-screen Leaflet map** with seven online base layers (OSM, HOT OSM, CartoDB Positron\n  and Dark Matter, OpenTopoMap, CyclOSM, Esri World Imagery) plus a bundled offline MBTiles\n  layer. The map auto-falls-back to offline after three tile errors in five seconds and\n  remembers the operator's explicit choice across reloads.\n- **GeoJSON cable-route editor** — leaflet-geoman drag-to-edit on cables and closures,\n  sharing the same base-layer machinery as the main map.\n- **Plant-boundary validation** — set a closed polygon in `PLUGINS_CONFIG` and\n  `OspCable.clean()` rejects any cable whose route falls outside it. Pure-Python\n  ray-casting, no PostGIS dependency.\n- **Per-Location GPS markers** — `LocationGeo` is a 1:1 side-table on `dcim.Location`\n  that adds latitude / longitude / elevation / marker colour, rendered as overlays on\n  the network map and as a panel on the Location detail page.\n- **REST + GraphQL APIs** — full CRUD on every model under `/api/plugins/osp/...` plus\n  read-only GraphQL types via `strawberry-django` at NetBox's shared `/graphql/` endpoint.\n- **Bulk import / bulk edit** for tubes, strands, splices, trays, closures, and\n  LocationGeo — closes the ~300-click data-entry gap on a 288-strand cable.\n- **NetBox 4.6+** with a CI matrix covering Python 3.12 and 3.13 on Postgres 17 + Redis 7.\n\n## Compatibility\n\n| netbox-osp | NetBox | Python      | Postgres | Redis | Notes                     |\n|------------|--------|-------------|----------|-------|---------------------------|\n| 0.1.x      | 4.6.x  | 3.12 · 3.13 | 17       | 7     | First functional release. |\n\nPostGIS is **not** required — geometry is stored as GeoJSON in `JSONField` columns and\nplant-boundary validation uses pure-Python ray-casting. See [COMPATIBILITY.md](COMPATIBILITY.md)\nfor the full support policy, NetBox / Python / Postgres version policies, and upgrade-path\nguidance.\n\n## Install\n\n### From PyPI\n\nThe recommended path for production installs:\n\n```bash\npip install netbox-osp\n```\n\nEnable the plugin in NetBox's `configuration/plugins.py`:\n\n```python\nPLUGINS = [\n    \"netbox_osp\",\n]\n```\n\nApply migrations and collect static assets:\n\n```bash\npython manage.py migrate\npython manage.py collectstatic --no-input\n```\n\nRestart NetBox and the RQ workers.\n\n### From source (development)\n\n```bash\ngit clone https://github.com/iamjohnnymac/netbox-osp.git\ncd netbox-osp\npip install -e .\n```\n\nThen follow the same `PLUGINS` / migrate / collectstatic steps above.\n\n### With netbox-docker\n\nAdd the plugin to `plugin_requirements.txt`:\n\n```text\nnetbox-osp\n```\n\nEnable it in `configuration/plugins.py`:\n\n```python\nPLUGINS = [\n    \"netbox_osp\",\n]\n\nPLUGINS_CONFIG = {\n    \"netbox_osp\": {\n        \"default_attenuation_db_per_km\": 0.22,\n        \"default_splice_loss_db\": 0.10,\n        \"default_connector_loss_db\": 0.30,\n        \"map_default_center\": [0.0, 0.0],\n        \"map_default_zoom\": 2,\n    },\n}\n```\n\nRebuild and restart the `netbox` and `netbox-worker` containers.\n\nSee [docs/install.md](docs/install.md) for the complete walk-through.\n\n## Configuration\n\nAll settings live under `PLUGINS_CONFIG[\"netbox_osp\"]`:\n\n```python\nPLUGINS_CONFIG = {\n    \"netbox_osp\": {\n        # Fallback attenuation when an OspCable has no explicit value.\n        \"default_attenuation_db_per_km\": 0.22,\n\n        # Fallback per-splice loss when a Splice has no explicit loss_db.\n        \"default_splice_loss_db\": 0.10,\n\n        # Per-connector loss applied at both ends of every FibreLink.\n        \"default_connector_loss_db\": 0.30,\n\n        # Default Leaflet view as [lat, lon]. Set to your area of interest.\n        \"map_default_center\": [0.0, 0.0],\n\n        # Default Leaflet zoom level. 13-16 is appropriate for site scale.\n        \"map_default_zoom\": 2,\n\n        # Optional closed polygon of [lon, lat] vertices (GeoJSON order).\n        # If set, OspCable.clean() rejects any cable whose route falls\n        # outside the polygon. Pure-Python ray-casting; no PostGIS needed.\n        # \"plant_boundary\": [\n        #     [10.000, 50.000],\n        #     [10.010, 50.000],\n        #     [10.010, 50.010],\n        #     [10.000, 50.010],\n        # ],\n    },\n}\n```\n\n| Key                             | Default      | Purpose                                                                 |\n|---------------------------------|--------------|-------------------------------------------------------------------------|\n| `default_attenuation_db_per_km` | `0.22`       | Fallback attenuation when an `OspCable` has no explicit value.          |\n| `default_splice_loss_db`        | `0.10`       | Fallback per-splice loss when a `Splice` has no explicit `loss_db`.     |\n| `default_connector_loss_db`     | `0.30`       | Per-connector loss applied at both ends of every `FibreLink`.           |\n| `map_default_center`            | `[0.0, 0.0]` | `[lat, lon]` for the default map view.                                  |\n| `map_default_zoom`              | `2`          | Default Leaflet zoom level (13–16 is appropriate for site scale).       |\n| `plant_boundary`                | `None`       | Optional closed `[lon, lat]` polygon. Validates `OspCable.route`.       |\n\nSee [docs/configure.md](docs/configure.md) for deeper docs on plant-boundary validation,\nper-Location GPS markers, and GraphQL.\n\n## Ecosystem integrations\n\n`netbox-osp` composes with the wider NetBox plugin ecosystem rather than reinventing it.\nEach integration below is **optional** — the plugin runs fine without any of them.\n\n- **[netbox-attachments](https://github.com/Kani999/netbox-attachments)** — attach OTDR\n  `.sor` traces, splice photos, as-built drawings, and acceptance certificates to any\n  OSP model. Install with `pip install netbox-osp[attachments]` and add a `scope_filter`\n  block to `PLUGINS_CONFIG`.\n- **Field QR codes** — built-in QR panel on `SpliceClosure` and `SpliceTray` detail\n  pages encoding the absolute URL. Field crews scan from a printed closure label and\n  land on the splice plan with attached photos. Install with `pip install\n  netbox-osp[qrcode]`; no `PLUGINS_CONFIG` changes needed.\n\nSee [docs/integrations.md](docs/integrations.md) for the full configuration snippets,\nuse-case matrix, and verification steps.\n\n## Data model\n\nAll geometry is stored as GeoJSON in WGS84 with `[lon, lat]` order (RFC 7946). Conversion\nto Leaflet's `[lat, lon]` happens at the JS boundary only.\n\n```\ndcim.Site                                       dcim.Manufacturer\n  |  |                                                |\n  |  +--\u003c OspCable \u003e-----------\u003c Tube \u003e-----\u003c Strand --+\n  |          (route GeoJSON)         |          |\n  |                                  +--------- |\n  |                                             +--\u003e dcim.Cable\n  |                                             |    (Strand.cable_link, optional)\n  |                                             |\n  +--\u003c SpliceClosure \u003e--\u003c SpliceTray \u003e--\u003c Splice \u003e--+\n            (Point)                       (strand_a, strand_b)\n\n  FibreLink \u003e--\u003c FibreLinkStrand \u003e--\u003c Strand\n        (loss budget, status)         (ordered hops)\n```\n\n| Model              | Purpose                                                                                                            |\n|--------------------|--------------------------------------------------------------------------------------------------------------------|\n| `OspCable`         | A physical fibre cable run between two sites. Stores type, attenuation, GeoJSON route, and length.                 |\n| `Tube`             | A buffer tube inside an `OspCable`. Unique on `(cable, number)`.                                                   |\n| `Strand`           | A single fibre strand. Optional bridge to `dcim.Cable` via `Strand.cable_link` for legacy strand-as-cable flows.   |\n| `SpliceClosure`    | A physical splice enclosure (dome, pedestal, etc.) sited at a location with optional GeoJSON point.                |\n| `SpliceTray`       | A tray inside a closure. Holds individual splices.                                                                 |\n| `Splice`           | A fusion or mechanical splice joining two strands. Stores measured `loss_db` and an optional OTDR trace URL.       |\n| `FibreLink`        | A logical end-to-end link composed of one or more strands joined by splices, with a configurable loss budget.      |\n| `FibreLinkStrand`  | Through-table assigning strands to a `FibreLink` in ordered hops.                                                  |\n| `LocationGeo`      | 1:1 side-table on `dcim.Location` adding latitude / longitude / elevation / marker colour for per-Location pins.   |\n\nSee [docs/data-model.md](docs/data-model.md) for the field-by-field reference.\n\n## API\n\n### REST\n\nAll endpoints sit under `/api/plugins/osp/`. Standard NetBox auth (`Authorization: Token \u003ckey\u003e`)\nand filtering / pagination apply.\n\n| Path                                | Methods                     | Notes                       |\n|-------------------------------------|-----------------------------|-----------------------------|\n| `/api/plugins/osp/cables/`          | GET / POST / PATCH / DELETE | List + CRUD `OspCable`      |\n| `/api/plugins/osp/tubes/`           | GET / POST / PATCH / DELETE | List + CRUD `Tube`          |\n| `/api/plugins/osp/strands/`         | GET / POST / PATCH / DELETE | List + CRUD `Strand`        |\n| `/api/plugins/osp/closures/`        | GET / POST / PATCH / DELETE | List + CRUD `SpliceClosure` |\n| `/api/plugins/osp/trays/`           | GET / POST / PATCH / DELETE | List + CRUD `SpliceTray`    |\n| `/api/plugins/osp/splices/`         | GET / POST / PATCH / DELETE | List + CRUD `Splice`        |\n| `/api/plugins/osp/links/`           | GET / POST / PATCH / DELETE | List + CRUD `FibreLink`     |\n| `/api/plugins/osp/location-geos/`   | GET / POST / PATCH / DELETE | List + CRUD `LocationGeo`   |\n\n### GraphQL\n\nTypes are registered with NetBox's shared `/graphql/` endpoint and authenticate with the same\nAPI token as REST.\n\n```graphql\n{\n  osp_cable_list {\n    id\n    cid\n    status\n    fibre_count\n    length_m\n  }\n}\n```\n\nThe schema is read-only — use REST for writes.\n\n### UI endpoints\n\n| Path                                       | Purpose                                  |\n|--------------------------------------------|------------------------------------------|\n| `/plugins/osp/map/`                        | Full-screen network map (HTML)           |\n| `/plugins/osp/map/data/`                   | Map GeoJSON FeatureCollection (filterable) |\n| `/plugins/osp/tiles/\u003cz\u003e/\u003cx\u003e/\u003cy\u003e.\u003cext\u003e`     | Tile proxy backed by MBTiles             |\n\n## Map tiles\n\nThe plant map uses Leaflet with seven public online base layers plus a bundled offline MBTiles\nlayer, and auto-falls-back to offline after three tile-load failures in five seconds. To replace\nthe bundled stub with your own area's imagery, drop one or more `.mbtiles` files under\n`\u003cMEDIA_ROOT\u003e/osp_tiles/` — they take precedence over the bundled basemap. See\n[docs/tile-bundling.md](docs/tile-bundling.md) for the `tilemaker`-based build workflow.\n\n## Development\n\n```bash\ngit clone https://github.com/iamjohnnymac/netbox-osp.git\ncd netbox-osp\npip install -e \".[test,docs]\"\npre-commit install\n```\n\nRun the lint + format check and the test suite:\n\n```bash\npre-commit run --all-files\npython -m coverage run -m pytest\n```\n\nRuff (lint + format) is configured in `pyproject.toml` to match the wider NetBox plugin\necosystem; the pre-commit hook runs it on every commit.\n\n## Roadmap\n\n- **Short-term** — extend the permission-matrix tests to the remaining seven primary-object view\n  sets, finish debugging the GraphQL `osp_\u003cmodel\u003e_list` field surfacing against a live `/graphql/`\n  endpoint.\n- **Medium-term** — OTDR trace upload, DWDM channel allocation on `FibreLink`, QGIS-friendly\n  export of cable routes.\n- **Long-term** — optional PostGIS backend for users who want native spatial indexes, and\n  submission to the NetBox Labs Plugin Certification Program.\n\nIssue tracker for the live picture: \u003chttps://github.com/iamjohnnymac/netbox-osp/issues\u003e.\n\n## Contributing\n\nPRs welcome. Please run `pre-commit run --all-files` and ensure the test suite passes before\npushing, and keep new code aligned with the ruff config in `pyproject.toml`. Substantive\nchanges should add or update tests under `netbox_osp/tests/`. A formal `CONTRIBUTING.md` will\nland alongside the first cert-program submission.\n\n## Support\n\n- **Bug reports** — \u003chttps://github.com/iamjohnnymac/netbox-osp/issues\u003e\n- **Questions and discussion** — \u003chttps://github.com/iamjohnnymac/netbox-osp/discussions\u003e\n- **Chat** — `#netbox-plugins` on [NetDev Slack](https://netdev.chat/)\n\n## License\n\nApache-2.0. See [LICENSE](LICENSE) for the full text. Icon CC BY 4.0.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fiamjohnnymac%2Fnetbox-osp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fiamjohnnymac%2Fnetbox-osp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fiamjohnnymac%2Fnetbox-osp/lists"}