Add cutsheets: planned cabling checked against discovery #18

Merged
jared merged 2 commits from cutsheets into main 2026-11-10 16:50:21 -05:00
Owner

Part 1 of #12. Part 2 adds trace mode and mismatch tickets.

A new Cutsheet page (/cutsheet) records where every cable is meant to go and compares it with what Gandalf discovers.

Features

  • Inventory: things discovery can't see: patch panels, wall jacks, shelves, and rack positions for devices. Panels and jacks pass through from a port's front to its rear.
  • Cables:
    • Each cable has two endpoints. An endpoint is a device or item, a port, and a side for panels and jacks.
    • Fields: ID, type, length, color, status (planned, installed, dark, retired) and notes.
    • The far end can be left unterminated, for example a fibre that's run but not connected.
  • Paths: each cable shows its full run through panels and jacks.
  • Plan vs. actual: a run that ends on a switch port is checked against that port's discovered neighbor, from LLDP, the UniFi client table, or a DAC serial seen at both ends. The possible outcomes are:
    • matches
    • wrong neighbor
    • no link
    • missing port
    • port conflict (two installed cables in one port)
    • unverified: linked, but the far end is a jack or isn't identified
    • idle: a jack with nothing linked
  • Undocumented links: live switch ports that no cable mentions, with one-click add. Each switch port panel on a device page shows its planned cable, or a link to add one.
  • Labels to make: cable IDs (one per cord end) and panel-port labels that aren't applied yet, with a print view.
  • CSV: export, plus import that adds or updates cables by ID. An import with any invalid row changes nothing.
  • Search: cables, panels and jacks appear in the dashboard search.

Safety

  • Viewing needs a login. Editing needs the admin group and a JSON request body, so a plain cross-site form post can't reach it.
  • Every change is written to cutsheet_history, with the user and time, in the same transaction as the change.
  • Tables are created on first use, so no migration step is needed.
  • All rendered values are escaped, and CSV export guards against spreadsheet formula injection.
  • A cutsheet failure can't break device pages or search.

Testing

  • 35 logic tests: validation, multi-hop tracing, every verdict, suggestions, labels, and a CSV round trip. 12 API tests: permissions, the JSON requirement, 400/409/404 handling, all-or-nothing import, and device-page and search integration. The full suite (279 tests), flake8, bandit -ll and eslint pass.
  • The db.py cutsheet functions were run against a local MariaDB 10.11:
    • duplicate labels and names are refused, case-insensitively
    • renaming an item carries over to its cables
    • an item still in use can't be deleted
    • every change is logged
    • an import that fails partway rolls back completely
  • Checked in a local preview with seed data on desktop and at 390px wide. On narrow screens cables show as cards, and there's no horizontal scroll.

🤖 Generated with Claude Code

