Archives, the queue and statistics report ownership as a numeric
created_by_id, and statistics accept it as a filter, but nothing let an
API key discover whose id was whose -- the only user listing returns
emails, roles, group membership and full permission sets, so it is
administrative and rejects keys.
Add GET /users/slim returning id + username only, gated on a new
users:read_slim permission mapped to can_read_status. That grants no
data a key could not already reach: for API-keyed requests the
permission deps return None as current_user, so the stats:filter_by_user
guard short-circuits and ?created_by_id=N is already honoured for every
N. What was missing was the ability to address the filter, not
permission to use it. The full listing stays unmapped = admin-only.
Also fix /auth/me, which answered an API key with a synthetic
administrator: id 0, role admin, is_admin true and every permission in
the enum. A key cannot reach an administrative route at all, so clients
building their UI from that response rendered actions that 403 on use.
It now reports the key owner's identity, is_admin false, and the
permissions the key's scopes actually admit. Ownerless legacy keys keep
id 0 but no longer claim admin.
---
Source user names from the slim listing where only names are needed (#1894)
Stats filter-by-user, the Archives print log filter, the File Manager
username autocomplete, the camera-token owner column and the Finance
member picker all render nothing but a username, but all of them read
the full user listing, which is gated on the admin-level users:read.
An operator granted stats:filter_by_user but not users:read got an
empty filter with no indication why.
Point them at /users/slim under a separate react-query key, since the
full listing shares the 'users' key and the two shapes would clobber
each other in the cache.
PrintQueueItem.created_by_id is what the queue:read_own / queue:update_own /
queue:delete_own permissions filter on, but only three of the paths that create
queue items were setting it.
The Library's bulk "Add to queue" required Permission.QUEUE_CREATE and then
bound the dependency to `_`, discarding the user, so every item it created was
ownerless -- and invisible to the person who added it if their permissions are
scoped to their own work. That is the one path built for adding many files at
once, which is where it was hardest to notice.
The webhook queue endpoint has no request user, but APIKey.user_id records the
key's owner, which is the acting identity everywhere else the key is used, so
its items are credited to that owner. Keys minted before per-user ownership
have no user_id and their items stay ownerless.
The virtual-printer path is left as-is on purpose. VirtualPrinter carries no
owner, and the obvious substitute is wrong rather than incomplete: one admin
typically configures the VP while everyone sends prints through it, so
crediting those to the admin would make the "added by" column lie and put other
people's jobs in the admin's own queue. Existing NULL rows are not backfilled
-- there is no record of who created them, and the ownerless case is already
handled throughout.
Tests pin both fixed paths and the two cases that must stay ownerless (auth
disabled, legacy key).
webhook.py treated printer_manager.get_status() return as a dict and
called .get(...) on it. The return is a PrinterState dataclass
(backend/app/services/bambu_mqtt.py), so the call raised AttributeError
and Starlette surfaced it as a generic 500 for every printer with a
status row. Non-existent printers correctly returned 404 because the
early "Printer not found" branch fired before the crash.
Reporter's repro matched exactly: id 1 (existing printer) returned 500,
id 2 and id 3 (no row) returned 404. Verified end-to-end against a live
PG-backed instance with the reporter's key shape — same 500 before the
patch, 200 with the correct payload after.
8 crash sites across 3 routes:
- webhook_get_printer_status GET /printer/{id}/status 5 sites
- webhook_stop_print POST /printer/{id}/stop 2 sites
- webhook_cancel_print POST /printer/{id}/cancel 2 sites
Every status.get("X", default) replaced with status.X if status else
default. Pydantic response schema unchanged; PrinterState's dataclass
defaults cleanly cover the "registered but never connected" branch so
the status route now returns 200 with connected=false, state=null
rather than crashing.
Reporter sliced in OrcaSlicer with timelapse on, sent the job to a VP
queue, started from the queue, and got no timelapse video. Their
dispatch chain itself was correct (queue item -> scheduler -> MQTT
command honors `timelapse`); the gap was at queue-add time.
The VP's `_add_to_print_queue` reads `default_timelapse` (and the four
other print-option settings) from the workflow settings card. That was
introduced in #1235 to stop column-level defaults from winning. But it
also discarded the slicer's actual choice carried on the MQTT
`project_file` command, which all the slicers (Studio / Handy / Orca)
ship as `timelapse: true|1`. Result: a user with the new-install value
`default_timelapse=false` had to either flip the global setting or
edit every queue item by hand, even though their slicer's "Print
options" UI clearly said "record timelapse".
Investigation went wider than #1403 because Martin's hypothesis was
"the print options modal isn't respected either." Cross-checking
86 captured P1S `project_file` commands across the support packages
shows 46 from the queue scheduler and 33 from background_dispatch
emitting `"timelapse": true` correctly to real printers - the modal +
re-print path is intact end-to-end. The slicer-side gap was the only
real bug. Two unrelated dead-code issues turned up in the same dig and
are folded in below.
Fix (VP queue inheritance)
- `on_print_command` in the VP manager now stashes the slicer's
project_file dict keyed by filename, then signals an asyncio.Event.
- `_add_to_print_queue` checks the dict first; if empty, creates the
event and waits up to 2 s for it before reading the settings
fallback. Each option flows through per-field - slicer value wins
if present, else the existing settings default (so users who
explicitly set `default_timelapse=true` in their VP workflow card
still get that on slicers that don't send a print command).
- MQTT field naming preserved exactly: `bed_leveling` (single L) on
the wire stays mapped to `bed_levelling` (double L) on the Bambuddy
column. Integer 0/1 from H-family slicers and bool true/false from
P1/X1 slicers both coerce via `bool()`.
- Capture is gated on `mode == "print_queue"` so immediate / review /
proxy modes keep their pre-fix no-op `on_print_command` and don't
accumulate stashed entries over the VP's uptime.
- Wait is also skipped when there's no MQTT server attached
(`self._mqtt is None`), so unit tests that invoke
`_add_to_print_queue` directly don't pay the 2 s tax.
- Capture is consumed on use so the dict stays bounded.
- `printer_manager.get_status(...).get(...)` against a `PrinterState`
dataclass that has no `.get()` method.
- Every print option discarded (timelapse, bed_levelling, AMS mapping).
The route 500'd before ever reaching the printer. Rewritten to mirror
`POST /print-queue/{item_id}/start`: clear `manual_start=False` on the
next pending queue item and let the scheduler dispatch with the
queue's stored options intact. Response shape preserved.
Side-bug b: vibration_cali default drift in background_dispatch
- `ReprintRequest.vibration_cali` and `FilePrintRequest.vibration_cali`
both default to `True` (matches Bambu Studio behavior for X1/P1).
- Both `_process_job` call sites read
`job.options.get("vibration_cali", False)`.
Cosmetic today because the frontend always sends the field, but a
latent landmine for any future caller that bypasses the schema. Both
sites flipped to `True`.
An API key with printer_ids=[] was treated the same as null (global
access) due to a falsy check. Now None means global access and []
means no printer access. Added a startup migration to normalize any
existing [] rows to NULL so they retain their intended global access.
Also fixed the webhook /queue endpoint which used the same falsy
check, allowing []-scoped keys to see all printers.
- New APIBrowser component with full OpenAPI schema integration
- Fetches and parses /openapi.json automatically
- Groups endpoints by API tags (printers, archives, settings, etc.)
- Expandable endpoint sections with color-coded method badges
- Path parameter, query parameter, and JSON body editors
- Auto-populates request body with schema examples
- Live API request execution with response display
- Response shows status code, timing, and formatted JSON
- Copy response button with clipboard fallback
- Search to filter endpoints across all categories
- Expand All / Collapse All buttons
- Link to Swagger UI (/docs)
- Two-column layout for API Keys tab
- Left: API key management + webhook documentation
- Right: API Browser with dedicated test key input
- Parameter validation
- Shows warning for missing required parameters
- Validates before sending requests to avoid 422 errors
- UX improvements
- "Use in API Browser" button on newly created keys
- Responsive layout (stacked on mobile, side-by-side on xl+)
### Projects / Print Grouping
- Create projects to group related prints (e.g., "Voron Build" with 50 parts)
- Track progress with target count and completion percentage
- Assign archives to projects via edit modal or context menu
- Project cards show archive thumbnails with clickable links
- Color-coded project badges on archive cards
- Filter and manage projects by status (active/completed/archived)
### Full-Text Search (FTS5)
- SQLite FTS5 virtual table for efficient searching
- Search across print_name, filename, tags, notes, designer, filament_type
- Automatic index sync with triggers for INSERT/UPDATE/DELETE
### Webhooks & API Keys
- API key authentication with granular permissions
- Permissions: can_read_status, can_manage_queue, can_control_printer
- Secure key generation with prefix display only after creation
- Settings page API Keys tab for key management
- Webhook endpoints for external integrations
### Failure Analysis
- Dashboard widget showing failure rate with color coding
- Correlate failures with conditions (filament type, printer, time)
- Top failure reasons breakdown
- Weekly trend visualization
### Archive Comparison
- Select 2-5 archives to compare side-by-side
- Highlight differences in print settings (yellow)
- Success/failure correlation insights
- Modal with close via button, X, Escape, or backdrop
### CSV/Excel Export
- Export archives and statistics with current filters
- Support for both CSV and Excel (.xlsx) formats
- openpyxl dependency added
## Bug Fixes
- Fixed context menu submenu not showing (removed overflow-hidden)
- Fixed project card thumbnails using correct API endpoint
- Fixed EditArchiveModal to invalidate projects query on save
- Fixed clipboard API fallback for HTTP contexts
- Fixed archive PATCH 500 error (FTS5 index rebuild)
- Fixed FastAPI trailing slash routing for projects endpoint
## UI Improvements
- Context menu submenu with hover/click support
- Project badge on archive cards with project color
- "Go to Project" context menu item for assigned archives
- Clickable project card thumbnails linking to archives
- Reset Layout button moved to Stats page header