mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-05-09 00:08:34 +02:00
[PR #1114] feat(inventory): unified Spoolman inventory UI + AMS slot assignments + Storage Location + NFC write support + Spoolman Filament Catalog Picker #1169
Labels
No labels
A1
automated
automated
bug
bug
Closed due to inactivity
contrib
dependencies
dependencies
duplicate
enhancement
feedback
hold
invalid
Notes
P1S
pull-request
security
ThumbsUp
user-report
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
starred/bambuddy-maziggy-1#1169
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
📋 Pull Request Information
Original PR: https://github.com/maziggy/bambuddy/pull/1114
Author: @netscout2001
Created: 4/24/2026
Status: 🔄 Open
Base:
dev← Head:feature/spoolman-inventory-ui📝 Commits (10+)
4257c1afeat(inventory): unified Spoolman inventory UI + Storage Location + AMS deep-link + SpoolBuddy NFC write support (#1063)1c7406dfix(spoolman): allow LAN Spoolman in SSRF guard46fcf2bfix(spoolbuddy): use INVENTORY_UPDATE permission for system command endpointb06e745fix(spoolbuddy): use INVENTORY_UPDATE permission for daemon update endpointc0a3c63fix(auth): allow API keys to read settings (SETTINGS_READ)05d0306fix(spoolbuddy): allow negative scale readings for uncalibrated tare8e45425fix(spoolbuddy): remove weight bounds from ScaleReadingRequest8c5ce21test(auth): update RBAC denylist test to reflect SETTINGS_READ exemption111ab19fix(spoolman): harden slot-assignment CRUD, sync robustness, and full test coverageb4c00ddfix(spoolman): harden integration — typed exceptions, SSRF, TOCTOU, cleanup, and test coverage📊 Changes
114 files changed (+24033 additions, -2192 deletions)
View changed files
📝
CHANGELOG.md(+15 -2)➕
backend/app/api/routes/_spoolman_helpers.py(+318 -0)📝
backend/app/api/routes/inventory.py(+51 -16)📝
backend/app/api/routes/printers.py(+337 -64)📝
backend/app/api/routes/settings.py(+93 -79)📝
backend/app/api/routes/spoolbuddy.py(+433 -88)📝
backend/app/api/routes/spoolman.py(+413 -109)➕
backend/app/api/routes/spoolman_inventory.py(+1541 -0)📝
backend/app/core/auth.py(+51 -2)📝
backend/app/core/database.py(+146 -37)📝
backend/app/main.py(+169 -22)📝
backend/app/models/spool.py(+3 -1)📝
backend/app/models/spoolbuddy_device.py(+1 -0)➕
backend/app/models/spoolman_k_profile.py(+34 -0)➕
backend/app/models/spoolman_slot_assignment.py(+35 -0)📝
backend/app/schemas/spoolbuddy.py(+48 -32)➕
backend/app/schemas/spoolman.py(+30 -0)📝
backend/app/services/opentag3d.py(+57 -23)📝
backend/app/services/spool_tag_matcher.py(+22 -0)📝
backend/app/services/spoolbuddy_ssh.py(+89 -22)...and 80 more files
📄 Description
Description
This PR integrates Spoolman more deeply into Bambuddy so users never have to leave the app to manage their filament, regardless of which inventory backend is active.
Unified Inventory UI for Spoolman mode
When Spoolman is enabled, the Bambuddy inventory page proxies all spool data through a new backend API (
/spoolman/inventory/*). Create, edit, archive, delete and bulk-create spools — all from the same table/card UI as the internal inventory. The frontend is fully inventory-backend-agnostic.AMS Slot Assignments (Spoolman)
A new
spoolman_slot_assignmentstable maps(printer, ams_id, tray_id) → spoolman_spool_idlocally, without touching Spoolman'sspool.locationfield (which remains user-managed). A full CRUD API (POST/DELETE/GET /spoolman/inventory/slot-assignments) handles assignment, unassignment, and lookup. The sync routes use the slot table as a no-RFID fallback (spoolman_spool_id_hint): if a tray has no readable RFID tag the previously assigned spool ID is used to keep weight data flowing. Trays with neither RFID nor a hint produce aSkippedSpoolentry in the sync response instead of silently returning null. When a printer is deleted, all its slot assignments are explicitly removed (SQLite does not enforce FK cascades withoutPRAGMA foreign_keys).Storage Location field
A new "Storage Location" text field on every spool (e.g. "Shelf A, Box 3"). In internal inventory mode it is stored locally. In Spoolman mode it maps bidirectionally to Spoolman's native
locationfield — reads on load, writes back on save, and can be explicitly cleared."Open in Inventory" deep-link from AMS slot hover card
The hover card shown when hovering over an AMS slot now contains an "Open in Inventory" button (for both Spoolman-linked and internally-assigned spools). Clicking navigates to
/inventory?spool=<id>, which immediately opens the edit modal for that exact spool. Falls back to a targeted single-spool fetch if the spool list is not yet cached.SpoolBuddy NFC write support for Spoolman spools (bonus — not part of the original FR)
SpoolBuddy devices can now write OpenTag3D NDEF tags for Spoolman-managed spools, not just local DB spools. The
nfc/write-tagendpoint falls back to Spoolman when a spool is not found locally and encodes the tag via a newencode_opentag3d_from_mapped()path. After a successful write,nfc/write-resultstores the tag UID back into Spoolman'sextra.tagfield usingmerge_spool_extrato preserve all other custom fields. Spoolman spools are also fully discoverable via NFC scan (nfc/tag-scanned) and weight sync (scale/update-spool-weight) uses Spoolman's ownfilament.spool_weightas core weight instead of a hardcoded 250 g fallback.Bug Fixes
SpoolBuddy kiosk buttons broken after permission hardening
The new
_APIKEY_DENIED_PERMISSIONSdenylist inadvertently blocked three operations for API-key-authenticated sessions:queue_system_command) requiredSETTINGS_UPDATE→ changed toINVENTORY_UPDATEtrigger_daemon_update) requiredSETTINGS_UPDATE→ changed toINVENTORY_UPDATEGET /settings) requiredSETTINGS_READ→SETTINGS_READremoved from the denylistScale reading 422 errors on uncalibrated devices
ScaleReadingRequest.weight_gramshad an upper bound of100 000.0. The NAU7802 24-bit ADC with defaultcalibration_factor=1.0produces raw values in the millions before calibration, causing every scale reading to be rejected with HTTP 422. Bounds removed.Sync robustness (priority review matrix)
exceptblocks andlink_spool/unlink_spoolDB writes (C1/C2/C3/H6)debugtowarninglevel (H1, 3 sites)assign_spoolman_slot: spool verified in Spoolman before the local DB row is written — prevents ghost rows pointing at non-existent spool IDs (H2)get_spoolman_slot_assignment: non-404 Spoolman errors are now re-raised instead of silently returningnull— a 503 from Spoolman surfaces as 503 to the caller (H3)unassign_spoolman_slot: catches 404 from Spoolman after a successful local delete and returns 200 — the local unassignment succeeded regardless of whether Spoolman still has the spool (H4)sync_ams_traynon-BL RFID path:find_or_create_filamentwrapped in try/except so a Spoolman timeout does not abort the whole sync cycle (H5)on_ams_change:isinstanceguards onams_unit/tray_databefore attribute access (CR4);ValueErrorfrominit_spoolman_clientcaught so an SSRF-blocked URL logs a warning and exits cleanly instead of propagating an unhandled exception (SSRF)sync_all_printers: missingelif spool_tagbranch added so not-found errors are reported for RFID-tagged trays too, not just no-RFID trays (asymmetry fix)DB DDL dialect fix
active_print_spoolmanandspool_usage_historymigration blocks previously usedINTEGER PRIMARY KEY AUTOINCREMENT(SQLite-only syntax) unconditionally. Both now branch onis_sqlite(), matching the pattern used bysmart_plug_energy_snapshots.Related Issue
Fixes https://github.com/maziggy/bambuddy/issues/1038
Documentation
Type of Change
Changes Made
Backend
models/spoolman_slot_assignment.py— new ORM model,(printer_id, ams_id, tray_id)unique constraint, FKON DELETE CASCADEcore/database.py— migration block forspoolman_slot_assignmentstable; DDL dialect fix foractive_print_spoolmanandspool_usage_historyapi/routes/spoolman_inventory.py— full slot-assignment CRUD (assign,unassign,get,get_all); ghost-row prevention, stale-row cleanup, 503 propagationapi/routes/spoolman.py—sync_printer_ams/sync_all_printers: slot-map loading, hint forwarding,SkippedSpoolentries, DB rollbacks, asymmetry fix;link_spool/unlink_spoolDB rollbacksapi/routes/printers.py— explicitSpoolmanSlotAssignmentdelete indelete_printerapp/main.py—on_ams_change: slot-map loading + hint,isinstanceguards, SSRFValueErrorcatch, DB rollbackservices/spoolman.py—sync_ams_tray: non-BL RFIDfind_or_create_filamentguard, hint uncachedget_spoolcall; updatedclear_location_for_removed_spoolsdocstringapi/routes/_spoolman_helpers.py—_map_spoolman_spool(), SSRF guard, helper utilitiesservices/opentag3d.py—encode_opentag3d_from_mapped()for Spoolman dict-based NDEF encodingapi/routes/spoolbuddy.py— Spoolman-aware NFC scan/write/result, weight sync; permission fixes for kiosk buttonsschemas/spoolbuddy.py— unboundedweight_gramscore/auth.py—SETTINGS_READremoved from API-key denylistFrontend
api/client.ts— slot-assignment API methods (assignSpoolmanSlot,unassignSpoolmanSlot,getSpoolmanSlotAssignment,getSpoolmanSlotAssignments)components/AssignSpoolModal.tsx— Spoolman spool picker for AMS slot assignmentcomponents/LinkSpoolModal.tsx— Spoolman link flowpages/PrintersPage.tsx— AMS slot assignment UI, "Open in Inventory" deep-linken,de,fr,it,ja,pt-BR,zh-CN,zh-TW)Tests (T1–T9 + 3 bonus)
link_spool→ Spoolman 404/503 returns correct HTTP statusunlink_spool→ Spoolman 404 returns correct HTTP statusget_spoolman_slot_assignment→ stale row cleaned up on Spoolman 404spoolman_spool_id_hintwhen no RFIDfind_or_create_filamenterror →None, not raisesget_spool(hint)calledDELETE /printers/{id}removes all slot assignmentsTesting
Backend: 3586 passed, 30 pre-existing failures (camera/timelapse hardware mocks — not introduced by this PR)
Frontend: 1447/1447 passed
Bandit security scan: 0 Medium/High findings
I have tested this on my local machine
Checklist
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.