Part 1 of #12. Part 2 adds trace mode and mismatch tickets. A new **Cutsheet** page (`/cutsheet`) records where every cable is meant to go and compares it with what Gandalf discovers. ## Features - **Inventory:** things discovery can't see: patch panels, wall jacks, shelves, and rack positions for devices. Panels and jacks pass through from a port's front to its rear. - **Cables:** - Each cable has two endpoints. An endpoint is a device or item, a port, and a side for panels and jacks. - Fields: ID, type, length, color, status (`planned`, `installed`, `dark`, `retired`) and notes. - The far end can be left unterminated, for example a fibre that's run but not connected. - **Paths:** each cable shows its full run through panels and jacks. - **Plan vs. actual:** a run that ends on a switch port is checked against that port's discovered neighbor, from LLDP, the UniFi client table, or a DAC serial seen at both ends. The possible outcomes are: - matches - wrong neighbor - no link - missing port - port conflict (two installed cables in one port) - unverified: linked, but the far end is a jack or isn't identified - idle: a jack with nothing linked - **Undocumented links:** live switch ports that no cable mentions, with one-click add. Each switch port panel on a device page shows its planned cable, or a link to add one. - **Labels to make:** cable IDs (one per cord end) and panel-port labels that aren't applied yet, with a print view. - **CSV:** export, plus import that adds or updates cables by ID. An import with any invalid row changes nothing. - **Search:** cables, panels and jacks appear in the dashboard search. ## Safety - Viewing needs a login. Editing needs the `admin` group and a JSON request body, so a plain cross-site form post can't reach it. - Every change is written to `cutsheet_history`, with the user and time, in the same transaction as the change. - Tables are created on first use, so no migration step is needed. - All rendered values are escaped, and CSV export guards against spreadsheet formula injection. - A cutsheet failure can't break device pages or search. ## Testing - 35 logic tests: validation, multi-hop tracing, every verdict, suggestions, labels, and a CSV round trip. 12 API tests: permissions, the JSON requirement, 400/409/404 handling, all-or-nothing import, and device-page and search integration. The full suite (279 tests), `flake8`, `bandit -ll` and `eslint` pass. - The `db.py` cutsheet functions were run against a local MariaDB 10.11: - duplicate labels and names are refused, case-insensitively - renaming an item carries over to its cables - an item still in use can't be deleted - every change is logged - an import that fails partway rolls back completely - Checked in a local preview with seed data on desktop and at 390px wide. On narrow screens cables show as cards, and there's no horizontal scroll. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
jared added 1 commit 2026-11-10 16:05:26 -05:00
Add cutsheets: planned cabling checked against discovery
Lint / JS (eslint) (push) Successful in 7s
Lint / Python (flake8) (push) Successful in 1m36s
Lint / Notify on failure (push) Skipped
Lint / Deploy (push) Skipped
Security / Python Security (bandit) (push) Successful in 1m40s
Lint / Python (flake8) (pull_request) Successful in 52s
Test / Python Tests (pytest) (push) Successful in 1m4s
Lint / JS (eslint) (pull_request) Successful in 7s
Lint / Notify on failure (pull_request) Skipped
Lint / Deploy (pull_request) Skipped
Security / Python Security (bandit) (pull_request) Successful in 1m13s
Test / Python Tests (pytest) (pull_request) Successful in 1m15s
b9412ff6ba
A new Cutsheet page records where every cable is meant to go and compares
it with what Gandalf discovers.

- Inventory for what discovery can't see: patch panels, wall jacks,
  shelves and rack positions. Panels and jacks pass through front to rear.
- Cables with two endpoints, ID, type, length, color, status (planned,
  installed, dark, retired) and notes; the far end may be unterminated.
- Each cable's full run is traced through panels and jacks, and runs
  ending on a switch port are checked against its discovered neighbor
  (LLDP, client table, or a DAC serial seen at both ends): matches,
  wrong neighbor, no link, missing port, port conflict, unverified, idle.
- Undocumented links: live switch ports no cable mentions, one click to
  add with what Gandalf already knows. Port panels show the planned cable.
- Labels to make: cable IDs and panel-port labels not yet applied, with a
  print view, for a handheld label maker.
- CSV export, and all-or-nothing import that upserts by cable ID.
- Cables, panels and jacks appear in the dashboard search.

Editing needs the admin group and a JSON request body. Every change is
written to cutsheet_history in the same transaction. Tables are created
on first use.

Refs #12

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jared added 1 commit 2026-11-10 16:10:54 -05:00
Cutsheet: show the next free cable ID as a hint, not a placeholder
Lint / JS (eslint) (push) Successful in 7s
Lint / Python (flake8) (push) Successful in 48s
Lint / Notify on failure (push) Skipped
Lint / Deploy (push) Skipped
Lint / Python (flake8) (pull_request) Successful in 55s
Lint / JS (eslint) (pull_request) Successful in 14s
Lint / Notify on failure (pull_request) Skipped
Lint / Deploy (pull_request) Skipped
Security / Python Security (bandit) (pull_request) Successful in 1m45s
Security / Python Security (bandit) (push) Successful in 1m22s
Test / Python Tests (pytest) (pull_request) Successful in 1m6s
Test / Python Tests (pytest) (push) Successful in 1m0s
52cfea5439
A placeholder of C01 in the empty ID field looked like a real value.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Author
Owner

Screenshots

