Commit Graph
2144 Commits
Author SHA1 Message Date
maziggy 62950e37c8 fix(camera): restore new-window camera view with auth enabled
Two root causes in the "Camera View Mode = Window" path when auth is on (#979):

  1. PrintersPage opened the popup with `noopener`, which severed the opener
     link and prevented the browser from copying sessionStorage (auth token)
     into the new window. The popup booted unauthenticated, POST
     /printers/camera/stream-token returned 401, and the <img> src went out
     with no ?token=. The backend's RequireCameraStreamTokenIfAuthEnabled
     then rejected every frame with "Valid camera stream token required".
  2. CameraPage computed its stream URL from the module-level stream-token
     cache in withStreamToken(). That cache is populated by a useEffect in
     useStreamTokenSync that runs after render, so even after the token
     resolved the first post-arrival render still produced a tokenless URL
     and nothing triggered another render.

  Fix:
  - Drop `noopener` from the camera popup features (same-origin, trusted).
  - Subscribe CameraPage to the `camera-stream-token` React Query so the
    page re-renders the moment the token arrives.
  - Gate currentUrl on `waitingForStreamToken` and append the token directly
    from the reactive query value instead of the effect-synced module cache.

  Embedded overlay mode was unaffected. Added CameraPage tests covering both
  the auth-enabled (token required, src empty until it arrives, then includes
  ?token=) and auth-disabled (src rendered immediately without token) paths.
2026-04-15 08:06:27 +02:00
maziggy 1b43488016 fix(printers): recover large-3mf metadata after FTP timeout (#972)
Two-part root cause for missing photos/filament/cost on large prints
  (#972). The configured ftp_timeout was only plumbed through as the FTP
  socket timeout; the asyncio.wait_for wrapping run_in_executor stayed on
  its 60s hardcoded default, so the user's 300s setting never applied.
  Worse, asyncio.wait_for cannot cancel run_in_executor threads — after
  the 60s outer timeout fired, the executor thread kept running
  ftplib.retrbinary and frequently completed the download ~30–60s later,
  but by then the async wrapper had returned False. with_ftp_retry kept
  re-attempting the same path, each retry truncating the file the zombie
  thread had just written, and the archive was ultimately persisted as a
  fallback with no 3MF.

  download_file_async now accepts timeout at each call site (plumbed from
  ftp_timeout) and salvages post-timeout success via an explicit
  completion flag the executor thread sets only after download_to_file
  returns True. Per-attempt completion dict so a prot_p zombie can't
  flip the flag for a later prot_c attempt. A cosmetic // prefix in the
  directory-search download path is also fixed by replacing string
  concatenation with posixpath.join.
2026-04-15 07:52:28 +02:00
maziggy 899c2c6480 revert(printers): remove SD card badge entirely
Four attempts at making the printer-card SD badge stable on H2D all failed:
  the final straw was powering on an A1 causing every connected H2D to flip to
  red simultaneously. Bambu firmware SD signaling is not reliably derivable
  from MQTT — the legacy `sdcard` field is sporadic and inconsistently typed,
  and home_flag bits 8-9 are cleared on heartbeat pushes regardless of card
  state with no clean way to distinguish heartbeats from full status reports.

  Remove the badge from the Printers page card and the Printer Info modal,
  drop `sdcard` from the frontend PrinterStatus type, and strip all home_flag
  derivation and heartbeat-handling code from the MQTT parser.

  `state.sdcard` is retained on the backend and populated only from a plain
  truthy read of the `sdcard` field, because firmware_update.py uses it as a
  precondition before starting firmware installs.
2026-04-14 18:41:46 +02:00
maziggy 0d7c0d4054 fix(printers): stop H2D SD badge from flipping red on heartbeat bursts
Third follow-up on the H2D SD card badge. The prior 3-strike downgrade still
  lost the race: on idle printers, a nearby printer coming online (e.g. an A1
  reconnecting) triggered an MQTT activity burst that let idle H2Ds accumulate
  ≥3 heartbeat home_flag pushes before the next full push_status, flipping every
  H2D badge to red at once.

  Reworked the derivation:
    - the top-level `sdcard` field is authoritative when present (truthy check
      handles bool / int / "HAS_SDCARD_NORMAL" string variants)
    - home_flag bits 8-9 are only consulted on full push_status payloads
      (detected via ≥2 of gcode_state, mc_percent, nozzle_temper, print_type,
      stg_cur, ams)
    - bare heartbeat pushes carrying home_flag alone no longer affect SD state

  Removed the now-dead `_home_flag_seen` latch and 3-strike counter. Tests in
  TestSdCardParsing rewritten to cover the new semantics.
2026-04-14 18:25:36 +02:00
maziggy 665419dd22 @renovate pillow 12.1.1 → 12.2.0: patch release, security fix only (CVE-2026-40192)
@renovate pytest 9.0.2 → 9.0.3: patch release (CVE-2025-71176)
2026-04-14 12:42:46 +02:00
maziggy dd349954a6 fix(printers): stop H2D SD-card badge flipping red on heartbeat pushes
H2D sends heartbeat-style home_flag pushes where bits 8-9 are clear
  even when a card is inserted, so a single heartbeat flipped the badge
  to red until the next full push. Downgrades true->false now require
  three consecutive clear reads; upgrades apply immediately.
2026-04-14 12:32:37 +02:00
maziggy b5ccc38e4a feat(support): include all settings (redacted) + SpoolBuddy devices in support bundle
Settings dump now retains every key from the Settings table and replaces
  sensitive values with [REDACTED] instead of dropping the row. New config
  flags automatically surface in future bundles without a code change.

  Adds integrations.spoolbuddy with per-device firmware, NFC/scale hardware,
  calibration, online state and uptime — anonymized (no hostnames, IPs or
  device IDs). Both /support/bundle and the bug-report bubble benefit, since
  they share _collect_support_info().
2026-04-14 12:22:07 +02:00
maziggy 44bb179364 feat: build-plate Z-jog control from printer card (#791)
Adds a compact "Bed" badge in the printer-card controls row
  between print speed and Stop/Pause. Opens a popover with up/down
  arrows and a 1 / 10 / 50 mm step selector.

  When the Z axis has not been homed since the last print, the
  first jog per session opens a Bambu Studio-style modal with
  Home Z / Move anyway / Cancel. "Move anyway" bypasses soft
  endstops (M211 S0 ... M211 S1) for a single move and is
  remembered for the browser session.

  Backend:
  - POST /printers/{id}/bed-jog?distance=N[&force=bool]
    Emits G91 / G1 ZN F600 / G90 (with optional M211 wrap).
    Distance validated server-side (non-zero, |N| <= 200 mm).
  - POST /printers/{id}/home-axes?axes=z|xy|all
    Emits G28 variants.
  Both gated behind Permission.PRINTERS_CONTROL.

  Frontend:
  - New indigo-themed badge + popover in PrintersPage.
  - Not-homed confirmation modal with sessionStorage "warned" flag.
  - i18n keys under printers.bedJog.* in all 7 locales.

  Tests:
  - backend/tests/unit/test_bed_jog.py — 13 tests covering
    404 / 400 / 500 / success paths for both endpoints, plus
    gcode-payload assertions for force on/off.

  Docs:
  - README feature list, CHANGELOG (0.2.3b4 Unreleased),
    printer-control wiki page, website features.html.
2026-04-14 12:05:06 +02:00
maziggy 0198226db1 Root cause: the Obico detection service handed the ML API a bare /camera/snapshot URL, and Bambuddy's auth returned 401 on that unauthenticated GET. The ML API then
surfaced it as "Failed to get image", which Bambuddy reported back as a 400.

  Fix: the snapshot endpoint already accepts a reusable camera-stream token (the same mechanism used by <img>-based camera consumers, since browsers can't send auth headers
  on image loads). The detection service now appends that token to the URL it gives the ML API. The token is cached on the service, refreshed 5 min before its 60-min expiry,
   and is simply ignored when Bambuddy auth is disabled — so no behavior change for users without auth.

  Refs #172
2026-04-14 11:36:46 +02:00
maziggy f03d0c4cf7 fix: attribute direct library prints to the authenticated user
The library POST /files/{file_id}/print endpoint discarded the
  authenticated user and passed requested_by_user_id=None to the
  dispatcher, so archives created from direct prints had no
  created_by_id and didn't show up in per-user statistics. Queue
  and reprint paths already forwarded the user correctly.

  Bind the auth dependency to current_user and pass its id/username
  through to dispatch_print_library_file, matching the reprint
  endpoint. The dispatcher already propagates this to
  printer_manager.set_current_print_user, which the archive
  creation reads.
2026-04-14 11:16:26 +02:00
maziggy d74ab06072 feat(firmware): list all announced versions with usable/unavailable status, support rollback
Firmware update modal now shows every version from Bambu's wiki release
  history, each badged Usable/Unavailable/Installed. Selecting a usable row
  — newer or older than current — swaps the release notes and enables
  install for that version, so rollback no longer requires hand-flashing.

  Wiki scraper tightened to only read heading-anchor ids (h-XXXXXXXX-YYYYMMDD)
  instead of any XX.XX.XX.XX substring, eliminating false positives like an
  AMS firmware version mentioned in an H2D changelog being listed as H2D
  firmware.

  Refs #568
2026-04-14 11:10:24 +02:00
maziggy 2fa3998508 Post work PR #973 2026-04-14 10:33:44 +02:00
Sn0rrii eba5a2924a feat(frontend): add auto-link existing accounts toggle to OIDC provider settings (#973)
Exposes the backend auto_link_existing_accounts field in the OIDC provider
form, edit view, and info display. Adds translations for all 7 supported
locales (en, de, fr, it, pt-BR, zh-CN, ja).
2026-04-14 10:29:37 +02:00
maziggy 63ce436e7f fix: make Add/Edit Printer modal scrollable on short viewports
On short viewports the modal exceeded the screen height with no scroll,
  hiding fields like Access Code and Save. Overlay now scrolls and the
  card caps at calc(100vh-2rem) with internal overflow.
2026-04-14 10:11:45 +02:00
maziggy 05ecceda75 Added Spoolbuddy device control buttons to settings card 2026-04-14 09:48:23 +02:00
maziggy 4a5b608a81 fix: surface AMS dry_sf_reason and backfill filament (#971)
Start Drying was publishing a valid MQTT command that the firmware
  silently refused. The per-AMS dry_sf_reason array was parsed but never
  consulted before publish, so users with the AMS 2 Pro external PSU
  unplugged (code 8) saw nothing happen. The empty filament field in our
  payload was also a refusal trigger on some firmwares.

  The /drying/start route now inspects dry_sf_reason and returns a 409
  with a specific message, and backfills filament from the first loaded
  tray (default PLA) so the printer can't reject the command for a
  missing field.
2026-04-14 09:27:00 +02:00
maziggy b71b721658 fix(security): stop leaking webhook tokens via httpx debug logging
Settings → Support → Debug Logging elevated httpx/httpcore to DEBUG,
  which makes httpx log every outbound request URL. For Discord and
  generic webhook notifications the bearer token is embedded in the URL
  path, so users who turned on debug logging to capture a support bundle
  were writing their webhook tokens straight into bambuddy.log.

  Pin httpx/httpcore to WARNING regardless of the debug toggle. paho.mqtt
  still honours debug. Users who enabled debug logging while notifications
  were sending must rotate any exposed Discord/webhook URLs — the token
  is the path, so the whole URL has to be regenerated in the provider UI.
2026-04-14 09:13:55 +02:00
maziggy 3762022444 fix(scheduler): revert stuck queue item when printer ignores start command
If the printer drops or ignores the MQTT project_file command (same
  half-broken-session shape as #887/#936), the queue item was permanently
  stuck in "printing" at 100% because the scheduler optimistically flipped
  the DB row right after the publish succeeded locally. A new watchdog
  polls the printer state for up to 45s after dispatch; if there's no
  transition, it reverts the item to "pending" and force-reconnects the
  MQTT session so the scheduler can retry.
2026-04-14 09:08:15 +02:00
maziggy 55cd050635 ● fix(mqtt): stop SD card badge flap from partial pushes
Follow-up to the earlier H2D SD card badge fix. The badge was still
  flapping because Bambu firmwares send partial MQTT pushes carrying only
  the legacy `sdcard` field (without home_flag), and the fallback path
  re-engaged on every such push. Latch home_flag as the canonical source
  once seen; reset the latch on reconnect so a firmware change still
  re-learns.
2026-04-14 08:45:28 +02:00
maziggy 4a2b9bc5d4 fix(mqtt): self-heal half-broken session on dispatch timeout (#936)
When a project_file command was unacknowledged for 15s, Bambuddy
  previously logged "printer may need restart" and left the broken MQTT
  session in place — requiring the user to power-cycle the printer. The
  existing half-broken-session recovery only ran via the developer-mode
  probe path, which skips printers with a known developer_mode value.

  Extract the existing force-reconnect logic into a reusable helper and
  call it from _verify_print_response on dispatch timeout. The next
  dispatch attempt then lands on a fresh MQTT session without a reboot.
2026-04-14 08:42:20 +02:00
maziggy 574b39aee5 change(workflow): default Plate-Clear Confirmation to off on fresh installs
New users repeatedly reported queued prints "not starting" because the
  confirmation prompt was waiting on an ack they didn't know existed.
  Flip the default in the settings schema and in the frontend fallbacks
  so a missing/unset value reads as disabled. Existing installs keep
  their saved preference.
2026-04-14 08:27:00 +02:00
maziggy 9cc7efdf5f fix(printers): stop SD card badge flapping on H2D
Parse `sdcard` from `home_flag` bits 8-9 (HAS_SDCARD_NORMAL /
  HAS_SDCARD_ABNORMAL) when available and fall back to a type-tolerant
  truthy check on the top-level `sdcard` field. Firmware ships that
  field inconsistently (bool, int `1`, or string `"HAS_SDCARD_NORMAL"`),
  so the previous `is True` identity check flipped the badge to red on
  every report that carried a non-bool value.
2026-04-14 08:17:34 +02:00
maziggy 53a70e37d9 fix: unbreak CSP for sidebar iframes, service worker, and Google Fonts
The strict CSP added in 0.2.3b4 blocked three things at once:
  external sidebar-link iframes (no frame-src declared, so they fell
  back to default-src 'self'), the inline service-worker registration
  script in index.html, and the Google Fonts @import used for Inter.

  - Add `frame-src 'self' https:` so user-configured HTTPS iframe
    targets load; frame-ancestors 'none' still prevents Bambuddy
    itself from being framed cross-origin.
  - Move the inline SW-registration script into public/sw-register.js
    so `script-src 'self'` covers it without 'unsafe-inline' or
    per-build hashes.
  - Allow fonts.googleapis.com in style-src and fonts.gstatic.com in
    font-src so the Inter webfont loads.
2026-04-13 16:38:17 +02:00
maziggy 180db8e8ad Expand Settings search with module-level registry
Search field at the top of Settings now finds Sidebar Links,
  Spoolman, Spool/Color Catalog, all four Failure Detection
  sections, Email auth (Advanced + SMTP test), 2FA (TOTP, Email
  OTP, Linked Accounts), SSO/OIDC, LDAP Server Config, and the
  four Backup sub-cards (GitHub, History, Local, Scheduled).

  Replaces the hardcoded searchIndex array in SettingsPage.tsx
  with a module-level registry (frontend/src/lib/settingsSearch.ts).
  Each settings card calls registerSettingsSearch(...) at module
  scope, so adding a new card means adding one colocated line
  instead of editing a distant central array. Anchor ids were
  added to the corresponding Card elements in the affected
  components so scrollIntoView lands on the right section.
2026-04-13 16:13:04 +02:00
maziggy 487d11fa94 Housekeeping 2026-04-13 15:28:19 +02:00
maziggy e33e63696a Hide SD card and door badges from printer card if printer is offline 2026-04-13 14:25:54 +02:00
maziggy 2f0cca186b Post work PR #933 2026-04-13 13:53:30 +02:00
Sn0rrii ba1c97c808 feat: Two-Factor Authentication (TOTP, Email OTP) and OIDC/SSO – full implementation with admin UI (#933)
feat: Two-Factor Authentication (TOTP, Email OTP) and OIDC/SSO – full implementation with admin UI (#933)
2026-04-13 13:24:28 +02:00
maziggy 8af0966e68 feat(printers): airduct mode + status badges + force refresh on printer card
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).
2026-04-13 12:50:17 +02:00
maziggy eec7793955 feat(obico): AI print-failure detection via self-hosted Obico ML API (#172)
Adds a Failure Detection tab under Settings that wires Bambuddy to a
  self-hosted Obico ml_api container — no cloud, no account, no WebSocket.
  While a print is running, the detection service periodically hands the
  printer's camera snapshot URL to the ML API and smooths scores over
  time (30-frame warmup + EWM, alpha=2/13, short/long rolling means) so
  one noisy frame can't trigger an action. When the smoothed score
  crosses HIGH, the configured action fires exactly once per print:
  notify, pause, or pause-and-cut-power (via linked smart plugs).

  - Backend: new obico_detection + obico_smoothing + obico_actions
    services, /obico/status and /obico/test-connection routes
    (SETTINGS_READ / SETTINGS_UPDATE), six obico_* AppSettings fields
    with validators for sensitivity/action/enabled_printers.
  - Frontend: FailureDetectionSettings component (enable, ML URL + test,
    sensitivity, action, poll interval, per-printer monitor list, live
    status + detection history), new sidebar tab with service-active
    bullet, toast on save.
  - Tests: 17 detection unit tests + 15 smoothing unit tests + 4
    frontend component tests.
  - Docs: README bullet, CHANGELOG entry, wiki page under Analytics,
    website features.html entry.
2026-04-13 09:54:26 +02:00
maziggy f1483f525d fix: relax X-Frame-Options to SAMEORIGIN so same-origin iframes load
The security-headers middleware added in 0.2.3b4 set X-Frame-Options: DENY
  on every response, which blocked the Spoolman page iframe when Spoolman
  was served from the same host as Bambuddy via a reverse proxy. Relaxed
  to SAMEORIGIN — same-origin embedding works again, cross-origin
  clickjacking protection is preserved.
2026-04-13 09:18:35 +02:00
maziggy de7fff0be4 fix: persist plate-clear gate so Auto Off power cycles can't bypass the queue confirmation (#961)
With Auto Off enabled and another job queued, the smart plug cut power when a
  print finished and immediately re-powered the printer because the scheduler
  saw pending items. The printer booted fresh into IDLE and the next job
  auto-dispatched, bypassing the "Clear Plate & Start Next" confirmation.

  Root cause: the plate-clear gate lived only in PrinterManager._plate_cleared
  (in-memory set) and _is_printer_idle treated IDLE as unconditionally idle. On
  power cycle the in-memory flag was lost and the IDLE-on-boot state skipped
  the gate entirely.

  Fix:
  - Replace the in-memory flag with an awaiting_plate_clear column on the
    printers table, rehydrated into the PrinterManager at startup.
  - Set the flag in on_print_complete for completed/failed prints (not user
    cancellations); clear it on ack and on scheduler dispatch.
  - _is_printer_idle now short-circuits to not-idle whenever require_plate_clear
    is on and the flag is set, regardless of the currently reported state —
    so the gate holds through power cycles, Bambuddy restarts, and the printer
    booting back into IDLE.
  - /printers/{id}/clear-plate no longer requires the printer to report
    FINISH/FAILED; it accepts the ack whenever the flag is raised.
  - Frontend widgets (PrinterQueueWidget, Layout, BulkPrinterToolbar) gate on
    the flag rather than reported state.

  Tests: added regression tests for IDLE+awaiting=True (the #961 case) and
  full DB round-trip tests for the persistence layer.
2026-04-13 09:12:12 +02:00
maziggy 1204369dd2 Housekeeping 2026-04-13 08:28:22 +02:00
Dakota G f84e5ba173 feat: Shows vendor name in Spoolman Link Modal (#958)
* Add filament_vendor field to UnlinkedSpool model and populate from API response
* Add filament_vendor field to UnlinkedSpool interface
* Enhance LinkSpoolModal to include filament_vendor in search and display
2026-04-13 08:26:46 +02:00
maziggy 18dceb3c32 . 2026-04-12 15:10:40 +02:00
maziggy a58ab8b8db Updated .github/workflows/ci.yml 2026-04-12 15:05:28 +02:00
maziggy b37d8d6a9d Bumped version 2026-04-12 14:57:15 +02:00
maziggy 4b1f7d6882 Updated docker-compose.yml 2026-04-12 14:29:19 +02:00
maziggy 774a639e9a . 2026-04-12 14:16:17 +02:00
maziggy d1d416728b . 2026-04-12 12:26:44 +02:00
maziggy 9e4cae82b9 . 2026-04-12 11:16:43 +02:00
maziggy 749257e97a . 2026-04-12 11:12:05 +02:00
maziggy a25f200529 . 2026-04-12 11:09:59 +02:00
maziggy 13d759c7ed . 2026-04-12 11:08:26 +02:00
maziggy 969174cc81 . 2026-04-12 11:05:22 +02:00
maziggy 2d6ddc431b feat(spoolbuddy): auto-wake display on NFC tag scan or scale activity (#945)
Display now powers on via wlopm when the daemon detects an NFC tag or
  a significant weight change (>=50g, i.e. spool placed/removed) while
  the screen is blanked.  Minor scale fluctuations no longer wake the
  display or prevent blanking.  The daemon only re-blanks screens it woke
  itself — touch-based wake/blank stays with swayidle so the two don't
  conflict.  Daemon discovers the Wayland session from the shared runtime
  dir since it runs as a systemd service outside the compositor.
2026-04-12 10:56:44 +02:00
maziggy 25203b3491 feat(spoolbuddy): auto-wake display on NFC tag scan or scale activity (#945)
Display now powers on via wlopm when the daemon detects an NFC tag or
  a significant weight change (>=50g, i.e. spool placed/removed) while
  the screen is blanked.  Minor scale fluctuations no longer wake the
  display or prevent blanking.  The daemon only re-blanks screens it woke
  itself — touch-based wake/blank stays with swayidle so the two don't
  conflict.  Daemon discovers the Wayland session from the shared runtime
  dir since it runs as a systemd service outside the compositor.
2026-04-12 10:51:11 +02:00
maziggy f135605908 feat(spoolbuddy): auto-wake display on NFC tag scan or scale activity (#945)
Display now powers on via wlopm when the daemon detects an NFC tag or
  weight change while the screen is blanked.  The daemon only re-blanks
  screens it woke itself — touch-based wake/blank stays with swayidle so
  the two don't conflict.  Daemon discovers the Wayland session from the
  shared runtime dir since it runs as a systemd service outside the
  compositor.
2026-04-12 10:47:04 +02:00
maziggy ca1b0a2cd5 feat(spoolbuddy): auto-wake display on NFC tag scan or scale activity (#945)
Display now powers on via wlopm when the daemon detects an NFC tag or
  weight change while the screen is blanked.  Daemon discovers the Wayland
  session from the shared runtime dir and coexists with swayidle which
  continues to handle touch-based wake independently.
2026-04-12 10:41:57 +02:00
maziggy df311dcb29 Updated README.md 2026-04-11 15:49:22 +02:00