When the JWT expired on an open tab, the next API request hit a 401 with
"Token has expired"; client.ts cleared the token from storage but
AuthContext.user stayed populated from the original mount. ProtectedRoute
only redirects when user === null, so the protected tree kept rendering
and every subsequent request silently failed with no Authorization
header — the UI looked like every list was empty until a manual refresh
remounted AuthProvider.
The 3 other setAuthToken(null) sites live inside AuthContext itself and
already pair with setUser(null), so only the client.ts cross-module
site needed a React-tree signal.
- client.ts: after setAuthToken(null) on a token-invalidating 401,
window.dispatchEvent(new CustomEvent('auth:expired')). Guarded on
`typeof window !== 'undefined'` for SSR / test safety. Generic
"Authentication required" 401s still don't clear the token or fire
the event — treated as transient timing issues per the pre-existing
comment at client.ts:155.
- AuthContext.tsx: mount useEffect adds a window listener that calls
setUser(null) under the mountedRef guard; cleanup removes the
listener so unmount → remount doesn't double-bind.
Mirrors the patch the reporter shipped on their fork (deec96d1).
Two new project fields: a free-text URL rendered as a one-click
external-link button beside the project name on every card (opens in a
new tab, click is e.stopPropagation()-guarded so it doesn't enter the
project), and a cover photo that replaces the status-icon box with a
square thumbnail.
URL is plumbed through ProjectCreate/Update/Response/ListResponse,
including from-template + create-template flows so it inherits between
a project and its template. Cover photo is not inherited because the
file would be shared on disk between source and copy.
Schema validator rejects anything other than http:// or https://
prefixes -- <a href> rendering would otherwise execute javascript:
/ data: / file: URLs even with React's default escaping. PATCH uses
model_fields_set for the URL field so users can clear it by sending
{"url": null}.
Cover image storage: Project.cover_image_filename references a file
Cover image storage: Project.cover_image_filename references a file
inside the existing archives/projects/{id}/attachments/ dir, but it's
tracked separately from the attachments JSON list so swap/delete on
the cover doesn't perturb the user's other attachments. Three routes
(POST/GET/DELETE /projects/{id}/cover-image) accept only .jpg/.jpeg/
.png/.gif/.webp (no SVG -- SVG can carry script payloads), replace in
place (prior file deleted before the new one lands so repeat uploads
can't accumulate orphans), and self-heal when a DB reference points at
a vanished disk file by clearing the column and 404'ing.
GET cover-image is gated by RequireCameraStreamTokenIfAuthEnabled
(accepts ?token=... query string) -- not the bearer-token gate -- so
<img src> requests work in both auth-on and auth-off configurations.
The frontend wraps getProjectCoverImageUrl with withStreamToken(),
matching the existing pattern from getArchiveThumbnail.
Permissions: PROJECTS_UPDATE for upload/delete/PATCH, PROJECTS_READ
gate is implicit via the stream-token credential. Migration: 2
idempotent ALTER TABLE projects ADD COLUMN. Localised across all 8
UI languages.
feat(oidc): add Azure Entra ID support with configurable email claim resolution
Adds two new OIDC provider fields: email_claim and require_email_verified.
Surface four Home Assistant-style controls on the Printers page card:
- SD Card badge in the top status row (green / red, icon-only).
- Enclosure Door badge in the top status row (green / yellow, icon-only).
Detection per printer family — X1/X1C/X1E read home_flag bit 23, all
others read top-level `stat` (hex string) bit 23 — so X1 firmware that
does not flip stat bit 23 stops false-triggering "open". WebSocket
status-change dedup key now includes door_open so toggling the door
alone publishes a push, no 30s REST-poll wait.
- Airduct Mode badge beside the speed control (cooling / heating)
for P2S/H2D/H2C/H2S; one-click dropdown calls the existing
set_airduct MQTT command via a new POST /printers/{id}/airduct-mode
route.
- Force Refresh entry in the kebab menu — calls the existing
/printers/{id}/refresh-status endpoint to request a pushall snapshot
without forcing a reconnect.
Tests: door-open parsing (X1 home_flag, non-X1 stat, ignore mismatched
source, invalid hex) and airduct route (validation, not-connected,
success, failure).
GitHub backup can now optionally include spool inventory (with usage
history) and print archive metadata as JSON. Both toggles are off by
default. No binary files (gcode/3MF) are included.
- Changed the 401 handling in request() to only clear the auth token when the error message indicates the token is actually invalid
- Previously: ANY 401 response cleared the token (could cause cascading failures)
- Now: Only clears on specific messages like "Token has expired", "Could not validate credentials", etc.
- "Authentication required" (which might be a timing issue) no longer clears the token
Closes#257
Introduces a new "Proxy Mode" for the Virtual Printer that enables
remote printing from anywhere in the world without VPN, port forwarding,
or Bambu Cloud dependency.
Bambuddy acts as a TLS relay between a remote slicer (Bambu Studio/
OrcaSlicer) and the local Bambu Lab printer:
Remote Slicer → Internet → Bambuddy Server → Local Network → Printer
The slicer connects to Bambuddy using the real printer's serial number
and access code. Bambuddy authenticates and relays all FTP (file transfer)
and MQTT (commands/status) traffic with end-to-end TLS encryption.
- No port forwarding required - printer stays safely on local network
- No VPN needed - connect from coffee shops, hotels, work, anywhere
- No Bambu Cloud dependency - fully self-hosted solution
- End-to-end TLS encryption on FTP (port 9990) and MQTT (port 8883)
- Works with Bambu Studio and OrcaSlicer
- Uses real printer credentials for authentication
- Automatic printer selection from connected printers
- Add SlicerProxyManager class for TLS relay (tcp_proxy.py)
- TLS termination with auto-generated certificates
- Concurrent FTP and MQTT proxy servers
- Connection lifecycle management with proper cleanup
- Extend VirtualPrinterManager with proxy mode support
- New 'proxy' mode alongside archive/review/queue modes
- Target printer selection and credential management
- Add proxy configuration endpoints to settings API
- Add permission checks for proxy endpoints
- Add Proxy Mode card to Virtual Printer settings
- Target printer dropdown for proxy destination
- Real-time proxy status display (ports, target, running state)
- Full i18n support (English, German)
- Add network architecture diagram
- Add proxy mode section to README
- Add comprehensive guide to wiki
- Add prominent feature section to website
- Backend unit tests for SlicerProxyManager
- Backend unit tests for proxy mode configuration
- Frontend tests for proxy mode UI components
Closes#207#170
Several frontend components were using raw fetch() instead of the API
client, causing 401 Unauthorized errors when authentication is enabled.
Changes:
- SpoolmanSettings: Use api.getSpoolmanSettings/updateSpoolmanSettings
- ProjectsPage: Use api.importProjectFile for ZIP imports
- ProjectDetailPage: Use api.exportProjectZip for exports
- CameraPage/EmbeddedCameraViewer: Use api.getCameraStatus
Added to API client:
- getSpoolmanSettings()
- updateSpoolmanSettings()
- importProjectFile()
- exportProjectZip()
- getCameraStatus()
Added tests for auth token handling in client.test.ts
Fixed VirtualPrinterSettings test expectations to match i18n strings
Closes#231
New feature to automatically backup K-profiles, cloud profiles, and app
settings to a GitHub repository with scheduled or on-demand execution.
Features:
- Configure GitHub repo URL and Personal Access Token
- Schedule backups hourly, daily, or weekly (background scheduler)
- Manual backup trigger with real-time progress tracking
- Skip unchanged commits (only creates commit when data changes)
- Backup history log with status and commit links
- Requires Bambu Cloud login for full profile access
- New Settings → Backup & Restore tab consolidating all backup options
- GitHub backup config included in local backup/restore (except PAT)
Backend:
- New models: GitHubBackupConfig, GitHubBackupLog
- New service: GitHubBackupService with scheduler and GitHub API client
- New routes: /github-backup/* for config, status, logs, and triggers
- Updated settings.py to include github_backup in backup/restore
Frontend:
- New GitHubBackupSettings.tsx component with auto-save
- Updated SettingsPage with Backup tab and status indicator
- Added API types and methods to client.ts
Tests:
- Backend integration tests for all GitHub backup API endpoints
- Frontend API type and endpoint tests