These come from a local preview with sample data: made-up hosts, plus a few cables seeded to show each check result. The images are hosted on the throwaway pr-18-screenshots branch, which can be deleted after review.

Cutsheet page: summary and cables

Problems sort to the top with a red edge: C03 is plugged into the wrong device, and C04 has no link. Runs through the patch panel show their full path. The DAC (D01) is confirmed by the serial number both ends report. The fibers are dark, and C05 is planned.

Cables

Undocumented links

Live switch ports that no cable mentions. Add opens the editor pre-filled with what Gandalf knows. The list shows 10 rows, with a "Show all" button.

Undocumented links

Patch panel

One row per port: the label (room) on it, the patch cord on the front, and the run on the rear.

Patch panel

Labels to make

Cable IDs (×2, one per cord end) and panel labels, to type on the label maker. Each is ticked off once it's stuck on. Print list prints only this section.

Labels to make

Rack

Rack

Cable editor

Panels and jacks show a front/rear choice. The hint offers the next free ID.

Cable editor

Port panel on a device page

The switch port panel shows its planned cable and the check result.

Port panel

Change history

History

Phone (390 px)

Cables become cards, and the page has no sideways scroll.

Cutsheet on a phone

While taking these I fixed one issue, pushed in 52cfea5: the empty Cable ID field had a placeholder of C01, which looked like a real value. It now says "Next free: C06" with a use button.

## Screenshots These come from a local preview with **sample data**: made-up hosts, plus a few cables seeded to show each check result. The images are hosted on the throwaway `pr-18-screenshots` branch, which can be deleted after review. ### Cutsheet page: summary and cables Problems sort to the top with a red edge: C03 is plugged into the wrong device, and C04 has no link. Runs through the patch panel show their full path. The DAC (D01) is confirmed by the serial number both ends report. The fibers are *dark*, and C05 is *planned*. ![Cables](https://code.lotusguild.org/LotusGuild/gandalf/raw/branch/pr-18-screenshots/1-cables.png) ### Undocumented links Live switch ports that no cable mentions. **Add** opens the editor pre-filled with what Gandalf knows. The list shows 10 rows, with a "Show all" button. ![Undocumented links](https://code.lotusguild.org/LotusGuild/gandalf/raw/branch/pr-18-screenshots/2-undocumented.png) ### Patch panel One row per port: the label (room) on it, the patch cord on the front, and the run on the rear. ![Patch panel](https://code.lotusguild.org/LotusGuild/gandalf/raw/branch/pr-18-screenshots/3-patch-panel.png) ### Labels to make Cable IDs (×2, one per cord end) and panel labels, to type on the label maker. Each is ticked off once it's stuck on. **Print list** prints only this section. ![Labels to make](https://code.lotusguild.org/LotusGuild/gandalf/raw/branch/pr-18-screenshots/4-labels.png) ### Rack ![Rack](https://code.lotusguild.org/LotusGuild/gandalf/raw/branch/pr-18-screenshots/5-rack.png) ### Cable editor Panels and jacks show a front/rear choice. The hint offers the next free ID. ![Cable editor](https://code.lotusguild.org/LotusGuild/gandalf/raw/branch/pr-18-screenshots/7-cable-editor.png) ### Port panel on a device page The switch port panel shows its planned cable and the check result. ![Port panel](https://code.lotusguild.org/LotusGuild/gandalf/raw/branch/pr-18-screenshots/8-port-panel.png) ### Change history ![History](https://code.lotusguild.org/LotusGuild/gandalf/raw/branch/pr-18-screenshots/6-history.png) ### Phone (390 px) Cables become cards, and the page has no sideways scroll. <img src="https://code.lotusguild.org/LotusGuild/gandalf/raw/branch/pr-18-screenshots/9-mobile.png" width="390" alt="Cutsheet on a phone"> While taking these I fixed one issue, pushed in 52cfea5: the empty Cable ID field had a placeholder of `C01`, which looked like a real value. It now says "Next free: C06" with a **use** button.
jared merged commit cd602fdacf into main 2026-11-10 16:50:21 -05:00
jared deleted branch cutsheets 2026-11-10 16:50:22 -05:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: LotusGuild/gandalf#18