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.
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)
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>
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.
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.
Patch panel
One row per port: the label (room) on it, the patch cord on the front, and the run on the rear.
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.
Rack
Cable editor
Panels and jacks show a front/rear choice. The hint offers the next free ID.
Port panel on a device page
The switch port panel shows its planned cable and the check result.
Change history
Phone (390 px)
Cables become cards, and the page has no sideways scroll.
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*.

### 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.

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

### 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.

### Rack

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

### Port panel on a device page
The switch port panel shows its planned cable and the check result.

### Change history

### 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 main2026-11-10 16:50:21 -05:00
jared
deleted branch cutsheets2026-11-10 16:50:22 -05:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
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
planned,installed,dark,retired) and notes.Safety
admingroup and a JSON request body, so a plain cross-site form post can't reach it.cutsheet_history, with the user and time, in the same transaction as the change.Testing
flake8,bandit -llandeslintpass.db.pycutsheet functions were run against a local MariaDB 10.11:🤖 Generated with Claude Code
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-screenshotsbranch, 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.
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.
Patch panel
One row per port: the label (room) on it, the patch cord on the front, and the run on the rear.
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.
Rack
Cable editor
Panels and jacks show a front/rear choice. The hint offers the next free ID.
Port panel on a device page
The switch port panel shows its planned cable and the check result.
Change history
Phone (390 px)
Cables become cards, and the page has no sideways scroll.
While taking these I fixed one issue, pushed in
52cfea5: the empty Cable ID field had a placeholder ofC01, which looked like a real value. It now says "Next free: C06" with a use button.